# HG changeset patch # User Chris Cannam # Date 1410251693 -3600 # Node ID afce8026aaeb5687a1b138a9f01fccf9cd312273 # Parent 2e8063097240cb3650b6659913d4195e42c5301d# Parent d98d22a98252dcb7c4ffc417728959ad302b3fab Merge from branch "live" diff -r d98d22a98252 -r afce8026aaeb .gitignore --- a/.gitignore Wed May 07 14:15:02 2014 +0100 +++ b/.gitignore Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,7 @@ /.project /.loadpath +/.powrc +/.rvmrc /config/additional_environment.rb /config/configuration.yml /config/database.yml @@ -15,8 +17,14 @@ /lib/redmine/scm/adapters/mercurial/redminehelper.pyo /log/*.log* /log/mongrel_debug +/plugins/* +!/plugins/README /public/dispatch.* /public/plugin_assets +/public/themes/* +!/public/themes/alternate +!/public/themes/classic +!/public/themes/README /tmp/* /tmp/cache/* /tmp/pdf/* diff -r d98d22a98252 -r afce8026aaeb .hgignore --- a/.hgignore Wed May 07 14:15:02 2014 +0100 +++ b/.hgignore Tue Sep 09 09:34:53 2014 +0100 @@ -2,6 +2,8 @@ .project .loadpath +.powrc +.rvmrc config/additional_environment.rb config/configuration.yml config/database.yml diff -r d98d22a98252 -r afce8026aaeb .svn/entries --- a/.svn/entries Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -12 diff -r d98d22a98252 -r afce8026aaeb .svn/format --- a/.svn/format Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -12 diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/00205ce9aaa0cc98a4089319a62880b29732ab31.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/00205ce9aaa0cc98a4089319a62880b29732ab31.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,4349 @@ +#============================================================+ +# File name : tcpdf.rb +# Begin : 2002-08-03 +# Last Update : 2007-03-20 +# Author : Nicola Asuni +# Version : 1.53.0.TC031 +# License : GNU LGPL (http://www.gnu.org/copyleft/lesser.html) +# +# Description : This is a Ruby class for generating PDF files +# on-the-fly without requiring external +# extensions. +# +# IMPORTANT: +# This class is an extension and improvement of the Public Domain +# FPDF class by Olivier Plathey (http://www.fpdf.org). +# +# Main changes by Nicola Asuni: +# Ruby porting; +# UTF-8 Unicode support; +# code refactoring; +# source code clean up; +# code style and formatting; +# source code documentation using phpDocumentor (www.phpdoc.org); +# All ISO page formats were included; +# image scale factor; +# includes methods to parse and printsome XHTML code, supporting the following elements: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small; +# includes a method to print various barcode formats using an improved version of "Generic Barcode Render Class" by Karim Mribti (http://www.mribti.com/barcode/) (require GD library: http://www.boutell.com/gd/); +# defines standard Header() and Footer() methods. +# +# Ported to Ruby by Ed Moss 2007-08-06 +# +#============================================================+ + +require 'tempfile' +require 'core/rmagick' + +# +# TCPDF Class. +# @package com.tecnick.tcpdf +# + +PDF_PRODUCER = 'TCPDF via RFPDF 1.53.0.TC031 (http://tcpdf.sourceforge.net)' + +module TCPDFFontDescriptor + @@descriptors = { 'freesans' => {} } + @@font_name = 'freesans' + + def self.font(font_name) + @@descriptors[font_name.gsub(".rb", "")] + end + + def self.define(font_name = 'freesans') + @@descriptors[font_name] ||= {} + yield @@descriptors[font_name] + end +end + +# This is a Ruby class for generating PDF files on-the-fly without requiring external extensions.
+# This class is an extension and improvement of the FPDF class by Olivier Plathey (http://www.fpdf.org).
+# This version contains some changes: [porting to Ruby, support for UTF-8 Unicode, code style and formatting, php documentation (www.phpdoc.org), ISO page formats, minor improvements, image scale factor]
+# TCPDF project (http://tcpdf.sourceforge.net) is based on the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org).
+# To add your own TTF fonts please read /fonts/README.TXT +# @name TCPDF +# @package com.tecnick.tcpdf +# @@version 1.53.0.TC031 +# @author Nicola Asuni +# @link http://tcpdf.sourceforge.net +# @license http://www.gnu.org/copyleft/lesser.html LGPL +# +class TCPDF + include RFPDF + include Core::RFPDF + include RFPDF::Math + + def logger + Rails.logger + end + + @@version = "1.53.0.TC031" + @@fpdf_charwidths = {} + + cattr_accessor :k_cell_height_ratio + @@k_cell_height_ratio = 1.25 + + cattr_accessor :k_blank_image + @@k_blank_image = "" + + cattr_accessor :k_small_ratio + @@k_small_ratio = 2/3.0 + + cattr_accessor :k_path_cache + @@k_path_cache = Rails.root.join('tmp') + + cattr_accessor :k_path_url_cache + @@k_path_url_cache = Rails.root.join('tmp') + + attr_accessor :barcode + + attr_accessor :buffer + + attr_accessor :diffs + + attr_accessor :color_flag + + attr_accessor :default_table_columns + + attr_accessor :max_table_columns + + attr_accessor :default_font + + attr_accessor :draw_color + + attr_accessor :encoding + + attr_accessor :fill_color + + attr_accessor :fonts + + attr_accessor :font_family + + attr_accessor :font_files + + cattr_accessor :font_path + + attr_accessor :font_style + + attr_accessor :font_size_pt + + attr_accessor :header_width + + attr_accessor :header_logo + + attr_accessor :header_logo_width + + attr_accessor :header_title + + attr_accessor :header_string + + attr_accessor :images + + attr_accessor :img_scale + + attr_accessor :in_footer + + attr_accessor :is_unicode + + attr_accessor :lasth + + attr_accessor :links + + attr_accessor :list_ordered + + attr_accessor :list_count + + attr_accessor :li_spacer + + attr_accessor :n + + attr_accessor :offsets + + attr_accessor :orientation_changes + + attr_accessor :page + + attr_accessor :page_links + + attr_accessor :pages + + attr_accessor :pdf_version + + attr_accessor :prevfill_color + + attr_accessor :prevtext_color + + attr_accessor :print_header + + attr_accessor :print_footer + + attr_accessor :state + + attr_accessor :tableborder + + attr_accessor :tdbegin + + attr_accessor :tdwidth + + attr_accessor :tdheight + + attr_accessor :tdalign + + attr_accessor :tdfill + + attr_accessor :tempfontsize + + attr_accessor :text_color + + attr_accessor :underline + + attr_accessor :ws + + # + # This is the class constructor. + # It allows to set up the page format, the orientation and + # the measure unit used in all the methods (except for the font sizes). + # @since 1.0 + # @param string :orientation page orientation. Possible values are (case insensitive): + # @param string :unit User measure unit. Possible values are:
A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. + # @param mixed :format The format used for pages. It can be either one of the following values (case insensitive) or a custom format in the form of a two-element array containing the width and the height (expressed in the unit given by unit). + # @param boolean :unicode TRUE means that the input text is unicode (default = true) + # @param String :encoding charset encoding; default is UTF-8 + # + def initialize(orientation = 'P', unit = 'mm', format = 'A4', unicode = true, encoding = "UTF-8") + + # Set internal character encoding to ASCII# + #FIXME 2007-05-25 (EJM) Level=0 - + # if (respond_to?("mb_internal_encoding") and mb_internal_encoding()) + # @internal_encoding = mb_internal_encoding(); + # mb_internal_encoding("ASCII"); + # } + + #Some checks + dochecks(); + + #Initialization of properties + @barcode ||= false + @buffer ||= '' + @diffs ||= [] + @color_flag ||= false + @default_table_columns ||= 4 + @table_columns ||= 0 + @max_table_columns ||= [] + @tr_id ||= 0 + @max_td_page ||= [] + @max_td_y ||= [] + @t_columns ||= 0 + @default_font ||= "FreeSans" if unicode + @default_font ||= "Helvetica" + @draw_color ||= '0 G' + @encoding ||= "UTF-8" + @fill_color ||= '0 g' + @fonts ||= {} + @font_family ||= '' + @font_files ||= {} + @font_style ||= '' + @font_size ||= 12 + @font_size_pt ||= 12 + @header_width ||= 0 + @header_logo ||= "" + @header_logo_width ||= 30 + @header_title ||= "" + @header_string ||= "" + @images ||= {} + @img_scale ||= 1 + @in_footer ||= false + @is_unicode = unicode + @lasth ||= 0 + @links ||= [] + @list_ordered ||= [] + @list_count ||= [] + @li_spacer ||= "" + @li_count ||= 0 + @spacer ||= "" + @quote_count ||= 0 + @prevquote_count ||= 0 + @quote_top ||= [] + @quote_page ||= [] + @n ||= 2 + @offsets ||= [] + @orientation_changes ||= [] + @page ||= 0 + @page_links ||= {} + @pages ||= [] + @pdf_version ||= "1.3" + @prevfill_color ||= [255,255,255] + @prevtext_color ||= [0,0,0] + @print_header ||= false + @print_footer ||= false + @state ||= 0 + @tableborder ||= 0 + @tdbegin ||= false + @tdtext ||= '' + @tdwidth ||= 0 + @tdheight ||= 0 + @tdalign ||= "L" + @tdfill ||= 0 + @tempfontsize ||= 10 + @text_color ||= '0 g' + @underline ||= false + @deleted ||= false + @ws ||= 0 + + #Standard Unicode fonts + @core_fonts = { + 'courier'=>'Courier', + 'courierB'=>'Courier-Bold', + 'courierI'=>'Courier-Oblique', + 'courierBI'=>'Courier-BoldOblique', + 'helvetica'=>'Helvetica', + 'helveticaB'=>'Helvetica-Bold', + 'helveticaI'=>'Helvetica-Oblique', + 'helveticaBI'=>'Helvetica-BoldOblique', + 'times'=>'Times-Roman', + 'timesB'=>'Times-Bold', + 'timesI'=>'Times-Italic', + 'timesBI'=>'Times-BoldItalic', + 'symbol'=>'Symbol', + 'zapfdingbats'=>'ZapfDingbats'} + + #Scale factor + case unit.downcase + when 'pt' ; @k=1 + when 'mm' ; @k=72/25.4 + when 'cm' ; @k=72/2.54 + when 'in' ; @k=72 + else Error("Incorrect unit: #{unit}") + end + + #Page format + if format.is_a?(String) + # Page formats (45 standard ISO paper formats and 4 american common formats). + # Paper cordinates are calculated in this way: (inches# 72) where (1 inch = 2.54 cm) + case (format.upcase) + when '4A0' ; format = [4767.87,6740.79] + when '2A0' ; format = [3370.39,4767.87] + when 'A0' ; format = [2383.94,3370.39] + when 'A1' ; format = [1683.78,2383.94] + when 'A2' ; format = [1190.55,1683.78] + when 'A3' ; format = [841.89,1190.55] + when 'A4' ; format = [595.28,841.89] # ; default + when 'A5' ; format = [419.53,595.28] + when 'A6' ; format = [297.64,419.53] + when 'A7' ; format = [209.76,297.64] + when 'A8' ; format = [147.40,209.76] + when 'A9' ; format = [104.88,147.40] + when 'A10' ; format = [73.70,104.88] + when 'B0' ; format = [2834.65,4008.19] + when 'B1' ; format = [2004.09,2834.65] + when 'B2' ; format = [1417.32,2004.09] + when 'B3' ; format = [1000.63,1417.32] + when 'B4' ; format = [708.66,1000.63] + when 'B5' ; format = [498.90,708.66] + when 'B6' ; format = [354.33,498.90] + when 'B7' ; format = [249.45,354.33] + when 'B8' ; format = [175.75,249.45] + when 'B9' ; format = [124.72,175.75] + when 'B10' ; format = [87.87,124.72] + when 'C0' ; format = [2599.37,3676.54] + when 'C1' ; format = [1836.85,2599.37] + when 'C2' ; format = [1298.27,1836.85] + when 'C3' ; format = [918.43,1298.27] + when 'C4' ; format = [649.13,918.43] + when 'C5' ; format = [459.21,649.13] + when 'C6' ; format = [323.15,459.21] + when 'C7' ; format = [229.61,323.15] + when 'C8' ; format = [161.57,229.61] + when 'C9' ; format = [113.39,161.57] + when 'C10' ; format = [79.37,113.39] + when 'RA0' ; format = [2437.80,3458.27] + when 'RA1' ; format = [1729.13,2437.80] + when 'RA2' ; format = [1218.90,1729.13] + when 'RA3' ; format = [864.57,1218.90] + when 'RA4' ; format = [609.45,864.57] + when 'SRA0' ; format = [2551.18,3628.35] + when 'SRA1' ; format = [1814.17,2551.18] + when 'SRA2' ; format = [1275.59,1814.17] + when 'SRA3' ; format = [907.09,1275.59] + when 'SRA4' ; format = [637.80,907.09] + when 'LETTER' ; format = [612.00,792.00] + when 'LEGAL' ; format = [612.00,1008.00] + when 'EXECUTIVE' ; format = [521.86,756.00] + when 'FOLIO' ; format = [612.00,936.00] + #else then Error("Unknown page format: #{format}" + end + @fw_pt = format[0] + @fh_pt = format[1] + else + @fw_pt = format[0]*@k + @fh_pt = format[1]*@k + end + + @fw = @fw_pt/@k + @fh = @fh_pt/@k + + #Page orientation + orientation = orientation.downcase + if orientation == 'p' or orientation == 'portrait' + @def_orientation = 'P' + @w_pt = @fw_pt + @h_pt = @fh_pt + elsif orientation == 'l' or orientation == 'landscape' + @def_orientation = 'L' + @w_pt = @fh_pt + @h_pt = @fw_pt + else + Error("Incorrect orientation: #{orientation}") + end + + @fw = @w_pt/@k + @fh = @h_pt/@k + + @cur_orientation = @def_orientation + @w = @w_pt/@k + @h = @h_pt/@k + #Page margins (1 cm) + margin = 28.35/@k + SetMargins(margin, margin) + #Interior cell margin (1 mm) + @c_margin = margin / 10 + #Line width (0.2 mm) + @line_width = 0.567 / @k + #Automatic page break + SetAutoPageBreak(true, 2 * margin) + #Full width display mode + SetDisplayMode('fullwidth') + #Compression + SetCompression(true) + #Set default PDF version number + @pdf_version = "1.3" + + @encoding = encoding + @b = 0 + @i = 0 + @u = 0 + @href = '' + @fontlist = ["arial", "times", "courier", "helvetica", "symbol"] + @issetfont = false + @issetcolor = false + + SetFillColor(200, 200, 200, true) + SetTextColor(0, 0, 0, true) + end + + # + # Set the image scale. + # @param float :scale image scale. + # @author Nicola Asuni + # @since 1.5.2 + # + def SetImageScale(scale) + @img_scale = scale; + end + alias_method :set_image_scale, :SetImageScale + + # + # Returns the image scale. + # @return float image scale. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetImageScale() + return @img_scale; + end + alias_method :get_image_scale, :GetImageScale + + # + # Returns the page width in units. + # @return int page width. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetPageWidth() + return @w; + end + alias_method :get_page_width, :GetPageWidth + + # + # Returns the page height in units. + # @return int page height. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetPageHeight() + return @h; + end + alias_method :get_page_height, :GetPageHeight + + # + # Returns the page break margin. + # @return int page break margin. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetBreakMargin() + return @b_margin; + end + alias_method :get_break_margin, :GetBreakMargin + + # + # Returns the scale factor (number of points in user unit). + # @return int scale factor. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetScaleFactor() + return @k; + end + alias_method :get_scale_factor, :GetScaleFactor + + # + # Defines the left, top and right margins. By default, they equal 1 cm. Call this method to change them. + # @param float :left Left margin. + # @param float :top Top margin. + # @param float :right Right margin. Default value is the left one. + # @since 1.0 + # @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak() + # + def SetMargins(left, top, right=-1) + #Set left, top and right margins + @l_margin = left + @t_margin = top + if (right == -1) + right = left + end + @r_margin = right + end + alias_method :set_margins, :SetMargins + + # + # Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin. + # @param float :margin The margin. + # @since 1.4 + # @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() + # + def SetLeftMargin(margin) + #Set left margin + @l_margin = margin + if ((@page>0) and (@x < margin)) + @x = margin + end + end + alias_method :set_left_margin, :SetLeftMargin + + # + # Defines the top margin. The method can be called before creating the first page. + # @param float :margin The margin. + # @since 1.5 + # @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() + # + def SetTopMargin(margin) + #Set top margin + @t_margin = margin + end + alias_method :set_top_margin, :SetTopMargin + + # + # Defines the right margin. The method can be called before creating the first page. + # @param float :margin The margin. + # @since 1.5 + # @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins() + # + def SetRightMargin(margin) + #Set right margin + @r_margin = margin + end + alias_method :set_right_margin, :SetRightMargin + + # + # Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm. + # @param boolean :auto Boolean indicating if mode should be on or off. + # @param float :margin Distance from the bottom of the page. + # @since 1.0 + # @see Cell(), MultiCell(), AcceptPageBreak() + # + def SetAutoPageBreak(auto, margin=0) + #Set auto page break mode and triggering margin + @auto_page_break = auto + @b_margin = margin + @page_break_trigger = @h - margin + end + alias_method :set_auto_page_break, :SetAutoPageBreak + + # + # Defines the way the document is to be displayed by the viewer. The zoom level can be set: pages can be displayed entirely on screen, occupy the full width of the window, use real size, be scaled by a specific zooming factor or use viewer default (configured in the Preferences menu of Acrobat). The page layout can be specified too: single at once, continuous display, two columns or viewer default. By default, documents use the full width mode with continuous display. + # @param mixed :zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. + # @param string :layout The page layout. Possible values are: + # @since 1.2 + # + def SetDisplayMode(zoom, layout = 'continuous') + #Set display mode in viewer + if (zoom == 'fullpage' or zoom == 'fullwidth' or zoom == 'real' or zoom == 'default' or !zoom.is_a?(String)) + @zoom_mode = zoom + else + Error("Incorrect zoom display mode: #{zoom}") + end + if (layout == 'single' or layout == 'continuous' or layout == 'two' or layout == 'default') + @layout_mode = layout + else + Error("Incorrect layout display mode: #{layout}") + end + end + alias_method :set_display_mode, :SetDisplayMode + + # + # Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default. + # Note: the Zlib extension is required for this feature. If not present, compression will be turned off. + # @param boolean :compress Boolean indicating if compression must be enabled. + # @since 1.4 + # + def SetCompression(compress) + #Set page compression + if (respond_to?('gzcompress')) + @compress = compress + else + @compress = false + end + end + alias_method :set_compression, :SetCompression + + # + # Defines the title of the document. + # @param string :title The title. + # @since 1.2 + # @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject() + # + def SetTitle(title) + #Title of document + @title = title + end + alias_method :set_title, :SetTitle + + # + # Defines the subject of the document. + # @param string :subject The subject. + # @since 1.2 + # @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle() + # + def SetSubject(subject) + #Subject of document + @subject = subject + end + alias_method :set_subject, :SetSubject + + # + # Defines the author of the document. + # @param string :author The name of the author. + # @since 1.2 + # @see SetCreator(), SetKeywords(), SetSubject(), SetTitle() + # + def SetAuthor(author) + #Author of document + @author = author + end + alias_method :set_author, :SetAuthor + + # + # Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'. + # @param string :keywords The list of keywords. + # @since 1.2 + # @see SetAuthor(), SetCreator(), SetSubject(), SetTitle() + # + def SetKeywords(keywords) + #Keywords of document + @keywords = keywords + end + alias_method :set_keywords, :SetKeywords + + # + # Defines the creator of the document. This is typically the name of the application that generates the PDF. + # @param string :creator The name of the creator. + # @since 1.2 + # @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle() + # + def SetCreator(creator) + #Creator of document + @creator = creator + end + alias_method :set_creator, :SetCreator + + # + # Defines an alias for the total number of pages. It will be substituted as the document is closed.
+ # Example:
+ #
+	# class PDF extends TCPDF {
+	# 	def Footer()
+	# 		#Go to 1.5 cm from bottom
+	# 		SetY(-15);
+	# 		#Select Arial italic 8
+	# 		SetFont('Arial','I',8);
+	# 		#Print current and total page numbers
+	# 		Cell(0,10,'Page '.PageNo().'/{nb}',0,0,'C');
+	# 	end
+	# }
+	# :pdf=new PDF();
+	# :pdf->alias_nb_pages();
+	# 
+ # @param string :alias The alias. Default valuenb}. + # @since 1.4 + # @see PageNo(), Footer() + # + def AliasNbPages(alias_nb ='{nb}') + #Define an alias for total number of pages + @alias_nb_pages = escapetext(alias_nb) + end + alias_method :alias_nb_pages, :AliasNbPages + + # + # This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid. + # 2004-06-11 :: Nicola Asuni : changed bold tag with strong + # @param string :msg The error message + # @since 1.0 + # + def Error(msg) + #Fatal error + raise ("TCPDF error: #{msg}") + end + alias_method :error, :Error + + # + # This method begins the generation of the PDF document. It is not necessary to call it explicitly because AddPage() does it automatically. + # Note: no page is created by this method + # @since 1.0 + # @see AddPage(), Close() + # + def Open() + #Begin document + @state = 1 + end + # alias_method :open, :Open + + # + # Terminates the PDF document. It is not necessary to call this method explicitly because Output() does it automatically. If the document contains no page, AddPage() is called to prevent from getting an invalid document. + # @since 1.0 + # @see Open(), Output() + # + def Close() + #Terminate document + if (@state==3) + return; + end + if (@page==0) + AddPage(); + end + #Page footer + @in_footer=true; + Footer(); + @in_footer=false; + #Close page + endpage(); + #Close document + enddoc(); + end + # alias_method :close, :Close + + # + # Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer. Then the page is added, the current position set to the top-left corner according to the left and top margins, and Header() is called to display the header. + # The font which was set before calling is automatically restored. There is no need to call SetFont() again if you want to continue with the same font. The same is true for colors and line width. + # The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards. + # @param string :orientation Page orientation. Possible values are (case insensitive): The default value is the one passed to the constructor. + # @since 1.0 + # @see TCPDF(), Header(), Footer(), SetMargins() + # + def AddPage(orientation='') + #Start a new page + if (@state==0) + Open(); + end + family=@font_family; + style=@font_style + (@underline ? 'U' : '') + (@deleted ? 'D' : ''); + size=@font_size_pt; + lw=@line_width; + dc=@draw_color; + fc=@fill_color; + tc=@text_color; + cf=@color_flag; + if (@page>0) + #Page footer + @in_footer=true; + Footer(); + @in_footer=false; + #Close page + endpage(); + end + #Start new page + beginpage(orientation); + #Set line cap style to square + out('2 J'); + #Set line width + @line_width = lw; + out(sprintf('%.2f w', lw*@k)); + #Set font + if (family) + SetFont(family, style, size); + end + #Set colors + @draw_color = dc; + if (dc!='0 G') + out(dc); + end + @fill_color = fc; + if (fc!='0 g') + out(fc); + end + @text_color = tc; + @color_flag = cf; + #Page header + Header(); + #Restore line width + if (@line_width != lw) + @line_width = lw; + out(sprintf('%.2f w', lw*@k)); + end + #Restore font + if (family) + SetFont(family, style, size); + end + #Restore colors + if (@draw_color != dc) + @draw_color = dc; + out(dc); + end + if (@fill_color != fc) + @fill_color = fc; + out(fc); + end + @text_color = tc; + @color_flag = cf; + end + alias_method :add_page, :AddPage + + # + # Rotate object. + # @param float :angle angle in degrees for counter-clockwise rotation + # @param int :x abscissa of the rotation center. Default is current x position + # @param int :y ordinate of the rotation center. Default is current y position + # + def Rotate(angle, x="", y="") + + if (x == '') + x = @x; + end + + if (y == '') + y = @y; + end + + if (@rtl) + x = @w - x; + angle = -@angle; + end + + y = (@h - y) * @k; + x *= @k; + + # calculate elements of transformation matrix + tm = [] + tm[0] = ::Math::cos(deg2rad(angle)); + tm[1] = ::Math::sin(deg2rad(angle)); + tm[2] = -tm[1]; + tm[3] = tm[0]; + tm[4] = x + tm[1] * y - tm[0] * x; + tm[5] = y - tm[0] * y - tm[1] * x; + + # generate the transformation matrix + Transform(tm); + end + alias_method :rotate, :Rotate + + # + # Starts a 2D tranformation saving current graphic state. + # This function must be called before scaling, mirroring, translation, rotation and skewing. + # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior. + # + def StartTransform + out('q'); + end + alias_method :start_transform, :StartTransform + + # + # Stops a 2D tranformation restoring previous graphic state. + # This function must be called after scaling, mirroring, translation, rotation and skewing. + # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior. + # + def StopTransform + out('Q'); + end + alias_method :stop_transform, :StopTransform + + # + # Apply graphic transformations. + # @since 2.1.000 (2008-01-07) + # @see StartTransform(), StopTransform() + # + def Transform(tm) + x = out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', tm[0], tm[1], tm[2], tm[3], tm[4], tm[5])); + end + alias_method :transform, :Transform + + # + # Set header data. + # @param string :ln header image logo + # @param string :lw header image logo width in mm + # @param string :ht string to print as title on document header + # @param string :hs string to print on document header + # + def SetHeaderData(ln="", lw=0, ht="", hs="") + @header_logo = ln || "" + @header_logo_width = lw || 0 + @header_title = ht || "" + @header_string = hs || "" + end + alias_method :set_header_data, :SetHeaderData + + # + # Set header margin. + # (minimum distance between header and top page margin) + # @param int :hm distance in millimeters + # + def SetHeaderMargin(hm=10) + @header_margin = hm; + end + alias_method :set_header_margin, :SetHeaderMargin + + # + # Set footer margin. + # (minimum distance between footer and bottom page margin) + # @param int :fm distance in millimeters + # + def SetFooterMargin(fm=10) + @footer_margin = fm; + end + alias_method :set_footer_margin, :SetFooterMargin + + # + # Set a flag to print page header. + # @param boolean :val set to true to print the page header (default), false otherwise. + # + def SetPrintHeader(val=true) + @print_header = val; + end + alias_method :set_print_header, :SetPrintHeader + + # + # Set a flag to print page footer. + # @param boolean :value set to true to print the page footer (default), false otherwise. + # + def SetPrintFooter(val=true) + @print_footer = val; + end + alias_method :set_print_footer, :SetPrintFooter + + # + # This method is used to render the page header. + # It is automatically called by AddPage() and could be overwritten in your own inherited class. + # + def Header() + if (@print_header) + if (@original_l_margin.nil?) + @original_l_margin = @l_margin; + end + if (@original_r_margin.nil?) + @original_r_margin = @r_margin; + end + + #set current position + SetXY(@original_l_margin, @header_margin); + + if ((@header_logo) and (@header_logo != @@k_blank_image)) + Image(@header_logo, @original_l_margin, @header_margin, @header_logo_width); + else + @img_rb_y = GetY(); + end + + cell_height = ((@@k_cell_height_ratio * @header_font[2]) / @k).round(2) + + header_x = @original_l_margin + (@header_logo_width * 1.05); #set left margin for text data cell + + # header title + SetFont(@header_font[0], 'B', @header_font[2] + 1); + SetX(header_x); + Cell(@header_width, cell_height, @header_title, 0, 1, 'L'); + + # header string + SetFont(@header_font[0], @header_font[1], @header_font[2]); + SetX(header_x); + MultiCell(@header_width, cell_height, @header_string, 0, 'L', 0); + + # print an ending header line + if (@header_width) + #set style for cell border + SetLineWidth(0.3); + SetDrawColor(0, 0, 0); + SetY(1 + (@img_rb_y > GetY() ? @img_rb_y : GetY())); + SetX(@original_l_margin); + Cell(0, 0, '', 'T', 0, 'C'); + end + + #restore position + SetXY(@original_l_margin, @t_margin); + end + end + alias_method :header, :Header + + # + # This method is used to render the page footer. + # It is automatically called by AddPage() and could be overwritten in your own inherited class. + # + def Footer() + if (@print_footer) + + if (@original_l_margin.nil?) + @original_l_margin = @l_margin; + end + if (@original_r_margin.nil?) + @original_r_margin = @r_margin; + end + + #set font + SetFont(@footer_font[0], @footer_font[1] , @footer_font[2]); + #set style for cell border + line_width = 0.3; + SetLineWidth(line_width); + SetDrawColor(0, 0, 0); + + footer_height = ((@@k_cell_height_ratio * @footer_font[2]) / @k).round; #footer height, was , 2) + #get footer y position + footer_y = @h - @footer_margin - footer_height; + #set current position + SetXY(@original_l_margin, footer_y); + + #print document barcode + if (@barcode) + Ln(); + barcode_width = ((@w - @original_l_margin - @original_r_margin)).round; #max width + writeBarcode(@original_l_margin, footer_y + line_width, barcode_width, footer_height - line_width, "C128B", false, false, 2, @barcode); + end + + SetXY(@original_l_margin, footer_y); + + #Print page number + Cell(0, footer_height, @l['w_page'] + " " + PageNo().to_s + ' / {nb}', 'T', 0, 'R'); + end + end + alias_method :footer, :Footer + + # + # Returns the current page number. + # @return int page number + # @since 1.0 + # @see alias_nb_pages() + # + def PageNo() + #Get current page number + return @page; + end + alias_method :page_no, :PageNo + + # + # Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. + # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255 + # @param int :g Green component (between 0 and 255) + # @param int :b Blue component (between 0 and 255) + # @since 1.3 + # @see SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell() + # + def SetDrawColor(r, g=-1, b=-1) + #Set color for all stroking operations + if ((r==0 and g==0 and b==0) or g==-1) + @draw_color=sprintf('%.3f G', r/255.0); + else + @draw_color=sprintf('%.3f %.3f %.3f RG', r/255.0, g/255.0, b/255.0); + end + if (@page>0) + out(@draw_color); + end + end + alias_method :set_draw_color, :SetDrawColor + + # + # Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. + # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255 + # @param int :g Green component (between 0 and 255) + # @param int :b Blue component (between 0 and 255) + # @param boolean :storeprev if true stores the RGB array on :prevfill_color variable. + # @since 1.3 + # @see SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell() + # + def SetFillColor(r, g=-1, b=-1, storeprev=false) + #Set color for all filling operations + if ((r==0 and g==0 and b==0) or g==-1) + @fill_color=sprintf('%.3f g', r/255.0); + else + @fill_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0); + end + @color_flag=(@fill_color!=@text_color); + if (@page>0) + out(@fill_color); + end + if (storeprev) + # store color as previous value + @prevfill_color = [r, g, b] + end + end + alias_method :set_fill_color, :SetFillColor + + # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors + def SetCmykFillColor(c, m, y, k, storeprev=false) + #Set color for all filling operations + @fill_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k); + @color_flag=(@fill_color!=@text_color); + if (storeprev) + # store color as previous value + @prevtext_color = [c, m, y, k] + end + if (@page>0) + out(@fill_color); + end + end + alias_method :set_cmyk_fill_color, :SetCmykFillColor + + # + # Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. + # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255 + # @param int :g Green component (between 0 and 255) + # @param int :b Blue component (between 0 and 255) + # @param boolean :storeprev if true stores the RGB array on :prevtext_color variable. + # @since 1.3 + # @see SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell() + # + def SetTextColor(r, g=-1, b=-1, storeprev=false) + #Set color for text + if ((r==0 and :g==0 and :b==0) or :g==-1) + @text_color=sprintf('%.3f g', r/255.0); + else + @text_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0); + end + @color_flag=(@fill_color!=@text_color); + if (storeprev) + # store color as previous value + @prevtext_color = [r, g, b] + end + end + alias_method :set_text_color, :SetTextColor + + # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors + def SetCmykTextColor(c, m, y, k, storeprev=false) + #Set color for text + @text_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k); + @color_flag=(@fill_color!=@text_color); + if (storeprev) + # store color as previous value + @prevtext_color = [c, m, y, k] + end + end + alias_method :set_cmyk_text_color, :SetCmykTextColor + + # + # Returns the length of a string in user unit. A font must be selected.
+ # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02] + # @param string :s The string whose length is to be computed + # @return int + # @since 1.2 + # + def GetStringWidth(s) + #Get width of a string in the current font + s = s.to_s; + cw = @current_font['cw'] + w = 0; + if (@is_unicode) + unicode = UTF8StringToArray(s); + unicode.each do |char| + if (!cw[char].nil?) + w += cw[char]; + # This should not happen. UTF8StringToArray should guarentee the array is ascii values. + # elsif (c!cw[char[0]].nil?) + # w += cw[char[0]]; + # elsif (!cw[char.chr].nil?) + # w += cw[char.chr]; + elsif (!@current_font['desc']['MissingWidth'].nil?) + w += @current_font['desc']['MissingWidth']; # set default size + else + w += 500; + end + end + else + s.each_byte do |c| + if cw[c.chr] + w += cw[c.chr]; + elsif cw[?c.chr] + w += cw[?c.chr] + end + end + end + return (w * @font_size / 1000.0); + end + alias_method :get_string_width, :GetStringWidth + + # + # Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page. + # @param float :width The width. + # @since 1.0 + # @see Line(), Rect(), Cell(), MultiCell() + # + def SetLineWidth(width) + #Set line width + @line_width = width; + if (@page>0) + out(sprintf('%.2f w', width*@k)); + end + end + alias_method :set_line_width, :SetLineWidth + + # + # Draws a line between two points. + # @param float :x1 Abscissa of first point + # @param float :y1 Ordinate of first point + # @param float :x2 Abscissa of second point + # @param float :y2 Ordinate of second point + # @since 1.0 + # @see SetLineWidth(), SetDrawColor() + # + def Line(x1, y1, x2, y2) + #Draw a line + out(sprintf('%.2f %.2f m %.2f %.2f l S', x1 * @k, (@h - y1) * @k, x2 * @k, (@h - y2) * @k)); + end + alias_method :line, :Line + + def Circle(mid_x, mid_y, radius, style='') + mid_y = (@h-mid_y)*@k + out(sprintf("q\n")) # postscript content in pdf + # init line type etc. with /GSD gs G g (grey) RG rg (RGB) w=line witdh etc. + out(sprintf("1 j\n")) # line join + # translate ("move") circle to mid_y, mid_y + out(sprintf("1 0 0 1 %f %f cm", mid_x, mid_y)) + kappa = 0.5522847498307933984022516322796 + # Quadrant 1 + x_s = 0.0 # 12 o'clock + y_s = 0.0 + radius + x_e = 0.0 + radius # 3 o'clock + y_e = 0.0 + out(sprintf("%f %f m\n", x_s, y_s)) # move to 12 o'clock + # cubic bezier control point 1, start height and kappa * radius to the right + bx_e1 = x_s + (radius * kappa) + by_e1 = y_s + # cubic bezier control point 2, end and kappa * radius above + bx_e2 = x_e + by_e2 = y_e + (radius * kappa) + # draw cubic bezier from current point to x_e/y_e with bx_e1/by_e1 and bx_e2/by_e2 as bezier control points + out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) + # Quadrant 2 + x_s = x_e + y_s = y_e # 3 o'clock + x_e = 0.0 + y_e = 0.0 - radius # 6 o'clock + bx_e1 = x_s # cubic bezier point 1 + by_e1 = y_s - (radius * kappa) + bx_e2 = x_e + (radius * kappa) # cubic bezier point 2 + by_e2 = y_e + out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) + # Quadrant 3 + x_s = x_e + y_s = y_e # 6 o'clock + x_e = 0.0 - radius + y_e = 0.0 # 9 o'clock + bx_e1 = x_s - (radius * kappa) # cubic bezier point 1 + by_e1 = y_s + bx_e2 = x_e # cubic bezier point 2 + by_e2 = y_e - (radius * kappa) + out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) + # Quadrant 4 + x_s = x_e + y_s = y_e # 9 o'clock + x_e = 0.0 + y_e = 0.0 + radius # 12 o'clock + bx_e1 = x_s # cubic bezier point 1 + by_e1 = y_s + (radius * kappa) + bx_e2 = x_e - (radius * kappa) # cubic bezier point 2 + by_e2 = y_e + out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) + if style=='F' + op='f' + elsif style=='FD' or style=='DF' + op='b' + else + op='s' + end + out(sprintf("#{op}\n")) # stroke circle, do not fill and close path + # for filling etc. b, b*, f, f* + out(sprintf("Q\n")) # finish postscript in PDF + end + alias_method :circle, :Circle + + # + # Outputs a rectangle. It can be drawn (border only), filled (with no border) or both. + # @param float :x Abscissa of upper-left corner + # @param float :y Ordinate of upper-left corner + # @param float :w Width + # @param float :h Height + # @param string :style Style of rendering. Possible values are: + # @since 1.0 + # @see SetLineWidth(), SetDrawColor(), SetFillColor() + # + def Rect(x, y, w, h, style='') + #Draw a rectangle + if (style=='F') + op='f'; + elsif (style=='FD' or style=='DF') + op='B'; + else + op='S'; + end + out(sprintf('%.2f %.2f %.2f %.2f re %s', x * @k, (@h - y) * @k, w * @k, -h * @k, op)); + end + alias_method :rect, :Rect + + # + # Imports a TrueType or Type1 font and makes it available. It is necessary to generate a font definition file first with the makefont.rb utility. The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by FPDF_FONTPATH if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated. + # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02]. + # Example:
+ #
+	# :pdf->AddFont('Comic','I');
+	# # is equivalent to:
+	# :pdf->AddFont('Comic','I','comici.rb');
+	# 
+ # @param string :family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font. + # @param string :style Font style. Possible values are (case insensitive): + # @param string :file The font definition file. By default, the name is built from the family and style, in lower case with no space. + # @since 1.5 + # @see SetFont() + # + def AddFont(family, style='', file='') + if (family.empty?) + return; + end + + #Add a TrueType or Type1 font + family = family.downcase + if ((!@is_unicode) and (family == 'arial')) + family = 'helvetica'; + end + + style=style.upcase + style=style.gsub('U',''); + style=style.gsub('D',''); + if (style == 'IB') + style = 'BI'; + end + + fontkey = family + style; + # check if the font has been already added + if !@fonts[fontkey].nil? + return; + end + + if (file=='') + file = family.gsub(' ', '') + style.downcase + '.rb'; + end + font_file_name = getfontpath(file) + if (font_file_name.nil?) + # try to load the basic file without styles + file = family.gsub(' ', '') + '.rb'; + font_file_name = getfontpath(file) + end + if font_file_name.nil? + Error("Could not find font #{file}.") + end + require(getfontpath(file)) + font_desc = TCPDFFontDescriptor.font(file) + + if (font_desc[:name].nil? and @@fpdf_charwidths.nil?) + Error('Could not include font definition file'); + end + + i = @fonts.length+1; + if (@is_unicode) + @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg], 'cMap' => font_desc[:cMap], 'registry' => font_desc[:registry]} + @@fpdf_charwidths[fontkey] = font_desc[:cw]; + else + @fonts[fontkey]={'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]} + @@fpdf_charwidths[fontkey] = font_desc[:cw]; + end + + if (!font_desc[:diff].nil? and (!font_desc[:diff].empty?)) + #Search existing encodings + d=0; + nb=@diffs.length; + 1.upto(nb) do |i| + if (@diffs[i]== font_desc[:diff]) + d = i; + break; + end + end + if (d==0) + d = nb+1; + @diffs[d] = font_desc[:diff]; + end + @fonts[fontkey]['diff'] = d; + end + if (font_desc[:file] and font_desc[:file].length > 0) + if (font_desc[:type] == "TrueType") or (font_desc[:type] == "TrueTypeUnicode") + @font_files[font_desc[:file]] = {'length1' => font_desc[:originalsize]} + else + @font_files[font_desc[:file]] = {'length1' => font_desc[:size1], 'length2' => font_desc[:size2]} + end + end + end + alias_method :add_font, :AddFont + + # + # Sets the font used to print character strings. It is mandatory to call this method at least once before printing text or the resulting document would not be valid. + # The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe). + # The method can be called before the first page is created and the font is retained from page to page. + # If you just wish to change the current font size, it is simpler to call SetFontSize(). + # Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:
+ # Example for the last case (note the trailing slash):
+ #
+	# define('FPDF_FONTPATH','/home/www/font/');
+	# require('tcpdf.rb');
+	#
+	# #Times regular 12
+	# :pdf->SetFont('Times');
+	# #Arial bold 14
+	# :pdf->SetFont('Arial','B',14);
+	# #Removes bold
+	# :pdf->SetFont('');
+	# #Times bold, italic and underlined 14
+	# :pdf->SetFont('Times','BIUD');
+	# 

+ # If the file corresponding to the requested font is not found, the error "Could not include font metric file" is generated. + # @param string :family Family font. It can be either a name defined by AddFont() or one of the standard families (case insensitive):It is also possible to pass an empty string. In that case, the current family is retained. + # @param string :style Font style. Possible values are (case insensitive):or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats + # @param float :size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12 + # @since 1.0 + # @see AddFont(), SetFontSize(), Cell(), MultiCell(), Write() + # + def SetFont(family, style='', size=0) + # save previous values + @prevfont_family = @font_family; + @prevfont_style = @font_style; + + family=family.downcase; + if (family=='') + family=@font_family; + end + if ((!@is_unicode) and (family == 'arial')) + family = 'helvetica'; + elsif ((family=="symbol") or (family=="zapfdingbats")) + style=''; + end + + style=style.upcase; + + if (style.include?('U')) + @underline=true; + style= style.gsub('U',''); + else + @underline=false; + end + if (style.include?('D')) + @deleted=true; + style= style.gsub('D',''); + else + @deleted=false; + end + if (style=='IB') + style='BI'; + end + if (size==0) + size=@font_size_pt; + end + + # try to add font (if not already added) + AddFont(family, style); + + #Test if font is already selected + if ((@font_family == family) and (@font_style == style) and (@font_size_pt == size)) + return; + end + + fontkey = family + style; + style = '' if (@fonts[fontkey].nil? and !@fonts[family].nil?) + + #Test if used for the first time + if (@fonts[fontkey].nil?) + #Check if one of the standard fonts + if (!@core_fonts[fontkey].nil?) + if @@fpdf_charwidths[fontkey].nil? + #Load metric file + file = family; + if ((family!='symbol') and (family!='zapfdingbats')) + file += style.downcase; + end + if (getfontpath(file + '.rb').nil?) + # try to load the basic file without styles + file = family; + fontkey = family; + end + require(getfontpath(file + '.rb')); + font_desc = TCPDFFontDescriptor.font(file) + if ((@is_unicode and ctg.nil?) or ((!@is_unicode) and (@@fpdf_charwidths[fontkey].nil?)) ) + Error("Could not include font metric file [" + fontkey + "]: " + getfontpath(file + ".rb")); + end + end + i = @fonts.length + 1; + + if (@is_unicode) + @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg]} + @@fpdf_charwidths[fontkey] = font_desc[:cw]; + else + @fonts[fontkey] = {'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]} + @@fpdf_charwidths[fontkey] = font_desc[:cw]; + end + else + Error('Undefined font: ' + family + ' ' + style); + end + end + #Select it + @font_family = family; + @font_style = style; + @font_size_pt = size; + @font_size = size / @k; + @current_font = @fonts[fontkey]; # was & may need deep copy? + if (@page>0) + out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt)); + end + end + alias_method :set_font, :SetFont + + # + # Defines the size of the current font. + # @param float :size The size (in points) + # @since 1.0 + # @see SetFont() + # + def SetFontSize(size) + #Set font size in points + if (@font_size_pt== size) + return; + end + @font_size_pt = size; + @font_size = size.to_f / @k; + if (@page > 0) + out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt)); + end + end + alias_method :set_font_size, :SetFontSize + + # + # Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.
+ # The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink(). + # @since 1.5 + # @see Cell(), Write(), Image(), Link(), SetLink() + # + def AddLink() + #Create a new internal link + n=@links.length+1; + @links[n]=[0,0]; + return n; + end + alias_method :add_link, :AddLink + + # + # Defines the page and position a link points to + # @param int :link The link identifier returned by AddLink() + # @param float :y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page) + # @param int :page Number of target page; -1 indicates the current page. This is the default value + # @since 1.5 + # @see AddLink() + # + def SetLink(link, y=0, page=-1) + #Set destination of internal link + if (y==-1) + y=@y; + end + if (page==-1) + page=@page; + end + @links[link] = [page, y] + end + alias_method :set_link, :SetLink + + # + # Puts a link on a rectangular area of the page. Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image. + # @param float :x Abscissa of the upper-left corner of the rectangle + # @param float :y Ordinate of the upper-left corner of the rectangle + # @param float :w Width of the rectangle + # @param float :h Height of the rectangle + # @param mixed :link URL or identifier returned by AddLink() + # @since 1.5 + # @see AddLink(), Cell(), Write(), Image() + # + def Link(x, y, w, h, link) + #Put a link on the page + @page_links ||= Array.new + @page_links[@page] ||= Array.new + @page_links[@page].push([x * @k, @h_pt - y * @k, w * @k, h*@k, link]); + end + alias_method :link, :Link + + # + # Prints a character string. The origin is on the left of the first charcter, on the baseline. This method allows to place a string precisely on the page, but it is usually easier to use Cell(), MultiCell() or Write() which are the standard methods to print text. + # @param float :x Abscissa of the origin + # @param float :y Ordinate of the origin + # @param string :txt String to print + # @since 1.0 + # @see SetFont(), SetTextColor(), Cell(), MultiCell(), Write() + # + def Text(x, y, txt) + #Output a string + s=sprintf('BT %.2f %.2f Td (%s) Tj ET', x * @k, (@h-y) * @k, escapetext(txt)); + if (@underline and (txt!='')) + s += ' ' + dolinetxt(x, y, txt); + end + if (@color_flag) + s='q ' + @text_color + ' ' + s + ' Q'; + end + out(s); + end + alias_method :text, :Text + + # + # Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value. The default implementation returns a value according to the mode selected by SetAutoPageBreak().
+ # This method is called automatically and should not be called directly by the application.
+ # Example:
+ # The method is overriden in an inherited class in order to obtain a 3 column layout:
+ #
+	# class PDF extends TCPDF {
+	# 	var :col=0;
+	#
+	# 	def SetCol(col)
+	# 		#Move position to a column
+	# 		@col = col;
+	# 		:x=10+:col*65;
+	# 		SetLeftMargin(x);
+	# 		SetX(x);
+	# 	end
+	#
+	# 	def AcceptPageBreak()
+	# 		if (@col<2)
+	# 			#Go to next column
+	# 			SetCol(@col+1);
+	# 			SetY(10);
+	# 			return false;
+	# 		end
+	# 		else
+	# 			#Go back to first column and issue page break
+	# 			SetCol(0);
+	# 			return true;
+	# 		end
+	# 	end
+	# }
+	#
+	# :pdf=new PDF();
+	# :pdf->Open();
+	# :pdf->AddPage();
+	# :pdf->SetFont('Arial','',12);
+	# for(i=1;:i<=300;:i++)
+	#     :pdf->Cell(0,5,"Line :i",0,1);
+	# }
+	# :pdf->Output();
+	# 
+ # @return boolean + # @since 1.4 + # @see SetAutoPageBreak() + # + def AcceptPageBreak() + #Accept automatic page break or not + return @auto_page_break; + end + alias_method :accept_page_break, :AcceptPageBreak + + def BreakThePage?(h) + if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak()) + true + else + false + end + end + alias_method :break_the_page?, :BreakThePage? + # + # Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.
+ # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. + # @param float :w Cell width. If 0, the cell extends up to the right margin. + # @param float :h Cell height. Default value: 0. + # @param string :txt String to print. Default value: empty string. + # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:or a string containing some or all of the following characters (in any order): + # @param int :ln Indicates where the current position should go after the call. Possible values are: + # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. + # @param string :align Allows to center or align the text. Possible values are: + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @param mixed :link URL or identifier returned by AddLink(). + # @since 1.0 + # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak() + # + def Cell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil) + #Output a cell + k=@k; + if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak()) + #Automatic page break + if @pages[@page+1].nil? + x = @x; + ws = @ws; + if (ws > 0) + @ws = 0; + out('0 Tw'); + end + AddPage(@cur_orientation); + @x = x; + if (ws > 0) + @ws = ws; + out(sprintf('%.3f Tw', ws * k)); + end + else + @page += 1; + @y=@t_margin; + end + end + + if (w == 0) + w = @w - @r_margin - @x; + end + s = ''; + if ((fill.to_i == 1) or (border.to_i == 1)) + if (fill.to_i == 1) + op = (border.to_i == 1) ? 'B' : 'f'; + else + op = 'S'; + end + s = sprintf('%.2f %.2f %.2f %.2f re %s ', @x * k, (@h - @y) * k, w * k, -h * k, op); + end + if (border.is_a?(String)) + x=@x; + y=@y; + if (border.include?('L')) + s<0) + # Go to next line + @y += h; + if (ln == 1) + @x = @l_margin; + end + else + @x += w; + end + end + alias_method :cell, :Cell + + # + # This method allows printing text with line breaks. They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.
+ # Text can be aligned, centered or justified. The cell block can be framed and the background painted. + # @param float :w Width of cells. If 0, they extend up to the right margin of the page. + # @param float :h Height of cells. + # @param string :txt String to print + # @param mixed :border Indicates if borders must be drawn around the cell block. The value can be either a number:or a string containing some or all of the following characters (in any order): + # @param string :align Allows to center or align the text. Possible values are: + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @param int :ln Indicates where the current position should go after the call. Possible values are: + # @since 1.3 + # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak() + # + def MultiCell(w, h, txt, border=0, align='J', fill=0, ln=1) + + # save current position + prevx = @x; + prevy = @y; + prevpage = @page; + + #Output text with automatic or explicit line breaks + + if (w == 0) + w = @w - @r_margin - @x; + end + + wmax = (w - 3 * @c_margin); + + s = txt.gsub("\r", ''); # remove carriage returns + nb = s.length; + + b=0; + if (border) + if (border==1) + border='LTRB'; + b='LRT'; + b2='LR'; + elsif border.is_a?(String) + b2=''; + if (border.include?('L')) + b2<<'L'; + end + if (border.include?('R')) + b2<<'R'; + end + b=(border.include?('T')) ? b2 + 'T' : b2; + end + end + sep=-1; + to_index=0; + from_j=0; + l=0; + ns=0; + nl=1; + + while to_index < nb + #Get next character + c = s[to_index]; + if c == "\n"[0] + #Explicit line break + if @ws > 0 + @ws = 0 + out('0 Tw') + end + #Ed Moss - change begin + end_i = to_index == 0 ? 0 : to_index - 1 + # Changed from s[from_j..to_index] to fix bug reported by Hans Allis. + from_j = to_index == 0 ? 1 : from_j + Cell(w, h, s[from_j..end_i], b, 2, align, fill) + #change end + to_index += 1 + sep=-1 + from_j=to_index + l=0 + ns=0 + nl += 1 + b = b2 if border and nl==2 + next + end + if (c == " "[0]) + sep = to_index; + ls = l; + ns += 1; + end + + l = GetStringWidth(s[from_j, to_index - from_j]); + + if (l > wmax) + #Automatic line break + if (sep == -1) + if (to_index == from_j) + to_index += 1; + end + if (@ws > 0) + @ws = 0; + out('0 Tw'); + end + Cell(w, h, s[from_j..to_index-1], b, 2, align, fill) # my FPDF version + else + if (align=='J' || align=='justify' || align=='justified') + @ws = (ns>1) ? (wmax-ls)/(ns-1) : 0; + out(sprintf('%.3f Tw', @ws * @k)); + end + Cell(w, h, s[from_j..sep], b, 2, align, fill); + to_index = sep + 1; + end + sep=-1; + from_j = to_index; + l=0; + ns=0; + nl += 1; + if (border and (nl==2)) + b = b2; + end + else + to_index += 1; + end + end + #Last chunk + if (@ws>0) + @ws=0; + out('0 Tw'); + end + if (border.is_a?(String) and border.include?('B')) + b<<'B'; + end + Cell(w, h, s[from_j, to_index-from_j], b, 2, align, fill); + + # move cursor to specified position + # since 2007-03-03 + if (ln == 1) + # go to the beginning of the next line + @x = @l_margin; + elsif (ln == 0) + # go to the top-right of the cell + @page = prevpage; + @y = prevy; + @x = prevx + w; + elsif (ln == 2) + # go to the bottom-left of the cell + @x = prevx; + end + end + alias_method :multi_cell, :MultiCell + + # + # This method prints text from the current position. When the right margin is reached (or the \n character is met) a line break occurs and text continues from the left margin. Upon method exit, the current position is left just at the end of the text. It is possible to put a link on the text.
+ # Example:
+ #
+	# #Begin with regular font
+	# :pdf->SetFont('Arial','',14);
+	# :pdf->Write(5,'Visit ');
+	# #Then put a blue underlined link
+	# :pdf->SetTextColor(0,0,255);
+	# :pdf->SetFont('','U');
+	# :pdf->Write(5,'www.tecnick.com','http://www.tecnick.com');
+	# 
+ # @param float :h Line height + # @param string :txt String to print + # @param mixed :link URL or identifier returned by AddLink() + # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0. + # @since 1.5 + # @see SetFont(), SetTextColor(), AddLink(), MultiCell(), SetAutoPageBreak() + # + def Write(h, txt, link=nil, fill=0) + + #Output text in flowing mode + w = @w - @r_margin - @x; + wmax = (w - 3 * @c_margin); + + s = txt.gsub("\r", ''); + nb = s.length; + + # handle single space character + if ((nb==1) and (s == " ")) + @x += GetStringWidth(s); + return; + end + + sep=-1; + i=0; + j=0; + l=0; + nl=1; + while(i wmax) + #Automatic line break (word wrapping) + if (sep == -1) + if (@x > @l_margin) + #Move to next line + @x = @l_margin; + @y += h; + w=@w - @r_margin - @x; + wmax=(w - 3 * @c_margin); + i += 1 + nl += 1 + next + end + if (i == j) + i += 1 + end + Cell(w, h, s[j, (i-1)], 0, 2, '', fill, link); + else + Cell(w, h, s[j, (sep-j)], 0, 2, '', fill, link); + i = sep+1; + end + sep = -1; + j = i; + l = 0; + if (nl==1) + @x = @l_margin; + w = @w - @r_margin - @x; + wmax = (w - 3 * @c_margin); + end + nl += 1; + else + i += 1; + end + end + #Last chunk + if (i != j) + Cell(GetStringWidth(s[j..i]), h, s[j..i], 0, 0, '', fill, link); + end + end + alias_method :write, :Write + + # + # Puts an image in the page. The upper-left corner must be given. The dimensions can be specified in different ways: + # Supported formats are JPEG and PNG. + # For JPEG, all flavors are allowed: + # For PNG, are allowed: + # but are not supported: + # If a transparent color is defined, it will be taken into account (but will be only interpreted by Acrobat 4 and above).
+ # The format can be specified explicitly or inferred from the file extension.
+ # It is possible to put a link on the image.
+ # Remark: if an image is used several times, only one copy will be embedded in the file.
+ # @param string :file Name of the file containing the image. + # @param float :x Abscissa of the upper-left corner. + # @param float :y Ordinate of the upper-left corner. + # @param float :w Width of the image in the page. If not specified or equal to zero, it is automatically calculated. + # @param float :h Height of the image in the page. If not specified or equal to zero, it is automatically calculated. + # @param string :type Image format. Possible values are (case insensitive): JPG, JPEG, PNG. If not specified, the type is inferred from the file extension. + # @param mixed :link URL or identifier returned by AddLink(). + # @since 1.1 + # @see AddLink() + # + def Image(file, x, y, w=0, h=0, type='', link=nil) + #Put an image on the page + if (@images[file].nil?) + #First use of image, get info + if (type == '') + pos = File::basename(file).rindex('.'); + if (pos.nil? or pos == 0) + Error('Image file has no extension and no type was specified: ' + file); + end + pos = file.rindex('.'); + type = file[pos+1..-1]; + end + type.downcase! + if (type == 'jpg' or type == 'jpeg') + info=parsejpg(file); + elsif (type == 'png') + info=parsepng(file); + elsif (type == 'gif') + tmpFile = imageToPNG(file); + info=parsepng(tmpFile.path); + tmpFile.delete + else + #Allow for additional formats + mtd='parse' + type; + if (!self.respond_to?(mtd)) + Error('Unsupported image type: ' + type); + end + info=send(mtd, file); + end + info['i']=@images.length+1; + @images[file] = info; + else + info=@images[file]; + end + #Automatic width and height calculation if needed + if ((w == 0) and (h == 0)) + rescale_x = (@w - @r_margin - x) / (info['w'] / (@img_scale * @k)) + rescale_x = 1 if rescale_x >= 1 + if (y + info['h'] * rescale_x / (@img_scale * @k) > @page_break_trigger and !@in_footer and AcceptPageBreak()) + #Automatic page break + if @pages[@page+1].nil? + ws = @ws; + if (ws > 0) + @ws = 0; + out('0 Tw'); + end + AddPage(@cur_orientation); + if (ws > 0) + @ws = ws; + out(sprintf('%.3f Tw', ws * @k)); + end + else + @page += 1; + end + y=@t_margin; + end + rescale_y = (@page_break_trigger - y) / (info['h'] / (@img_scale * @k)) + rescale_y = 1 if rescale_y >= 1 + rescale = rescale_y >= rescale_x ? rescale_x : rescale_y + + #Put image at 72 dpi + # 2004-06-14 :: Nicola Asuni, scale factor where added + w = info['w'] * rescale / (@img_scale * @k); + h = info['h'] * rescale / (@img_scale * @k); + elsif (w == 0) + w = h * info['w'] / info['h']; + elsif (h == 0) + h = w * info['h'] / info['w']; + end + out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', w*@k, h*@k, x*@k, (@h-(y+h))*@k, info['i'])); + if (link) + Link(x, y, w, h, link); + end + + #2002-07-31 - Nicola Asuni + # set right-bottom corner coordinates + @img_rb_x = x + w; + @img_rb_y = y + h; + end + alias_method :image, :Image + + # + # Performs a line break. The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter. + # @param float :h The height of the break. By default, the value equals the height of the last printed cell. + # @since 1.0 + # @see Cell() + # + def Ln(h='') + #Line feed; default value is last cell height + @x=@l_margin; + if (h.is_a?(String)) + @y += @lasth; + else + @y += h; + end + + k=@k; + if (@y > @page_break_trigger and !@in_footer and AcceptPageBreak()) + #Automatic page break + if @pages[@page+1].nil? + x = @x; + ws = @ws; + if (ws > 0) + @ws = 0; + out('0 Tw'); + end + AddPage(@cur_orientation); + @x = x; + if (ws > 0) + @ws = ws; + out(sprintf('%.3f Tw', ws * k)); + end + else + @page += 1; + @y=@t_margin; + end + end + + end + alias_method :ln, :Ln + + # + # Returns the abscissa of the current position. + # @return float + # @since 1.2 + # @see SetX(), GetY(), SetY() + # + def GetX() + #Get x position + return @x; + end + alias_method :get_x, :GetX + + # + # Defines the abscissa of the current position. If the passed value is negative, it is relative to the right of the page. + # @param float :x The value of the abscissa. + # @since 1.2 + # @see GetX(), GetY(), SetY(), SetXY() + # + def SetX(x) + #Set x position + if (x>=0) + @x = x; + else + @x=@w+x; + end + end + alias_method :set_x, :SetX + + # + # Returns the ordinate of the current position. + # @return float + # @since 1.0 + # @see SetY(), GetX(), SetX() + # + def GetY() + #Get y position + return @y; + end + alias_method :get_y, :GetY + + # + # Moves the current abscissa back to the left margin and sets the ordinate. If the passed value is negative, it is relative to the bottom of the page. + # @param float :y The value of the ordinate. + # @since 1.0 + # @see GetX(), GetY(), SetY(), SetXY() + # + def SetY(y) + #Set y position and reset x + @x=@l_margin; + if (y>=0) + @y = y; + else + @y=@h+y; + end + end + alias_method :set_y, :SetY + + # + # Defines the abscissa and ordinate of the current position. If the passed values are negative, they are relative respectively to the right and bottom of the page. + # @param float :x The value of the abscissa + # @param float :y The value of the ordinate + # @since 1.2 + # @see SetX(), SetY() + # + def SetXY(x, y) + #Set x and y positions + SetY(y); + SetX(x); + end + alias_method :set_xy, :SetXY + + # + # Send the document to a given destination: string, local file or browser. In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.
+ # The method first calls Close() if necessary to terminate the document. + # @param string :name The name of the file. If not given, the document will be sent to the browser (destination I) with the name doc.pdf. + # @param string :dest Destination where to send the document. It can take one of the following values:If the parameter is not specified but a name is given, destination is F. If no parameter is specified at all, destination is I.
+ # @since 1.0 + # @see Close() + # + def Output(name='', dest='') + #Output PDF to some destination + #Finish document if necessary + if (@state < 3) + Close(); + end + #Normalize parameters + # Boolean no longer supported + # if (dest.is_a?(Boolean)) + # dest = dest ? 'D' : 'F'; + # end + dest = dest.upcase + if (dest=='') + if (name=='') + name='doc.pdf'; + dest='I'; + else + dest='F'; + end + end + case (dest) + when 'I' + # This is PHP specific code + ##Send to standard output + # if (ob_get_contents()) + # Error('Some data has already been output, can\'t send PDF file'); + # end + # if (php_sapi_name()!='cli') + # #We send to a browser + # header('Content-Type: application/pdf'); + # if (headers_sent()) + # Error('Some data has already been output to browser, can\'t send PDF file'); + # end + # header('Content-Length: ' + @buffer.length); + # header('Content-disposition: inline; filename="' + name + '"'); + # end + return @buffer; + + when 'D' + # PHP specific + #Download file + # if (ob_get_contents()) + # Error('Some data has already been output, can\'t send PDF file'); + # end + # if (!_SERVER['HTTP_USER_AGENT'].nil? && SERVER['HTTP_USER_AGENT'].include?('MSIE')) + # header('Content-Type: application/force-download'); + # else + # header('Content-Type: application/octet-stream'); + # end + # if (headers_sent()) + # Error('Some data has already been output to browser, can\'t send PDF file'); + # end + # header('Content-Length: '+ @buffer.length); + # header('Content-disposition: attachment; filename="' + name + '"'); + return @buffer; + + when 'F' + open(name,'wb') do |f| + f.write(@buffer) + end + # PHP code + # #Save to local file + # f=open(name,'wb'); + # if (!f) + # Error('Unable to create output file: ' + name); + # end + # fwrite(f,@buffer,@buffer.length); + # f.close + + when 'S' + #Return as a string + return @buffer; + else + Error('Incorrect output destination: ' + dest); + + end + return ''; + end + alias_method :output, :Output + + # Protected methods + + # + # Check for locale-related bug + # @access protected + # + def dochecks() + #Check for locale-related bug + if (1.1==1) + Error('Don\'t alter the locale before including class file'); + end + #Check for decimal separator + if (sprintf('%.1f',1.0)!='1.0') + setlocale(LC_NUMERIC,'C'); + end + end + + # + # Return fonts path + # @access protected + # + def getfontpath(file) + # Is it in the @@font_path? + if @@font_path + fpath = File.join @@font_path, file + if File.exists?(fpath) + return fpath + end + end + # Is it in this plugin's font folder? + fpath = File.join File.dirname(__FILE__), 'fonts', file + if File.exists?(fpath) + return fpath + end + # Could not find it. + nil + end + + # + # Start document + # @access protected + # + def begindoc() + #Start document + @state=1; + out('%PDF-1.3'); + end + + # + # putpages + # @access protected + # + def putpages() + nb = @page; + if (@alias_nb_pages) + nbstr = UTF8ToUTF16BE(nb.to_s, false); + #Replace number of pages + 1.upto(nb) do |n| + @pages[n].gsub!(@alias_nb_pages, nbstr) + end + end + if @def_orientation=='P' + w_pt=@fw_pt + h_pt=@fh_pt + else + w_pt=@fh_pt + h_pt=@fw_pt + end + filter=(@compress) ? '/Filter /FlateDecode ' : '' + 1.upto(nb) do |n| + #Page + newobj + out('<>>>'; + else + l=@links[pl[4]]; + h=!@orientation_changes[l[0]].nil? ? w_pt : h_pt; + annots<>',1+2*l[0], h-l[1]*@k); + end + end + out(annots + ']'); + end + out('/Contents ' + (@n+1).to_s + ' 0 R>>'); + out('endobj'); + #Page content + p=(@compress) ? gzcompress(@pages[n]) : @pages[n]; + newobj(); + out('<<' + filter + '/Length '+ p.length.to_s + '>>'); + putstream(p); + out('endobj'); + end + #Pages root + @offsets[1]=@buffer.length; + out('1 0 obj'); + out('<>'); + out('endobj'); + end + + # + # Adds fonts + # putfonts + # @access protected + # + def putfonts() + nf=@n; + @diffs.each do |diff| + #Encodings + newobj(); + out('<>'); + out('endobj'); + end + @font_files.each do |file, info| + #Font file embedding + newobj(); + @font_files[file]['n']=@n; + font=''; + open(getfontpath(file),'rb') do |f| + font = f.read(); + end + compressed=(file[-2,2]=='.z'); + if (!compressed && !info['length2'].nil?) + header=((font[0][0])==128); + if (header) + #Strip first binary header + font=font[6]; + end + if header && (font[info['length1']][0] == 128) + #Strip second binary header + font=font[0..info['length1']] + font[info['length1']+6]; + end + end + out('<>'); + open(getfontpath(file),'rb') do |f| + putstream(font) + end + out('endobj'); + end + @fonts.each do |k, font| + #Font objects + @fonts[k]['n']=@n+1; + type = font['type']; + name = font['name']; + if (type=='core') + #Standard font + newobj(); + out('<>'); + out('endobj'); + elsif type == 'Type0' + putType0(font) + elsif (type=='Type1' || type=='TrueType') + #Additional Type1 or TrueType font + newobj(); + out('<>'); + out('endobj'); + #Widths + newobj(); + cw=font['cw']; # & + s='['; + 32.upto(255) do |i| + s << cw[i.chr] + ' '; + end + out(s + ']'); + out('endobj'); + #Descriptor + newobj(); + s='<>'); + out('endobj'); + else + #Allow for additional types + mtd='put' + type.downcase; + if (!self.respond_to?(mtd)) + Error('Unsupported font type: ' + type) + else + self.send(mtd,font) + end + end + end + end + + def putType0(font) + #Type0 + newobj(); + out('<>') + out('endobj') + #CIDFont + newobj() + out('<>') + out('/FontDescriptor '+(@n+1).to_s+' 0 R') + w='/W [1 [' + font['cw'].keys.sort.each {|key| + w+=font['cw'][key].to_s + " " +# ActionController::Base::logger.debug key.to_s +# ActionController::Base::logger.debug font['cw'][key].to_s + } + out(w+'] 231 325 500 631 [500] 326 389 500]') + out('>>') + out('endobj') + #Font descriptor + newobj() + out('<>') + out('endobj') + end + + # + # putimages + # @access protected + # + def putimages() + filter=(@compress) ? '/Filter /FlateDecode ' : ''; + @images.each do |file, info| # was while(list(file, info)=each(@images)) + newobj(); + @images[file]['n']=@n; + out('<>'); + putstream(info['data']); + @images[file]['data']=nil + out('endobj'); + #Palette + if (info['cs']=='Indexed') + newobj(); + pal=(@compress) ? gzcompress(info['pal']) : info['pal']; + out('<<' + filter + '/Length ' + pal.length.to_s + '>>'); + putstream(pal); + out('endobj'); + end + end + end + + # + # putxobjectdict + # @access protected + # + def putxobjectdict() + @images.each_value do |image| + out('/I' + image['i'].to_s + ' ' + image['n'].to_s + ' 0 R'); + end + end + + # + # putresourcedict + # @access protected + # + def putresourcedict() + out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); + out('/Font <<'); + @fonts.each_value do |font| + out('/F' + font['i'].to_s + ' ' + font['n'].to_s + ' 0 R'); + end + out('>>'); + out('/XObject <<'); + putxobjectdict(); + out('>>'); + end + + # + # putresources + # @access protected + # + def putresources() + putfonts(); + putimages(); + #Resource dictionary + @offsets[2]=@buffer.length; + out('2 0 obj'); + out('<<'); + putresourcedict(); + out('>>'); + out('endobj'); + end + + # + # putinfo + # @access protected + # + def putinfo() + out('/Producer ' + textstring(PDF_PRODUCER)); + if (!@title.nil?) + out('/Title ' + textstring(@title)); + end + if (!@subject.nil?) + out('/Subject ' + textstring(@subject)); + end + if (!@author.nil?) + out('/Author ' + textstring(@author)); + end + if (!@keywords.nil?) + out('/Keywords ' + textstring(@keywords)); + end + if (!@creator.nil?) + out('/Creator ' + textstring(@creator)); + end + out('/CreationDate ' + textstring('D:' + Time.now.strftime('%Y%m%d%H%M%S'))); + end + + # + # putcatalog + # @access protected + # + def putcatalog() + out('/Type /Catalog'); + out('/Pages 1 0 R'); + if (@zoom_mode=='fullpage') + out('/OpenAction [3 0 R /Fit]'); + elsif (@zoom_mode=='fullwidth') + out('/OpenAction [3 0 R /FitH null]'); + elsif (@zoom_mode=='real') + out('/OpenAction [3 0 R /XYZ null null 1]'); + elsif (!@zoom_mode.is_a?(String)) + out('/OpenAction [3 0 R /XYZ null null ' + (@zoom_mode/100) + ']'); + end + if (@layout_mode=='single') + out('/PageLayout /SinglePage'); + elsif (@layout_mode=='continuous') + out('/PageLayout /OneColumn'); + elsif (@layout_mode=='two') + out('/PageLayout /TwoColumnLeft'); + end + end + + # + # puttrailer + # @access protected + # + def puttrailer() + out('/Size ' + (@n+1).to_s); + out('/Root ' + @n.to_s + ' 0 R'); + out('/Info ' + (@n-1).to_s + ' 0 R'); + end + + # + # putheader + # @access protected + # + def putheader() + out('%PDF-' + @pdf_version); + end + + # + # enddoc + # @access protected + # + def enddoc() + putheader(); + putpages(); + putresources(); + #Info + newobj(); + out('<<'); + putinfo(); + out('>>'); + out('endobj'); + #Catalog + newobj(); + out('<<'); + putcatalog(); + out('>>'); + out('endobj'); + #Cross-ref + o=@buffer.length; + out('xref'); + out('0 ' + (@n+1).to_s); + out('0000000000 65535 f '); + 1.upto(@n) do |i| + out(sprintf('%010d 00000 n ',@offsets[i])); + end + #Trailer + out('trailer'); + out('<<'); + puttrailer(); + out('>>'); + out('startxref'); + out(o); + out('%%EOF'); + @state=3; + end + + # + # beginpage + # @access protected + # + def beginpage(orientation) + @page += 1; + @pages[@page]=''; + @state=2; + @x=@l_margin; + @y=@t_margin; + @font_family=''; + #Page orientation + if (orientation.empty?) + orientation=@def_orientation; + else + orientation.upcase! + if (orientation!=@def_orientation) + @orientation_changes[@page]=true; + end + end + if (orientation!=@cur_orientation) + #Change orientation + if (orientation=='P') + @w_pt=@fw_pt; + @h_pt=@fh_pt; + @w=@fw; + @h=@fh; + else + @w_pt=@fh_pt; + @h_pt=@fw_pt; + @w=@fh; + @h=@fw; + end + @page_break_trigger=@h-@b_margin; + @cur_orientation = orientation; + end + end + + # + # End of page contents + # @access protected + # + def endpage() + @state=1; + end + + # + # Begin a new object + # @access protected + # + def newobj() + @n += 1; + @offsets[@n]=@buffer.length; + out(@n.to_s + ' 0 obj'); + end + + # + # Underline and Deleted text + # @access protected + # + def dolinetxt(x, y, txt) + up = @current_font['up']; + ut = @current_font['ut']; + w = GetStringWidth(txt) + @ws * txt.count(' '); + sprintf('%.2f %.2f %.2f %.2f re f', x * @k, (@h - (y - up / 1000.0 * @font_size)) * @k, w * @k, -ut / 1000.0 * @font_size_pt); + end + + # + # Extract info from a JPEG file + # @access protected + # + def parsejpg(file) + a=getimagesize(file); + if (a.empty?) + Error('Missing or incorrect image file: ' + file); + end + if (!a[2].nil? and a[2]!='JPEG') + Error('Not a JPEG file: ' + file); + end + if (a['channels'].nil? or a['channels']==3) + colspace='DeviceRGB'; + elsif (a['channels']==4) + colspace='DeviceCMYK'; + else + colspace='DeviceGray'; + end + bpc=!a['bits'].nil? ? a['bits'] : 8; + #Read whole file + data=''; + + open(file,'rb') do |f| + data< a[0],'h' => a[1],'cs' => colspace,'bpc' => bpc,'f'=>'DCTDecode','data' => data} + end + + def imageToPNG(file) + return unless Object.const_defined?(:Magick) + + img = Magick::ImageList.new(file) + img.format = 'PNG' # convert to PNG from gif + img.opacity = 0 # PNG alpha channel delete + + #use a temporary file.... + tmpFile = Tempfile.new(['', '_' + File::basename(file) + '.png'], @@k_path_cache); + tmpFile.binmode + tmpFile.print img.to_blob + tmpFile + ensure + tmpFile.close + end + + # + # Extract info from a PNG file + # @access protected + # + def parsepng(file) + f=open(file,'rb'); + #Check signature + if (f.read(8)!=137.chr + 'PNG' + 13.chr + 10.chr + 26.chr + 10.chr) + Error('Not a PNG file: ' + file); + end + #Read header chunk + f.read(4); + if (f.read(4)!='IHDR') + Error('Incorrect PNG file: ' + file); + end + w=freadint(f); + h=freadint(f); + bpc=f.read(1).unpack('C')[0]; + if (bpc>8) + Error('16-bit depth not supported: ' + file); + end + ct=f.read(1).unpack('C')[0]; + if (ct==0) + colspace='DeviceGray'; + elsif (ct==2) + colspace='DeviceRGB'; + elsif (ct==3) + colspace='Indexed'; + else + Error('Alpha channel not supported: ' + file); + end + if (f.read(1).unpack('C')[0] != 0) + Error('Unknown compression method: ' + file); + end + if (f.read(1).unpack('C')[0] != 0) + Error('Unknown filter method: ' + file); + end + if (f.read(1).unpack('C')[0] != 0) + Error('Interlacing not supported: ' + file); + end + f.read(4); + parms='/DecodeParms <>'; + #Scan chunks looking for palette, transparency and image data + pal=''; + trns=''; + data=''; + begin + n=freadint(f); + type=f.read(4); + if (type=='PLTE') + #Read palette + pal=f.read( n); + f.read(4); + elsif (type=='tRNS') + #Read transparency info + t=f.read( n); + if (ct==0) + trns = t[1].unpack('C')[0] + elsif (ct==2) + trns = t[[1].unpack('C')[0], t[3].unpack('C')[0], t[5].unpack('C')[0]] + else + pos=t.index(0.chr); + unless (pos.nil?) + trns = [pos] + end + end + f.read(4); + elsif (type=='IDAT') + #Read image data block + data< w, 'h' => h, 'cs' => colspace, 'bpc' => bpc, 'f'=>'FlateDecode', 'parms' => parms, 'pal' => pal, 'trns' => trns, 'data' => data} + ensure + f.close + end + + # + # Read a 4-byte integer from file + # @access protected + # + def freadint(f) + # Read a 4-byte integer from file + a = f.read(4).unpack('N') + return a[0] + end + + # + # Format a text string + # @access protected + # + def textstring(s) + if (@is_unicode) + #Convert string to UTF-16BE + s = UTF8ToUTF16BE(s, true); + end + return '(' + escape(s) + ')'; + end + + # + # Format a text string + # @access protected + # + def escapetext(s) + if (@is_unicode) + #Convert string to UTF-16BE + s = UTF8ToUTF16BE(s, false); + end + return escape(s); + end + + # + # Add \ before \, ( and ) + # @access protected + # + def escape(s) + # Add \ before \, ( and ) + s.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)').gsub(13.chr, '\r') + end + + # + # + # @access protected + # + def putstream(s) + out('stream'); + out(s); + out('endstream'); + end + + # + # Add a line to the document + # @access protected + # + def out(s) + if (@state==2) + @pages[@page] << s.to_s + "\n"; + else + @buffer << s.to_s + "\n"; + end + end + + # + # Adds unicode fonts.
+ # Based on PDF Reference 1.3 (section 5) + # @access protected + # @author Nicola Asuni + # @since 1.52.0.TC005 (2005-01-05) + # + def puttruetypeunicode(font) + # Type0 Font + # A composite font composed of other fonts, organized hierarchically + newobj(); + out('<>'); + out('endobj'); + + # CIDFontType2 + # A CIDFont whose glyph descriptions are based on TrueType font technology + newobj(); + out('<>'); + out('endobj'); + + # ToUnicode + # is a stream object that contains the definition of the CMap + # (PDF Reference 1.3 chap. 5.9) + newobj(); + out('<>') + out('stream'); + out('/CIDInit /ProcSet findresource begin'); + out('12 dict begin'); + out('begincmap'); + out('/CIDSystemInfo'); + out('<> def'); + out('/CMapName /Adobe-Identity-UCS def'); + out('/CMapType 2 def'); + out('1 begincodespacerange'); + out('<0000> '); + out('endcodespacerange'); + out('1 beginbfrange'); + out('<0000> <0000>'); + out('endbfrange'); + out('endcmap'); + out('CMapName currentdict /CMap defineresource pop'); + out('end'); + out('end'); + out('endstream'); + out('endobj'); + + # CIDSystemInfo dictionary + # A dictionary containing entries that define the character collection of the CIDFont. + newobj(); + out('<>'); + out('endobj'); + + # Font descriptor + # A font descriptor describing the CIDFont default metrics other than its glyph widths + newobj(); + out('<>'); + out('endobj'); + + # Embed CIDToGIDMap + # A specification of the mapping from CIDs to glyph indices + newobj(); + ctgfile = getfontpath(font['ctg']) + if (!ctgfile) + Error('Font file not found: ' + ctgfile); + end + size = File.size(ctgfile); + out('<>'); + open(ctgfile, "rb") do |f| + putstream(f.read()) + end + out('endobj'); + end + + # + # Converts UTF-8 strings to codepoints array.
+ # Invalid byte sequences will be replaced with 0xFFFD (replacement character)
+ # Based on: http://www.faqs.org/rfcs/rfc3629.html + #
+	# 	  Char. number range  |        UTF-8 octet sequence
+	#       (hexadecimal)    |              (binary)
+	#    --------------------+-----------------------------------------------
+	#    0000 0000-0000 007F | 0xxxxxxx
+	#    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
+	#    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
+	#    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+	#    ---------------------------------------------------------------------
+	#
+	#   ABFN notation:
+	#   ---------------------------------------------------------------------
+	#   UTF8-octets =#( UTF8-char )
+	#   UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+	#   UTF8-1      = %x00-7F
+	#   UTF8-2      = %xC2-DF UTF8-tail
+	#
+	#   UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+	#                 %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+	#   UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+	#                 %xF4 %x80-8F 2( UTF8-tail )
+	#   UTF8-tail   = %x80-BF
+	#   ---------------------------------------------------------------------
+	# 
+ # @param string :str string to process. + # @return array containing codepoints (UTF-8 characters values) + # @access protected + # @author Nicola Asuni + # @since 1.53.0.TC005 (2005-01-05) + # + def UTF8StringToArray(str) + if (!@is_unicode) + return str; # string is not in unicode + end + + unicode = [] # array containing unicode values + bytes = [] # array containing single character byte sequences + numbytes = 1; # number of octetc needed to represent the UTF-8 character + + str = str.to_s; # force :str to be a string + + str.each_byte do |char| + if (bytes.length == 0) # get starting octect + if (char <= 0x7F) + unicode << char # use the character "as is" because is ASCII + numbytes = 1 + elsif ((char >> 0x05) == 0x06) # 2 bytes character (0x06 = 110 BIN) + bytes << ((char - 0xC0) << 0x06) + numbytes = 2 + elsif ((char >> 0x04) == 0x0E) # 3 bytes character (0x0E = 1110 BIN) + bytes << ((char - 0xE0) << 0x0C) + numbytes = 3 + elsif ((char >> 0x03) == 0x1E) # 4 bytes character (0x1E = 11110 BIN) + bytes << ((char - 0xF0) << 0x12) + numbytes = 4 + else + # use replacement character for other invalid sequences + unicode << 0xFFFD + bytes = [] + numbytes = 1 + end + elsif ((char >> 0x06) == 0x02) # bytes 2, 3 and 4 must start with 0x02 = 10 BIN + bytes << (char - 0x80) + if (bytes.length == numbytes) + # compose UTF-8 bytes to a single unicode value + char = bytes[0] + 1.upto(numbytes-1) do |j| + char += (bytes[j] << ((numbytes - j - 1) * 0x06)) + end + if (((char >= 0xD800) and (char <= 0xDFFF)) or (char >= 0x10FFFF)) + # The definition of UTF-8 prohibits encoding character numbers between + # U+D800 and U+DFFF, which are reserved for use with the UTF-16 + # encoding form (as surrogate pairs) and do not directly represent + # characters + unicode << 0xFFFD; # use replacement character + else + unicode << char; # add char to array + end + # reset data for next char + bytes = [] + numbytes = 1; + end + else + # use replacement character for other invalid sequences + unicode << 0xFFFD; + bytes = [] + numbytes = 1; + end + end + return unicode; + end + + # + # Converts UTF-8 strings to UTF16-BE.
+ # Based on: http://www.faqs.org/rfcs/rfc2781.html + #
+	#   Encoding UTF-16:
+	# 
+		#   Encoding of a single character from an ISO 10646 character value to
+	#    UTF-16 proceeds as follows. Let U be the character number, no greater
+	#    than 0x10FFFF.
+	# 
+	#    1) If U < 0x10000, encode U as a 16-bit unsigned integer and
+	#       terminate.
+	# 
+	#    2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
+	#       U' must be less than or equal to 0xFFFFF. That is, U' can be
+	#       represented in 20 bits.
+	# 
+	#    3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
+	#       0xDC00, respectively. These integers each have 10 bits free to
+	#       encode the character value, for a total of 20 bits.
+	# 
+	#    4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
+	#       bits of W1 and the 10 low-order bits of U' to the 10 low-order
+	#       bits of W2. Terminate.
+	# 
+	#    Graphically, steps 2 through 4 look like:
+	#    U' = yyyyyyyyyyxxxxxxxxxx
+	#    W1 = 110110yyyyyyyyyy
+	#    W2 = 110111xxxxxxxxxx
+	# 
+ # @param string :str string to process. + # @param boolean :setbom if true set the Byte Order Mark (BOM = 0xFEFF) + # @return string + # @access protected + # @author Nicola Asuni + # @since 1.53.0.TC005 (2005-01-05) + # @uses UTF8StringToArray + # + def UTF8ToUTF16BE(str, setbom=true) + if (!@is_unicode) + return str; # string is not in unicode + end + outstr = ""; # string to be returned + unicode = UTF8StringToArray(str); # array containing UTF-8 unicode values + numitems = unicode.length; + + if (setbom) + outstr << "\xFE\xFF"; # Byte Order Mark (BOM) + end + unicode.each do |char| + if (char == 0xFFFD) + outstr << "\xFF\xFD"; # replacement character + elsif (char < 0x10000) + outstr << (char >> 0x08).chr; + outstr << (char & 0xFF).chr; + else + char -= 0x10000; + w1 = 0xD800 | (char >> 0x10); + w2 = 0xDC00 | (char & 0x3FF); + outstr << (w1 >> 0x08).chr; + outstr << (w1 & 0xFF).chr; + outstr << (w2 >> 0x08).chr; + outstr << (w2 & 0xFF).chr; + end + end + return outstr; + end + + # ==================================================== + + # + # Set header font. + # @param array :font font + # @since 1.1 + # + def SetHeaderFont(font) + @header_font = font; + end + alias_method :set_header_font, :SetHeaderFont + + # + # Set footer font. + # @param array :font font + # @since 1.1 + # + def SetFooterFont(font) + @footer_font = font; + end + alias_method :set_footer_font, :SetFooterFont + + # + # Set language array. + # @param array :language + # @since 1.1 + # + def SetLanguageArray(language) + @l = language; + end + alias_method :set_language_array, :SetLanguageArray + # + # Set document barcode. + # @param string :bc barcode + # + def SetBarcode(bc="") + @barcode = bc; + end + + # + # Print Barcode. + # @param int :x x position in user units + # @param int :y y position in user units + # @param int :w width in user units + # @param int :h height position in user units + # @param string :type type of barcode (I25, C128A, C128B, C128C, C39) + # @param string :style barcode style + # @param string :font font for text + # @param int :xres x resolution + # @param string :code code to print + # + def writeBarcode(x, y, w, h, type, style, font, xres, code) + require(File.dirname(__FILE__) + "/barcode/barcode.rb"); + require(File.dirname(__FILE__) + "/barcode/i25object.rb"); + require(File.dirname(__FILE__) + "/barcode/c39object.rb"); + require(File.dirname(__FILE__) + "/barcode/c128aobject.rb"); + require(File.dirname(__FILE__) + "/barcode/c128bobject.rb"); + require(File.dirname(__FILE__) + "/barcode/c128cobject.rb"); + + if (code.empty?) + return; + end + + if (style.empty?) + style = BCS_ALIGN_LEFT; + style |= BCS_IMAGE_PNG; + style |= BCS_TRANSPARENT; + #:style |= BCS_BORDER; + #:style |= BCS_DRAW_TEXT; + #:style |= BCS_STRETCH_TEXT; + #:style |= BCS_REVERSE_COLOR; + end + if (font.empty?) then font = BCD_DEFAULT_FONT; end + if (xres.empty?) then xres = BCD_DEFAULT_XRES; end + + scale_factor = 1.5 * xres * @k; + bc_w = (w * scale_factor).round #width in points + bc_h = (h * scale_factor).round #height in points + + case (type.upcase) + when "I25" + obj = I25Object.new(bc_w, bc_h, style, code); + when "C128A" + obj = C128AObject.new(bc_w, bc_h, style, code); + when "C128B" + obj = C128BObject.new(bc_w, bc_h, style, code); + when "C128C" + obj = C128CObject.new(bc_w, bc_h, style, code); + when "C39" + obj = C39Object.new(bc_w, bc_h, style, code); + end + + obj.SetFont(font); + obj.DrawObject(xres); + + #use a temporary file.... + tmpName = tempnam(@@k_path_cache,'img'); + imagepng(obj.getImage(), tmpName); + Image(tmpName, x, y, w, h, 'png'); + obj.DestroyObject(); + obj = nil + unlink(tmpName); + end + + # + # Returns the PDF data. + # + def GetPDFData() + if (@state < 3) + Close(); + end + return @buffer; + end + + # --- HTML PARSER FUNCTIONS --- + + # + # Allows to preserve some HTML formatting.
+ # Supports: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, ins, del, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small + # @param string :html text to display + # @param boolean :ln if true add a new line after text (default = true) + # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0. + # + def writeHTML(html, ln=true, fill=0, h=0) + + @lasth = h if h > 0 + if (@lasth == 0) + #set row height + @lasth = @font_size * @@k_cell_height_ratio; + end + + @href = nil + @style = ""; + @t_cells = [[]]; + @table_id = 0; + + # pre calculate + html.split(/(<[^>]+>)/).each do |element| + if "<" == element[0,1] + #Tag + if (element[1, 1] == '/') + closedHTMLTagCalc(element[2..-2].downcase); + else + #Extract attributes + # get tag name + tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0} + tag = tag[0].to_s.downcase; + + # get attributes + attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/) + attrs = {} + attr_array.each do |name, value| + attrs[name.downcase] = value; + end + openHTMLTagCalc(tag, attrs); + end + end + end + @table_id = 0; + + html.split(/(<[A-Za-z!?\/][^>]*?>)/).each do |element| + if "<" == element[0,1] + #Tag + if (element[1, 1] == '/') + closedHTMLTagHandler(element[2..-2].downcase); + else + #Extract attributes + # get tag name + tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0} + tag = tag[0].to_s.downcase; + + # get attributes + attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/) + attrs = {} + attr_array.each do |name, value| + attrs[name.downcase] = value; + end + openHTMLTagHandler(tag, attrs, fill); + end + + else + #Text + if (@tdbegin) + element.gsub!(/[\t\r\n\f]/, ""); + @tdtext << element.gsub(/ /, " "); + elsif (@href) + element.gsub!(/[\t\r\n\f]/, ""); + addHtmlLink(@href, element, fill); + elsif (@pre_state == true and element.length > 0) + Write(@lasth, unhtmlentities(element), '', fill); + elsif (element.strip.length > 0) + element.gsub!(/[\t\r\n\f]/, ""); + element.gsub!(/ /, " "); + Write(@lasth, unhtmlentities(element), '', fill); + end + end + end + + if (ln) + Ln(@lasth); + end + end + alias_method :write_html, :writeHTML + + # + # Prints a cell (rectangular area) with optional borders, background color and html text string. The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.
+ # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. + # @param float :w Cell width. If 0, the cell extends up to the right margin. + # @param float :h Cell minimum height. The cell extends automatically if needed. + # @param float :x upper-left corner X coordinate + # @param float :y upper-left corner Y coordinate + # @param string :html html text to print. Default value: empty string. + # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:
  • 0: no border (default)
  • 1: frame
or a string containing some or all of the following characters (in any order):
  • L: left
  • T: top
  • R: right
  • B: bottom
+ # @param int :ln Indicates where the current position should go after the call. Possible values are:
  • 0: to the right
  • 1: to the beginning of the next line
  • 2: below
+# Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @see Cell() + # + def writeHTMLCell(w, h, x, y, html='', border=0, ln=1, fill=0) + + if (@lasth == 0) + #set row height + @lasth = @font_size * @@k_cell_height_ratio; + end + + if (x == 0) + x = GetX(); + end + if (y == 0) + y = GetY(); + end + + # get current page number + pagenum = @page; + + SetX(x); + SetY(y); + + if (w == 0) + w = @fw - x - @r_margin; + end + + b=0; + if (border) + if (border==1) + border='LTRB'; + b='LRT'; + b2='LR'; + elsif border.is_a?(String) + b2=''; + if (border.include?('L')) + b2<<'L'; + end + if (border.include?('R')) + b2<<'R'; + end + b=(border.include?('T')) ? b2 + 'T' : b2; + end + end + + # store original margin values + l_margin = @l_margin; + r_margin = @r_margin; + + # set new margin values + SetLeftMargin(x); + SetRightMargin(@fw - x - w); + + # calculate remaining vertical space on page + restspace = GetPageHeight() - GetY() - GetBreakMargin(); + + writeHTML(html, true, fill); # write html text + SetX(x) + + currentY = GetY(); + @auto_page_break = false; + # check if a new page has been created + if (@page > pagenum) + # design a cell around the text on first page + currentpage = @page; + @page = pagenum; + SetY(GetPageHeight() - restspace - GetBreakMargin()); + SetX(x) + Cell(w, restspace - 1, "", b, 0, 'L', 0); + b = b2; + @page += 1; + while @page < currentpage + SetY(@t_margin); # put cursor at the beginning of text + SetX(x) + Cell(w, @page_break_trigger - @t_margin, "", b, 0, 'L', 0); + @page += 1; + end + if (border.is_a?(String) and border.include?('B')) + b<<'B'; + end + # design a cell around the text on last page + SetY(@t_margin); # put cursor at the beginning of text + SetX(x) + Cell(w, currentY - @t_margin, "", b, 0, 'L', 0); + else + SetY(y); # put cursor at the beginning of text + # design a cell around the text + SetX(x) + Cell(w, [h, (currentY - y)].max, "", border, 0, 'L', 0); + end + @auto_page_break = true; + + # restore original margin values + SetLeftMargin(l_margin); + SetRightMargin(r_margin); + + @lasth = h + + # move cursor to specified position + if (ln == 0) + # go to the top-right of the cell + @x = x + w; + @y = y; + elsif (ln == 1) + # go to the beginning of the next line + @x = @l_margin; + @y = currentY; + elsif (ln == 2) + # go to the bottom-left of the cell (below) + @x = x; + @y = currentY; + end + end + alias_method :write_html_cell, :writeHTMLCell + + # + # Check html table tag position. + # + # @param array :table potision array + # @param int :current tr tag id number + # @param int :current td tag id number + # @access private + # @return int : next td_id position. + # value 0 mean that can use position. + # + def checkTableBlockingCellPosition(table, tr_id, td_id ) + 0.upto(tr_id) do |j| + 0.upto(@t_cells[table][j].size - 1) do |i| + if @t_cells[table][j][i]['i0'] <= td_id and td_id <= @t_cells[table][j][i]['i1'] + if @t_cells[table][j][i]['j0'] <= tr_id and tr_id <= @t_cells[table][j][i]['j1'] + return @t_cells[table][j][i]['i1'] - td_id + 1; + end + end + end + end + return 0; + end + + # + # Calculate opening tags. + # + # html table cell array : @t_cells + # + # i0: table cell start position + # i1: table cell end position + # j0: table row start position + # j1: table row end position + # + # +------+ + # |i0,j0 | + # | i1,j1| + # +------+ + # + # example html: + # + # + # + # + # + #
+ # + # i: 0 1 2 + # j+----+----+----+ + # :|0,0 |1,0 |2,0 | + # 0| 0,0| 1,0| 2,0| + # +----+----+----+ + # |0,1 |2,1 | + # 1| 1,1| 2,1| + # +----+----+----+ + # |0,2 |1,2 |2,2 | + # 2| | 1,2| 2,2| + # + +----+----+ + # | |1,3 |2,3 | + # 3| 0,3| 1,3| 2,3| + # +----+----+----+ + # + # html table cell array : + # [[[i0=>0,j0=>0,i1=>0,j1=>0],[i0=>1,j0=>0,i1=>1,j1=>0],[i0=>2,j0=>0,i1=>2,j1=>0]], + # [[i0=>0,j0=>1,i1=>1,j1=>1],[i0=>2,j0=>1,i1=>2,j1=>1]], + # [[i0=>0,j0=>2,i1=>0,j1=>3],[i0=>1,j0=>2,i1=>1,j1=>2],[i0=>2,j0=>2,i1=>2,j1=>2]] + # [[i0=>1,j0=>3,i1=>1,j1=>3],[i0=>2,j0=>3,i1=>2,j1=>3]]] + # + # @param string :tag tag name (in upcase) + # @param string :attr tag attribute (in upcase) + # @access private + # + def openHTMLTagCalc(tag, attrs) + #Opening tag + case (tag) + when 'table' + @max_table_columns[@table_id] = 0; + @t_columns = 0; + @tr_id = -1; + when 'tr' + if @max_table_columns[@table_id] < @t_columns + @max_table_columns[@table_id] = @t_columns; + end + @t_columns = 0; + @tr_id += 1; + @td_id = -1; + @t_cells[@table_id].push [] + when 'td', 'th' + @td_id += 1; + if attrs['colspan'].nil? or attrs['colspan'] == '' + colspan = 1; + else + colspan = attrs['colspan'].to_i; + end + if attrs['rowspan'].nil? or attrs['rowspan'] == '' + rowspan = 1; + else + rowspan = attrs['rowspan'].to_i; + end + + i = 0; + while true + next_i_distance = checkTableBlockingCellPosition(@table_id, @tr_id, @td_id + i); + if next_i_distance == 0 + @t_cells[@table_id][@tr_id].push "i0"=>@td_id + i, "j0"=>@tr_id, "i1"=>(@td_id + i + colspan - 1), "j1"=>@tr_id + rowspan - 1 + break; + end + i += next_i_distance; + end + + @t_columns += colspan; + end + end + + # + # Calculate closing tags. + # @param string :tag tag name (in upcase) + # @access private + # + def closedHTMLTagCalc(tag) + #Closing tag + case (tag) + when 'table' + if @max_table_columns[@table_id] < @t_columns + @max_table_columns[@table_id] = @t_columns; + end + @table_id += 1; + @t_cells.push [] + end + end + + # + # Convert to accessible file path + # @param string :attrname image file name + # + def getImageFilename( attrname ) + nil + end + + # + # Process opening tags. + # @param string :tag tag name (in upcase) + # @param string :attr tag attribute (in upcase) + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @access private + # + def openHTMLTagHandler(tag, attrs, fill=0) + #Opening tag + case (tag) + when 'pre' + @pre_state = true; + @l_margin += 5; + @r_margin += 5; + @x += 5; + + when 'table' + Ln(); + if @default_table_columns < @max_table_columns[@table_id] + @table_columns = @max_table_columns[@table_id]; + else + @table_columns = @default_table_columns; + end + @l_margin += 5; + @r_margin += 5; + @x += 5; + + if attrs['border'].nil? or attrs['border'] == '' + @tableborder = 0; + else + @tableborder = attrs['border']; + end + @tr_id = -1; + @max_td_page[0] = @page; + @max_td_y[0] = @y; + + when 'tr', 'td', 'th' + if tag == 'th' + SetStyle('b', true); + @tdalign = "C"; + end + if ((!attrs['width'].nil?) and (attrs['width'] != '')) + @tdwidth = (attrs['width'].to_i/4); + else + @tdwidth = ((@w - @l_margin - @r_margin) / @table_columns); + end + + if tag == 'tr' + @tr_id += 1; + @td_id = -1; + else + @td_id += 1; + @x = @l_margin + @tdwidth * @t_cells[@table_id][@tr_id][@td_id]['i0']; + end + + if attrs['colspan'].nil? or attrs['border'] == '' + @colspan = 1; + else + @colspan = attrs['colspan'].to_i; + end + @tdwidth *= @colspan; + if ((!attrs['height'].nil?) and (attrs['height'] != '')) + @tdheight=(attrs['height'].to_i / @k); + else + @tdheight = @lasth; + end + if ((!attrs['align'].nil?) and (attrs['align'] != '')) + case (attrs['align']) + when 'center' + @tdalign = "C"; + when 'right' + @tdalign = "R"; + when 'left' + @tdalign = "L"; + end + end + if ((!attrs['bgcolor'].nil?) and (attrs['bgcolor'] != '')) + coul = convertColorHexToDec(attrs['bgcolor']); + SetFillColor(coul['R'], coul['G'], coul['B']); + @tdfill=1; + end + @tdbegin=true; + + when 'hr' + margin = 1; + if ((!attrs['width'].nil?) and (attrs['width'] != '')) + hrWidth = attrs['width']; + else + hrWidth = @w - @l_margin - @r_margin - margin; + end + SetLineWidth(0.2); + Line(@x + margin, @y, @x + hrWidth, @y); + Ln(); + + when 'strong' + SetStyle('b', true); + + when 'em' + SetStyle('i', true); + + when 'ins' + SetStyle('u', true); + + when 'del' + SetStyle('d', true); + + when 'b', 'i', 'u' + SetStyle(tag, true); + + when 'a' + @href = attrs['href']; + + when 'img' + if (!attrs['src'].nil?) + # Don't generates image inside table tag + if (@tdbegin) + @tdtext << attrs['src']; + return + end + # Only generates image include a pdf if RMagick is avalaible + unless Object.const_defined?(:Magick) + Write(@lasth, attrs['src'], '', fill); + return + end + file = getImageFilename(attrs['src']) + if (file.nil?) + Write(@lasth, attrs['src'], '', fill); + return + end + + if (attrs['width'].nil?) + attrs['width'] = 0; + end + if (attrs['height'].nil?) + attrs['height'] = 0; + end + + begin + Image(file, GetX(),GetY(), pixelsToMillimeters(attrs['width']), pixelsToMillimeters(attrs['height'])); + #SetX(@img_rb_x); + SetY(@img_rb_y); + rescue => err + logger.error "pdf: Image: error: #{err.message}" + Write(@lasth, attrs['src'], '', fill); + end + end + + when 'ul', 'ol' + if @li_count == 0 + Ln() if @prevquote_count == @quote_count; # insert Ln for keeping quote lines + @prevquote_count = @quote_count; + end + if @li_state == true + Ln(); + @li_state = false; + end + if tag == 'ul' + @list_ordered[@li_count] = false; + else + @list_ordered[@li_count] = true; + end + @list_count[@li_count] = 0; + @li_count += 1 + + when 'li' + Ln() if @li_state == true + if (@list_ordered[@li_count - 1]) + @list_count[@li_count - 1] += 1; + @li_spacer = " " * @li_count + (@list_count[@li_count - 1]).to_s + ". "; + else + #unordered list simbol + @li_spacer = " " * @li_count + "- "; + end + Write(@lasth, @spacer + @li_spacer, '', fill); + @li_state = true; + + when 'blockquote' + if (@quote_count == 0) + SetStyle('i', true); + @l_margin += 5; + else + @l_margin += 5 / 2; + end + @x = @l_margin; + @quote_top[@quote_count] = @y; + @quote_page[@quote_count] = @page; + @quote_count += 1 + when 'br' + if @tdbegin + @tdtext << "\n" + return + end + Ln(); + + if (@li_spacer.length > 0) + @x += GetStringWidth(@li_spacer); + end + + when 'p' + Ln(); + 0.upto(@quote_count - 1) do |i| + if @quote_page[i] == @page; + if @quote_top[i] == @y - @lasth; # fix start line + @quote_top[i] = @y; + end + else + if @quote_page[i] == @page - 1; + @quote_page[i] = @page; # fix start line + @quote_top[i] = @t_margin; + end + end + end + + when 'sup' + currentfont_size = @font_size; + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt * @@k_small_ratio); + SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio))); + + when 'sub' + currentfont_size = @font_size; + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt * @@k_small_ratio); + SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio))); + + when 'small' + currentfont_size = @font_size; + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt * @@k_small_ratio); + SetXY(GetX(), GetY() + ((currentfont_size - @font_size)/3)); + + when 'font' + if (!attrs['color'].nil? and attrs['color']!='') + coul = convertColorHexToDec(attrs['color']); + SetTextColor(coul['R'], coul['G'], coul['B']); + @issetcolor=true; + end + if (!attrs['face'].nil? and @fontlist.include?(attrs['face'].downcase)) + SetFont(attrs['face'].downcase); + @issetfont=true; + end + if (!attrs['size'].nil?) + headsize = attrs['size'].to_i; + else + headsize = 0; + end + currentfont_size = @font_size; + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt + headsize); + @lasth = @font_size * @@k_cell_height_ratio; + + when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' + Ln(); + headsize = (4 - tag[1,1].to_f) * 2 + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt + headsize); + SetStyle('b', true); + @lasth = @font_size * @@k_cell_height_ratio; + + end + end + + # + # Process closing tags. + # @param string :tag tag name (in upcase) + # @access private + # + def closedHTMLTagHandler(tag) + #Closing tag + case (tag) + when 'pre' + @pre_state = false; + @l_margin -= 5; + @r_margin -= 5; + @x = @l_margin; + Ln(); + + when 'td','th' + base_page = @page; + base_x = @x; + base_y = @y; + + MultiCell(@tdwidth, @tdheight, unhtmlentities(@tdtext.strip), @tableborder, @tdalign, @tdfill, 1); + tr_end = @t_cells[@table_id][@tr_id][@td_id]['j1'] + 1; + if @max_td_page[tr_end].nil? or (@max_td_page[tr_end] < @page) + @max_td_page[tr_end] = @page + @max_td_y[tr_end] = @y + elsif (@max_td_page[tr_end] == @page) + @max_td_y[tr_end] = @y if @max_td_y[tr_end].nil? or (@max_td_y[tr_end] < @y) + end + + @page = base_page; + @x = base_x + @tdwidth; + @y = base_y; + @tdtext = ''; + @tdbegin = false; + @tdwidth = 0; + @tdheight = 0; + @tdalign = "L"; + SetStyle('b', false); + @tdfill = 0; + SetFillColor(@prevfill_color[0], @prevfill_color[1], @prevfill_color[2]); + + when 'tr' + @y = @max_td_y[@tr_id + 1]; + @x = @l_margin; + @page = @max_td_page[@tr_id + 1]; + + when 'table' + # Write Table Line + width = (@w - @l_margin - @r_margin) / @table_columns; + 0.upto(@t_cells[@table_id].size - 1) do |j| + 0.upto(@t_cells[@table_id][j].size - 1) do |i| + @page = @max_td_page[j] + i0=@t_cells[@table_id][j][i]['i0']; + j0=@t_cells[@table_id][j][i]['j0']; + i1=@t_cells[@table_id][j][i]['i1']; + j1=@t_cells[@table_id][j][i]['j1']; + + Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * (i1+1), @max_td_y[j0]) # top + if ( @page == @max_td_page[j1 + 1]) + Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * i0, @max_td_y[j1+1]) # left + Line(@l_margin + width * (i1+1), @max_td_y[j0], @l_margin + width * (i1+1), @max_td_y[j1+1]) # right + else + Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * i0, @page_break_trigger) # left + Line(@l_margin + width * (i1+1), @max_td_y[j0], @l_margin + width * (i1+1), @page_break_trigger) # right + @page += 1; + while @page < @max_td_page[j1 + 1] + Line(@l_margin + width * i0, @t_margin, @l_margin + width * i0, @page_break_trigger) # left + Line(@l_margin + width * (i1+1), @t_margin, @l_margin + width * (i1+1), @page_break_trigger) # right + @page += 1; + end + Line(@l_margin + width * i0, @t_margin, @l_margin + width * i0, @max_td_y[j1+1]) # left + Line(@l_margin + width * (i1+1), @t_margin, @l_margin + width * (i1+1), @max_td_y[j1+1]) # right + end + Line(@l_margin + width * i0, @max_td_y[j1+1], @l_margin + width * (i1+1), @max_td_y[j1+1]) # bottom + end + end + + @l_margin -= 5; + @r_margin -= 5; + @tableborder=0; + @table_id += 1; + + when 'strong' + SetStyle('b', false); + + when 'em' + SetStyle('i', false); + + when 'ins' + SetStyle('u', false); + + when 'del' + SetStyle('d', false); + + when 'b', 'i', 'u' + SetStyle(tag, false); + + when 'a' + @href = nil; + + when 'p' + Ln(); + + when 'sup' + currentfont_size = @font_size; + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio))); + + when 'sub' + currentfont_size = @font_size; + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio))); + + when 'small' + currentfont_size = @font_size; + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + SetXY(GetX(), GetY() - ((@font_size - currentfont_size)/3)); + + when 'font' + if (@issetcolor == true) + SetTextColor(@prevtext_color[0], @prevtext_color[1], @prevtext_color[2]); + end + if (@issetfont) + @font_family = @prevfont_family; + @font_style = @prevfont_style; + SetFont(@font_family); + @issetfont = false; + end + currentfont_size = @font_size; + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + #@text_color = @prevtext_color; + @lasth = @font_size * @@k_cell_height_ratio; + + when 'blockquote' + @quote_count -= 1 + if (@quote_page[@quote_count] == @page) + Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @y) # quoto line + else + cur_page = @page; + cur_y = @y; + @page = @quote_page[@quote_count]; + if (@quote_top[@quote_count] < @page_break_trigger) + Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @page_break_trigger) # quoto line + end + @page += 1; + while @page < cur_page + Line(@l_margin - 1, @t_margin, @l_margin - 1, @page_break_trigger) # quoto line + @page += 1; + end + @y = cur_y; + Line(@l_margin - 1, @t_margin, @l_margin - 1, @y) # quoto line + end + if (@quote_count <= 0) + SetStyle('i', false); + @l_margin -= 5; + else + @l_margin -= 5 / 2; + end + @x = @l_margin; + Ln() if @quote_count == 0 + + when 'ul', 'ol' + @li_count -= 1 + if @li_state == true + Ln(); + @li_state = false; + end + + when 'li' + @li_spacer = ""; + if @li_state == true + Ln(); + @li_state = false; + end + + when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + SetStyle('b', false); + Ln(); + @lasth = @font_size * @@k_cell_height_ratio; + + if tag == 'h1' or tag == 'h2' or tag == 'h3' or tag == 'h4' + margin = 1; + hrWidth = @w - @l_margin - @r_margin - margin; + if tag == 'h1' or tag == 'h2' + SetLineWidth(0.2); + else + SetLineWidth(0.1); + end + Line(@x + margin, @y, @x + hrWidth, @y); + end + end + end + + # + # Sets font style. + # @param string :tag tag name (in lowercase) + # @param boolean :enable + # @access private + # + def SetStyle(tag, enable) + #Modify style and select corresponding font + ['b', 'i', 'u', 'd'].each do |s| + if tag.downcase == s + if enable + @style << s if ! @style.include?(s) + else + @style = @style.gsub(s,'') + end + end + end + SetFont('', @style); + end + + # + # Output anchor link. + # @param string :url link URL + # @param string :name link name + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @access public + # + def addHtmlLink(url, name, fill=0) + #Put a hyperlink + SetTextColor(0, 0, 255); + SetStyle('u', true); + Write(@lasth, name, url, fill); + SetStyle('u', false); + SetTextColor(0); + end + + # + # Returns an associative array (keys: R,G,B) from + # a hex html code (e.g. #3FE5AA). + # @param string :color hexadecimal html color [#rrggbb] + # @return array + # @access private + # + def convertColorHexToDec(color = "#000000") + tbl_color = {} + tbl_color['R'] = color[1,2].hex.to_i; + tbl_color['G'] = color[3,2].hex.to_i; + tbl_color['B'] = color[5,2].hex.to_i; + return tbl_color; + end + + # + # Converts pixels to millimeters in 72 dpi. + # @param int :px pixels + # @return float millimeters + # @access private + # + def pixelsToMillimeters(px) + return px.to_f * 25.4 / 72; + end + + # + # Reverse function for htmlentities. + # Convert entities in UTF-8. + # + # @param :text_to_convert Text to convert. + # @return string converted + # + def unhtmlentities(string) + CGI.unescapeHTML(string) + end + +end # END OF CLASS + +#TODO 2007-05-25 (EJM) Level=0 - +#Handle special IE contype request +# if (!_SERVER['HTTP_USER_AGENT'].nil? and (_SERVER['HTTP_USER_AGENT']=='contype')) +# header('Content-Type: application/pdf'); +# exit; +# } diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/002350bfb6f2834394af5b5afbf87a58fa102afc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/002350bfb6f2834394af5b5afbf87a58fa102afc.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1116 @@ +#Ernad Husremovic hernad@bring.out.ba + +bs: + direction: ltr + date: + formats: + default: "%d.%m.%Y" + short: "%e. %b" + long: "%e. %B %Y" + only_day: "%e" + + + day_names: [Nedjelja, Ponedjeljak, Utorak, Srijeda, ÄŒetvrtak, Petak, Subota] + abbr_day_names: [Ned, Pon, Uto, Sri, ÄŒet, Pet, Sub] + + month_names: [~, Januar, Februar, Mart, April, Maj, Jun, Jul, Avgust, Septembar, Oktobar, Novembar, Decembar] + abbr_month_names: [~, Jan, Feb, Mar, Apr, Maj, Jun, Jul, Avg, Sep, Okt, Nov, Dec] + order: + - :day + - :month + - :year + + time: + formats: + default: "%A, %e. %B %Y, %H:%M" + short: "%e. %B, %H:%M Uhr" + long: "%A, %e. %B %Y, %H:%M" + time: "%H:%M" + + am: "prijepodne" + pm: "poslijepodne" + + datetime: + distance_in_words: + half_a_minute: "pola minute" + less_than_x_seconds: + one: "manje od 1 sekunde" + other: "manje od %{count} sekudni" + x_seconds: + one: "1 sekunda" + other: "%{count} sekundi" + less_than_x_minutes: + one: "manje od 1 minute" + other: "manje od %{count} minuta" + x_minutes: + one: "1 minuta" + other: "%{count} minuta" + about_x_hours: + one: "oko 1 sahat" + other: "oko %{count} sahata" + x_hours: + one: "1 sahat" + other: "%{count} sahata" + x_days: + one: "1 dan" + other: "%{count} dana" + about_x_months: + one: "oko 1 mjesec" + other: "oko %{count} mjeseci" + x_months: + one: "1 mjesec" + other: "%{count} mjeseci" + about_x_years: + one: "oko 1 godine" + other: "oko %{count} godina" + over_x_years: + one: "preko 1 godine" + other: "preko %{count} godina" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + + + number: + format: + precision: 2 + separator: ',' + delimiter: '.' + currency: + format: + unit: 'KM' + format: '%u %n' + negative_format: '%u -%n' + delimiter: '' + percentage: + format: + delimiter: "" + precision: + format: + delimiter: "" + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "i" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "nije ukljuÄeno u listu" + exclusion: "je rezervisano" + invalid: "nije ispravno" + confirmation: "ne odgovara potvrdi" + accepted: "mora se prihvatiti" + empty: "ne može biti prazno" + blank: "ne može biti znak razmaka" + too_long: "je predugaÄko" + too_short: "je prekratko" + wrong_length: "je pogreÅ¡ne dužine" + taken: "već je zauzeto" + not_a_number: "nije broj" + not_a_date: "nije ispravan datum" + greater_than: "mora bit veći od %{count}" + greater_than_or_equal_to: "mora bit veći ili jednak %{count}" + equal_to: "mora biti jednak %{count}" + less_than: "mora biti manji od %{count}" + less_than_or_equal_to: "mora bit manji ili jednak %{count}" + odd: "mora biti neparan" + even: "mora biti paran" + greater_than_start_date: "mora biti veći nego poÄetni datum" + not_same_project: "ne pripada istom projektu" + circular_dependency: "Ova relacija stvar cirkularnu zavisnost" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Molimo odaberite + + general_text_No: 'Da' + general_text_Yes: 'Ne' + general_text_no: 'ne' + general_text_yes: 'da' + general_lang_name: 'Bosanski' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_activated: VaÅ¡ nalog je aktiviran. Možete se prijaviti. + notice_account_invalid_creditentials: PogreÅ¡an korisnik ili lozinka + notice_account_lost_email_sent: Email sa uputstvima o izboru nove Å¡ifre je poslat na vaÅ¡u adresu. + notice_account_password_updated: Lozinka je uspjeÅ¡no promjenjena. + notice_account_pending: "VaÅ¡ nalog je kreiran i Äeka odobrenje administratora." + notice_account_register_done: Nalog je uspjeÅ¡no kreiran. Da bi ste aktivirali vaÅ¡ nalog kliknite na link koji vam je poslat. + notice_account_unknown_email: Nepoznati korisnik. + notice_account_updated: Nalog je uspjeÅ¡no promjenen. + notice_account_wrong_password: PogreÅ¡na lozinka + notice_can_t_change_password: Ovaj nalog koristi eksterni izvor prijavljivanja. Ne mogu da promjenim Å¡ifru. + notice_default_data_loaded: Podrazumjevana konfiguracija uspjeÄno uÄitana. + notice_email_error: DoÅ¡lo je do greÅ¡ke pri slanju emaila (%{value}) + notice_email_sent: "Email je poslan %{value}" + notice_failed_to_save_issues: "NeuspjeÅ¡no snimanje %{count} aktivnosti na %{total} izabrano: %{ids}." + notice_feeds_access_key_reseted: VaÅ¡ Atom pristup je resetovan. + notice_file_not_found: Stranica kojoj pokuÅ¡avate da pristupite ne postoji ili je uklonjena. + notice_locking_conflict: "Konflikt: podaci su izmjenjeni od strane drugog korisnika." + notice_no_issue_selected: "Nijedna aktivnost nije izabrana! Molim, izaberite aktivnosti koje želite za ispravljate." + notice_not_authorized: Niste ovlašćeni da pristupite ovoj stranici. + notice_successful_connection: UspjeÅ¡na konekcija. + notice_successful_create: UspjeÅ¡no kreiranje. + notice_successful_delete: Brisanje izvrÅ¡eno. + notice_successful_update: Promjene uspjeÅ¡no izvrÅ¡ene. + + error_can_t_load_default_data: "Podrazumjevane postavke se ne mogu uÄitati %{value}" + error_scm_command_failed: "Desila se greÅ¡ka pri pristupu repozitoriju: %{value}" + error_scm_not_found: "Unos i/ili revizija ne postoji u repozitoriju." + + error_scm_annotate: "Ova stavka ne postoji ili nije oznaÄena." + error_issue_not_found_in_project: 'Aktivnost nije naÄ‘ena ili ne pripada ovom projektu' + + warning_attachments_not_saved: "%{count} fajl(ovi) ne mogu biti snimljen(i)." + + mail_subject_lost_password: "VaÅ¡a %{value} lozinka" + mail_body_lost_password: 'Za promjenu lozinke, kliknite na sljedeći link:' + mail_subject_register: "Aktivirajte %{value} vaÅ¡ korisniÄki raÄun" + mail_body_register: 'Za aktivaciju vaÅ¡eg korisniÄkog raÄuna, kliknite na sljedeći link:' + mail_body_account_information_external: "Možete koristiti vaÅ¡ %{value} korisniÄki raÄun za prijavu na sistem." + mail_body_account_information: Informacija o vaÅ¡em korisniÄkom raÄunu + mail_subject_account_activation_request: "%{value} zahtjev za aktivaciju korisniÄkog raÄuna" + mail_body_account_activation_request: "Novi korisnik (%{value}) se registrovao. KorisniÄki raÄun Äeka vaÅ¡e odobrenje za aktivaciju:" + mail_subject_reminder: "%{count} aktivnost(i) u kaÅ¡njenju u narednim %{days} danima" + mail_body_reminder: "%{count} aktivnost(i) koje su dodjeljenje vama u narednim %{days} danima:" + + + field_name: Ime + field_description: Opis + field_summary: PojaÅ¡njenje + field_is_required: Neophodno popuniti + field_firstname: Ime + field_lastname: Prezime + field_mail: Email + field_filename: Fajl + field_filesize: VeliÄina + field_downloads: Downloadi + field_author: Autor + field_created_on: Kreirano + field_updated_on: Izmjenjeno + field_field_format: Format + field_is_for_all: Za sve projekte + field_possible_values: Moguće vrijednosti + field_regexp: '"Regularni izraz"' + field_min_length: Minimalna veliÄina + field_max_length: Maksimalna veliÄina + field_value: Vrijednost + field_category: Kategorija + field_title: Naslov + field_project: Projekat + field_issue: Aktivnost + field_status: Status + field_notes: BiljeÅ¡ke + field_is_closed: Aktivnost zatvorena + field_is_default: Podrazumjevana vrijednost + field_tracker: PodruÄje aktivnosti + field_subject: Subjekat + field_due_date: ZavrÅ¡iti do + field_assigned_to: Dodijeljeno + field_priority: Prioritet + field_fixed_version: Ciljna verzija + field_user: Korisnik + field_role: Uloga + field_homepage: Naslovna strana + field_is_public: Javni + field_parent: Podprojekt od + field_is_in_roadmap: Aktivnosti prikazane u planu realizacije + field_login: Prijava + field_mail_notification: Email notifikacije + field_admin: Administrator + field_last_login_on: Posljednja konekcija + field_language: Jezik + field_effective_date: Datum + field_password: Lozinka + field_new_password: Nova lozinka + field_password_confirmation: Potvrda + field_version: Verzija + field_type: Tip + field_host: Host + field_port: Port + field_account: KorisniÄki raÄun + field_base_dn: Base DN + field_attr_login: Attribut za prijavu + field_attr_firstname: Attribut za ime + field_attr_lastname: Atribut za prezime + field_attr_mail: Atribut za email + field_onthefly: 'Kreiranje korisnika "On-the-fly"' + field_start_date: PoÄetak + field_done_ratio: "% Realizovano" + field_auth_source: Mod za authentifikaciju + field_hide_mail: Sakrij moju email adresu + field_comments: Komentar + field_url: URL + field_start_page: PoÄetna stranica + field_subproject: Podprojekat + field_hours: Sahata + field_activity: Operacija + field_spent_on: Datum + field_identifier: Identifikator + field_is_filter: KoriÅ¡teno kao filter + field_issue_to: Povezana aktivnost + field_delay: OdgaÄ‘anje + field_assignable: Aktivnosti dodijeljene ovoj ulozi + field_redirect_existing_links: IzvrÅ¡i redirekciju postojećih linkova + field_estimated_hours: Procjena vremena + field_column_names: Kolone + field_time_zone: Vremenska zona + field_searchable: Pretraživo + field_default_value: Podrazumjevana vrijednost + field_comments_sorting: Prikaži komentare + field_parent_title: 'Stranica "roditelj"' + field_editable: Može se mijenjati + field_watcher: PosmatraÄ + field_identity_url: OpenID URL + field_content: Sadržaj + + setting_app_title: Naslov aplikacije + setting_app_subtitle: Podnaslov aplikacije + setting_welcome_text: Tekst dobrodoÅ¡lice + setting_default_language: Podrazumjevani jezik + setting_login_required: Authentifikacija neophodna + setting_self_registration: Samo-registracija + setting_attachment_max_size: Maksimalna veliÄina prikaÄenog fajla + setting_issues_export_limit: Limit za eksport aktivnosti + setting_mail_from: Mail adresa - poÅ¡aljilac + setting_bcc_recipients: '"BCC" (blind carbon copy) primaoci ' + setting_plain_text_mail: Email sa obiÄnim tekstom (bez HTML-a) + setting_host_name: Ime hosta i putanja + setting_text_formatting: Formatiranje teksta + setting_wiki_compression: Kompresija Wiki istorije + + setting_feeds_limit: 'Limit za "Atom" feed-ove' + setting_default_projects_public: Podrazumjeva se da je novi projekat javni + setting_autofetch_changesets: 'Automatski kupi "commit"-e' + setting_sys_api_enabled: 'Omogući "WS" za upravljanje repozitorijom' + setting_commit_ref_keywords: KljuÄne rijeÄi za reference + setting_commit_fix_keywords: 'KljuÄne rijeÄi za status "zatvoreno"' + setting_autologin: Automatski login + setting_date_format: Format datuma + setting_time_format: Format vremena + setting_cross_project_issue_relations: Omogući relacije izmeÄ‘u aktivnosti na razliÄitim projektima + setting_issue_list_default_columns: Podrazumjevane koleone za prikaz na listi aktivnosti + setting_emails_footer: Potpis na email-ovima + setting_protocol: Protokol + setting_per_page_options: Broj objekata po stranici + setting_user_format: Format korisniÄkog prikaza + setting_activity_days_default: Prikaz promjena na projektu - opseg dana + setting_display_subprojects_issues: Prikaz podprojekata na glavnom projektima (podrazumjeva se) + setting_enabled_scm: Omogući SCM (source code management) + setting_mail_handler_api_enabled: Omogući automatsku obradu ulaznih emailova + setting_mail_handler_api_key: API kljuÄ (obrada ulaznih mailova) + setting_sequential_project_identifiers: GeneriÅ¡i identifikatore projekta sekvencijalno + setting_gravatar_enabled: 'Koristi "gravatar" korisniÄke ikone' + setting_diff_max_lines_displayed: Maksimalan broj linija za prikaz razlika izmeÄ‘u dva fajla + setting_file_max_size_displayed: Maksimalna veliÄina fajla kod prikaza razlika unutar fajla (inline) + setting_repository_log_display_limit: Maksimalna veliÄina revizija prikazanih na log fajlu + setting_openid: Omogući OpenID prijavu i registraciju + + permission_edit_project: Ispravke projekta + permission_select_project_modules: Odaberi module projekta + permission_manage_members: Upravljanje Älanovima + permission_manage_versions: Upravljanje verzijama + permission_manage_categories: Upravljanje kategorijama aktivnosti + permission_add_issues: Dodaj aktivnosti + permission_edit_issues: Ispravka aktivnosti + permission_manage_issue_relations: Upravljaj relacijama meÄ‘u aktivnostima + permission_add_issue_notes: Dodaj biljeÅ¡ke + permission_edit_issue_notes: Ispravi biljeÅ¡ke + permission_edit_own_issue_notes: Ispravi sopstvene biljeÅ¡ke + permission_move_issues: Pomjeri aktivnosti + permission_delete_issues: IzbriÅ¡i aktivnosti + permission_manage_public_queries: Upravljaj javnim upitima + permission_save_queries: Snimi upite + permission_view_gantt: Pregled gantograma + permission_view_calendar: Pregled kalendara + permission_view_issue_watchers: Pregled liste korisnika koji prate aktivnost + permission_add_issue_watchers: Dodaj onoga koji prati aktivnost + permission_log_time: Evidentiraj utroÅ¡ak vremena + permission_view_time_entries: Pregled utroÅ¡ka vremena + permission_edit_time_entries: Ispravka utroÅ¡ka vremena + permission_edit_own_time_entries: Ispravka svog utroÅ¡ka vremena + permission_manage_news: Upravljaj novostima + permission_comment_news: Komentiraj novosti + permission_view_documents: Pregled dokumenata + permission_manage_files: Upravljaj fajlovima + permission_view_files: Pregled fajlova + permission_manage_wiki: Upravljaj wiki stranicama + permission_rename_wiki_pages: Ispravi wiki stranicu + permission_delete_wiki_pages: IzbriÅ¡i wiki stranicu + permission_view_wiki_pages: Pregled wiki sadržaja + permission_view_wiki_edits: Pregled wiki istorije + permission_edit_wiki_pages: Ispravka wiki stranica + permission_delete_wiki_pages_attachments: Brisanje fajlova prikaÄenih wiki-ju + permission_protect_wiki_pages: ZaÅ¡titi wiki stranicu + permission_manage_repository: Upravljaj repozitorijem + permission_browse_repository: Pregled repozitorija + permission_view_changesets: Pregled setova promjena + permission_commit_access: 'Pristup "commit"-u' + permission_manage_boards: Upravljaj forumima + permission_view_messages: Pregled poruka + permission_add_messages: Å alji poruke + permission_edit_messages: Ispravi poruke + permission_edit_own_messages: Ispravka sopstvenih poruka + permission_delete_messages: Prisanje poruka + permission_delete_own_messages: Brisanje sopstvenih poruka + + project_module_issue_tracking: Praćenje aktivnosti + project_module_time_tracking: Praćenje vremena + project_module_news: Novosti + project_module_documents: Dokumenti + project_module_files: Fajlovi + project_module_wiki: Wiki stranice + project_module_repository: Repozitorij + project_module_boards: Forumi + + label_user: Korisnik + label_user_plural: Korisnici + label_user_new: Novi korisnik + label_project: Projekat + label_project_new: Novi projekat + label_project_plural: Projekti + label_x_projects: + zero: 0 projekata + one: 1 projekat + other: "%{count} projekata" + label_project_all: Svi projekti + label_project_latest: Posljednji projekti + label_issue: Aktivnost + label_issue_new: Nova aktivnost + label_issue_plural: Aktivnosti + label_issue_view_all: Vidi sve aktivnosti + label_issues_by: "Aktivnosti po %{value}" + label_issue_added: Aktivnost je dodana + label_issue_updated: Aktivnost je izmjenjena + label_document: Dokument + label_document_new: Novi dokument + label_document_plural: Dokumenti + label_document_added: Dokument je dodan + label_role: Uloga + label_role_plural: Uloge + label_role_new: Nove uloge + label_role_and_permissions: Uloge i dozvole + label_member: IzvrÅ¡ilac + label_member_new: Novi izvrÅ¡ilac + label_member_plural: IzvrÅ¡ioci + label_tracker: PodruÄje aktivnosti + label_tracker_plural: PodruÄja aktivnosti + label_tracker_new: Novo podruÄje aktivnosti + label_workflow: Tok promjena na aktivnosti + label_issue_status: Status aktivnosti + label_issue_status_plural: Statusi aktivnosti + label_issue_status_new: Novi status + label_issue_category: Kategorija aktivnosti + label_issue_category_plural: Kategorije aktivnosti + label_issue_category_new: Nova kategorija + label_custom_field: Proizvoljno polje + label_custom_field_plural: Proizvoljna polja + label_custom_field_new: Novo proizvoljno polje + label_enumerations: Enumeracije + label_enumeration_new: Nova vrijednost + label_information: Informacija + label_information_plural: Informacije + label_please_login: Molimo prijavite se + label_register: Registracija + label_login_with_open_id_option: ili prijava sa OpenID-om + label_password_lost: Izgubljena lozinka + label_home: PoÄetna stranica + label_my_page: Moja stranica + label_my_account: Moj korisniÄki raÄun + label_my_projects: Moji projekti + label_administration: Administracija + label_login: Prijavi se + label_logout: Odjavi se + label_help: Pomoć + label_reported_issues: Prijavljene aktivnosti + label_assigned_to_me_issues: Aktivnosti dodjeljene meni + label_last_login: Posljednja konekcija + label_registered_on: Registrovan na + label_activity_plural: Promjene + label_activity: Operacija + label_overall_activity: Pregled svih promjena + label_user_activity: "Promjene izvrÅ¡ene od: %{value}" + label_new: Novi + label_logged_as: Prijavljen kao + label_environment: Sistemsko okruženje + label_authentication: Authentifikacija + label_auth_source: Mod authentifikacije + label_auth_source_new: Novi mod authentifikacije + label_auth_source_plural: Modovi authentifikacije + label_subproject_plural: Podprojekti + label_and_its_subprojects: "%{value} i njegovi podprojekti" + label_min_max_length: Min - Maks dužina + label_list: Lista + label_date: Datum + label_integer: Cijeli broj + label_float: Float + label_boolean: LogiÄka varijabla + label_string: Tekst + label_text: Dugi tekst + label_attribute: Atribut + label_attribute_plural: Atributi + label_no_data: Nema podataka za prikaz + label_change_status: Promjeni status + label_history: Istorija + label_attachment: Fajl + label_attachment_new: Novi fajl + label_attachment_delete: IzbriÅ¡i fajl + label_attachment_plural: Fajlovi + label_file_added: Fajl je dodan + label_report: IzvjeÅ¡taj + label_report_plural: IzvjeÅ¡taji + label_news: Novosti + label_news_new: Dodaj novosti + label_news_plural: Novosti + label_news_latest: Posljednje novosti + label_news_view_all: Pogledaj sve novosti + label_news_added: Novosti su dodane + label_settings: Postavke + label_overview: Pregled + label_version: Verzija + label_version_new: Nova verzija + label_version_plural: Verzije + label_confirmation: Potvrda + label_export_to: 'TakoÄ‘e dostupno u:' + label_read: ÄŒitaj... + label_public_projects: Javni projekti + label_open_issues: otvoren + label_open_issues_plural: otvoreni + label_closed_issues: zatvoren + label_closed_issues_plural: zatvoreni + label_x_open_issues_abbr_on_total: + zero: 0 otvoreno / %{total} + one: 1 otvorena / %{total} + other: "%{count} otvorene / %{total}" + label_x_open_issues_abbr: + zero: 0 otvoreno + one: 1 otvorena + other: "%{count} otvorene" + label_x_closed_issues_abbr: + zero: 0 zatvoreno + one: 1 zatvorena + other: "%{count} zatvorene" + label_total: Ukupno + label_permissions: Dozvole + label_current_status: Tekući status + label_new_statuses_allowed: Novi statusi dozvoljeni + label_all: sve + label_none: niÅ¡ta + label_nobody: niko + label_next: Sljedeće + label_previous: Predhodno + label_used_by: KoriÅ¡teno od + label_details: Detalji + label_add_note: Dodaj biljeÅ¡ku + label_per_page: Po stranici + label_calendar: Kalendar + label_months_from: mjeseci od + label_gantt: Gantt + label_internal: Interno + label_last_changes: "posljednjih %{count} promjena" + label_change_view_all: Vidi sve promjene + label_personalize_page: Personaliziraj ovu stranicu + label_comment: Komentar + label_comment_plural: Komentari + label_x_comments: + zero: bez komentara + one: 1 komentar + other: "%{count} komentari" + label_comment_add: Dodaj komentar + label_comment_added: Komentar je dodan + label_comment_delete: IzbriÅ¡i komentar + label_query: Proizvoljan upit + label_query_plural: Proizvoljni upiti + label_query_new: Novi upit + label_filter_add: Dodaj filter + label_filter_plural: Filteri + label_equals: je + label_not_equals: nije + label_in_less_than: je manji nego + label_in_more_than: je viÅ¡e nego + label_in: u + label_today: danas + label_all_time: sve vrijeme + label_yesterday: juÄe + label_this_week: ova hefta + label_last_week: zadnja hefta + label_last_n_days: "posljednjih %{count} dana" + label_this_month: ovaj mjesec + label_last_month: posljednji mjesec + label_this_year: ova godina + label_date_range: Datumski opseg + label_less_than_ago: ranije nego (dana) + label_more_than_ago: starije nego (dana) + label_ago: prije (dana) + label_contains: sadrži + label_not_contains: ne sadrži + label_day_plural: dani + label_repository: Repozitorij + label_repository_plural: Repozitoriji + label_browse: Listaj + label_revision: Revizija + label_revision_plural: Revizije + label_associated_revisions: Doddjeljene revizije + label_added: dodano + label_modified: izmjenjeno + label_copied: kopirano + label_renamed: preimenovano + label_deleted: izbrisano + label_latest_revision: Posljednja revizija + label_latest_revision_plural: Posljednje revizije + label_view_revisions: Vidi revizije + label_max_size: Maksimalna veliÄina + label_sort_highest: Pomjeri na vrh + label_sort_higher: Pomjeri gore + label_sort_lower: Pomjeri dole + label_sort_lowest: Pomjeri na dno + label_roadmap: Plan realizacije + label_roadmap_due_in: "Obavezan do %{value}" + label_roadmap_overdue: "%{value} kasni" + label_roadmap_no_issues: Nema aktivnosti za ovu verziju + label_search: Traži + label_result_plural: Rezultati + label_all_words: Sve rijeÄi + label_wiki: Wiki stranice + label_wiki_edit: ispravka wiki-ja + label_wiki_edit_plural: ispravke wiki-ja + label_wiki_page: Wiki stranica + label_wiki_page_plural: Wiki stranice + label_index_by_title: Indeks prema naslovima + label_index_by_date: Indeks po datumima + label_current_version: Tekuća verzija + label_preview: Pregled + label_feed_plural: Feeds + label_changes_details: Detalji svih promjena + label_issue_tracking: Evidencija aktivnosti + label_spent_time: UtroÅ¡ak vremena + label_f_hour: "%{value} sahat" + label_f_hour_plural: "%{value} sahata" + label_time_tracking: Evidencija vremena + label_change_plural: Promjene + label_statistics: Statistika + label_commits_per_month: '"Commit"-a po mjesecu' + label_commits_per_author: '"Commit"-a po autoru' + label_view_diff: Pregled razlika + label_diff_inline: zajedno + label_diff_side_by_side: jedna pored druge + label_options: Opcije + label_copy_workflow_from: Kopiraj tok promjena statusa iz + label_permissions_report: IzvjeÅ¡taj + label_watched_issues: Aktivnosti koje pratim + label_related_issues: Korelirane aktivnosti + label_applied_status: Status je primjenjen + label_loading: UÄitavam... + label_relation_new: Nova relacija + label_relation_delete: IzbriÅ¡i relaciju + label_relates_to: korelira sa + label_duplicates: duplikat + label_duplicated_by: duplicirano od + label_blocks: blokira + label_blocked_by: blokirano on + label_precedes: predhodi + label_follows: slijedi + label_end_to_start: 'kraj -> poÄetak' + label_end_to_end: 'kraja -> kraj' + label_start_to_start: 'poÄetak -> poÄetak' + label_start_to_end: 'poÄetak -> kraj' + label_stay_logged_in: Ostani prijavljen + label_disabled: onemogućen + label_show_completed_versions: Prikaži zavrÅ¡ene verzije + label_me: ja + label_board: Forum + label_board_new: Novi forum + label_board_plural: Forumi + label_topic_plural: Teme + label_message_plural: Poruke + label_message_last: Posljednja poruka + label_message_new: Nova poruka + label_message_posted: Poruka je dodana + label_reply_plural: Odgovori + label_send_information: PoÅ¡alji informaciju o korisniÄkom raÄunu + label_year: Godina + label_month: Mjesec + label_week: Hefta + label_date_from: Od + label_date_to: Do + label_language_based: Bazirano na korisnikovom jeziku + label_sort_by: "Sortiraj po %{value}" + label_send_test_email: PoÅ¡alji testni email + label_feeds_access_key_created_on: "Atom pristupni kljuÄ kreiran prije %{value} dana" + label_module_plural: Moduli + label_added_time_by: "Dodano od %{author} prije %{age}" + label_updated_time_by: "Izmjenjeno od %{author} prije %{age}" + label_updated_time: "Izmjenjeno prije %{value}" + label_jump_to_a_project: SkoÄi na projekat... + label_file_plural: Fajlovi + label_changeset_plural: Setovi promjena + label_default_columns: Podrazumjevane kolone + label_no_change_option: (Bez promjene) + label_bulk_edit_selected_issues: Ispravi odjednom odabrane aktivnosti + label_theme: Tema + label_default: Podrazumjevano + label_search_titles_only: Pretraži samo naslove + label_user_mail_option_all: "Za bilo koji dogaÄ‘aj na svim mojim projektima" + label_user_mail_option_selected: "Za bilo koji dogaÄ‘aj na odabranim projektima..." + label_user_mail_no_self_notified: "Ne želim notifikaciju za promjene koje sam ja napravio" + label_registration_activation_by_email: aktivacija korisniÄkog raÄuna email-om + label_registration_manual_activation: ruÄna aktivacija korisniÄkog raÄuna + label_registration_automatic_activation: automatska kreacija korisniÄkog raÄuna + label_display_per_page: "Po stranici: %{value}" + label_age: Starost + label_change_properties: Promjena osobina + label_general: Generalno + label_more: ViÅ¡e + label_scm: SCM + label_plugins: Plugin-ovi + label_ldap_authentication: LDAP authentifikacija + label_downloads_abbr: D/L + label_optional_description: Opis (opciono) + label_add_another_file: Dodaj joÅ¡ jedan fajl + label_preferences: Postavke + label_chronological_order: HronoloÅ¡ki poredak + label_reverse_chronological_order: Reverzni hronoloÅ¡ki poredak + label_planning: Planiranje + label_incoming_emails: Dolazni email-ovi + label_generate_key: GeneriÅ¡i kljuÄ + label_issue_watchers: Praćeno od + label_example: Primjer + label_display: Prikaz + + button_apply: Primjeni + button_add: Dodaj + button_archive: Arhiviranje + button_back: Nazad + button_cancel: Odustani + button_change: Izmjeni + button_change_password: Izmjena lozinke + button_check_all: OznaÄi sve + button_clear: BriÅ¡i + button_copy: Kopiraj + button_create: Novi + button_delete: BriÅ¡i + button_download: Download + button_edit: Ispravka + button_list: Lista + button_lock: ZakljuÄaj + button_log_time: UtroÅ¡ak vremena + button_login: Prijava + button_move: Pomjeri + button_rename: Promjena imena + button_reply: Odgovor + button_reset: Resetuj + button_rollback: Vrati predhodno stanje + button_save: Snimi + button_sort: Sortiranje + button_submit: PoÅ¡alji + button_test: Testiraj + button_unarchive: Otpakuj arhivu + button_uncheck_all: IskljuÄi sve + button_unlock: OtkljuÄaj + button_unwatch: Prekini notifikaciju + button_update: Promjena na aktivnosti + button_view: Pregled + button_watch: Notifikacija + button_configure: Konfiguracija + button_quote: Citat + + status_active: aktivan + status_registered: registrovan + status_locked: zakljuÄan + + text_select_mail_notifications: Odaberi dogaÄ‘aje za koje će se slati email notifikacija. + text_regexp_info: npr. ^[A-Z0-9]+$ + text_min_max_length_info: 0 znaÄi bez restrikcije + text_project_destroy_confirmation: Sigurno želite izbrisati ovaj projekat i njegove podatke ? + text_subprojects_destroy_warning: "Podprojekt(i): %{value} će takoÄ‘e biti izbrisani." + text_workflow_edit: Odaberite ulogu i podruÄje aktivnosti za ispravku toka promjena na aktivnosti + text_are_you_sure: Da li ste sigurni ? + text_tip_issue_begin_day: zadatak poÄinje danas + text_tip_issue_end_day: zadatak zavrÅ¡ava danas + text_tip_issue_begin_end_day: zadatak zapoÄinje i zavrÅ¡ava danas + text_caracters_maximum: "maksimum %{count} karaktera." + text_caracters_minimum: "Dužina mora biti najmanje %{count} znakova." + text_length_between: "Broj znakova izmeÄ‘u %{min} i %{max}." + text_tracker_no_workflow: Tok statusa nije definisan za ovo podruÄje aktivnosti + text_unallowed_characters: Nedozvoljeni znakovi + text_comma_separated: ViÅ¡estruke vrijednosti dozvoljene (odvojiti zarezom). + text_issues_ref_in_commit_messages: 'Referenciranje i zatvaranje aktivnosti putem "commit" poruka' + text_issue_added: "Aktivnost %{id} je prijavljena od %{author}." + text_issue_updated: "Aktivnost %{id} je izmjenjena od %{author}." + text_wiki_destroy_confirmation: Sigurno želite izbrisati ovaj wiki i Äitav njegov sadržaj ? + text_issue_category_destroy_question: "Neke aktivnosti (%{count}) pripadaju ovoj kategoriji. Sigurno to želite uraditi ?" + text_issue_category_destroy_assignments: Ukloni kategoriju + text_issue_category_reassign_to: Ponovo dodijeli ovu kategoriju + text_user_mail_option: "Za projekte koje niste odabrali, primićete samo notifikacije o stavkama koje pratite ili ste u njih ukljuÄeni (npr. vi ste autor ili su vama dodjeljenje)." + text_no_configuration_data: "Uloge, podruÄja aktivnosti, statusi aktivnosti i tok promjena statusa nisu konfigurisane.\nKrajnje je preporuÄeno da uÄitate tekuÄ‘e postavke. Kasnije ćete ih moći mjenjati po svojim potrebama." + text_load_default_configuration: UÄitaj tekuću konfiguraciju + text_status_changed_by_changeset: "Primjenjeno u setu promjena %{value}." + text_issues_destroy_confirmation: 'Sigurno želite izbrisati odabranu/e aktivnost/i ?' + text_select_project_modules: 'Odaberi module koje želite u ovom projektu:' + text_default_administrator_account_changed: Tekući administratorski raÄun je promjenjen + text_file_repository_writable: U direktorij sa fajlovima koji su prilozi se može pisati + text_plugin_assets_writable: U direktorij plugin-ova se može pisati + text_rmagick_available: RMagick je dostupan (opciono) + text_destroy_time_entries_question: "%{hours} sahata je prijavljeno na aktivnostima koje želite brisati. Želite li to uÄiniti ?" + text_destroy_time_entries: IzbriÅ¡i prijavljeno vrijeme + text_assign_time_entries_to_project: Dodaj prijavljenoo vrijeme projektu + text_reassign_time_entries: 'Preraspodjeli prijavljeno vrijeme na ovu aktivnost:' + text_user_wrote: "%{value} je napisao/la:" + text_enumeration_destroy_question: "Za %{count} objekata je dodjeljenja ova vrijednost." + text_enumeration_category_reassign_to: 'Ponovo im dodjeli ovu vrijednost:' + text_email_delivery_not_configured: "Email dostava nije konfiguraisana, notifikacija je onemogućena.\nKonfiguriÅ¡i SMTP server u config/configuration.yml i restartuj aplikaciju nakon toga." + text_repository_usernames_mapping: "Odaberi ili ispravi redmine korisnika mapiranog za svako korisniÄko ima naÄ‘eno u logu repozitorija.\nKorisnici sa istim imenom u redmineu i u repozitoruju se automatski mapiraju." + text_diff_truncated: '... Ovaj prikaz razlike je odsjeÄen poÅ¡to premaÅ¡uje maksimalnu veliÄinu za prikaz' + text_custom_field_possible_values_info: 'Jedna linija za svaku vrijednost' + + default_role_manager: Menadžer + default_role_developer: Programer + default_role_reporter: Reporter + default_tracker_bug: GreÅ¡ka + default_tracker_feature: Nova funkcija + default_tracker_support: PodrÅ¡ka + default_issue_status_new: Novi + default_issue_status_in_progress: In Progress + default_issue_status_resolved: RijeÅ¡en + default_issue_status_feedback: ÄŒeka se povratna informacija + default_issue_status_closed: Zatvoren + default_issue_status_rejected: Odbijen + default_doc_category_user: KorisniÄka dokumentacija + default_doc_category_tech: TehniÄka dokumentacija + default_priority_low: Nizak + default_priority_normal: Normalan + default_priority_high: Visok + default_priority_urgent: Urgentno + default_priority_immediate: Odmah + default_activity_design: Dizajn + default_activity_development: Programiranje + + enumeration_issue_priorities: Prioritet aktivnosti + enumeration_doc_categories: Kategorije dokumenata + enumeration_activities: Operacije (utroÅ¡ak vremena) + notice_unable_delete_version: Ne mogu izbrisati verziju. + button_create_and_continue: Kreiraj i nastavi + button_annotate: Zabilježi + button_activate: Aktiviraj + label_sort: Sortiranje + label_date_from_to: Od %{start} do %{end} + label_ascending: Rastuće + label_descending: Opadajuće + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? + text_wiki_page_reassign_children: Reassign child pages to this parent page + text_wiki_page_nullify_children: Keep child pages as root pages + text_wiki_page_destroy_children: Delete child pages and all their descendants + setting_password_min_length: Minimum password length + field_group_by: Group results by + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + label_wiki_content_added: Wiki page added + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: The '%{id}' wiki page has been added by %{author}. + label_wiki_content_updated: Wiki page updated + mail_body_wiki_content_updated: The '%{id}' wiki page has been updated by %{author}. + permission_add_project: Create project + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + label_view_all_revisions: View all revisions + label_tag: Tag + label_branch: Branch + error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. + error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). + text_journal_changed: "%{label} changed from %{old} to %{new}" + text_journal_set_to: "%{label} set to %{value}" + text_journal_deleted: "%{label} deleted (%{old})" + label_group_plural: Groups + label_group: Group + label_group_new: New group + label_time_entry_plural: Spent time + text_journal_added: "%{label} %{value} added" + field_active: Active + enumeration_system_activity: System Activity + permission_delete_issue_watchers: Delete watchers + version_status_closed: closed + version_status_locked: locked + version_status_open: open + error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened + label_user_anonymous: Anonymous + button_move_and_follow: Move and follow + setting_default_projects_modules: Default enabled modules for new projects + setting_gravatar_default: Default Gravatar image + field_sharing: Sharing + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_system: With all projects + label_version_sharing_descendants: With subprojects + label_version_sharing_tree: With project tree + label_version_sharing_none: Not shared + error_can_not_archive_project: This project can not be archived + button_duplicate: Duplicate + button_copy_and_follow: Copy and follow + label_copy_source: Source + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_status: Use the issue status + error_issue_done_ratios_not_updated: Issue done ratios not updated. + error_workflow_copy_target: Please select target tracker(s) and role(s) + setting_issue_done_ratio_issue_field: Use the issue field + label_copy_same_as_target: Same as target + label_copy_target: Target + notice_issue_done_ratios_updated: Issue done ratios updated. + error_workflow_copy_source: Please select a source tracker or role + label_update_issue_done_ratios: Update issue done ratios + setting_start_of_week: Start calendars on + permission_view_issues: View Issues + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_revision_id: Revision %{value} + label_api_access_key: API access key + label_api_access_key_created_on: API access key created %{value} ago + label_feeds_access_key: Atom access key + notice_api_access_key_reseted: Your API access key was reset. + setting_rest_api_enabled: Enable REST web service + label_missing_api_access_key: Missing an API access key + label_missing_feeds_access_key: Missing a Atom access key + button_show: Show + text_line_separated: Multiple values allowed (one line for each value). + setting_mail_handler_body_delimiters: Truncate emails after one of these lines + permission_add_subprojects: Create subprojects + label_subproject_new: New subproject + text_own_membership_delete_confirmation: |- + You are about to remove some or all of your permissions and may no longer be able to edit this project after that. + Are you sure you want to continue? + label_close_versions: Close completed versions + label_board_sticky: Sticky + label_board_locked: Locked + permission_export_wiki_pages: Export wiki pages + setting_cache_formatted_text: Cache formatted text + permission_manage_project_activities: Manage project activities + error_unable_delete_issue_status: Unable to delete issue status + label_profile: Profile + permission_manage_subtasks: Manage subtasks + field_parent_issue: Parent task + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + error_can_not_delete_custom_field: Unable to delete custom field + error_unable_to_connect: Unable to connect (%{value}) + error_can_not_remove_role: This role is in use and can not be deleted. + error_can_not_delete_tracker: This tracker contains issues and can't be deleted. + field_principal: Principal + label_my_page_block: My page block + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + text_zoom_out: Zoom out + text_zoom_in: Zoom in + notice_unable_delete_time_entry: Unable to delete time log entry. + label_overall_spent_time: Overall spent time + field_time_entries: Log time + project_module_gantt: Gantt + project_module_calendar: Calendar + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: 'Enkodiranje "commit" poruka' + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 aktivnost + one: 1 aktivnost + other: "%{count} aktivnosti" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: sve + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: With subprojects + label_cross_project_tree: With project tree + label_cross_project_hierarchy: With project hierarchy + label_cross_project_system: With all projects + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/002a1096c30994f0a75d2638aabe3bf4e1d79ef4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/002a1096c30994f0a75d2638aabe3bf4e1d79ef4.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

Wiki記法 クイックリファレンス

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
フォントスタイル
太字*太字*太字
斜体_斜体_斜体
下線+下線+下線
å–り消ã—ç·š-å–り消ã—ç·š-å–り消ã—ç·š
??引用??引用
コード@コード@コード
整形済ã¿ãƒ†ã‚­ã‚¹ãƒˆ<pre>
 è¤‡æ•°è¡Œã®
 ã‚³ãƒ¼ãƒ‰
</pre>
+
+複数行ã®
+コード
+
+
リスト
リスト* 項目1
* é …ç›®2
  • é …ç›®1
  • é …ç›®2
é †åºä»˜ãリスト# é …ç›®1
# é …ç›®2
  1. é …ç›®1
  2. é …ç›®2
見出ã—
見出ã—1h1. タイトル1

タイトル1

見出ã—2h2. タイトル2

タイトル2

見出ã—3h3. タイトル3

タイトル3

リンク
http://foo.barhttp://foo.bar
"Foo":http://foo.barFoo
Redmine内ã®ãƒªãƒ³ã‚¯
Wikiページã¸ã®ãƒªãƒ³ã‚¯[[Wiki page]]Wiki page
ãƒã‚±ãƒƒãƒˆ #12ãƒã‚±ãƒƒãƒˆ #12
リビジョン r43リビジョン r43
commit:f30e13e43f30e13e4
source:some/filesource:some/file
ç”»åƒ
Image!ç”»åƒURL!
!添付ファイルå!
+ +

より詳細ãªãƒªãƒ•ァレンス

+ + + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/0065f42c8aab54558480736376f69f7b72cabc58.svn-base --- a/.svn/pristine/00/0065f42c8aab54558480736376f69f7b72cabc58.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -require 'test/unit' -require 'rubygems' - -gem 'activesupport' -require 'active_support' - -gem 'actionpack' -require 'action_controller' - -gem 'mocha' -require 'mocha' - -gem 'ruby-openid' -require 'openid' - -RAILS_ROOT = File.dirname(__FILE__) unless defined? RAILS_ROOT -require File.dirname(__FILE__) + "/../lib/open_id_authentication" diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/006e32915b2ea0756e649e1fb61801b416e2d647.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/006e32915b2ea0756e649e1fb61801b416e2d647.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,88 @@ +# ActsAsWatchable +module Redmine + module Acts + module Watchable + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def acts_as_watchable(options = {}) + return if self.included_modules.include?(Redmine::Acts::Watchable::InstanceMethods) + class_eval do + has_many :watchers, :as => :watchable, :dependent => :delete_all + has_many :watcher_users, :through => :watchers, :source => :user, :validate => false + + scope :watched_by, lambda { |user_id| + joins(:watchers). + where("#{Watcher.table_name}.user_id = ?", user_id) + } + attr_protected :watcher_ids, :watcher_user_ids + end + send :include, Redmine::Acts::Watchable::InstanceMethods + alias_method_chain :watcher_user_ids=, :uniq_ids + end + end + + module InstanceMethods + def self.included(base) + base.extend ClassMethods + end + + # Returns an array of users that are proposed as watchers + def addable_watcher_users + users = self.project.users.sort - self.watcher_users + if respond_to?(:visible?) + users.reject! {|user| !visible?(user)} + end + users + end + + # Adds user as a watcher + def add_watcher(user) + self.watchers << Watcher.new(:user => user) + end + + # Removes user from the watchers list + def remove_watcher(user) + return nil unless user && user.is_a?(User) + watchers.where(:user_id => user.id).delete_all + end + + # Adds/removes watcher + def set_watcher(user, watching=true) + watching ? add_watcher(user) : remove_watcher(user) + end + + # Overrides watcher_user_ids= to make user_ids uniq + def watcher_user_ids_with_uniq_ids=(user_ids) + if user_ids.is_a?(Array) + user_ids = user_ids.uniq + end + send :watcher_user_ids_without_uniq_ids=, user_ids + end + + # Returns true if object is watched by +user+ + def watched_by?(user) + !!(user && self.watcher_user_ids.detect {|uid| uid == user.id }) + end + + def notified_watchers + notified = watcher_users.active + notified.reject! {|user| user.mail.blank? || user.mail_notification == 'none'} + if respond_to?(:visible?) + notified.reject! {|user| !visible?(user)} + end + notified + end + + # Returns an array of watchers' email addresses + def watcher_recipients + notified_watchers.collect(&:mail) + end + + module ClassMethods; end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/009460d580c5e7ab7c5519f32165ee2250a363d7.svn-base --- a/.svn/pristine/00/009460d580c5e7ab7c5519f32165ee2250a363d7.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,229 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class Redmine::UnifiedDiffTest < ActiveSupport::TestCase - def test_subversion_diff - diff = Redmine::UnifiedDiff.new(read_diff_fixture('subversion.diff')) - # number of files - assert_equal 4, diff.size - assert diff.detect {|file| file.file_name =~ %r{^config/settings.yml}} - end - - def test_truncate_diff - diff = Redmine::UnifiedDiff.new(read_diff_fixture('subversion.diff'), :max_lines => 20) - assert_equal 2, diff.size - end - - def test_inline_partials - diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff')) - assert_equal 1, diff.size - diff = diff.first - assert_equal 43, diff.size - - assert_equal [51, -1], diff[0].offsets - assert_equal [51, -1], diff[1].offsets - assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', diff[0].html_line - assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing xx', diff[1].html_line - - assert_nil diff[2].offsets - assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[2].html_line - - assert_equal [0, -14], diff[3].offsets - assert_equal [0, -14], diff[4].offsets - assert_equal 'Ut sed auctor justo', diff[3].html_line - assert_equal 'xxx auctor justo', diff[4].html_line - - assert_equal [13, -19], diff[6].offsets - assert_equal [13, -19], diff[7].offsets - - assert_equal [24, -8], diff[9].offsets - assert_equal [24, -8], diff[10].offsets - - assert_equal [37, -1], diff[12].offsets - assert_equal [37, -1], diff[13].offsets - - assert_equal [0, -38], diff[15].offsets - assert_equal [0, -38], diff[16].offsets - end - - def test_side_by_side_partials - diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff'), :type => 'sbs') - assert_equal 1, diff.size - diff = diff.first - assert_equal 32, diff.size - - assert_equal [51, -1], diff[0].offsets - assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', diff[0].html_line_left - assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing xx', diff[0].html_line_right - - assert_nil diff[1].offsets - assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_left - assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_right - - assert_equal [0, -14], diff[2].offsets - assert_equal 'Ut sed auctor justo', diff[2].html_line_left - assert_equal 'xxx auctor justo', diff[2].html_line_right - - assert_equal [13, -19], diff[4].offsets - assert_equal [24, -8], diff[6].offsets - assert_equal [37, -1], diff[8].offsets - assert_equal [0, -38], diff[10].offsets - - end - - def test_partials_with_html_entities - raw = <<-DIFF ---- test.orig.txt Wed Feb 15 16:10:39 2012 -+++ test.new.txt Wed Feb 15 16:11:25 2012 -@@ -1,5 +1,5 @@ - Semicolons were mysteriously appearing in code diffs in the repository - --void DoSomething(std::auto_ptr myObj) -+void DoSomething(const MyClass& myObj) - -DIFF - - diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs') - assert_equal 1, diff.size - assert_equal 'void DoSomething(std::auto_ptr<MyClass> myObj)', diff.first[2].html_line_left - assert_equal 'void DoSomething(const MyClass& myObj)', diff.first[2].html_line_right - - diff = Redmine::UnifiedDiff.new(raw, :type => 'inline') - assert_equal 1, diff.size - assert_equal 'void DoSomething(std::auto_ptr<MyClass> myObj)', diff.first[2].html_line - assert_equal 'void DoSomething(const MyClass& myObj)', diff.first[3].html_line - end - - def test_line_starting_with_dashes - diff = Redmine::UnifiedDiff.new(<<-DIFF ---- old.txt Wed Nov 11 14:24:58 2009 -+++ new.txt Wed Nov 11 14:25:02 2009 -@@ -1,8 +1,4 @@ --Lines that starts with dashes: -- -------------------------- ---- file.c -------------------------- -+A line that starts with dashes: - - and removed. - -@@ -23,4 +19,4 @@ - - - --Another chunk of change -+Another chunk of changes - -DIFF - ) - assert_equal 1, diff.size - end - - def test_one_line_new_files - diff = Redmine::UnifiedDiff.new(<<-DIFF -diff -r 000000000000 -r ea98b14f75f0 README1 ---- /dev/null -+++ b/README1 -@@ -0,0 +1,1 @@ -+test1 -diff -r 000000000000 -r ea98b14f75f0 README2 ---- /dev/null -+++ b/README2 -@@ -0,0 +1,1 @@ -+test2 -diff -r 000000000000 -r ea98b14f75f0 README3 ---- /dev/null -+++ b/README3 -@@ -0,0 +1,3 @@ -+test4 -+test5 -+test6 -diff -r 000000000000 -r ea98b14f75f0 README4 ---- /dev/null -+++ b/README4 -@@ -0,0 +1,3 @@ -+test4 -+test5 -+test6 -DIFF - ) - assert_equal 4, diff.size - assert_equal "README1", diff[0].file_name - end - - def test_both_git_diff - diff = Redmine::UnifiedDiff.new(<<-DIFF -# HG changeset patch -# User test -# Date 1348014182 -32400 -# Node ID d1c871b8ef113df7f1c56d41e6e3bfbaff976e1f -# Parent 180b6605936cdc7909c5f08b59746ec1a7c99b3e -modify test1.txt - -diff -r 180b6605936c -r d1c871b8ef11 test1.txt ---- a/test1.txt -+++ b/test1.txt -@@ -1,1 +1,1 @@ --test1 -+modify test1 -DIFF - ) - assert_equal 1, diff.size - assert_equal "test1.txt", diff[0].file_name - end - - def test_include_a_b_slash - diff = Redmine::UnifiedDiff.new(<<-DIFF ---- test1.txt -+++ b/test02.txt -@@ -1 +0,0 @@ --modify test1 -DIFF - ) - assert_equal 1, diff.size - assert_equal "b/test02.txt", diff[0].file_name - - diff = Redmine::UnifiedDiff.new(<<-DIFF ---- a/test1.txt -+++ a/test02.txt -@@ -1 +0,0 @@ --modify test1 -DIFF - ) - assert_equal 1, diff.size - assert_equal "a/test02.txt", diff[0].file_name - - diff = Redmine::UnifiedDiff.new(<<-DIFF ---- a/test1.txt -+++ test02.txt -@@ -1 +0,0 @@ --modify test1 -DIFF - ) - assert_equal 1, diff.size - assert_equal "test02.txt", diff[0].file_name - end - - private - - def read_diff_fixture(filename) - File.new(File.join(File.dirname(__FILE__), '/../../../fixtures/diffs', filename)).read - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/0097cb632567412c9ae3b42993114578789fd825.svn-base --- a/.svn/pristine/00/0097cb632567412c9ae3b42993114578789fd825.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class VersionCustomField < CustomField - def type_name - :label_version_plural - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/00c1a9b51dc321ecb980f25b62c13dfa7fd628ff.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/00c1a9b51dc321ecb980f25b62c13dfa7fd628ff.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,24 @@ +<%= error_messages_for 'auth_source' %> + +
+

<%= f.text_field :name, :required => true %>

+

<%= f.text_field :host, :required => true %>

+

<%= f.text_field :port, :required => true, :size => 6 %> <%= f.check_box :tls, :no_label => true %> LDAPS

+

<%= f.text_field :account %>

+

<%= f.password_field :account_password, :label => :field_password, + :name => 'dummy_password', + :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), + :onfocus => "this.value=''; this.name='auth_source[account_password]';", + :onchange => "this.name='auth_source[account_password]';" %>

+

<%= f.text_field :base_dn, :required => true, :size => 60 %>

+

<%= f.text_field :filter, :size => 60, :label => :field_auth_source_ldap_filter %>

+

<%= f.text_field :timeout, :size => 4 %>

+

<%= f.check_box :onthefly_register, :label => :field_onthefly %>

+
+ +
<%=l(:label_attribute_plural)%> +

<%= f.text_field :attr_login, :required => true, :size => 20 %>

+

<%= f.text_field :attr_firstname, :size => 20 %>

+

<%= f.text_field :attr_lastname, :size => 20 %>

+

<%= f.text_field :attr_mail, :size => 20 %>

+
diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/00dbd874dfe947c78eb0cddc2e6a0cbc57c3f815.svn-base --- a/.svn/pristine/00/00dbd874dfe947c78eb0cddc2e6a0cbc57c3f815.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::DisabledRestApiTest < ActionController::IntegrationTest - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows - - def setup - Setting.rest_api_enabled = '0' - Setting.login_required = '1' - end - - def teardown - Setting.rest_api_enabled = '1' - Setting.login_required = '0' - end - - def test_with_a_valid_api_token - @user = User.generate! - @token = Token.create!(:user => @user, :action => 'api') - - get "/news.xml?key=#{@token.value}" - assert_response :unauthorized - assert_equal User.anonymous, User.current - - get "/news.json?key=#{@token.value}" - assert_response :unauthorized - assert_equal User.anonymous, User.current - end - - def test_with_valid_username_password_http_authentication - @user = User.generate! do |user| - user.password = 'my_password' - end - - get "/news.xml", nil, credentials(@user.login, 'my_password') - assert_response :unauthorized - assert_equal User.anonymous, User.current - - get "/news.json", nil, credentials(@user.login, 'my_password') - assert_response :unauthorized - assert_equal User.anonymous, User.current - end - - def test_with_valid_token_http_authentication - @user = User.generate! - @token = Token.create!(:user => @user, :action => 'api') - - get "/news.xml", nil, credentials(@token.value, 'X') - assert_response :unauthorized - assert_equal User.anonymous, User.current - - get "/news.json", nil, credentials(@token.value, 'X') - assert_response :unauthorized - assert_equal User.anonymous, User.current - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/00e064a58814bd1506484c9e41b6bc1f8553a834.svn-base --- a/.svn/pristine/00/00e064a58814bd1506484c9e41b6bc1f8553a834.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,795 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::IssuesTest < ActionController::IntegrationTest - fixtures :projects, - :users, - :roles, - :members, - :member_roles, - :issues, - :issue_statuses, - :issue_relations, - :versions, - :trackers, - :projects_trackers, - :issue_categories, - :enabled_modules, - :enumerations, - :attachments, - :workflows, - :custom_fields, - :custom_values, - :custom_fields_projects, - :custom_fields_trackers, - :time_entries, - :journals, - :journal_details, - :queries, - :attachments - - def setup - Setting.rest_api_enabled = '1' - end - - context "/issues" do - # Use a private project to make sure auth is really working and not just - # only showing public issues. - should_allow_api_authentication(:get, "/projects/private-child/issues.xml") - - should "contain metadata" do - get '/issues.xml' - - assert_tag :tag => 'issues', - :attributes => { - :type => 'array', - :total_count => assigns(:issue_count), - :limit => 25, - :offset => 0 - } - end - - context "with offset and limit" do - should "use the params" do - get '/issues.xml?offset=2&limit=3' - - assert_equal 3, assigns(:limit) - assert_equal 2, assigns(:offset) - assert_tag :tag => 'issues', :children => {:count => 3, :only => {:tag => 'issue'}} - end - end - - context "with nometa param" do - should "not contain metadata" do - get '/issues.xml?nometa=1' - - assert_tag :tag => 'issues', - :attributes => { - :type => 'array', - :total_count => nil, - :limit => nil, - :offset => nil - } - end - end - - context "with nometa header" do - should "not contain metadata" do - get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'} - - assert_tag :tag => 'issues', - :attributes => { - :type => 'array', - :total_count => nil, - :limit => nil, - :offset => nil - } - end - end - - context "with relations" do - should "display relations" do - get '/issues.xml?include=relations' - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag 'relations', - :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '3'}}, - :children => {:count => 1}, - :child => { - :tag => 'relation', - :attributes => {:id => '2', :issue_id => '2', :issue_to_id => '3', - :relation_type => 'relates'} - } - assert_tag 'relations', - :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '1'}}, - :children => {:count => 0} - end - end - - context "with invalid query params" do - should "return errors" do - get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}} - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Start date can't be blank"} - end - end - - context "with custom field filter" do - should "show only issues with the custom field value" do - get '/issues.xml', - {:set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, - :v => {:cf_1 => ['MySQL']}} - expected_ids = Issue.visible.all( - :include => :custom_values, - :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id) - assert_select 'issues > issue > id', :count => expected_ids.count do |ids| - ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } - end - end - end - - context "with custom field filter (shorthand method)" do - should "show only issues with the custom field value" do - get '/issues.xml', { :cf_1 => 'MySQL' } - - expected_ids = Issue.visible.all( - :include => :custom_values, - :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id) - - assert_select 'issues > issue > id', :count => expected_ids.count do |ids| - ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } - end - end - end - end - - context "/index.json" do - should_allow_api_authentication(:get, "/projects/private-child/issues.json") - end - - context "/index.xml with filter" do - should "show only issues with the status_id" do - get '/issues.xml?status_id=5' - - expected_ids = Issue.visible.all(:conditions => {:status_id => 5}).map(&:id) - - assert_select 'issues > issue > id', :count => expected_ids.count do |ids| - ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } - end - end - end - - context "/index.json with filter" do - should "show only issues with the status_id" do - get '/issues.json?status_id=5' - - json = ActiveSupport::JSON.decode(response.body) - status_ids_used = json['issues'].collect {|j| j['status']['id'] } - assert_equal 3, status_ids_used.length - assert status_ids_used.all? {|id| id == 5 } - end - - end - - # Issue 6 is on a private project - context "/issues/6.xml" do - should_allow_api_authentication(:get, "/issues/6.xml") - end - - context "/issues/6.json" do - should_allow_api_authentication(:get, "/issues/6.json") - end - - context "GET /issues/:id" do - context "with journals" do - context ".xml" do - should "display journals" do - get '/issues/1.xml?include=journals' - - assert_tag :tag => 'issue', - :child => { - :tag => 'journals', - :attributes => { :type => 'array' }, - :child => { - :tag => 'journal', - :attributes => { :id => '1'}, - :child => { - :tag => 'details', - :attributes => { :type => 'array' }, - :child => { - :tag => 'detail', - :attributes => { :name => 'status_id' }, - :child => { - :tag => 'old_value', - :content => '1', - :sibling => { - :tag => 'new_value', - :content => '2' - } - } - } - } - } - } - end - end - end - - context "with custom fields" do - context ".xml" do - should "display custom fields" do - get '/issues/3.xml' - - assert_tag :tag => 'issue', - :child => { - :tag => 'custom_fields', - :attributes => { :type => 'array' }, - :child => { - :tag => 'custom_field', - :attributes => { :id => '1'}, - :child => { - :tag => 'value', - :content => 'MySQL' - } - } - } - - assert_nothing_raised do - Hash.from_xml(response.body).to_xml - end - end - end - end - - context "with multi custom fields" do - setup do - field = CustomField.find(1) - field.update_attribute :multiple, true - issue = Issue.find(3) - issue.custom_field_values = {1 => ['MySQL', 'Oracle']} - issue.save! - end - - context ".xml" do - should "display custom fields" do - get '/issues/3.xml' - assert_response :success - assert_tag :tag => 'issue', - :child => { - :tag => 'custom_fields', - :attributes => { :type => 'array' }, - :child => { - :tag => 'custom_field', - :attributes => { :id => '1'}, - :child => { - :tag => 'value', - :attributes => { :type => 'array' }, - :children => { :count => 2 } - } - } - } - - xml = Hash.from_xml(response.body) - custom_fields = xml['issue']['custom_fields'] - assert_kind_of Array, custom_fields - field = custom_fields.detect {|f| f['id'] == '1'} - assert_kind_of Hash, field - assert_equal ['MySQL', 'Oracle'], field['value'].sort - end - end - - context ".json" do - should "display custom fields" do - get '/issues/3.json' - assert_response :success - json = ActiveSupport::JSON.decode(response.body) - custom_fields = json['issue']['custom_fields'] - assert_kind_of Array, custom_fields - field = custom_fields.detect {|f| f['id'] == 1} - assert_kind_of Hash, field - assert_equal ['MySQL', 'Oracle'], field['value'].sort - end - end - end - - context "with empty value for multi custom field" do - setup do - field = CustomField.find(1) - field.update_attribute :multiple, true - issue = Issue.find(3) - issue.custom_field_values = {1 => ['']} - issue.save! - end - - context ".xml" do - should "display custom fields" do - get '/issues/3.xml' - assert_response :success - assert_tag :tag => 'issue', - :child => { - :tag => 'custom_fields', - :attributes => { :type => 'array' }, - :child => { - :tag => 'custom_field', - :attributes => { :id => '1'}, - :child => { - :tag => 'value', - :attributes => { :type => 'array' }, - :children => { :count => 0 } - } - } - } - - xml = Hash.from_xml(response.body) - custom_fields = xml['issue']['custom_fields'] - assert_kind_of Array, custom_fields - field = custom_fields.detect {|f| f['id'] == '1'} - assert_kind_of Hash, field - assert_equal [], field['value'] - end - end - - context ".json" do - should "display custom fields" do - get '/issues/3.json' - assert_response :success - json = ActiveSupport::JSON.decode(response.body) - custom_fields = json['issue']['custom_fields'] - assert_kind_of Array, custom_fields - field = custom_fields.detect {|f| f['id'] == 1} - assert_kind_of Hash, field - assert_equal [], field['value'].sort - end - end - end - - context "with attachments" do - context ".xml" do - should "display attachments" do - get '/issues/3.xml?include=attachments' - - assert_tag :tag => 'issue', - :child => { - :tag => 'attachments', - :children => {:count => 5}, - :child => { - :tag => 'attachment', - :child => { - :tag => 'filename', - :content => 'source.rb', - :sibling => { - :tag => 'content_url', - :content => 'http://www.example.com/attachments/download/4/source.rb' - } - } - } - } - end - end - end - - context "with subtasks" do - setup do - @c1 = Issue.create!( - :status_id => 1, :subject => "child c1", - :tracker_id => 1, :project_id => 1, :author_id => 1, - :parent_issue_id => 1 - ) - @c2 = Issue.create!( - :status_id => 1, :subject => "child c2", - :tracker_id => 1, :project_id => 1, :author_id => 1, - :parent_issue_id => 1 - ) - @c3 = Issue.create!( - :status_id => 1, :subject => "child c3", - :tracker_id => 1, :project_id => 1, :author_id => 1, - :parent_issue_id => @c1.id - ) - end - - context ".xml" do - should "display children" do - get '/issues/1.xml?include=children' - - assert_tag :tag => 'issue', - :child => { - :tag => 'children', - :children => {:count => 2}, - :child => { - :tag => 'issue', - :attributes => {:id => @c1.id.to_s}, - :child => { - :tag => 'subject', - :content => 'child c1', - :sibling => { - :tag => 'children', - :children => {:count => 1}, - :child => { - :tag => 'issue', - :attributes => {:id => @c3.id.to_s} - } - } - } - } - } - end - - context ".json" do - should "display children" do - get '/issues/1.json?include=children' - - json = ActiveSupport::JSON.decode(response.body) - assert_equal([ - { - 'id' => @c1.id, 'subject' => 'child c1', 'tracker' => {'id' => 1, 'name' => 'Bug'}, - 'children' => [{'id' => @c3.id, 'subject' => 'child c3', - 'tracker' => {'id' => 1, 'name' => 'Bug'} }] - }, - { 'id' => @c2.id, 'subject' => 'child c2', 'tracker' => {'id' => 1, 'name' => 'Bug'} } - ], - json['issue']['children']) - end - end - end - end - end - - context "POST /issues.xml" do - should_allow_api_authentication( - :post, - '/issues.xml', - {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, - {:success_code => :created} - ) - should "create an issue with the attributes" do - assert_difference('Issue.count') do - post '/issues.xml', - {:issue => {:project_id => 1, :subject => 'API test', - :tracker_id => 2, :status_id => 3}}, credentials('jsmith') - end - issue = Issue.first(:order => 'id DESC') - assert_equal 1, issue.project_id - assert_equal 2, issue.tracker_id - assert_equal 3, issue.status_id - assert_equal 'API test', issue.subject - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s} - end - end - - context "POST /issues.xml with failure" do - should "have an errors tag" do - assert_no_difference('Issue.count') do - post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith') - end - - assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} - end - end - - context "POST /issues.json" do - should_allow_api_authentication(:post, - '/issues.json', - {:issue => {:project_id => 1, :subject => 'API test', - :tracker_id => 2, :status_id => 3}}, - {:success_code => :created}) - - should "create an issue with the attributes" do - assert_difference('Issue.count') do - post '/issues.json', - {:issue => {:project_id => 1, :subject => 'API test', - :tracker_id => 2, :status_id => 3}}, - credentials('jsmith') - end - - issue = Issue.first(:order => 'id DESC') - assert_equal 1, issue.project_id - assert_equal 2, issue.tracker_id - assert_equal 3, issue.status_id - assert_equal 'API test', issue.subject - end - - end - - context "POST /issues.json with failure" do - should "have an errors element" do - assert_no_difference('Issue.count') do - post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith') - end - - json = ActiveSupport::JSON.decode(response.body) - assert json['errors'].include?("Subject can't be blank") - end - end - - # Issue 6 is on a private project - context "PUT /issues/6.xml" do - setup do - @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}} - end - - should_allow_api_authentication(:put, - '/issues/6.xml', - {:issue => {:subject => 'API update', :notes => 'A new note'}}, - {:success_code => :ok}) - - should "not create a new issue" do - assert_no_difference('Issue.count') do - put '/issues/6.xml', @parameters, credentials('jsmith') - end - end - - should "create a new journal" do - assert_difference('Journal.count') do - put '/issues/6.xml', @parameters, credentials('jsmith') - end - end - - should "add the note to the journal" do - put '/issues/6.xml', @parameters, credentials('jsmith') - - journal = Journal.last - assert_equal "A new note", journal.notes - end - - should "update the issue" do - put '/issues/6.xml', @parameters, credentials('jsmith') - - issue = Issue.find(6) - assert_equal "API update", issue.subject - end - - end - - context "PUT /issues/3.xml with custom fields" do - setup do - @parameters = { - :issue => {:custom_fields => [{'id' => '1', 'value' => 'PostgreSQL' }, - {'id' => '2', 'value' => '150'}]} - } - end - - should "update custom fields" do - assert_no_difference('Issue.count') do - put '/issues/3.xml', @parameters, credentials('jsmith') - end - - issue = Issue.find(3) - assert_equal '150', issue.custom_value_for(2).value - assert_equal 'PostgreSQL', issue.custom_value_for(1).value - end - end - - context "PUT /issues/3.xml with multi custom fields" do - setup do - field = CustomField.find(1) - field.update_attribute :multiple, true - @parameters = { - :issue => {:custom_fields => [{'id' => '1', 'value' => ['MySQL', 'PostgreSQL'] }, - {'id' => '2', 'value' => '150'}]} - } - end - - should "update custom fields" do - assert_no_difference('Issue.count') do - put '/issues/3.xml', @parameters, credentials('jsmith') - end - - issue = Issue.find(3) - assert_equal '150', issue.custom_value_for(2).value - assert_equal ['MySQL', 'PostgreSQL'], issue.custom_field_value(1).sort - end - end - - context "PUT /issues/3.xml with project change" do - setup do - @parameters = {:issue => {:project_id => 2, :subject => 'Project changed'}} - end - - should "update project" do - assert_no_difference('Issue.count') do - put '/issues/3.xml', @parameters, credentials('jsmith') - end - - issue = Issue.find(3) - assert_equal 2, issue.project_id - assert_equal 'Project changed', issue.subject - end - end - - context "PUT /issues/6.xml with failed update" do - setup do - @parameters = {:issue => {:subject => ''}} - end - - should "not create a new issue" do - assert_no_difference('Issue.count') do - put '/issues/6.xml', @parameters, credentials('jsmith') - end - end - - should "not create a new journal" do - assert_no_difference('Journal.count') do - put '/issues/6.xml', @parameters, credentials('jsmith') - end - end - - should "have an errors tag" do - put '/issues/6.xml', @parameters, credentials('jsmith') - - assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} - end - end - - context "PUT /issues/6.json" do - setup do - @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}} - end - - should_allow_api_authentication(:put, - '/issues/6.json', - {:issue => {:subject => 'API update', :notes => 'A new note'}}, - {:success_code => :ok}) - - should "update the issue" do - assert_no_difference('Issue.count') do - assert_difference('Journal.count') do - put '/issues/6.json', @parameters, credentials('jsmith') - - assert_response :ok - assert_equal '', response.body - end - end - - issue = Issue.find(6) - assert_equal "API update", issue.subject - journal = Journal.last - assert_equal "A new note", journal.notes - end - end - - context "PUT /issues/6.json with failed update" do - should "return errors" do - assert_no_difference('Issue.count') do - assert_no_difference('Journal.count') do - put '/issues/6.json', {:issue => {:subject => ''}}, credentials('jsmith') - - assert_response :unprocessable_entity - end - end - - json = ActiveSupport::JSON.decode(response.body) - assert json['errors'].include?("Subject can't be blank") - end - end - - context "DELETE /issues/1.xml" do - should_allow_api_authentication(:delete, - '/issues/6.xml', - {}, - {:success_code => :ok}) - - should "delete the issue" do - assert_difference('Issue.count', -1) do - delete '/issues/6.xml', {}, credentials('jsmith') - - assert_response :ok - assert_equal '', response.body - end - - assert_nil Issue.find_by_id(6) - end - end - - context "DELETE /issues/1.json" do - should_allow_api_authentication(:delete, - '/issues/6.json', - {}, - {:success_code => :ok}) - - should "delete the issue" do - assert_difference('Issue.count', -1) do - delete '/issues/6.json', {}, credentials('jsmith') - - assert_response :ok - assert_equal '', response.body - end - - assert_nil Issue.find_by_id(6) - end - end - - def test_create_issue_with_uploaded_file - set_tmp_attachments_directory - # upload the file - assert_difference 'Attachment.count' do - post '/uploads.xml', 'test_create_with_upload', - {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) - assert_response :created - end - xml = Hash.from_xml(response.body) - token = xml['upload']['token'] - attachment = Attachment.first(:order => 'id DESC') - - # create the issue with the upload's token - assert_difference 'Issue.count' do - post '/issues.xml', - {:issue => {:project_id => 1, :subject => 'Uploaded file', - :uploads => [{:token => token, :filename => 'test.txt', - :content_type => 'text/plain'}]}}, - credentials('jsmith') - assert_response :created - end - issue = Issue.first(:order => 'id DESC') - assert_equal 1, issue.attachments.count - assert_equal attachment, issue.attachments.first - - attachment.reload - assert_equal 'test.txt', attachment.filename - assert_equal 'text/plain', attachment.content_type - assert_equal 'test_create_with_upload'.size, attachment.filesize - assert_equal 2, attachment.author_id - - # get the issue with its attachments - get "/issues/#{issue.id}.xml", :include => 'attachments' - assert_response :success - xml = Hash.from_xml(response.body) - attachments = xml['issue']['attachments'] - assert_kind_of Array, attachments - assert_equal 1, attachments.size - url = attachments.first['content_url'] - assert_not_nil url - - # download the attachment - get url - assert_response :success - end - - def test_update_issue_with_uploaded_file - set_tmp_attachments_directory - # upload the file - assert_difference 'Attachment.count' do - post '/uploads.xml', 'test_upload_with_upload', - {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) - assert_response :created - end - xml = Hash.from_xml(response.body) - token = xml['upload']['token'] - attachment = Attachment.first(:order => 'id DESC') - - # update the issue with the upload's token - assert_difference 'Journal.count' do - put '/issues/1.xml', - {:issue => {:notes => 'Attachment added', - :uploads => [{:token => token, :filename => 'test.txt', - :content_type => 'text/plain'}]}}, - credentials('jsmith') - assert_response :ok - assert_equal '', @response.body - end - - issue = Issue.find(1) - assert_include attachment, issue.attachments - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/00/00e6c0514da25c76abd6063e97c17125ab6f9899.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/00e6c0514da25c76abd6063e97c17125ab6f9899.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,16 @@ +jsToolBar.strings = {}; +jsToolBar.strings['Strong'] = 'TuÄné'; +jsToolBar.strings['Italic'] = 'Kurzíva'; +jsToolBar.strings['Underline'] = 'PodÄiarknuté'; +jsToolBar.strings['Deleted'] = 'PreÅ¡krtnuté'; +jsToolBar.strings['Code'] = 'Zobrazenie kódu'; +jsToolBar.strings['Heading 1'] = 'Nadpis 1'; +jsToolBar.strings['Heading 2'] = 'Nadpis 2'; +jsToolBar.strings['Heading 3'] = 'Nadpis 3'; +jsToolBar.strings['Unordered list'] = 'Odrážkový zoznam'; +jsToolBar.strings['Ordered list'] = 'Číslovaný zoznam'; +jsToolBar.strings['Quote'] = 'Odsadenie'; +jsToolBar.strings['Unquote'] = 'ZruÅ¡iÅ¥ odsadenie'; +jsToolBar.strings['Preformatted text'] = 'Predformátovaný text'; +jsToolBar.strings['Wiki link'] = 'Odkaz na wikistránku'; +jsToolBar.strings['Image'] = 'Obrázok'; diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/01/01295ecfb41026f1bdc485aae7fd6a8c5187ac83.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/01/01295ecfb41026f1bdc485aae7fd6a8c5187ac83.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,58 @@ +
+<%= link_to l(:label_user_new), new_user_path, :class => 'icon icon-add' %> +
+ +

<%=l(:label_user_plural)%>

+ +<%= form_tag(users_path, :method => :get) do %> +
<%= l(:label_filter_plural) %> + +<%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> + +<% if @groups.present? %> + +<%= select_tag 'group_id', content_tag('option') + options_from_collection_for_select(@groups, :id, :name, params[:group_id].to_i), :onchange => "this.form.submit(); return false;" %> +<% end %> + + +<%= text_field_tag 'name', params[:name], :size => 30 %> +<%= submit_tag l(:button_apply), :class => "small", :name => nil %> +<%= link_to l(:button_clear), users_path, :class => 'icon icon-reload' %> +
+<% end %> +  + +
+ + + <%= sort_header_tag('login', :caption => l(:field_login)) %> + <%= sort_header_tag('firstname', :caption => l(:field_firstname)) %> + <%= sort_header_tag('lastname', :caption => l(:field_lastname)) %> + <%= sort_header_tag('mail', :caption => l(:field_mail)) %> + <%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %> + <%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %> + <%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %> + + + +<% for user in @users -%> + "> + + + + + + + + + +<% end -%> + +
<%= avatar(user, :size => "14") %><%= link_to h(user.login), edit_user_path(user) %><%= h(user.firstname) %><%= h(user.lastname) %><%= checked_image user.admin? %><%= format_time(user.created_on) %> + <%= change_status_link(user) %> + <%= delete_link user_path(user, :back_url => users_path(params)) unless User.current == user %> +
+
+

<%= pagination_links_full @user_pages, @user_count %>

+ +<% html_title(l(:label_user_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/01/013035c9e2d7a0734359dad136ab782f7272366b.svn-base --- a/.svn/pristine/01/013035c9e2d7a0734359dad136ab782f7272366b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -class CreateJournals < ActiveRecord::Migration - - # model removed, but needed for data migration - class IssueHistory < ActiveRecord::Base; belongs_to :issue; end - # model removed - class Permission < ActiveRecord::Base; end - - def self.up - create_table :journals, :force => true do |t| - t.column "journalized_id", :integer, :default => 0, :null => false - t.column "journalized_type", :string, :limit => 30, :default => "", :null => false - t.column "user_id", :integer, :default => 0, :null => false - t.column "notes", :text - t.column "created_on", :datetime, :null => false - end - create_table :journal_details, :force => true do |t| - t.column "journal_id", :integer, :default => 0, :null => false - t.column "property", :string, :limit => 30, :default => "", :null => false - t.column "prop_key", :string, :limit => 30, :default => "", :null => false - t.column "old_value", :string - t.column "value", :string - end - - # indexes - add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id" - add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id" - - Permission.create :controller => "issues", :action => "history", :description => "label_history", :sort => 1006, :is_public => true, :mail_option => 0, :mail_enabled => 0 - - # data migration - IssueHistory.find(:all, :include => :issue).each {|h| - j = Journal.new(:journalized => h.issue, :user_id => h.author_id, :notes => h.notes, :created_on => h.created_on) - j.details << JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :value => h.status_id) - j.save - } - - drop_table :issue_histories - end - - def self.down - drop_table :journal_details - drop_table :journals - - create_table "issue_histories", :force => true do |t| - t.column "issue_id", :integer, :default => 0, :null => false - t.column "status_id", :integer, :default => 0, :null => false - t.column "author_id", :integer, :default => 0, :null => false - t.column "notes", :text, :default => "" - t.column "created_on", :timestamp - end - - add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id" - - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'history']).destroy - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/01/0140b2e0d97ff88f8ef3743106e70b9d5c56caec.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/01/0140b2e0d97ff88f8ef3743106e70b9d5c56caec.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,96 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class AuthSourcesController < ApplicationController + layout 'admin' + menu_item :ldap_authentication + + before_filter :require_admin + before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy] + + def index + @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25 + end + + def new + klass_name = params[:type] || 'AuthSourceLdap' + @auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) + render_404 unless @auth_source + end + + def create + @auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) + if @auth_source.save + flash[:notice] = l(:notice_successful_create) + redirect_to auth_sources_path + else + render :action => 'new' + end + end + + def edit + end + + def update + if @auth_source.update_attributes(params[:auth_source]) + flash[:notice] = l(:notice_successful_update) + redirect_to auth_sources_path + else + render :action => 'edit' + end + end + + def test_connection + begin + @auth_source.test_connection + flash[:notice] = l(:notice_successful_connection) + rescue Exception => e + flash[:error] = l(:error_unable_to_connect, e.message) + end + redirect_to auth_sources_path + end + + def destroy + unless @auth_source.users.exists? + @auth_source.destroy + flash[:notice] = l(:notice_successful_delete) + end + redirect_to auth_sources_path + end + + def autocomplete_for_new_user + results = AuthSource.search(params[:term]) + + render :json => results.map {|result| { + 'value' => result[:login], + 'label' => "#{result[:login]} (#{result[:firstname]} #{result[:lastname]})", + 'login' => result[:login].to_s, + 'firstname' => result[:firstname].to_s, + 'lastname' => result[:lastname].to_s, + 'mail' => result[:mail].to_s, + 'auth_source_id' => result[:auth_source_id].to_s + }} + end + + private + + def find_auth_source + @auth_source = AuthSource.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/01/018db29c39216709e43cc1f869161f57aa497455.svn-base --- a/.svn/pristine/01/018db29c39216709e43cc1f869161f57aa497455.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class CustomFieldValue - attr_accessor :custom_field, :customized, :value - - def custom_field_id - custom_field.id - end - - def true? - self.value == '1' - end - - def editable? - custom_field.editable? - end - - def visible? - custom_field.visible? - end - - def required? - custom_field.is_required? - end - - def to_s - value.to_s - end - - def validate_value - custom_field.validate_field_value(value).each do |message| - customized.errors.add(:base, custom_field.name + ' ' + message) - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/01/01d08f5b37dfa46aae097b270dbf10baa02d7e48.svn-base --- a/.svn/pristine/01/01d08f5b37dfa46aae097b270dbf10baa02d7e48.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -class ExportPdf < ActiveRecord::Migration - # model removed - class Permission < ActiveRecord::Base; end - - def self.up - Permission.create :controller => "projects", :action => "export_issues_pdf", :description => "label_export_pdf", :sort => 1002, :is_public => true, :mail_option => 0, :mail_enabled => 0 - Permission.create :controller => "issues", :action => "export_pdf", :description => "label_export_pdf", :sort => 1015, :is_public => true, :mail_option => 0, :mail_enabled => 0 - end - - def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'export_issues_pdf']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'export_pdf']).destroy - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/01/01df356dd5562f68f76338bddccfd73e2d039ee5.svn-base --- a/.svn/pristine/01/01df356dd5562f68f76338bddccfd73e2d039ee5.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Acts - module Attachable - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - def acts_as_attachable(options = {}) - cattr_accessor :attachable_options - self.attachable_options = {} - attachable_options[:view_permission] = options.delete(:view_permission) || "view_#{self.name.pluralize.underscore}".to_sym - attachable_options[:delete_permission] = options.delete(:delete_permission) || "edit_#{self.name.pluralize.underscore}".to_sym - - has_many :attachments, options.merge(:as => :container, - :order => "#{Attachment.table_name}.created_on ASC, #{Attachment.table_name}.id ASC", - :dependent => :destroy) - send :include, Redmine::Acts::Attachable::InstanceMethods - before_save :attach_saved_attachments - end - end - - module InstanceMethods - def self.included(base) - base.extend ClassMethods - end - - def attachments_visible?(user=User.current) - (respond_to?(:visible?) ? visible?(user) : true) && - user.allowed_to?(self.class.attachable_options[:view_permission], self.project) - end - - def attachments_deletable?(user=User.current) - (respond_to?(:visible?) ? visible?(user) : true) && - user.allowed_to?(self.class.attachable_options[:delete_permission], self.project) - end - - def saved_attachments - @saved_attachments ||= [] - end - - def unsaved_attachments - @unsaved_attachments ||= [] - end - - def save_attachments(attachments, author=User.current) - if attachments.is_a?(Hash) - attachments = attachments.stringify_keys - attachments = attachments.to_a.sort {|a, b| - if a.first.to_i > 0 && b.first.to_i > 0 - a.first.to_i <=> b.first.to_i - elsif a.first.to_i > 0 - 1 - elsif b.first.to_i > 0 - -1 - else - a.first <=> b.first - end - } - attachments = attachments.map(&:last) - end - if attachments.is_a?(Array) - attachments.each do |attachment| - a = nil - if file = attachment['file'] - next unless file.size > 0 - a = Attachment.create(:file => file, :author => author) - elsif token = attachment['token'] - a = Attachment.find_by_token(token) - next unless a - a.filename = attachment['filename'] unless attachment['filename'].blank? - a.content_type = attachment['content_type'] - end - next unless a - a.description = attachment['description'].to_s.strip - if a.new_record? - unsaved_attachments << a - else - saved_attachments << a - end - end - end - {:files => saved_attachments, :unsaved => unsaved_attachments} - end - - def attach_saved_attachments - saved_attachments.each do |attachment| - self.attachments << attachment - end - end - - module ClassMethods - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/01/01e977490126a0e666397a535bfda934cac273ac.svn-base --- a/.svn/pristine/01/01e977490126a0e666397a535bfda934cac273ac.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,435 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class ScmFetchError < Exception; end - -class Repository < ActiveRecord::Base - include Redmine::Ciphering - include Redmine::SafeAttributes - - # Maximum length for repository identifiers - IDENTIFIER_MAX_LENGTH = 255 - - belongs_to :project - has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" - has_many :filechanges, :class_name => 'Change', :through => :changesets - - serialize :extra_info - - before_save :check_default - - # Raw SQL to delete changesets and changes in the database - # has_many :changesets, :dependent => :destroy is too slow for big repositories - before_destroy :clear_changesets - - validates_length_of :password, :maximum => 255, :allow_nil => true - validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true - validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? } - validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true - validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph) - # donwcase letters, digits, dashes, underscores but not digits only - validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-_]*$/, :allow_blank => true - # Checks if the SCM is enabled when creating a repository - validate :repo_create_validation, :on => :create - - safe_attributes 'identifier', - 'login', - 'password', - 'path_encoding', - 'log_encoding', - 'is_default' - - safe_attributes 'url', - :if => lambda {|repository, user| repository.new_record?} - - def repo_create_validation - unless Setting.enabled_scm.include?(self.class.name.demodulize) - errors.add(:type, :invalid) - end - end - - def self.human_attribute_name(attribute_key_name, *args) - attr_name = attribute_key_name.to_s - if attr_name == "log_encoding" - attr_name = "commit_logs_encoding" - end - super(attr_name, *args) - end - - # Removes leading and trailing whitespace - def url=(arg) - write_attribute(:url, arg ? arg.to_s.strip : nil) - end - - # Removes leading and trailing whitespace - def root_url=(arg) - write_attribute(:root_url, arg ? arg.to_s.strip : nil) - end - - def password - read_ciphered_attribute(:password) - end - - def password=(arg) - write_ciphered_attribute(:password, arg) - end - - def scm_adapter - self.class.scm_adapter_class - end - - def scm - unless @scm - @scm = self.scm_adapter.new(url, root_url, - login, password, path_encoding) - if root_url.blank? && @scm.root_url.present? - update_attribute(:root_url, @scm.root_url) - end - end - @scm - end - - def scm_name - self.class.scm_name - end - - def name - if identifier.present? - identifier - elsif is_default? - l(:field_repository_is_default) - else - scm_name - end - end - - def identifier=(identifier) - super unless identifier_frozen? - end - - def identifier_frozen? - errors[:identifier].blank? && !(new_record? || identifier.blank?) - end - - def identifier_param - if is_default? - nil - elsif identifier.present? - identifier - else - id.to_s - end - end - - def <=>(repository) - if is_default? - -1 - elsif repository.is_default? - 1 - else - identifier.to_s <=> repository.identifier.to_s - end - end - - def self.find_by_identifier_param(param) - if param.to_s =~ /^\d+$/ - find_by_id(param) - else - find_by_identifier(param) - end - end - - def merge_extra_info(arg) - h = extra_info || {} - return h if arg.nil? - h.merge!(arg) - write_attribute(:extra_info, h) - end - - def report_last_commit - true - end - - def supports_cat? - scm.supports_cat? - end - - def supports_annotate? - scm.supports_annotate? - end - - def supports_all_revisions? - true - end - - def supports_directory_revisions? - false - end - - def supports_revision_graph? - false - end - - def entry(path=nil, identifier=nil) - scm.entry(path, identifier) - end - - def entries(path=nil, identifier=nil) - entries = scm.entries(path, identifier) - load_entries_changesets(entries) - entries - end - - def branches - scm.branches - end - - def tags - scm.tags - end - - def default_branch - nil - end - - def properties(path, identifier=nil) - scm.properties(path, identifier) - end - - def cat(path, identifier=nil) - scm.cat(path, identifier) - end - - def diff(path, rev, rev_to) - scm.diff(path, rev, rev_to) - end - - def diff_format_revisions(cs, cs_to, sep=':') - text = "" - text << cs_to.format_identifier + sep if cs_to - text << cs.format_identifier if cs - text - end - - # Returns a path relative to the url of the repository - def relative_path(path) - path - end - - # Finds and returns a revision with a number or the beginning of a hash - def find_changeset_by_name(name) - return nil if name.blank? - s = name.to_s - changesets.find(:first, :conditions => (s.match(/^\d*$/) ? - ["revision = ?", s] : ["revision LIKE ?", s + '%'])) - end - - def latest_changeset - @latest_changeset ||= changesets.find(:first) - end - - # Returns the latest changesets for +path+ - # Default behaviour is to search in cached changesets - def latest_changesets(path, rev, limit=10) - if path.blank? - changesets.find( - :all, - :include => :user, - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", - :limit => limit) - else - filechanges.find( - :all, - :include => {:changeset => :user}, - :conditions => ["path = ?", path.with_leading_slash], - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", - :limit => limit - ).collect(&:changeset) - end - end - - def scan_changesets_for_issue_ids - self.changesets.each(&:scan_comment_for_issue_ids) - end - - # Returns an array of committers usernames and associated user_id - def committers - @committers ||= Changeset.connection.select_rows( - "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}") - end - - # Maps committers username to a user ids - def committer_ids=(h) - if h.is_a?(Hash) - committers.each do |committer, user_id| - new_user_id = h[committer] - if new_user_id && (new_user_id.to_i != user_id.to_i) - new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil) - Changeset.update_all( - "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", - ["repository_id = ? AND committer = ?", id, committer]) - end - end - @committers = nil - @found_committer_users = nil - true - else - false - end - end - - # Returns the Redmine User corresponding to the given +committer+ - # It will return nil if the committer is not yet mapped and if no User - # with the same username or email was found - def find_committer_user(committer) - unless committer.blank? - @found_committer_users ||= {} - return @found_committer_users[committer] if @found_committer_users.has_key?(committer) - - user = nil - c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user) - if c && c.user - user = c.user - elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/ - username, email = $1.strip, $3 - u = User.find_by_login(username) - u ||= User.find_by_mail(email) unless email.blank? - user = u - end - @found_committer_users[committer] = user - user - end - end - - def repo_log_encoding - encoding = log_encoding.to_s.strip - encoding.blank? ? 'UTF-8' : encoding - end - - # Fetches new changesets for all repositories of active projects - # Can be called periodically by an external script - # eg. ruby script/runner "Repository.fetch_changesets" - def self.fetch_changesets - Project.active.has_module(:repository).all.each do |project| - project.repositories.each do |repository| - begin - repository.fetch_changesets - rescue Redmine::Scm::Adapters::CommandFailed => e - logger.error "scm: error during fetching changesets: #{e.message}" - end - end - end - end - - # scan changeset comments to find related and fixed issues for all repositories - def self.scan_changesets_for_issue_ids - find(:all).each(&:scan_changesets_for_issue_ids) - end - - def self.scm_name - 'Abstract' - end - - def self.available_scm - subclasses.collect {|klass| [klass.scm_name, klass.name]} - end - - def self.factory(klass_name, *args) - klass = "Repository::#{klass_name}".constantize - klass.new(*args) - rescue - nil - end - - def self.scm_adapter_class - nil - end - - def self.scm_command - ret = "" - begin - ret = self.scm_adapter_class.client_command if self.scm_adapter_class - rescue Exception => e - logger.error "scm: error during get command: #{e.message}" - end - ret - end - - def self.scm_version_string - ret = "" - begin - ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class - rescue Exception => e - logger.error "scm: error during get version string: #{e.message}" - end - ret - end - - def self.scm_available - ret = false - begin - ret = self.scm_adapter_class.client_available if self.scm_adapter_class - rescue Exception => e - logger.error "scm: error during get scm available: #{e.message}" - end - ret - end - - def set_as_default? - new_record? && project && !Repository.first(:conditions => {:project_id => project.id}) - end - - protected - - def check_default - if !is_default? && set_as_default? - self.is_default = true - end - if is_default? && is_default_changed? - Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id]) - end - end - - def load_entries_changesets(entries) - if entries - entries.each do |entry| - if entry.lastrev && entry.lastrev.identifier - entry.changeset = find_changeset_by_name(entry.lastrev.identifier) - end - end - end - end - - private - - # Deletes repository data - def clear_changesets - cs = Changeset.table_name - ch = Change.table_name - ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}" - cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}" - - connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}") - clear_extra_info_of_changesets - end - - def clear_extra_info_of_changesets - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/01/01eefd173bf6e8d083047e854c1bec1880bf6c4e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/01/01eefd173bf6e8d083047e854c1bec1880bf6c4e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,950 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Helpers + # Simple class to handle gantt chart data + class Gantt + include ERB::Util + include Redmine::I18n + include Redmine::Utils::DateCalculation + + # Relation types that are rendered + DRAW_TYPES = { + IssueRelation::TYPE_BLOCKS => { :landscape_margin => 16, :color => '#F34F4F' }, + IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' } + }.freeze + + # :nodoc: + # Some utility methods for the PDF export + class PDF + MaxCharactorsForSubject = 45 + TotalWidth = 280 + LeftPaneWidth = 100 + + def self.right_pane_width + TotalWidth - LeftPaneWidth + end + end + + attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows + attr_accessor :query + attr_accessor :project + attr_accessor :view + + def initialize(options={}) + options = options.dup + if options[:year] && options[:year].to_i >0 + @year_from = options[:year].to_i + if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12 + @month_from = options[:month].to_i + else + @month_from = 1 + end + else + @month_from ||= Date.today.month + @year_from ||= Date.today.year + end + zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i + @zoom = (zoom > 0 && zoom < 5) ? zoom : 2 + months = (options[:months] || User.current.pref[:gantt_months]).to_i + @months = (months > 0 && months < 25) ? months : 6 + # Save gantt parameters as user preference (zoom and months count) + if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || + @months != User.current.pref[:gantt_months])) + User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months + User.current.preference.save + end + @date_from = Date.civil(@year_from, @month_from, 1) + @date_to = (@date_from >> @months) - 1 + @subjects = '' + @lines = '' + @number_of_rows = nil + @issue_ancestors = [] + @truncated = false + if options.has_key?(:max_rows) + @max_rows = options[:max_rows] + else + @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i + end + end + + def common_params + { :controller => 'gantts', :action => 'show', :project_id => @project } + end + + def params + common_params.merge({:zoom => zoom, :year => year_from, + :month => month_from, :months => months}) + end + + def params_previous + common_params.merge({:year => (date_from << months).year, + :month => (date_from << months).month, + :zoom => zoom, :months => months}) + end + + def params_next + common_params.merge({:year => (date_from >> months).year, + :month => (date_from >> months).month, + :zoom => zoom, :months => months}) + end + + # Returns the number of rows that will be rendered on the Gantt chart + def number_of_rows + return @number_of_rows if @number_of_rows + rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)} + rows > @max_rows ? @max_rows : rows + end + + # Returns the number of rows that will be used to list a project on + # the Gantt chart. This will recurse for each subproject. + def number_of_rows_on_project(project) + return 0 unless projects.include?(project) + count = 1 + count += project_issues(project).size + count += project_versions(project).size + count + end + + # Renders the subjects of the Gantt chart, the left side. + def subjects(options={}) + render(options.merge(:only => :subjects)) unless @subjects_rendered + @subjects + end + + # Renders the lines of the Gantt chart, the right side + def lines(options={}) + render(options.merge(:only => :lines)) unless @lines_rendered + @lines + end + + # Returns issues that will be rendered + def issues + @issues ||= @query.issues( + :include => [:assigned_to, :tracker, :priority, :category, :fixed_version], + :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC", + :limit => @max_rows + ) + end + + # Returns a hash of the relations between the issues that are present on the gantt + # and that should be displayed, grouped by issue ids. + def relations + return @relations if @relations + if issues.any? + issue_ids = issues.map(&:id) + @relations = IssueRelation. + where(:issue_from_id => issue_ids, :issue_to_id => issue_ids, :relation_type => DRAW_TYPES.keys). + group_by(&:issue_from_id) + else + @relations = {} + end + end + + # Return all the project nodes that will be displayed + def projects + return @projects if @projects + ids = issues.collect(&:project).uniq.collect(&:id) + if ids.any? + # All issues projects and their visible ancestors + @projects = Project.visible. + joins("LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt"). + where("child.id IN (?)", ids). + order("#{Project.table_name}.lft ASC"). + uniq. + all + else + @projects = [] + end + end + + # Returns the issues that belong to +project+ + def project_issues(project) + @issues_by_project ||= issues.group_by(&:project) + @issues_by_project[project] || [] + end + + # Returns the distinct versions of the issues that belong to +project+ + def project_versions(project) + project_issues(project).collect(&:fixed_version).compact.uniq + end + + # Returns the issues that belong to +project+ and are assigned to +version+ + def version_issues(project, version) + project_issues(project).select {|issue| issue.fixed_version == version} + end + + def render(options={}) + options = {:top => 0, :top_increment => 20, + :indent_increment => 20, :render => :subject, + :format => :html}.merge(options) + indent = options[:indent] || 4 + @subjects = '' unless options[:only] == :lines + @lines = '' unless options[:only] == :subjects + @number_of_rows = 0 + Project.project_tree(projects) do |project, level| + options[:indent] = indent + level * options[:indent_increment] + render_project(project, options) + break if abort? + end + @subjects_rendered = true unless options[:only] == :lines + @lines_rendered = true unless options[:only] == :subjects + render_end(options) + end + + def render_project(project, options={}) + subject_for_project(project, options) unless options[:only] == :lines + line_for_project(project, options) unless options[:only] == :subjects + options[:top] += options[:top_increment] + options[:indent] += options[:indent_increment] + @number_of_rows += 1 + return if abort? + issues = project_issues(project).select {|i| i.fixed_version.nil?} + self.class.sort_issues!(issues) + if issues + render_issues(issues, options) + return if abort? + end + versions = project_versions(project) + self.class.sort_versions!(versions) + versions.each do |version| + render_version(project, version, options) + end + # Remove indent to hit the next sibling + options[:indent] -= options[:indent_increment] + end + + def render_issues(issues, options={}) + @issue_ancestors = [] + issues.each do |i| + subject_for_issue(i, options) unless options[:only] == :lines + line_for_issue(i, options) unless options[:only] == :subjects + options[:top] += options[:top_increment] + @number_of_rows += 1 + break if abort? + end + options[:indent] -= (options[:indent_increment] * @issue_ancestors.size) + end + + def render_version(project, version, options={}) + # Version header + subject_for_version(version, options) unless options[:only] == :lines + line_for_version(version, options) unless options[:only] == :subjects + options[:top] += options[:top_increment] + @number_of_rows += 1 + return if abort? + issues = version_issues(project, version) + if issues + self.class.sort_issues!(issues) + # Indent issues + options[:indent] += options[:indent_increment] + render_issues(issues, options) + options[:indent] -= options[:indent_increment] + end + end + + def render_end(options={}) + case options[:format] + when :pdf + options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top]) + end + end + + def subject_for_project(project, options) + case options[:format] + when :html + html_class = "" + html_class << 'icon icon-projects ' + html_class << (project.overdue? ? 'project-overdue' : '') + s = view.link_to_project(project).html_safe + subject = view.content_tag(:span, s, + :class => html_class).html_safe + html_subject(options, subject, :css => "project-name") + when :image + image_subject(options, project.name) + when :pdf + pdf_new_page?(options) + pdf_subject(options, project.name) + end + end + + def line_for_project(project, options) + # Skip versions that don't have a start_date or due date + if project.is_a?(Project) && project.start_date && project.due_date + options[:zoom] ||= 1 + options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] + coords = coordinates(project.start_date, project.due_date, nil, options[:zoom]) + label = h(project) + case options[:format] + when :html + html_task(options, coords, :css => "project task", :label => label, :markers => true) + when :image + image_task(options, coords, :label => label, :markers => true, :height => 3) + when :pdf + pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) + end + else + '' + end + end + + def subject_for_version(version, options) + case options[:format] + when :html + html_class = "" + html_class << 'icon icon-package ' + html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " " + html_class << (version.overdue? ? 'version-overdue' : '') + html_class << ' version-closed' unless version.open? + if version.start_date && version.due_date && version.completed_pourcent + progress_date = calc_progress_date(version.start_date, + version.due_date, version.completed_pourcent) + html_class << ' behind-start-date' if progress_date < self.date_from + html_class << ' over-end-date' if progress_date > self.date_to + end + s = view.link_to_version(version).html_safe + subject = view.content_tag(:span, s, + :class => html_class).html_safe + html_subject(options, subject, :css => "version-name", + :id => "version-#{version.id}") + when :image + image_subject(options, version.to_s_with_project) + when :pdf + pdf_new_page?(options) + pdf_subject(options, version.to_s_with_project) + end + end + + def line_for_version(version, options) + # Skip versions that don't have a start_date + if version.is_a?(Version) && version.due_date && version.start_date + options[:zoom] ||= 1 + options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] + coords = coordinates(version.start_date, + version.due_date, version.completed_percent, + options[:zoom]) + label = "#{h version} #{h version.completed_percent.to_i.to_s}%" + label = h("#{version.project} -") + label unless @project && @project == version.project + case options[:format] + when :html + html_task(options, coords, :css => "version task", + :label => label, :markers => true, :version => version) + when :image + image_task(options, coords, :label => label, :markers => true, :height => 3) + when :pdf + pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) + end + else + '' + end + end + + def subject_for_issue(issue, options) + while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last) + @issue_ancestors.pop + options[:indent] -= options[:indent_increment] + end + output = case options[:format] + when :html + css_classes = '' + css_classes << ' issue-overdue' if issue.overdue? + css_classes << ' issue-behind-schedule' if issue.behind_schedule? + css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to + css_classes << ' issue-closed' if issue.closed? + if issue.start_date && issue.due_before && issue.done_ratio + progress_date = calc_progress_date(issue.start_date, + issue.due_before, issue.done_ratio) + css_classes << ' behind-start-date' if progress_date < self.date_from + css_classes << ' over-end-date' if progress_date > self.date_to + end + s = "".html_safe + if issue.assigned_to.present? + assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name + s << view.avatar(issue.assigned_to, + :class => 'gravatar icon-gravatar', + :size => 10, + :title => assigned_string).to_s.html_safe + end + s << view.link_to_issue(issue).html_safe + subject = view.content_tag(:span, s, :class => css_classes).html_safe + html_subject(options, subject, :css => "issue-subject", + :title => issue.subject, :id => "issue-#{issue.id}") + "\n" + when :image + image_subject(options, issue.subject) + when :pdf + pdf_new_page?(options) + pdf_subject(options, issue.subject) + end + unless issue.leaf? + @issue_ancestors << issue + options[:indent] += options[:indent_increment] + end + output + end + + def line_for_issue(issue, options) + # Skip issues that don't have a due_before (due_date or version's due_date) + if issue.is_a?(Issue) && issue.due_before + coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom]) + label = "#{issue.status.name} #{issue.done_ratio}%" + case options[:format] + when :html + html_task(options, coords, + :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), + :label => label, :issue => issue, + :markers => !issue.leaf?) + when :image + image_task(options, coords, :label => label) + when :pdf + pdf_task(options, coords, :label => label) + end + else + '' + end + end + + # Generates a gantt image + # Only defined if RMagick is avalaible + def to_image(format='PNG') + date_to = (@date_from >> @months) - 1 + show_weeks = @zoom > 1 + show_days = @zoom > 2 + subject_width = 400 + header_height = 18 + # width of one day in pixels + zoom = @zoom * 2 + g_width = (@date_to - @date_from + 1) * zoom + g_height = 20 * number_of_rows + 30 + headers_height = (show_weeks ? 2 * header_height : header_height) + height = g_height + headers_height + imgl = Magick::ImageList.new + imgl.new_image(subject_width + g_width + 1, height) + gc = Magick::Draw.new + gc.font = Redmine::Configuration['rmagick_font_path'] || "" + # Subjects + gc.stroke('transparent') + subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image) + # Months headers + month_f = @date_from + left = subject_width + @months.times do + width = ((month_f >> 1) - month_f) * zoom + gc.fill('white') + gc.stroke('grey') + gc.stroke_width(1) + gc.rectangle(left, 0, left + width, height) + gc.fill('black') + gc.stroke('transparent') + gc.stroke_width(1) + gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}") + left = left + width + month_f = month_f >> 1 + end + # Weeks headers + if show_weeks + left = subject_width + height = header_height + if @date_from.cwday == 1 + # date_from is monday + week_f = date_from + else + # find next monday after date_from + week_f = @date_from + (7 - @date_from.cwday + 1) + width = (7 - @date_from.cwday + 1) * zoom + gc.fill('white') + gc.stroke('grey') + gc.stroke_width(1) + gc.rectangle(left, header_height, left + width, 2 * header_height + g_height - 1) + left = left + width + end + while week_f <= date_to + width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom + gc.fill('white') + gc.stroke('grey') + gc.stroke_width(1) + gc.rectangle(left.round, header_height, left.round + width, 2 * header_height + g_height - 1) + gc.fill('black') + gc.stroke('transparent') + gc.stroke_width(1) + gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s) + left = left + width + week_f = week_f + 7 + end + end + # Days details (week-end in grey) + if show_days + left = subject_width + height = g_height + header_height - 1 + wday = @date_from.cwday + (date_to - @date_from + 1).to_i.times do + width = zoom + gc.fill(non_working_week_days.include?(wday) ? '#eee' : 'white') + gc.stroke('#ddd') + gc.stroke_width(1) + gc.rectangle(left, 2 * header_height, left + width, 2 * header_height + g_height - 1) + left = left + width + wday = wday + 1 + wday = 1 if wday > 7 + end + end + # border + gc.fill('transparent') + gc.stroke('grey') + gc.stroke_width(1) + gc.rectangle(0, 0, subject_width + g_width, headers_height) + gc.stroke('black') + gc.rectangle(0, 0, subject_width + g_width, g_height + headers_height - 1) + # content + top = headers_height + 20 + gc.stroke('transparent') + lines(:image => gc, :top => top, :zoom => zoom, + :subject_width => subject_width, :format => :image) + # today red line + if Date.today >= @date_from and Date.today <= date_to + gc.stroke('red') + x = (Date.today - @date_from + 1) * zoom + subject_width + gc.line(x, headers_height, x, headers_height + g_height - 1) + end + gc.draw(imgl) + imgl.format = format + imgl.to_blob + end if Object.const_defined?(:Magick) + + def to_pdf + pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language) + pdf.SetTitle("#{l(:label_gantt)} #{project}") + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.AddPage("L") + pdf.SetFontStyle('B', 12) + pdf.SetX(15) + pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s) + pdf.Ln + pdf.SetFontStyle('B', 9) + subject_width = PDF::LeftPaneWidth + header_height = 5 + headers_height = header_height + show_weeks = false + show_days = false + if self.months < 7 + show_weeks = true + headers_height = 2 * header_height + if self.months < 3 + show_days = true + headers_height = 3 * header_height + end + end + g_width = PDF.right_pane_width + zoom = (g_width) / (self.date_to - self.date_from + 1) + g_height = 120 + t_height = g_height + headers_height + y_start = pdf.GetY + # Months headers + month_f = self.date_from + left = subject_width + height = header_height + self.months.times do + width = ((month_f >> 1) - month_f) * zoom + pdf.SetY(y_start) + pdf.SetX(left) + pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C") + left = left + width + month_f = month_f >> 1 + end + # Weeks headers + if show_weeks + left = subject_width + height = header_height + if self.date_from.cwday == 1 + # self.date_from is monday + week_f = self.date_from + else + # find next monday after self.date_from + week_f = self.date_from + (7 - self.date_from.cwday + 1) + width = (7 - self.date_from.cwday + 1) * zoom-1 + pdf.SetY(y_start + header_height) + pdf.SetX(left) + pdf.RDMCell(width + 1, height, "", "LTR") + left = left + width + 1 + end + while week_f <= self.date_to + width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom + pdf.SetY(y_start + header_height) + pdf.SetX(left) + pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C") + left = left + width + week_f = week_f + 7 + end + end + # Days headers + if show_days + left = subject_width + height = header_height + wday = self.date_from.cwday + pdf.SetFontStyle('B', 7) + (self.date_to - self.date_from + 1).to_i.times do + width = zoom + pdf.SetY(y_start + 2 * header_height) + pdf.SetX(left) + pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C") + left = left + width + wday = wday + 1 + wday = 1 if wday > 7 + end + end + pdf.SetY(y_start) + pdf.SetX(15) + pdf.RDMCell(subject_width + g_width - 15, headers_height, "", 1) + # Tasks + top = headers_height + y_start + options = { + :top => top, + :zoom => zoom, + :subject_width => subject_width, + :g_width => g_width, + :indent => 0, + :indent_increment => 5, + :top_increment => 5, + :format => :pdf, + :pdf => pdf + } + render(options) + pdf.Output + end + + private + + def coordinates(start_date, end_date, progress, zoom=nil) + zoom ||= @zoom + coords = {} + if start_date && end_date && start_date < self.date_to && end_date > self.date_from + if start_date > self.date_from + coords[:start] = start_date - self.date_from + coords[:bar_start] = start_date - self.date_from + else + coords[:bar_start] = 0 + end + if end_date < self.date_to + coords[:end] = end_date - self.date_from + coords[:bar_end] = end_date - self.date_from + 1 + else + coords[:bar_end] = self.date_to - self.date_from + 1 + end + if progress + progress_date = calc_progress_date(start_date, end_date, progress) + if progress_date > self.date_from && progress_date > start_date + if progress_date < self.date_to + coords[:bar_progress_end] = progress_date - self.date_from + else + coords[:bar_progress_end] = self.date_to - self.date_from + 1 + end + end + if progress_date < Date.today + late_date = [Date.today, end_date].min + if late_date > self.date_from && late_date > start_date + if late_date < self.date_to + coords[:bar_late_end] = late_date - self.date_from + 1 + else + coords[:bar_late_end] = self.date_to - self.date_from + 1 + end + end + end + end + end + # Transforms dates into pixels witdh + coords.keys.each do |key| + coords[key] = (coords[key] * zoom).floor + end + coords + end + + def calc_progress_date(start_date, end_date, progress) + start_date + (end_date - start_date + 1) * (progress / 100.0) + end + + def self.sort_issues!(issues) + issues.sort! {|a, b| sort_issue_logic(a) <=> sort_issue_logic(b)} + end + + def self.sort_issue_logic(issue) + julian_date = Date.new() + ancesters_start_date = [] + current_issue = issue + begin + ancesters_start_date.unshift([current_issue.start_date || julian_date, current_issue.id]) + current_issue = current_issue.parent + end while (current_issue) + ancesters_start_date + end + + def self.sort_versions!(versions) + versions.sort! + end + + def current_limit + if @max_rows + @max_rows - @number_of_rows + else + nil + end + end + + def abort? + if @max_rows && @number_of_rows >= @max_rows + @truncated = true + end + end + + def pdf_new_page?(options) + if options[:top] > 180 + options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top]) + options[:pdf].AddPage("L") + options[:top] = 15 + options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1) + end + end + + def html_subject(params, subject, options={}) + style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" + style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] + output = view.content_tag(:div, subject, + :class => options[:css], :style => style, + :title => options[:title], + :id => options[:id]) + @subjects << output + output + end + + def pdf_subject(params, subject, options={}) + params[:pdf].SetY(params[:top]) + params[:pdf].SetX(15) + char_limit = PDF::MaxCharactorsForSubject - params[:indent] + params[:pdf].RDMCell(params[:subject_width] - 15, 5, + (" " * params[:indent]) + + subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), + "LR") + params[:pdf].SetY(params[:top]) + params[:pdf].SetX(params[:subject_width]) + params[:pdf].RDMCell(params[:g_width], 5, "", "LR") + end + + def image_subject(params, subject, options={}) + params[:image].fill('black') + params[:image].stroke('transparent') + params[:image].stroke_width(1) + params[:image].text(params[:indent], params[:top] + 2, subject) + end + + def issue_relations(issue) + rels = {} + if relations[issue.id] + relations[issue.id].each do |relation| + (rels[relation.relation_type] ||= []) << relation.issue_to_id + end + end + rels + end + + def html_task(params, coords, options={}) + output = '' + # Renders the task bar, with progress and late + if coords[:bar_start] && coords[:bar_end] + width = coords[:bar_end] - coords[:bar_start] - 2 + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:bar_start]}px;" + style << "width:#{width}px;" + html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue] + html_id = "task-todo-version-#{options[:version].id}" if options[:version] + content_opt = {:style => style, + :class => "#{options[:css]} task_todo", + :id => html_id} + if options[:issue] + rels = issue_relations(options[:issue]) + if rels.present? + content_opt[:data] = {"rels" => rels.to_json} + end + end + output << view.content_tag(:div, ' '.html_safe, content_opt) + if coords[:bar_late_end] + width = coords[:bar_late_end] - coords[:bar_start] - 2 + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:bar_start]}px;" + style << "width:#{width}px;" + output << view.content_tag(:div, ' '.html_safe, + :style => style, + :class => "#{options[:css]} task_late") + end + if coords[:bar_progress_end] + width = coords[:bar_progress_end] - coords[:bar_start] - 2 + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:bar_start]}px;" + style << "width:#{width}px;" + html_id = "task-done-issue-#{options[:issue].id}" if options[:issue] + html_id = "task-done-version-#{options[:version].id}" if options[:version] + output << view.content_tag(:div, ' '.html_safe, + :style => style, + :class => "#{options[:css]} task_done", + :id => html_id) + end + end + # Renders the markers + if options[:markers] + if coords[:start] + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:start]}px;" + style << "width:15px;" + output << view.content_tag(:div, ' '.html_safe, + :style => style, + :class => "#{options[:css]} marker starting") + end + if coords[:end] + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:end] + params[:zoom]}px;" + style << "width:15px;" + output << view.content_tag(:div, ' '.html_safe, + :style => style, + :class => "#{options[:css]} marker ending") + end + end + # Renders the label on the right + if options[:label] + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{(coords[:bar_end] || 0) + 8}px;" + style << "width:15px;" + output << view.content_tag(:div, options[:label], + :style => style, + :class => "#{options[:css]} label") + end + # Renders the tooltip + if options[:issue] && coords[:bar_start] && coords[:bar_end] + s = view.content_tag(:span, + view.render_issue_tooltip(options[:issue]).html_safe, + :class => "tip") + style = "" + style << "position: absolute;" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:bar_start]}px;" + style << "width:#{coords[:bar_end] - coords[:bar_start]}px;" + style << "height:12px;" + output << view.content_tag(:div, s.html_safe, + :style => style, + :class => "tooltip") + end + @lines << output + output + end + + def pdf_task(params, coords, options={}) + height = options[:height] || 2 + # Renders the task bar, with progress and late + if coords[:bar_start] && coords[:bar_end] + params[:pdf].SetY(params[:top] + 1.5) + params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) + params[:pdf].SetFillColor(200, 200, 200) + params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1) + if coords[:bar_late_end] + params[:pdf].SetY(params[:top] + 1.5) + params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) + params[:pdf].SetFillColor(255, 100, 100) + params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1) + end + if coords[:bar_progress_end] + params[:pdf].SetY(params[:top] + 1.5) + params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) + params[:pdf].SetFillColor(90, 200, 90) + params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1) + end + end + # Renders the markers + if options[:markers] + if coords[:start] + params[:pdf].SetY(params[:top] + 1) + params[:pdf].SetX(params[:subject_width] + coords[:start] - 1) + params[:pdf].SetFillColor(50, 50, 200) + params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1) + end + if coords[:end] + params[:pdf].SetY(params[:top] + 1) + params[:pdf].SetX(params[:subject_width] + coords[:end] - 1) + params[:pdf].SetFillColor(50, 50, 200) + params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1) + end + end + # Renders the label on the right + if options[:label] + params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5) + params[:pdf].RDMCell(30, 2, options[:label]) + end + end + + def image_task(params, coords, options={}) + height = options[:height] || 6 + # Renders the task bar, with progress and late + if coords[:bar_start] && coords[:bar_end] + params[:image].fill('#aaa') + params[:image].rectangle(params[:subject_width] + coords[:bar_start], + params[:top], + params[:subject_width] + coords[:bar_end], + params[:top] - height) + if coords[:bar_late_end] + params[:image].fill('#f66') + params[:image].rectangle(params[:subject_width] + coords[:bar_start], + params[:top], + params[:subject_width] + coords[:bar_late_end], + params[:top] - height) + end + if coords[:bar_progress_end] + params[:image].fill('#00c600') + params[:image].rectangle(params[:subject_width] + coords[:bar_start], + params[:top], + params[:subject_width] + coords[:bar_progress_end], + params[:top] - height) + end + end + # Renders the markers + if options[:markers] + if coords[:start] + x = params[:subject_width] + coords[:start] + y = params[:top] - height / 2 + params[:image].fill('blue') + params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4) + end + if coords[:end] + x = params[:subject_width] + coords[:end] + params[:zoom] + y = params[:top] - height / 2 + params[:image].fill('blue') + params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4) + end + end + # Renders the label on the right + if options[:label] + params[:image].fill('black') + params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5, + params[:top] + 1, + options[:label]) + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/02/020539cf91037b37b7739ed19a56c058529a8860.svn-base --- a/.svn/pristine/02/020539cf91037b37b7739ed19a56c058529a8860.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class WorkflowPermission < WorkflowRule - validates_inclusion_of :rule, :in => %w(readonly required) - validate :validate_field_name - - # Replaces the workflow permissions for the given tracker and role - # - # Example: - # WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}} - def self.replace_permissions(tracker, role, permissions) - destroy_all(:tracker_id => tracker.id, :role_id => role.id) - - permissions.each { |field, rule_by_status_id| - rule_by_status_id.each { |status_id, rule| - if rule.present? - WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule) - end - } - } - end - - protected - - def validate_field_name - unless Tracker::CORE_FIELDS_ALL.include?(field_name) || field_name.to_s.match(/^\d+$/) - errors.add :field_name, :invalid - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/02/0235738b45d2757cc6462ce5ba2c1e87548fc84e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/02/0235738b45d2757cc6462ce5ba2c1e87548fc84e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,13 @@ +class CreateQueriesRoles < ActiveRecord::Migration + def self.up + create_table :queries_roles, :id => false do |t| + t.column :query_id, :integer, :null => false + t.column :role_id, :integer, :null => false + end + add_index :queries_roles, [:query_id, :role_id], :unique => true, :name => :queries_roles_ids + end + + def self.down + drop_table :queries_roles + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/02/02795f92f4fa997a818d2a8cff87758b237ae0bf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/02/02795f92f4fa997a818d2a8cff87758b237ae0bf.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,59 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::ThemesTest < ActiveSupport::TestCase + + def test_themes + themes = Redmine::Themes.themes + assert_kind_of Array, themes + assert_kind_of Redmine::Themes::Theme, themes.first + end + + def test_rescan + Redmine::Themes.themes.pop + + assert_difference 'Redmine::Themes.themes.size' do + Redmine::Themes.rescan + end + end + + def test_theme_loaded + theme = Redmine::Themes.themes.last + + assert_equal theme, Redmine::Themes.theme(theme.id) + end + + def test_theme_loaded_without_rescan + theme = Redmine::Themes.themes.last + + assert_equal theme, Redmine::Themes.theme(theme.id, :rescan => false) + end + + def test_theme_not_loaded + theme = Redmine::Themes.themes.pop + + assert_equal theme, Redmine::Themes.theme(theme.id) + end + + def test_theme_not_loaded_without_rescan + theme = Redmine::Themes.themes.pop + + assert_nil Redmine::Themes.theme(theme.id, :rescan => false) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/02/0282f7d2cfc4f0e2579792f126b478789007d665.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/02/0282f7d2cfc4f0e2579792f126b478789007d665.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,110 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::IssueCategoriesTest < Redmine::ApiTest::Base + fixtures :projects, :users, :issue_categories, :issues, + :roles, + :member_roles, + :members, + :enabled_modules + + def setup + Setting.rest_api_enabled = '1' + end + + test "GET /projects/:project_id/issue_categories.xml should return the issue categories" do + get '/projects/1/issue_categories.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'issue_categories', + :child => {:tag => 'issue_category', :child => {:tag => 'id', :content => '2'}} + end + + test "GET /issue_categories/:id.xml should return the issue category" do + get '/issue_categories/2.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'issue_category', + :child => {:tag => 'id', :content => '2'} + end + + test "POST /projects/:project_id/issue_categories.xml should return create issue category" do + assert_difference 'IssueCategory.count' do + post '/projects/1/issue_categories.xml', {:issue_category => {:name => 'API'}}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type + + category = IssueCategory.first(:order => 'id DESC') + assert_equal 'API', category.name + assert_equal 1, category.project_id + end + + test "POST /projects/:project_id/issue_categories.xml with invalid parameters should return errors" do + assert_no_difference 'IssueCategory.count' do + post '/projects/1/issue_categories.xml', {:issue_category => {:name => ''}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} + end + + test "PUT /issue_categories/:id.xml with valid parameters should update the issue category" do + assert_no_difference 'IssueCategory.count' do + put '/issue_categories/2.xml', {:issue_category => {:name => 'API Update'}}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_equal 'API Update', IssueCategory.find(2).name + end + + test "PUT /issue_categories/:id.xml with invalid parameters should return errors" do + assert_no_difference 'IssueCategory.count' do + put '/issue_categories/2.xml', {:issue_category => {:name => ''}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} + end + + test "DELETE /issue_categories/:id.xml should destroy the issue category" do + assert_difference 'IssueCategory.count', -1 do + delete '/issue_categories/1.xml', {}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_nil IssueCategory.find_by_id(1) + end + + test "DELETE /issue_categories/:id.xml should reassign issues with :reassign_to_id param" do + issue_count = Issue.where(:category_id => 1).count + assert issue_count > 0 + + assert_difference 'IssueCategory.count', -1 do + assert_difference 'Issue.where(:category_id => 2).count', 3 do + delete '/issue_categories/1.xml', {:reassign_to_id => 2}, credentials('jsmith') + end + end + assert_response :ok + assert_equal '', @response.body + assert_nil IssueCategory.find_by_id(1) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/03/03186ac93c7f57779f7557150cdd51f4c66c78c8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/03/03186ac93c7f57779f7557150cdd51f4c66c78c8.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,34 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::MenuManagerTest < ActiveSupport::TestCase + def test_map_should_yield_a_mapper + assert_difference 'Redmine::MenuManager.items(:project_menu).size' do + Redmine::MenuManager.map :project_menu do |mapper| + assert_kind_of Redmine::MenuManager::Mapper, mapper + mapper.push :new_item, '/' + end + end + end + + def test_items_should_return_menu_items + items = Redmine::MenuManager.items(:project_menu) + assert_kind_of Redmine::MenuManager::MenuNode, items.first + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/03/0327b87f817dd6e7b29c9fc60cf3afcc96b39ebb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/03/0327b87f817dd6e7b29c9fc60cf3afcc96b39ebb.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,32 @@ +

<%=l(:label_enumerations)%>

+ +<% Enumeration.get_subclasses.each do |klass| %> +

<%= l(klass::OptionName) %>

+ +<% enumerations = klass.shared %> +<% if enumerations.any? %> + + + + + + + + +<% enumerations.each do |enumeration| %> + + + + + + + +<% end %> +
<%= l(:field_name) %><%= l(:field_is_default) %><%= l(:field_active) %><%=l(:button_sort)%>
<%= link_to h(enumeration), edit_enumeration_path(enumeration) %><%= checked_image enumeration.is_default? %><%= checked_image enumeration.active? %><%= reorder_links('enumeration', {:action => 'update', :id => enumeration}, :put) %><%= delete_link enumeration_path(enumeration) %>
+<% reset_cycle %> +<% end %> + +

<%= link_to l(:label_enumeration_new), new_enumeration_path(:type => klass.name) %>

+<% end %> + +<% html_title(l(:label_enumerations)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/03/0335a712dd5fb2c44f8cb6daaf182ac72aee4c7f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/03/0335a712dd5fb2c44f8cb6daaf182ac72aee4c7f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

Wiki æ ¼å¼è¨­å®š

+ +

連çµ

+ +

Redmine 連çµ

+ +

在任何å¯ä»¥ä½¿ç”¨ Wiki æ ¼å¼è¨­å®šçš„地方, Redmine 都å…許在資æºï¼ˆå•題ã€è®Šæ›´é›†ã€ Wiki é é¢...)間建立超連çµã€‚

+
    +
  • 連çµè‡³ä¸€å€‹å•題: #124 (若該å•é¡Œå·²ç¶“çµæŸï¼Œå‰‡ä½¿ç”¨åˆªé™¤ç·šé¡¯ç¤ºé€£çµï¼š #124)
  • +
  • 連çµè‡³ä¸€å€‹å•題的筆記: #124-6, or #124#note-6
  • +
+ +

Wiki 連çµï¼š

+ +
    +
  • [[Guide]] 顯示一個é é¢å稱為 'Guide' 的連çµï¼š Guide
  • +
  • [[Guide#further-reading]] 會連çµè‡³ä¸€å€‹ "further-reading" çš„ HTML 錨定 (anchor) 。æ¯å€‹æ¨™é¡Œæ–‡å­—都會被自動指定一個 HTML 錨定,以便您å¯ä»¥ç”¨ä¾†é€£çµå®ƒå€‘: Guide
  • +
  • [[Guide|User manual]] 使用ä¸åŒçš„æ–‡å­—來顯示一個é é¢å稱為 'Guide' 的連çµï¼š User manual
  • +
+ +

您也å¯ä»¥é€£çµè‡³å…¶ä»–專案的 Wiki é é¢ï¼š

+ +
    +
  • [[sandbox:some page]] 顯示一個 Sanbox wiki 中é é¢å稱為 'Some page' 的連çµ
  • +
  • [[sandbox:]] 顯示 Sandbox wiki 首é é é¢çš„連çµ
  • +
+ +

ç•¶é é¢ä¸å­˜åœ¨çš„æ™‚候, Wiki é€£çµæœƒä»¥ç´…色的方å¼é¡¯ç¤ºï¼Œä¾‹å¦‚: Nonexistent page.

+ +

連çµè‡³å…¶ä»–資æºï¼š

+ +
    +
  • 文件: +
      +
    • document#17 (連çµåˆ°ç·¨è™Ÿç‚º 17 的文件)
    • +
    • document:Greetings (連çµè‡³æ–‡ä»¶æ¨™é¡Œç‚º "Greetings" 的文件)
    • +
    • document:"Some document" (文件標題包å«ç©ºç™½å­—元時å¯ä»¥ä½¿ç”¨é›™å¼•號來標示)
    • +
    • sandbox:document:"Some document" (連çµè‡³å¦å¤–一個 "sandbox" 專案中,文件標題為 "Some document" 的文件)
    • +
  • +
+ +
    +
  • 版本: +
      +
    • version#3 (連çµè‡³ç·¨è™Ÿç‚º 3 的版本)
    • +
    • version:1.0.0 (連çµè‡³å稱為 "1.0.0" 的版本)
    • +
    • version:"1.0 beta 2" (版本å稱包å«ç©ºç™½å­—元時å¯ä»¥ä½¿ç”¨é›™å¼•號來標示)
    • +
    • sandbox:version:1.0.0 (連çµè‡³ "sandbox" 專案中,å稱為 "1.0.0" 的版本)
    • +
  • +
+ +
    +
  • 附加檔案: +
      +
    • attachment:file.zip (連çµè‡³ç›®å‰ç‰©ä»¶ä¸­ï¼Œå稱為 file.zip 的附加檔案)
    • +
    • ç›®å‰åƒ…æä¾›åƒè€ƒåˆ°ç›®å‰ç‰©ä»¶ä¸­çš„附加檔案 (è‹¥æ‚¨æ­£ä½æ–¼ä¸€å€‹å•題中,僅å¯åƒè€ƒä½æ–¼æ­¤å•題中之附加檔案)
    • +
  • +
+ +
    +
  • 變更集: +
      +
    • r758 (連çµè‡³ä¸€å€‹è®Šæ›´é›†)
    • +
    • commit:c6f4d0fd (連çµè‡³ä¸€å€‹ä½¿ç”¨éžæ•¸å­—雜湊的變更集)
    • +
    • svn1|r758 (連çµè‡³æŒ‡å®šå„²å­˜æ©Ÿåˆ¶ä¸­ä¹‹è®Šæ›´é›†ï¼Œç”¨æ–¼å°ˆæ¡ˆä½¿ç”¨å¤šå€‹å„²å­˜æ©Ÿåˆ¶æ™‚之情æ³)
    • +
    • commit:hg|c6f4d0fd (連çµè‡³æŒ‡å®šå„²å­˜æ©Ÿåˆ¶ä¸­ï¼Œä½¿ç”¨éžæ•¸å­—雜湊的變更集)
    • +
    • sandbox:r758 (連çµè‡³å…¶ä»–專案的變更集)
    • +
    • sandbox:commit:c6f4d0fd (連çµè‡³å…¶ä»–å°ˆæ¡ˆä¸­ï¼Œä½¿ç”¨éžæ•¸å­—雜湊的變更集)
    • +
  • +
+ +
    +
  • 儲存機制中之檔案: +
      +
    • source:some/file (連çµè‡³å°ˆæ¡ˆå„²å­˜æ©Ÿåˆ¶ä¸­ï¼Œä½æ–¼ /some/file 的檔案)
    • +
    • source:some/file@52 (連çµè‡³æ­¤æª”案的 52 版次)
    • +
    • source:some/file#L120 (連çµè‡³æ­¤æª”案的第 120 行)
    • +
    • source:some/file@52#L120 (連çµè‡³æ­¤æª”案的 52 版刺中之第 120 行)
    • +
    • source:"some file@52#L120" (ç•¶ URL 中包å«ç©ºç™½å­—元時,使用雙引號來標示)
    • +
    • export:some/file (強制下載此檔案)
    • +
    • source:svn1|some/file (連çµè‡³æŒ‡å®šå„²å­˜æ©Ÿåˆ¶ä¸­çš„æ­¤æª”案,用於專案使用多個儲存機制時之情æ³)
    • +
    • sandbox:source:some/file (連çµè‡³ "sandbox" å°ˆæ¡ˆçš„å„²å­˜æ©Ÿåˆ¶ä¸­ï¼Œä½æ–¼ /some/file 的檔案)
    • +
    • sandbox:export:some/file (強迫下載該檔案)
    • +
  • +
+ +
    +
  • 論壇訊æ¯ï¼š +
      +
    • message#1218 (連çµè‡³ç·¨è™Ÿ 1218 的訊æ¯)
    • +
  • +
+ +
    +
  • 專案: +
      +
    • project#3 (連çµè‡³ç·¨è™Ÿç‚º 3 的專案)
    • +
    • project:someproject (連çµè‡³å稱為 "someproject" 的專案)
    • +
  • +
+ + +

逸出字元:

+ +
    +
  • 您å¯ä»¥åœ¨æ–‡å­—çš„å‰é¢åŠ ä¸Šé©šå˜†è™Ÿ (!) 來é¿å…è©²æ–‡å­—è¢«å‰–æžæˆ Redmine 連çµ
  • +
+ + +

外部連çµ

+ +

HTTP URLs 與電å­éƒµä»¶åœ°å€æœƒè‡ªå‹•è¢«è½‰æ›æˆå¯è¢«é»žæ“Šçš„連çµï¼š

+ +
+http://www.redmine.org, someone@foo.bar
+
+ +

顯示為: http://www.redmine.org,

+ +

若您想è¦é¡¯ç¤ºæŒ‡å®šçš„æ–‡å­—而éžè©² URL ,您å¯ä»¥ä½¿ç”¨ä¸‹åˆ—標準的 textile 語法:

+ +
+"Redmine web site":http://www.redmine.org
+
+ +

顯示為: Redmine web site

+ + +

文字格å¼è¨­å®š

+ + +

å°æ–¼è«¸å¦‚標題ã€ç²—é«”ã€è¡¨æ ¼ã€æ¸…單等項目, Redmine 支æ´ä½¿ç”¨ Textile 語法。å¯åƒè€ƒ http://en.wikipedia.org/wiki/Textile_(markup_language) 中關於使用這些格å¼åŒ–功能的說明資訊。 下é¢åŒ…å«äº†ä¸€äº›ä½¿ç”¨ç¯„例,但格å¼åŒ–引擎的處ç†èƒ½åŠ›é å¤šæ–¼é€™äº›ç°¡å–®çš„使用範例。

+ +

字型樣å¼

+ +
+* *ç²—é«”*
+* _斜體_
+* _*粗斜體*_
+* +底線+
+* -刪除線-
+
+ +

顯示為:

+ +
    +
  • ç²—é«”
  • +
  • 斜體
  • +
  • 粗斜體
  • +
  • 底線
  • +
  • 刪除線
  • +
+ +

內置圖åƒ

+ +
    +
  • !圖åƒ_url! é¡¯ç¤ºä¸€å€‹ä½æ–¼ 圖åƒ_url ä½å€çš„圖åƒ(textile 語法)
  • +
  • !>image_url! å³å´æµ®å‹•圖åƒ
  • +
  • 若您附加了一個圖åƒåˆ° Wiki é é¢ä¸­ï¼Œå¯ä»¥ä½¿ç”¨ä»–的檔案å稱來顯示æˆå…§ç½®åœ–åƒï¼š !attached_image.png!
  • +
+ +

標題

+ +
+h1. 標題
+h2. 次標題
+h3. 次次標題
+
+ +

Redmine 為æ¯ä¸€ç¨®æ¨™é¡ŒæŒ‡å®šä¸€å€‹ HTML 錨定 (anchor) ,因此您å¯ä½¿ç”¨ "#Heading" 〠"#Subheading" 等方å¼é€£çµè‡³é€™äº›æ¨™é¡Œã€‚

+ + +

段è½

+ +
+p>. é å³å°é½Š
+p=. 置中å°é½Š
+
+ +

這是一個置中å°é½Šçš„æ®µè½ã€‚

+ + +

引用文字

+ +

使用 bq. 開始一個引文的段è½

+ +
+bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
+To go live, all you need to add is a database and a web server.
+
+ +

顯示為:

+ +
+

Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
To go live, all you need to add is a database and a web server.

+
+ + +

目錄

+ +
+{{toc}} => é å·¦å°é½Šç›®éŒ„
+{{>toc}} => é å³å°é½Šç›®éŒ„
+
+ +

水平線

+ +
+---
+
+ +

巨集

+ +

Redmine 內建下列巨集:

+ +

hello_world

範例巨集。

include

引入一個 wiki é é¢ã€‚例å­ï¼š

+ +
{{include(Foo)}}
macro_list

顯示所有å¯ç”¨å·¨é›†çš„æ¸…單,若巨集有æä¾›èªªæ˜Žä¹Ÿæœƒä¸€ä½µé¡¯ç¤ºã€‚

+ + +

程å¼ç¢¼é†’ç›®æç¤º

+ +

é è¨­ä½¿ç”¨ CodeRay 作為程å¼ç¢¼é†’ç›®æç¤ºçš„æ©Ÿåˆ¶ï¼Œå®ƒæ˜¯ä¸€å€‹ä½¿ç”¨ Ruby 撰寫的語法醒目æç¤ºå‡½å¼åº«ã€‚å®ƒç›®å‰æ”¯æ´ c 〠cpp 〠css 〠delphi 〠groovy 〠html 〠java 〠javascript 〠json 〠php 〠python 〠rhtml 〠ruby 〠scheme 〠sql 〠xml 與 yaml 等程å¼èªžè¨€ã€‚

+ +

您å¯ä»¥ä½¿ç”¨ä¸‹åˆ—語法,在 Wiki é é¢ä¸­å°‡ç¨‹å¼ç¢¼æ¨™ç¤ºç‚ºé†’ç›®æç¤ºï¼š

+ +
+<pre><code class="ruby">
+  將程å¼ç¢¼æ”¾åœ¨é€™è£¡ã€‚
+</code></pre>
+
+ +

例å­ï¼š

+ +
 1 # The Greeter class
+ 2 class Greeter
+ 3   def initialize(name)
+ 4     @name = name.capitalize
+ 5   end
+ 6
+ 7   def salute
+ 8     puts "Hello #{@name}!"
+ 9   end
+10 end
+
+ + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/03/03387de6b294853cc867f560276231ad9e9e4136.svn-base --- a/.svn/pristine/03/03387de6b294853cc867f560276231ad9e9e4136.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -== Redmine upgrade - -Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang -http://www.redmine.org/ - - -== Upgrading - -1. Uncompress the program archive in a new directory - -2. Copy your database settings (RAILS_ROOT/config/database.yml) - and your configuration file (RAILS_ROOT/config/configuration.yml) - into the new config directory - Note: before Redmine 1.2, SMTP configuration was stored in - config/email.yml. It should now be stored in config/configuration.yml. - -3. Copy the RAILS_ROOT/files directory content into your new installation - This directory contains all the attached files. - -4. Copy the folders of the installed plugins and themes into new installation - Plugins must be stored in the [redmine_root]/plugins directory - Themes must be stored in the [redmine_root]/public/themes directory - - WARNING: plugins from your previous Redmine version may not be compatible - with the Redmine version you're upgrading to. - -5. Install the required gems by running: - bundle install --without development test - - If ImageMagick is not installed on your system, you should skip the installation - of the rmagick gem using: - bundle install --without development test rmagick - -6. Generate a session store secret - - Redmine stores session data in cookies by default, which requires - a secret to be generated. Under the new application directory run: - rake generate_secret_token - - DO NOT REPLACE OR EDIT ANY OTHER FILES. - -7. Migrate your database - - If you are upgrading to Rails 2.3.14 as part of this migration, you - need to upgrade the plugin migrations before running the plugin migrations - using: - rake db:migrate:upgrade_plugin_migrations RAILS_ENV="production" - - Please make a backup before doing this! Under the new application - directory run: - rake db:migrate RAILS_ENV="production" - - If you have installed any plugins, you should also run their database - migrations using: - rake db:migrate_plugins RAILS_ENV="production" - -8. Clear the cache and the existing sessions by running: - rake tmp:cache:clear - rake tmp:sessions:clear - -9. Restart the application server (e.g. mongrel, thin, passenger) - -10. Finally go to "Administration -> Roles & permissions" to check/set permissions - for new features, if any - -== References - -* http://www.redmine.org/wiki/redmine/RedmineUpgrade diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/03/038dcd875902d442a622bf1915b8e83469f95936.svn-base --- a/.svn/pristine/03/038dcd875902d442a622bf1915b8e83469f95936.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,177 +0,0 @@ -require File.dirname(__FILE__) + '/helper' -require File.dirname(__FILE__) + '/../init' - -class PaginationTest < ActiveRecordTestCase - fixtures :topics, :replies, :developers, :projects, :developers_projects - - class PaginationController < ActionController::Base - if respond_to? :view_paths= - self.view_paths = [ "#{File.dirname(__FILE__)}/../fixtures/" ] - else - self.template_root = [ "#{File.dirname(__FILE__)}/../fixtures/" ] - end - - def simple_paginate - @topic_pages, @topics = paginate(:topics) - render :nothing => true - end - - def paginate_with_per_page - @topic_pages, @topics = paginate(:topics, :per_page => 1) - render :nothing => true - end - - def paginate_with_order - @topic_pages, @topics = paginate(:topics, :order => 'created_at asc') - render :nothing => true - end - - def paginate_with_order_by - @topic_pages, @topics = paginate(:topics, :order_by => 'created_at asc') - render :nothing => true - end - - def paginate_with_include_and_order - @topic_pages, @topics = paginate(:topics, :include => :replies, :order => 'replies.created_at asc, topics.created_at asc') - render :nothing => true - end - - def paginate_with_conditions - @topic_pages, @topics = paginate(:topics, :conditions => ["created_at > ?", 30.minutes.ago]) - render :nothing => true - end - - def paginate_with_class_name - @developer_pages, @developers = paginate(:developers, :class_name => "DeVeLoPeR") - render :nothing => true - end - - def paginate_with_singular_name - @developer_pages, @developers = paginate() - render :nothing => true - end - - def paginate_with_joins - @developer_pages, @developers = paginate(:developers, - :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1') - render :nothing => true - end - - def paginate_with_join - @developer_pages, @developers = paginate(:developers, - :join => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1') - render :nothing => true - end - - def paginate_with_join_and_count - @developer_pages, @developers = paginate(:developers, - :join => 'd LEFT JOIN developers_projects ON d.id = developers_projects.developer_id', - :conditions => 'project_id=1', - :count => "d.id") - render :nothing => true - end - - def paginate_with_join_and_group - @developer_pages, @developers = paginate(:developers, - :join => 'INNER JOIN developers_projects ON developers.id = developers_projects.developer_id', - :group => 'developers.id') - render :nothing => true - end - - def rescue_errors(e) raise e end - - def rescue_action(e) raise end - - end - - def setup - @controller = PaginationController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - super - end - - # Single Action Pagination Tests - - def test_simple_paginate - get :simple_paginate - assert_equal 1, assigns(:topic_pages).page_count - assert_equal 3, assigns(:topics).size - end - - def test_paginate_with_per_page - get :paginate_with_per_page - assert_equal 1, assigns(:topics).size - assert_equal 3, assigns(:topic_pages).page_count - end - - def test_paginate_with_order - get :paginate_with_order - expected = [topics(:futurama), - topics(:harvey_birdman), - topics(:rails)] - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_order_by - get :paginate_with_order - expected = assigns(:topics) - get :paginate_with_order_by - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_conditions - get :paginate_with_conditions - expected = [topics(:rails)] - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_class_name - get :paginate_with_class_name - - assert assigns(:developers).size > 0 - assert_equal DeVeLoPeR, assigns(:developers).first.class - end - - def test_paginate_with_joins - get :paginate_with_joins - assert_equal 2, assigns(:developers).size - developer_names = assigns(:developers).map { |d| d.name } - assert developer_names.include?('David') - assert developer_names.include?('Jamis') - end - - def test_paginate_with_join_and_conditions - get :paginate_with_joins - expected = assigns(:developers) - get :paginate_with_join - assert_equal expected, assigns(:developers) - end - - def test_paginate_with_join_and_count - get :paginate_with_joins - expected = assigns(:developers) - get :paginate_with_join_and_count - assert_equal expected, assigns(:developers) - end - - def test_paginate_with_include_and_order - get :paginate_with_include_and_order - expected = Topic.find(:all, :include => 'replies', :order => 'replies.created_at asc, topics.created_at asc', :limit => 10) - assert_equal expected, assigns(:topics) - end - - def test_paginate_with_join_and_group - get :paginate_with_join_and_group - assert_equal 2, assigns(:developers).size - assert_equal 2, assigns(:developer_pages).item_count - developer_names = assigns(:developers).map { |d| d.name } - assert developer_names.include?('David') - assert developer_names.include?('Jamis') - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/03/03da074b61ddfe4ad348c236abb94ecdb763073e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/03/03da074b61ddfe4ad348c236abb94ecdb763073e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,111 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class NewsController < ApplicationController + default_search_scope :news + model_object News + before_filter :find_model_object, :except => [:new, :create, :index] + before_filter :find_project_from_association, :except => [:new, :create, :index] + before_filter :find_project_by_project_id, :only => [:new, :create] + before_filter :authorize, :except => [:index] + before_filter :find_optional_project, :only => :index + accept_rss_auth :index + accept_api_auth :index + + helper :watchers + helper :attachments + + def index + case params[:format] + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = 10 + end + + scope = @project ? @project.news.visible : News.visible + + @news_count = scope.count + @news_pages = Paginator.new @news_count, @limit, params['page'] + @offset ||= @news_pages.offset + @newss = scope.all(:include => [:author, :project], + :order => "#{News.table_name}.created_on DESC", + :offset => @offset, + :limit => @limit) + + respond_to do |format| + format.html { + @news = News.new # for adding news inline + render :layout => false if request.xhr? + } + format.api + format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") } + end + end + + def show + @comments = @news.comments + @comments.reverse! if User.current.wants_comments_in_reverse_order? + end + + def new + @news = News.new(:project => @project, :author => User.current) + end + + def create + @news = News.new(:project => @project, :author => User.current) + @news.safe_attributes = params[:news] + @news.save_attachments(params[:attachments]) + if @news.save + render_attachment_warning_if_needed(@news) + flash[:notice] = l(:notice_successful_create) + redirect_to project_news_index_path(@project) + else + render :action => 'new' + end + end + + def edit + end + + def update + @news.safe_attributes = params[:news] + @news.save_attachments(params[:attachments]) + if @news.save + render_attachment_warning_if_needed(@news) + flash[:notice] = l(:notice_successful_update) + redirect_to news_path(@news) + else + render :action => 'edit' + end + end + + def destroy + @news.destroy + redirect_to project_news_index_path(@project) + end + + private + + def find_optional_project + return true unless params[:project_id] + @project = Project.find(params[:project_id]) + authorize + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/04/0406b4818e714c1d680ae173e23dbc29be7a3556.svn-base --- a/.svn/pristine/04/0406b4818e714c1d680ae173e23dbc29be7a3556.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1100 +0,0 @@ -# Polish translations for Ruby on Rails -# by Jacek Becela (jacek.becela@gmail.com, http://github.com/ncr) -# by Krzysztof Podejma (kpodejma@customprojects.pl, http://www.customprojects.pl) - -pl: - number: - format: - separator: "," - delimiter: " " - precision: 2 - currency: - format: - format: "%n %u" - unit: "PLN" - percentage: - format: - delimiter: "" - precision: - format: - delimiter: "" - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "B" - other: "B" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - direction: ltr - date: - formats: - default: "%Y-%m-%d" - short: "%d %b" - long: "%d %B %Y" - - day_names: [Niedziela, PoniedziaÅ‚ek, Wtorek, Åšroda, Czwartek, PiÄ…tek, Sobota] - abbr_day_names: [nie, pon, wto, Å›ro, czw, pia, sob] - - month_names: [~, StyczeÅ„, Luty, Marzec, KwiecieÅ„, Maj, Czerwiec, Lipiec, SierpieÅ„, WrzesieÅ„, Październik, Listopad, GrudzieÅ„] - abbr_month_names: [~, sty, lut, mar, kwi, maj, cze, lip, sie, wrz, paź, lis, gru] - order: - - :year - - :month - - :day - - time: - formats: - default: "%a, %d %b %Y, %H:%M:%S %z" - time: "%H:%M" - short: "%d %b, %H:%M" - long: "%d %B %Y, %H:%M" - am: "przed poÅ‚udniem" - pm: "po poÅ‚udniu" - - datetime: - distance_in_words: - half_a_minute: "pół minuty" - less_than_x_seconds: - one: "mniej niż sekundÄ™" - few: "mniej niż %{count} sekundy" - other: "mniej niż %{count} sekund" - x_seconds: - one: "sekundÄ™" - few: "%{count} sekundy" - other: "%{count} sekund" - less_than_x_minutes: - one: "mniej niż minutÄ™" - few: "mniej niż %{count} minuty" - other: "mniej niż %{count} minut" - x_minutes: - one: "minutÄ™" - few: "%{count} minuty" - other: "%{count} minut" - about_x_hours: - one: "okoÅ‚o godziny" - other: "okoÅ‚o %{count} godzin" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 dzieÅ„" - other: "%{count} dni" - about_x_months: - one: "okoÅ‚o miesiÄ…ca" - other: "okoÅ‚o %{count} miesiÄ™cy" - x_months: - one: "1 miesiÄ…c" - few: "%{count} miesiÄ…ce" - other: "%{count} miesiÄ™cy" - about_x_years: - one: "okoÅ‚o roku" - other: "okoÅ‚o %{count} lat" - over_x_years: - one: "ponad rok" - few: "ponad %{count} lata" - other: "ponad %{count} lat" - almost_x_years: - one: "prawie rok" - other: "prawie %{count} lata" - - activerecord: - errors: - template: - header: - one: "%{model} nie zostaÅ‚ zachowany z powodu jednego błędu" - other: "%{model} nie zostaÅ‚ zachowany z powodu %{count} błędów" - body: "Błędy dotyczÄ… nastÄ™pujÄ…cych pól:" - messages: - inclusion: "nie znajduje siÄ™ na liÅ›cie dopuszczalnych wartoÅ›ci" - exclusion: "znajduje siÄ™ na liÅ›cie zabronionych wartoÅ›ci" - invalid: "jest nieprawidÅ‚owe" - confirmation: "nie zgadza siÄ™ z potwierdzeniem" - accepted: "musi być zaakceptowane" - empty: "nie może być puste" - blank: "nie może być puste" - too_long: "jest za dÅ‚ugie (maksymalnie %{count} znaków)" - too_short: "jest za krótkie (minimalnie %{count} znaków)" - wrong_length: "jest nieprawidÅ‚owej dÅ‚ugoÅ›ci (powinna wynosić %{count} znaków)" - taken: "jest już zajÄ™te" - not_a_number: "nie jest liczbÄ…" - greater_than: "musi być wiÄ™ksze niż %{count}" - greater_than_or_equal_to: "musi być wiÄ™ksze lub równe %{count}" - equal_to: "musi być równe %{count}" - less_than: "musi być mniejsze niż %{count}" - less_than_or_equal_to: "musi być mniejsze lub równe %{count}" - odd: "musi być nieparzyste" - even: "musi być parzyste" - greater_than_start_date: "musi być wiÄ™ksze niż poczÄ…tkowa data" - not_same_project: "nie należy do tego samego projektu" - circular_dependency: "Ta relacja może wytworzyć koÅ‚owÄ… zależność" - cant_link_an_issue_with_a_descendant: "Zagadnienie nie może zostać powiÄ…zane z jednym z wÅ‚asnych podzagadnieÅ„" - - support: - array: - sentence_connector: "i" - skip_last_comma: true - - # Keep this line in order to avoid problems with Windows Notepad UTF-8 EF-BB-BFidea... - # Best regards from Lublin@Poland :-) - # PL translation by Mariusz@Olejnik.net, - actionview_instancetag_blank_option: ProszÄ™ wybierz - - button_activate: Aktywuj - button_add: Dodaj - button_annotate: Adnotuj - button_apply: Ustaw - button_archive: Archiwizuj - button_back: Wstecz - button_cancel: Anuluj - button_change: ZmieÅ„ - button_change_password: ZmieÅ„ hasÅ‚o - button_check_all: Zaznacz wszystko - button_clear: Wyczyść - button_configure: Konfiguruj - button_copy: Kopia - button_create: Stwórz - button_delete: UsuÅ„ - button_download: Pobierz - button_edit: Edytuj - button_list: Lista - button_lock: Zablokuj - button_log_time: Dziennik - button_login: Login - button_move: PrzenieÅ› - button_quote: Cytuj - button_rename: ZmieÅ„ nazwÄ™ - button_reply: Odpowiedz - button_reset: Resetuj - button_rollback: Przywróć do tej wersji - button_save: Zapisz - button_sort: Sortuj - button_submit: WyÅ›lij - button_test: Testuj - button_unarchive: Przywróć z archiwum - button_uncheck_all: Odznacz wszystko - button_unlock: Odblokuj - button_unwatch: Nie obserwuj - button_update: Uaktualnij - button_view: Pokaż - button_watch: Obserwuj - default_activity_design: Projektowanie - default_activity_development: Rozwój - default_doc_category_tech: Dokumentacja techniczna - default_doc_category_user: Dokumentacja użytkownika - default_issue_status_in_progress: W Toku - default_issue_status_closed: ZamkniÄ™ty - default_issue_status_feedback: Odpowiedź - default_issue_status_new: Nowy - default_issue_status_rejected: Odrzucony - default_issue_status_resolved: RozwiÄ…zany - default_priority_high: Wysoki - default_priority_immediate: Natychmiastowy - default_priority_low: Niski - default_priority_normal: Normalny - default_priority_urgent: Pilny - default_role_developer: Programista - default_role_manager: Kierownik - default_role_reporter: Wprowadzajacy - default_tracker_bug: Błąd - default_tracker_feature: Zadanie - default_tracker_support: Wsparcie - enumeration_activities: DziaÅ‚ania (Å›ledzenie czasu) - enumeration_doc_categories: Kategorie dokumentów - enumeration_issue_priorities: Priorytety zagadnieÅ„ - error_can_t_load_default_data: "DomyÅ›lna konfiguracja nie może być zaÅ‚adowana: %{value}" - error_issue_not_found_in_project: 'Zaganienie nie zostaÅ‚o znalezione lub nie należy do tego projektu' - error_scm_annotate: "Wpis nie istnieje lub nie można do niego dodawać adnotacji." - error_scm_command_failed: "WystÄ…piÅ‚ błąd przy próbie dostÄ™pu do repozytorium: %{value}" - error_scm_not_found: "Obiekt lub wersja nie zostaÅ‚y znalezione w repozytorium." - field_account: Konto - field_activity: Aktywność - field_admin: Administrator - field_assignable: Zagadnienia mogÄ… być przypisane do tej roli - field_assigned_to: Przydzielony do - field_attr_firstname: ImiÄ™ atrybut - field_attr_lastname: Nazwisko atrybut - field_attr_login: Login atrybut - field_attr_mail: Email atrybut - field_auth_source: Tryb identyfikacji - field_author: Autor - field_base_dn: Base DN - field_category: Kategoria - field_column_names: Nazwy kolumn - field_comments: Komentarz - field_comments_sorting: Pokazuj komentarze - field_created_on: Stworzone - field_default_value: DomyÅ›lny - field_delay: Opóźnienie - field_description: Opis - field_done_ratio: "% Wykonane" - field_downloads: PobraÅ„ - field_due_date: Data oddania - field_effective_date: Data - field_estimated_hours: Szacowany czas - field_field_format: Format - field_filename: Plik - field_filesize: Rozmiar - field_firstname: ImiÄ™ - field_fixed_version: Wersja docelowa - field_hide_mail: Ukryj mój adres email - field_homepage: Strona www - field_host: Host - field_hours: Godzin - field_identifier: Identifikator - field_is_closed: Zagadnienie zamkniÄ™te - field_is_default: DomyÅ›lny status - field_is_filter: Atrybut filtrowania - field_is_for_all: Dla wszystkich projektów - field_is_in_roadmap: Zagadnienie pokazywane na mapie - field_is_public: Publiczny - field_is_required: Wymagane - field_issue: Zagadnienie - field_issue_to: PowiÄ…zania zagadnienia - field_language: JÄ™zyk - field_last_login_on: Ostatnie połączenie - field_lastname: Nazwisko - field_login: Login - field_mail: Email - field_mail_notification: Powiadomienia Email - field_max_length: Maksymalna dÅ‚ugość - field_min_length: Minimalna dÅ‚ugość - field_name: Nazwa - field_new_password: Nowe hasÅ‚o - field_notes: Notatki - field_onthefly: Tworzenie użytkownika w locie - field_parent: Nadprojekt - field_parent_title: Strona rodzica - field_password: HasÅ‚o - field_password_confirmation: Potwierdzenie - field_port: Port - field_possible_values: Możliwe wartoÅ›ci - field_priority: Priorytet - field_project: Projekt - field_redirect_existing_links: Przekierowanie istniejÄ…cych odnoÅ›ników - field_regexp: Wyrażenie regularne - field_role: Rola - field_searchable: Przeszukiwalne - field_spent_on: Data - field_start_date: Start - field_start_page: Strona startowa - field_status: Status - field_subject: Temat - field_subproject: Podprojekt - field_summary: Podsumowanie - field_time_zone: Strefa czasowa - field_title: TytuÅ‚ - field_tracker: Typ zagadnienia - field_type: Typ - field_updated_on: Zmienione - field_url: URL - field_user: Użytkownik - field_value: Wartość - field_version: Wersja - field_vf_personnel: Personel - field_vf_watcher: Obserwator - general_csv_decimal_separator: ',' - general_csv_encoding: UTF-8 - general_csv_separator: ';' - general_first_day_of_week: '1' - general_lang_name: 'Polski' - general_pdf_encoding: UTF-8 - general_text_No: 'Nie' - general_text_Yes: 'Tak' - general_text_no: 'nie' - general_text_yes: 'tak' - gui_validation_error: 1 błąd - gui_validation_error_plural234: "%{count} błędy" - gui_validation_error_plural5: "%{count} błędów" - gui_validation_error_plural: "%{count} błędów" - label_activity: Aktywność - label_add_another_file: Dodaj kolejny plik - label_add_note: Dodaj notatkÄ™ - label_added: dodane - label_added_time_by: "Dodane przez %{author} %{age} temu" - label_administration: Administracja - label_age: Wiek - label_ago: dni temu - label_all: wszystko - label_all_time: caÅ‚y czas - label_all_words: Wszystkie sÅ‚owa - label_and_its_subprojects: "%{value} i podprojekty" - label_applied_status: Stosowany status - label_assigned_to_me_issues: Zagadnienia przypisane do mnie - label_associated_revisions: Skojarzone rewizje - label_attachment: Plik - label_attachment_delete: UsuÅ„ plik - label_attachment_new: Nowy plik - label_attachment_plural: Pliki - label_attribute: Atrybut - label_attribute_plural: Atrybuty - label_auth_source: Tryb identyfikacji - label_auth_source_new: Nowy tryb identyfikacji - label_auth_source_plural: Tryby identyfikacji - label_authentication: Identyfikacja - label_blocked_by: zablokowane przez - label_blocks: blokuje - label_board: Forum - label_board_new: Nowe forum - label_board_plural: Fora - label_boolean: Wartość logiczna - label_browse: PrzeglÄ…d - label_bulk_edit_selected_issues: Zbiorowa edycja zagadnieÅ„ - label_calendar: Kalendarz - label_change_plural: Zmiany - label_change_properties: ZmieÅ„ wÅ‚aÅ›ciwoÅ›ci - label_change_status: Status zmian - label_change_view_all: Pokaż wszystkie zmiany - label_changes_details: Szczegóły wszystkich zmian - label_changeset_plural: Zestawienia zmian - label_chronological_order: W kolejnoÅ›ci chronologicznej - label_closed_issues: zamkniÄ™te - label_closed_issues_plural234: zamkniÄ™te - label_closed_issues_plural5: zamkniÄ™te - label_closed_issues_plural: zamkniÄ™te - label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_comment: Komentarz - label_comment_add: Dodaj komentarz - label_comment_added: Komentarz dodany - label_comment_delete: UsuÅ„ komentarze - label_comment_plural234: Komentarze - label_comment_plural5: Komentarze - label_comment_plural: Komentarze - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_commits_per_author: Zatwierdzenia wedÅ‚ug autorów - label_commits_per_month: Zatwierdzenia wedÅ‚ug miesiÄ™cy - label_confirmation: Potwierdzenie - label_contains: zawiera - label_copied: skopiowano - label_copy_workflow_from: Kopiuj przepÅ‚yw z - label_current_status: Obecny status - label_current_version: Obecna wersja - label_custom_field: Dowolne pole - label_custom_field_new: Nowe dowolne pole - label_custom_field_plural: Dowolne pola - label_date: Data - label_date_from: Z - label_date_range: Zakres datowy - label_date_to: Do - label_day_plural: dni - label_default: DomyÅ›lne - label_default_columns: DomyÅ›lne kolumny - label_deleted: usuniÄ™te - label_details: Szczegóły - label_diff_inline: w linii - label_diff_side_by_side: obok siebie - label_disabled: zablokowany - label_display_per_page: "Na stronÄ™: %{value}" - label_document: Dokument - label_document_added: Dodano dokument - label_document_new: Nowy dokument - label_document_plural: Dokumenty - label_download: "%{count} Pobranie" - label_download_plural234: "%{count} Pobrania" - label_download_plural5: "%{count} PobraÅ„" - label_download_plural: "%{count} Pobrania" - label_downloads_abbr: Pobieranie - label_duplicated_by: zduplikowane przez - label_duplicates: duplikuje - label_end_to_end: koniec do koÅ„ca - label_end_to_start: koniec do poczÄ…tku - label_enumeration_new: Nowa wartość - label_enumerations: Wyliczenia - label_environment: Åšrodowisko - label_equals: równa siÄ™ - label_example: PrzykÅ‚ad - label_export_to: Eksportuj do - label_f_hour: "%{value} godzina" - label_f_hour_plural: "%{value} godzin" - label_feed_plural: Ilość RSS - label_feeds_access_key_created_on: "Klucz dostÄ™pu RSS stworzony %{value} dni temu" - label_file_added: Dodano plik - label_file_plural: Pliki - label_filter_add: Dodaj filtr - label_filter_plural: Filtry - label_float: Liczba rzeczywista - label_follows: nastÄ™puje po - label_gantt: Gantt - label_general: Ogólne - label_generate_key: Wygeneruj klucz - label_help: Pomoc - label_history: Historia - label_home: Główna - label_in: w - label_in_less_than: mniejsze niż - label_in_more_than: wiÄ™ksze niż - label_incoming_emails: PrzychodzÄ…ca poczta elektroniczna - label_index_by_date: Indeks wg daty - label_index_by_title: Indeks - label_information: Informacja - label_information_plural: Informacje - label_integer: Liczba caÅ‚kowita - label_internal: WewnÄ™trzny - label_issue: Zagadnienie - label_issue_added: Dodano zagadnienie - label_issue_category: Kategoria zagadnienia - label_issue_category_new: Nowa kategoria - label_issue_category_plural: Kategorie zagadnieÅ„ - label_issue_new: Nowe zagadnienie - label_issue_plural: Zagadnienia - label_issue_status: Status zagadnienia - label_issue_status_new: Nowy status - label_issue_status_plural: Statusy zagadnieÅ„ - label_issue_tracking: Åšledzenie zagadnieÅ„ - label_issue_updated: Uaktualniono zagadnienie - label_issue_view_all: Zobacz wszystkie zagadnienia - label_issue_watchers: Obserwatorzy - label_issues_by: "Zagadnienia wprowadzone przez %{value}" - label_jump_to_a_project: Skocz do projektu... - label_language_based: Na podstawie jÄ™zyka - label_last_changes: "ostatnie %{count} zmian" - label_last_login: Ostatnie połączenie - label_last_month: ostatni miesiÄ…c - label_last_n_days: "ostatnie %{count} dni" - label_last_week: ostatni tydzieÅ„ - label_latest_revision: Najnowsza rewizja - label_latest_revision_plural: Najnowsze rewizje - label_ldap_authentication: Autoryzacja LDAP - label_less_than_ago: dni mniej - label_list: Lista - label_loading: Åadowanie... - label_logged_as: Zalogowany jako - label_login: Login - label_logout: Wylogowanie - label_max_size: Maksymalny rozmiar - label_me: ja - label_member: Uczestnik - label_member_new: Nowy uczestnik - label_member_plural: Uczestnicy - label_message_last: Ostatnia wiadomość - label_message_new: Nowa wiadomość - label_message_plural: WiadomoÅ›ci - label_message_posted: Dodano wiadomość - label_min_max_length: Min - Maks dÅ‚ugość - label_modification: "%{count} modyfikacja" - label_modification_plural234: "%{count} modyfikacje" - label_modification_plural5: "%{count} modyfikacji" - label_modification_plural: "%{count} modyfikacje" - label_modified: zmodyfikowane - label_module_plural: ModuÅ‚y - label_month: MiesiÄ…c - label_months_from: miesiÄ…ce od - label_more: WiÄ™cej - label_more_than_ago: dni wiÄ™cej - label_my_account: Moje konto - label_my_page: Moja strona - label_my_projects: Moje projekty - label_new: Nowy - label_new_statuses_allowed: Uprawnione nowe statusy - label_news: Komunikat - label_news_added: Dodano komunikat - label_news_latest: Ostatnie komunikaty - label_news_new: Dodaj komunikat - label_news_plural: Komunikaty - label_news_view_all: Pokaż wszystkie komunikaty - label_next: NastÄ™pne - label_no_change_option: (Bez zmian) - label_no_data: Brak danych do pokazania - label_nobody: nikt - label_none: brak - label_not_contains: nie zawiera - label_not_equals: różni siÄ™ - label_open_issues: otwarte - label_open_issues_plural234: otwarte - label_open_issues_plural5: otwarte - label_open_issues_plural: otwarte - label_optional_description: Opcjonalny opis - label_options: Opcje - label_overall_activity: Ogólna aktywność - label_overview: PrzeglÄ…d - label_password_lost: Zapomniane hasÅ‚o - label_per_page: Na stronÄ™ - label_permissions: Uprawnienia - label_permissions_report: Raport uprawnieÅ„ - label_personalize_page: Personalizuj tÄ… stronÄ™ - label_planning: Planowanie - label_please_login: Zaloguj siÄ™ - label_plugins: Wtyczki - label_precedes: poprzedza - label_preferences: Preferencje - label_preview: PodglÄ…d - label_previous: Poprzednie - label_project: Projekt - label_project_all: Wszystkie projekty - label_project_latest: Ostatnie projekty - label_project_new: Nowy projekt - label_project_plural234: Projekty - label_project_plural5: Projektów - label_project_plural: Projekty - label_x_projects: - zero: brak projektów - one: jeden projekt - other: "%{count} projektów" - label_public_projects: Projekty publiczne - label_query: Kwerenda - label_query_new: Nowa kwerenda - label_query_plural: Kwerendy - label_read: Czytanie... - label_register: Rejestracja - label_registered_on: Zarejestrowany - label_registration_activation_by_email: aktywacja konta przez e-mail - label_registration_automatic_activation: automatyczna aktywacja kont - label_registration_manual_activation: manualna aktywacja kont - label_related_issues: PowiÄ…zane zagadnienia - label_relates_to: powiÄ…zane z - label_relation_delete: UsuÅ„ powiÄ…zanie - label_relation_new: Nowe powiÄ…zanie - label_renamed: przemianowano - label_reply_plural: Odpowiedzi - label_report: Raport - label_report_plural: Raporty - label_reported_issues: Wprowadzone zagadnienia - label_repository: Repozytorium - label_repository_plural: Repozytoria - label_result_plural: Rezultatów - label_reverse_chronological_order: W kolejnoÅ›ci odwrotnej do chronologicznej - label_revision: Rewizja - label_revision_plural: Rewizje - label_roadmap: Mapa - label_roadmap_due_in: W czasie - label_roadmap_no_issues: Brak zagadnieÅ„ do tej wersji - label_roadmap_overdue: "%{value} spóźnienia" - label_role: Rola - label_role_and_permissions: Role i Uprawnienia - label_role_new: Nowa rola - label_role_plural: Role - label_scm: SCM - label_search: Szukaj - label_search_titles_only: Przeszukuj tylko tytuÅ‚y - label_send_information: WyÅ›lij informacjÄ™ użytkownikowi - label_send_test_email: WyÅ›lij próbny email - label_settings: Ustawienia - label_show_completed_versions: Pokaż kompletne wersje - label_sort_by: "Sortuj po %{value}" - label_sort_higher: Do góry - label_sort_highest: PrzesuÅ„ na górÄ™ - label_sort_lower: Do doÅ‚u - label_sort_lowest: PrzesuÅ„ na dół - label_spent_time: Przepracowany czas - label_start_to_end: poczÄ…tek do koÅ„ca - label_start_to_start: poczÄ…tek do poczÄ…tku - label_statistics: Statystyki - label_stay_logged_in: PozostaÅ„ zalogowany - label_string: Tekst - label_subproject_plural: Podprojekty - label_text: DÅ‚ugi tekst - label_theme: Temat - label_this_month: ten miesiÄ…c - label_this_week: ten tydzieÅ„ - label_this_year: ten rok - label_time_tracking: Åšledzenie czasu pracy - label_today: dzisiaj - label_topic_plural: Tematy - label_total: Ogółem - label_tracker: Typ zagadnienia - label_tracker_new: Nowy typ zagadnienia - label_tracker_plural: Typy zagadnieÅ„ - label_updated_time: "Zaktualizowane %{value} temu" - label_used_by: Używane przez - label_user: Użytkownik - label_user_mail_no_self_notified: "Nie chcÄ™ powiadomieÅ„ o zmianach, które sam wprowadzam." - label_user_mail_option_all: "Dla każdego zdarzenia w każdym moim projekcie" - label_user_mail_option_selected: "Tylko dla każdego zdarzenia w wybranych projektach..." - label_user_new: Nowy użytkownik - label_user_plural: Użytkownicy - label_version: Wersja - label_version_new: Nowa wersja - label_version_plural: Wersje - label_view_diff: Pokaż różnice - label_view_revisions: Pokaż rewizje - label_watched_issues: Obserwowane zagadnienia - label_week: TydzieÅ„ - label_wiki: Wiki - label_wiki_edit: Edycja wiki - label_wiki_edit_plural: Edycje wiki - label_wiki_page: Strona wiki - label_wiki_page_plural: Strony wiki - label_workflow: PrzepÅ‚yw - label_year: Rok - label_yesterday: wczoraj - mail_body_account_activation_request: "Zarejestrowano nowego użytkownika: (%{value}). Konto oczekuje na twoje zatwierdzenie:" - mail_body_account_information: Twoje konto - mail_body_account_information_external: "Możesz użyć twojego %{value} konta do zalogowania." - mail_body_lost_password: 'W celu zmiany swojego hasÅ‚a użyj poniższego odnoÅ›nika:' - mail_body_register: 'W celu aktywacji Twojego konta, użyj poniższego odnoÅ›nika:' - mail_body_reminder: "Wykaz przypisanych do Ciebie zagadnieÅ„, których termin wypada w ciÄ…gu nastÄ™pnych %{count} dni" - mail_subject_account_activation_request: "Zapytanie aktywacyjne konta %{value}" - mail_subject_lost_password: "Twoje hasÅ‚o do %{value}" - mail_subject_register: "Aktywacja konta w %{value}" - mail_subject_reminder: "Uwaga na terminy, masz zagadnienia do obsÅ‚użenia w ciÄ…gu nastÄ™pnych %{count} dni! (%{days})" - notice_account_activated: Twoje konto zostaÅ‚o aktywowane. Możesz siÄ™ zalogować. - notice_account_invalid_creditentials: ZÅ‚y użytkownik lub hasÅ‚o - notice_account_lost_email_sent: Email z instrukcjami zmiany hasÅ‚a zostaÅ‚ wysÅ‚any do Ciebie. - notice_account_password_updated: HasÅ‚o prawidÅ‚owo zmienione. - notice_account_pending: "Twoje konto zostaÅ‚o utworzone i oczekuje na zatwierdzenie administratora." - notice_account_register_done: Konto prawidÅ‚owo stworzone. - notice_account_unknown_email: Nieznany użytkownik. - notice_account_updated: Konto prawidÅ‚owo zaktualizowane. - notice_account_wrong_password: ZÅ‚e hasÅ‚o - notice_can_t_change_password: To konto ma zewnÄ™trzne źródÅ‚o identyfikacji. Nie możesz zmienić hasÅ‚a. - notice_default_data_loaded: DomyÅ›lna konfiguracja zostaÅ‚a pomyÅ›lnie zaÅ‚adowana. - notice_email_error: "WystÄ…piÅ‚ błąd w trakcie wysyÅ‚ania maila (%{value})" - notice_email_sent: "Email zostaÅ‚ wysÅ‚any do %{value}" - notice_failed_to_save_issues: "Błąd podczas zapisu zagadnieÅ„ %{count} z %{total} zaznaczonych: %{ids}." - notice_feeds_access_key_reseted: Twój klucz dostÄ™pu RSS zostaÅ‚ zrestetowany. - notice_file_not_found: Strona do której próbujesz siÄ™ dostać nie istnieje lub zostaÅ‚a usuniÄ™ta. - notice_locking_conflict: Dane poprawione przez innego użytkownika. - notice_no_issue_selected: "Nie wybrano zagadnienia! Zaznacz zagadnienie, które chcesz edytować." - notice_not_authorized: Nie jesteÅ› autoryzowany by zobaczyć stronÄ™. - notice_successful_connection: Udane nawiÄ…zanie połączenia. - notice_successful_create: Utworzenie zakoÅ„czone sukcesem. - notice_successful_delete: UsuniÄ™cie zakoÅ„czone sukcesem. - notice_successful_update: Uaktualnienie zakoÅ„czone sukcesem. - notice_unable_delete_version: Nie można usunąć wersji - permission_add_issue_notes: Dodawanie notatek - permission_add_issue_watchers: Dodawanie obserwatorów - permission_add_issues: Dodawanie zagadnieÅ„ - permission_add_messages: Dodawanie wiadomoÅ›ci - permission_browse_repository: PrzeglÄ…danie repozytorium - permission_comment_news: Komentowanie komunikatów - permission_commit_access: Wykonywanie zatwierdzeÅ„ - permission_delete_issues: Usuwanie zagadnieÅ„ - permission_delete_messages: Usuwanie wiadomoÅ›ci - permission_delete_wiki_pages: Usuwanie stron wiki - permission_delete_wiki_pages_attachments: Usuwanie załączników - permission_delete_own_messages: Usuwanie wÅ‚asnych wiadomoÅ›ci - permission_edit_issue_notes: Edycja notatek - permission_edit_issues: Edycja zagadnieÅ„ - permission_edit_messages: Edycja wiadomoÅ›ci - permission_edit_own_issue_notes: Edycja wÅ‚asnych notatek - permission_edit_own_messages: Edycja wÅ‚asnych wiadomoÅ›ci - permission_edit_own_time_entries: Edycja wÅ‚asnego dziennika - permission_edit_project: Edycja projektów - permission_edit_time_entries: Edycja wpisów dziennika - permission_edit_wiki_pages: Edycja stron wiki - permission_log_time: Zapisywanie przepracowanego czasu - permission_manage_boards: ZarzÄ…dzanie forami - permission_manage_categories: ZarzÄ…dzanie kategoriami zaganieÅ„ - permission_manage_documents: ZarzÄ…dzanie dokumentami - permission_manage_files: ZarzÄ…dzanie plikami - permission_manage_issue_relations: ZarzÄ…dzanie powiÄ…zaniami zagadnieÅ„ - permission_manage_members: ZarzÄ…dzanie uczestnikami - permission_manage_news: ZarzÄ…dzanie komunikatami - permission_manage_public_queries: ZarzÄ…dzanie publicznymi kwerendami - permission_manage_repository: ZarzÄ…dzanie repozytorium - permission_manage_versions: ZarzÄ…dzanie wersjami - permission_manage_wiki: ZarzÄ…dzanie wiki - permission_move_issues: Przenoszenie zagadnieÅ„ - permission_protect_wiki_pages: Blokowanie stron wiki - permission_rename_wiki_pages: Zmiana nazw stron wiki - permission_save_queries: Zapisywanie kwerend - permission_select_project_modules: Wybieranie modułów projektu - permission_view_calendar: PodglÄ…d kalendarza - permission_view_changesets: PodglÄ…d zmian - permission_view_documents: PodglÄ…d dokumentów - permission_view_files: PodglÄ…d plików - permission_view_gantt: PodglÄ…d diagramu Gantta - permission_view_issue_watchers: PodglÄ…d listy obserwatorów - permission_view_messages: PodglÄ…d wiadomoÅ›ci - permission_view_time_entries: PodglÄ…d przepracowanego czasu - permission_view_wiki_edits: PodglÄ…d historii wiki - permission_view_wiki_pages: PodglÄ…d wiki - project_module_boards: Fora - project_module_documents: Dokumenty - project_module_files: Pliki - project_module_issue_tracking: Åšledzenie zagadnieÅ„ - project_module_news: Komunikaty - project_module_repository: Repozytorium - project_module_time_tracking: Åšledzenie czasu pracy - project_module_wiki: Wiki - setting_activity_days_default: Dni wyÅ›wietlane w aktywnoÅ›ci projektu - setting_app_subtitle: PodtytuÅ‚ aplikacji - setting_app_title: TytuÅ‚ aplikacji - setting_attachment_max_size: Maks. rozm. załącznika - setting_autofetch_changesets: Automatyczne pobieranie zmian - setting_autologin: Auto logowanie - setting_bcc_recipients: Odbiorcy kopii tajnej (kt/bcc) - setting_commit_fix_keywords: SÅ‚owa zmieniajÄ…ce status - setting_commit_ref_keywords: SÅ‚owa tworzÄ…ce powiÄ…zania - setting_cross_project_issue_relations: Zezwól na powiÄ…zania zagadnieÅ„ miÄ™dzy projektami - setting_date_format: Format daty - setting_default_language: DomyÅ›lny jÄ™zyk - setting_default_projects_public: Nowe projekty sÄ… domyÅ›lnie publiczne - setting_display_subprojects_issues: DomyÅ›lnie pokazuj zagadnienia podprojektów w głównym projekcie - setting_emails_footer: Stopka e-mail - setting_enabled_scm: DostÄ™pny SCM - setting_feeds_limit: Limit danych RSS - setting_gravatar_enabled: Używaj ikon użytkowników Gravatar - setting_host_name: Nazwa hosta i Å›cieżka - setting_issue_list_default_columns: DomyÅ›lne kolumny wyÅ›wietlane na liÅ›cie zagadnieÅ„ - setting_issues_export_limit: Limit eksportu zagadnieÅ„ - setting_login_required: Identyfikacja wymagana - setting_mail_from: Adres email wysyÅ‚ki - setting_mail_handler_api_enabled: Uaktywnij usÅ‚ugi sieciowe (WebServices) dla poczty przychodzÄ…cej - setting_mail_handler_api_key: Klucz API - setting_per_page_options: Opcje iloÅ›ci obiektów na stronie - setting_plain_text_mail: tylko tekst (bez HTML) - setting_protocol: ProtokoÅ‚ - setting_self_registration: Samodzielna rejestracja użytkowników - setting_sequential_project_identifiers: Generuj sekwencyjne identyfikatory projektów - setting_sys_api_enabled: Włączenie WS do zarzÄ…dzania repozytorium - setting_text_formatting: Formatowanie tekstu - setting_time_format: Format czasu - setting_user_format: Personalny format wyÅ›wietlania - setting_welcome_text: Tekst powitalny - setting_wiki_compression: Kompresja historii Wiki - status_active: aktywny - status_locked: zablokowany - status_registered: zarejestrowany - text_are_you_sure: JesteÅ› pewien ? - text_assign_time_entries_to_project: Przypisz wpisy dziennika do projektu - text_caracters_maximum: "%{count} znaków maksymalnie." - text_caracters_minimum: "Musi być nie krótsze niż %{count} znaków." - text_comma_separated: Wielokrotne wartoÅ›ci dozwolone (rozdzielone przecinkami). - text_default_administrator_account_changed: Zmieniono domyÅ›lne hasÅ‚o administratora - text_destroy_time_entries: UsuÅ„ wpisy dziennika - text_destroy_time_entries_question: Przepracowano %{hours} godzin przy zagadnieniu, które chcesz usunąć. Co chcesz zrobić? - text_email_delivery_not_configured: "Dostarczanie poczty elektronicznej nie zostaÅ‚o skonfigurowane, wiÄ™c powiadamianie jest nieaktywne.\nSkonfiguruj serwer SMTP w config/configuration.yml a nastÄ™pnie zrestartuj aplikacjÄ™ i uaktywnij to." - text_enumeration_category_reassign_to: 'ZmieÅ„ przypisanie na tÄ… wartość:' - text_enumeration_destroy_question: "%{count} obiektów jest przypisana do tej wartoÅ›ci." - text_file_repository_writable: Zapisywalne repozytorium plików - text_issue_added: "Zagadnienie %{id} zostaÅ‚o wprowadzone (by %{author})." - text_issue_category_destroy_assignments: UsuÅ„ przydziaÅ‚y kategorii - text_issue_category_destroy_question: "Zagadnienia (%{count}) sÄ… przypisane do tej kategorii. Co chcesz zrobić?" - text_issue_category_reassign_to: Przydziel zagadnienie do tej kategorii - text_issue_updated: "Zagadnienie %{id} zostaÅ‚o zaktualizowane (by %{author})." - text_issues_destroy_confirmation: 'Czy jestes pewien, że chcesz usunąć wskazane zagadnienia?' - text_issues_ref_in_commit_messages: OdwoÅ‚ania do zagadnieÅ„ w komentarzach zatwierdzeÅ„ - text_length_between: "DÅ‚ugość pomiÄ™dzy %{min} i %{max} znaków." - text_load_default_configuration: ZaÅ‚aduj domyÅ›lnÄ… konfiguracjÄ™ - text_min_max_length_info: 0 oznacza brak restrykcji - text_no_configuration_data: "Role użytkowników, typy zagadnieÅ„, statusy zagadnieÅ„ oraz przepÅ‚yw pracy nie zostaÅ‚y jeszcze skonfigurowane.\nJest wysoce rekomendowane by zaÅ‚adować domyÅ›lnÄ… konfiguracjÄ™. Po zaÅ‚adowaniu bÄ™dzie możliwość edycji tych danych." - text_project_destroy_confirmation: JesteÅ› pewien, że chcesz usunąć ten projekt i wszystkie powiÄ…zane dane? - text_reassign_time_entries: 'Przepnij przepracowany czas do tego zagadnienia:' - text_regexp_info: np. ^[A-Z0-9]+$ - text_repository_usernames_mapping: "Wybierz lub uaktualnij przyporzÄ…dkowanie użytkowników Redmine do użytkowników repozytorium.\nUżytkownicy z takÄ… samÄ… nazwÄ… lub adresem email sÄ… przyporzÄ…dkowani automatycznie." - text_rmagick_available: RMagick dostÄ™pne (opcjonalnie) - text_select_mail_notifications: Zaznacz czynnoÅ›ci przy których użytkownik powinien być powiadomiony mailem. - text_select_project_modules: 'Wybierz moduÅ‚y do aktywacji w tym projekcie:' - text_status_changed_by_changeset: "Zastosowane w zmianach %{value}." - text_subprojects_destroy_warning: "Podprojekt(y): %{value} zostanÄ… także usuniÄ™te." - text_tip_issue_begin_day: zadanie zaczynajÄ…ce siÄ™ dzisiaj - text_tip_issue_begin_end_day: zadanie zaczynajÄ…ce i koÅ„czÄ…ce siÄ™ dzisiaj - text_tip_issue_end_day: zadanie koÅ„czÄ…ce siÄ™ dzisiaj - text_tracker_no_workflow: Brak przepÅ‚ywu zdefiniowanego dla tego typu zagadnienia - text_unallowed_characters: Niedozwolone znaki - text_user_mail_option: "W przypadku niezaznaczonych projektów, bÄ™dziesz otrzymywaÅ‚ powiadomienia tylko na temat zagadnieÅ„, które obserwujesz, lub w których bierzesz udziaÅ‚ (np. jesteÅ› autorem lub adresatem)." - text_user_wrote: "%{value} napisaÅ‚:" - text_wiki_destroy_confirmation: JesteÅ› pewien, że chcesz usunąć to wiki i całą jego zawartość ? - text_workflow_edit: Zaznacz rolÄ™ i typ zagadnienia do edycji przepÅ‚ywu - - label_user_activity: "Aktywność: %{value}" - label_updated_time_by: "Uaktualnione przez %{author} %{age} temu" - text_diff_truncated: '... Ten plik różnic zostaÅ‚ przyciÄ™ty ponieważ jest zbyt dÅ‚ugi.' - setting_diff_max_lines_displayed: Maksymalna liczba linii różnicy do pokazania - text_plugin_assets_writable: Zapisywalny katalog zasobów wtyczek - warning_attachments_not_saved: "%{count} załącznik(ów) nie zostaÅ‚o zapisanych." - field_editable: Edytowalne - label_display: WyglÄ…d - button_create_and_continue: Stwórz i dodaj kolejne - text_custom_field_possible_values_info: 'Każda wartość w osobnej linii' - setting_repository_log_display_limit: Maksymalna liczba rewizji pokazywanych w logu pliku - setting_file_max_size_displayed: Maksymalny rozmiar plików tekstowych osadzanych w stronie - field_watcher: Obserwator - setting_openid: Logowanie i rejestracja przy użyciu OpenID - field_identity_url: Identyfikator OpenID (URL) - label_login_with_open_id_option: albo użyj OpenID - field_content: Treść - label_descending: MalejÄ…co - label_sort: Sortuj - label_ascending: RosnÄ…co - label_date_from_to: Od %{start} do %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Ta strona posiada podstrony (%{descendants}). Co chcesz zrobić? - text_wiki_page_reassign_children: Podepnij je do strony nadrzÄ™dnej wzglÄ™dem usuwanej - text_wiki_page_nullify_children: PrzesuÅ„ je na szczyt hierarchii - text_wiki_page_destroy_children: UsuÅ„ wszystkie podstrony - setting_password_min_length: Minimalna dÅ‚ugość hasÅ‚a - field_group_by: Grupuj wyniki wg - mail_subject_wiki_content_updated: "Strona wiki '%{id}' zostaÅ‚a uaktualniona" - label_wiki_content_added: Dodano stronÄ™ wiki - mail_subject_wiki_content_added: "Strona wiki '%{id}' zostaÅ‚a dodana" - mail_body_wiki_content_added: Strona wiki '%{id}' zostaÅ‚a dodana przez %{author}. - label_wiki_content_updated: Uaktualniono stronÄ™ wiki - mail_body_wiki_content_updated: Strona wiki '%{id}' zostaÅ‚a uaktualniona przez %{author}. - permission_add_project: Tworzenie projektu - setting_new_project_user_role_id: Rola nadawana twórcom projektów, którzy nie posiadajÄ… uprawnieÅ„ administatora - label_view_all_revisions: Pokaż wszystkie rewizje - label_tag: SÅ‚owo kluczowe - label_branch: Gałąź - error_no_tracker_in_project: Projekt nie posiada powiÄ…zanych typów zagadnieÅ„. Sprawdź ustawienia projektu. - error_no_default_issue_status: Nie zdefiniowano domyÅ›lnego statusu zagadnieÅ„. Sprawdź konfiguracjÄ™ (Przejdź do "Administracja -> Statusy zagadnieÅ„). - text_journal_changed: "Zmieniono %{label} z %{old} na %{new}" - text_journal_set_to: "Ustawiono %{label} na %{value}" - text_journal_deleted: "UsuniÄ™to %{label} (%{old})" - label_group_plural: Grupy - label_group: Grupa - label_group_new: Nowa grupa - label_time_entry_plural: Przepracowany czas - text_journal_added: "Dodano %{label} %{value}" - field_active: Aktywne - enumeration_system_activity: Aktywność Systemowa - button_copy_and_follow: Kopiuj i przejdź do kopii zagadnienia - button_duplicate: Duplikuj - button_move_and_follow: PrzenieÅ› i przejdź do zagadnienia - button_show: Pokaż - error_can_not_archive_project: Ten projekt nie może zostać zarchiwizowany - error_can_not_reopen_issue_on_closed_version: Zagadnienie przydzielone do zakoÅ„czonej wersji nie może zostać ponownie otwarte - error_issue_done_ratios_not_updated: "% wykonania zagadnienia nie zostaÅ‚ uaktualniony." - error_workflow_copy_source: ProszÄ™ wybrać źródÅ‚owy typ zagadnienia lub rolÄ™ - error_workflow_copy_target: ProszÄ™ wybrać docelowe typ(y) zagadnieÅ„ i rolÄ™(e) - field_sharing: Współdzielenie - label_api_access_key: Klucz dostÄ™pu do API - label_api_access_key_created_on: Klucz dostÄ™pu do API zostaÅ‚ utworzony %{value} temu - label_close_versions: Zamknij ukoÅ„czone wersje - label_copy_same_as_target: Jak cel - label_copy_source: ŹródÅ‚o - label_copy_target: Cel - label_display_used_statuses_only: WyÅ›wietlaj tylko statusy używane przez ten typ zagadnienia - label_feeds_access_key: Klucz dostÄ™pu do RSS - label_missing_api_access_key: Brakuje klucza dostÄ™pu do API - label_missing_feeds_access_key: Brakuje klucza dostÄ™pu do RSS - label_revision_id: Rewizja %{value} - label_subproject_new: Nowy podprojekt - label_update_issue_done_ratios: Uaktualnij % wykonania - label_user_anonymous: Anonimowy - label_version_sharing_descendants: Z podprojektami - label_version_sharing_hierarchy: Z hierarchiÄ… projektów - label_version_sharing_none: Brak współdzielenia - label_version_sharing_system: Ze wszystkimi projektami - label_version_sharing_tree: Z drzewem projektów - notice_api_access_key_reseted: Twój klucz dostÄ™pu do API zostaÅ‚ zresetowany. - notice_issue_done_ratios_updated: Uaktualnienie % wykonania zakoÅ„czone sukcesem. - permission_add_subprojects: Tworzenie podprojektów - permission_delete_issue_watchers: UsuÅ„ obserwatorów - permission_view_issues: PrzeglÄ…danie zagadnieÅ„ - setting_default_projects_modules: DomyÅ›lnie włączone moduÅ‚y dla nowo tworzonych projektów - setting_gravatar_default: DomyÅ›lny obraz Gravatar - setting_issue_done_ratio: Obliczaj postÄ™p realizacji zagadnieÅ„ za pomocÄ… - setting_issue_done_ratio_issue_field: "% Wykonania zagadnienia" - setting_issue_done_ratio_issue_status: Statusu zagadnienia - setting_mail_handler_body_delimiters: Przycinaj e-maile po jednej z tych linii - setting_rest_api_enabled: Uaktywnij usÅ‚ugÄ™ sieciowÄ… REST - setting_start_of_week: Pierwszy dzieÅ„ tygodnia - text_line_separated: Dozwolone jest wiele wartoÅ›ci (każda wartość w osobnej linii). - text_own_membership_delete_confirmation: |- - Masz zamiar usunąć niektóre lub wszystkie swoje uprawnienia. Po wykonaniu tej czynnoÅ›ci możesz utracić możliwoÅ›ci edycji tego projektu. - Czy na pewno chcesz kontynuować? - version_status_closed: zamkniÄ™ta - version_status_locked: zablokowana - version_status_open: otwarta - - label_board_sticky: Przyklejona - label_board_locked: ZamkniÄ™ta - permission_export_wiki_pages: Eksport stron wiki - permission_manage_project_activities: ZarzÄ…dzanie aktywnoÅ›ciami projektu - setting_cache_formatted_text: Buforuj sformatowany tekst - error_unable_delete_issue_status: Nie można usunąć statusu zagadnienia - label_profile: Profil - permission_manage_subtasks: ZarzÄ…dzanie podzagadnieniami - field_parent_issue: Zagadnienie nadrzÄ™dne - label_subtask_plural: Podzagadnienia - label_project_copy_notifications: WyÅ›lij powiadomienia mailowe przy kopiowaniu projektu - error_can_not_delete_custom_field: Nie można usunąć tego pola - error_unable_to_connect: Nie można połączyć (%{value}) - error_can_not_remove_role: Ta rola przypisana jest niektórym użytkownikom i nie może zostać usuniÄ™ta. - error_can_not_delete_tracker: Ten typ przypisany jest do części zagadnieÅ„ i nie może zostać usuniÄ™ty. - field_principal: PrzeÅ‚ożony - label_my_page_block: Elementy - notice_failed_to_save_members: "Nie można zapisać uczestników: %{errors}." - text_zoom_out: Zmniejsz czcionkÄ™ - text_zoom_in: PowiÄ™ksz czcionkÄ™ - notice_unable_delete_time_entry: Nie można usunąć wpisu z dziennika. - label_overall_spent_time: Przepracowany czas - field_time_entries: Dziennik - project_module_gantt: Diagram Gantta - project_module_calendar: Kalendarz - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Kodowanie komentarzy zatwierdzeÅ„ - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 zagadnienie - one: 1 zagadnienie - other: "%{count} zagadnienia" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: wszystko - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Z podprojektami - label_cross_project_tree: Z drzewem projektów - label_cross_project_hierarchy: Z hierarchiÄ… projektów - label_cross_project_system: Ze wszystkimi projektami - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/04/042891243ed00da7c541ac0d9a5f19f90a344ef6.svn-base --- a/.svn/pristine/04/042891243ed00da7c541ac0d9a5f19f90a344ef6.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1133 +0,0 @@ -# Korean translations for Ruby on Rails -ko: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y/%m/%d" - short: "%m/%d" - long: "%Yë…„ %mì›” %dì¼ (%a)" - - day_names: [ì¼ìš”ì¼, 월요ì¼, 화요ì¼, 수요ì¼, 목요ì¼, 금요ì¼, 토요ì¼] - abbr_day_names: [ì¼, ì›”, í™”, 수, 목, 금, 토] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, 1ì›”, 2ì›”, 3ì›”, 4ì›”, 5ì›”, 6ì›”, 7ì›”, 8ì›”, 9ì›”, 10ì›”, 11ì›”, 12ì›”] - abbr_month_names: [~, 1ì›”, 2ì›”, 3ì›”, 4ì›”, 5ì›”, 6ì›”, 7ì›”, 8ì›”, 9ì›”, 10ì›”, 11ì›”, 12ì›”] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%Y/%m/%d %H:%M:%S" - time: "%H:%M" - short: "%y/%m/%d %H:%M" - long: "%Yë…„ %Bì›” %dì¼, %H시 %Më¶„ %Sì´ˆ %Z" - am: "오전" - pm: "오후" - - datetime: - distance_in_words: - half_a_minute: "30ì´ˆ" - less_than_x_seconds: - one: "ì¼ì´ˆ ì´í•˜" - other: "%{count}ì´ˆ ì´í•˜" - x_seconds: - one: "ì¼ì´ˆ" - other: "%{count}ì´ˆ" - less_than_x_minutes: - one: "ì¼ë¶„ ì´í•˜" - other: "%{count}ë¶„ ì´í•˜" - x_minutes: - one: "ì¼ë¶„" - other: "%{count}ë¶„" - about_x_hours: - one: "약 한시간" - other: "약 %{count}시간" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "하루" - other: "%{count}ì¼" - about_x_months: - one: "약 한달" - other: "약 %{count}달" - x_months: - one: "한달" - other: "%{count}달" - about_x_years: - one: "약 ì¼ë…„" - other: "약 %{count}ë…„" - over_x_years: - one: "ì¼ë…„ ì´ìƒ" - other: "%{count}ë…„ ì´ìƒ" - almost_x_years: - one: "약 1ë…„" - other: "약 %{count}ë…„" - prompts: - year: "ë…„" - month: "ì›”" - day: "ì¼" - hour: "시" - minute: "ë¶„" - second: "ì´ˆ" - - number: - # Used in number_with_delimiter() - # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' - format: - # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) - separator: "." - # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) - delimiter: "," - # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) - precision: 3 - - # Used in number_to_currency() - currency: - format: - # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) - format: "%u%n" - unit: "â‚©" - # These three are to override number.format and are optional - separator: "." - delimiter: "," - precision: 0 - - # Used in number_to_percentage() - percentage: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_precision() - precision: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_human_size() - human: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - words_connector: ", " - two_words_connector: "ê³¼ " - last_word_connector: ", " - sentence_connector: "그리고" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "í•œê°œì˜ ì˜¤ë¥˜ê°€ ë°œìƒí•´ %{model}ì„(를) 저장하지 않았습니다." - other: "%{count}ê°œì˜ ì˜¤ë¥˜ê°€ ë°œìƒí•´ %{model}ì„(를) 저장하지 않았습니다." - # The variable :count is also available - body: "ë‹¤ìŒ í•­ëª©ì— ë¬¸ì œê°€ 발견했습니다:" - - messages: - inclusion: "ì€ ëª©ë¡ì— í¬í•¨ë˜ì–´ 있지 않습니다" - exclusion: "ì€ ì˜ˆì•½ë˜ì–´ 있습니다" - invalid: "ì€ ìœ íš¨í•˜ì§€ 않습니다." - confirmation: "ì€ í™•ì¸ì´ ë˜ì§€ 않았습니다" - accepted: "ì€ ì¸ì •ë˜ì–´ì•¼ 합니다" - empty: "ì€ ê¸¸ì´ê°€ 0ì´ì–´ì„œëŠ” 안ë©ë‹ˆë‹¤." - blank: "ì€ ë¹ˆ ê°’ì´ì–´ì„œëŠ” 안 ë©ë‹ˆë‹¤" - too_long: "ì€ ë„ˆë¬´ ê¹ë‹ˆë‹¤ (최대 %{count}ìž ê¹Œì§€)" - too_short: "ì€ ë„ˆë¬´ 짧습니다 (최소 %{count}ìž ê¹Œì§€)" - wrong_length: "ì€ ê¸¸ì´ê°€ 틀렸습니다 (%{count}ìžì´ì–´ì•¼ 합니다.)" - taken: "ì€ ì´ë¯¸ ì„ íƒëœ ê²ë‹ˆë‹¤" - not_a_number: "ì€ ìˆ«ìžê°€ 아닙니다" - greater_than: "ì€ %{count}보다 커야 합니다." - greater_than_or_equal_to: "ì€ %{count}보다 í¬ê±°ë‚˜ 같아야 합니다" - equal_to: "ì€ %{count}(와)ê³¼ 같아야 합니다" - less_than: "ì€ %{count}보다 작어야 합니다" - less_than_or_equal_to: "ì€ %{count}ê³¼ 같거나 ì´í•˜ì„ 요구합니다" - odd: "ì€ í™€ìˆ˜ì—¬ì•¼ 합니다" - even: "ì€ ì§ìˆ˜ì—¬ì•¼ 합니다" - greater_than_start_date: "는 시작날짜보다 커야 합니다" - not_same_project: "는 ê°™ì€ í”„ë¡œì íŠ¸ì— ì†í•´ 있지 않습니다" - circular_dependency: "ì´ ê´€ê³„ëŠ” 순환 ì˜ì¡´ê´€ê³„를 만들 수 있습니다" - cant_link_an_issue_with_a_descendant: "ì¼ê°ì€ 하위 ì¼ê°ê³¼ ì—°ê²°í•  수 없습니다." - - actionview_instancetag_blank_option: ì„ íƒí•˜ì„¸ìš” - - general_text_No: '아니오' - general_text_Yes: '예' - general_text_no: '아니오' - general_text_yes: '예' - general_lang_name: 'Korean (한국어)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: CP949 - general_pdf_encoding: CP949 - general_first_day_of_week: '7' - - notice_account_updated: ê³„ì •ì´ ì„±ê³µì ìœ¼ë¡œ 변경ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_account_invalid_creditentials: ìž˜ëª»ëœ ê³„ì • ë˜ëŠ” 비밀번호 - notice_account_password_updated: 비밀번호가 잘 변경ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_account_wrong_password: ìž˜ëª»ëœ ë¹„ë°€ë²ˆí˜¸ - notice_account_register_done: ê³„ì •ì´ ìž˜ 만들어졌습니다. ê³„ì •ì„ í™œì„±í™”í•˜ì‹œë ¤ë©´ ë°›ì€ ë©”ì¼ì˜ ë§í¬ë¥¼ í´ë¦­í•´ì£¼ì„¸ìš”. - notice_account_unknown_email: 알려지지 ì•Šì€ ì‚¬ìš©ìž. - notice_can_t_change_password: ì´ ê³„ì •ì€ ì™¸ë¶€ ì¸ì¦ì„ ì´ìš©í•©ë‹ˆë‹¤. 비밀번호를 변경할 수 없습니다. - notice_account_lost_email_sent: 새로운 비밀번호를 위한 ë©”ì¼ì´ 발송ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_account_activated: ê³„ì •ì´ í™œì„±í™”ë˜ì—ˆìŠµë‹ˆë‹¤. ì´ì œ ë¡œê·¸ì¸ í•˜ì‹¤ìˆ˜ 있습니다. - notice_successful_create: ìƒì„± 성공. - notice_successful_update: 변경 성공. - notice_successful_delete: ì‚­ì œ 성공. - notice_successful_connection: ì—°ê²° 성공. - notice_file_not_found: 요청하신 페ì´ì§€ëŠ” ì‚­ì œë˜ì—ˆê±°ë‚˜ 옮겨졌습니다. - notice_locking_conflict: 다른 사용ìžì— ì˜í•´ì„œ ë°ì´í„°ê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_not_authorized: ì´ íŽ˜ì´ì§€ì— 접근할 ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤. - notice_email_sent: "%{value}님ì—게 ë©”ì¼ì´ 발송ë˜ì—ˆìŠµë‹ˆë‹¤." - notice_email_error: "ë©”ì¼ì„ 전송하는 ê³¼ì •ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. (%{value})" - notice_feeds_access_key_reseted: RSSì— ì ‘ê·¼ê°€ëŠ¥í•œ 열쇠(key)ê°€ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_failed_to_save_issues: "ì €ìž¥ì— ì‹¤íŒ¨í•˜ì˜€ìŠµë‹ˆë‹¤: 실패 %{count}(ì„ íƒ %{total}): %{ids}." - notice_no_issue_selected: "ì¼ê°ì´ ì„ íƒë˜ì§€ 않았습니다. 수정하기 ì›í•˜ëŠ” ì¼ê°ì„ ì„ íƒí•˜ì„¸ìš”" - notice_account_pending: "ê³„ì •ì´ ë§Œë“¤ì–´ì¡Œìœ¼ë©° ê´€ë¦¬ìž ìŠ¹ì¸ ëŒ€ê¸°ì¤‘ìž…ë‹ˆë‹¤." - notice_default_data_loaded: ê¸°ë³¸ê°’ì„ ì„±ê³µì ìœ¼ë¡œ ì½ì–´ë“¤ì˜€ìŠµë‹ˆë‹¤. - notice_unable_delete_version: 삭제할 수 없는 버전입니다. - - error_can_t_load_default_data: "ê¸°ë³¸ê°’ì„ ì½ì–´ë“¤ì¼ 수 없습니다.: %{value}" - error_scm_not_found: 항목ì´ë‚˜ ë¦¬ë¹„ì ¼ì´ ì €ìž¥ì†Œì— ì¡´ìž¬í•˜ì§€ 않습니다. - error_scm_command_failed: "ì €ìž¥ì†Œì— ì ‘ê·¼í•˜ëŠ” ë„ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤.: %{value}" - error_scm_annotate: "í•­ëª©ì´ ì—†ê±°ë‚˜ 행별 ì´ë ¥ì„ ë³¼ 수 없습니다." - error_issue_not_found_in_project: 'ì¼ê°ì´ 없거나 ì´ í”„ë¡œì íŠ¸ì˜ ê²ƒì´ ì•„ë‹™ë‹ˆë‹¤.' - - warning_attachments_not_saved: "%{count}ê°œ 파ì¼ì„ 저장할 수 없습니다." - - mail_subject_lost_password: "%{value} 비밀번호" - mail_body_lost_password: '비밀번호를 변경하려면 ë‹¤ìŒ ë§í¬ë¥¼ í´ë¦­í•˜ì„¸ìš”.' - mail_subject_register: "%{value} 계정 활성화" - mail_body_register: 'ê³„ì •ì„ í™œì„±í™”í•˜ë ¤ë©´ ë§í¬ë¥¼ í´ë¦­í•˜ì„¸ìš”.:' - mail_body_account_information_external: "로그ì¸í•  때 %{value} ê³„ì •ì„ ì‚¬ìš©í•˜ì‹¤ 수 있습니다." - mail_body_account_information: 계정 ì •ë³´ - mail_subject_account_activation_request: "%{value} 계정 활성화 요청" - mail_body_account_activation_request: "새 사용ìž(%{value})ê°€ 등ë¡ë˜ì—ˆìŠµë‹ˆë‹¤. 관리ìžë‹˜ì˜ 승ì¸ì„ 기다리고 있습니다.:" - mail_body_reminder: "ë‹¹ì‹ ì´ ë§¡ê³  있는 ì¼ê° %{count}ê°œì˜ ì™„ë£Œ ê¸°í•œì´ %{days}ì¼ í›„ 입니다." - mail_subject_reminder: "ë‚´ì¼ì´ ë§Œê¸°ì¸ ì¼ê° %{count}ê°œ (%{days})" - mail_subject_wiki_content_added: "위키페ì´ì§€ '%{id}'ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." - mail_subject_wiki_content_updated: "'위키페ì´ì§€ %{id}'ì´(ê°€) 수정ë˜ì—ˆìŠµë‹ˆë‹¤." - mail_body_wiki_content_added: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 추가하였습니다." - mail_body_wiki_content_updated: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 수정하였습니다." - - gui_validation_error: ì—러 - gui_validation_error_plural: "%{count}ê°œ ì—러" - - field_name: ì´ë¦„ - field_description: 설명 - field_summary: 요약 - field_is_required: 필수 - field_firstname: ì´ë¦„ - field_lastname: 성 - field_mail: ë©”ì¼ - field_filename: íŒŒì¼ - field_filesize: í¬ê¸° - field_downloads: 다운로드 - field_author: ì €ìž - field_created_on: ë“±ë¡ - field_updated_on: 변경 - field_field_format: í˜•ì‹ - field_is_for_all: 모든 프로ì íЏ - field_possible_values: 가능한 값들 - field_regexp: ì •ê·œì‹ - field_min_length: 최소 ê¸¸ì´ - field_max_length: 최대 ê¸¸ì´ - field_value: ê°’ - field_category: 범주 - field_title: 제목 - field_project: 프로ì íЏ - field_issue: ì¼ê° - field_status: ìƒíƒœ - field_notes: ë§ê¸€ - field_is_closed: 완료 ìƒíƒœ - field_is_default: 기본값 - field_tracker: 유형 - field_subject: 제목 - field_due_date: 완료 기한 - field_assigned_to: ë‹´ë‹¹ìž - field_priority: 우선순위 - field_fixed_version: 목표버전 - field_user: ì‚¬ìš©ìž - field_role: ì—­í•  - field_homepage: 홈페ì´ì§€ - field_is_public: 공개 - field_parent: ìƒìœ„ 프로ì íЏ - field_is_in_roadmap: ë¡œë“œë§µì— í‘œì‹œ - field_login: ë¡œê·¸ì¸ - field_mail_notification: ë©”ì¼ ì•Œë¦¼ - field_admin: ê´€ë¦¬ìž - field_last_login_on: 마지막 ë¡œê·¸ì¸ - field_language: 언어 - field_effective_date: ë‚ ì§œ - field_password: 비밀번호 - field_new_password: 새 비밀번호 - field_password_confirmation: 비밀번호 í™•ì¸ - field_version: 버전 - field_type: ë°©ì‹ - field_host: 호스트 - field_port: í¬íЏ - field_account: 계정 - field_base_dn: 기본 DN - field_attr_login: ë¡œê·¸ì¸ ì†ì„± - field_attr_firstname: ì´ë¦„ ì†ì„± - field_attr_lastname: 성 ì†ì„± - field_attr_mail: ë©”ì¼ ì†ì„± - field_onthefly: ë™ì  ì‚¬ìš©ìž ìƒì„± - field_start_date: 시작시간 - field_done_ratio: ì§„ì²™ë„ - field_auth_source: ì¸ì¦ ê³µê¸‰ìž - field_hide_mail: ë©”ì¼ ì£¼ì†Œ 숨기기 - field_comments: 설명 - field_url: URL - field_start_page: 첫 페ì´ì§€ - field_subproject: 하위 프로ì íЏ - field_hours: 시간 - field_activity: 작업종류 - field_spent_on: 작업시간 - field_identifier: ì‹ë³„ìž - field_is_filter: 검색조건으로 ì‚¬ìš©ë¨ - field_issue_to_id: ì—°ê´€ëœ ì¼ê° - field_delay: 지연 - field_assignable: ì´ ì—­í• ì—게 ì¼ê°ì„ 맡길 수 ìžˆìŒ - field_redirect_existing_links: ê¸°ì¡´ì˜ ë§í¬ë¡œ ëŒë ¤ë³´ëƒ„(redirect) - field_estimated_hours: 추정시간 - field_column_names: 컬럼 - field_default_value: 기본값 - field_time_zone: 시간대 - field_searchable: 검색가능 - field_comments_sorting: 댓글 ì •ë ¬ - field_parent_title: ìƒìœ„ 제목 - field_editable: 편집가능 - field_watcher: ì¼ê°ì§€í‚´ì´ - field_identity_url: OpenID URL - field_content: ë‚´ìš© - field_group_by: 결과를 묶어 보여줄 기준 - - setting_app_title: ë ˆë“œë§ˆì¸ ì œëª© - setting_app_subtitle: ë ˆë“œë§ˆì¸ ë¶€ì œëª© - setting_welcome_text: í™˜ì˜ ë©”ì‹œì§€ - setting_default_language: 기본 언어 - setting_login_required: ì¸ì¦ì´ 필요함 - setting_self_registration: ì‚¬ìš©ìž ì§ì ‘ë“±ë¡ - setting_attachment_max_size: 최대 ì²¨ë¶€íŒŒì¼ í¬ê¸° - setting_issues_export_limit: ì¼ê° 내보내기 제한 - setting_mail_from: 발신 ë©”ì¼ ì£¼ì†Œ - setting_bcc_recipients: 참조ìžë“¤ì„ bcc로 숨기기 - setting_plain_text_mail: í…스트만 (HTML ì—†ì´) - setting_host_name: 호스트 ì´ë¦„ê³¼ 경로 - setting_text_formatting: 본문 í˜•ì‹ - setting_wiki_compression: 위키 ì´ë ¥ ì••ì¶• - setting_feeds_limit: í”¼ë“œì— í¬í•¨í•  í•­ëª©ì˜ ìˆ˜ - setting_default_projects_public: 새 프로ì íŠ¸ë¥¼ 공개로 설정 - setting_autofetch_changesets: 커밋(commit)ëœ ë³€ê²½ë¬¶ìŒì„ ìžë™ìœ¼ë¡œ 가져오기 - setting_sys_api_enabled: 저장소 ê´€ë¦¬ì— WS를 사용 - setting_commit_ref_keywords: ì¼ê° ì°¸ì¡°ì— ì‚¬ìš©í•  키워드들 - setting_commit_fix_keywords: ì¼ê° í•´ê²°ì— ì‚¬ìš©í•  키워드들 - setting_autologin: ìžë™ ë¡œê·¸ì¸ - setting_date_format: ë‚ ì§œ í˜•ì‹ - setting_time_format: 시간 í˜•ì‹ - setting_cross_project_issue_relations: 다른 프로ì íŠ¸ì˜ ì¼ê°ê³¼ 연결하는 ê²ƒì„ í—ˆìš© - setting_issue_list_default_columns: ì¼ê° 목ë¡ì— 표시할 항목 - setting_emails_footer: ë©”ì¼ ê¼¬ë¦¬ - setting_protocol: 프로토콜 - setting_per_page_options: 목ë¡ì—서, 한 페ì´ì§€ì— 표시할 í–‰ - setting_user_format: ì‚¬ìš©ìž í‘œì‹œ í˜•ì‹ - setting_activity_days_default: 프로ì íЏ ìž‘ì—…ë‚´ì—­ì— í‘œì‹œí•  기간 - setting_display_subprojects_issues: 하위 프로ì íŠ¸ì˜ ì¼ê°ì„ 함께 표시 - setting_enabled_scm: "ì§€ì›í•  SCM(Source Control Management)" - setting_mail_handler_api_enabled: 수신 ë©”ì¼ì— WS를 허용 - setting_mail_handler_api_key: API 키 - setting_sequential_project_identifiers: 프로ì íЏ ì‹ë³„ìžë¥¼ 순차ì ìœ¼ë¡œ ìƒì„± - setting_gravatar_enabled: ê·¸ë¼ë°”타 ì‚¬ìš©ìž ì•„ì´ì½˜ 사용 - setting_diff_max_lines_displayed: ì°¨ì´ì (diff) ë³´ê¸°ì— í‘œì‹œí•  최대 줄수 - setting_repository_log_display_limit: 저장소 ë³´ê¸°ì— í‘œì‹œí•  ê°œì •íŒ ì´ë ¥ì˜ 최대 갯수 - setting_file_max_size_displayed: 바로 보여줄 í…스트파ì¼ì˜ 최대 í¬ê¸° - setting_openid: OpenID 로그ì¸ê³¼ ë“±ë¡ í—ˆìš© - setting_password_min_length: 최소 암호 ê¸¸ì´ - setting_new_project_user_role_id: 프로ì íŠ¸ë¥¼ 만든 사용ìžì—게 주어질 ì—­í•  - - permission_add_project: 프로ì íЏ ìƒì„± - permission_edit_project: 프로ì íЏ 편집 - permission_select_project_modules: 프로ì íЏ 모듈 ì„ íƒ - permission_manage_members: êµ¬ì„±ì› ê´€ë¦¬ - permission_manage_versions: 버전 관리 - permission_manage_categories: ì¼ê° 범주 관리 - permission_add_issues: ì¼ê° 추가 - permission_edit_issues: ì¼ê° 편집 - permission_manage_issue_relations: ì¼ê° 관계 관리 - permission_add_issue_notes: ë§ê¸€ 추가 - permission_edit_issue_notes: ë§ê¸€ 편집 - permission_edit_own_issue_notes: ë‚´ ë§ê¸€ 편집 - permission_move_issues: ì¼ê° ì´ë™ - permission_delete_issues: ì¼ê° ì‚­ì œ - permission_manage_public_queries: 공용 ê²€ìƒ‰ì–‘ì‹ ê´€ë¦¬ - permission_save_queries: ê²€ìƒ‰ì–‘ì‹ ì €ìž¥ - permission_view_gantt: Gantt차트 보기 - permission_view_calendar: 달력 보기 - permission_view_issue_watchers: ì¼ê°ì§€í‚´ì´ 보기 - permission_add_issue_watchers: ì¼ê°ì§€í‚´ì´ 추가 - permission_log_time: 작업시간 ê¸°ë¡ - permission_view_time_entries: 시간입력 보기 - permission_edit_time_entries: 시간입력 편집 - permission_edit_own_time_entries: ë‚´ 시간입력 편집 - permission_manage_news: 뉴스 관리 - permission_comment_news: ë‰´ìŠ¤ì— ëŒ“ê¸€ë‹¬ê¸° - permission_manage_documents: 문서 관리 - permission_view_documents: 문서 보기 - permission_manage_files: 파ì¼ê´€ë¦¬ - permission_view_files: 파ì¼ë³´ê¸° - permission_manage_wiki: 위키 관리 - permission_rename_wiki_pages: 위키 페ì´ì§€ ì´ë¦„변경 - permission_delete_wiki_pages: 위치 페ì´ì§€ ì‚­ì œ - permission_view_wiki_pages: 위키 보기 - permission_view_wiki_edits: 위키 ê¸°ë¡ ë³´ê¸° - permission_edit_wiki_pages: 위키 페ì´ì§€ 편집 - permission_delete_wiki_pages_attachments: ì²¨ë¶€íŒŒì¼ ì‚­ì œ - permission_protect_wiki_pages: 프로ì íЏ 위키 페ì´ì§€ - permission_manage_repository: 저장소 관리 - permission_browse_repository: 저장소 둘러보기 - permission_view_changesets: 변경묶ìŒë³´ê¸° - permission_commit_access: 변경로그 보기 - permission_manage_boards: ê²Œì‹œíŒ ê´€ë¦¬ - permission_view_messages: 메시지 보기 - permission_add_messages: 메시지 추가 - permission_edit_messages: 메시지 편집 - permission_edit_own_messages: ìžê¸° 메시지 편집 - permission_delete_messages: 메시지 ì‚­ì œ - permission_delete_own_messages: ìžê¸° 메시지 ì‚­ì œ - - project_module_issue_tracking: ì¼ê°ê´€ë¦¬ - project_module_time_tracking: ì‹œê°„ì¶”ì  - project_module_news: 뉴스 - project_module_documents: 문서 - project_module_files: íŒŒì¼ - project_module_wiki: 위키 - project_module_repository: 저장소 - project_module_boards: ê²Œì‹œíŒ - - label_user: ì‚¬ìš©ìž - label_user_plural: ì‚¬ìš©ìž - label_user_new: 새 ì‚¬ìš©ìž - label_project: 프로ì íЏ - label_project_new: 새 프로ì íЏ - label_project_plural: 프로ì íЏ - label_x_projects: - zero: ì—†ìŒ - one: "한 프로ì íЏ" - other: "%{count}ê°œ 프로ì íЏ" - label_project_all: 모든 프로ì íЏ - label_project_latest: 최근 프로ì íЏ - label_issue: ì¼ê° - label_issue_new: 새 ì¼ê°ë§Œë“¤ê¸° - label_issue_plural: ì¼ê° - label_issue_view_all: 모든 ì¼ê° 보기 - label_issues_by: "%{value}별 ì¼ê°" - label_issue_added: ì¼ê° 추가 - label_issue_updated: ì¼ê° 수정 - label_document: 문서 - label_document_new: 새 문서 - label_document_plural: 문서 - label_document_added: 문서 추가 - label_role: ì—­í•  - label_role_plural: ì—­í•  - label_role_new: 새 ì—­í•  - label_role_and_permissions: ì—­í•  ë° ê¶Œí•œ - label_member: ë‹´ë‹¹ìž - label_member_new: 새 ë‹´ë‹¹ìž - label_member_plural: ë‹´ë‹¹ìž - label_tracker: ì¼ê° 유형 - label_tracker_plural: ì¼ê° 유형 - label_tracker_new: 새 ì¼ê° 유형 - label_workflow: 업무í름 - label_issue_status: ì¼ê° ìƒíƒœ - label_issue_status_plural: ì¼ê° ìƒíƒœ - label_issue_status_new: 새 ì¼ê° ìƒíƒœ - label_issue_category: ì¼ê° 범주 - label_issue_category_plural: ì¼ê° 범주 - label_issue_category_new: 새 ì¼ê° 범주 - label_custom_field: ì‚¬ìš©ìž ì •ì˜ í•­ëª© - label_custom_field_plural: ì‚¬ìš©ìž ì •ì˜ í•­ëª© - label_custom_field_new: 새 ì‚¬ìš©ìž ì •ì˜ í•­ëª© - label_enumerations: 코드값 - label_enumeration_new: 새 코드값 - label_information: ì •ë³´ - label_information_plural: ì •ë³´ - label_please_login: 로그ì¸í•˜ì„¸ìš”. - label_register: ë“±ë¡ - label_login_with_open_id_option: ë˜ëŠ” OpenID로 ë¡œê·¸ì¸ - label_password_lost: 비밀번호 찾기 - label_home: 초기화면 - label_my_page: ë‚´ 페ì´ì§€ - label_my_account: ë‚´ 계정 - label_my_projects: ë‚´ 프로ì íЏ - label_administration: 관리 - label_login: ë¡œê·¸ì¸ - label_logout: 로그아웃 - label_help: ë„ì›€ë§ - label_reported_issues: 보고한 ì¼ê° - label_assigned_to_me_issues: ë‚´ê°€ ë§¡ì€ ì¼ê° - label_last_login: 마지막 ì ‘ì† - label_registered_on: 등ë¡ì‹œê° - label_activity: 작업내역 - label_overall_activity: ì „ì²´ 작업내역 - label_user_activity: "%{value}ì˜ ìž‘ì—…ë‚´ì—­" - label_new: 새로 만들기 - label_logged_as: '로그ì¸ê³„ì •:' - label_environment: 환경 - label_authentication: ì¸ì¦ - label_auth_source: ì¸ì¦ ê³µê¸‰ìž - label_auth_source_new: 새 ì¸ì¦ ê³µê¸‰ìž - label_auth_source_plural: ì¸ì¦ ê³µê¸‰ìž - label_subproject_plural: 하위 프로ì íЏ - label_and_its_subprojects: "%{value}와 하위 프로ì íŠ¸ë“¤" - label_min_max_length: 최소 - 최대 ê¸¸ì´ - label_list: ëª©ë¡ - label_date: ë‚ ì§œ - label_integer: 정수 - label_float: ë¶€ë™ì†Œìˆ˜ - label_boolean: 부울린 - label_string: 문ìžì—´ - label_text: í…스트 - label_attribute: ì†ì„± - label_attribute_plural: ì†ì„± - label_download: "%{count}회 다운로드" - label_download_plural: "%{count}회 다운로드" - label_no_data: 표시할 ë°ì´í„°ê°€ 없습니다. - label_change_status: ìƒíƒœ 변경 - label_history: ì´ë ¥ - label_attachment: íŒŒì¼ - label_attachment_new: 파ì¼ì¶”ê°€ - label_attachment_delete: 파ì¼ì‚­ì œ - label_attachment_plural: íŒŒì¼ - label_file_added: íŒŒì¼ ì¶”ê°€ - label_report: 보고서 - label_report_plural: 보고서 - label_news: 뉴스 - label_news_new: 새 뉴스 - label_news_plural: 뉴스 - label_news_latest: 최근 뉴스 - label_news_view_all: 모든 뉴스 - label_news_added: 뉴스 추가 - label_settings: 설정 - label_overview: 개요 - label_version: 버전 - label_version_new: 새 버전 - label_version_plural: 버전 - label_confirmation: í™•ì¸ - label_export_to: 내보내기 - label_read: ì½ê¸°... - label_public_projects: 공개 프로ì íЏ - label_open_issues: 진행중 - label_open_issues_plural: 진행중 - label_closed_issues: ì™„ë£Œë¨ - label_closed_issues_plural: ì™„ë£Œë¨ - label_x_open_issues_abbr_on_total: - zero: "ì´ %{total} ê±´ ëª¨ë‘ ì™„ë£Œ" - one: "한 ê±´ ì§„í–‰ 중 / ì´ %{total} ê±´ 중 " - other: "%{count} ê±´ ì§„í–‰ 중 / ì´ %{total} ê±´" - label_x_open_issues_abbr: - zero: ëª¨ë‘ ì™„ë£Œ - one: 한 ê±´ ì§„í–‰ 중 - other: "%{count} ê±´ ì§„í–‰ 중" - label_x_closed_issues_abbr: - zero: ëª¨ë‘ ë¯¸ì™„ë£Œ - one: 한 ê±´ 완료 - other: "%{count} ê±´ 완료" - label_total: 합계 - label_permissions: 권한 - label_current_status: ì¼ê° ìƒíƒœ - label_new_statuses_allowed: 허용ë˜ëŠ” ì¼ê° ìƒíƒœ - label_all: ëª¨ë‘ - label_none: ì—†ìŒ - label_nobody: 미지정 - label_next: ë‹¤ìŒ - label_previous: 뒤로 - label_used_by: ì‚¬ìš©ë¨ - label_details: ìžì„¸ížˆ - label_add_note: ì¼ê°ë§ê¸€ 추가 - label_per_page: 페ì´ì§€ë³„ - label_calendar: 달력 - label_months_from: 개월 ë™ì•ˆ | 다ìŒë¶€í„° - label_gantt: Gantt 챠트 - label_internal: ë‚´ë¶€ - label_last_changes: "최근 %{count}ê°œì˜ ë³€ê²½ì‚¬í•­" - label_change_view_all: 모든 변경 ë‚´ì—­ 보기 - label_personalize_page: 입맛대로 구성하기 - label_comment: 댓글 - label_comment_plural: 댓글 - label_x_comments: - zero: 댓글 ì—†ìŒ - one: 한 ê°œì˜ ëŒ“ê¸€ - other: "%{count} ê°œì˜ ëŒ“ê¸€" - label_comment_add: 댓글 추가 - label_comment_added: ëŒ“ê¸€ì´ ì¶”ê°€ë˜ì—ˆìŠµë‹ˆë‹¤. - label_comment_delete: 댓글 ì‚­ì œ - label_query: ê²€ìƒ‰ì–‘ì‹ - label_query_plural: ê²€ìƒ‰ì–‘ì‹ - label_query_new: 새 ê²€ìƒ‰ì–‘ì‹ - label_filter_add: 검색조건 추가 - label_filter_plural: 검색조건 - label_equals: ì´ë‹¤ - label_not_equals: 아니다 - label_in_less_than: ì´ë‚´ - label_in_more_than: ì´í›„ - label_greater_or_equal: ">=" - label_less_or_equal: "<=" - label_in: ì´ë‚´ - label_today: 오늘 - label_all_time: 모든 시간 - label_yesterday: ì–´ì œ - label_this_week: ì´ë²ˆì£¼ - label_last_week: 지난 주 - label_last_n_days: "지난 %{count} ì¼" - label_this_month: ì´ë²ˆ 달 - label_last_month: 지난 달 - label_this_year: 올해 - label_date_range: ë‚ ì§œ 범위 - label_less_than_ago: ì´ì „ - label_more_than_ago: ì´í›„ - label_ago: ì¼ ì „ - label_contains: í¬í•¨ë˜ëŠ” 키워드 - label_not_contains: í¬í•¨í•˜ì§€ 않는 키워드 - label_day_plural: ì¼ - label_repository: 저장소 - label_repository_plural: 저장소 - label_browse: 저장소 둘러보기 - label_modification: "%{count} 변경" - label_modification_plural: "%{count} 변경" - label_revision: ê°œì •íŒ - label_revision_plural: ê°œì •íŒ - label_associated_revisions: ê´€ë ¨ëœ ê°œì •íŒë“¤ - label_added: ì¶”ê°€ë¨ - label_modified: ë³€ê²½ë¨ - label_copied: ë³µì‚¬ë¨ - label_renamed: ì´ë¦„바뀜 - label_deleted: ì‚­ì œë¨ - label_latest_revision: 최근 ê°œì •íŒ - label_latest_revision_plural: 최근 ê°œì •íŒ - label_view_revisions: ê°œì •íŒ ë³´ê¸° - label_max_size: 최대 í¬ê¸° - label_sort_highest: 맨 위로 - label_sort_higher: 위로 - label_sort_lower: 아래로 - label_sort_lowest: 맨 아래로 - label_roadmap: 로드맵 - label_roadmap_due_in: "기한 %{value}" - label_roadmap_overdue: "%{value} 지연" - label_roadmap_no_issues: ì´ ë²„ì „ì— í•´ë‹¹í•˜ëŠ” ì¼ê° ì—†ìŒ - label_search: 검색 - label_result_plural: ê²°ê³¼ - label_all_words: 모든 단어 - label_wiki: 위키 - label_wiki_edit: 위키 편집 - label_wiki_edit_plural: 위키 편집 - label_wiki_page: 위키 페ì´ì§€ - label_wiki_page_plural: 위키 페ì´ì§€ - label_index_by_title: 제목별 ìƒ‰ì¸ - label_index_by_date: 날짜별 ìƒ‰ì¸ - label_current_version: 현재 버전 - label_preview: 미리보기 - label_feed_plural: 피드(Feeds) - label_changes_details: 모든 ìƒì„¸ 변경 ë‚´ì—­ - label_issue_tracking: ì¼ê° ì¶”ì  - label_spent_time: 소요 시간 - label_f_hour: "%{value} 시간" - label_f_hour_plural: "%{value} 시간" - label_time_tracking: ì‹œê°„ì¶”ì  - label_change_plural: 변경사항들 - label_statistics: 통계 - label_commits_per_month: 월별 커밋 ë‚´ì—­ - label_commits_per_author: ì €ìžë³„ 커밋 ë‚´ì—­ - label_view_diff: ì°¨ì´ì  보기 - label_diff_inline: 한줄로 - label_diff_side_by_side: ë‘줄로 - label_options: 옵션 - label_copy_workflow_from: 업무í름 복사하기 - label_permissions_report: 권한 보고서 - label_watched_issues: 지켜보고 있는 ì¼ê° - label_related_issues: ì—°ê²°ëœ ì¼ê° - label_applied_status: ì ìš©ëœ ìƒíƒœ - label_loading: ì½ëŠ” 중... - label_relation_new: 새 관계 - label_relation_delete: 관계 지우기 - label_relates_to: "ë‹¤ìŒ ì¼ê°ê³¼ 관련ë¨:" - label_duplicates: "ë‹¤ìŒ ì¼ê°ì— 중복ë¨:" - label_duplicated_by: "ì¤‘ë³µëœ ì¼ê°:" - label_blocks: "ë‹¤ìŒ ì¼ê°ì˜ í•´ê²°ì„ ë§‰ê³  있ìŒ:" - label_blocked_by: "ë‹¤ìŒ ì¼ê°ì—게 막혀 있ìŒ:" - label_precedes: "다ìŒì— 진행할 ì¼ê°:" - label_follows: "ë‹¤ìŒ ì¼ê°ì„ ìš°ì„  ì§„í–‰:" - label_end_to_start: "ëì—서 시작" - label_end_to_end: "ëì—서 ë" - label_start_to_start: "시작ì—서 시작" - label_start_to_end: "시작ì—서 ë" - label_stay_logged_in: ë¡œê·¸ì¸ ìœ ì§€ - label_disabled: 비활성화 - label_show_completed_versions: ì™„ë£Œëœ ë²„ì „ 보기 - label_me: 나 - label_board: ê²Œì‹œíŒ - label_board_new: 새 ê²Œì‹œíŒ - label_board_plural: ê²Œì‹œíŒ - label_topic_plural: 주제 - label_message_plural: 글 - label_message_last: 마지막 글 - label_message_new: 새글쓰기 - label_message_posted: 글 추가 - label_reply_plural: 답글 - label_send_information: 사용ìžì—게 계정정보를 보내기 - label_year: ë…„ - label_month: ì›” - label_week: 주 - label_date_from: '기간:' - label_date_to: ' ~ ' - label_language_based: ì–¸ì–´ì„¤ì •ì— ë”°ë¦„ - label_sort_by: "%{value}(으)로 ì •ë ¬" - label_send_test_email: 테스트 ë©”ì¼ ë³´ë‚´ê¸° - label_feeds_access_key_created_on: "피드 ì ‘ê·¼ 키가 %{value} ì´ì „ì— ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤." - label_module_plural: 모듈 - label_added_time_by: "%{author}ì´(ê°€) %{age} ì „ì— ì¶”ê°€í•¨" - label_updated_time_by: "%{author}ì´(ê°€) %{age} ì „ì— ë³€ê²½" - label_updated_time: "%{value} ì „ì— ìˆ˜ì •ë¨" - label_jump_to_a_project: 프로ì íЏ 바로가기 - label_file_plural: íŒŒì¼ - label_changeset_plural: ë³€ê²½ë¬¶ìŒ - label_default_columns: 기본 컬럼 - label_no_change_option: (수정 안함) - label_bulk_edit_selected_issues: ì„ íƒí•œ ì¼ê°ë“¤ì„ í•œêº¼ë²ˆì— ìˆ˜ì •í•˜ê¸° - label_theme: 테마 - label_default: 기본 - label_search_titles_only: 제목ì—서만 찾기 - label_user_mail_option_all: "ë‚´ê°€ ì†í•œ 프로ì íŠ¸ë¡œë“¤ë¶€í„° 모든 ë©”ì¼ ë°›ê¸°" - label_user_mail_option_selected: "ì„ íƒí•œ 프로ì íŠ¸ë“¤ë¡œë¶€í„° 모든 ë©”ì¼ ë°›ê¸°.." - label_user_mail_no_self_notified: "ë‚´ê°€ 만든 ë³€ê²½ì‚¬í•­ë“¤ì— ëŒ€í•´ì„œëŠ” 알림메ì¼ì„ 받지 않습니다." - label_registration_activation_by_email: ë©”ì¼ë¡œ ê³„ì •ì„ í™œì„±í™”í•˜ê¸° - label_registration_automatic_activation: ìžë™ 계정 활성화 - label_registration_manual_activation: ìˆ˜ë™ ê³„ì • 활성화 - label_display_per_page: "페ì´ì§€ë‹¹ 줄수: %{value}" - label_age: 마지막 ìˆ˜ì •ì¼ - label_change_properties: ì†ì„± 변경 - label_general: ì¼ë°˜ - label_more: 제목 ë° ì„¤ëª… 수정 - label_scm: 형ìƒê´€ë¦¬ì‹œìŠ¤í…œ - label_plugins: í”ŒëŸ¬ê·¸ì¸ - label_ldap_authentication: LDAP ì¸ì¦ - label_downloads_abbr: D/L - label_optional_description: 부가ì ì¸ 설명 - label_add_another_file: 다른 íŒŒì¼ ì¶”ê°€ - label_preferences: 설정 - label_chronological_order: 시간 순으로 ì •ë ¬ - label_reverse_chronological_order: 시간 역순으로 ì •ë ¬ - label_planning: 프로ì íŠ¸ê³„íš - label_incoming_emails: 수신 ë©”ì¼ - label_generate_key: 키 ìƒì„± - label_issue_watchers: ì¼ê°ì§€í‚´ì´ - label_example: 예 - label_display: í‘œì‹œë°©ì‹ - label_sort: ì •ë ¬ - label_ascending: 오름차순 - label_descending: 내림차순 - label_date_from_to: "%{start}부터 %{end}까지" - label_wiki_content_added: 위키페ì´ì§€ 추가 - label_wiki_content_updated: 위키페ì´ì§€ 수정 - - button_login: ë¡œê·¸ì¸ - button_submit: í™•ì¸ - button_save: 저장 - button_check_all: 모ë‘ì„ íƒ - button_uncheck_all: ì„ íƒí•´ì œ - button_delete: ì‚­ì œ - button_create: 만들기 - button_create_and_continue: 만들고 계ì†í•˜ê¸° - button_test: 테스트 - button_edit: 편집 - button_add: 추가 - button_change: 변경 - button_apply: ì ìš© - button_clear: 지우기 - button_lock: 잠금 - button_unlock: 잠금해제 - button_download: 다운로드 - button_list: ëª©ë¡ - button_view: 보기 - button_move: ì´ë™ - button_back: 뒤로 - button_cancel: 취소 - button_activate: 활성화 - button_sort: ì •ë ¬ - button_log_time: 작업시간 ê¸°ë¡ - button_rollback: ì´ ë²„ì „ìœ¼ë¡œ ë˜ëŒë¦¬ê¸° - button_watch: 지켜보기 - button_unwatch: 관심ë„기 - button_reply: 답글 - button_archive: 잠금보관 - button_unarchive: 잠금보관해제 - button_reset: 초기화 - button_rename: ì´ë¦„바꾸기 - button_change_password: 비밀번호 바꾸기 - button_copy: 복사 - button_annotate: ì´ë ¥í•´ì„¤ - button_update: 수정 - button_configure: 설정 - button_quote: 댓글달기 - - status_active: 사용중 - status_registered: 등ë¡ëŒ€ê¸° - status_locked: ìž ê¹€ - - text_select_mail_notifications: 알림메ì¼ì´ 필요한 ìž‘ì—…ì„ ì„ íƒí•˜ì„¸ìš”. - text_regexp_info: 예) ^[A-Z0-9]+$ - text_min_max_length_info: 0 는 ì œí•œì´ ì—†ìŒì„ ì˜ë¯¸í•¨ - text_project_destroy_confirmation: ì´ í”„ë¡œì íŠ¸ë¥¼ 삭제하고 모든 ë°ì´í„°ë¥¼ 지우시겠습니까? - text_subprojects_destroy_warning: "하위 프로ì íЏ(%{value})ì´(ê°€) ìžë™ìœ¼ë¡œ 지워질 것입니다." - text_workflow_edit: 업무íë¦„ì„ ìˆ˜ì •í•˜ë ¤ë©´ ì—­í• ê³¼ ì¼ê° ìœ í˜•ì„ ì„ íƒí•˜ì„¸ìš”. - text_are_you_sure: ê³„ì† ì§„í–‰ 하시겠습니까? - text_tip_issue_begin_day: 오늘 시작하는 업무(task) - text_tip_issue_end_day: 오늘 종료하는 업무(task) - text_tip_issue_begin_end_day: 오늘 시작하고 종료하는 업무(task) - text_caracters_maximum: "최대 %{count} ê¸€ìž ê°€ëŠ¥" - text_caracters_minimum: "최소한 %{count} ê¸€ìž ì´ìƒì´ì–´ì•¼ 합니다." - text_length_between: "%{min} ì—서 %{max} 글ìž" - text_tracker_no_workflow: ì´ ì¼ê° 유형ì—는 업무íë¦„ì´ ì •ì˜ë˜ì§€ 않았습니다. - text_unallowed_characters: 허용ë˜ì§€ 않는 문ìžì—´ - text_comma_separated: "구분ìž','를 ì´ìš©í•´ì„œ 여러 ê°œì˜ ê°’ì„ ìž…ë ¥í•  수 있습니다." - text_issues_ref_in_commit_messages: 커밋 메시지ì—서 ì¼ê°ì„ 참조하거나 해결하기 - text_issue_added: "%{author}ì´(ê°€) ì¼ê° %{id}ì„(를) 보고하였습니다." - text_issue_updated: "%{author}ì´(ê°€) ì¼ê° %{id}ì„(를) 수정하였습니다." - text_wiki_destroy_confirmation: ì´ ìœ„í‚¤ì™€ 모든 ë‚´ìš©ì„ ì§€ìš°ì‹œê² ìŠµë‹ˆê¹Œ? - text_issue_category_destroy_question: "ì¼ë¶€ ì¼ê°ë“¤(%{count}ê°œ)ì´ ì´ ë²”ì£¼ì— ì§€ì •ë˜ì–´ 있습니다. 어떻게 하시겠습니까?" - text_issue_category_destroy_assignments: 범주 지정 지우기 - text_issue_category_reassign_to: ì¼ê°ì„ ì´ ë²”ì£¼ì— ë‹¤ì‹œ 지정하기 - text_user_mail_option: "ì„ íƒí•˜ì§€ ì•Šì€ í”„ë¡œì íЏì—서ë„, 지켜보는 중ì´ê±°ë‚˜ ì†í•´ìžˆëŠ” 사항(ì¼ê°ë¥¼ 발행했거나 í• ë‹¹ëœ ê²½ìš°)ì´ ìžˆìœ¼ë©´ 알림메ì¼ì„ 받게 ë©ë‹ˆë‹¤." - text_no_configuration_data: "ì—­í• , ì¼ê° 유형, ì¼ê° ìƒíƒœë“¤ê³¼ 업무íë¦„ì´ ì•„ì§ ì„¤ì •ë˜ì§€ 않았습니다.\n기본 ì„¤ì •ì„ ì½ì–´ë“¤ì´ëŠ” ê²ƒì„ ê¶Œìž¥í•©ë‹ˆë‹¤. ì½ì–´ë“¤ì¸ í›„ì— ìˆ˜ì •í•  수 있습니다." - text_load_default_configuration: 기본 ì„¤ì •ì„ ì½ì–´ë“¤ì´ê¸° - text_status_changed_by_changeset: "ë³€ê²½ë¬¶ìŒ %{value}ì— ì˜í•˜ì—¬ 변경ë¨" - text_issues_destroy_confirmation: 'ì„ íƒí•œ ì¼ê°ë¥¼ ì •ë§ë¡œ 삭제하시겠습니까?' - text_select_project_modules: 'ì´ í”„ë¡œì íЏì—서 활성화시킬 ëª¨ë“ˆì„ ì„ íƒí•˜ì„¸ìš”:' - text_default_administrator_account_changed: 기본 ê´€ë¦¬ìž ê³„ì •ì´ ë³€ê²½ - text_file_repository_writable: íŒŒì¼ ì €ìž¥ì†Œ 쓰기 가능 - text_plugin_assets_writable: í”ŒëŸ¬ê·¸ì¸ ì „ìš© 디렉토리가 쓰기 가능 - text_rmagick_available: RMagick 사용 가능 (ì„ íƒì ) - text_destroy_time_entries_question: 삭제하려는 ì¼ê°ì— %{hours} ì‹œê°„ì´ ë³´ê³ ë˜ì–´ 있습니다. 어떻게 하시겠습니까? - text_destroy_time_entries: ë³´ê³ ëœ ì‹œê°„ì„ ì‚­ì œí•˜ê¸° - text_assign_time_entries_to_project: ë³´ê³ ëœ ì‹œê°„ì„ í”„ë¡œì íŠ¸ì— í• ë‹¹í•˜ê¸° - text_reassign_time_entries: 'ì´ ì•Œë¦¼ì— ë³´ê³ ëœ ì‹œê°„ì„ ìž¬í• ë‹¹í•˜ê¸°:' - text_user_wrote: "%{value}ì˜ ë§ê¸€:" - text_enumeration_category_reassign_to: '새로운 ê°’ì„ ì„¤ì •:' - text_enumeration_destroy_question: "%{count} ê°œì˜ ì¼ê°ì´ ì´ ê°’ì„ ì‚¬ìš©í•˜ê³  있습니다." - text_email_delivery_not_configured: "ì´ë©”ì¼ ì „ë‹¬ì´ ì„¤ì •ë˜ì§€ 않았습니다. 그래서 ì•Œë¦¼ì´ ë¹„í™œì„±í™”ë˜ì—ˆìŠµë‹ˆë‹¤.\n SMTP서버를 config/configuration.ymlì—서 설정하고 어플리케ì´ì…˜ì„ 다시 시작하십시오. 그러면 ë™ìž‘합니다." - text_repository_usernames_mapping: "저장소 로그ì—서 ë°œê²¬ëœ ê° ì‚¬ìš©ìžì— ë ˆë“œë§ˆì¸ ì‚¬ìš©ìžë¥¼ ì—…ë°ì´íŠ¸í• ë•Œ ì„ íƒí•©ë‹ˆë‹¤.\n레드마ì¸ê³¼ ì €ìž¥ì†Œì˜ ì´ë¦„ì´ë‚˜ ì´ë©”ì¼ì´ ê°™ì€ ì‚¬ìš©ìžê°€ ìžë™ìœ¼ë¡œ ì—°ê²°ë©ë‹ˆë‹¤." - text_diff_truncated: '... ì´ ì°¨ì´ì ì€ 표시할 수 있는 최대 줄수를 초과해서 ì´ ì°¨ì´ì ì€ 잘렸습니다.' - text_custom_field_possible_values_info: 'ê° ê°’ 당 한 줄' - text_wiki_page_destroy_question: ì´ íŽ˜ì´ì§€ëŠ” %{descendants} ê°œì˜ í•˜ìœ„ 페ì´ì§€ì™€ 관련 ë‚´ìš©ì´ ìžˆìŠµë‹ˆë‹¤. ì´ ë‚´ìš©ì„ ì–´ë–»ê²Œ 하시겠습니까? - text_wiki_page_nullify_children: 하위 페ì´ì§€ë¥¼ 최ìƒìœ„ 페ì´ì§€ 아래로 지정 - text_wiki_page_destroy_children: 모든 하위 페ì´ì§€ì™€ 관련 ë‚´ìš©ì„ ì‚­ì œ - text_wiki_page_reassign_children: 하위 페ì´ì§€ë¥¼ ì´ íŽ˜ì´ì§€ 아래로 지정 - - default_role_manager: ê´€ë¦¬ìž - default_role_developer: ê°œë°œìž - default_role_reporter: ë³´ê³ ìž - default_tracker_bug: 결함 - default_tracker_feature: 새기능 - default_tracker_support: ì§€ì› - default_issue_status_new: ì‹ ê·œ - default_issue_status_in_progress: ì§„í–‰ - default_issue_status_resolved: í•´ê²° - default_issue_status_feedback: ì˜ê²¬ - default_issue_status_closed: 완료 - default_issue_status_rejected: ê±°ì ˆ - default_doc_category_user: ì‚¬ìš©ìž ë¬¸ì„œ - default_doc_category_tech: 기술 문서 - default_priority_low: ë‚®ìŒ - default_priority_normal: 보통 - default_priority_high: ë†’ìŒ - default_priority_urgent: 긴급 - default_priority_immediate: 즉시 - default_activity_design: 설계 - default_activity_development: 개발 - - enumeration_issue_priorities: ì¼ê° 우선순위 - enumeration_doc_categories: 문서 범주 - enumeration_activities: 작업분류(시간추ì ) - - field_issue_to: 관련 ì¼ê° - label_view_all_revisions: 모든 ê°œì •íŒ í‘œì‹œ - label_tag: 태그(Tag) - label_branch: 브랜치(Branch) - error_no_tracker_in_project: 사용할 수 있ë„ë¡ ì„¤ì •ëœ ì¼ê° ìœ í˜•ì´ ì—†ìŠµë‹ˆë‹¤. 프로ì íЏ ì„¤ì •ì„ í™•ì¸í•˜ì‹­ì‹œì˜¤. - error_no_default_issue_status: '기본 ìƒíƒœê°€ ì •í•´ì ¸ 있지 않습니다. ì„¤ì •ì„ í™•ì¸í•˜ì‹­ì‹œì˜¤. (주 ë©”ë‰´ì˜ "관리" -> "ì¼ê° ìƒíƒœ")' - text_journal_changed: "%{label}ì„(를) %{old}ì—서 %{new}(으)로 변경ë˜ì—ˆìŠµë‹ˆë‹¤." - text_journal_set_to: "%{label}ì„(를) %{value}(으)로 지정ë˜ì—ˆìŠµë‹ˆë‹¤." - text_journal_deleted: "%{label} ê°’ì´ ì§€ì›Œì¡ŒìŠµë‹ˆë‹¤. (%{old})" - label_group_plural: 그룹 - label_group: 그룹 - label_group_new: 새 그룹 - label_time_entry_plural: 작업시간 - text_journal_added: "%{label}ì— %{value}ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." - field_active: 사용중 - enumeration_system_activity: 시스템 작업 - permission_delete_issue_watchers: ì¼ê°ì§€í‚´ì´ 지우기 - version_status_closed: 닫힘 - version_status_locked: ìž ê¹€ - version_status_open: ì§„í–‰ - error_can_not_reopen_issue_on_closed_version: 닫힌 ë²„ì „ì— í• ë‹¹ëœ ì¼ê°ì€ 다시 재발ìƒì‹œí‚¬ 수 없습니다. - label_user_anonymous: ì´ë¦„ì—†ìŒ - button_move_and_follow: ì´ë™í•˜ê³  ë”°ë¼ê°€ê¸° - setting_default_projects_modules: 새 프로ì íŠ¸ì— ê¸°ë³¸ì ìœ¼ë¡œ í™œì„±í™”ë  ëª¨ë“ˆ - setting_gravatar_default: 기본 ê·¸ë¼ë°”타 ì´ë¯¸ì§€ - field_sharing: 공유 - label_version_sharing_hierarchy: ìƒìœ„ ë° í•˜ìœ„ 프로ì íЏ - label_version_sharing_system: 모든 프로ì íЏ - label_version_sharing_descendants: 하위 프로ì íЏ - label_version_sharing_tree: 최ìƒìœ„ ë° ëª¨ë“  하위 프로ì íЏ - label_version_sharing_none: ê³µìœ ì—†ìŒ - error_can_not_archive_project: ì´ í”„ë¡œì íŠ¸ë¥¼ 잠금보관할 수 없습니다. - button_duplicate: 복제 - button_copy_and_follow: 복사하고 ë”°ë¼ê°€ê¸° - label_copy_source: ì›ë³¸ - setting_issue_done_ratio: ì¼ê°ì˜ ì§„ì²™ë„ ê³„ì‚°ë°©ë²• - setting_issue_done_ratio_issue_status: ì¼ê° ìƒíƒœë¥¼ 사용하기 - error_issue_done_ratios_not_updated: ì¼ê° ì§„ì²™ë„ê°€ 수정ë˜ì§€ 않았습니다. - error_workflow_copy_target: ëŒ€ìƒ ì¼ê°ì˜ 유형과 ì—­í• ì„ ì„ íƒí•˜ì„¸ìš”. - setting_issue_done_ratio_issue_field: ì¼ê° 수정ì—서 ì§„ì²™ë„ ìž…ë ¥í•˜ê¸° - label_copy_same_as_target: 대ìƒê³¼ ê°™ìŒ. - label_copy_target: ëŒ€ìƒ - notice_issue_done_ratios_updated: ì¼ê° ì§„ì²™ë„ê°€ 수정ë˜ì—ˆìŠµë‹ˆë‹¤. - error_workflow_copy_source: ì›ë³¸ ì¼ê°ì˜ 유형ì´ë‚˜ ì—­í• ì„ ì„ íƒí•˜ì„¸ìš”. - label_update_issue_done_ratios: 모든 ì¼ê° ì§„ì²™ë„ ê°±ì‹ í•˜ê¸° - setting_start_of_week: 달력 시작 ìš”ì¼ - permission_view_issues: ì¼ê° 보기 - label_display_used_statuses_only: ì´ ì¼ê° 유형ì—서 사용ë˜ëŠ” ìƒíƒœë§Œ 보여주기 - label_revision_id: ê°œì •íŒ %{value} - label_api_access_key: API 접근키 - label_api_access_key_created_on: API 접근키가 %{value} ì „ì— ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. - label_feeds_access_key: RSS 접근키 - notice_api_access_key_reseted: API 접근키가 초기화ë˜ì—ˆìŠµë‹ˆë‹¤. - setting_rest_api_enabled: REST 웹서비스 활성화 - label_missing_api_access_key: API 접근키가 없습니다. - label_missing_feeds_access_key: RSS 접근키가 없습니다. - button_show: 보기 - text_line_separated: 여러 ê°’ì´ í—ˆìš©ë¨(ê°’ 마다 한 줄씩) - setting_mail_handler_body_delimiters: ë©”ì¼ ë³¸ë¬¸ êµ¬ë¶„ìž - permission_add_subprojects: 하위 프로ì íЏ 만들기 - label_subproject_new: 새 하위 프로ì íЏ - text_own_membership_delete_confirmation: |- - 권한들 ì¼ë¶€ ë˜ëŠ” 전부를 막 삭제하려고 하고 있습니다. 그렇게 ë˜ë©´ ì´ í”„ë¡œì íŠ¸ë¥¼ ë”ì´ìƒ 수정할 수 없게 ë©ë‹ˆë‹¤. - 계ì†í•˜ì‹œê² ìŠµë‹ˆê¹Œ? - label_close_versions: ì™„ë£Œëœ ë²„ì „ 닫기 - label_board_sticky: ë¶™ë°•ì´ - label_board_locked: 잠금 - permission_export_wiki_pages: 위키 페ì´ì§€ 내보내기 - setting_cache_formatted_text: 형ì‹ì„ 가진 í…스트 빠른 임시 기억 - permission_manage_project_activities: 프로ì íЏ 작업내역 관리 - error_unable_delete_issue_status: ì¼ê° ìƒíƒœë¥¼ 지울 수 없습니다. - label_profile: 사용ìžì •ë³´ - permission_manage_subtasks: 하위 ì¼ê° 관리 - field_parent_issue: ìƒìœ„ ì¼ê° - label_subtask_plural: 하위 ì¼ê° - label_project_copy_notifications: 프로ì íЏ 복사 ì¤‘ì— ì´ë©”ì¼ ì•Œë¦¼ 보내기 - error_can_not_delete_custom_field: ì‚¬ìš©ìž ì •ì˜ í•„ë“œë¥¼ 삭제할 수 없습니다. - error_unable_to_connect: ì—°ê²°í•  수 없습니다((%{value}) - error_can_not_remove_role: ì´ ì—­í• ì€ í˜„ìž¬ 사용 중ì´ì´ì„œ 삭제할 수 없습니다. - error_can_not_delete_tracker: ì´ ìœ í˜•ì˜ ì¼ê°ë“¤ì´ 있어서 삭제할 수 없습니다. - field_principal: ì‹ ì› - label_my_page_block: ë‚´ 페ì´ì§€ 출력화면 - notice_failed_to_save_members: "%{errors}:구성ì›ì„ 저장 중 실패하였습니다" - text_zoom_out: ë” ìž‘ê²Œ - text_zoom_in: ë” í¬ê²Œ - notice_unable_delete_time_entry: 시간 ê¸°ë¡ í•­ëª©ì„ ì‚­ì œí•  수 없습니다. - label_overall_spent_time: ì´ ì†Œìš”ì‹œê°„ - field_time_entries: 기ë¡ëœ 시간 - project_module_gantt: Gantt 챠트 - project_module_calendar: 달력 - button_edit_associated_wikipage: "ì—°ê´€ëœ ìœ„í‚¤ 페ì´ì§€ %{page_title} 수정" - field_text: í…스트 ì˜ì—­ - label_user_mail_option_only_owner: ë‚´ê°€ ì €ìžì¸ 사항만 - setting_default_notification_option: 기본 알림 옵션 - label_user_mail_option_only_my_events: ë‚´ê°€ 지켜보거나 ì†í•´ìžˆëŠ” 사항만 - label_user_mail_option_only_assigned: ë‚´ì—게 í• ë‹¹ëœ ì‚¬í•­ë§Œ - label_user_mail_option_none: 알림 ì—†ìŒ - field_member_of_group: í• ë‹¹ëœ ì‚¬ëžŒì˜ ê·¸ë£¹ - field_assigned_to_role: í• ë‹¹ëœ ì‚¬ëžŒì˜ ì—­í•  - notice_not_authorized_archived_project: 접근하려는 프로ì íŠ¸ëŠ” ì´ë¯¸ 잠금보관ë˜ì–´ 있습니다. - label_principal_search: "ì‚¬ìš©ìž ë° ê·¸ë£¹ 찾기:" - label_user_search: "ì‚¬ìš©ìž ì°¾ê¸°::" - field_visible: ë³´ì´ê¸° - setting_emails_header: ì´ë©”ì¼ í—¤ë” - setting_commit_logtime_activity_id: 기ë¡ëœ ì‹œê°„ì— ì ìš©í•  작업분류 - text_time_logged_by_changeset: "ë³€ê²½ë¬¶ìŒ %{value}ì—서 ì ìš©ë˜ì—ˆìŠµë‹ˆë‹¤." - setting_commit_logtime_enabled: 커밋 시ì ì— 작업 시간 ê¸°ë¡ í™œì„±í™” - notice_gantt_chart_truncated: "표시할 수 있는 최대 항목수(%{max})를 초과하여 차트가 잘렸습니다." - setting_gantt_items_limit: "Gantt ì°¨íŠ¸ì— í‘œì‹œë˜ëŠ” 최대 항목수" - field_warn_on_leaving_unsaved: "저장하지 ì•Šì€ íŽ˜ì´ì§€ë¥¼ 빠져나갈 때 나ì—게 알림" - text_warn_on_leaving_unsaved: "현재 페ì´ì§€ëŠ” 저장ë˜ì§€ ì•Šì€ ë¬¸ìžê°€ 있습니다. ì´ íŽ˜ì´ì§€ë¥¼ 빠져나가면 ë‚´ìš©ì„ ìžƒì„것입니다." - label_my_queries: "ë‚´ 검색 ì–‘ì‹" - text_journal_changed_no_detail: "%{label}ì´ ë³€ê²½ë˜ì—ˆìŠµë‹ˆë‹¤." - label_news_comment_added: "ë‰´ìŠ¤ì— ì„¤ëª…ì´ ì¶”ê°€ë˜ì—ˆìŠµë‹ˆë‹¤." - button_expand_all: "ëª¨ë‘ í™•ìž¥" - button_collapse_all: "ëª¨ë‘ ì¶•ì†Œ" - label_additional_workflow_transitions_for_assignee: "사용ìžê°€ 작업ìžì¼ 때 허용ë˜ëŠ” 추가 ìƒíƒœ" - label_additional_workflow_transitions_for_author: "사용ìžê°€ ì €ìžì¼ 때 허용ë˜ëŠ” 추가 ìƒíƒœ" - label_bulk_edit_selected_time_entries: "ì„ íƒëœ 소요 시간 대량 편집" - text_time_entries_destroy_confirmation: "ì„ íƒí•œ 소요 시간 í•­ëª©ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" - label_role_anonymous: Anonymous - label_role_non_member: Non member - - label_issue_note_added: "ë§ê¸€ì´ 추가ë˜ì—ˆìŠµë‹ˆë‹¤." - label_issue_status_updated: "ìƒíƒœê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤." - label_issue_priority_updated: "ìš°ì„  순위가 변경ë˜ì—ˆìŠµë‹ˆë‹¤." - label_issues_visibility_own: "ì¼ê°ì„ ìƒì„±í•˜ê±°ë‚˜ ë§¡ì€ ì‚¬ìš©ìž" - field_issues_visibility: "ì¼ê° ë³´ìž„" - label_issues_visibility_all: "모든 ì¼ê°" - permission_set_own_issues_private: "ìžì‹ ì˜ ì¼ê°ì„ 공개나 비공개로 설정" - field_is_private: "비공개" - permission_set_issues_private: "ì¼ê°ì„ 공개나 비공개로 설정" - label_issues_visibility_public: "모든 비공개 ì¼ê°" - text_issues_destroy_descendants_confirmation: "%{count} ê°œì˜ í•˜ìœ„ ì¼ê°ì„ 삭제할 것입니다." - field_commit_logs_encoding: "커밋(commit) ê¸°ë¡ ì¸ì½”딩" - field_scm_path_encoding: "경로 ì¸ì½”딩" - text_scm_path_encoding_note: "기본: UTF-8" - field_path_to_repository: "저장소 경로" - field_root_directory: "루트 경로" - field_cvs_module: "모듈" - field_cvsroot: "CVS 루트" - text_mercurial_repository_note: "로컬 저장소 (예: /hgrepo, c:\\hgrepo)" - text_scm_command: "명령" - text_scm_command_version: "버전" - label_git_report_last_commit: "파ì¼ì´ë‚˜ í´ë”ì˜ ë§ˆì§€ë§‰ 커밋(commit)ì„ ë³´ê³ " - text_scm_config: "SCM ëª…ë ¹ì„ config/configuration.ymlì—서 수정할 수 있습니다. 수정후ì—는 재시작하십시오." - text_scm_command_not_available: "SCM ëª…ë ¹ì„ ì‚¬ìš©í•  수 없습니다. 관리 페ì´ì§€ì˜ ì„¤ì •ì„ ê²€ì‚¬í•˜ì‹­ì‹œì˜¤." - notice_issue_successful_create: "%{id} ì¼ê°ì´ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤." - label_between: "사ì´" - setting_issue_group_assignment: "ê·¸ë£¹ì— ì¼ê° 할당 허용" - label_diff: "비êµ(diff)" - text_git_repository_note: "ë¡œì»¬ì˜ bare 저장소 (예: /gitrepo, c:\\gitrepo)" - description_query_sort_criteria_direction: "ì •ë ¬ ë°©í–¥" - description_project_scope: "검색 범위" - description_filter: "검색 ì¡°ê±´" - description_user_mail_notification: "ë©”ì¼ ì•Œë¦¼ 설정" - description_date_from: "시작 ë‚ ì§œ ìž…ë ¥" - description_message_content: "메세지 ë‚´ìš©" - description_available_columns: "가능한 컬럼" - description_date_range_interval: "시작과 ë 날짜로 범위를 ì„ íƒí•˜ì‹­ì‹œì˜¤." - description_issue_category_reassign: "ì¼ê° 범주를 ì„ íƒí•˜ì‹­ì‹œì˜¤." - description_search: "검색항목" - description_notes: "ë§ê¸€" - description_date_range_list: "목ë¡ì—서 범위를 ì„ íƒ í•˜ì‹­ì‹œì˜¤." - description_choose_project: "프로ì íЏ" - description_date_to: "종료 ë‚ ì§œ ìž…ë ¥" - description_query_sort_criteria_attribute: "ì •ë ¬ ì†ì„±" - description_wiki_subpages_reassign: "새로운 ìƒìœ„ 페ì´ì§€ë¥¼ ì„ íƒí•˜ì‹­ì‹œì˜¤." - description_selected_columns: "ì„ íƒëœ 컬럼" - label_parent_revision: "ìƒìœ„" - label_child_revision: "하위" - error_scm_annotate_big_text_file: "최대 í…스트 íŒŒì¼ í¬ê¸°ë¥¼ 초과 하면 í•­ëª©ì€ ì´ë ¥í™” ë  ìˆ˜ 없습니다." - setting_default_issue_start_date_to_creation_date: "새로운 ì¼ê°ì˜ 시작 날짜로 오늘 ë‚ ì§œ 사용" - button_edit_section: "ì´ ë¶€ë¶„ 수정" - setting_repositories_encodings: "첨부파ì¼ì´ë‚˜ 저장소 ì¸ì½”딩" - description_all_columns: "모든 컬럼" - button_export: "내보내기" - label_export_options: "내보내기 옵션: %{export_format}" - error_attachment_too_big: "ì´ íŒŒì¼ì€ ì œí•œëœ í¬ê¸°(%{max_size})를 초과하였기 ë•Œë¬¸ì— ì—…ë¡œë“œ í•  수 없습니다." - - notice_failed_to_save_time_entries: "%{total} ê°œì˜ ì‹œê°„ìž…ë ¥ì¤‘ ë‹¤ìŒ %{count} ê°œì˜ ì €ìž¥ì— ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤:: %{ids}." - label_x_issues: - zero: 0 ì¼ê° - one: 1 ì¼ê° - other: "%{count} ì¼ê°" - label_repository_new: 저장소 추가 - field_repository_is_default: 주 저장소 - label_copy_attachments: ì²¨ë¶€íŒŒì¼ ë³µì‚¬ - label_item_position: "%{position}/%{count}" - label_completed_versions: 완료 버전 - text_project_identifier_info: "소문ìž(a-z),숫ìž,대쉬(-)와 밑줄(_)ë§Œ 가능합니다.
ì‹ë³„ìžëŠ” 저장후ì—는 수정할 수 없습니다." - field_multiple: 복수선íƒê°€ëŠ¥ - setting_commit_cross_project_ref: 다른 프로ì íŠ¸ì˜ ì¼ê° 참조 ë° ìˆ˜ì • 허용 - text_issue_conflict_resolution_add_notes: ë³€ê²½ë‚´ìš©ì€ ì·¨ì†Œí•˜ê³  ë§ê¸€ë§Œ 추가 - text_issue_conflict_resolution_overwrite: 변경내용 ê°•ì œì ìš© (ì´ì „ ë§ê¸€ì„ 제외하고 ë®ì–´ ì”니다) - notice_issue_update_conflict: ì¼ê°ì´ 수정ë˜ëŠ” ë™ì•ˆ 다른 사용ìžì— ì˜í•´ì„œ 변경ë˜ì—ˆìŠµë‹ˆë‹¤. - text_issue_conflict_resolution_cancel: "ë³€ê²½ë‚´ìš©ì„ ë˜ëŒë¦¬ê³  다시 표시 %{link}" - permission_manage_related_issues: ì—°ê²°ëœ ì¼ê° 관리 - field_auth_source_ldap_filter: LDAP í•„í„° - label_search_for_watchers: 추가할 ì¼ê°ì§€í‚´ì´ 검색 - notice_account_deleted: ë‹¹ì‹ ì˜ ê³„ì •ì´ ì™„ì „ížˆ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. - setting_unsubscribe: 사용ìžë“¤ì´ ìžì‹ ì˜ ê³„ì •ì„ ì‚­ì œí† ë¡ í—ˆìš© - button_delete_my_account: ë‚˜ì˜ ê³„ì • ì‚­ì œ - text_account_destroy_confirmation: |- - 계ì†í•˜ì‹œê² ìŠµë‹ˆê¹Œ? - ê³„ì •ì´ ì‚­ì œë˜ë©´ 복구할 수 없습니다. - error_session_expired: ë‹¹ì‹ ì˜ ì„¸ì…˜ì´ ë§Œë£Œë˜ì—ˆìŠµë‹ˆë‹¤. 다시 로그ì¸í•˜ì„¸ìš”. - text_session_expiration_settings: "경고: ì´ ì„¤ì •ì„ ë°”ê¾¸ë©´ ë‹¹ì‹ ì„ í¬í•¨í•˜ì—¬ í˜„ìž¬ì˜ ì„¸ì…˜ë“¤ì„ ë§Œë£Œì‹œí‚¬ 수 있습니다." - setting_session_lifetime: 세션 최대 시간 - setting_session_timeout: 세션 비활성화 타임아웃 - label_session_expiration: 세션 만료 - permission_close_project: 프로ì íŠ¸ë¥¼ 닫거나 다시 열기 - label_show_closed_projects: 닫힌 프로ì íЏ 보기 - button_close: 닫기 - button_reopen: 다시 열기 - project_status_active: 사용중 - project_status_closed: 닫힘 - project_status_archived: 잠금보관 - text_project_closed: ì´ í”„ë¡œì íŠ¸ëŠ” 닫혀 있으며 ì½ê¸° 전용입니다. - notice_user_successful_create: ì‚¬ìš©ìž %{id} ì´(ê°€) ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. - field_core_fields: 표준 항목들 - field_timeout: 타임아웃 (ì´ˆ) - setting_thumbnails_enabled: 첨부파ì¼ì˜ ì¸ë„¤ì¼ì„ 보여줌 - setting_thumbnails_size: ì¸ë„¤ì¼ í¬ê¸° (픽셀) - label_status_transitions: ì¼ê° ìƒíƒœ 변경 - label_fields_permissions: 항목 편집 권한 - label_readonly: ì½ê¸° ì „ìš© - label_required: 필수 - text_repository_identifier_info: "소문ìž(a-z),숫ìž,대쉬(-)와 밑줄(_)ë§Œ 가능합니다.
ì‹ë³„ìžëŠ” 저장후ì—는 수정할 수 없습니다." - field_board_parent: Parent forum - label_attribute_of_project: "프로ì íŠ¸ì˜ %{name}" - label_attribute_of_author: "ì €ìžì˜ %{name}" - label_attribute_of_assigned_to: "담당ìžì˜ %{name}" - label_attribute_of_fixed_version: "ëª©í‘œë²„ì „ì˜ %{name}" - label_copy_subtasks: 하위 ì¼ê°ë“¤ì„ 복사 - label_copied_to: "ë‹¤ìŒ ì¼ê°ìœ¼ë¡œ 복사ë¨:" - label_copied_from: "ë‹¤ìŒ ì¼ê°ìœ¼ë¡œë¶€í„° 복사ë¨:" - label_any_issues_in_project: ë‹¤ìŒ í”„ë¡œì íŠ¸ì— ì†í•œ 아무 ì¼ê° - label_any_issues_not_in_project: ë‹¤ìŒ í”„ë¡œì íŠ¸ì— ì†í•˜ì§€ ì•Šì€ ì•„ë¬´ ì¼ê° - field_private_notes: 비공개 ë§ê¸€ - permission_view_private_notes: 비공개 ë§ê¸€ 보기 - permission_set_notes_private: ë§ê¸€ì„ 비공개로 설정 - label_no_issues_in_project: ë‹¤ìŒ í”„ë¡œì íЏ ë‚´ì—서 해당 ì¼ê° ì—†ìŒ - label_any: ëª¨ë‘ - label_last_n_weeks: 최근 %{count} 주 - setting_cross_project_subtasks: 다른 프로ì íŠ¸ì˜ ì¼ê°ì„ ìƒìœ„ ì¼ê°ìœ¼ë¡œ 지정하는 ê²ƒì„ í—ˆìš© - label_cross_project_descendants: 하위 프로ì íЏ - label_cross_project_tree: 최ìƒìœ„ ë° ëª¨ë“  하위 프로ì íЏ - label_cross_project_hierarchy: ìƒìœ„ ë° í•˜ìœ„ 프로ì íЏ - label_cross_project_system: 모든 프로ì íЏ - button_hide: 숨기기 - setting_non_working_week_days: ë¹„ê·¼ë¬´ì¼ (non-working days) - label_in_the_next_days: ë‹¤ìŒ - label_in_the_past_days: 지난 diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/04/04353faa53c81964d7239a9938818ad1596cb331.svn-base --- a/.svn/pristine/04/04353faa53c81964d7239a9938818ad1596cb331.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,218 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'messages_controller' - -# Re-raise errors caught by the controller. -class MessagesController; def rescue_action(e) raise e end; end - -class MessagesControllerTest < ActionController::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, :boards, :messages, :enabled_modules - - def setup - @controller = MessagesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_show - get :show, :board_id => 1, :id => 1 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:board) - assert_not_nil assigns(:project) - assert_not_nil assigns(:topic) - end - - def test_show_should_contain_reply_field_tags_for_quoting - @request.session[:user_id] = 2 - get :show, :board_id => 1, :id => 1 - assert_response :success - - # tags required by MessagesController#quote - assert_tag 'input', :attributes => {:id => 'message_subject'} - assert_tag 'textarea', :attributes => {:id => 'message_content'} - assert_tag 'div', :attributes => {:id => 'reply'} - end - - def test_show_with_pagination - message = Message.find(1) - assert_difference 'Message.count', 30 do - 30.times do - message.children << Message.new(:subject => 'Reply', :content => 'Reply body', :author_id => 2, :board_id => 1) - end - end - get :show, :board_id => 1, :id => 1, :r => message.children.last(:order => 'id').id - assert_response :success - assert_template 'show' - replies = assigns(:replies) - assert_not_nil replies - assert !replies.include?(message.children.first(:order => 'id')) - assert replies.include?(message.children.last(:order => 'id')) - end - - def test_show_with_reply_permission - @request.session[:user_id] = 2 - get :show, :board_id => 1, :id => 1 - assert_response :success - assert_template 'show' - assert_tag :div, :attributes => { :id => 'reply' }, - :descendant => { :tag => 'textarea', :attributes => { :id => 'message_content' } } - end - - def test_show_message_not_found - get :show, :board_id => 1, :id => 99999 - assert_response 404 - end - - def test_show_message_from_invalid_board_should_respond_with_404 - get :show, :board_id => 999, :id => 1 - assert_response 404 - end - - def test_get_new - @request.session[:user_id] = 2 - get :new, :board_id => 1 - assert_response :success - assert_template 'new' - end - - def test_post_new - @request.session[:user_id] = 2 - ActionMailer::Base.deliveries.clear - - with_settings :notified_events => %w(message_posted) do - post :new, :board_id => 1, - :message => { :subject => 'Test created message', - :content => 'Message body'} - end - message = Message.find_by_subject('Test created message') - assert_not_nil message - assert_redirected_to "/boards/1/topics/#{message.to_param}" - assert_equal 'Message body', message.content - assert_equal 2, message.author_id - assert_equal 1, message.board_id - - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - assert_equal "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] Test created message", mail.subject - assert_mail_body_match 'Message body', mail - # author - assert mail.bcc.include?('jsmith@somenet.foo') - # project member - assert mail.bcc.include?('dlopper@somenet.foo') - end - - def test_get_edit - @request.session[:user_id] = 2 - get :edit, :board_id => 1, :id => 1 - assert_response :success - assert_template 'edit' - end - - def test_post_edit - @request.session[:user_id] = 2 - post :edit, :board_id => 1, :id => 1, - :message => { :subject => 'New subject', - :content => 'New body'} - assert_redirected_to '/boards/1/topics/1' - message = Message.find(1) - assert_equal 'New subject', message.subject - assert_equal 'New body', message.content - end - - def test_post_edit_sticky_and_locked - @request.session[:user_id] = 2 - post :edit, :board_id => 1, :id => 1, - :message => { :subject => 'New subject', - :content => 'New body', - :locked => '1', - :sticky => '1'} - assert_redirected_to '/boards/1/topics/1' - message = Message.find(1) - assert_equal true, message.sticky? - assert_equal true, message.locked? - end - - def test_post_edit_should_allow_to_change_board - @request.session[:user_id] = 2 - post :edit, :board_id => 1, :id => 1, - :message => { :subject => 'New subject', - :content => 'New body', - :board_id => 2} - assert_redirected_to '/boards/2/topics/1' - message = Message.find(1) - assert_equal Board.find(2), message.board - end - - def test_reply - @request.session[:user_id] = 2 - post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' } - reply = Message.find(:first, :order => 'id DESC') - assert_redirected_to "/boards/1/topics/1?r=#{reply.id}" - assert Message.find_by_subject('Test reply') - end - - def test_destroy_topic - @request.session[:user_id] = 2 - assert_difference 'Message.count', -3 do - post :destroy, :board_id => 1, :id => 1 - end - assert_redirected_to '/projects/ecookbook/boards/1' - assert_nil Message.find_by_id(1) - end - - def test_destroy_reply - @request.session[:user_id] = 2 - assert_difference 'Message.count', -1 do - post :destroy, :board_id => 1, :id => 2 - end - assert_redirected_to '/boards/1/topics/1?r=2' - assert_nil Message.find_by_id(2) - end - - def test_quote - @request.session[:user_id] = 2 - xhr :get, :quote, :board_id => 1, :id => 3 - assert_response :success - assert_equal 'text/javascript', response.content_type - assert_template 'quote' - assert_include 'RE: First post', response.body - assert_include '> An other reply', response.body - end - - def test_preview_new - @request.session[:user_id] = 2 - post :preview, - :board_id => 1, - :message => {:subject => "", :content => "Previewed text"} - assert_response :success - assert_template 'common/_preview' - end - - def test_preview_edit - @request.session[:user_id] = 2 - post :preview, - :id => 4, - :board_id => 1, - :message => {:subject => "", :content => "Previewed text"} - assert_response :success - assert_template 'common/_preview' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/04/046cbe787072732ca9cd18ffb9fce1aa135f723e.svn-base --- a/.svn/pristine/04/046cbe787072732ca9cd18ffb9fce1aa135f723e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -

<%= l(@enumeration.option_name) %>: <%=h @enumeration %>

- -<%= form_tag({}, :method => :delete) do %> -
-

<%= l(:text_enumeration_destroy_question, @enumeration.objects_count) %>

-

-<%= select_tag 'reassign_to_id', (content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") + options_from_collection_for_select(@enumerations, 'id', 'name')) %>

-
- -<%= submit_tag l(:button_apply) %> -<%= link_to l(:button_cancel), enumerations_path %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/04/046d428ac69934d1d2d0ae1a160753bb78acb462.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/04/046d428ac69934d1d2d0ae1a160753bb78acb462.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,304 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class CustomFieldTest < ActiveSupport::TestCase + fixtures :custom_fields + + def test_create + field = UserCustomField.new(:name => 'Money money money', :field_format => 'float') + assert field.save + end + + def test_before_validation + field = CustomField.new(:name => 'test_before_validation', :field_format => 'int') + field.searchable = true + assert field.save + assert_equal false, field.searchable + field.searchable = true + assert field.save + assert_equal false, field.searchable + end + + def test_regexp_validation + field = IssueCustomField.new(:name => 'regexp', :field_format => 'text', :regexp => '[a-z0-9') + assert !field.save + assert_include I18n.t('activerecord.errors.messages.invalid'), + field.errors[:regexp] + field.regexp = '[a-z0-9]' + assert field.save + end + + def test_default_value_should_be_validated + field = CustomField.new(:name => 'Test', :field_format => 'int') + field.default_value = 'abc' + assert !field.valid? + field.default_value = '6' + assert field.valid? + end + + def test_default_value_should_not_be_validated_when_blank + field = CustomField.new(:name => 'Test', :field_format => 'list', :possible_values => ['a', 'b'], :is_required => true, :default_value => '') + assert field.valid? + end + + def test_field_format_should_be_validated + field = CustomField.new(:name => 'Test', :field_format => 'foo') + assert !field.valid? + end + + def test_field_format_validation_should_accept_formats_added_at_runtime + Redmine::CustomFieldFormat.register 'foobar' + + field = CustomField.new(:name => 'Some Custom Field', :field_format => 'foobar') + assert field.valid?, 'field should be valid' + ensure + Redmine::CustomFieldFormat.delete 'foobar' + end + + def test_should_not_change_field_format_of_existing_custom_field + field = CustomField.find(1) + field.field_format = 'int' + assert_equal 'list', field.field_format + end + + def test_possible_values_should_accept_an_array + field = CustomField.new + field.possible_values = ["One value", ""] + assert_equal ["One value"], field.possible_values + end + + def test_possible_values_should_accept_a_string + field = CustomField.new + field.possible_values = "One value" + assert_equal ["One value"], field.possible_values + end + + def test_possible_values_should_accept_a_multiline_string + field = CustomField.new + field.possible_values = "One value\nAnd another one \r\n \n" + assert_equal ["One value", "And another one"], field.possible_values + end + + if "string".respond_to?(:encoding) + def test_possible_values_stored_as_binary_should_be_utf8_encoded + field = CustomField.find(11) + assert_kind_of Array, field.possible_values + assert field.possible_values.size > 0 + field.possible_values.each do |value| + assert_equal "UTF-8", value.encoding.name + end + end + end + + def test_destroy + field = CustomField.find(1) + assert field.destroy + end + + def test_new_subclass_instance_should_return_an_instance + f = CustomField.new_subclass_instance('IssueCustomField') + assert_kind_of IssueCustomField, f + end + + def test_new_subclass_instance_should_set_attributes + f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test') + assert_kind_of IssueCustomField, f + assert_equal 'Test', f.name + end + + def test_new_subclass_instance_with_invalid_class_name_should_return_nil + assert_nil CustomField.new_subclass_instance('WrongClassName') + end + + def test_new_subclass_instance_with_non_subclass_name_should_return_nil + assert_nil CustomField.new_subclass_instance('Project') + end + + def test_string_field_validation_with_blank_value + f = CustomField.new(:field_format => 'string') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + + f.is_required = true + assert !f.valid_field_value?(nil) + assert !f.valid_field_value?('') + end + + def test_string_field_validation_with_min_and_max_lengths + f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5) + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') + assert f.valid_field_value?('a' * 2) + assert !f.valid_field_value?('a') + assert !f.valid_field_value?('a' * 6) + end + + def test_string_field_validation_with_regexp + f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') + assert f.valid_field_value?('ABC') + assert !f.valid_field_value?('abc') + end + + def test_date_field_validation + f = CustomField.new(:field_format => 'date') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') + assert f.valid_field_value?('1975-07-14') + assert !f.valid_field_value?('1975-07-33') + assert !f.valid_field_value?('abc') + end + + def test_list_field_validation + f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2']) + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') + assert f.valid_field_value?('value2') + assert !f.valid_field_value?('abc') + end + + def test_int_field_validation + f = CustomField.new(:field_format => 'int') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') + assert f.valid_field_value?('123') + assert f.valid_field_value?('+123') + assert f.valid_field_value?('-123') + assert !f.valid_field_value?('6abc') + end + + def test_float_field_validation + f = CustomField.new(:field_format => 'float') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') + assert f.valid_field_value?('11.2') + assert f.valid_field_value?('-6.250') + assert f.valid_field_value?('5') + assert !f.valid_field_value?('6abc') + end + + def test_multi_field_validation + f = CustomField.new(:field_format => 'list', :multiple => 'true', :possible_values => ['value1', 'value2']) + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') + assert f.valid_field_value?([]) + assert f.valid_field_value?([nil]) + assert f.valid_field_value?(['']) + assert !f.valid_field_value?([' ']) + + assert f.valid_field_value?('value2') + assert !f.valid_field_value?('abc') + + assert f.valid_field_value?(['value2']) + assert !f.valid_field_value?(['abc']) + + assert f.valid_field_value?(['', 'value2']) + assert !f.valid_field_value?(['', 'abc']) + + assert f.valid_field_value?(['value1', 'value2']) + assert !f.valid_field_value?(['value1', 'abc']) + end + + def test_changing_multiple_to_false_should_delete_multiple_values + field = ProjectCustomField.create!(:name => 'field', :field_format => 'list', :multiple => 'true', :possible_values => ['field1', 'field2']) + other = ProjectCustomField.create!(:name => 'other', :field_format => 'list', :multiple => 'true', :possible_values => ['other1', 'other2']) + + item_with_multiple_values = Project.generate!(:custom_field_values => {field.id => ['field1', 'field2'], other.id => ['other1', 'other2']}) + item_with_single_values = Project.generate!(:custom_field_values => {field.id => ['field1'], other.id => ['other2']}) + + assert_difference 'CustomValue.count', -1 do + field.multiple = false + field.save! + end + + item_with_multiple_values = Project.find(item_with_multiple_values.id) + assert_kind_of String, item_with_multiple_values.custom_field_value(field) + assert_kind_of Array, item_with_multiple_values.custom_field_value(other) + assert_equal 2, item_with_multiple_values.custom_field_value(other).size + end + + def test_value_class_should_return_the_class_used_for_fields_values + assert_equal User, CustomField.new(:field_format => 'user').value_class + assert_equal Version, CustomField.new(:field_format => 'version').value_class + end + + def test_value_class_should_return_nil_for_other_fields + assert_nil CustomField.new(:field_format => 'text').value_class + assert_nil CustomField.new.value_class + end + + def test_value_from_keyword_for_list_custom_field + field = CustomField.find(1) + assert_equal 'PostgreSQL', field.value_from_keyword('postgresql', Issue.find(1)) + end + + def test_visibile_scope_with_admin_should_return_all_custom_fields + CustomField.delete_all + fields = [ + CustomField.generate!(:visible => true), + CustomField.generate!(:visible => false), + CustomField.generate!(:visible => false, :role_ids => [1, 3]), + CustomField.generate!(:visible => false, :role_ids => [1, 2]), + ] + + assert_equal 4, CustomField.visible(User.find(1)).count + end + + def test_visibile_scope_with_non_admin_user_should_return_visible_custom_fields + CustomField.delete_all + fields = [ + CustomField.generate!(:visible => true), + CustomField.generate!(:visible => false), + CustomField.generate!(:visible => false, :role_ids => [1, 3]), + CustomField.generate!(:visible => false, :role_ids => [1, 2]), + ] + user = User.generate! + User.add_to_project(user, Project.first, Role.find(3)) + + assert_equal [fields[0], fields[2]], CustomField.visible(user).order("id").to_a + end + + def test_visibile_scope_with_anonymous_user_should_return_visible_custom_fields + CustomField.delete_all + fields = [ + CustomField.generate!(:visible => true), + CustomField.generate!(:visible => false), + CustomField.generate!(:visible => false, :role_ids => [1, 3]), + CustomField.generate!(:visible => false, :role_ids => [1, 2]), + ] + + assert_equal [fields[0]], CustomField.visible(User.anonymous).order("id").to_a + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/04/046e745fdf9ac7fc7dec5d1b9e47c29909c11738.svn-base --- a/.svn/pristine/04/046e745fdf9ac7fc7dec5d1b9e47c29909c11738.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -.jstEditor { - padding-left: 0px; -} -.jstEditor textarea, .jstEditor iframe { - margin: 0; -} - -.jstHandle { - height: 10px; - font-size: 0.1em; - cursor: s-resize; - /*background: transparent url(img/resizer.png) no-repeat 45% 50%;*/ -} - -.jstElements { - padding: 3px 3px 3px 0; -} - -.jstElements button { - margin-right: 4px; - width : 24px; - height: 24px; - padding: 4px; - border-style: solid; - border-width: 1px; - border-color: #ddd; - background-color : #f7f7f7; - background-position : 50% 50%; - background-repeat: no-repeat; -} -.jstElements button:hover { - border-color: #bbb; - background-color: #e5e5e5; -} -.jstElements button span { - display : none; -} -.jstElements span { - display : inline; -} - -.jstSpacer { - width : 0px; - font-size: 1px; - margin-right: 6px; -} - -.jstElements .help { float: right; margin-right: 0.5em; padding-top: 8px; font-size: 0.9em; } -.jstElements .help a {padding: 2px 0 2px 20px; background: url(../images/help.png) no-repeat 0 50%;} - -/* Buttons --------------------------------------------------------- */ -.jstb_strong { - background-image: url(../images/jstoolbar/bt_strong.png); -} -.jstb_em { - background-image: url(../images/jstoolbar/bt_em.png); -} -.jstb_ins { - background-image: url(../images/jstoolbar/bt_ins.png); -} -.jstb_del { - background-image: url(../images/jstoolbar/bt_del.png); -} -.jstb_code { - background-image: url(../images/jstoolbar/bt_code.png); -} -.jstb_h1 { - background-image: url(../images/jstoolbar/bt_h1.png); -} -.jstb_h2 { - background-image: url(../images/jstoolbar/bt_h2.png); -} -.jstb_h3 { - background-image: url(../images/jstoolbar/bt_h3.png); -} -.jstb_ul { - background-image: url(../images/jstoolbar/bt_ul.png); -} -.jstb_ol { - background-image: url(../images/jstoolbar/bt_ol.png); -} -.jstb_bq { - background-image: url(../images/jstoolbar/bt_bq.png); -} -.jstb_unbq { - background-image: url(../images/jstoolbar/bt_bq_remove.png); -} -.jstb_pre { - background-image: url(../images/jstoolbar/bt_pre.png); -} -.jstb_link { - background-image: url(../images/jstoolbar/bt_link.png); -} -.jstb_img { - background-image: url(../images/jstoolbar/bt_img.png); -} diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/04/048c8c7c3fc4fac08662cbdaac59cd6348bb3d14.svn-base --- a/.svn/pristine/04/048c8c7c3fc4fac08662cbdaac59cd6348bb3d14.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1287 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'forwardable' -require 'cgi' - -module ApplicationHelper - include Redmine::WikiFormatting::Macros::Definitions - include Redmine::I18n - include GravatarHelper::PublicMethods - - extend Forwardable - def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter - - # Return true if user is authorized for controller/action, otherwise false - def authorize_for(controller, action) - User.current.allowed_to?({:controller => controller, :action => action}, @project) - end - - # Display a link if user is authorized - # - # @param [String] name Anchor text (passed to link_to) - # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized - # @param [optional, Hash] html_options Options passed to link_to - # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to - def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) - link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) - end - - # Displays a link to user's account page if active - def link_to_user(user, options={}) - if user.is_a?(User) - name = h(user.name(options[:format])) - if user.active? || (User.current.admin? && user.logged?) - link_to name, user_path(user), :class => user.css_classes - else - name - end - else - h(user.to_s) - end - end - - # Displays a link to +issue+ with its subject. - # Examples: - # - # link_to_issue(issue) # => Defect #6: This is the subject - # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... - # link_to_issue(issue, :subject => false) # => Defect #6 - # link_to_issue(issue, :project => true) # => Foo - Defect #6 - # link_to_issue(issue, :subject => false, :tracker => false) # => #6 - # - def link_to_issue(issue, options={}) - title = nil - subject = nil - text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" - if options[:subject] == false - title = truncate(issue.subject, :length => 60) - else - subject = issue.subject - if options[:truncate] - subject = truncate(subject, :length => options[:truncate]) - end - end - s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title - s << h(": #{subject}") if subject - s = h("#{issue.project} - ") + s if options[:project] - s - end - - # Generates a link to an attachment. - # Options: - # * :text - Link text (default to attachment filename) - # * :download - Force download (default: false) - def link_to_attachment(attachment, options={}) - text = options.delete(:text) || attachment.filename - action = options.delete(:download) ? 'download' : 'show' - opt_only_path = {} - opt_only_path[:only_path] = (options[:only_path] == false ? false : true) - options.delete(:only_path) - link_to(h(text), - {:controller => 'attachments', :action => action, - :id => attachment, :filename => attachment.filename}.merge(opt_only_path), - options) - end - - # Generates a link to a SCM revision - # Options: - # * :text - Link text (default to the formatted revision) - def link_to_revision(revision, repository, options={}) - if repository.is_a?(Project) - repository = repository.repository - end - text = options.delete(:text) || format_revision(revision) - rev = revision.respond_to?(:identifier) ? revision.identifier : revision - link_to( - h(text), - {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, - :title => l(:label_revision_id, format_revision(revision)) - ) - end - - # Generates a link to a message - def link_to_message(message, options={}, html_options = nil) - link_to( - h(truncate(message.subject, :length => 60)), - { :controller => 'messages', :action => 'show', - :board_id => message.board_id, - :id => (message.parent_id || message.id), - :r => (message.parent_id && message.id), - :anchor => (message.parent_id ? "message-#{message.id}" : nil) - }.merge(options), - html_options - ) - end - - # Generates a link to a project if active - # Examples: - # - # link_to_project(project) # => link to the specified project overview - # link_to_project(project, :action=>'settings') # => link to project settings - # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options - # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) - # - def link_to_project(project, options={}, html_options = nil) - if project.archived? - h(project) - else - url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) - link_to(h(project), url, html_options) - end - end - - def wiki_page_path(page, options={}) - url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options)) - end - - def thumbnail_tag(attachment) - link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)), - {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename}, - :title => attachment.filename - end - - def toggle_link(name, id, options={}) - onclick = "$('##{id}').toggle(); " - onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") - onclick << "return false;" - link_to(name, "#", :onclick => onclick) - end - - def image_to_function(name, function, html_options = {}) - html_options.symbolize_keys! - tag(:input, html_options.merge({ - :type => "image", :src => image_path(name), - :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" - })) - end - - def format_activity_title(text) - h(truncate_single_line(text, :length => 100)) - end - - def format_activity_day(date) - date == User.current.today ? l(:label_today).titleize : format_date(date) - end - - def format_activity_description(text) - h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') - ).gsub(/[\r\n]+/, "
").html_safe - end - - def format_version_name(version) - if version.project == @project - h(version) - else - h("#{version.project} - #{version}") - end - end - - def due_date_distance_in_words(date) - if date - l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) - end - end - - # 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_nested_lists(projects) - s = '' - if projects.any? - ancestors = [] - original_project = @project - projects.sort_by(&:lft).each do |project| - # set the project environment to please macros. - @project = project - if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) - s << "
    \n" - else - ancestors.pop - s << "" - while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) - ancestors.pop - s << "
\n" - end - end - classes = (ancestors.empty? ? 'root' : 'child') - s << "
  • " - s << h(block_given? ? yield(project) : project.name) - s << "
    \n" - ancestors << project - end - s << ("
  • \n" * ancestors.size) - @project = original_project - end - s.html_safe - end - - def render_page_hierarchy(pages, node=nil, options={}) - content = '' - if pages[node] - content << "
      \n" - pages[node].each do |page| - content << "
    • " - content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil}, - :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) - content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] - content << "
    • \n" - end - content << "
    \n" - end - content.html_safe - end - - # Renders flash messages - def render_flash_messages - s = '' - flash.each do |k,v| - s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") - end - s.html_safe - end - - # Renders tabs and their content - def render_tabs(tabs) - if tabs.any? - render :partial => 'common/tabs', :locals => {:tabs => tabs} - else - content_tag 'p', l(:label_no_data), :class => "nodata" - end - end - - # Renders the project quick-jump box - def render_project_jump_box - return unless User.current.logged? - projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq - if projects.any? - options = - ("" + - '').html_safe - - options << project_tree_options_for_select(projects, :selected => @project) do |p| - { :value => project_path(:id => p, :jump => current_menu_item) } - end - - select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') - end - end - - def project_tree_options_for_select(projects, options = {}) - s = '' - project_tree(projects) do |project, level| - name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe - tag_options = {:value => project.id} - if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) - tag_options[:selected] = 'selected' - else - tag_options[:selected] = nil - end - tag_options.merge!(yield(project)) if block_given? - s << content_tag('option', name_prefix + h(project), tag_options) - end - s.html_safe - end - - # Yields the given block for each project with its level in the tree - # - # Wrapper for Project#project_tree - def project_tree(projects, &block) - Project.project_tree(projects, &block) - end - - def principals_check_box_tags(name, principals) - s = '' - principals.sort.each do |principal| - s << "\n" - end - s.html_safe - end - - # Returns a string for users/groups option tags - def principals_options_for_select(collection, selected=nil) - s = '' - if collection.include?(User.current) - s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) - end - groups = '' - collection.sort.each do |element| - selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) - (element.is_a?(Group) ? groups : s) << %() - end - unless groups.empty? - s << %(#{groups}) - end - s.html_safe - end - - # Options for the new membership projects combo-box - def options_for_membership_project_select(principal, projects) - options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") - options << project_tree_options_for_select(projects) do |p| - {:disabled => principal.projects.include?(p)} - end - options - end - - # Truncates and returns the string as a single line - def truncate_single_line(string, *args) - truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') - end - - # Truncates at line break after 250 characters or options[:length] - def truncate_lines(string, options={}) - length = options[:length] || 250 - if string.to_s =~ /\A(.{#{length}}.*?)$/m - "#{$1}..." - else - string - end - end - - def anchor(text) - text.to_s.gsub(' ', '_') - end - - def html_hours(text) - text.gsub(%r{(\d+)\.(\d+)}, '\1.\2').html_safe - end - - def authoring(created, author, options={}) - l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe - end - - def time_tag(time) - text = distance_of_time_in_words(Time.now, time) - if @project - link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) - else - content_tag('acronym', text, :title => format_time(time)) - end - end - - def syntax_highlight_lines(name, content) - lines = [] - syntax_highlight(name, content).each_line { |line| lines << line } - lines - end - - def syntax_highlight(name, content) - Redmine::SyntaxHighlighting.highlight_by_filename(content, name) - end - - def to_path_param(path) - str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") - str.blank? ? nil : str - end - - def pagination_links_full(paginator, count=nil, options={}) - page_param = options.delete(:page_param) || :page - per_page_links = options.delete(:per_page_links) - url_param = params.dup - - html = '' - if paginator.current.previous - # \xc2\xab(utf-8) = « - html << link_to_content_update( - "\xc2\xab " + l(:label_previous), - url_param.merge(page_param => paginator.current.previous)) + ' ' - end - - html << (pagination_links_each(paginator, options) do |n| - link_to_content_update(n.to_s, url_param.merge(page_param => n)) - end || '') - - if paginator.current.next - # \xc2\xbb(utf-8) = » - html << ' ' + link_to_content_update( - (l(:label_next) + " \xc2\xbb"), - url_param.merge(page_param => paginator.current.next)) - end - - unless count.nil? - html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" - if per_page_links != false && links = per_page_links(paginator.items_per_page, count) - html << " | #{links}" - end - end - - html.html_safe - end - - def per_page_links(selected=nil, item_count=nil) - values = Setting.per_page_options_array - if item_count && values.any? - if item_count > values.first - max = values.detect {|value| value >= item_count} || item_count - else - max = item_count - end - values = values.select {|value| value <= max || value == selected} - end - if values.empty? || (values.size == 1 && values.first == selected) - return nil - end - links = values.collect do |n| - n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) - end - l(:label_display_per_page, links.join(', ')) - end - - def reorder_links(name, url, method = :post) - link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), - url.merge({"#{name}[move_to]" => 'highest'}), - :method => method, :title => l(:label_sort_highest)) + - link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), - url.merge({"#{name}[move_to]" => 'higher'}), - :method => method, :title => l(:label_sort_higher)) + - link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), - url.merge({"#{name}[move_to]" => 'lower'}), - :method => method, :title => l(:label_sort_lower)) + - link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), - url.merge({"#{name}[move_to]" => 'lowest'}), - :method => method, :title => l(:label_sort_lowest)) - end - - def breadcrumb(*args) - elements = args.flatten - elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil - end - - def other_formats_links(&block) - concat('

    '.html_safe + l(:label_export_to)) - yield Redmine::Views::OtherFormatsBuilder.new(self) - concat('

    '.html_safe) - end - - def page_header_title - if @project.nil? || @project.new_record? - h(Setting.app_title) - else - b = [] - ancestors = (@project.root? ? [] : @project.ancestors.visible.all) - if ancestors.any? - root = ancestors.shift - b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') - if ancestors.size > 2 - b << "\xe2\x80\xa6" - ancestors = ancestors[-2, 2] - end - b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } - end - b << h(@project) - b.join(" \xc2\xbb ").html_safe - end - end - - def html_title(*args) - if args.empty? - title = @html_title || [] - title << @project.name if @project - title << Setting.app_title unless Setting.app_title == title.last - title.select {|t| !t.blank? }.join(' - ') - else - @html_title ||= [] - @html_title += args - end - end - - # Returns the theme, controller name, and action as css classes for the - # HTML body. - def body_css_classes - css = [] - if theme = Redmine::Themes.theme(Setting.ui_theme) - css << 'theme-' + theme.name - end - - css << 'controller-' + controller_name - css << 'action-' + action_name - css.join(' ') - end - - def accesskey(s) - Redmine::AccessKeys.key_for s - end - - # Formats text according to system settings. - # 2 ways to call this method: - # * with a String: textilizable(text, options) - # * with an object and one of its attribute: textilizable(issue, :description, options) - def textilizable(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - case args.size - when 1 - obj = options[:object] - text = args.shift - when 2 - obj = args.shift - attr = args.shift - text = obj.send(attr).to_s - else - raise ArgumentError, 'invalid arguments to textilizable' - end - return '' if text.blank? - project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) - only_path = options.delete(:only_path) == false ? false : true - - text = text.dup - macros = catch_macros(text) - text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) - - @parsed_headings = [] - @heading_anchors = {} - @current_section = 0 if options[:edit_section_links] - - parse_sections(text, project, obj, attr, only_path, options) - text = parse_non_pre_blocks(text, obj, macros) do |text| - [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| - send method_name, text, project, obj, attr, only_path, options - end - end - parse_headings(text, project, obj, attr, only_path, options) - - if @parsed_headings.any? - replace_toc(text, @parsed_headings) - end - - text.html_safe - end - - def parse_non_pre_blocks(text, obj, macros) - s = StringScanner.new(text) - tags = [] - parsed = '' - while !s.eos? - s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) - text, full_tag, closing, tag = s[1], s[2], s[3], s[4] - if tags.empty? - yield text - inject_macros(text, obj, macros) if macros.any? - else - inject_macros(text, obj, macros, false) if macros.any? - end - parsed << text - if tag - if closing - if tags.last == tag.downcase - tags.pop - end - else - tags << tag.downcase - end - parsed << full_tag - end - end - # Close any non closing tags - while tag = tags.pop - parsed << "" - end - parsed - end - - def parse_inline_attachments(text, project, obj, attr, only_path, options) - # when using an image link, try to use an attachment, if possible - attachments = options[:attachments] || [] - attachments += obj.attachments if obj.respond_to?(:attachments) - if attachments.present? - text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| - filename, ext, alt, alttext = $1.downcase, $2, $3, $4 - # search for the picture in attachments - if found = Attachment.latest_attach(attachments, filename) - image_url = url_for :only_path => only_path, :controller => 'attachments', - :action => 'download', :id => found - desc = found.description.to_s.gsub('"', '') - if !desc.blank? && alttext.blank? - alt = " title=\"#{desc}\" alt=\"#{desc}\"" - end - "src=\"#{image_url}\"#{alt}" - else - m - end - end - end - end - - # Wiki links - # - # Examples: - # [[mypage]] - # [[mypage|mytext]] - # wiki links can refer other project wikis, using project name or identifier: - # [[project:]] -> wiki starting page - # [[project:|mytext]] - # [[project:mypage]] - # [[project:mypage|mytext]] - def parse_wiki_links(text, project, obj, attr, only_path, options) - text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| - link_project = project - esc, all, page, title = $1, $2, $3, $5 - if esc.nil? - if page =~ /^([^\:]+)\:(.*)$/ - link_project = Project.find_by_identifier($1) || Project.find_by_name($1) - page = $2 - title ||= $1 if page.blank? - end - - if link_project && link_project.wiki - # extract anchor - anchor = nil - if page =~ /^(.+?)\#(.+)$/ - page, anchor = $1, $2 - end - anchor = sanitize_anchor_name(anchor) if anchor.present? - # check if page exists - wiki_page = link_project.wiki.find_page(page) - url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page - "##{anchor}" - else - case options[:wiki_links] - when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') - when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export - else - wiki_page_id = page.present? ? Wiki.titleize(page) : nil - parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil - url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, - :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) - end - end - link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) - else - # project or wiki doesn't exist - all - end - else - all - end - end - end - - # Redmine links - # - # Examples: - # Issues: - # #52 -> Link to issue #52 - # Changesets: - # r52 -> Link to revision 52 - # commit:a85130f -> Link to scmid starting with a85130f - # Documents: - # document#17 -> Link to document with id 17 - # document:Greetings -> Link to the document with title "Greetings" - # document:"Some document" -> Link to the document with title "Some document" - # Versions: - # version#3 -> Link to version with id 3 - # version:1.0.0 -> Link to version named "1.0.0" - # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" - # Attachments: - # attachment:file.zip -> Link to the attachment of the current object named file.zip - # Source files: - # source:some/file -> Link to the file located at /some/file in the project's repository - # source:some/file@52 -> Link to the file's revision 52 - # source:some/file#L120 -> Link to line 120 of the file - # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 - # export:some/file -> Force the download of the file - # Forum messages: - # message#1218 -> Link to message with id 1218 - # - # Links can refer other objects from other projects, using project identifier: - # identifier:r52 - # identifier:document:"Some document" - # identifier:version:1.0.0 - # identifier:source:some/file - def parse_redmine_links(text, default_project, obj, attr, only_path, options) - text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| - leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 - link = nil - project = default_project - if project_identifier - project = Project.visible.find_by_identifier(project_identifier) - end - if esc.nil? - if prefix.nil? && sep == 'r' - if project - repository = nil - if repo_identifier - repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} - else - repository = project.repository - end - # project.changesets.visible raises an SQL error because of a double join on repositories - if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) - link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, - :class => 'changeset', - :title => truncate_single_line(changeset.comments, :length => 100)) - end - end - elsif sep == '#' - oid = identifier.to_i - case prefix - when nil - if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) - anchor = comment_id ? "note-#{comment_id}" : nil - link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, - :class => issue.css_classes, - :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") - end - when 'document' - if document = Document.visible.find_by_id(oid) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if version = Version.visible.find_by_id(oid) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'message' - if message = Message.visible.find_by_id(oid, :include => :parent) - link = link_to_message(message, {:only_path => only_path}, :class => 'message') - end - when 'forum' - if board = Board.visible.find_by_id(oid) - link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, - :class => 'board' - end - when 'news' - if news = News.visible.find_by_id(oid) - link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, - :class => 'news' - end - when 'project' - if p = Project.visible.find_by_id(oid) - link = link_to_project(p, {:only_path => only_path}, :class => 'project') - end - end - elsif sep == ':' - # removes the double quotes if any - name = identifier.gsub(%r{^"(.*)"$}, "\\1") - case prefix - when 'document' - if project && document = project.documents.visible.find_by_title(name) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if project && version = project.versions.visible.find_by_name(name) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'forum' - if project && board = project.boards.visible.find_by_name(name) - link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, - :class => 'board' - end - when 'news' - if project && news = project.news.visible.find_by_title(name) - link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, - :class => 'news' - end - when 'commit', 'source', 'export' - if project - repository = nil - if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} - repo_prefix, repo_identifier, name = $1, $2, $3 - repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} - else - repository = project.repository - end - if prefix == 'commit' - if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"])) - link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, - :class => 'changeset', - :title => truncate_single_line(h(changeset.comments), :length => 100) - end - else - if repository && User.current.allowed_to?(:browse_repository, project) - name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} - path, rev, anchor = $1, $3, $5 - link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, - :path => to_path_param(path), - :rev => rev, - :anchor => anchor}, - :class => (prefix == 'export' ? 'source download' : 'source') - end - end - repo_prefix = nil - end - when 'attachment' - attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) - if attachments && attachment = Attachment.latest_attach(attachments, name) - link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, - :class => 'attachment' - end - when 'project' - if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) - link = link_to_project(p, {:only_path => only_path}, :class => 'project') - end - end - end - end - (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) - end - end - - HEADING_RE = /(]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) - - def parse_sections(text, project, obj, attr, only_path, options) - return unless options[:edit_section_links] - text.gsub!(HEADING_RE) do - heading = $1 - @current_section += 1 - if @current_section > 1 - content_tag('div', - link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), - :class => 'contextual', - :title => l(:button_edit_section)) + heading.html_safe - else - heading - end - end - end - - # Headings and TOC - # Adds ids and links to headings unless options[:headings] is set to false - def parse_headings(text, project, obj, attr, only_path, options) - return if options[:headings] == false - - text.gsub!(HEADING_RE) do - level, attrs, content = $2.to_i, $3, $4 - item = strip_tags(content).strip - anchor = sanitize_anchor_name(item) - # used for single-file wiki export - anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) - @heading_anchors[anchor] ||= 0 - idx = (@heading_anchors[anchor] += 1) - if idx > 1 - anchor = "#{anchor}-#{idx}" - end - @parsed_headings << [level, anchor, item] - "\n#{content}" - end - end - - MACROS_RE = /( - (!)? # escaping - ( - \{\{ # opening tag - ([\w]+) # macro name - (\(([^\n\r]*?)\))? # optional arguments - ([\n\r].*?[\n\r])? # optional block of text - \}\} # closing tag - ) - )/mx unless const_defined?(:MACROS_RE) - - MACRO_SUB_RE = /( - \{\{ - macro\((\d+)\) - \}\} - )/x unless const_defined?(:MACRO_SUB_RE) - - # Extracts macros from text - def catch_macros(text) - macros = {} - text.gsub!(MACROS_RE) do - all, macro = $1, $4.downcase - if macro_exists?(macro) || all =~ MACRO_SUB_RE - index = macros.size - macros[index] = all - "{{macro(#{index})}}" - else - all - end - end - macros - end - - # Executes and replaces macros in text - def inject_macros(text, obj, macros, execute=true) - text.gsub!(MACRO_SUB_RE) do - all, index = $1, $2.to_i - orig = macros.delete(index) - if execute && orig && orig =~ MACROS_RE - esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) - if esc.nil? - h(exec_macro(macro, obj, args, block) || all) - else - h(all) - end - elsif orig - h(orig) - else - h(all) - end - end - end - - TOC_RE = /

    \{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) - - # Renders the TOC with given headings - def replace_toc(text, headings) - text.gsub!(TOC_RE) do - # Keep only the 4 first levels - headings = headings.select{|level, anchor, item| level <= 4} - if headings.empty? - '' - else - div_class = 'toc' - div_class << ' right' if $1 == '>' - div_class << ' left' if $1 == '<' - out = "

    • " - root = headings.map(&:first).min - current = root - started = false - headings.each do |level, anchor, item| - if level > current - out << '
      • ' * (level - current) - elsif level < current - out << "
      \n" * (current - level) + "
    • " - elsif started - out << '
    • ' - end - out << "#{item}" - current = level - started = true - end - out << '
    ' * (current - root) - out << '' - end - end - end - - # Same as Rails' simple_format helper without using paragraphs - def simple_format_without_paragraph(text) - text.to_s. - gsub(/\r\n?/, "\n"). # \r\n and \r -> \n - gsub(/\n\n+/, "

    "). # 2+ newline -> 2 br - gsub(/([^\n]\n)(?=[^\n])/, '\1
    '). # 1 newline -> br - html_safe - end - - def lang_options_for_select(blank=true) - (blank ? [["(auto)", ""]] : []) + languages_options - end - - def label_tag_for(name, option_tags = nil, options = {}) - label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") - content_tag("label", label_text) - end - - def labelled_form_for(*args, &proc) - args << {} unless args.last.is_a?(Hash) - options = args.last - if args.first.is_a?(Symbol) - options.merge!(:as => args.shift) - end - options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) - form_for(*args, &proc) - end - - def labelled_fields_for(*args, &proc) - args << {} unless args.last.is_a?(Hash) - options = args.last - options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) - fields_for(*args, &proc) - end - - def labelled_remote_form_for(*args, &proc) - ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." - args << {} unless args.last.is_a?(Hash) - options = args.last - options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true}) - form_for(*args, &proc) - end - - def error_messages_for(*objects) - html = "" - objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact - errors = objects.map {|o| o.errors.full_messages}.flatten - if errors.any? - html << "
      \n" - errors.each do |error| - html << "
    • #{h error}
    • \n" - end - html << "
    \n" - end - html.html_safe - end - - def delete_link(url, options={}) - options = { - :method => :delete, - :data => {:confirm => l(:text_are_you_sure)}, - :class => 'icon icon-del' - }.merge(options) - - link_to l(:button_delete), url, options - end - - def preview_link(url, form, target='preview', options={}) - content_tag 'a', l(:label_preview), { - :href => "#", - :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, - :accesskey => accesskey(:preview) - }.merge(options) - end - - def link_to_function(name, function, html_options={}) - content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) - end - - # Helper to render JSON in views - def raw_json(arg) - arg.to_json.to_s.gsub('/', '\/').html_safe - end - - def back_url - url = params[:back_url] - if url.nil? && referer = request.env['HTTP_REFERER'] - url = CGI.unescape(referer.to_s) - end - url - end - - def back_url_hidden_field_tag - url = back_url - hidden_field_tag('back_url', url, :id => nil) unless url.blank? - end - - def check_all_links(form_name) - link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + - " | ".html_safe + - link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") - end - - def progress_bar(pcts, options={}) - pcts = [pcts, pcts] unless pcts.is_a?(Array) - pcts = pcts.collect(&:round) - pcts[1] = pcts[1] - pcts[0] - pcts << (100 - pcts[1] - pcts[0]) - width = options[:width] || '100px;' - legend = options[:legend] || '' - content_tag('table', - content_tag('tr', - (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + - (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + - (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) - ), :class => 'progress', :style => "width: #{width};").html_safe + - content_tag('p', legend, :class => 'pourcent').html_safe - end - - def checked_image(checked=true) - if checked - image_tag 'toggle_check.png' - end - end - - def context_menu(url) - unless @context_menu_included - content_for :header_tags do - javascript_include_tag('context_menu') + - stylesheet_link_tag('context_menu') - end - if l(:direction) == 'rtl' - content_for :header_tags do - stylesheet_link_tag('context_menu_rtl') - end - end - @context_menu_included = true - end - javascript_tag "contextMenuInit('#{ url_for(url) }')" - end - - def calendar_for(field_id) - include_calendar_headers_tags - javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") - end - - def include_calendar_headers_tags - unless @calendar_headers_tags_included - @calendar_headers_tags_included = true - content_for :header_tags do - start_of_week = Setting.start_of_week - start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? - # Redmine uses 1..7 (monday..sunday) in settings and locales - # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 - start_of_week = start_of_week.to_i % 7 - - tags = javascript_tag( - "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + - "showOn: 'button', buttonImageOnly: true, buttonImage: '" + - path_to_image('/images/calendar.png') + - "', showButtonPanel: true};") - jquery_locale = l('jquery.locale', :default => current_language.to_s) - unless jquery_locale == 'en' - tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") - end - tags - end - end - end - - # Overrides Rails' stylesheet_link_tag with themes and plugins support. - # Examples: - # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults - # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets - # - def stylesheet_link_tag(*sources) - options = sources.last.is_a?(Hash) ? sources.pop : {} - plugin = options.delete(:plugin) - sources = sources.map do |source| - if plugin - "/plugin_assets/#{plugin}/stylesheets/#{source}" - elsif current_theme && current_theme.stylesheets.include?(source) - current_theme.stylesheet_path(source) - else - source - end - end - super sources, options - end - - # Overrides Rails' image_tag with themes and plugins support. - # Examples: - # image_tag('image.png') # => picks image.png from the current theme or defaults - # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets - # - def image_tag(source, options={}) - if plugin = options.delete(:plugin) - source = "/plugin_assets/#{plugin}/images/#{source}" - elsif current_theme && current_theme.images.include?(source) - source = current_theme.image_path(source) - end - super source, options - end - - # Overrides Rails' javascript_include_tag with plugins support - # Examples: - # javascript_include_tag('scripts') # => picks scripts.js from defaults - # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets - # - def javascript_include_tag(*sources) - options = sources.last.is_a?(Hash) ? sources.pop : {} - if plugin = options.delete(:plugin) - sources = sources.map do |source| - if plugin - "/plugin_assets/#{plugin}/javascripts/#{source}" - else - source - end - end - end - super sources, options - end - - def content_for(name, content = nil, &block) - @has_content ||= {} - @has_content[name] = true - super(name, content, &block) - end - - def has_content?(name) - (@has_content && @has_content[name]) || false - end - - def sidebar_content? - has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present? - end - - def view_layouts_base_sidebar_hook_response - @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) - end - - def email_delivery_enabled? - !!ActionMailer::Base.perform_deliveries - end - - # Returns the avatar image tag for the given +user+ if avatars are enabled - # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe ') - def avatar(user, options = { }) - if Setting.gravatar_enabled? - options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) - email = nil - if user.respond_to?(:mail) - email = user.mail - elsif user.to_s =~ %r{<(.+?)>} - email = $1 - end - return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil - else - '' - end - end - - def sanitize_anchor_name(anchor) - if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' - anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') - else - # TODO: remove when ruby1.8 is no longer supported - anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') - end - end - - # Returns the javascript tags that are included in the html layout head - def javascript_heads - tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application') - unless User.current.pref.warn_on_leaving_unsaved == '0' - tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") - end - tags - end - - def favicon - "".html_safe - end - - def robot_exclusion_tag - ''.html_safe - end - - # Returns true if arg is expected in the API response - def include_in_api_response?(arg) - unless @included_in_api_response - param = params[:include] - @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') - @included_in_api_response.collect!(&:strip) - end - @included_in_api_response.include?(arg.to_s) - end - - # Returns options or nil if nometa param or X-Redmine-Nometa header - # was set in the request - def api_meta(options) - if params[:nometa].present? || request.headers['X-Redmine-Nometa'] - # compatibility mode for activeresource clients that raise - # an error when unserializing an array with attributes - nil - else - options - end - end - - private - - def wiki_helper - helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) - extend helper - return self - end - - def link_to_content_update(text, url_params = {}, html_options = {}) - link_to(text, url_params, html_options) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/04/04dfe98ce0a611eee30923688999ac146c40ca35.svn-base --- a/.svn/pristine/04/04dfe98ce0a611eee30923688999ac146c40ca35.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Principal < ActiveRecord::Base - self.table_name = "#{table_name_prefix}users#{table_name_suffix}" - - has_many :members, :foreign_key => 'user_id', :dependent => :destroy - has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}", :order => "#{Project.table_name}.name" - has_many :projects, :through => :memberships - has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify - - # Groups and active users - scope :active, :conditions => "#{Principal.table_name}.status = 1" - - scope :like, lambda {|q| - q = q.to_s - if q.blank? - where({}) - else - pattern = "%#{q}%" - sql = %w(login firstname lastname mail).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ") - params = {:p => pattern} - if q =~ /^(.+)\s+(.+)$/ - a, b = "#{$1}%", "#{$2}%" - sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))" - sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))" - params.merge!(:a => a, :b => b) - end - where(sql, params) - end - } - - # Principals that are members of a collection of projects - scope :member_of, lambda {|projects| - projects = [projects] unless projects.is_a?(Array) - if projects.empty? - where("1=0") - else - ids = projects.map(&:id) - where("#{Principal.table_name}.status = 1 AND #{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids) - end - } - # Principals that are not members of projects - scope :not_member_of, lambda {|projects| - projects = [projects] unless projects.is_a?(Array) - if projects.empty? - where("1=0") - else - ids = projects.map(&:id) - where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids) - end - } - - before_create :set_default_empty_values - - def name(formatter = nil) - to_s - end - - def <=>(principal) - if principal.nil? - -1 - elsif self.class.name == principal.class.name - self.to_s.downcase <=> principal.to_s.downcase - else - # groups after users - principal.class.name <=> self.class.name - end - end - - protected - - # Make sure we don't try to insert NULL values (see #4632) - def set_default_empty_values - self.login ||= '' - self.hashed_password ||= '' - self.firstname ||= '' - self.lastname ||= '' - self.mail ||= '' - true - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/05/0513c24a062b523037219d9978aab2b119808571.svn-base --- a/.svn/pristine/05/0513c24a062b523037219d9978aab2b119808571.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -<%= form_for @project, - :url => { :action => 'modules', :id => @project }, - :html => {:id => 'modules-form', - :method => :post} do |f| %> - -
    -
    -<%= l(:text_select_project_modules) %> - -<% Redmine::AccessControl.available_project_modules.each do |m| %> -

    -<% end %> -
    -
    - -

    <%= check_all_links 'modules-form' %>

    -

    <%= submit_tag l(:button_save) %>

    - -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/05/0539624edaa056ab07935479230d09abf0794955.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/05/0539624edaa056ab07935479230d09abf0794955.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,28 @@ +<% selected_tab = params[:tab] ? params[:tab].to_s : tabs.first[:name] %> + +
    +
      + <% tabs.each do |tab| -%> +
    • <%= link_to l(tab[:label]), { :tab => tab[:name] }, + :id => "tab-#{tab[:name]}", + :class => (tab[:name] != selected_tab ? nil : 'selected'), + :onclick => "showTab('#{tab[:name]}', this.href); this.blur(); return false;" %>
    • + <% end -%> +
    + +
    + + + +<% tabs.each do |tab| -%> + <%= content_tag('div', render(:partial => tab[:partial], :locals => {:tab => tab} ), + :id => "tab-content-#{tab[:name]}", + :style => (tab[:name] != selected_tab ? 'display:none' : nil), + :class => 'tab-content') %> +<% end -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/05/0548f38a902595a77747491d864f451fe12f0f47.svn-base --- a/.svn/pristine/05/0548f38a902595a77747491d864f451fe12f0f47.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,346 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -RedmineApp::Application.routes.draw do - root :to => 'welcome#index', :as => 'home' - - match 'login', :to => 'account#login', :as => 'signin' - match 'logout', :to => 'account#logout', :as => 'signout' - match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register' - match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password' - match 'account/activate', :to => 'account#activate', :via => :get - - match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news' - match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue' - match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue' - match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue' - - match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post - match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post] - - match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post] - get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message' - match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post] - get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' - - post 'boards/:board_id/topics/preview', :to => 'messages#preview' - post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply' - post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' - post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' - - # Misc issue routes. TODO: move into resources - match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' - match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu' - match '/issues/changes', :to => 'journals#index', :as => 'issue_changes' - match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue' - - match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get - match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post] - - match '/projects/:project_id/issues/gantt', :to => 'gantts#show' - match '/issues/gantt', :to => 'gantts#show' - - match '/projects/:project_id/issues/calendar', :to => 'calendars#show' - match '/issues/calendar', :to => 'calendars#show' - - match 'projects/:id/issues/report', :to => 'reports#issue_report', :via => :get - match 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :via => :get - - match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post] - match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post] - match 'my/page', :controller => 'my', :action => 'page', :via => :get - match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page - match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post - match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post - match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post] - match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get - match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post - match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post - match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post - - resources :users - match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership' - match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete - match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships' - - match 'watchers/new', :controller=> 'watchers', :action => 'new', :via => :get - match 'watchers', :controller=> 'watchers', :action => 'create', :via => :post - match 'watchers/append', :controller=> 'watchers', :action => 'append', :via => :post - match 'watchers/destroy', :controller=> 'watchers', :action => 'destroy', :via => :post - match 'watchers/watch', :controller=> 'watchers', :action => 'watch', :via => :post - match 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch', :via => :post - match 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :via => :get - - match 'projects/:id/settings/:tab', :to => "projects#settings" - - resources :projects do - member do - get 'settings' - post 'modules' - post 'archive' - post 'unarchive' - post 'close' - post 'reopen' - match 'copy', :via => [:get, :post] - end - - resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do - collection do - get 'autocomplete' - end - end - - resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy] - - match 'issues/:copy_from/copy', :to => 'issues#new' - resources :issues, :only => [:index, :new, :create] do - resources :time_entries, :controller => 'timelog' do - collection do - get 'report' - end - end - end - # issue form update - match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form' - - resources :files, :only => [:index, :new, :create] - - resources :versions, :except => [:index, :show, :edit, :update, :destroy] do - collection do - put 'close_completed' - end - end - match 'versions.:format', :to => 'versions#index' - match 'roadmap', :to => 'versions#index', :format => false - match 'versions', :to => 'versions#index' - - resources :news, :except => [:show, :edit, :update, :destroy] - resources :time_entries, :controller => 'timelog' do - get 'report', :on => :collection - end - resources :queries, :only => [:new, :create] - resources :issue_categories, :shallow => true - resources :documents, :except => [:show, :edit, :update, :destroy] - resources :boards - resources :repositories, :shallow => true, :except => [:index, :show] do - member do - match 'committers', :via => [:get, :post] - end - end - - match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get - resources :wiki, :except => [:index, :new, :create] do - member do - get 'rename' - post 'rename' - get 'history' - get 'diff' - match 'preview', :via => [:post, :put] - post 'protect' - post 'add_attachment' - end - collection do - get 'export' - get 'date_index' - end - end - match 'wiki', :controller => 'wiki', :action => 'show', :via => :get - get 'wiki/:id/:version', :to => 'wiki#show' - delete 'wiki/:id/:version', :to => 'wiki#destroy_version' - get 'wiki/:id/:version/annotate', :to => 'wiki#annotate' - get 'wiki/:id/:version/diff', :to => 'wiki#diff' - end - - resources :issues do - collection do - match 'bulk_edit', :via => [:get, :post] - post 'bulk_update' - end - resources :time_entries, :controller => 'timelog' do - collection do - get 'report' - end - end - resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy] - end - match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete - - resources :queries, :except => [:show] - - resources :news, :only => [:index, :show, :edit, :update, :destroy] - match '/news/:id/comments', :to => 'comments#create', :via => :post - match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete - - resources :versions, :only => [:show, :edit, :update, :destroy] do - post 'status_by', :on => :member - end - - resources :documents, :only => [:show, :edit, :update, :destroy] do - post 'add_attachment', :on => :member - end - - match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu - - resources :time_entries, :controller => 'timelog', :except => :destroy do - collection do - get 'report' - get 'bulk_edit' - post 'bulk_update' - end - end - match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/ - # TODO: delete /time_entries for bulk deletion - match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete - - # TODO: port to be part of the resources route(s) - match 'projects/:id/settings/:tab', :to => 'projects#settings', :via => :get - - get 'projects/:id/activity', :to => 'activities#index' - get 'projects/:id/activity.:format', :to => 'activities#index' - get 'activity', :to => 'activities#index' - - # repositories routes - get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats' - get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph' - - get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))', - :to => 'repositories#changes' - - get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision' - get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision' - post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue' - delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue' - get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions' - get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))', - :controller => 'repositories', - :format => false, - :constraints => { - :action => /(browse|show|entry|raw|annotate|diff)/, - :rev => /[a-z0-9\.\-_]+/ - } - - get 'projects/:id/repository/statistics', :to => 'repositories#stats' - get 'projects/:id/repository/graph', :to => 'repositories#graph' - - get 'projects/:id/repository/changes(/*path(.:ext))', - :to => 'repositories#changes' - - get 'projects/:id/repository/revisions', :to => 'repositories#revisions' - get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision' - get 'projects/:id/repository/revision', :to => 'repositories#revision' - post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue' - delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue' - get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))', - :controller => 'repositories', - :format => false, - :constraints => { - :action => /(browse|show|entry|raw|annotate|diff)/, - :rev => /[a-z0-9\.\-_]+/ - } - get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))', - :controller => 'repositories', - :action => /(browse|show|entry|raw|changes|annotate|diff)/ - get 'projects/:id/repository/:action(/*path(.:ext))', - :controller => 'repositories', - :action => /(browse|show|entry|raw|changes|annotate|diff)/ - - get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil - get 'projects/:id/repository', :to => 'repositories#show', :path => nil - - # additional routes for having the file name at the end of url - match 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/, :via => :get - match 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/, :via => :get - match 'attachments/download/:id', :controller => 'attachments', :action => 'download', :id => /\d+/, :via => :get - match 'attachments/thumbnail/:id(/:size)', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get, :size => /\d+/ - resources :attachments, :only => [:show, :destroy] - - resources :groups do - member do - get 'autocomplete_for_user' - end - end - - match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users' - match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user' - match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post - match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post - - resources :trackers, :except => :show do - collection do - match 'fields', :via => [:get, :post] - end - end - resources :issue_statuses, :except => :show do - collection do - post 'update_issue_done_ratio' - end - end - resources :custom_fields, :except => :show - resources :roles do - collection do - match 'permissions', :via => [:get, :post] - end - end - resources :enumerations, :except => :show - match 'enumerations/:type', :to => 'enumerations#index', :via => :get - - get 'projects/:id/search', :controller => 'search', :action => 'index' - get 'search', :controller => 'search', :action => 'index' - - match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post - - match 'admin', :controller => 'admin', :action => 'index', :via => :get - match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get - match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get - match 'admin/info', :controller => 'admin', :action => 'info', :via => :get - match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get - match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post - - resources :auth_sources do - member do - get 'test_connection' - end - end - - match 'workflows', :controller => 'workflows', :action => 'index', :via => :get - match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post] - match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post] - match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post] - match 'settings', :controller => 'settings', :action => 'index', :via => :get - match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post] - match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post] - - match 'sys/projects', :to => 'sys#projects', :via => :get - match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post - match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get - - match 'uploads', :to => 'attachments#upload', :via => :post - - get 'robots.txt', :to => 'welcome#robots' - - Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir| - file = File.join(plugin_dir, "config/routes.rb") - if File.exists?(file) - begin - instance_eval File.read(file) - rescue Exception => e - puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}." - exit 1 - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/05/05764e8ea551fb41de1ac455c66cb6f2907259b8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/05/05764e8ea551fb41de1ac455c66cb6f2907259b8.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,22 @@ +/* Portuguese initialisation for the jQuery UI date picker plugin. */ +jQuery(function($){ + $.datepicker.regional['pt'] = { + closeText: 'Fechar', + prevText: '<Anterior', + nextText: 'Seguinte', + currentText: 'Hoje', + monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho', + 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], + monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun', + 'Jul','Ago','Set','Out','Nov','Dez'], + dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'], + dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], + dayNamesMin: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], + weekHeader: 'Sem', + dateFormat: 'dd/mm/yy', + firstDay: 0, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['pt']); +}); diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/05/05bd9a477032dec0ee79cadb6f6626cf79ccae69.svn-base --- a/.svn/pristine/05/05bd9a477032dec0ee79cadb6f6626cf79ccae69.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,431 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingRepositoriesTest < ActionController::IntegrationTest - def setup - @path_hash = repository_path_hash(%w[path to file.c]) - assert_equal "path/to/file.c", @path_hash[:path] - assert_equal "path/to/file.c", @path_hash[:param] - end - - def test_repositories_resources - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repositories/new" }, - { :controller => 'repositories', :action => 'new', :project_id => 'redmine' } - ) - assert_routing( - { :method => 'post', - :path => "/projects/redmine/repositories" }, - { :controller => 'repositories', :action => 'create', :project_id => 'redmine' } - ) - assert_routing( - { :method => 'get', - :path => "/repositories/1/edit" }, - { :controller => 'repositories', :action => 'edit', :id => '1' } - ) - assert_routing( - { :method => 'put', - :path => "/repositories/1" }, - { :controller => 'repositories', :action => 'update', :id => '1' } - ) - assert_routing( - { :method => 'delete', - :path => "/repositories/1" }, - { :controller => 'repositories', :action => 'destroy', :id => '1' } - ) - ["get", "post"].each do |method| - assert_routing( - { :method => method, - :path => "/repositories/1/committers" }, - { :controller => 'repositories', :action => 'committers', :id => '1' } - ) - end - end - - def test_repositories_show - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine' } - ) - end - - def test_repositories - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/statistics" }, - { :controller => 'repositories', :action => 'stats', :id => 'redmine' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/graph" }, - { :controller => 'repositories', :action => 'graph', :id => 'redmine' } - ) - end - - def test_repositories_show_with_repository_id - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', :repository_id => 'foo' } - ) - end - - def test_repositories_with_repository_id - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/statistics" }, - { :controller => 'repositories', :action => 'stats', :id => 'redmine', :repository_id => 'foo' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/graph" }, - { :controller => 'repositories', :action => 'graph', :id => 'redmine', :repository_id => 'foo' } - ) - end - - def test_repositories_revisions - empty_path_param = [] - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions" }, - { :controller => 'repositories', :action => 'revisions', :id => 'redmine' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions.atom" }, - { :controller => 'repositories', :action => 'revisions', :id => 'redmine', - :format => 'atom' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457" }, - { :controller => 'repositories', :action => 'revision', :id => 'redmine', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457/show" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457/show/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', - :path => @path_hash[:param] , :rev => '2457'} - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457/diff" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457/diff" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :rev => '2457', :format => 'diff' }, - {}, - { :format => 'diff' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :path => @path_hash[:param], :rev => '2', :format => 'diff' }, - {}, - { :format => 'diff' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/entry/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'entry', :id => 'redmine', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/raw/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'raw', :id => 'redmine', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/annotate/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'annotate', :id => 'redmine', - :path => @path_hash[:param], :rev => '2' } - ) - end - - def test_repositories_revisions_with_repository_id - empty_path_param = [] - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions" }, - { :controller => 'repositories', :action => 'revisions', :id => 'redmine', :repository_id => 'foo' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions.atom" }, - { :controller => 'repositories', :action => 'revisions', :id => 'redmine', :repository_id => 'foo', - :format => 'atom' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457" }, - { :controller => 'repositories', :action => 'revision', :id => 'redmine', :repository_id => 'foo', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457/show" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', :repository_id => 'foo', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457/show/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] , :rev => '2457'} - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457/diff" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457/diff" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :rev => '2457', :format => 'diff' }, - {}, - { :format => 'diff' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2', :format => 'diff' }, - {}, - { :format => 'diff' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/entry/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'entry', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/raw/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'raw', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/annotate/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'annotate', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2' } - ) - end - - def test_repositories_non_revisions_path - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/changes" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine' } - ) - ['2457', 'master', 'slash/slash'].each do |rev| - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/changes" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', - :rev => rev }, - {}, - { :rev => rev } - ) - end - ['2457', 'master', 'slash/slash'].each do |rev| - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/changes/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', - :path => @path_hash[:param], :rev => rev }, - {}, - { :rev => rev } - ) - end - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/browse/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'browse', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/entry/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'entry', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/raw/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'raw', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/annotate/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'annotate', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/changes/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revision" }, - { :controller => 'repositories', :action => 'revision', :id => 'redmine' } - ) - end - - def test_repositories_non_revisions_path_with_repository_id - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/changes" }, - { :controller => 'repositories', :action => 'changes', - :id => 'redmine', :repository_id => 'foo' } - ) - ['2457', 'master', 'slash/slash'].each do |rev| - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/changes" }, - { :controller => 'repositories', :action => 'changes', - :id => 'redmine', - :repository_id => 'foo', :rev => rev }, - {}, - { :rev => rev } - ) - end - ['2457', 'master', 'slash/slash'].each do |rev| - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/changes/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', - :repository_id => 'foo', :path => @path_hash[:param], :rev => rev }, - {}, - { :rev => rev } - ) - end - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/browse/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'browse', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/entry/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'entry', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/raw/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'raw', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/annotate/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'annotate', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/changes/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revision" }, - { :controller => 'repositories', :action => 'revision', :id => 'redmine', :repository_id => 'foo'} - ) - end - - def test_repositories_related_issues - assert_routing( - { :method => 'post', - :path => "/projects/redmine/repository/revisions/123/issues" }, - { :controller => 'repositories', :action => 'add_related_issue', - :id => 'redmine', :rev => '123' } - ) - assert_routing( - { :method => 'delete', - :path => "/projects/redmine/repository/revisions/123/issues/25" }, - { :controller => 'repositories', :action => 'remove_related_issue', - :id => 'redmine', :rev => '123', :issue_id => '25' } - ) - end - - def test_repositories_related_issues_with_repository_id - assert_routing( - { :method => 'post', - :path => "/projects/redmine/repository/foo/revisions/123/issues" }, - { :controller => 'repositories', :action => 'add_related_issue', - :id => 'redmine', :repository_id => 'foo', :rev => '123' } - ) - assert_routing( - { :method => 'delete', - :path => "/projects/redmine/repository/foo/revisions/123/issues/25" }, - { :controller => 'repositories', :action => 'remove_related_issue', - :id => 'redmine', :repository_id => 'foo', :rev => '123', :issue_id => '25' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/05/05c2095e86e69b0ded063e085ccf41b6310d9ac0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/05/05c2095e86e69b0ded063e085ccf41b6310d9ac0.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,3 @@ +<%= title l(:label_custom_field_plural) %> + +<%= render_tabs custom_fields_tabs %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/06/06191abb72622bec9f1387de935e8ce335158e32.svn-base --- a/.svn/pristine/06/06191abb72622bec9f1387de935e8ce335158e32.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,255 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class AttachmentTest < ActiveSupport::TestCase - fixtures :users, :projects, :roles, :members, :member_roles, - :enabled_modules, :issues, :trackers, :attachments - - class MockFile - attr_reader :original_filename, :content_type, :content, :size - - def initialize(attributes) - @original_filename = attributes[:original_filename] - @content_type = attributes[:content_type] - @content = attributes[:content] || "Content" - @size = content.size - end - end - - def setup - set_tmp_attachments_directory - end - - def test_container_for_new_attachment_should_be_nil - assert_nil Attachment.new.container - end - - def test_create - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - assert a.save - assert_equal 'testfile.txt', a.filename - assert_equal 59, a.filesize - assert_equal 'text/plain', a.content_type - assert_equal 0, a.downloads - assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest - assert File.exist?(a.diskfile) - assert_equal 59, File.size(a.diskfile) - end - - def test_size_should_be_validated_for_new_file - with_settings :attachment_max_size => 0 do - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - assert !a.save - end - end - - def test_size_should_not_be_validated_when_copying - a = Attachment.create!(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - with_settings :attachment_max_size => 0 do - copy = a.copy - assert copy.save - end - end - - def test_description_length_should_be_validated - a = Attachment.new(:description => 'a' * 300) - assert !a.save - assert_not_nil a.errors[:description] - end - - def test_destroy - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - assert a.save - assert_equal 'testfile.txt', a.filename - assert_equal 59, a.filesize - assert_equal 'text/plain', a.content_type - assert_equal 0, a.downloads - assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest - diskfile = a.diskfile - assert File.exist?(diskfile) - assert_equal 59, File.size(a.diskfile) - assert a.destroy - assert !File.exist?(diskfile) - end - - def test_destroy_should_not_delete_file_referenced_by_other_attachment - a = Attachment.create!(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - diskfile = a.diskfile - - copy = a.copy - copy.save! - - assert File.exists?(diskfile) - a.destroy - assert File.exists?(diskfile) - copy.destroy - assert !File.exists?(diskfile) - end - - def test_create_should_auto_assign_content_type - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", ""), - :author => User.find(1)) - assert a.save - assert_equal 'text/plain', a.content_type - end - - def test_identical_attachments_at_the_same_time_should_not_overwrite - a1 = Attachment.create!(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", ""), - :author => User.find(1)) - a2 = Attachment.create!(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", ""), - :author => User.find(1)) - assert a1.disk_filename != a2.disk_filename - end - - def test_filename_should_be_basenamed - a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file")) - assert_equal 'file', a.filename - end - - def test_filename_should_be_sanitized - a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars")) - assert_equal 'valid_[] invalid_chars', a.filename - end - - def test_diskfilename - assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/ - assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1] - assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentué.txt")[13..-1] - assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentué")[13..-1] - assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentué.ça")[13..-1] - end - - def test_title - a = Attachment.new(:filename => "test.png") - assert_equal "test.png", a.title - - a = Attachment.new(:filename => "test.png", :description => "Cool image") - assert_equal "test.png (Cool image)", a.title - end - - def test_prune_should_destroy_old_unattached_attachments - Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago) - Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago) - Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1) - - assert_difference 'Attachment.count', -2 do - Attachment.prune - end - end - - context "Attachmnet.attach_files" do - should "attach the file" do - issue = Issue.first - assert_difference 'Attachment.count' do - Attachment.attach_files(issue, - '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain'), - 'description' => 'test' - }) - end - - attachment = Attachment.first(:order => 'id DESC') - assert_equal issue, attachment.container - assert_equal 'testfile.txt', attachment.filename - assert_equal 59, attachment.filesize - assert_equal 'test', attachment.description - assert_equal 'text/plain', attachment.content_type - assert File.exists?(attachment.diskfile) - assert_equal 59, File.size(attachment.diskfile) - end - - should "add unsaved files to the object as unsaved attachments" do - # Max size of 0 to force Attachment creation failures - with_settings(:attachment_max_size => 0) do - @project = Project.find(1) - response = Attachment.attach_files(@project, { - '1' => {'file' => mock_file, 'description' => 'test'}, - '2' => {'file' => mock_file, 'description' => 'test'} - }) - - assert response[:unsaved].present? - assert_equal 2, response[:unsaved].length - assert response[:unsaved].first.new_record? - assert response[:unsaved].second.new_record? - assert_equal response[:unsaved], @project.unsaved_attachments - end - end - end - - def test_latest_attach - set_fixtures_attachments_directory - a1 = Attachment.find(16) - assert_equal "testfile.png", a1.filename - assert a1.readable? - assert (! a1.visible?(User.anonymous)) - assert a1.visible?(User.find(2)) - a2 = Attachment.find(17) - assert_equal "testfile.PNG", a2.filename - assert a2.readable? - assert (! a2.visible?(User.anonymous)) - assert a2.visible?(User.find(2)) - assert a1.created_on < a2.created_on - - la1 = Attachment.latest_attach([a1, a2], "testfile.png") - assert_equal 17, la1.id - la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG") - assert_equal 17, la2.id - - set_tmp_attachments_directory - end - - def test_thumbnailable_should_be_true_for_images - assert_equal true, Attachment.new(:filename => 'test.jpg').thumbnailable? - end - - def test_thumbnailable_should_be_true_for_non_images - assert_equal false, Attachment.new(:filename => 'test.txt').thumbnailable? - end - - if convert_installed? - def test_thumbnail_should_generate_the_thumbnail - set_fixtures_attachments_directory - attachment = Attachment.find(16) - Attachment.clear_thumbnails - - assert_difference "Dir.glob(File.join(Attachment.thumbnails_storage_path, '*.thumb')).size" do - thumbnail = attachment.thumbnail - assert_equal "16_8e0294de2441577c529f170b6fb8f638_100.thumb", File.basename(thumbnail) - assert File.exists?(thumbnail) - end - end - else - puts '(ImageMagick convert not available)' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/06/06594ea20a7f9dbceb45bfdd779a780237719c5b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/06/06594ea20a7f9dbceb45bfdd779a780237719c5b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,123 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class IssueStatusesControllerTest < ActionController::TestCase + fixtures :issue_statuses, :issues, :users + + def setup + User.current = nil + @request.session[:user_id] = 1 # admin + end + + def test_index + get :index + assert_response :success + assert_template 'index' + end + + def test_index_by_anonymous_should_redirect_to_login_form + @request.session[:user_id] = nil + get :index + assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fissue_statuses' + end + + def test_index_by_user_should_respond_with_406 + @request.session[:user_id] = 2 + get :index + assert_response 406 + end + + def test_new + get :new + assert_response :success + assert_template 'new' + end + + def test_create + assert_difference 'IssueStatus.count' do + post :create, :issue_status => {:name => 'New status'} + end + assert_redirected_to :action => 'index' + status = IssueStatus.order('id DESC').first + assert_equal 'New status', status.name + end + + def test_create_with_failure + post :create, :issue_status => {:name => ''} + assert_response :success + assert_template 'new' + assert_error_tag :content => /name can't be blank/i + end + + def test_edit + get :edit, :id => '3' + assert_response :success + assert_template 'edit' + end + + def test_update + put :update, :id => '3', :issue_status => {:name => 'Renamed status'} + assert_redirected_to :action => 'index' + status = IssueStatus.find(3) + assert_equal 'Renamed status', status.name + end + + def test_update_with_failure + put :update, :id => '3', :issue_status => {:name => ''} + assert_response :success + assert_template 'edit' + assert_error_tag :content => /name can't be blank/i + end + + def test_destroy + Issue.delete_all("status_id = 1") + + assert_difference 'IssueStatus.count', -1 do + delete :destroy, :id => '1' + end + assert_redirected_to :action => 'index' + assert_nil IssueStatus.find_by_id(1) + end + + def test_destroy_should_block_if_status_in_use + assert_not_nil Issue.find_by_status_id(1) + + assert_no_difference 'IssueStatus.count' do + delete :destroy, :id => '1' + end + assert_redirected_to :action => 'index' + assert_not_nil IssueStatus.find_by_id(1) + end + + def test_update_issue_done_ratio_with_issue_done_ratio_set_to_issue_field + with_settings :issue_done_ratio => 'issue_field' do + post :update_issue_done_ratio + assert_match /not updated/, flash[:error].to_s + assert_redirected_to '/issue_statuses' + end + end + + def test_update_issue_done_ratio_with_issue_done_ratio_set_to_issue_status + with_settings :issue_done_ratio => 'issue_status' do + post :update_issue_done_ratio + assert_match /Issue done ratios updated/, flash[:notice].to_s + assert_redirected_to '/issue_statuses' + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/06/06dd3ee8641385912fee02a84e6de53eed535f28.svn-base --- a/.svn/pristine/06/06dd3ee8641385912fee02a84e6de53eed535f28.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -
    -<%= link_to l(:label_auth_source_new), {:action => 'new'}, :class => 'icon icon-add' %> -
    - -

    <%=l(:label_auth_source_plural)%>

    - - - - - - - - - - -<% for source in @auth_sources %> - "> - - - - - - -<% end %> - -
    <%=l(:field_name)%><%=l(:field_type)%><%=l(:field_host)%><%=l(:label_user_plural)%>
    <%= link_to(h(source.name), :action => 'edit', :id => source)%><%= h source.auth_method_name %><%= h source.host %><%= h source.users.count %> - <%= link_to l(:button_test), {:action => 'test_connection', :id => source}, :class => 'icon icon-test' %> - <%= delete_link auth_source_path(source) %> -
    - -

    <%= pagination_links_full @auth_source_pages %>

    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/07/071604c5dacdea2b6b0884f22cd68afddd0f1a2a.svn-base --- a/.svn/pristine/07/071604c5dacdea2b6b0884f22cd68afddd0f1a2a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../../../test_helper', __FILE__) -begin - require 'mocha' - - class CvsAdapterTest < ActiveSupport::TestCase - REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s - REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? - MODULE_NAME = 'test' - - if File.directory?(REPOSITORY_PATH) - def setup - @adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH) - end - - def test_scm_version - to_test = { "\nConcurrent Versions System (CVS) 1.12.13 (client/server)\n" => [1,12,13], - "\r\n1.12.12\r\n1.12.11" => [1,12,12], - "1.12.11\r\n1.12.10\r\n" => [1,12,11]} - to_test.each do |s, v| - test_scm_version_for(s, v) - end - end - - def test_revisions_all - cnt = 0 - @adapter.revisions('', nil, nil, :log_encoding => 'UTF-8') do |revision| - cnt += 1 - end - assert_equal 16, cnt - end - - def test_revisions_from_rev3 - rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) - cnt = 0 - @adapter.revisions('', rev3_committed_on, nil, :log_encoding => 'UTF-8') do |revision| - cnt += 1 - end - assert_equal 4, cnt - end - - def test_entries_rev3 - rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) - entries = @adapter.entries('sources', rev3_committed_on) - assert_equal 2, entries.size - assert_equal entries[0].name, "watchers_controller.rb" - assert_equal entries[0].lastrev.time, Time.gm(2007, 12, 13, 16, 27, 22) - end - - def test_path_encoding_default_utf8 - adpt1 = Redmine::Scm::Adapters::CvsAdapter.new( - MODULE_NAME, - REPOSITORY_PATH - ) - assert_equal "UTF-8", adpt1.path_encoding - adpt2 = Redmine::Scm::Adapters::CvsAdapter.new( - MODULE_NAME, - REPOSITORY_PATH, - nil, - nil, - "" - ) - assert_equal "UTF-8", adpt2.path_encoding - end - - private - - def test_scm_version_for(scm_command_version, version) - @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) - assert_equal version, @adapter.class.scm_command_version - end - else - puts "Cvs test repository NOT FOUND. Skipping unit tests !!!" - def test_fake; assert true end - end - end - -rescue LoadError - class CvsMochaFake < ActiveSupport::TestCase - def test_fake; assert(false, "Requires mocha to run those tests") end - end -end - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/07/072d65dcf460d00cc4770641f038bc5801ca9243.svn-base --- a/.svn/pristine/07/072d65dcf460d00cc4770641f038bc5801ca9243.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class PrincipalTest < ActiveSupport::TestCase - fixtures :users, :projects, :members, :member_roles - - def test_active_scope_should_return_groups_and_active_users - result = Principal.active.all - assert_include Group.first, result - assert_not_nil result.detect {|p| p.is_a?(User)} - assert_nil result.detect {|p| p.is_a?(User) && !p.active?} - assert_nil result.detect {|p| p.is_a?(AnonymousUser)} - end - - def test_member_of_scope_should_return_the_union_of_all_members - projects = Project.find_all_by_id(1, 2) - assert_equal projects.map(&:principals).flatten.sort, Principal.member_of(projects).sort - end - - def test_member_of_scope_should_be_empty_for_no_projects - assert_equal [], Principal.member_of([]).sort - end - - def test_not_member_of_scope_should_return_users_that_have_no_memberships - projects = Project.find_all_by_id(1, 2) - expected = (Principal.all - projects.map(&:memberships).flatten.map(&:principal)).sort - assert_equal expected, Principal.not_member_of(projects).sort - end - - def test_not_member_of_scope_should_be_empty_for_no_projects - assert_equal [], Principal.not_member_of([]).sort - end - - context "#like" do - setup do - Principal.create!(:login => 'login') - Principal.create!(:login => 'login2') - - Principal.create!(:firstname => 'firstname') - Principal.create!(:firstname => 'firstname2') - - Principal.create!(:lastname => 'lastname') - Principal.create!(:lastname => 'lastname2') - - Principal.create!(:mail => 'mail@example.com') - Principal.create!(:mail => 'mail2@example.com') - - @palmer = Principal.create!(:firstname => 'David', :lastname => 'Palmer') - end - - should "search login" do - results = Principal.like('login') - - assert_equal 2, results.count - assert results.all? {|u| u.login.match(/login/) } - end - - should "search firstname" do - results = Principal.like('firstname') - - assert_equal 2, results.count - assert results.all? {|u| u.firstname.match(/firstname/) } - end - - should "search lastname" do - results = Principal.like('lastname') - - assert_equal 2, results.count - assert results.all? {|u| u.lastname.match(/lastname/) } - end - - should "search mail" do - results = Principal.like('mail') - - assert_equal 2, results.count - assert results.all? {|u| u.mail.match(/mail/) } - end - - should "search firstname and lastname" do - results = Principal.like('david palm') - - assert_equal 1, results.count - assert_equal @palmer, results.first - end - - should "search lastname and firstname" do - results = Principal.like('palmer davi') - - assert_equal 1, results.count - assert_equal @palmer, results.first - end - end - - def test_like_scope_with_cyrillic_name - user = User.generate!(:firstname => 'Соболев', :lastname => 'ДениÑ') - results = Principal.like('Собо') - assert_equal 1, results.count - assert_equal user, results.first - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/07/0760e17a4ae9d6716b2c149e62df20d426be1e60.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/07/0760e17a4ae9d6716b2c149e62df20d426be1e60.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,86 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +module RedmineMenuTestHelper + # Assertions + def assert_number_of_items_in_menu(menu_name, count) + assert Redmine::MenuManager.items(menu_name).size >= count, "Menu has less than #{count} items" + end + + def assert_menu_contains_item_named(menu_name, item_name) + assert Redmine::MenuManager.items(menu_name).collect(&:name).include?(item_name.to_sym), "Menu did not have an item named #{item_name}" + end + + # Helpers + def get_menu_item(menu_name, item_name) + Redmine::MenuManager.items(menu_name).find {|item| item.name == item_name.to_sym} + end +end + +class RedmineTest < ActiveSupport::TestCase + include RedmineMenuTestHelper + + def test_top_menu + assert_number_of_items_in_menu :top_menu, 5 + assert_menu_contains_item_named :top_menu, :home + assert_menu_contains_item_named :top_menu, :my_page + assert_menu_contains_item_named :top_menu, :projects + assert_menu_contains_item_named :top_menu, :administration + assert_menu_contains_item_named :top_menu, :help + end + + def test_account_menu + assert_number_of_items_in_menu :account_menu, 4 + assert_menu_contains_item_named :account_menu, :login + assert_menu_contains_item_named :account_menu, :register + assert_menu_contains_item_named :account_menu, :my_account + assert_menu_contains_item_named :account_menu, :logout + end + + def test_application_menu + assert_number_of_items_in_menu :application_menu, 0 + end + + def test_admin_menu + assert_number_of_items_in_menu :admin_menu, 0 + end + + def test_project_menu + assert_number_of_items_in_menu :project_menu, 14 + assert_menu_contains_item_named :project_menu, :overview + assert_menu_contains_item_named :project_menu, :activity + assert_menu_contains_item_named :project_menu, :roadmap + assert_menu_contains_item_named :project_menu, :issues + assert_menu_contains_item_named :project_menu, :new_issue + assert_menu_contains_item_named :project_menu, :calendar + assert_menu_contains_item_named :project_menu, :gantt + assert_menu_contains_item_named :project_menu, :news + assert_menu_contains_item_named :project_menu, :documents + assert_menu_contains_item_named :project_menu, :wiki + assert_menu_contains_item_named :project_menu, :boards + assert_menu_contains_item_named :project_menu, :files + assert_menu_contains_item_named :project_menu, :repository + assert_menu_contains_item_named :project_menu, :settings + end + + def test_new_issue_should_have_root_as_a_parent + new_issue = get_menu_item(:project_menu, :new_issue) + assert_equal :root, new_issue.parent.name + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/07/077e480273d388c124ae7826c285eaf11442be18.svn-base --- a/.svn/pristine/07/077e480273d388c124ae7826c285eaf11442be18.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -<% roles = Role.find_all_givable %> -<% projects = Project.active.find(:all, :order => 'lft') %> - -
    -<% if @group.memberships.any? %> - - - - - - - - <% @group.memberships.each do |membership| %> - <% next if membership.new_record? %> - - - - - -<% end; reset_cycle %> - -
    <%= l(:label_project) %><%= l(:label_role_plural) %>
    <%=h membership.project %> - <%=h membership.roles.sort.collect(&:to_s).join(', ') %> - <%= form_for(:membership, :remote => true, - :url => { :action => 'edit_membership', :id => @group, :membership_id => membership }, - :html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %> -

    <% roles.each do |role| %> -
    - <% end %>

    -

    <%= submit_tag l(:button_change) %> - <%= link_to_function( - l(:button_cancel), - "$('#member-#{membership.id}-roles').show(); $('#member-#{membership.id}-roles-form').hide(); return false;" - ) %>

    - <% end %> -
    - <%= link_to_function( - l(:button_edit), - "$('#member-#{membership.id}-roles').hide(); $('#member-#{membership.id}-roles-form').show(); return false;", - :class => 'icon icon-edit' - ) %> - <%= delete_link({:controller => 'groups', :action => 'destroy_membership', :id => @group, :membership_id => membership}, - :remote => true, - :method => :post) %> -
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> -
    - -
    -<% if projects.any? %> -
    <%=l(:label_project_new)%> -<%= form_for(:membership, :remote => true, :url => { :action => 'edit_membership', :id => @group }) do %> -<%= label_tag "membership_project_id", l(:description_choose_project), :class => "hidden-for-sighted" %> -<%= select_tag 'membership[project_id]', options_for_membership_project_select(@group, projects) %> -

    <%= l(:label_role_plural) %>: -<% roles.each do |role| %> - -<% end %>

    -

    <%= submit_tag l(:button_add) %>

    -<% end %> -
    -<% end %> -
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/07/078a4373b77072983b303eb90be2e921eb7adcfd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/07/078a4373b77072983b303eb90be2e921eb7adcfd.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,165 @@ +--- +queries_001: + id: 1 + type: IssueQuery + project_id: 1 + visibility: 2 + name: Multiple custom fields query + filters: | + --- + cf_1: + :values: + - MySQL + :operator: "=" + status_id: + :values: + - "1" + :operator: o + cf_2: + :values: + - "125" + :operator: "=" + + user_id: 1 + column_names: +queries_002: + id: 2 + type: IssueQuery + project_id: 1 + visibility: 0 + name: Private query for cookbook + filters: | + --- + tracker_id: + :values: + - "3" + :operator: "=" + status_id: + :values: + - "1" + :operator: o + + user_id: 3 + column_names: +queries_003: + id: 3 + type: IssueQuery + project_id: + visibility: 0 + name: Private query for all projects + filters: | + --- + tracker_id: + :values: + - "3" + :operator: "=" + + user_id: 3 + column_names: +queries_004: + id: 4 + type: IssueQuery + project_id: + visibility: 2 + name: Public query for all projects + filters: | + --- + tracker_id: + :values: + - "3" + :operator: "=" + + user_id: 2 + column_names: +queries_005: + id: 5 + type: IssueQuery + project_id: + visibility: 2 + name: Open issues by priority and tracker + filters: | + --- + status_id: + :values: + - "1" + :operator: o + + user_id: 1 + column_names: + sort_criteria: | + --- + - - priority + - desc + - - tracker + - asc +queries_006: + id: 6 + type: IssueQuery + project_id: + visibility: 2 + name: Open issues grouped by tracker + filters: | + --- + status_id: + :values: + - "1" + :operator: o + + user_id: 1 + column_names: + group_by: tracker + sort_criteria: | + --- + - - priority + - desc +queries_007: + id: 7 + type: IssueQuery + project_id: 2 + visibility: 2 + name: Public query for project 2 + filters: | + --- + tracker_id: + :values: + - "3" + :operator: "=" + + user_id: 2 + column_names: +queries_008: + id: 8 + type: IssueQuery + project_id: 2 + visibility: 0 + name: Private query for project 2 + filters: | + --- + tracker_id: + :values: + - "3" + :operator: "=" + + user_id: 2 + column_names: +queries_009: + id: 9 + type: IssueQuery + project_id: + visibility: 2 + name: Open issues grouped by list custom field + filters: | + --- + status_id: + :values: + - "1" + :operator: o + + user_id: 1 + column_names: + group_by: cf_1 + sort_criteria: | + --- + - - priority + - desc + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/07/07f4ae691a86068505e5dcb35963f143e7c54566.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/07/07f4ae691a86068505e5dcb35963f143e7c54566.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,30 @@ +

    +<%= label_tag "user_mail_notification", l(:description_user_mail_notification), :class => "hidden-for-sighted" %> +<%= select_tag( + 'user[mail_notification]', + options_for_select( + user_mail_notification_options(@user), @user.mail_notification), + :onchange => 'if (this.value == "selected") {$("#notified-projects").show();} else {$("#notified-projects").hide();}' + ) %> +

    +<%= content_tag 'div', :id => 'notified-projects', :style => (@user.mail_notification == 'selected' ? '' : 'display:none;') do %> + <%= render_project_nested_lists(@user.projects) do |project| + content_tag('label', + check_box_tag( + 'user[notified_project_ids][]', + project.id, + @user.notified_projects_ids.include?(project.id), + :id => nil + ) + ' ' + h(project.name) + ) + end %> + <%= hidden_field_tag 'user[notified_project_ids][]', '' %> +

    <%= l(:text_user_mail_option) %>

    +<% end %> + +<%= fields_for :pref, @user.pref do |pref_fields| %> +

    + <%= pref_fields.check_box :no_self_notified %> + +

    +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/08/081cb3c9d1cc0a40761d7f17097c046e4db924a7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/08/081cb3c9d1cc0a40761d7f17097c046e4db924a7.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,264 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../base', __FILE__) + +class Redmine::UiTest::IssuesTest < Redmine::UiTest::Base + fixtures :projects, :users, :roles, :members, :member_roles, + :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues, + :enumerations, :custom_fields, :custom_values, :custom_fields_trackers, + :watchers + + def test_create_issue + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + within('form#issue-form') do + select 'Bug', :from => 'Tracker' + select 'Low', :from => 'Priority' + fill_in 'Subject', :with => 'new test issue' + fill_in 'Description', :with => 'new issue' + select '0 %', :from => 'Done' + fill_in 'Due date', :with => '' + fill_in 'Searchable field', :with => 'Value for field 2' + # click_button 'Create' would match both 'Create' and 'Create and continue' buttons + find('input[name=commit]').click + end + + # find created issue + issue = Issue.find_by_subject("new test issue") + assert_kind_of Issue, issue + + # check redirection + find 'div#flash_notice', :visible => true, :text => "Issue \##{issue.id} created." + assert_equal issue_path(:id => issue), current_path + + # check issue attributes + assert_equal 'jsmith', issue.author.login + assert_equal 1, issue.project.id + assert_equal IssueStatus.find_by_name('New'), issue.status + assert_equal Tracker.find_by_name('Bug'), issue.tracker + assert_equal IssuePriority.find_by_name('Low'), issue.priority + assert_equal 'Value for field 2', issue.custom_field_value(CustomField.find_by_name('Searchable field')) + end + + def test_create_issue_with_form_update + field1 = IssueCustomField.create!( + :field_format => 'string', + :name => 'Field1', + :is_for_all => true, + :trackers => Tracker.find_all_by_id([1, 2]) + ) + field2 = IssueCustomField.create!( + :field_format => 'string', + :name => 'Field2', + :is_for_all => true, + :trackers => Tracker.find_all_by_id(2) + ) + + Role.non_member.add_permission! :add_issues + Role.non_member.remove_permission! :edit_issues, :add_issue_notes + + log_user('someone', 'foo') + visit '/projects/ecookbook/issues/new' + assert page.has_no_content?(field2.name) + assert page.has_content?(field1.name) + + fill_in 'Subject', :with => 'New test issue' + fill_in 'Description', :with => 'New test issue description' + fill_in field1.name, :with => 'CF1 value' + select 'Low', :from => 'Priority' + + # field2 should show up when changing tracker + select 'Feature request', :from => 'Tracker' + assert page.has_content?(field2.name) + assert page.has_content?(field1.name) + + fill_in field2.name, :with => 'CF2 value' + assert_difference 'Issue.count' do + page.first(:button, 'Create').click + end + + issue = Issue.order('id desc').first + assert_equal 'New test issue', issue.subject + assert_equal 'New test issue description', issue.description + assert_equal 'Low', issue.priority.name + assert_equal 'CF1 value', issue.custom_field_value(field1) + assert_equal 'CF2 value', issue.custom_field_value(field2) + end + + def test_create_issue_with_watchers + user = User.generate!(:firstname => 'Some', :lastname => 'Watcher') + assert_equal 'Some Watcher', user.name + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + fill_in 'Subject', :with => 'Issue with watchers' + # Add a project member as watcher + check 'Dave Lopper' + # Search for another user + assert page.has_no_css?('form#new-watcher-form') + assert page.has_no_content?('Some Watcher') + click_link 'Search for watchers to add' + within('form#new-watcher-form') do + assert page.has_content?('Some One') + fill_in 'user_search', :with => 'watch' + assert page.has_no_content?('Some One') + check 'Some Watcher' + click_button 'Add' + end + assert page.has_css?('form#issue-form') + assert page.has_css?('p#watchers_form') + using_wait_time(30) do + within('span#watchers_inputs') do + within("label#issue_watcher_user_ids_#{user.id}") do + assert has_content?('Some Watcher'), "No watcher content" + end + end + end + assert_difference 'Issue.count' do + find('input[name=commit]').click + end + + issue = Issue.order('id desc').first + assert_equal ['Dave Lopper', 'Some Watcher'], issue.watcher_users.map(&:name).sort + end + + def test_create_issue_start_due_date + with_settings :default_issue_start_date_to_creation_date => 0 do + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + assert_equal "", page.find('input#issue_start_date').value + assert_equal "", page.find('input#issue_due_date').value + page.first('p#start_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal Date.today.to_s, page.find('input#issue_start_date').value + page.first('p#due_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal Date.today.to_s, page.find('input#issue_due_date').value + end + end + + def test_create_issue_start_due_date_default + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + fill_in 'Start date', :with => '2012-04-01' + fill_in 'Due date', :with => '' + page.first('p#due_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal '2012-04-01', page.find('input#issue_due_date').value + + fill_in 'Start date', :with => '' + fill_in 'Due date', :with => '2012-04-01' + page.first('p#start_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal '2012-04-01', page.find('input#issue_start_date').value + end + + def test_preview_issue_description + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + within('form#issue-form') do + fill_in 'Subject', :with => 'new issue subject' + fill_in 'Description', :with => 'new issue description' + click_link 'Preview' + end + find 'div#preview fieldset', :visible => true, :text => 'new issue description' + assert_difference 'Issue.count' do + find('input[name=commit]').click + end + + issue = Issue.order('id desc').first + assert_equal 'new issue description', issue.description + end + + def test_update_issue_with_form_update + field = IssueCustomField.create!( + :field_format => 'string', + :name => 'Form update CF', + :is_for_all => true, + :trackers => Tracker.find_all_by_name('Feature request') + ) + + Role.non_member.add_permission! :edit_issues + Role.non_member.remove_permission! :add_issues, :add_issue_notes + + log_user('someone', 'foo') + visit '/issues/1' + assert page.has_no_content?('Form update CF') + + page.first(:link, 'Update').click + # the custom field should show up when changing tracker + select 'Feature request', :from => 'Tracker' + assert page.has_content?('Form update CF') + + fill_in 'Form update', :with => 'CF value' + assert_no_difference 'Issue.count' do + page.first(:button, 'Submit').click + end + + issue = Issue.find(1) + assert_equal 'CF value', issue.custom_field_value(field) + end + + def test_remove_issue_watcher_from_sidebar + user = User.find(3) + Watcher.create!(:watchable => Issue.find(1), :user => user) + + log_user('jsmith', 'jsmith') + visit '/issues/1' + assert page.first('#sidebar').has_content?('Watchers (1)') + assert page.first('#sidebar').has_content?(user.name) + assert_difference 'Watcher.count', -1 do + page.first('ul.watchers .user-3 a.delete').click + assert page.first('#sidebar').has_content?('Watchers (0)') + end + assert page.first('#sidebar').has_no_content?(user.name) + end + + def test_watch_issue_via_context_menu + log_user('jsmith', 'jsmith') + visit '/issues' + assert page.has_css?('tr#issue-1') + find('tr#issue-1 td.updated_on').click + page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');" + assert_difference 'Watcher.count' do + within('#context-menu') do + click_link 'Watch' + end + assert page.has_css?('tr#issue-1') + end + assert Issue.find(1).watched_by?(User.find_by_login('jsmith')) + end + + def test_bulk_watch_issues_via_context_menu + log_user('jsmith', 'jsmith') + visit '/issues' + assert page.has_css?('tr#issue-1') + assert page.has_css?('tr#issue-4') + find('tr#issue-1 input[type=checkbox]').click + find('tr#issue-4 input[type=checkbox]').click + page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');" + assert_difference 'Watcher.count', 2 do + within('#context-menu') do + click_link 'Watch' + end + assert page.has_css?('tr#issue-1') + assert page.has_css?('tr#issue-4') + end + assert Issue.find(1).watched_by?(User.find_by_login('jsmith')) + assert Issue.find(4).watched_by?(User.find_by_login('jsmith')) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/08/08415c997f1d03456d84b86652b037399f12d911.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/08/08415c997f1d03456d84b86652b037399f12d911.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,119 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class LayoutTest < ActionController::IntegrationTest + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + test "browsing to a missing page should render the base layout" do + get "/users/100000000" + + assert_response :not_found + + # UsersController uses the admin layout by default + assert_select "#admin-menu", :count => 0 + end + + test "browsing to an unauthorized page should render the base layout" do + change_user_password('miscuser9', 'test1234') + + log_user('miscuser9','test1234') + + get "/admin" + assert_response :forbidden + assert_select "#admin-menu", :count => 0 + end + + def test_top_menu_and_search_not_visible_when_login_required + with_settings :login_required => '1' do + get '/' + assert_select "#top-menu > ul", 0 + assert_select "#quick-search", 0 + end + end + + def test_top_menu_and_search_visible_when_login_not_required + with_settings :login_required => '0' do + get '/' + assert_select "#top-menu > ul" + assert_select "#quick-search" + end + end + + def test_wiki_formatter_header_tags + Role.anonymous.add_permission! :add_issues + + get '/projects/ecookbook/issues/new' + assert_tag :script, + :attributes => {:src => %r{^/javascripts/jstoolbar/jstoolbar-textile.min.js}}, + :parent => {:tag => 'head'} + end + + def test_calendar_header_tags + with_settings :default_language => 'fr' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-fr.js", response.body + end + + with_settings :default_language => 'en-GB' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-en-GB.js", response.body + end + + with_settings :default_language => 'en' do + get '/issues' + assert_not_include "/javascripts/i18n/jquery.ui.datepicker", response.body + end + + with_settings :default_language => 'zh' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-zh-CN.js", response.body + end + + with_settings :default_language => 'zh-TW' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-zh-TW.js", response.body + end + + with_settings :default_language => 'pt' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-pt.js", response.body + end + + with_settings :default_language => 'pt-BR' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-pt-BR.js", response.body + end + end + + def test_search_field_outside_project_should_link_to_global_search + get '/' + assert_select 'div#quick-search form[action=/search]' + end + + def test_search_field_inside_project_should_link_to_project_search + get '/projects/ecookbook' + assert_select 'div#quick-search form[action=/projects/ecookbook/search]' + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/08/084dba3511eace99e8953d1d7626d2fa107595d7.svn-base --- a/.svn/pristine/08/084dba3511eace99e8953d1d7626d2fa107595d7.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class NewsController < ApplicationController - default_search_scope :news - model_object News - before_filter :find_model_object, :except => [:new, :create, :index] - before_filter :find_project_from_association, :except => [:new, :create, :index] - before_filter :find_project_by_project_id, :only => [:new, :create] - before_filter :authorize, :except => [:index] - before_filter :find_optional_project, :only => :index - accept_rss_auth :index - accept_api_auth :index - - helper :watchers - helper :attachments - - def index - case params[:format] - when 'xml', 'json' - @offset, @limit = api_offset_and_limit - else - @limit = 10 - end - - scope = @project ? @project.news.visible : News.visible - - @news_count = scope.count - @news_pages = Paginator.new self, @news_count, @limit, params['page'] - @offset ||= @news_pages.current.offset - @newss = scope.all(:include => [:author, :project], - :order => "#{News.table_name}.created_on DESC", - :offset => @offset, - :limit => @limit) - - respond_to do |format| - format.html { - @news = News.new # for adding news inline - render :layout => false if request.xhr? - } - format.api - format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") } - end - end - - def show - @comments = @news.comments - @comments.reverse! if User.current.wants_comments_in_reverse_order? - end - - def new - @news = News.new(:project => @project, :author => User.current) - end - - def create - @news = News.new(:project => @project, :author => User.current) - @news.safe_attributes = params[:news] - @news.save_attachments(params[:attachments]) - if @news.save - render_attachment_warning_if_needed(@news) - flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'news', :action => 'index', :project_id => @project - else - render :action => 'new' - end - end - - def edit - end - - def update - @news.safe_attributes = params[:news] - @news.save_attachments(params[:attachments]) - if @news.save - render_attachment_warning_if_needed(@news) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'show', :id => @news - else - render :action => 'edit' - end - end - - def destroy - @news.destroy - redirect_to :action => 'index', :project_id => @project - end - - private - - def find_optional_project - return true unless params[:project_id] - @project = Project.find(params[:project_id]) - authorize - rescue ActiveRecord::RecordNotFound - render_404 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/08/085f61120574ccfad3bedefd8a6623102e2bf32b.svn-base --- a/.svn/pristine/08/085f61120574ccfad3bedefd8a6623102e2bf32b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -<% content_for :header_tags do %> - <%= javascript_include_tag 'repository_navigation' %> -<% end %> - -<%= link_to l(:label_statistics), - {:action => 'stats', :id => @project, :repository_id => @repository.identifier_param}, - :class => 'icon icon-stats' if @repository.supports_all_revisions? %> - -<%= form_tag({:action => controller.action_name, - :id => @project, - :repository_id => @repository.identifier_param, - :path => to_path_param(@path), - :rev => nil}, - {:method => :get, :id => 'revision_selector'}) do -%> - - <% if !@repository.branches.nil? && @repository.branches.length > 0 -%> - | <%= l(:label_branch) %>: - <%= select_tag :branch, - options_for_select([''] + @repository.branches, @rev), - :id => 'branch' %> - <% end -%> - - <% if !@repository.tags.nil? && @repository.tags.length > 0 -%> - | <%= l(:label_tag) %>: - <%= select_tag :tag, - options_for_select([''] + @repository.tags, @rev), - :id => 'tag' %> - <% end -%> - - <% if @repository.supports_all_revisions? %> - | <%= l(:label_revision) %>: - <%= text_field_tag 'rev', @rev, :size => 8 %> - <% end %> -<% end -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/08/08918fc8f853142db140298c79057a45064d5050.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/08/08918fc8f853142db140298c79057a45064d5050.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,72 @@ +desc "Run the Continous Integration tests for Redmine" +task :ci do + # RAILS_ENV and ENV[] can diverge so force them both to test + ENV['RAILS_ENV'] = 'test' + RAILS_ENV = 'test' + Rake::Task["ci:setup"].invoke + Rake::Task["ci:build"].invoke + Rake::Task["ci:teardown"].invoke +end + +namespace :ci do + desc "Setup Redmine for a new build" + task :setup do + Rake::Task["tmp:clear"].invoke + Rake::Task["log:clear"].invoke + Rake::Task["db:create:all"].invoke + Rake::Task["db:migrate"].invoke + Rake::Task["db:schema:dump"].invoke + Rake::Task["test:scm:setup:all"].invoke + Rake::Task["test:scm:update"].invoke + end + + desc "Build Redmine" + task :build do + Rake::Task["test"].invoke + # Rake::Task["test:ui"].invoke if RUBY_VERSION >= '1.9.3' + end + + desc "Finish the build" + task :teardown do + end +end + +desc "Creates database.yml for the CI server" +file 'config/database.yml' do + require 'yaml' + database = ENV['DATABASE_ADAPTER'] + ruby = ENV['RUBY_VER'].gsub('.', '').gsub('-', '') + branch = ENV['BRANCH'].gsub('.', '').gsub('-', '') + dev_db_name = "ci_#{branch}_#{ruby}_dev" + test_db_name = "ci_#{branch}_#{ruby}_test" + + case database + when 'mysql' + dev_conf = {'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), + 'database' => dev_db_name, 'host' => 'localhost', + 'username' => 'jenkins', 'password' => 'jenkins', + 'encoding' => 'utf8'} + test_conf = dev_conf.merge('database' => test_db_name) + when 'postgresql' + dev_conf = {'adapter' => 'postgresql', 'database' => dev_db_name, + 'host' => 'localhost', + 'username' => 'jenkins', 'password' => 'jenkins'} + test_conf = dev_conf.merge('database' => test_db_name) + when /sqlite3/ + dev_conf = {'adapter' => (Object.const_defined?(:JRUBY_VERSION) ? + 'jdbcsqlite3' : 'sqlite3'), + 'database' => "db/#{dev_db_name}.sqlite3"} + test_conf = dev_conf.merge('database' => "db/#{test_db_name}.sqlite3") + when 'sqlserver' + dev_conf = {'adapter' => 'sqlserver', 'database' => dev_db_name, + 'host' => 'mssqlserver', 'port' => 1433, + 'username' => 'jenkins', 'password' => 'jenkins'} + test_conf = dev_conf.merge('database' => test_db_name) + else + abort "Unknown database" + end + + File.open('config/database.yml', 'w') do |f| + f.write YAML.dump({'development' => dev_conf, 'test' => test_conf}) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/08/08ad4f57c782749abd613605456ebd0acf953d61.svn-base --- a/.svn/pristine/08/08ad4f57c782749abd613605456ebd0acf953d61.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,978 +0,0 @@ -begin - require 'zlib' - @@__have_zlib = true -rescue - @@__have_zlib = false -end - -require 'rexml/document' - -module SVG - module Graph - VERSION = '@ANT_VERSION@' - - # === Base object for generating SVG Graphs - # - # == Synopsis - # - # This class is only used as a superclass of specialized charts. Do not - # attempt to use this class directly, unless creating a new chart type. - # - # For examples of how to subclass this class, see the existing specific - # subclasses, such as SVG::Graph::Pie. - # - # == Examples - # - # For examples of how to use this package, see either the test files, or - # the documentation for the specific class you want to use. - # - # * file:test/plot.rb - # * file:test/single.rb - # * file:test/test.rb - # * file:test/timeseries.rb - # - # == Description - # - # This package should be used as a base for creating SVG graphs. - # - # == Acknowledgements - # - # Leo Lapworth for creating the SVG::TT::Graph package which this Ruby - # port is based on. - # - # Stephen Morgan for creating the TT template and SVG. - # - # == See - # - # * SVG::Graph::BarHorizontal - # * SVG::Graph::Bar - # * SVG::Graph::Line - # * SVG::Graph::Pie - # * SVG::Graph::Plot - # * SVG::Graph::TimeSeries - # - # == Author - # - # Sean E. Russell - # - # Copyright 2004 Sean E. Russell - # This software is available under the Ruby license[LICENSE.txt] - # - class Graph - include REXML - - # Initialize the graph object with the graph settings. You won't - # instantiate this class directly; see the subclass for options. - # [width] 500 - # [height] 300 - # [show_x_guidelines] false - # [show_y_guidelines] true - # [show_data_values] true - # [min_scale_value] 0 - # [show_x_labels] true - # [stagger_x_labels] false - # [rotate_x_labels] false - # [step_x_labels] 1 - # [step_include_first_x_label] true - # [show_y_labels] true - # [rotate_y_labels] false - # [scale_integers] false - # [show_x_title] false - # [x_title] 'X Field names' - # [show_y_title] false - # [y_title_text_direction] :bt - # [y_title] 'Y Scale' - # [show_graph_title] false - # [graph_title] 'Graph Title' - # [show_graph_subtitle] false - # [graph_subtitle] 'Graph Sub Title' - # [key] true, - # [key_position] :right, # bottom or righ - # [font_size] 12 - # [title_font_size] 16 - # [subtitle_font_size] 14 - # [x_label_font_size] 12 - # [x_title_font_size] 14 - # [y_label_font_size] 12 - # [y_title_font_size] 14 - # [key_font_size] 10 - # [no_css] false - # [add_popups] false - def initialize( config ) - @config = config - - self.top_align = self.top_font = self.right_align = self.right_font = 0 - - init_with({ - :width => 500, - :height => 300, - :show_x_guidelines => false, - :show_y_guidelines => true, - :show_data_values => true, - -# :min_scale_value => 0, - - :show_x_labels => true, - :stagger_x_labels => false, - :rotate_x_labels => false, - :step_x_labels => 1, - :step_include_first_x_label => true, - - :show_y_labels => true, - :rotate_y_labels => false, - :stagger_y_labels => false, - :scale_integers => false, - - :show_x_title => false, - :x_title => 'X Field names', - - :show_y_title => false, - :y_title_text_direction => :bt, - :y_title => 'Y Scale', - - :show_graph_title => false, - :graph_title => 'Graph Title', - :show_graph_subtitle => false, - :graph_subtitle => 'Graph Sub Title', - :key => true, - :key_position => :right, # bottom or right - - :font_size =>12, - :title_font_size =>16, - :subtitle_font_size =>14, - :x_label_font_size =>12, - :x_title_font_size =>14, - :y_label_font_size =>12, - :y_title_font_size =>14, - :key_font_size =>10, - - :no_css =>false, - :add_popups =>false, - }) - - set_defaults if respond_to? :set_defaults - - init_with config - end - - - # This method allows you do add data to the graph object. - # It can be called several times to add more data sets in. - # - # data_sales_02 = [12, 45, 21]; - # - # graph.add_data({ - # :data => data_sales_02, - # :title => 'Sales 2002' - # }) - def add_data conf - @data = [] unless defined? @data - - if conf[:data] and conf[:data].kind_of? Array - @data << conf - else - raise "No data provided by #{conf.inspect}" - end - end - - - # This method removes all data from the object so that you can - # reuse it to create a new graph but with the same config options. - # - # graph.clear_data - def clear_data - @data = [] - end - - - # This method processes the template with the data and - # config which has been set and returns the resulting SVG. - # - # This method will croak unless at least one data set has - # been added to the graph object. - # - # print graph.burn - def burn - raise "No data available" unless @data.size > 0 - - calculations if respond_to? :calculations - - start_svg - calculate_graph_dimensions - @foreground = Element.new( "g" ) - draw_graph - draw_titles - draw_legend - draw_data - @graph.add_element( @foreground ) - style - - data = "" - @doc.write( data, 0 ) - - if @config[:compress] - if @@__have_zlib - inp, out = IO.pipe - gz = Zlib::GzipWriter.new( out ) - gz.write data - gz.close - data = inp.read - else - data << ""; - end - end - - return data - end - - - # Set the height of the graph box, this is the total height - # of the SVG box created - not the graph it self which auto - # scales to fix the space. - attr_accessor :height - # Set the width of the graph box, this is the total width - # of the SVG box created - not the graph it self which auto - # scales to fix the space. - attr_accessor :width - # Set the path to an external stylesheet, set to '' if - # you want to revert back to using the defaut internal version. - # - # To create an external stylesheet create a graph using the - # default internal version and copy the stylesheet section to - # an external file and edit from there. - attr_accessor :style_sheet - # (Bool) Show the value of each element of data on the graph - attr_accessor :show_data_values - # The point at which the Y axis starts, defaults to '0', - # if set to nil it will default to the minimum data value. - attr_accessor :min_scale_value - # Whether to show labels on the X axis or not, defaults - # to true, set to false if you want to turn them off. - attr_accessor :show_x_labels - # This puts the X labels at alternative levels so if they - # are long field names they will not overlap so easily. - # Default it false, to turn on set to true. - attr_accessor :stagger_x_labels - # This puts the Y labels at alternative levels so if they - # are long field names they will not overlap so easily. - # Default it false, to turn on set to true. - attr_accessor :stagger_y_labels - # This turns the X axis labels by 90 degrees. - # Default it false, to turn on set to true. - attr_accessor :rotate_x_labels - # This turns the Y axis labels by 90 degrees. - # Default it false, to turn on set to true. - attr_accessor :rotate_y_labels - # How many "steps" to use between displayed X axis labels, - # a step of one means display every label, a step of two results - # in every other label being displayed (label label label), - # a step of three results in every third label being displayed - # (label label label) and so on. - attr_accessor :step_x_labels - # Whether to (when taking "steps" between X axis labels) step from - # the first label (i.e. always include the first label) or step from - # the X axis origin (i.e. start with a gap if step_x_labels is greater - # than one). - attr_accessor :step_include_first_x_label - # Whether to show labels on the Y axis or not, defaults - # to true, set to false if you want to turn them off. - attr_accessor :show_y_labels - # Ensures only whole numbers are used as the scale divisions. - # Default it false, to turn on set to true. This has no effect if - # scale divisions are less than 1. - attr_accessor :scale_integers - # This defines the gap between markers on the Y axis, - # default is a 10th of the max_value, e.g. you will have - # 10 markers on the Y axis. NOTE: do not set this too - # low - you are limited to 999 markers, after that the - # graph won't generate. - attr_accessor :scale_divisions - # Whether to show the title under the X axis labels, - # default is false, set to true to show. - attr_accessor :show_x_title - # What the title under X axis should be, e.g. 'Months'. - attr_accessor :x_title - # Whether to show the title under the Y axis labels, - # default is false, set to true to show. - attr_accessor :show_y_title - # Aligns writing mode for Y axis label. - # Defaults to :bt (Bottom to Top). - # Change to :tb (Top to Bottom) to reverse. - attr_accessor :y_title_text_direction - # What the title under Y axis should be, e.g. 'Sales in thousands'. - attr_accessor :y_title - # Whether to show a title on the graph, defaults - # to false, set to true to show. - attr_accessor :show_graph_title - # What the title on the graph should be. - attr_accessor :graph_title - # Whether to show a subtitle on the graph, defaults - # to false, set to true to show. - attr_accessor :show_graph_subtitle - # What the subtitle on the graph should be. - attr_accessor :graph_subtitle - # Whether to show a key, defaults to false, set to - # true if you want to show it. - attr_accessor :key - # Where the key should be positioned, defaults to - # :right, set to :bottom if you want to move it. - attr_accessor :key_position - # Set the font size (in points) of the data point labels - attr_accessor :font_size - # Set the font size of the X axis labels - attr_accessor :x_label_font_size - # Set the font size of the X axis title - attr_accessor :x_title_font_size - # Set the font size of the Y axis labels - attr_accessor :y_label_font_size - # Set the font size of the Y axis title - attr_accessor :y_title_font_size - # Set the title font size - attr_accessor :title_font_size - # Set the subtitle font size - attr_accessor :subtitle_font_size - # Set the key font size - attr_accessor :key_font_size - # Show guidelines for the X axis - attr_accessor :show_x_guidelines - # Show guidelines for the Y axis - attr_accessor :show_y_guidelines - # Do not use CSS if set to true. Many SVG viewers do not support CSS, but - # not using CSS can result in larger SVGs as well as making it impossible to - # change colors after the chart is generated. Defaults to false. - attr_accessor :no_css - # Add popups for the data points on some graphs - attr_accessor :add_popups - - - protected - - def sort( *arrys ) - sort_multiple( arrys ) - end - - # Overwrite configuration options with supplied options. Used - # by subclasses. - def init_with config - config.each { |key, value| - self.send((key.to_s+"=").to_sym, value ) if respond_to? key.to_sym - } - end - - attr_accessor :top_align, :top_font, :right_align, :right_font - - KEY_BOX_SIZE = 12 - - # Override this (and call super) to change the margin to the left - # of the plot area. Results in @border_left being set. - def calculate_left_margin - @border_left = 7 - # Check for Y labels - max_y_label_height_px = rotate_y_labels ? - y_label_font_size : - get_y_labels.max{|a,b| - a.to_s.length<=>b.to_s.length - }.to_s.length * y_label_font_size * 0.6 - @border_left += max_y_label_height_px if show_y_labels - @border_left += max_y_label_height_px + 10 if stagger_y_labels - @border_left += y_title_font_size + 5 if show_y_title - end - - - # Calculates the width of the widest Y label. This will be the - # character height if the Y labels are rotated - def max_y_label_width_px - return font_size if rotate_y_labels - end - - - # Override this (and call super) to change the margin to the right - # of the plot area. Results in @border_right being set. - def calculate_right_margin - @border_right = 7 - if key and key_position == :right - val = keys.max { |a,b| a.length <=> b.length } - @border_right += val.length * key_font_size * 0.6 - @border_right += KEY_BOX_SIZE - @border_right += 10 # Some padding around the box - end - end - - - # Override this (and call super) to change the margin to the top - # of the plot area. Results in @border_top being set. - def calculate_top_margin - @border_top = 5 - @border_top += title_font_size if show_graph_title - @border_top += 5 - @border_top += subtitle_font_size if show_graph_subtitle - end - - - # Adds pop-up point information to a graph. - def add_popup( x, y, label ) - txt_width = label.length * font_size * 0.6 + 10 - tx = (x+txt_width > width ? x-5 : x+5) - t = @foreground.add_element( "text", { - "x" => tx.to_s, - "y" => (y - font_size).to_s, - "visibility" => "hidden", - }) - t.attributes["style"] = "fill: #000; "+ - (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;") - t.text = label.to_s - t.attributes["id"] = t.object_id.to_s - - @foreground.add_element( "circle", { - "cx" => x.to_s, - "cy" => y.to_s, - "r" => "10", - "style" => "opacity: 0", - "onmouseover" => - "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )", - "onmouseout" => - "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )", - }) - - end - - - # Override this (and call super) to change the margin to the bottom - # of the plot area. Results in @border_bottom being set. - def calculate_bottom_margin - @border_bottom = 7 - if key and key_position == :bottom - @border_bottom += @data.size * (font_size + 5) - @border_bottom += 10 - end - if show_x_labels - max_x_label_height_px = (not rotate_x_labels) ? - x_label_font_size : - get_x_labels.max{|a,b| - a.to_s.length<=>b.to_s.length - }.to_s.length * x_label_font_size * 0.6 - @border_bottom += max_x_label_height_px - @border_bottom += max_x_label_height_px + 10 if stagger_x_labels - end - @border_bottom += x_title_font_size + 5 if show_x_title - end - - - # Draws the background, axis, and labels. - def draw_graph - @graph = @root.add_element( "g", { - "transform" => "translate( #@border_left #@border_top )" - }) - - # Background - @graph.add_element( "rect", { - "x" => "0", - "y" => "0", - "width" => @graph_width.to_s, - "height" => @graph_height.to_s, - "class" => "graphBackground" - }) - - # Axis - @graph.add_element( "path", { - "d" => "M 0 0 v#@graph_height", - "class" => "axis", - "id" => "xAxis" - }) - @graph.add_element( "path", { - "d" => "M 0 #@graph_height h#@graph_width", - "class" => "axis", - "id" => "yAxis" - }) - - draw_x_labels - draw_y_labels - end - - - # Where in the X area the label is drawn - # Centered in the field, should be width/2. Start, 0. - def x_label_offset( width ) - 0 - end - - def make_datapoint_text( x, y, value, style="" ) - if show_data_values - @foreground.add_element( "text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "dataPointLabel", - "style" => "#{style} stroke: #fff; stroke-width: 2;" - }).text = value.to_s - text = @foreground.add_element( "text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "dataPointLabel" - }) - text.text = value.to_s - text.attributes["style"] = style if style.length > 0 - end - end - - - # Draws the X axis labels - def draw_x_labels - stagger = x_label_font_size + 5 - if show_x_labels - label_width = field_width - - count = 0 - for label in get_x_labels - if step_include_first_x_label == true then - step = count % step_x_labels - else - step = (count + 1) % step_x_labels - end - - if step == 0 then - text = @graph.add_element( "text" ) - text.attributes["class"] = "xAxisLabels" - text.text = label.to_s - - x = count * label_width + x_label_offset( label_width ) - y = @graph_height + x_label_font_size + 3 - t = 0 - (font_size / 2) - - if stagger_x_labels and count % 2 == 1 - y += stagger - @graph.add_element( "path", { - "d" => "M#{x} #@graph_height v#{stagger}", - "class" => "staggerGuideLine" - }) - end - - text.attributes["x"] = x.to_s - text.attributes["y"] = y.to_s - if rotate_x_labels - text.attributes["transform"] = - "rotate( 90 #{x} #{y-x_label_font_size} )"+ - " translate( 0 -#{x_label_font_size/4} )" - text.attributes["style"] = "text-anchor: start" - else - text.attributes["style"] = "text-anchor: middle" - end - end - - draw_x_guidelines( label_width, count ) if show_x_guidelines - count += 1 - end - end - end - - - # Where in the Y area the label is drawn - # Centered in the field, should be width/2. Start, 0. - def y_label_offset( height ) - 0 - end - - - def field_width - (@graph_width.to_f - font_size*2*right_font) / - (get_x_labels.length - right_align) - end - - - def field_height - (@graph_height.to_f - font_size*2*top_font) / - (get_y_labels.length - top_align) - end - - - # Draws the Y axis labels - def draw_y_labels - stagger = y_label_font_size + 5 - if show_y_labels - label_height = field_height - - count = 0 - y_offset = @graph_height + y_label_offset( label_height ) - y_offset += font_size/1.2 unless rotate_y_labels - for label in get_y_labels - y = y_offset - (label_height * count) - x = rotate_y_labels ? 0 : -3 - - if stagger_y_labels and count % 2 == 1 - x -= stagger - @graph.add_element( "path", { - "d" => "M#{x} #{y} h#{stagger}", - "class" => "staggerGuideLine" - }) - end - - text = @graph.add_element( "text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "yAxisLabels" - }) - text.text = label.to_s - if rotate_y_labels - text.attributes["transform"] = "translate( -#{font_size} 0 ) "+ - "rotate( 90 #{x} #{y} ) " - text.attributes["style"] = "text-anchor: middle" - else - text.attributes["y"] = (y - (y_label_font_size/2)).to_s - text.attributes["style"] = "text-anchor: end" - end - draw_y_guidelines( label_height, count ) if show_y_guidelines - count += 1 - end - end - end - - - # Draws the X axis guidelines - def draw_x_guidelines( label_height, count ) - if count != 0 - @graph.add_element( "path", { - "d" => "M#{label_height*count} 0 v#@graph_height", - "class" => "guideLines" - }) - end - end - - - # Draws the Y axis guidelines - def draw_y_guidelines( label_height, count ) - if count != 0 - @graph.add_element( "path", { - "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width", - "class" => "guideLines" - }) - end - end - - - # Draws the graph title and subtitle - def draw_titles - if show_graph_title - @root.add_element( "text", { - "x" => (width / 2).to_s, - "y" => (title_font_size).to_s, - "class" => "mainTitle" - }).text = graph_title.to_s - end - - if show_graph_subtitle - y_subtitle = show_graph_title ? - title_font_size + 10 : - subtitle_font_size - @root.add_element("text", { - "x" => (width / 2).to_s, - "y" => (y_subtitle).to_s, - "class" => "subTitle" - }).text = graph_subtitle.to_s - end - - if show_x_title - y = @graph_height + @border_top + x_title_font_size - if show_x_labels - y += x_label_font_size + 5 if stagger_x_labels - y += x_label_font_size + 5 - end - x = width / 2 - - @root.add_element("text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "xAxisTitle", - }).text = x_title.to_s - end - - if show_y_title - x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3) - y = height / 2 - - text = @root.add_element("text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "yAxisTitle", - }) - text.text = y_title.to_s - if y_title_text_direction == :bt - text.attributes["transform"] = "rotate( -90, #{x}, #{y} )" - else - text.attributes["transform"] = "rotate( 90, #{x}, #{y} )" - end - end - end - - def keys - return @data.collect{ |d| d[:title] } - end - - # Draws the legend on the graph - def draw_legend - if key - group = @root.add_element( "g" ) - - key_count = 0 - for key_name in keys - y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5) - group.add_element( "rect", { - "x" => 0.to_s, - "y" => y_offset.to_s, - "width" => KEY_BOX_SIZE.to_s, - "height" => KEY_BOX_SIZE.to_s, - "class" => "key#{key_count+1}" - }) - group.add_element( "text", { - "x" => (KEY_BOX_SIZE + 5).to_s, - "y" => (y_offset + KEY_BOX_SIZE).to_s, - "class" => "keyText" - }).text = key_name.to_s - key_count += 1 - end - - case key_position - when :right - x_offset = @graph_width + @border_left + 10 - y_offset = @border_top + 20 - when :bottom - x_offset = @border_left + 20 - y_offset = @border_top + @graph_height + 5 - if show_x_labels - max_x_label_height_px = (not rotate_x_labels) ? - x_label_font_size : - get_x_labels.max{|a,b| - a.to_s.length<=>b.to_s.length - }.to_s.length * x_label_font_size * 0.6 - x_label_font_size - y_offset += max_x_label_height_px - y_offset += max_x_label_height_px + 5 if stagger_x_labels - end - y_offset += x_title_font_size + 5 if show_x_title - end - group.attributes["transform"] = "translate(#{x_offset} #{y_offset})" - end - end - - - private - - def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 ) - if lo < hi - p = partition(arrys,lo,hi) - sort_multiple(arrys, lo, p-1) - sort_multiple(arrys, p+1, hi) - end - arrys - end - - def partition( arrys, lo, hi ) - p = arrys[0][lo] - l = lo - z = lo+1 - while z <= hi - if arrys[0][z] < p - l += 1 - arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] } - end - z += 1 - end - arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] } - l - end - - def style - if no_css - styles = parse_css - @root.elements.each("//*[@class]") { |el| - cl = el.attributes["class"] - style = styles[cl] - style += el.attributes["style"] if el.attributes["style"] - el.attributes["style"] = style - } - end - end - - def parse_css - css = get_style - rv = {} - while css =~ /^(\.(\w+)(?:\s*,\s*\.\w+)*)\s*\{/m - names_orig = names = $1 - css = $' - css =~ /([^}]+)\}/m - content = $1 - css = $' - - nms = [] - while names =~ /^\s*,?\s*\.(\w+)/ - nms << $1 - names = $' - end - - content = content.tr( "\n\t", " ") - for name in nms - current = rv[name] - current = current ? current+"; "+content : content - rv[name] = current.strip.squeeze(" ") - end - end - return rv - end - - - # Override and place code to add defs here - def add_defs defs - end - - - def start_svg - # Base document - @doc = Document.new - @doc << XMLDecl.new - @doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } + - %q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} ) - if style_sheet && style_sheet != '' - @doc << Instruction.new( "xml-stylesheet", - %Q{href="#{style_sheet}" type="text/css"} ) - end - @root = @doc.add_element( "svg", { - "width" => width.to_s, - "height" => height.to_s, - "viewBox" => "0 0 #{width} #{height}", - "xmlns" => "http://www.w3.org/2000/svg", - "xmlns:xlink" => "http://www.w3.org/1999/xlink", - "xmlns:a3" => "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/", - "a3:scriptImplementation" => "Adobe" - }) - @root << Comment.new( " "+"\\"*66 ) - @root << Comment.new( " Created with SVG::Graph " ) - @root << Comment.new( " SVG::Graph by Sean E. Russell " ) - @root << Comment.new( " Losely based on SVG::TT::Graph for Perl by"+ - " Leo Lapworth & Stephan Morgan " ) - @root << Comment.new( " "+"/"*66 ) - - defs = @root.add_element( "defs" ) - add_defs defs - if not(style_sheet && style_sheet != '') and !no_css - @root << Comment.new(" include default stylesheet if none specified ") - style = defs.add_element( "style", {"type"=>"text/css"} ) - style << CData.new( get_style ) - end - - @root << Comment.new( "SVG Background" ) - @root.add_element( "rect", { - "width" => width.to_s, - "height" => height.to_s, - "x" => "0", - "y" => "0", - "class" => "svgBackground" - }) - end - - - def calculate_graph_dimensions - calculate_left_margin - calculate_right_margin - calculate_bottom_margin - calculate_top_margin - @graph_width = width - @border_left - @border_right - @graph_height = height - @border_top - @border_bottom - end - - def get_style - return < 'get', :path => "/projects/5234/memberships.xml" }, + { :controller => 'members', :action => 'index', :project_id => '5234', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/memberships/5234.xml" }, + { :controller => 'members', :action => 'show', :id => '5234', :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/projects/5234/memberships" }, + { :controller => 'members', :action => 'create', :project_id => '5234' } + ) + assert_routing( + { :method => 'post', :path => "/projects/5234/memberships.xml" }, + { :controller => 'members', :action => 'create', :project_id => '5234', :format => 'xml' } + ) + assert_routing( + { :method => 'put', :path => "/memberships/5234" }, + { :controller => 'members', :action => 'update', :id => '5234' } + ) + assert_routing( + { :method => 'put', :path => "/memberships/5234.xml" }, + { :controller => 'members', :action => 'update', :id => '5234', :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/memberships/5234" }, + { :controller => 'members', :action => 'destroy', :id => '5234' } + ) + assert_routing( + { :method => 'delete', :path => "/memberships/5234.xml" }, + { :controller => 'members', :action => 'destroy', :id => '5234', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/projects/5234/memberships/autocomplete" }, + { :controller => 'members', :action => 'autocomplete', :project_id => '5234' } + ) + assert_routing( + { :method => 'get', :path => "/projects/5234/memberships/autocomplete.js" }, + { :controller => 'members', :action => 'autocomplete', :project_id => '5234', :format => 'js' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/08/08ba366b8c38c360363f9f7f4597d533502606b0.svn-base --- a/.svn/pristine/08/08ba366b8c38c360363f9f7f4597d533502606b0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,312 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'workflows_controller' - -# Re-raise errors caught by the controller. -class WorkflowsController; def rescue_action(e) raise e end; end - -class WorkflowsControllerTest < ActionController::TestCase - fixtures :roles, :trackers, :workflows, :users, :issue_statuses - - def setup - @controller = WorkflowsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_response :success - assert_template 'index' - - count = WorkflowTransition.count(:all, :conditions => 'role_id = 1 AND tracker_id = 2') - assert_tag :tag => 'a', :content => count.to_s, - :attributes => { :href => '/workflows/edit?role_id=1&tracker_id=2' } - end - - def test_get_edit - get :edit - assert_response :success - assert_template 'edit' - assert_not_nil assigns(:roles) - assert_not_nil assigns(:trackers) - end - - def test_get_edit_with_role_and_tracker - WorkflowTransition.delete_all - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3) - WorkflowTransition.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5) - - get :edit, :role_id => 2, :tracker_id => 1 - assert_response :success - assert_template 'edit' - - # used status only - assert_not_nil assigns(:statuses) - assert_equal [2, 3, 5], assigns(:statuses).collect(&:id) - - # allowed transitions - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[3][5][]', - :value => 'always', - :checked => 'checked' } - # not allowed - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[3][2][]', - :value => 'always', - :checked => nil } - # unused - assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[1][1][]' } - end - - def test_get_edit_with_role_and_tracker_and_all_statuses - WorkflowTransition.delete_all - - get :edit, :role_id => 2, :tracker_id => 1, :used_statuses_only => '0' - assert_response :success - assert_template 'edit' - - assert_not_nil assigns(:statuses) - assert_equal IssueStatus.count, assigns(:statuses).size - - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[1][1][]', - :value => 'always', - :checked => nil } - end - - def test_post_edit - post :edit, :role_id => 2, :tracker_id => 1, - :issue_status => { - '4' => {'5' => ['always']}, - '3' => {'1' => ['always'], '2' => ['always']} - } - assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' - - assert_equal 3, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) - assert_not_nil WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2}) - assert_nil WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4}) - end - - def test_post_edit_with_additional_transitions - post :edit, :role_id => 2, :tracker_id => 1, - :issue_status => { - '4' => {'5' => ['always']}, - '3' => {'1' => ['author'], '2' => ['assignee'], '4' => ['author', 'assignee']} - } - assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' - - assert_equal 4, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) - - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5}) - assert ! w.author - assert ! w.assignee - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1}) - assert w.author - assert ! w.assignee - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2}) - assert ! w.author - assert w.assignee - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4}) - assert w.author - assert w.assignee - end - - def test_clear_workflow - assert WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) > 0 - - post :edit, :role_id => 2, :tracker_id => 1 - assert_equal 0, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) - end - - def test_get_permissions - get :permissions - - assert_response :success - assert_template 'permissions' - assert_not_nil assigns(:roles) - assert_not_nil assigns(:trackers) - end - - def test_get_permissions_with_role_and_tracker - WorkflowPermission.delete_all - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') - - get :permissions, :role_id => 1, :tracker_id => 2 - assert_response :success - assert_template 'permissions' - - assert_select 'input[name=role_id][value=1]' - assert_select 'input[name=tracker_id][value=2]' - - # Required field - assert_select 'select[name=?]', 'permissions[assigned_to_id][2]' do - assert_select 'option[value=]' - assert_select 'option[value=][selected=selected]', 0 - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=readonly][selected=selected]', 0 - assert_select 'option[value=required]', :text => 'Required' - assert_select 'option[value=required][selected=selected]' - end - - # Read-only field - assert_select 'select[name=?]', 'permissions[fixed_version_id][3]' do - assert_select 'option[value=]' - assert_select 'option[value=][selected=selected]', 0 - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=readonly][selected=selected]' - assert_select 'option[value=required]', :text => 'Required' - assert_select 'option[value=required][selected=selected]', 0 - end - - # Other field - assert_select 'select[name=?]', 'permissions[due_date][3]' do - assert_select 'option[value=]' - assert_select 'option[value=][selected=selected]', 0 - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=readonly][selected=selected]', 0 - assert_select 'option[value=required]', :text => 'Required' - assert_select 'option[value=required][selected=selected]', 0 - end - end - - def test_get_permissions_with_required_custom_field_should_not_show_required_option - cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :tracker_ids => [1], :is_required => true) - - get :permissions, :role_id => 1, :tracker_id => 1 - assert_response :success - assert_template 'permissions' - - # Custom field that is always required - # The default option is "(Required)" - assert_select 'select[name=?]', "permissions[#{cf.id}][3]" do - assert_select 'option[value=]' - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=required]', 0 - end - end - - def test_get_permissions_with_role_and_tracker_and_all_statuses - WorkflowTransition.delete_all - - get :permissions, :role_id => 1, :tracker_id => 2, :used_statuses_only => '0' - assert_response :success - assert_equal IssueStatus.sorted.all, assigns(:statuses) - end - - def test_post_permissions - WorkflowPermission.delete_all - - post :permissions, :role_id => 1, :tracker_id => 2, :permissions => { - 'assigned_to_id' => {'1' => '', '2' => 'readonly', '3' => ''}, - 'fixed_version_id' => {'1' => 'required', '2' => 'readonly', '3' => ''}, - 'due_date' => {'1' => '', '2' => '', '3' => ''}, - } - assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' - - workflows = WorkflowPermission.all - assert_equal 3, workflows.size - workflows.each do |workflow| - assert_equal 1, workflow.role_id - assert_equal 2, workflow.tracker_id - end - assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'assigned_to_id' && wf.rule == 'readonly'} - assert workflows.detect {|wf| wf.old_status_id == 1 && wf.field_name == 'fixed_version_id' && wf.rule == 'required'} - assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'} - end - - def test_post_permissions_should_clear_permissions - WorkflowPermission.delete_all - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') - wf1 = WorkflowPermission.create!(:role_id => 1, :tracker_id => 3, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') - wf2 = WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') - - post :permissions, :role_id => 1, :tracker_id => 2 - assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' - - workflows = WorkflowPermission.all - assert_equal 2, workflows.size - assert wf1.reload - assert wf2.reload - end - - def test_get_copy - get :copy - assert_response :success - assert_template 'copy' - assert_select 'select[name=source_tracker_id]' do - assert_select 'option[value=1]', :text => 'Bug' - end - assert_select 'select[name=source_role_id]' do - assert_select 'option[value=2]', :text => 'Developer' - end - assert_select 'select[name=?]', 'target_tracker_ids[]' do - assert_select 'option[value=3]', :text => 'Support request' - end - assert_select 'select[name=?]', 'target_role_ids[]' do - assert_select 'option[value=1]', :text => 'Manager' - end - end - - def test_post_copy_one_to_one - source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) - - post :copy, :source_tracker_id => '1', :source_role_id => '2', - :target_tracker_ids => ['3'], :target_role_ids => ['1'] - assert_response 302 - assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) - end - - def test_post_copy_one_to_many - source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) - - post :copy, :source_tracker_id => '1', :source_role_id => '2', - :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] - assert_response 302 - assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 1) - assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) - assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 3) - assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 3) - end - - def test_post_copy_many_to_many - source_t2 = status_transitions(:tracker_id => 2, :role_id => 2) - source_t3 = status_transitions(:tracker_id => 3, :role_id => 2) - - post :copy, :source_tracker_id => 'any', :source_role_id => '2', - :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] - assert_response 302 - assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 1) - assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 1) - assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 3) - assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 3) - end - - # Returns an array of status transitions that can be compared - def status_transitions(conditions) - WorkflowTransition.find(:all, :conditions => conditions, - :order => 'tracker_id, role_id, old_status_id, new_status_id').collect {|w| [w.old_status, w.new_status_id]} - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/08/08f914636b6cd922b89f9ce8cb2268cea53be558.svn-base --- a/.svn/pristine/08/08f914636b6cd922b89f9ce8cb2268cea53be558.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -# Class used to represent the relations of an issue -class IssueRelations < Array - include Redmine::I18n - - def initialize(issue, *args) - @issue = issue - super(*args) - end - - def to_s(*args) - map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ') - end -end - -class IssueRelation < ActiveRecord::Base - belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id' - belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id' - - TYPE_RELATES = "relates" - TYPE_DUPLICATES = "duplicates" - TYPE_DUPLICATED = "duplicated" - TYPE_BLOCKS = "blocks" - TYPE_BLOCKED = "blocked" - TYPE_PRECEDES = "precedes" - TYPE_FOLLOWS = "follows" - TYPE_COPIED_TO = "copied_to" - TYPE_COPIED_FROM = "copied_from" - - TYPES = { - TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, - :order => 1, :sym => TYPE_RELATES }, - TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, - :order => 2, :sym => TYPE_DUPLICATED }, - TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates, - :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES }, - TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, - :order => 4, :sym => TYPE_BLOCKED }, - TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks, - :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS }, - TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, - :order => 6, :sym => TYPE_FOLLOWS }, - TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes, - :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES }, - TYPE_COPIED_TO => { :name => :label_copied_to, :sym_name => :label_copied_from, - :order => 8, :sym => TYPE_COPIED_FROM }, - TYPE_COPIED_FROM => { :name => :label_copied_from, :sym_name => :label_copied_to, - :order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO } - }.freeze - - validates_presence_of :issue_from, :issue_to, :relation_type - validates_inclusion_of :relation_type, :in => TYPES.keys - validates_numericality_of :delay, :allow_nil => true - validates_uniqueness_of :issue_to_id, :scope => :issue_from_id - validate :validate_issue_relation - - attr_protected :issue_from_id, :issue_to_id - before_save :handle_issue_order - - def visible?(user=User.current) - (issue_from.nil? || issue_from.visible?(user)) && (issue_to.nil? || issue_to.visible?(user)) - end - - def deletable?(user=User.current) - visible?(user) && - ((issue_from.nil? || user.allowed_to?(:manage_issue_relations, issue_from.project)) || - (issue_to.nil? || user.allowed_to?(:manage_issue_relations, issue_to.project))) - end - - def initialize(attributes=nil, *args) - super - if new_record? - if relation_type.blank? - self.relation_type = IssueRelation::TYPE_RELATES - end - end - end - - def validate_issue_relation - if issue_from && issue_to - errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id - unless issue_from.project_id == issue_to.project_id || - Setting.cross_project_issue_relations? - errors.add :issue_to_id, :not_same_project - end - # detect circular dependencies depending wether the relation should be reversed - if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse] - errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to - else - errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from - end - if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to) - errors.add :base, :cant_link_an_issue_with_a_descendant - end - end - end - - def other_issue(issue) - (self.issue_from_id == issue.id) ? issue_to : issue_from - end - - # Returns the relation type for +issue+ - def relation_type_for(issue) - if TYPES[relation_type] - if self.issue_from_id == issue.id - relation_type - else - TYPES[relation_type][:sym] - end - end - end - - def label_for(issue) - TYPES[relation_type] ? - TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : - :unknow - end - - def css_classes_for(issue) - "rel-#{relation_type_for(issue)}" - end - - def handle_issue_order - reverse_if_needed - - if TYPE_PRECEDES == relation_type - self.delay ||= 0 - else - self.delay = nil - end - set_issue_to_dates - end - - def set_issue_to_dates - soonest_start = self.successor_soonest_start - if soonest_start && issue_to - issue_to.reschedule_on!(soonest_start) - end - end - - def successor_soonest_start - if (TYPE_PRECEDES == self.relation_type) && delay && issue_from && - (issue_from.start_date || issue_from.due_date) - (issue_from.due_date || issue_from.start_date) + 1 + delay - end - end - - def <=>(relation) - r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order] - r == 0 ? id <=> relation.id : r - end - - private - - # Reverses the relation if needed so that it gets stored in the proper way - # Should not be reversed before validation so that it can be displayed back - # as entered on new relation form - def reverse_if_needed - if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse] - issue_tmp = issue_to - self.issue_to = issue_from - self.issue_from = issue_tmp - self.relation_type = TYPES[relation_type][:reverse] - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/08/08fb7d1dbcb8591c64d100b93daff66b95400b98.svn-base --- a/.svn/pristine/08/08fb7d1dbcb8591c64d100b93daff66b95400b98.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,185 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module DefaultData - class DataAlreadyLoaded < Exception; end - - module Loader - include Redmine::I18n - - class << self - # Returns true if no data is already loaded in the database - # otherwise false - def no_data? - !Role.find(:first, :conditions => {:builtin => 0}) && - !Tracker.find(:first) && - !IssueStatus.find(:first) && - !Enumeration.find(:first) - end - - # Loads the default data - # Raises a RecordNotSaved exception if something goes wrong - def load(lang=nil) - raise DataAlreadyLoaded.new("Some configuration data is already loaded.") unless no_data? - set_language_if_valid(lang) - - Role.transaction do - # Roles - manager = Role.create! :name => l(:default_role_manager), - :issues_visibility => 'all', - :position => 1 - manager.permissions = manager.setable_permissions.collect {|p| p.name} - manager.save! - - developer = Role.create! :name => l(:default_role_developer), - :position => 2, - :permissions => [:manage_versions, - :manage_categories, - :view_issues, - :add_issues, - :edit_issues, - :view_private_notes, - :set_notes_private, - :manage_issue_relations, - :manage_subtasks, - :add_issue_notes, - :save_queries, - :view_gantt, - :view_calendar, - :log_time, - :view_time_entries, - :comment_news, - :view_documents, - :view_wiki_pages, - :view_wiki_edits, - :edit_wiki_pages, - :delete_wiki_pages, - :add_messages, - :edit_own_messages, - :view_files, - :manage_files, - :browse_repository, - :view_changesets, - :commit_access, - :manage_related_issues] - - reporter = Role.create! :name => l(:default_role_reporter), - :position => 3, - :permissions => [:view_issues, - :add_issues, - :add_issue_notes, - :save_queries, - :view_gantt, - :view_calendar, - :log_time, - :view_time_entries, - :comment_news, - :view_documents, - :view_wiki_pages, - :view_wiki_edits, - :add_messages, - :edit_own_messages, - :view_files, - :browse_repository, - :view_changesets] - - Role.non_member.update_attribute :permissions, [:view_issues, - :add_issues, - :add_issue_notes, - :save_queries, - :view_gantt, - :view_calendar, - :view_time_entries, - :comment_news, - :view_documents, - :view_wiki_pages, - :view_wiki_edits, - :add_messages, - :view_files, - :browse_repository, - :view_changesets] - - Role.anonymous.update_attribute :permissions, [:view_issues, - :view_gantt, - :view_calendar, - :view_time_entries, - :view_documents, - :view_wiki_pages, - :view_wiki_edits, - :view_files, - :browse_repository, - :view_changesets] - - # Trackers - Tracker.create!(:name => l(:default_tracker_bug), :is_in_chlog => true, :is_in_roadmap => false, :position => 1) - Tracker.create!(:name => l(:default_tracker_feature), :is_in_chlog => true, :is_in_roadmap => true, :position => 2) - Tracker.create!(:name => l(:default_tracker_support), :is_in_chlog => false, :is_in_roadmap => false, :position => 3) - - # Issue statuses - new = IssueStatus.create!(:name => l(:default_issue_status_new), :is_closed => false, :is_default => true, :position => 1) - in_progress = IssueStatus.create!(:name => l(:default_issue_status_in_progress), :is_closed => false, :is_default => false, :position => 2) - resolved = IssueStatus.create!(:name => l(:default_issue_status_resolved), :is_closed => false, :is_default => false, :position => 3) - feedback = IssueStatus.create!(:name => l(:default_issue_status_feedback), :is_closed => false, :is_default => false, :position => 4) - closed = IssueStatus.create!(:name => l(:default_issue_status_closed), :is_closed => true, :is_default => false, :position => 5) - rejected = IssueStatus.create!(:name => l(:default_issue_status_rejected), :is_closed => true, :is_default => false, :position => 6) - - # Workflow - Tracker.find(:all).each { |t| - IssueStatus.find(:all).each { |os| - IssueStatus.find(:all).each { |ns| - WorkflowTransition.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns - } - } - } - - Tracker.find(:all).each { |t| - [new, in_progress, resolved, feedback].each { |os| - [in_progress, resolved, feedback, closed].each { |ns| - WorkflowTransition.create!(:tracker_id => t.id, :role_id => developer.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns - } - } - } - - Tracker.find(:all).each { |t| - [new, in_progress, resolved, feedback].each { |os| - [closed].each { |ns| - WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns - } - } - WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => resolved.id, :new_status_id => feedback.id) - } - - # Enumerations - IssuePriority.create!(:name => l(:default_priority_low), :position => 1) - IssuePriority.create!(:name => l(:default_priority_normal), :position => 2, :is_default => true) - IssuePriority.create!(:name => l(:default_priority_high), :position => 3) - IssuePriority.create!(:name => l(:default_priority_urgent), :position => 4) - IssuePriority.create!(:name => l(:default_priority_immediate), :position => 5) - - DocumentCategory.create!(:name => l(:default_doc_category_user), :position => 1) - DocumentCategory.create!(:name => l(:default_doc_category_tech), :position => 2) - - TimeEntryActivity.create!(:name => l(:default_activity_design), :position => 1) - TimeEntryActivity.create!(:name => l(:default_activity_development), :position => 2) - end - true - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/09/09050a06606068974830186c1100d7dceca803ff.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09050a06606068974830186c1100d7dceca803ff.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,31 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::NotifiableTest < ActiveSupport::TestCase + def setup + end + + def test_all + assert_equal 12, Redmine::Notifiable.all.length + + %w(issue_added issue_updated issue_note_added issue_status_updated issue_priority_updated news_added news_comment_added document_added file_added message_posted wiki_content_added wiki_content_updated).each do |notifiable| + assert Redmine::Notifiable.all.collect(&:name).include?(notifiable), "missing #{notifiable}" + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/09/0930e7dcd668d1bc526e085d51f359ac0b2bfdf3.svn-base --- a/.svn/pristine/09/0930e7dcd668d1bc526e085d51f359ac0b2bfdf3.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -api.user do - api.id @user.id - api.login @user.login if User.current.admin? - api.firstname @user.firstname - api.lastname @user.lastname - api.mail @user.mail if User.current.admin? || !@user.pref.hide_mail - api.created_on @user.created_on - api.last_login_on @user.last_login_on - - render_api_custom_values @user.visible_custom_field_values, api - - api.array :groups do |groups| - @user.groups.each do |group| - api.group :id => group.id, :name => group.name - end - end if User.current.admin? && include_in_api_response?('groups') - - api.array :memberships do - @memberships.each do |membership| - api.membership do - api.id membership.id - api.project :id => membership.project.id, :name => membership.project.name - api.array :roles do - membership.member_roles.each do |member_role| - if member_role.role - attrs = {:id => member_role.role.id, :name => member_role.role.name} - attrs.merge!(:inherited => true) if member_role.inherited_from.present? - api.role attrs - end - end - end - end if membership.project - end - end if include_in_api_response?('memberships') && @memberships -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/09/09319bb04693b301a469674c2043f8c089fbdcbb.svn-base --- a/.svn/pristine/09/09319bb04693b301a469674c2043f8c089fbdcbb.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4349 +0,0 @@ -#============================================================+ -# File name : tcpdf.rb -# Begin : 2002-08-03 -# Last Update : 2007-03-20 -# Author : Nicola Asuni -# Version : 1.53.0.TC031 -# License : GNU LGPL (http://www.gnu.org/copyleft/lesser.html) -# -# Description : This is a Ruby class for generating PDF files -# on-the-fly without requiring external -# extensions. -# -# IMPORTANT: -# This class is an extension and improvement of the Public Domain -# FPDF class by Olivier Plathey (http://www.fpdf.org). -# -# Main changes by Nicola Asuni: -# Ruby porting; -# UTF-8 Unicode support; -# code refactoring; -# source code clean up; -# code style and formatting; -# source code documentation using phpDocumentor (www.phpdoc.org); -# All ISO page formats were included; -# image scale factor; -# includes methods to parse and printsome XHTML code, supporting the following elements: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small; -# includes a method to print various barcode formats using an improved version of "Generic Barcode Render Class" by Karim Mribti (http://www.mribti.com/barcode/) (require GD library: http://www.boutell.com/gd/); -# defines standard Header() and Footer() methods. -# -# Ported to Ruby by Ed Moss 2007-08-06 -# -#============================================================+ - -require 'tempfile' -require 'core/rmagick' - -# -# TCPDF Class. -# @package com.tecnick.tcpdf -# - -@@version = "1.53.0.TC031" -@@fpdf_charwidths = {} - -PDF_PRODUCER = 'TCPDF via RFPDF 1.53.0.TC031 (http://tcpdf.sourceforge.net)' - -module TCPDFFontDescriptor - @@descriptors = { 'freesans' => {} } - @@font_name = 'freesans' - - def self.font(font_name) - @@descriptors[font_name.gsub(".rb", "")] - end - - def self.define(font_name = 'freesans') - @@descriptors[font_name] ||= {} - yield @@descriptors[font_name] - end -end - -# This is a Ruby class for generating PDF files on-the-fly without requiring external extensions.
    -# This class is an extension and improvement of the FPDF class by Olivier Plathey (http://www.fpdf.org).
    -# This version contains some changes: [porting to Ruby, support for UTF-8 Unicode, code style and formatting, php documentation (www.phpdoc.org), ISO page formats, minor improvements, image scale factor]
    -# TCPDF project (http://tcpdf.sourceforge.net) is based on the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org).
    -# To add your own TTF fonts please read /fonts/README.TXT -# @name TCPDF -# @package com.tecnick.tcpdf -# @@version 1.53.0.TC031 -# @author Nicola Asuni -# @link http://tcpdf.sourceforge.net -# @license http://www.gnu.org/copyleft/lesser.html LGPL -# -class TCPDF - include RFPDF - include Core::RFPDF - include RFPDF::Math - - def logger - Rails.logger - end - - cattr_accessor :k_cell_height_ratio - @@k_cell_height_ratio = 1.25 - - cattr_accessor :k_blank_image - @@k_blank_image = "" - - cattr_accessor :k_small_ratio - @@k_small_ratio = 2/3.0 - - cattr_accessor :k_path_cache - @@k_path_cache = Rails.root.join('tmp') - - cattr_accessor :k_path_url_cache - @@k_path_url_cache = Rails.root.join('tmp') - - attr_accessor :barcode - - attr_accessor :buffer - - attr_accessor :diffs - - attr_accessor :color_flag - - attr_accessor :default_table_columns - - attr_accessor :max_table_columns - - attr_accessor :default_font - - attr_accessor :draw_color - - attr_accessor :encoding - - attr_accessor :fill_color - - attr_accessor :fonts - - attr_accessor :font_family - - attr_accessor :font_files - - cattr_accessor :font_path - - attr_accessor :font_style - - attr_accessor :font_size_pt - - attr_accessor :header_width - - attr_accessor :header_logo - - attr_accessor :header_logo_width - - attr_accessor :header_title - - attr_accessor :header_string - - attr_accessor :images - - attr_accessor :img_scale - - attr_accessor :in_footer - - attr_accessor :is_unicode - - attr_accessor :lasth - - attr_accessor :links - - attr_accessor :list_ordered - - attr_accessor :list_count - - attr_accessor :li_spacer - - attr_accessor :n - - attr_accessor :offsets - - attr_accessor :orientation_changes - - attr_accessor :page - - attr_accessor :page_links - - attr_accessor :pages - - attr_accessor :pdf_version - - attr_accessor :prevfill_color - - attr_accessor :prevtext_color - - attr_accessor :print_header - - attr_accessor :print_footer - - attr_accessor :state - - attr_accessor :tableborder - - attr_accessor :tdbegin - - attr_accessor :tdwidth - - attr_accessor :tdheight - - attr_accessor :tdalign - - attr_accessor :tdfill - - attr_accessor :tempfontsize - - attr_accessor :text_color - - attr_accessor :underline - - attr_accessor :ws - - # - # This is the class constructor. - # It allows to set up the page format, the orientation and - # the measure unit used in all the methods (except for the font sizes). - # @since 1.0 - # @param string :orientation page orientation. Possible values are (case insensitive):
    • P or Portrait (default)
    • L or Landscape
    - # @param string :unit User measure unit. Possible values are:
    • pt: point
    • mm: millimeter (default)
    • cm: centimeter
    • in: inch

    A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. - # @param mixed :format The format used for pages. It can be either one of the following values (case insensitive) or a custom format in the form of a two-element array containing the width and the height (expressed in the unit given by unit).
    • 4A0
    • 2A0
    • A0
    • A1
    • A2
    • A3
    • A4 (default)
    • A5
    • A6
    • A7
    • A8
    • A9
    • A10
    • B0
    • B1
    • B2
    • B3
    • B4
    • B5
    • B6
    • B7
    • B8
    • B9
    • B10
    • C0
    • C1
    • C2
    • C3
    • C4
    • C5
    • C6
    • C7
    • C8
    • C9
    • C10
    • RA0
    • RA1
    • RA2
    • RA3
    • RA4
    • SRA0
    • SRA1
    • SRA2
    • SRA3
    • SRA4
    • LETTER
    • LEGAL
    • EXECUTIVE
    • FOLIO
    - # @param boolean :unicode TRUE means that the input text is unicode (default = true) - # @param String :encoding charset encoding; default is UTF-8 - # - def initialize(orientation = 'P', unit = 'mm', format = 'A4', unicode = true, encoding = "UTF-8") - - # Set internal character encoding to ASCII# - #FIXME 2007-05-25 (EJM) Level=0 - - # if (respond_to?("mb_internal_encoding") and mb_internal_encoding()) - # @internal_encoding = mb_internal_encoding(); - # mb_internal_encoding("ASCII"); - # } - - #Some checks - dochecks(); - - #Initialization of properties - @barcode ||= false - @buffer ||= '' - @diffs ||= [] - @color_flag ||= false - @default_table_columns ||= 4 - @table_columns ||= 0 - @max_table_columns ||= [] - @tr_id ||= 0 - @max_td_page ||= [] - @max_td_y ||= [] - @t_columns ||= 0 - @default_font ||= "FreeSans" if unicode - @default_font ||= "Helvetica" - @draw_color ||= '0 G' - @encoding ||= "UTF-8" - @fill_color ||= '0 g' - @fonts ||= {} - @font_family ||= '' - @font_files ||= {} - @font_style ||= '' - @font_size ||= 12 - @font_size_pt ||= 12 - @header_width ||= 0 - @header_logo ||= "" - @header_logo_width ||= 30 - @header_title ||= "" - @header_string ||= "" - @images ||= {} - @img_scale ||= 1 - @in_footer ||= false - @is_unicode = unicode - @lasth ||= 0 - @links ||= [] - @list_ordered ||= [] - @list_count ||= [] - @li_spacer ||= "" - @li_count ||= 0 - @spacer ||= "" - @quote_count ||= 0 - @prevquote_count ||= 0 - @quote_top ||= [] - @quote_page ||= [] - @n ||= 2 - @offsets ||= [] - @orientation_changes ||= [] - @page ||= 0 - @page_links ||= {} - @pages ||= [] - @pdf_version ||= "1.3" - @prevfill_color ||= [255,255,255] - @prevtext_color ||= [0,0,0] - @print_header ||= false - @print_footer ||= false - @state ||= 0 - @tableborder ||= 0 - @tdbegin ||= false - @tdtext ||= '' - @tdwidth ||= 0 - @tdheight ||= 0 - @tdalign ||= "L" - @tdfill ||= 0 - @tempfontsize ||= 10 - @text_color ||= '0 g' - @underline ||= false - @deleted ||= false - @ws ||= 0 - - #Standard Unicode fonts - @core_fonts = { - 'courier'=>'Courier', - 'courierB'=>'Courier-Bold', - 'courierI'=>'Courier-Oblique', - 'courierBI'=>'Courier-BoldOblique', - 'helvetica'=>'Helvetica', - 'helveticaB'=>'Helvetica-Bold', - 'helveticaI'=>'Helvetica-Oblique', - 'helveticaBI'=>'Helvetica-BoldOblique', - 'times'=>'Times-Roman', - 'timesB'=>'Times-Bold', - 'timesI'=>'Times-Italic', - 'timesBI'=>'Times-BoldItalic', - 'symbol'=>'Symbol', - 'zapfdingbats'=>'ZapfDingbats'} - - #Scale factor - case unit.downcase - when 'pt' ; @k=1 - when 'mm' ; @k=72/25.4 - when 'cm' ; @k=72/2.54 - when 'in' ; @k=72 - else Error("Incorrect unit: #{unit}") - end - - #Page format - if format.is_a?(String) - # Page formats (45 standard ISO paper formats and 4 american common formats). - # Paper cordinates are calculated in this way: (inches# 72) where (1 inch = 2.54 cm) - case (format.upcase) - when '4A0' ; format = [4767.87,6740.79] - when '2A0' ; format = [3370.39,4767.87] - when 'A0' ; format = [2383.94,3370.39] - when 'A1' ; format = [1683.78,2383.94] - when 'A2' ; format = [1190.55,1683.78] - when 'A3' ; format = [841.89,1190.55] - when 'A4' ; format = [595.28,841.89] # ; default - when 'A5' ; format = [419.53,595.28] - when 'A6' ; format = [297.64,419.53] - when 'A7' ; format = [209.76,297.64] - when 'A8' ; format = [147.40,209.76] - when 'A9' ; format = [104.88,147.40] - when 'A10' ; format = [73.70,104.88] - when 'B0' ; format = [2834.65,4008.19] - when 'B1' ; format = [2004.09,2834.65] - when 'B2' ; format = [1417.32,2004.09] - when 'B3' ; format = [1000.63,1417.32] - when 'B4' ; format = [708.66,1000.63] - when 'B5' ; format = [498.90,708.66] - when 'B6' ; format = [354.33,498.90] - when 'B7' ; format = [249.45,354.33] - when 'B8' ; format = [175.75,249.45] - when 'B9' ; format = [124.72,175.75] - when 'B10' ; format = [87.87,124.72] - when 'C0' ; format = [2599.37,3676.54] - when 'C1' ; format = [1836.85,2599.37] - when 'C2' ; format = [1298.27,1836.85] - when 'C3' ; format = [918.43,1298.27] - when 'C4' ; format = [649.13,918.43] - when 'C5' ; format = [459.21,649.13] - when 'C6' ; format = [323.15,459.21] - when 'C7' ; format = [229.61,323.15] - when 'C8' ; format = [161.57,229.61] - when 'C9' ; format = [113.39,161.57] - when 'C10' ; format = [79.37,113.39] - when 'RA0' ; format = [2437.80,3458.27] - when 'RA1' ; format = [1729.13,2437.80] - when 'RA2' ; format = [1218.90,1729.13] - when 'RA3' ; format = [864.57,1218.90] - when 'RA4' ; format = [609.45,864.57] - when 'SRA0' ; format = [2551.18,3628.35] - when 'SRA1' ; format = [1814.17,2551.18] - when 'SRA2' ; format = [1275.59,1814.17] - when 'SRA3' ; format = [907.09,1275.59] - when 'SRA4' ; format = [637.80,907.09] - when 'LETTER' ; format = [612.00,792.00] - when 'LEGAL' ; format = [612.00,1008.00] - when 'EXECUTIVE' ; format = [521.86,756.00] - when 'FOLIO' ; format = [612.00,936.00] - #else then Error("Unknown page format: #{format}" - end - @fw_pt = format[0] - @fh_pt = format[1] - else - @fw_pt = format[0]*@k - @fh_pt = format[1]*@k - end - - @fw = @fw_pt/@k - @fh = @fh_pt/@k - - #Page orientation - orientation = orientation.downcase - if orientation == 'p' or orientation == 'portrait' - @def_orientation = 'P' - @w_pt = @fw_pt - @h_pt = @fh_pt - elsif orientation == 'l' or orientation == 'landscape' - @def_orientation = 'L' - @w_pt = @fh_pt - @h_pt = @fw_pt - else - Error("Incorrect orientation: #{orientation}") - end - - @fw = @w_pt/@k - @fh = @h_pt/@k - - @cur_orientation = @def_orientation - @w = @w_pt/@k - @h = @h_pt/@k - #Page margins (1 cm) - margin = 28.35/@k - SetMargins(margin, margin) - #Interior cell margin (1 mm) - @c_margin = margin / 10 - #Line width (0.2 mm) - @line_width = 0.567 / @k - #Automatic page break - SetAutoPageBreak(true, 2 * margin) - #Full width display mode - SetDisplayMode('fullwidth') - #Compression - SetCompression(true) - #Set default PDF version number - @pdf_version = "1.3" - - @encoding = encoding - @b = 0 - @i = 0 - @u = 0 - @href = '' - @fontlist = ["arial", "times", "courier", "helvetica", "symbol"] - @issetfont = false - @issetcolor = false - - SetFillColor(200, 200, 200, true) - SetTextColor(0, 0, 0, true) - end - - # - # Set the image scale. - # @param float :scale image scale. - # @author Nicola Asuni - # @since 1.5.2 - # - def SetImageScale(scale) - @img_scale = scale; - end - alias_method :set_image_scale, :SetImageScale - - # - # Returns the image scale. - # @return float image scale. - # @author Nicola Asuni - # @since 1.5.2 - # - def GetImageScale() - return @img_scale; - end - alias_method :get_image_scale, :GetImageScale - - # - # Returns the page width in units. - # @return int page width. - # @author Nicola Asuni - # @since 1.5.2 - # - def GetPageWidth() - return @w; - end - alias_method :get_page_width, :GetPageWidth - - # - # Returns the page height in units. - # @return int page height. - # @author Nicola Asuni - # @since 1.5.2 - # - def GetPageHeight() - return @h; - end - alias_method :get_page_height, :GetPageHeight - - # - # Returns the page break margin. - # @return int page break margin. - # @author Nicola Asuni - # @since 1.5.2 - # - def GetBreakMargin() - return @b_margin; - end - alias_method :get_break_margin, :GetBreakMargin - - # - # Returns the scale factor (number of points in user unit). - # @return int scale factor. - # @author Nicola Asuni - # @since 1.5.2 - # - def GetScaleFactor() - return @k; - end - alias_method :get_scale_factor, :GetScaleFactor - - # - # Defines the left, top and right margins. By default, they equal 1 cm. Call this method to change them. - # @param float :left Left margin. - # @param float :top Top margin. - # @param float :right Right margin. Default value is the left one. - # @since 1.0 - # @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak() - # - def SetMargins(left, top, right=-1) - #Set left, top and right margins - @l_margin = left - @t_margin = top - if (right == -1) - right = left - end - @r_margin = right - end - alias_method :set_margins, :SetMargins - - # - # Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin. - # @param float :margin The margin. - # @since 1.4 - # @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() - # - def SetLeftMargin(margin) - #Set left margin - @l_margin = margin - if ((@page>0) and (@x < margin)) - @x = margin - end - end - alias_method :set_left_margin, :SetLeftMargin - - # - # Defines the top margin. The method can be called before creating the first page. - # @param float :margin The margin. - # @since 1.5 - # @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() - # - def SetTopMargin(margin) - #Set top margin - @t_margin = margin - end - alias_method :set_top_margin, :SetTopMargin - - # - # Defines the right margin. The method can be called before creating the first page. - # @param float :margin The margin. - # @since 1.5 - # @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins() - # - def SetRightMargin(margin) - #Set right margin - @r_margin = margin - end - alias_method :set_right_margin, :SetRightMargin - - # - # Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm. - # @param boolean :auto Boolean indicating if mode should be on or off. - # @param float :margin Distance from the bottom of the page. - # @since 1.0 - # @see Cell(), MultiCell(), AcceptPageBreak() - # - def SetAutoPageBreak(auto, margin=0) - #Set auto page break mode and triggering margin - @auto_page_break = auto - @b_margin = margin - @page_break_trigger = @h - margin - end - alias_method :set_auto_page_break, :SetAutoPageBreak - - # - # Defines the way the document is to be displayed by the viewer. The zoom level can be set: pages can be displayed entirely on screen, occupy the full width of the window, use real size, be scaled by a specific zooming factor or use viewer default (configured in the Preferences menu of Acrobat). The page layout can be specified too: single at once, continuous display, two columns or viewer default. By default, documents use the full width mode with continuous display. - # @param mixed :zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use.
    • fullpage: displays the entire page on screen
    • fullwidth: uses maximum width of window
    • real: uses real size (equivalent to 100% zoom)
    • default: uses viewer default mode
    - # @param string :layout The page layout. Possible values are:
    • single: displays one page at once
    • continuous: displays pages continuously (default)
    • two: displays two pages on two columns
    • default: uses viewer default mode
    - # @since 1.2 - # - def SetDisplayMode(zoom, layout = 'continuous') - #Set display mode in viewer - if (zoom == 'fullpage' or zoom == 'fullwidth' or zoom == 'real' or zoom == 'default' or !zoom.is_a?(String)) - @zoom_mode = zoom - else - Error("Incorrect zoom display mode: #{zoom}") - end - if (layout == 'single' or layout == 'continuous' or layout == 'two' or layout == 'default') - @layout_mode = layout - else - Error("Incorrect layout display mode: #{layout}") - end - end - alias_method :set_display_mode, :SetDisplayMode - - # - # Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default. - # Note: the Zlib extension is required for this feature. If not present, compression will be turned off. - # @param boolean :compress Boolean indicating if compression must be enabled. - # @since 1.4 - # - def SetCompression(compress) - #Set page compression - if (respond_to?('gzcompress')) - @compress = compress - else - @compress = false - end - end - alias_method :set_compression, :SetCompression - - # - # Defines the title of the document. - # @param string :title The title. - # @since 1.2 - # @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject() - # - def SetTitle(title) - #Title of document - @title = title - end - alias_method :set_title, :SetTitle - - # - # Defines the subject of the document. - # @param string :subject The subject. - # @since 1.2 - # @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle() - # - def SetSubject(subject) - #Subject of document - @subject = subject - end - alias_method :set_subject, :SetSubject - - # - # Defines the author of the document. - # @param string :author The name of the author. - # @since 1.2 - # @see SetCreator(), SetKeywords(), SetSubject(), SetTitle() - # - def SetAuthor(author) - #Author of document - @author = author - end - alias_method :set_author, :SetAuthor - - # - # Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'. - # @param string :keywords The list of keywords. - # @since 1.2 - # @see SetAuthor(), SetCreator(), SetSubject(), SetTitle() - # - def SetKeywords(keywords) - #Keywords of document - @keywords = keywords - end - alias_method :set_keywords, :SetKeywords - - # - # Defines the creator of the document. This is typically the name of the application that generates the PDF. - # @param string :creator The name of the creator. - # @since 1.2 - # @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle() - # - def SetCreator(creator) - #Creator of document - @creator = creator - end - alias_method :set_creator, :SetCreator - - # - # Defines an alias for the total number of pages. It will be substituted as the document is closed.
    - # Example:
    - #
    -	# class PDF extends TCPDF {
    -	# 	def Footer()
    -	# 		#Go to 1.5 cm from bottom
    -	# 		SetY(-15);
    -	# 		#Select Arial italic 8
    -	# 		SetFont('Arial','I',8);
    -	# 		#Print current and total page numbers
    -	# 		Cell(0,10,'Page '.PageNo().'/{nb}',0,0,'C');
    -	# 	end
    -	# }
    -	# :pdf=new PDF();
    -	# :pdf->alias_nb_pages();
    -	# 
    - # @param string :alias The alias. Default valuenb}. - # @since 1.4 - # @see PageNo(), Footer() - # - def AliasNbPages(alias_nb ='{nb}') - #Define an alias for total number of pages - @alias_nb_pages = escapetext(alias_nb) - end - alias_method :alias_nb_pages, :AliasNbPages - - # - # This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid. - # 2004-06-11 :: Nicola Asuni : changed bold tag with strong - # @param string :msg The error message - # @since 1.0 - # - def Error(msg) - #Fatal error - raise ("TCPDF error: #{msg}") - end - alias_method :error, :Error - - # - # This method begins the generation of the PDF document. It is not necessary to call it explicitly because AddPage() does it automatically. - # Note: no page is created by this method - # @since 1.0 - # @see AddPage(), Close() - # - def Open() - #Begin document - @state = 1 - end - # alias_method :open, :Open - - # - # Terminates the PDF document. It is not necessary to call this method explicitly because Output() does it automatically. If the document contains no page, AddPage() is called to prevent from getting an invalid document. - # @since 1.0 - # @see Open(), Output() - # - def Close() - #Terminate document - if (@state==3) - return; - end - if (@page==0) - AddPage(); - end - #Page footer - @in_footer=true; - Footer(); - @in_footer=false; - #Close page - endpage(); - #Close document - enddoc(); - end - # alias_method :close, :Close - - # - # Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer. Then the page is added, the current position set to the top-left corner according to the left and top margins, and Header() is called to display the header. - # The font which was set before calling is automatically restored. There is no need to call SetFont() again if you want to continue with the same font. The same is true for colors and line width. - # The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards. - # @param string :orientation Page orientation. Possible values are (case insensitive):
    • P or Portrait
    • L or Landscape
    The default value is the one passed to the constructor. - # @since 1.0 - # @see TCPDF(), Header(), Footer(), SetMargins() - # - def AddPage(orientation='') - #Start a new page - if (@state==0) - Open(); - end - family=@font_family; - style=@font_style + (@underline ? 'U' : '') + (@deleted ? 'D' : ''); - size=@font_size_pt; - lw=@line_width; - dc=@draw_color; - fc=@fill_color; - tc=@text_color; - cf=@color_flag; - if (@page>0) - #Page footer - @in_footer=true; - Footer(); - @in_footer=false; - #Close page - endpage(); - end - #Start new page - beginpage(orientation); - #Set line cap style to square - out('2 J'); - #Set line width - @line_width = lw; - out(sprintf('%.2f w', lw*@k)); - #Set font - if (family) - SetFont(family, style, size); - end - #Set colors - @draw_color = dc; - if (dc!='0 G') - out(dc); - end - @fill_color = fc; - if (fc!='0 g') - out(fc); - end - @text_color = tc; - @color_flag = cf; - #Page header - Header(); - #Restore line width - if (@line_width != lw) - @line_width = lw; - out(sprintf('%.2f w', lw*@k)); - end - #Restore font - if (family) - SetFont(family, style, size); - end - #Restore colors - if (@draw_color != dc) - @draw_color = dc; - out(dc); - end - if (@fill_color != fc) - @fill_color = fc; - out(fc); - end - @text_color = tc; - @color_flag = cf; - end - alias_method :add_page, :AddPage - - # - # Rotate object. - # @param float :angle angle in degrees for counter-clockwise rotation - # @param int :x abscissa of the rotation center. Default is current x position - # @param int :y ordinate of the rotation center. Default is current y position - # - def Rotate(angle, x="", y="") - - if (x == '') - x = @x; - end - - if (y == '') - y = @y; - end - - if (@rtl) - x = @w - x; - angle = -@angle; - end - - y = (@h - y) * @k; - x *= @k; - - # calculate elements of transformation matrix - tm = [] - tm[0] = ::Math::cos(deg2rad(angle)); - tm[1] = ::Math::sin(deg2rad(angle)); - tm[2] = -tm[1]; - tm[3] = tm[0]; - tm[4] = x + tm[1] * y - tm[0] * x; - tm[5] = y - tm[0] * y - tm[1] * x; - - # generate the transformation matrix - Transform(tm); - end - alias_method :rotate, :Rotate - - # - # Starts a 2D tranformation saving current graphic state. - # This function must be called before scaling, mirroring, translation, rotation and skewing. - # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior. - # - def StartTransform - out('q'); - end - alias_method :start_transform, :StartTransform - - # - # Stops a 2D tranformation restoring previous graphic state. - # This function must be called after scaling, mirroring, translation, rotation and skewing. - # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior. - # - def StopTransform - out('Q'); - end - alias_method :stop_transform, :StopTransform - - # - # Apply graphic transformations. - # @since 2.1.000 (2008-01-07) - # @see StartTransform(), StopTransform() - # - def Transform(tm) - x = out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', tm[0], tm[1], tm[2], tm[3], tm[4], tm[5])); - end - alias_method :transform, :Transform - - # - # Set header data. - # @param string :ln header image logo - # @param string :lw header image logo width in mm - # @param string :ht string to print as title on document header - # @param string :hs string to print on document header - # - def SetHeaderData(ln="", lw=0, ht="", hs="") - @header_logo = ln || "" - @header_logo_width = lw || 0 - @header_title = ht || "" - @header_string = hs || "" - end - alias_method :set_header_data, :SetHeaderData - - # - # Set header margin. - # (minimum distance between header and top page margin) - # @param int :hm distance in millimeters - # - def SetHeaderMargin(hm=10) - @header_margin = hm; - end - alias_method :set_header_margin, :SetHeaderMargin - - # - # Set footer margin. - # (minimum distance between footer and bottom page margin) - # @param int :fm distance in millimeters - # - def SetFooterMargin(fm=10) - @footer_margin = fm; - end - alias_method :set_footer_margin, :SetFooterMargin - - # - # Set a flag to print page header. - # @param boolean :val set to true to print the page header (default), false otherwise. - # - def SetPrintHeader(val=true) - @print_header = val; - end - alias_method :set_print_header, :SetPrintHeader - - # - # Set a flag to print page footer. - # @param boolean :value set to true to print the page footer (default), false otherwise. - # - def SetPrintFooter(val=true) - @print_footer = val; - end - alias_method :set_print_footer, :SetPrintFooter - - # - # This method is used to render the page header. - # It is automatically called by AddPage() and could be overwritten in your own inherited class. - # - def Header() - if (@print_header) - if (@original_l_margin.nil?) - @original_l_margin = @l_margin; - end - if (@original_r_margin.nil?) - @original_r_margin = @r_margin; - end - - #set current position - SetXY(@original_l_margin, @header_margin); - - if ((@header_logo) and (@header_logo != @@k_blank_image)) - Image(@header_logo, @original_l_margin, @header_margin, @header_logo_width); - else - @img_rb_y = GetY(); - end - - cell_height = ((@@k_cell_height_ratio * @header_font[2]) / @k).round(2) - - header_x = @original_l_margin + (@header_logo_width * 1.05); #set left margin for text data cell - - # header title - SetFont(@header_font[0], 'B', @header_font[2] + 1); - SetX(header_x); - Cell(@header_width, cell_height, @header_title, 0, 1, 'L'); - - # header string - SetFont(@header_font[0], @header_font[1], @header_font[2]); - SetX(header_x); - MultiCell(@header_width, cell_height, @header_string, 0, 'L', 0); - - # print an ending header line - if (@header_width) - #set style for cell border - SetLineWidth(0.3); - SetDrawColor(0, 0, 0); - SetY(1 + (@img_rb_y > GetY() ? @img_rb_y : GetY())); - SetX(@original_l_margin); - Cell(0, 0, '', 'T', 0, 'C'); - end - - #restore position - SetXY(@original_l_margin, @t_margin); - end - end - alias_method :header, :Header - - # - # This method is used to render the page footer. - # It is automatically called by AddPage() and could be overwritten in your own inherited class. - # - def Footer() - if (@print_footer) - - if (@original_l_margin.nil?) - @original_l_margin = @l_margin; - end - if (@original_r_margin.nil?) - @original_r_margin = @r_margin; - end - - #set font - SetFont(@footer_font[0], @footer_font[1] , @footer_font[2]); - #set style for cell border - line_width = 0.3; - SetLineWidth(line_width); - SetDrawColor(0, 0, 0); - - footer_height = ((@@k_cell_height_ratio * @footer_font[2]) / @k).round; #footer height, was , 2) - #get footer y position - footer_y = @h - @footer_margin - footer_height; - #set current position - SetXY(@original_l_margin, footer_y); - - #print document barcode - if (@barcode) - Ln(); - barcode_width = ((@w - @original_l_margin - @original_r_margin)).round; #max width - writeBarcode(@original_l_margin, footer_y + line_width, barcode_width, footer_height - line_width, "C128B", false, false, 2, @barcode); - end - - SetXY(@original_l_margin, footer_y); - - #Print page number - Cell(0, footer_height, @l['w_page'] + " " + PageNo().to_s + ' / {nb}', 'T', 0, 'R'); - end - end - alias_method :footer, :Footer - - # - # Returns the current page number. - # @return int page number - # @since 1.0 - # @see alias_nb_pages() - # - def PageNo() - #Get current page number - return @page; - end - alias_method :page_no, :PageNo - - # - # Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. - # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255 - # @param int :g Green component (between 0 and 255) - # @param int :b Blue component (between 0 and 255) - # @since 1.3 - # @see SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell() - # - def SetDrawColor(r, g=-1, b=-1) - #Set color for all stroking operations - if ((r==0 and g==0 and b==0) or g==-1) - @draw_color=sprintf('%.3f G', r/255.0); - else - @draw_color=sprintf('%.3f %.3f %.3f RG', r/255.0, g/255.0, b/255.0); - end - if (@page>0) - out(@draw_color); - end - end - alias_method :set_draw_color, :SetDrawColor - - # - # Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. - # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255 - # @param int :g Green component (between 0 and 255) - # @param int :b Blue component (between 0 and 255) - # @param boolean :storeprev if true stores the RGB array on :prevfill_color variable. - # @since 1.3 - # @see SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell() - # - def SetFillColor(r, g=-1, b=-1, storeprev=false) - #Set color for all filling operations - if ((r==0 and g==0 and b==0) or g==-1) - @fill_color=sprintf('%.3f g', r/255.0); - else - @fill_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0); - end - @color_flag=(@fill_color!=@text_color); - if (@page>0) - out(@fill_color); - end - if (storeprev) - # store color as previous value - @prevfill_color = [r, g, b] - end - end - alias_method :set_fill_color, :SetFillColor - - # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors - def SetCmykFillColor(c, m, y, k, storeprev=false) - #Set color for all filling operations - @fill_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k); - @color_flag=(@fill_color!=@text_color); - if (storeprev) - # store color as previous value - @prevtext_color = [c, m, y, k] - end - if (@page>0) - out(@fill_color); - end - end - alias_method :set_cmyk_fill_color, :SetCmykFillColor - - # - # Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. - # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255 - # @param int :g Green component (between 0 and 255) - # @param int :b Blue component (between 0 and 255) - # @param boolean :storeprev if true stores the RGB array on :prevtext_color variable. - # @since 1.3 - # @see SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell() - # - def SetTextColor(r, g=-1, b=-1, storeprev=false) - #Set color for text - if ((r==0 and :g==0 and :b==0) or :g==-1) - @text_color=sprintf('%.3f g', r/255.0); - else - @text_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0); - end - @color_flag=(@fill_color!=@text_color); - if (storeprev) - # store color as previous value - @prevtext_color = [r, g, b] - end - end - alias_method :set_text_color, :SetTextColor - - # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors - def SetCmykTextColor(c, m, y, k, storeprev=false) - #Set color for text - @text_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k); - @color_flag=(@fill_color!=@text_color); - if (storeprev) - # store color as previous value - @prevtext_color = [c, m, y, k] - end - end - alias_method :set_cmyk_text_color, :SetCmykTextColor - - # - # Returns the length of a string in user unit. A font must be selected.
    - # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02] - # @param string :s The string whose length is to be computed - # @return int - # @since 1.2 - # - def GetStringWidth(s) - #Get width of a string in the current font - s = s.to_s; - cw = @current_font['cw'] - w = 0; - if (@is_unicode) - unicode = UTF8StringToArray(s); - unicode.each do |char| - if (!cw[char].nil?) - w += cw[char]; - # This should not happen. UTF8StringToArray should guarentee the array is ascii values. - # elsif (c!cw[char[0]].nil?) - # w += cw[char[0]]; - # elsif (!cw[char.chr].nil?) - # w += cw[char.chr]; - elsif (!@current_font['desc']['MissingWidth'].nil?) - w += @current_font['desc']['MissingWidth']; # set default size - else - w += 500; - end - end - else - s.each_byte do |c| - if cw[c.chr] - w += cw[c.chr]; - elsif cw[?c.chr] - w += cw[?c.chr] - end - end - end - return (w * @font_size / 1000.0); - end - alias_method :get_string_width, :GetStringWidth - - # - # Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page. - # @param float :width The width. - # @since 1.0 - # @see Line(), Rect(), Cell(), MultiCell() - # - def SetLineWidth(width) - #Set line width - @line_width = width; - if (@page>0) - out(sprintf('%.2f w', width*@k)); - end - end - alias_method :set_line_width, :SetLineWidth - - # - # Draws a line between two points. - # @param float :x1 Abscissa of first point - # @param float :y1 Ordinate of first point - # @param float :x2 Abscissa of second point - # @param float :y2 Ordinate of second point - # @since 1.0 - # @see SetLineWidth(), SetDrawColor() - # - def Line(x1, y1, x2, y2) - #Draw a line - out(sprintf('%.2f %.2f m %.2f %.2f l S', x1 * @k, (@h - y1) * @k, x2 * @k, (@h - y2) * @k)); - end - alias_method :line, :Line - - def Circle(mid_x, mid_y, radius, style='') - mid_y = (@h-mid_y)*@k - out(sprintf("q\n")) # postscript content in pdf - # init line type etc. with /GSD gs G g (grey) RG rg (RGB) w=line witdh etc. - out(sprintf("1 j\n")) # line join - # translate ("move") circle to mid_y, mid_y - out(sprintf("1 0 0 1 %f %f cm", mid_x, mid_y)) - kappa = 0.5522847498307933984022516322796 - # Quadrant 1 - x_s = 0.0 # 12 o'clock - y_s = 0.0 + radius - x_e = 0.0 + radius # 3 o'clock - y_e = 0.0 - out(sprintf("%f %f m\n", x_s, y_s)) # move to 12 o'clock - # cubic bezier control point 1, start height and kappa * radius to the right - bx_e1 = x_s + (radius * kappa) - by_e1 = y_s - # cubic bezier control point 2, end and kappa * radius above - bx_e2 = x_e - by_e2 = y_e + (radius * kappa) - # draw cubic bezier from current point to x_e/y_e with bx_e1/by_e1 and bx_e2/by_e2 as bezier control points - out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) - # Quadrant 2 - x_s = x_e - y_s = y_e # 3 o'clock - x_e = 0.0 - y_e = 0.0 - radius # 6 o'clock - bx_e1 = x_s # cubic bezier point 1 - by_e1 = y_s - (radius * kappa) - bx_e2 = x_e + (radius * kappa) # cubic bezier point 2 - by_e2 = y_e - out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) - # Quadrant 3 - x_s = x_e - y_s = y_e # 6 o'clock - x_e = 0.0 - radius - y_e = 0.0 # 9 o'clock - bx_e1 = x_s - (radius * kappa) # cubic bezier point 1 - by_e1 = y_s - bx_e2 = x_e # cubic bezier point 2 - by_e2 = y_e - (radius * kappa) - out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) - # Quadrant 4 - x_s = x_e - y_s = y_e # 9 o'clock - x_e = 0.0 - y_e = 0.0 + radius # 12 o'clock - bx_e1 = x_s # cubic bezier point 1 - by_e1 = y_s + (radius * kappa) - bx_e2 = x_e - (radius * kappa) # cubic bezier point 2 - by_e2 = y_e - out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) - if style=='F' - op='f' - elsif style=='FD' or style=='DF' - op='b' - else - op='s' - end - out(sprintf("#{op}\n")) # stroke circle, do not fill and close path - # for filling etc. b, b*, f, f* - out(sprintf("Q\n")) # finish postscript in PDF - end - alias_method :circle, :Circle - - # - # Outputs a rectangle. It can be drawn (border only), filled (with no border) or both. - # @param float :x Abscissa of upper-left corner - # @param float :y Ordinate of upper-left corner - # @param float :w Width - # @param float :h Height - # @param string :style Style of rendering. Possible values are:
    • D or empty string: draw (default)
    • F: fill
    • DF or FD: draw and fill
    - # @since 1.0 - # @see SetLineWidth(), SetDrawColor(), SetFillColor() - # - def Rect(x, y, w, h, style='') - #Draw a rectangle - if (style=='F') - op='f'; - elsif (style=='FD' or style=='DF') - op='B'; - else - op='S'; - end - out(sprintf('%.2f %.2f %.2f %.2f re %s', x * @k, (@h - y) * @k, w * @k, -h * @k, op)); - end - alias_method :rect, :Rect - - # - # Imports a TrueType or Type1 font and makes it available. It is necessary to generate a font definition file first with the makefont.rb utility. The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by FPDF_FONTPATH if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated. - # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02]. - # Example:
    - #
    -	# :pdf->AddFont('Comic','I');
    -	# # is equivalent to:
    -	# :pdf->AddFont('Comic','I','comici.rb');
    -	# 
    - # @param string :family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font. - # @param string :style Font style. Possible values are (case insensitive):
    • empty string: regular (default)
    • B: bold
    • I: italic
    • BI or IB: bold italic
    - # @param string :file The font definition file. By default, the name is built from the family and style, in lower case with no space. - # @since 1.5 - # @see SetFont() - # - def AddFont(family, style='', file='') - if (family.empty?) - return; - end - - #Add a TrueType or Type1 font - family = family.downcase - if ((!@is_unicode) and (family == 'arial')) - family = 'helvetica'; - end - - style=style.upcase - style=style.gsub('U',''); - style=style.gsub('D',''); - if (style == 'IB') - style = 'BI'; - end - - fontkey = family + style; - # check if the font has been already added - if !@fonts[fontkey].nil? - return; - end - - if (file=='') - file = family.gsub(' ', '') + style.downcase + '.rb'; - end - font_file_name = getfontpath(file) - if (font_file_name.nil?) - # try to load the basic file without styles - file = family.gsub(' ', '') + '.rb'; - font_file_name = getfontpath(file) - end - if font_file_name.nil? - Error("Could not find font #{file}.") - end - require(getfontpath(file)) - font_desc = TCPDFFontDescriptor.font(file) - - if (font_desc[:name].nil? and @@fpdf_charwidths.nil?) - Error('Could not include font definition file'); - end - - i = @fonts.length+1; - if (@is_unicode) - @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg], 'cMap' => font_desc[:cMap], 'registry' => font_desc[:registry]} - @@fpdf_charwidths[fontkey] = font_desc[:cw]; - else - @fonts[fontkey]={'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]} - @@fpdf_charwidths[fontkey] = font_desc[:cw]; - end - - if (!font_desc[:diff].nil? and (!font_desc[:diff].empty?)) - #Search existing encodings - d=0; - nb=@diffs.length; - 1.upto(nb) do |i| - if (@diffs[i]== font_desc[:diff]) - d = i; - break; - end - end - if (d==0) - d = nb+1; - @diffs[d] = font_desc[:diff]; - end - @fonts[fontkey]['diff'] = d; - end - if (font_desc[:file] and font_desc[:file].length > 0) - if (font_desc[:type] == "TrueType") or (font_desc[:type] == "TrueTypeUnicode") - @font_files[font_desc[:file]] = {'length1' => font_desc[:originalsize]} - else - @font_files[font_desc[:file]] = {'length1' => font_desc[:size1], 'length2' => font_desc[:size2]} - end - end - end - alias_method :add_font, :AddFont - - # - # Sets the font used to print character strings. It is mandatory to call this method at least once before printing text or the resulting document would not be valid. - # The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe). - # The method can be called before the first page is created and the font is retained from page to page. - # If you just wish to change the current font size, it is simpler to call SetFontSize(). - # Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:
    • They are in the current directory (the one where the running script lies)
    • They are in one of the directories defined by the include_path parameter
    • They are in the directory defined by the FPDF_FONTPATH constant

    - # Example for the last case (note the trailing slash):
    - #
    -	# define('FPDF_FONTPATH','/home/www/font/');
    -	# require('tcpdf.rb');
    -	#
    -	# #Times regular 12
    -	# :pdf->SetFont('Times');
    -	# #Arial bold 14
    -	# :pdf->SetFont('Arial','B',14);
    -	# #Removes bold
    -	# :pdf->SetFont('');
    -	# #Times bold, italic and underlined 14
    -	# :pdf->SetFont('Times','BIUD');
    -	# 

    - # If the file corresponding to the requested font is not found, the error "Could not include font metric file" is generated. - # @param string :family Family font. It can be either a name defined by AddFont() or one of the standard families (case insensitive):
    • Courier (fixed-width)
    • Helvetica or Arial (synonymous; sans serif)
    • Times (serif)
    • Symbol (symbolic)
    • ZapfDingbats (symbolic)
    It is also possible to pass an empty string. In that case, the current family is retained. - # @param string :style Font style. Possible values are (case insensitive):
    • empty string: regular
    • B: bold
    • I: italic
    • U: underline
    or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats - # @param float :size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12 - # @since 1.0 - # @see AddFont(), SetFontSize(), Cell(), MultiCell(), Write() - # - def SetFont(family, style='', size=0) - # save previous values - @prevfont_family = @font_family; - @prevfont_style = @font_style; - - family=family.downcase; - if (family=='') - family=@font_family; - end - if ((!@is_unicode) and (family == 'arial')) - family = 'helvetica'; - elsif ((family=="symbol") or (family=="zapfdingbats")) - style=''; - end - - style=style.upcase; - - if (style.include?('U')) - @underline=true; - style= style.gsub('U',''); - else - @underline=false; - end - if (style.include?('D')) - @deleted=true; - style= style.gsub('D',''); - else - @deleted=false; - end - if (style=='IB') - style='BI'; - end - if (size==0) - size=@font_size_pt; - end - - # try to add font (if not already added) - AddFont(family, style); - - #Test if font is already selected - if ((@font_family == family) and (@font_style == style) and (@font_size_pt == size)) - return; - end - - fontkey = family + style; - style = '' if (@fonts[fontkey].nil? and !@fonts[family].nil?) - - #Test if used for the first time - if (@fonts[fontkey].nil?) - #Check if one of the standard fonts - if (!@core_fonts[fontkey].nil?) - if @@fpdf_charwidths[fontkey].nil? - #Load metric file - file = family; - if ((family!='symbol') and (family!='zapfdingbats')) - file += style.downcase; - end - if (getfontpath(file + '.rb').nil?) - # try to load the basic file without styles - file = family; - fontkey = family; - end - require(getfontpath(file + '.rb')); - font_desc = TCPDFFontDescriptor.font(file) - if ((@is_unicode and ctg.nil?) or ((!@is_unicode) and (@@fpdf_charwidths[fontkey].nil?)) ) - Error("Could not include font metric file [" + fontkey + "]: " + getfontpath(file + ".rb")); - end - end - i = @fonts.length + 1; - - if (@is_unicode) - @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg]} - @@fpdf_charwidths[fontkey] = font_desc[:cw]; - else - @fonts[fontkey] = {'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]} - @@fpdf_charwidths[fontkey] = font_desc[:cw]; - end - else - Error('Undefined font: ' + family + ' ' + style); - end - end - #Select it - @font_family = family; - @font_style = style; - @font_size_pt = size; - @font_size = size / @k; - @current_font = @fonts[fontkey]; # was & may need deep copy? - if (@page>0) - out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt)); - end - end - alias_method :set_font, :SetFont - - # - # Defines the size of the current font. - # @param float :size The size (in points) - # @since 1.0 - # @see SetFont() - # - def SetFontSize(size) - #Set font size in points - if (@font_size_pt== size) - return; - end - @font_size_pt = size; - @font_size = size.to_f / @k; - if (@page > 0) - out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt)); - end - end - alias_method :set_font_size, :SetFontSize - - # - # Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.
    - # The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink(). - # @since 1.5 - # @see Cell(), Write(), Image(), Link(), SetLink() - # - def AddLink() - #Create a new internal link - n=@links.length+1; - @links[n]=[0,0]; - return n; - end - alias_method :add_link, :AddLink - - # - # Defines the page and position a link points to - # @param int :link The link identifier returned by AddLink() - # @param float :y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page) - # @param int :page Number of target page; -1 indicates the current page. This is the default value - # @since 1.5 - # @see AddLink() - # - def SetLink(link, y=0, page=-1) - #Set destination of internal link - if (y==-1) - y=@y; - end - if (page==-1) - page=@page; - end - @links[link] = [page, y] - end - alias_method :set_link, :SetLink - - # - # Puts a link on a rectangular area of the page. Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image. - # @param float :x Abscissa of the upper-left corner of the rectangle - # @param float :y Ordinate of the upper-left corner of the rectangle - # @param float :w Width of the rectangle - # @param float :h Height of the rectangle - # @param mixed :link URL or identifier returned by AddLink() - # @since 1.5 - # @see AddLink(), Cell(), Write(), Image() - # - def Link(x, y, w, h, link) - #Put a link on the page - @page_links ||= Array.new - @page_links[@page] ||= Array.new - @page_links[@page].push([x * @k, @h_pt - y * @k, w * @k, h*@k, link]); - end - alias_method :link, :Link - - # - # Prints a character string. The origin is on the left of the first charcter, on the baseline. This method allows to place a string precisely on the page, but it is usually easier to use Cell(), MultiCell() or Write() which are the standard methods to print text. - # @param float :x Abscissa of the origin - # @param float :y Ordinate of the origin - # @param string :txt String to print - # @since 1.0 - # @see SetFont(), SetTextColor(), Cell(), MultiCell(), Write() - # - def Text(x, y, txt) - #Output a string - s=sprintf('BT %.2f %.2f Td (%s) Tj ET', x * @k, (@h-y) * @k, escapetext(txt)); - if (@underline and (txt!='')) - s += ' ' + dolinetxt(x, y, txt); - end - if (@color_flag) - s='q ' + @text_color + ' ' + s + ' Q'; - end - out(s); - end - alias_method :text, :Text - - # - # Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value. The default implementation returns a value according to the mode selected by SetAutoPageBreak().
    - # This method is called automatically and should not be called directly by the application.
    - # Example:
    - # The method is overriden in an inherited class in order to obtain a 3 column layout:
    - #
    -	# class PDF extends TCPDF {
    -	# 	var :col=0;
    -	#
    -	# 	def SetCol(col)
    -	# 		#Move position to a column
    -	# 		@col = col;
    -	# 		:x=10+:col*65;
    -	# 		SetLeftMargin(x);
    -	# 		SetX(x);
    -	# 	end
    -	#
    -	# 	def AcceptPageBreak()
    -	# 		if (@col<2)
    -	# 			#Go to next column
    -	# 			SetCol(@col+1);
    -	# 			SetY(10);
    -	# 			return false;
    -	# 		end
    -	# 		else
    -	# 			#Go back to first column and issue page break
    -	# 			SetCol(0);
    -	# 			return true;
    -	# 		end
    -	# 	end
    -	# }
    -	#
    -	# :pdf=new PDF();
    -	# :pdf->Open();
    -	# :pdf->AddPage();
    -	# :pdf->SetFont('Arial','',12);
    -	# for(i=1;:i<=300;:i++)
    -	#     :pdf->Cell(0,5,"Line :i",0,1);
    -	# }
    -	# :pdf->Output();
    -	# 
    - # @return boolean - # @since 1.4 - # @see SetAutoPageBreak() - # - def AcceptPageBreak() - #Accept automatic page break or not - return @auto_page_break; - end - alias_method :accept_page_break, :AcceptPageBreak - - def BreakThePage?(h) - if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak()) - true - else - false - end - end - alias_method :break_the_page?, :BreakThePage? - # - # Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.
    - # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. - # @param float :w Cell width. If 0, the cell extends up to the right margin. - # @param float :h Cell height. Default value: 0. - # @param string :txt String to print. Default value: empty string. - # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:
    • 0: no border (default)
    • 1: frame
    or a string containing some or all of the following characters (in any order):
    • L: left
    • T: top
    • R: right
    • B: bottom
    - # @param int :ln Indicates where the current position should go after the call. Possible values are:
    • 0: to the right
    • 1: to the beginning of the next line
    • 2: below
    - # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. - # @param string :align Allows to center or align the text. Possible values are:
    • L or empty string: left align (default value)
    • C: center
    • R: right align
    - # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. - # @param mixed :link URL or identifier returned by AddLink(). - # @since 1.0 - # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak() - # - def Cell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil) - #Output a cell - k=@k; - if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak()) - #Automatic page break - if @pages[@page+1].nil? - x = @x; - ws = @ws; - if (ws > 0) - @ws = 0; - out('0 Tw'); - end - AddPage(@cur_orientation); - @x = x; - if (ws > 0) - @ws = ws; - out(sprintf('%.3f Tw', ws * k)); - end - else - @page += 1; - @y=@t_margin; - end - end - - if (w == 0) - w = @w - @r_margin - @x; - end - s = ''; - if ((fill.to_i == 1) or (border.to_i == 1)) - if (fill.to_i == 1) - op = (border.to_i == 1) ? 'B' : 'f'; - else - op = 'S'; - end - s = sprintf('%.2f %.2f %.2f %.2f re %s ', @x * k, (@h - @y) * k, w * k, -h * k, op); - end - if (border.is_a?(String)) - x=@x; - y=@y; - if (border.include?('L')) - s<0) - # Go to next line - @y += h; - if (ln == 1) - @x = @l_margin; - end - else - @x += w; - end - end - alias_method :cell, :Cell - - # - # This method allows printing text with line breaks. They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.
    - # Text can be aligned, centered or justified. The cell block can be framed and the background painted. - # @param float :w Width of cells. If 0, they extend up to the right margin of the page. - # @param float :h Height of cells. - # @param string :txt String to print - # @param mixed :border Indicates if borders must be drawn around the cell block. The value can be either a number:
    • 0: no border (default)
    • 1: frame
    or a string containing some or all of the following characters (in any order):
    • L: left
    • T: top
    • R: right
    • B: bottom
    - # @param string :align Allows to center or align the text. Possible values are:
    • L or empty string: left align
    • C: center
    • R: right align
    • J: justification (default value)
    - # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. - # @param int :ln Indicates where the current position should go after the call. Possible values are:
    • 0: to the right
    • 1: to the beginning of the next line [DEFAULT]
    • 2: below
    - # @since 1.3 - # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak() - # - def MultiCell(w, h, txt, border=0, align='J', fill=0, ln=1) - - # save current position - prevx = @x; - prevy = @y; - prevpage = @page; - - #Output text with automatic or explicit line breaks - - if (w == 0) - w = @w - @r_margin - @x; - end - - wmax = (w - 3 * @c_margin); - - s = txt.gsub("\r", ''); # remove carriage returns - nb = s.length; - - b=0; - if (border) - if (border==1) - border='LTRB'; - b='LRT'; - b2='LR'; - elsif border.is_a?(String) - b2=''; - if (border.include?('L')) - b2<<'L'; - end - if (border.include?('R')) - b2<<'R'; - end - b=(border.include?('T')) ? b2 + 'T' : b2; - end - end - sep=-1; - to_index=0; - from_j=0; - l=0; - ns=0; - nl=1; - - while to_index < nb - #Get next character - c = s[to_index]; - if c == "\n"[0] - #Explicit line break - if @ws > 0 - @ws = 0 - out('0 Tw') - end - #Ed Moss - change begin - end_i = to_index == 0 ? 0 : to_index - 1 - # Changed from s[from_j..to_index] to fix bug reported by Hans Allis. - from_j = to_index == 0 ? 1 : from_j - Cell(w, h, s[from_j..end_i], b, 2, align, fill) - #change end - to_index += 1 - sep=-1 - from_j=to_index - l=0 - ns=0 - nl += 1 - b = b2 if border and nl==2 - next - end - if (c == " "[0]) - sep = to_index; - ls = l; - ns += 1; - end - - l = GetStringWidth(s[from_j, to_index - from_j]); - - if (l > wmax) - #Automatic line break - if (sep == -1) - if (to_index == from_j) - to_index += 1; - end - if (@ws > 0) - @ws = 0; - out('0 Tw'); - end - Cell(w, h, s[from_j..to_index-1], b, 2, align, fill) # my FPDF version - else - if (align=='J' || align=='justify' || align=='justified') - @ws = (ns>1) ? (wmax-ls)/(ns-1) : 0; - out(sprintf('%.3f Tw', @ws * @k)); - end - Cell(w, h, s[from_j..sep], b, 2, align, fill); - to_index = sep + 1; - end - sep=-1; - from_j = to_index; - l=0; - ns=0; - nl += 1; - if (border and (nl==2)) - b = b2; - end - else - to_index += 1; - end - end - #Last chunk - if (@ws>0) - @ws=0; - out('0 Tw'); - end - if (border.is_a?(String) and border.include?('B')) - b<<'B'; - end - Cell(w, h, s[from_j, to_index-from_j], b, 2, align, fill); - - # move cursor to specified position - # since 2007-03-03 - if (ln == 1) - # go to the beginning of the next line - @x = @l_margin; - elsif (ln == 0) - # go to the top-right of the cell - @page = prevpage; - @y = prevy; - @x = prevx + w; - elsif (ln == 2) - # go to the bottom-left of the cell - @x = prevx; - end - end - alias_method :multi_cell, :MultiCell - - # - # This method prints text from the current position. When the right margin is reached (or the \n character is met) a line break occurs and text continues from the left margin. Upon method exit, the current position is left just at the end of the text. It is possible to put a link on the text.
    - # Example:
    - #
    -	# #Begin with regular font
    -	# :pdf->SetFont('Arial','',14);
    -	# :pdf->Write(5,'Visit ');
    -	# #Then put a blue underlined link
    -	# :pdf->SetTextColor(0,0,255);
    -	# :pdf->SetFont('','U');
    -	# :pdf->Write(5,'www.tecnick.com','http://www.tecnick.com');
    -	# 
    - # @param float :h Line height - # @param string :txt String to print - # @param mixed :link URL or identifier returned by AddLink() - # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0. - # @since 1.5 - # @see SetFont(), SetTextColor(), AddLink(), MultiCell(), SetAutoPageBreak() - # - def Write(h, txt, link=nil, fill=0) - - #Output text in flowing mode - w = @w - @r_margin - @x; - wmax = (w - 3 * @c_margin); - - s = txt.gsub("\r", ''); - nb = s.length; - - # handle single space character - if ((nb==1) and (s == " ")) - @x += GetStringWidth(s); - return; - end - - sep=-1; - i=0; - j=0; - l=0; - nl=1; - while(i wmax) - #Automatic line break (word wrapping) - if (sep == -1) - if (@x > @l_margin) - #Move to next line - @x = @l_margin; - @y += h; - w=@w - @r_margin - @x; - wmax=(w - 3 * @c_margin); - i += 1 - nl += 1 - next - end - if (i == j) - i += 1 - end - Cell(w, h, s[j, (i-1)], 0, 2, '', fill, link); - else - Cell(w, h, s[j, (sep-j)], 0, 2, '', fill, link); - i = sep+1; - end - sep = -1; - j = i; - l = 0; - if (nl==1) - @x = @l_margin; - w = @w - @r_margin - @x; - wmax = (w - 3 * @c_margin); - end - nl += 1; - else - i += 1; - end - end - #Last chunk - if (i != j) - Cell(GetStringWidth(s[j..i]), h, s[j..i], 0, 0, '', fill, link); - end - end - alias_method :write, :Write - - # - # Puts an image in the page. The upper-left corner must be given. The dimensions can be specified in different ways:
    • explicit width and height (expressed in user unit)
    • one explicit dimension, the other being calculated automatically in order to keep the original proportions
    • no explicit dimension, in which case the image is put at 72 dpi
    - # Supported formats are JPEG and PNG. - # For JPEG, all flavors are allowed:
    • gray scales
    • true colors (24 bits)
    • CMYK (32 bits)
    - # For PNG, are allowed:
    • gray scales on at most 8 bits (256 levels)
    • indexed colors
    • true colors (24 bits)
    - # but are not supported:
    • Interlacing
    • Alpha channel
    - # If a transparent color is defined, it will be taken into account (but will be only interpreted by Acrobat 4 and above).
    - # The format can be specified explicitly or inferred from the file extension.
    - # It is possible to put a link on the image.
    - # Remark: if an image is used several times, only one copy will be embedded in the file.
    - # @param string :file Name of the file containing the image. - # @param float :x Abscissa of the upper-left corner. - # @param float :y Ordinate of the upper-left corner. - # @param float :w Width of the image in the page. If not specified or equal to zero, it is automatically calculated. - # @param float :h Height of the image in the page. If not specified or equal to zero, it is automatically calculated. - # @param string :type Image format. Possible values are (case insensitive): JPG, JPEG, PNG. If not specified, the type is inferred from the file extension. - # @param mixed :link URL or identifier returned by AddLink(). - # @since 1.1 - # @see AddLink() - # - def Image(file, x, y, w=0, h=0, type='', link=nil) - #Put an image on the page - if (@images[file].nil?) - #First use of image, get info - if (type == '') - pos = File::basename(file).rindex('.'); - if (pos.nil? or pos == 0) - Error('Image file has no extension and no type was specified: ' + file); - end - pos = file.rindex('.'); - type = file[pos+1..-1]; - end - type.downcase! - if (type == 'jpg' or type == 'jpeg') - info=parsejpg(file); - elsif (type == 'png') - info=parsepng(file); - elsif (type == 'gif') - tmpFile = imageToPNG(file); - info=parsepng(tmpFile.path); - tmpFile.delete - else - #Allow for additional formats - mtd='parse' + type; - if (!self.respond_to?(mtd)) - Error('Unsupported image type: ' + type); - end - info=send(mtd, file); - end - info['i']=@images.length+1; - @images[file] = info; - else - info=@images[file]; - end - #Automatic width and height calculation if needed - if ((w == 0) and (h == 0)) - rescale_x = (@w - @r_margin - x) / (info['w'] / (@img_scale * @k)) - rescale_x = 1 if rescale_x >= 1 - if (y + info['h'] * rescale_x / (@img_scale * @k) > @page_break_trigger and !@in_footer and AcceptPageBreak()) - #Automatic page break - if @pages[@page+1].nil? - ws = @ws; - if (ws > 0) - @ws = 0; - out('0 Tw'); - end - AddPage(@cur_orientation); - if (ws > 0) - @ws = ws; - out(sprintf('%.3f Tw', ws * @k)); - end - else - @page += 1; - end - y=@t_margin; - end - rescale_y = (@page_break_trigger - y) / (info['h'] / (@img_scale * @k)) - rescale_y = 1 if rescale_y >= 1 - rescale = rescale_y >= rescale_x ? rescale_x : rescale_y - - #Put image at 72 dpi - # 2004-06-14 :: Nicola Asuni, scale factor where added - w = info['w'] * rescale / (@img_scale * @k); - h = info['h'] * rescale / (@img_scale * @k); - elsif (w == 0) - w = h * info['w'] / info['h']; - elsif (h == 0) - h = w * info['h'] / info['w']; - end - out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', w*@k, h*@k, x*@k, (@h-(y+h))*@k, info['i'])); - if (link) - Link(x, y, w, h, link); - end - - #2002-07-31 - Nicola Asuni - # set right-bottom corner coordinates - @img_rb_x = x + w; - @img_rb_y = y + h; - end - alias_method :image, :Image - - # - # Performs a line break. The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter. - # @param float :h The height of the break. By default, the value equals the height of the last printed cell. - # @since 1.0 - # @see Cell() - # - def Ln(h='') - #Line feed; default value is last cell height - @x=@l_margin; - if (h.is_a?(String)) - @y += @lasth; - else - @y += h; - end - - k=@k; - if (@y > @page_break_trigger and !@in_footer and AcceptPageBreak()) - #Automatic page break - if @pages[@page+1].nil? - x = @x; - ws = @ws; - if (ws > 0) - @ws = 0; - out('0 Tw'); - end - AddPage(@cur_orientation); - @x = x; - if (ws > 0) - @ws = ws; - out(sprintf('%.3f Tw', ws * k)); - end - else - @page += 1; - @y=@t_margin; - end - end - - end - alias_method :ln, :Ln - - # - # Returns the abscissa of the current position. - # @return float - # @since 1.2 - # @see SetX(), GetY(), SetY() - # - def GetX() - #Get x position - return @x; - end - alias_method :get_x, :GetX - - # - # Defines the abscissa of the current position. If the passed value is negative, it is relative to the right of the page. - # @param float :x The value of the abscissa. - # @since 1.2 - # @see GetX(), GetY(), SetY(), SetXY() - # - def SetX(x) - #Set x position - if (x>=0) - @x = x; - else - @x=@w+x; - end - end - alias_method :set_x, :SetX - - # - # Returns the ordinate of the current position. - # @return float - # @since 1.0 - # @see SetY(), GetX(), SetX() - # - def GetY() - #Get y position - return @y; - end - alias_method :get_y, :GetY - - # - # Moves the current abscissa back to the left margin and sets the ordinate. If the passed value is negative, it is relative to the bottom of the page. - # @param float :y The value of the ordinate. - # @since 1.0 - # @see GetX(), GetY(), SetY(), SetXY() - # - def SetY(y) - #Set y position and reset x - @x=@l_margin; - if (y>=0) - @y = y; - else - @y=@h+y; - end - end - alias_method :set_y, :SetY - - # - # Defines the abscissa and ordinate of the current position. If the passed values are negative, they are relative respectively to the right and bottom of the page. - # @param float :x The value of the abscissa - # @param float :y The value of the ordinate - # @since 1.2 - # @see SetX(), SetY() - # - def SetXY(x, y) - #Set x and y positions - SetY(y); - SetX(x); - end - alias_method :set_xy, :SetXY - - # - # Send the document to a given destination: string, local file or browser. In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.
    - # The method first calls Close() if necessary to terminate the document. - # @param string :name The name of the file. If not given, the document will be sent to the browser (destination I) with the name doc.pdf. - # @param string :dest Destination where to send the document. It can take one of the following values:
    • I: send the file inline to the browser. The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.
    • D: send to the browser and force a file download with the name given by name.
    • F: save to a local file with the name given by name.
    • S: return the document as a string. name is ignored.
    If the parameter is not specified but a name is given, destination is F. If no parameter is specified at all, destination is I.
    - # @since 1.0 - # @see Close() - # - def Output(name='', dest='') - #Output PDF to some destination - #Finish document if necessary - if (@state < 3) - Close(); - end - #Normalize parameters - # Boolean no longer supported - # if (dest.is_a?(Boolean)) - # dest = dest ? 'D' : 'F'; - # end - dest = dest.upcase - if (dest=='') - if (name=='') - name='doc.pdf'; - dest='I'; - else - dest='F'; - end - end - case (dest) - when 'I' - # This is PHP specific code - ##Send to standard output - # if (ob_get_contents()) - # Error('Some data has already been output, can\'t send PDF file'); - # end - # if (php_sapi_name()!='cli') - # #We send to a browser - # header('Content-Type: application/pdf'); - # if (headers_sent()) - # Error('Some data has already been output to browser, can\'t send PDF file'); - # end - # header('Content-Length: ' + @buffer.length); - # header('Content-disposition: inline; filename="' + name + '"'); - # end - return @buffer; - - when 'D' - # PHP specific - #Download file - # if (ob_get_contents()) - # Error('Some data has already been output, can\'t send PDF file'); - # end - # if (!_SERVER['HTTP_USER_AGENT'].nil? && SERVER['HTTP_USER_AGENT'].include?('MSIE')) - # header('Content-Type: application/force-download'); - # else - # header('Content-Type: application/octet-stream'); - # end - # if (headers_sent()) - # Error('Some data has already been output to browser, can\'t send PDF file'); - # end - # header('Content-Length: '+ @buffer.length); - # header('Content-disposition: attachment; filename="' + name + '"'); - return @buffer; - - when 'F' - open(name,'wb') do |f| - f.write(@buffer) - end - # PHP code - # #Save to local file - # f=open(name,'wb'); - # if (!f) - # Error('Unable to create output file: ' + name); - # end - # fwrite(f,@buffer,@buffer.length); - # f.close - - when 'S' - #Return as a string - return @buffer; - else - Error('Incorrect output destination: ' + dest); - - end - return ''; - end - alias_method :output, :Output - - # Protected methods - - # - # Check for locale-related bug - # @access protected - # - def dochecks() - #Check for locale-related bug - if (1.1==1) - Error('Don\'t alter the locale before including class file'); - end - #Check for decimal separator - if (sprintf('%.1f',1.0)!='1.0') - setlocale(LC_NUMERIC,'C'); - end - end - - # - # Return fonts path - # @access protected - # - def getfontpath(file) - # Is it in the @@font_path? - if @@font_path - fpath = File.join @@font_path, file - if File.exists?(fpath) - return fpath - end - end - # Is it in this plugin's font folder? - fpath = File.join File.dirname(__FILE__), 'fonts', file - if File.exists?(fpath) - return fpath - end - # Could not find it. - nil - end - - # - # Start document - # @access protected - # - def begindoc() - #Start document - @state=1; - out('%PDF-1.3'); - end - - # - # putpages - # @access protected - # - def putpages() - nb = @page; - if (@alias_nb_pages) - nbstr = UTF8ToUTF16BE(nb.to_s, false); - #Replace number of pages - 1.upto(nb) do |n| - @pages[n].gsub!(@alias_nb_pages, nbstr) - end - end - if @def_orientation=='P' - w_pt=@fw_pt - h_pt=@fh_pt - else - w_pt=@fh_pt - h_pt=@fw_pt - end - filter=(@compress) ? '/Filter /FlateDecode ' : '' - 1.upto(nb) do |n| - #Page - newobj - out('<>>>'; - else - l=@links[pl[4]]; - h=!@orientation_changes[l[0]].nil? ? w_pt : h_pt; - annots<>',1+2*l[0], h-l[1]*@k); - end - end - out(annots + ']'); - end - out('/Contents ' + (@n+1).to_s + ' 0 R>>'); - out('endobj'); - #Page content - p=(@compress) ? gzcompress(@pages[n]) : @pages[n]; - newobj(); - out('<<' + filter + '/Length '+ p.length.to_s + '>>'); - putstream(p); - out('endobj'); - end - #Pages root - @offsets[1]=@buffer.length; - out('1 0 obj'); - out('<>'); - out('endobj'); - end - - # - # Adds fonts - # putfonts - # @access protected - # - def putfonts() - nf=@n; - @diffs.each do |diff| - #Encodings - newobj(); - out('<>'); - out('endobj'); - end - @font_files.each do |file, info| - #Font file embedding - newobj(); - @font_files[file]['n']=@n; - font=''; - open(getfontpath(file),'rb') do |f| - font = f.read(); - end - compressed=(file[-2,2]=='.z'); - if (!compressed && !info['length2'].nil?) - header=((font[0][0])==128); - if (header) - #Strip first binary header - font=font[6]; - end - if header && (font[info['length1']][0] == 128) - #Strip second binary header - font=font[0..info['length1']] + font[info['length1']+6]; - end - end - out('<>'); - open(getfontpath(file),'rb') do |f| - putstream(font) - end - out('endobj'); - end - @fonts.each do |k, font| - #Font objects - @fonts[k]['n']=@n+1; - type = font['type']; - name = font['name']; - if (type=='core') - #Standard font - newobj(); - out('<>'); - out('endobj'); - elsif type == 'Type0' - putType0(font) - elsif (type=='Type1' || type=='TrueType') - #Additional Type1 or TrueType font - newobj(); - out('<>'); - out('endobj'); - #Widths - newobj(); - cw=font['cw']; # & - s='['; - 32.upto(255) do |i| - s << cw[i.chr] + ' '; - end - out(s + ']'); - out('endobj'); - #Descriptor - newobj(); - s='<>'); - out('endobj'); - else - #Allow for additional types - mtd='put' + type.downcase; - if (!self.respond_to?(mtd)) - Error('Unsupported font type: ' + type) - else - self.send(mtd,font) - end - end - end - end - - def putType0(font) - #Type0 - newobj(); - out('<>') - out('endobj') - #CIDFont - newobj() - out('<>') - out('/FontDescriptor '+(@n+1).to_s+' 0 R') - w='/W [1 [' - font['cw'].keys.sort.each {|key| - w+=font['cw'][key].to_s + " " -# ActionController::Base::logger.debug key.to_s -# ActionController::Base::logger.debug font['cw'][key].to_s - } - out(w+'] 231 325 500 631 [500] 326 389 500]') - out('>>') - out('endobj') - #Font descriptor - newobj() - out('<>') - out('endobj') - end - - # - # putimages - # @access protected - # - def putimages() - filter=(@compress) ? '/Filter /FlateDecode ' : ''; - @images.each do |file, info| # was while(list(file, info)=each(@images)) - newobj(); - @images[file]['n']=@n; - out('<>'); - putstream(info['data']); - @images[file]['data']=nil - out('endobj'); - #Palette - if (info['cs']=='Indexed') - newobj(); - pal=(@compress) ? gzcompress(info['pal']) : info['pal']; - out('<<' + filter + '/Length ' + pal.length.to_s + '>>'); - putstream(pal); - out('endobj'); - end - end - end - - # - # putxobjectdict - # @access protected - # - def putxobjectdict() - @images.each_value do |image| - out('/I' + image['i'].to_s + ' ' + image['n'].to_s + ' 0 R'); - end - end - - # - # putresourcedict - # @access protected - # - def putresourcedict() - out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); - out('/Font <<'); - @fonts.each_value do |font| - out('/F' + font['i'].to_s + ' ' + font['n'].to_s + ' 0 R'); - end - out('>>'); - out('/XObject <<'); - putxobjectdict(); - out('>>'); - end - - # - # putresources - # @access protected - # - def putresources() - putfonts(); - putimages(); - #Resource dictionary - @offsets[2]=@buffer.length; - out('2 0 obj'); - out('<<'); - putresourcedict(); - out('>>'); - out('endobj'); - end - - # - # putinfo - # @access protected - # - def putinfo() - out('/Producer ' + textstring(PDF_PRODUCER)); - if (!@title.nil?) - out('/Title ' + textstring(@title)); - end - if (!@subject.nil?) - out('/Subject ' + textstring(@subject)); - end - if (!@author.nil?) - out('/Author ' + textstring(@author)); - end - if (!@keywords.nil?) - out('/Keywords ' + textstring(@keywords)); - end - if (!@creator.nil?) - out('/Creator ' + textstring(@creator)); - end - out('/CreationDate ' + textstring('D:' + Time.now.strftime('%Y%m%d%H%M%S'))); - end - - # - # putcatalog - # @access protected - # - def putcatalog() - out('/Type /Catalog'); - out('/Pages 1 0 R'); - if (@zoom_mode=='fullpage') - out('/OpenAction [3 0 R /Fit]'); - elsif (@zoom_mode=='fullwidth') - out('/OpenAction [3 0 R /FitH null]'); - elsif (@zoom_mode=='real') - out('/OpenAction [3 0 R /XYZ null null 1]'); - elsif (!@zoom_mode.is_a?(String)) - out('/OpenAction [3 0 R /XYZ null null ' + (@zoom_mode/100) + ']'); - end - if (@layout_mode=='single') - out('/PageLayout /SinglePage'); - elsif (@layout_mode=='continuous') - out('/PageLayout /OneColumn'); - elsif (@layout_mode=='two') - out('/PageLayout /TwoColumnLeft'); - end - end - - # - # puttrailer - # @access protected - # - def puttrailer() - out('/Size ' + (@n+1).to_s); - out('/Root ' + @n.to_s + ' 0 R'); - out('/Info ' + (@n-1).to_s + ' 0 R'); - end - - # - # putheader - # @access protected - # - def putheader() - out('%PDF-' + @pdf_version); - end - - # - # enddoc - # @access protected - # - def enddoc() - putheader(); - putpages(); - putresources(); - #Info - newobj(); - out('<<'); - putinfo(); - out('>>'); - out('endobj'); - #Catalog - newobj(); - out('<<'); - putcatalog(); - out('>>'); - out('endobj'); - #Cross-ref - o=@buffer.length; - out('xref'); - out('0 ' + (@n+1).to_s); - out('0000000000 65535 f '); - 1.upto(@n) do |i| - out(sprintf('%010d 00000 n ',@offsets[i])); - end - #Trailer - out('trailer'); - out('<<'); - puttrailer(); - out('>>'); - out('startxref'); - out(o); - out('%%EOF'); - @state=3; - end - - # - # beginpage - # @access protected - # - def beginpage(orientation) - @page += 1; - @pages[@page]=''; - @state=2; - @x=@l_margin; - @y=@t_margin; - @font_family=''; - #Page orientation - if (orientation.empty?) - orientation=@def_orientation; - else - orientation.upcase! - if (orientation!=@def_orientation) - @orientation_changes[@page]=true; - end - end - if (orientation!=@cur_orientation) - #Change orientation - if (orientation=='P') - @w_pt=@fw_pt; - @h_pt=@fh_pt; - @w=@fw; - @h=@fh; - else - @w_pt=@fh_pt; - @h_pt=@fw_pt; - @w=@fh; - @h=@fw; - end - @page_break_trigger=@h-@b_margin; - @cur_orientation = orientation; - end - end - - # - # End of page contents - # @access protected - # - def endpage() - @state=1; - end - - # - # Begin a new object - # @access protected - # - def newobj() - @n += 1; - @offsets[@n]=@buffer.length; - out(@n.to_s + ' 0 obj'); - end - - # - # Underline and Deleted text - # @access protected - # - def dolinetxt(x, y, txt) - up = @current_font['up']; - ut = @current_font['ut']; - w = GetStringWidth(txt) + @ws * txt.count(' '); - sprintf('%.2f %.2f %.2f %.2f re f', x * @k, (@h - (y - up / 1000.0 * @font_size)) * @k, w * @k, -ut / 1000.0 * @font_size_pt); - end - - # - # Extract info from a JPEG file - # @access protected - # - def parsejpg(file) - a=getimagesize(file); - if (a.empty?) - Error('Missing or incorrect image file: ' + file); - end - if (!a[2].nil? and a[2]!='JPEG') - Error('Not a JPEG file: ' + file); - end - if (a['channels'].nil? or a['channels']==3) - colspace='DeviceRGB'; - elsif (a['channels']==4) - colspace='DeviceCMYK'; - else - colspace='DeviceGray'; - end - bpc=!a['bits'].nil? ? a['bits'] : 8; - #Read whole file - data=''; - - open(file,'rb') do |f| - data< a[0],'h' => a[1],'cs' => colspace,'bpc' => bpc,'f'=>'DCTDecode','data' => data} - end - - def imageToPNG(file) - return unless Object.const_defined?(:Magick) - - img = Magick::ImageList.new(file) - img.format = 'PNG' # convert to PNG from gif - img.opacity = 0 # PNG alpha channel delete - - #use a temporary file.... - tmpFile = Tempfile.new(['', '_' + File::basename(file) + '.png'], @@k_path_cache); - tmpFile.binmode - tmpFile.print img.to_blob - tmpFile - ensure - tmpFile.close - end - - # - # Extract info from a PNG file - # @access protected - # - def parsepng(file) - f=open(file,'rb'); - #Check signature - if (f.read(8)!=137.chr + 'PNG' + 13.chr + 10.chr + 26.chr + 10.chr) - Error('Not a PNG file: ' + file); - end - #Read header chunk - f.read(4); - if (f.read(4)!='IHDR') - Error('Incorrect PNG file: ' + file); - end - w=freadint(f); - h=freadint(f); - bpc=f.read(1).unpack('C')[0]; - if (bpc>8) - Error('16-bit depth not supported: ' + file); - end - ct=f.read(1).unpack('C')[0]; - if (ct==0) - colspace='DeviceGray'; - elsif (ct==2) - colspace='DeviceRGB'; - elsif (ct==3) - colspace='Indexed'; - else - Error('Alpha channel not supported: ' + file); - end - if (f.read(1).unpack('C')[0] != 0) - Error('Unknown compression method: ' + file); - end - if (f.read(1).unpack('C')[0] != 0) - Error('Unknown filter method: ' + file); - end - if (f.read(1).unpack('C')[0] != 0) - Error('Interlacing not supported: ' + file); - end - f.read(4); - parms='/DecodeParms <>'; - #Scan chunks looking for palette, transparency and image data - pal=''; - trns=''; - data=''; - begin - n=freadint(f); - type=f.read(4); - if (type=='PLTE') - #Read palette - pal=f.read( n); - f.read(4); - elsif (type=='tRNS') - #Read transparency info - t=f.read( n); - if (ct==0) - trns = t[1].unpack('C')[0] - elsif (ct==2) - trns = t[[1].unpack('C')[0], t[3].unpack('C')[0], t[5].unpack('C')[0]] - else - pos=t.index(0.chr); - unless (pos.nil?) - trns = [pos] - end - end - f.read(4); - elsif (type=='IDAT') - #Read image data block - data< w, 'h' => h, 'cs' => colspace, 'bpc' => bpc, 'f'=>'FlateDecode', 'parms' => parms, 'pal' => pal, 'trns' => trns, 'data' => data} - ensure - f.close - end - - # - # Read a 4-byte integer from file - # @access protected - # - def freadint(f) - # Read a 4-byte integer from file - a = f.read(4).unpack('N') - return a[0] - end - - # - # Format a text string - # @access protected - # - def textstring(s) - if (@is_unicode) - #Convert string to UTF-16BE - s = UTF8ToUTF16BE(s, true); - end - return '(' + escape(s) + ')'; - end - - # - # Format a text string - # @access protected - # - def escapetext(s) - if (@is_unicode) - #Convert string to UTF-16BE - s = UTF8ToUTF16BE(s, false); - end - return escape(s); - end - - # - # Add \ before \, ( and ) - # @access protected - # - def escape(s) - # Add \ before \, ( and ) - s.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)').gsub(13.chr, '\r') - end - - # - # - # @access protected - # - def putstream(s) - out('stream'); - out(s); - out('endstream'); - end - - # - # Add a line to the document - # @access protected - # - def out(s) - if (@state==2) - @pages[@page] << s.to_s + "\n"; - else - @buffer << s.to_s + "\n"; - end - end - - # - # Adds unicode fonts.
    - # Based on PDF Reference 1.3 (section 5) - # @access protected - # @author Nicola Asuni - # @since 1.52.0.TC005 (2005-01-05) - # - def puttruetypeunicode(font) - # Type0 Font - # A composite font composed of other fonts, organized hierarchically - newobj(); - out('<>'); - out('endobj'); - - # CIDFontType2 - # A CIDFont whose glyph descriptions are based on TrueType font technology - newobj(); - out('<>'); - out('endobj'); - - # ToUnicode - # is a stream object that contains the definition of the CMap - # (PDF Reference 1.3 chap. 5.9) - newobj(); - out('<>'); - out('stream'); - out('/CIDInit /ProcSet findresource begin'); - out('12 dict begin'); - out('begincmap'); - out('/CIDSystemInfo'); - out('<> def'); - out('/CMapName /Adobe-Identity-UCS def'); - out('/CMapType 2 def'); - out('1 begincodespacerange'); - out('<0000> '); - out('endcodespacerange'); - out('1 beginbfrange'); - out('<0000> <0000>'); - out('endbfrange'); - out('endcmap'); - out('CMapName currentdict /CMap defineresource pop'); - out('end'); - out('end'); - out('endstream'); - out('endobj'); - - # CIDSystemInfo dictionary - # A dictionary containing entries that define the character collection of the CIDFont. - newobj(); - out('<>'); - out('endobj'); - - # Font descriptor - # A font descriptor describing the CIDFont default metrics other than its glyph widths - newobj(); - out('<>'); - out('endobj'); - - # Embed CIDToGIDMap - # A specification of the mapping from CIDs to glyph indices - newobj(); - ctgfile = getfontpath(font['ctg']) - if (!ctgfile) - Error('Font file not found: ' + ctgfile); - end - size = File.size(ctgfile); - out('<>'); - open(ctgfile, "rb") do |f| - putstream(f.read()) - end - out('endobj'); - end - - # - # Converts UTF-8 strings to codepoints array.
    - # Invalid byte sequences will be replaced with 0xFFFD (replacement character)
    - # Based on: http://www.faqs.org/rfcs/rfc3629.html - #
    -	# 	  Char. number range  |        UTF-8 octet sequence
    -	#       (hexadecimal)    |              (binary)
    -	#    --------------------+-----------------------------------------------
    -	#    0000 0000-0000 007F | 0xxxxxxx
    -	#    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
    -	#    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
    -	#    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    -	#    ---------------------------------------------------------------------
    -	#
    -	#   ABFN notation:
    -	#   ---------------------------------------------------------------------
    -	#   UTF8-octets =#( UTF8-char )
    -	#   UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
    -	#   UTF8-1      = %x00-7F
    -	#   UTF8-2      = %xC2-DF UTF8-tail
    -	#
    -	#   UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
    -	#                 %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
    -	#   UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
    -	#                 %xF4 %x80-8F 2( UTF8-tail )
    -	#   UTF8-tail   = %x80-BF
    -	#   ---------------------------------------------------------------------
    -	# 
    - # @param string :str string to process. - # @return array containing codepoints (UTF-8 characters values) - # @access protected - # @author Nicola Asuni - # @since 1.53.0.TC005 (2005-01-05) - # - def UTF8StringToArray(str) - if (!@is_unicode) - return str; # string is not in unicode - end - - unicode = [] # array containing unicode values - bytes = [] # array containing single character byte sequences - numbytes = 1; # number of octetc needed to represent the UTF-8 character - - str = str.to_s; # force :str to be a string - - str.each_byte do |char| - if (bytes.length == 0) # get starting octect - if (char <= 0x7F) - unicode << char # use the character "as is" because is ASCII - numbytes = 1 - elsif ((char >> 0x05) == 0x06) # 2 bytes character (0x06 = 110 BIN) - bytes << ((char - 0xC0) << 0x06) - numbytes = 2 - elsif ((char >> 0x04) == 0x0E) # 3 bytes character (0x0E = 1110 BIN) - bytes << ((char - 0xE0) << 0x0C) - numbytes = 3 - elsif ((char >> 0x03) == 0x1E) # 4 bytes character (0x1E = 11110 BIN) - bytes << ((char - 0xF0) << 0x12) - numbytes = 4 - else - # use replacement character for other invalid sequences - unicode << 0xFFFD - bytes = [] - numbytes = 1 - end - elsif ((char >> 0x06) == 0x02) # bytes 2, 3 and 4 must start with 0x02 = 10 BIN - bytes << (char - 0x80) - if (bytes.length == numbytes) - # compose UTF-8 bytes to a single unicode value - char = bytes[0] - 1.upto(numbytes-1) do |j| - char += (bytes[j] << ((numbytes - j - 1) * 0x06)) - end - if (((char >= 0xD800) and (char <= 0xDFFF)) or (char >= 0x10FFFF)) - # The definition of UTF-8 prohibits encoding character numbers between - # U+D800 and U+DFFF, which are reserved for use with the UTF-16 - # encoding form (as surrogate pairs) and do not directly represent - # characters - unicode << 0xFFFD; # use replacement character - else - unicode << char; # add char to array - end - # reset data for next char - bytes = [] - numbytes = 1; - end - else - # use replacement character for other invalid sequences - unicode << 0xFFFD; - bytes = [] - numbytes = 1; - end - end - return unicode; - end - - # - # Converts UTF-8 strings to UTF16-BE.
    - # Based on: http://www.faqs.org/rfcs/rfc2781.html - #
    -	#   Encoding UTF-16:
    -	# 
    -		#   Encoding of a single character from an ISO 10646 character value to
    -	#    UTF-16 proceeds as follows. Let U be the character number, no greater
    -	#    than 0x10FFFF.
    -	# 
    -	#    1) If U < 0x10000, encode U as a 16-bit unsigned integer and
    -	#       terminate.
    -	# 
    -	#    2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
    -	#       U' must be less than or equal to 0xFFFFF. That is, U' can be
    -	#       represented in 20 bits.
    -	# 
    -	#    3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
    -	#       0xDC00, respectively. These integers each have 10 bits free to
    -	#       encode the character value, for a total of 20 bits.
    -	# 
    -	#    4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
    -	#       bits of W1 and the 10 low-order bits of U' to the 10 low-order
    -	#       bits of W2. Terminate.
    -	# 
    -	#    Graphically, steps 2 through 4 look like:
    -	#    U' = yyyyyyyyyyxxxxxxxxxx
    -	#    W1 = 110110yyyyyyyyyy
    -	#    W2 = 110111xxxxxxxxxx
    -	# 
    - # @param string :str string to process. - # @param boolean :setbom if true set the Byte Order Mark (BOM = 0xFEFF) - # @return string - # @access protected - # @author Nicola Asuni - # @since 1.53.0.TC005 (2005-01-05) - # @uses UTF8StringToArray - # - def UTF8ToUTF16BE(str, setbom=true) - if (!@is_unicode) - return str; # string is not in unicode - end - outstr = ""; # string to be returned - unicode = UTF8StringToArray(str); # array containing UTF-8 unicode values - numitems = unicode.length; - - if (setbom) - outstr << "\xFE\xFF"; # Byte Order Mark (BOM) - end - unicode.each do |char| - if (char == 0xFFFD) - outstr << "\xFF\xFD"; # replacement character - elsif (char < 0x10000) - outstr << (char >> 0x08).chr; - outstr << (char & 0xFF).chr; - else - char -= 0x10000; - w1 = 0xD800 | (char >> 0x10); - w2 = 0xDC00 | (char & 0x3FF); - outstr << (w1 >> 0x08).chr; - outstr << (w1 & 0xFF).chr; - outstr << (w2 >> 0x08).chr; - outstr << (w2 & 0xFF).chr; - end - end - return outstr; - end - - # ==================================================== - - # - # Set header font. - # @param array :font font - # @since 1.1 - # - def SetHeaderFont(font) - @header_font = font; - end - alias_method :set_header_font, :SetHeaderFont - - # - # Set footer font. - # @param array :font font - # @since 1.1 - # - def SetFooterFont(font) - @footer_font = font; - end - alias_method :set_footer_font, :SetFooterFont - - # - # Set language array. - # @param array :language - # @since 1.1 - # - def SetLanguageArray(language) - @l = language; - end - alias_method :set_language_array, :SetLanguageArray - # - # Set document barcode. - # @param string :bc barcode - # - def SetBarcode(bc="") - @barcode = bc; - end - - # - # Print Barcode. - # @param int :x x position in user units - # @param int :y y position in user units - # @param int :w width in user units - # @param int :h height position in user units - # @param string :type type of barcode (I25, C128A, C128B, C128C, C39) - # @param string :style barcode style - # @param string :font font for text - # @param int :xres x resolution - # @param string :code code to print - # - def writeBarcode(x, y, w, h, type, style, font, xres, code) - require(File.dirname(__FILE__) + "/barcode/barcode.rb"); - require(File.dirname(__FILE__) + "/barcode/i25object.rb"); - require(File.dirname(__FILE__) + "/barcode/c39object.rb"); - require(File.dirname(__FILE__) + "/barcode/c128aobject.rb"); - require(File.dirname(__FILE__) + "/barcode/c128bobject.rb"); - require(File.dirname(__FILE__) + "/barcode/c128cobject.rb"); - - if (code.empty?) - return; - end - - if (style.empty?) - style = BCS_ALIGN_LEFT; - style |= BCS_IMAGE_PNG; - style |= BCS_TRANSPARENT; - #:style |= BCS_BORDER; - #:style |= BCS_DRAW_TEXT; - #:style |= BCS_STRETCH_TEXT; - #:style |= BCS_REVERSE_COLOR; - end - if (font.empty?) then font = BCD_DEFAULT_FONT; end - if (xres.empty?) then xres = BCD_DEFAULT_XRES; end - - scale_factor = 1.5 * xres * @k; - bc_w = (w * scale_factor).round #width in points - bc_h = (h * scale_factor).round #height in points - - case (type.upcase) - when "I25" - obj = I25Object.new(bc_w, bc_h, style, code); - when "C128A" - obj = C128AObject.new(bc_w, bc_h, style, code); - when "C128B" - obj = C128BObject.new(bc_w, bc_h, style, code); - when "C128C" - obj = C128CObject.new(bc_w, bc_h, style, code); - when "C39" - obj = C39Object.new(bc_w, bc_h, style, code); - end - - obj.SetFont(font); - obj.DrawObject(xres); - - #use a temporary file.... - tmpName = tempnam(@@k_path_cache,'img'); - imagepng(obj.getImage(), tmpName); - Image(tmpName, x, y, w, h, 'png'); - obj.DestroyObject(); - obj = nil - unlink(tmpName); - end - - # - # Returns the PDF data. - # - def GetPDFData() - if (@state < 3) - Close(); - end - return @buffer; - end - - # --- HTML PARSER FUNCTIONS --- - - # - # Allows to preserve some HTML formatting.
    - # Supports: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, ins, del, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small - # @param string :html text to display - # @param boolean :ln if true add a new line after text (default = true) - # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0. - # - def writeHTML(html, ln=true, fill=0, h=0) - - @lasth = h if h > 0 - if (@lasth == 0) - #set row height - @lasth = @font_size * @@k_cell_height_ratio; - end - - @href = nil - @style = ""; - @t_cells = [[]]; - @table_id = 0; - - # pre calculate - html.split(/(<[^>]+>)/).each do |element| - if "<" == element[0,1] - #Tag - if (element[1, 1] == '/') - closedHTMLTagCalc(element[2..-2].downcase); - else - #Extract attributes - # get tag name - tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0} - tag = tag[0].to_s.downcase; - - # get attributes - attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/) - attrs = {} - attr_array.each do |name, value| - attrs[name.downcase] = value; - end - openHTMLTagCalc(tag, attrs); - end - end - end - @table_id = 0; - - html.split(/(<[A-Za-z!?\/][^>]*?>)/).each do |element| - if "<" == element[0,1] - #Tag - if (element[1, 1] == '/') - closedHTMLTagHandler(element[2..-2].downcase); - else - #Extract attributes - # get tag name - tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0} - tag = tag[0].to_s.downcase; - - # get attributes - attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/) - attrs = {} - attr_array.each do |name, value| - attrs[name.downcase] = value; - end - openHTMLTagHandler(tag, attrs, fill); - end - - else - #Text - if (@tdbegin) - element.gsub!(/[\t\r\n\f]/, ""); - @tdtext << element.gsub(/ /, " "); - elsif (@href) - element.gsub!(/[\t\r\n\f]/, ""); - addHtmlLink(@href, element, fill); - elsif (@pre_state == true and element.length > 0) - Write(@lasth, unhtmlentities(element), '', fill); - elsif (element.strip.length > 0) - element.gsub!(/[\t\r\n\f]/, ""); - element.gsub!(/ /, " "); - Write(@lasth, unhtmlentities(element), '', fill); - end - end - end - - if (ln) - Ln(@lasth); - end - end - alias_method :write_html, :writeHTML - - # - # Prints a cell (rectangular area) with optional borders, background color and html text string. The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.
    - # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. - # @param float :w Cell width. If 0, the cell extends up to the right margin. - # @param float :h Cell minimum height. The cell extends automatically if needed. - # @param float :x upper-left corner X coordinate - # @param float :y upper-left corner Y coordinate - # @param string :html html text to print. Default value: empty string. - # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:
    • 0: no border (default)
    • 1: frame
    or a string containing some or all of the following characters (in any order):
    • L: left
    • T: top
    • R: right
    • B: bottom
    - # @param int :ln Indicates where the current position should go after the call. Possible values are:
    • 0: to the right
    • 1: to the beginning of the next line
    • 2: below
    -# Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. - # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. - # @see Cell() - # - def writeHTMLCell(w, h, x, y, html='', border=0, ln=1, fill=0) - - if (@lasth == 0) - #set row height - @lasth = @font_size * @@k_cell_height_ratio; - end - - if (x == 0) - x = GetX(); - end - if (y == 0) - y = GetY(); - end - - # get current page number - pagenum = @page; - - SetX(x); - SetY(y); - - if (w == 0) - w = @fw - x - @r_margin; - end - - b=0; - if (border) - if (border==1) - border='LTRB'; - b='LRT'; - b2='LR'; - elsif border.is_a?(String) - b2=''; - if (border.include?('L')) - b2<<'L'; - end - if (border.include?('R')) - b2<<'R'; - end - b=(border.include?('T')) ? b2 + 'T' : b2; - end - end - - # store original margin values - l_margin = @l_margin; - r_margin = @r_margin; - - # set new margin values - SetLeftMargin(x); - SetRightMargin(@fw - x - w); - - # calculate remaining vertical space on page - restspace = GetPageHeight() - GetY() - GetBreakMargin(); - - writeHTML(html, true, fill); # write html text - SetX(x) - - currentY = GetY(); - @auto_page_break = false; - # check if a new page has been created - if (@page > pagenum) - # design a cell around the text on first page - currentpage = @page; - @page = pagenum; - SetY(GetPageHeight() - restspace - GetBreakMargin()); - SetX(x) - Cell(w, restspace - 1, "", b, 0, 'L', 0); - b = b2; - @page += 1; - while @page < currentpage - SetY(@t_margin); # put cursor at the beginning of text - SetX(x) - Cell(w, @page_break_trigger - @t_margin, "", b, 0, 'L', 0); - @page += 1; - end - if (border.is_a?(String) and border.include?('B')) - b<<'B'; - end - # design a cell around the text on last page - SetY(@t_margin); # put cursor at the beginning of text - SetX(x) - Cell(w, currentY - @t_margin, "", b, 0, 'L', 0); - else - SetY(y); # put cursor at the beginning of text - # design a cell around the text - SetX(x) - Cell(w, [h, (currentY - y)].max, "", border, 0, 'L', 0); - end - @auto_page_break = true; - - # restore original margin values - SetLeftMargin(l_margin); - SetRightMargin(r_margin); - - @lasth = h - - # move cursor to specified position - if (ln == 0) - # go to the top-right of the cell - @x = x + w; - @y = y; - elsif (ln == 1) - # go to the beginning of the next line - @x = @l_margin; - @y = currentY; - elsif (ln == 2) - # go to the bottom-left of the cell (below) - @x = x; - @y = currentY; - end - end - alias_method :write_html_cell, :writeHTMLCell - - # - # Check html table tag position. - # - # @param array :table potision array - # @param int :current tr tag id number - # @param int :current td tag id number - # @access private - # @return int : next td_id position. - # value 0 mean that can use position. - # - def checkTableBlockingCellPosition(table, tr_id, td_id ) - 0.upto(tr_id) do |j| - 0.upto(@t_cells[table][j].size - 1) do |i| - if @t_cells[table][j][i]['i0'] <= td_id and td_id <= @t_cells[table][j][i]['i1'] - if @t_cells[table][j][i]['j0'] <= tr_id and tr_id <= @t_cells[table][j][i]['j1'] - return @t_cells[table][j][i]['i1'] - td_id + 1; - end - end - end - end - return 0; - end - - # - # Calculate opening tags. - # - # html table cell array : @t_cells - # - # i0: table cell start position - # i1: table cell end position - # j0: table row start position - # j1: table row end position - # - # +------+ - # |i0,j0 | - # | i1,j1| - # +------+ - # - # example html: - # - # - # - # - # - #
    - # - # i: 0 1 2 - # j+----+----+----+ - # :|0,0 |1,0 |2,0 | - # 0| 0,0| 1,0| 2,0| - # +----+----+----+ - # |0,1 |2,1 | - # 1| 1,1| 2,1| - # +----+----+----+ - # |0,2 |1,2 |2,2 | - # 2| | 1,2| 2,2| - # + +----+----+ - # | |1,3 |2,3 | - # 3| 0,3| 1,3| 2,3| - # +----+----+----+ - # - # html table cell array : - # [[[i0=>0,j0=>0,i1=>0,j1=>0],[i0=>1,j0=>0,i1=>1,j1=>0],[i0=>2,j0=>0,i1=>2,j1=>0]], - # [[i0=>0,j0=>1,i1=>1,j1=>1],[i0=>2,j0=>1,i1=>2,j1=>1]], - # [[i0=>0,j0=>2,i1=>0,j1=>3],[i0=>1,j0=>2,i1=>1,j1=>2],[i0=>2,j0=>2,i1=>2,j1=>2]] - # [[i0=>1,j0=>3,i1=>1,j1=>3],[i0=>2,j0=>3,i1=>2,j1=>3]]] - # - # @param string :tag tag name (in upcase) - # @param string :attr tag attribute (in upcase) - # @access private - # - def openHTMLTagCalc(tag, attrs) - #Opening tag - case (tag) - when 'table' - @max_table_columns[@table_id] = 0; - @t_columns = 0; - @tr_id = -1; - when 'tr' - if @max_table_columns[@table_id] < @t_columns - @max_table_columns[@table_id] = @t_columns; - end - @t_columns = 0; - @tr_id += 1; - @td_id = -1; - @t_cells[@table_id].push [] - when 'td', 'th' - @td_id += 1; - if attrs['colspan'].nil? or attrs['colspan'] == '' - colspan = 1; - else - colspan = attrs['colspan'].to_i; - end - if attrs['rowspan'].nil? or attrs['rowspan'] == '' - rowspan = 1; - else - rowspan = attrs['rowspan'].to_i; - end - - i = 0; - while true - next_i_distance = checkTableBlockingCellPosition(@table_id, @tr_id, @td_id + i); - if next_i_distance == 0 - @t_cells[@table_id][@tr_id].push "i0"=>@td_id + i, "j0"=>@tr_id, "i1"=>(@td_id + i + colspan - 1), "j1"=>@tr_id + rowspan - 1 - break; - end - i += next_i_distance; - end - - @t_columns += colspan; - end - end - - # - # Calculate closing tags. - # @param string :tag tag name (in upcase) - # @access private - # - def closedHTMLTagCalc(tag) - #Closing tag - case (tag) - when 'table' - if @max_table_columns[@table_id] < @t_columns - @max_table_columns[@table_id] = @t_columns; - end - @table_id += 1; - @t_cells.push [] - end - end - - # - # Convert to accessible file path - # @param string :attrname image file name - # - def getImageFilename( attrname ) - nil - end - - # - # Process opening tags. - # @param string :tag tag name (in upcase) - # @param string :attr tag attribute (in upcase) - # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. - # @access private - # - def openHTMLTagHandler(tag, attrs, fill=0) - #Opening tag - case (tag) - when 'pre' - @pre_state = true; - @l_margin += 5; - @r_margin += 5; - @x += 5; - - when 'table' - Ln(); - if @default_table_columns < @max_table_columns[@table_id] - @table_columns = @max_table_columns[@table_id]; - else - @table_columns = @default_table_columns; - end - @l_margin += 5; - @r_margin += 5; - @x += 5; - - if attrs['border'].nil? or attrs['border'] == '' - @tableborder = 0; - else - @tableborder = attrs['border']; - end - @tr_id = -1; - @max_td_page[0] = @page; - @max_td_y[0] = @y; - - when 'tr', 'td', 'th' - if tag == 'th' - SetStyle('b', true); - @tdalign = "C"; - end - if ((!attrs['width'].nil?) and (attrs['width'] != '')) - @tdwidth = (attrs['width'].to_i/4); - else - @tdwidth = ((@w - @l_margin - @r_margin) / @table_columns); - end - - if tag == 'tr' - @tr_id += 1; - @td_id = -1; - else - @td_id += 1; - @x = @l_margin + @tdwidth * @t_cells[@table_id][@tr_id][@td_id]['i0']; - end - - if attrs['colspan'].nil? or attrs['border'] == '' - @colspan = 1; - else - @colspan = attrs['colspan'].to_i; - end - @tdwidth *= @colspan; - if ((!attrs['height'].nil?) and (attrs['height'] != '')) - @tdheight=(attrs['height'].to_i / @k); - else - @tdheight = @lasth; - end - if ((!attrs['align'].nil?) and (attrs['align'] != '')) - case (attrs['align']) - when 'center' - @tdalign = "C"; - when 'right' - @tdalign = "R"; - when 'left' - @tdalign = "L"; - end - end - if ((!attrs['bgcolor'].nil?) and (attrs['bgcolor'] != '')) - coul = convertColorHexToDec(attrs['bgcolor']); - SetFillColor(coul['R'], coul['G'], coul['B']); - @tdfill=1; - end - @tdbegin=true; - - when 'hr' - margin = 1; - if ((!attrs['width'].nil?) and (attrs['width'] != '')) - hrWidth = attrs['width']; - else - hrWidth = @w - @l_margin - @r_margin - margin; - end - SetLineWidth(0.2); - Line(@x + margin, @y, @x + hrWidth, @y); - Ln(); - - when 'strong' - SetStyle('b', true); - - when 'em' - SetStyle('i', true); - - when 'ins' - SetStyle('u', true); - - when 'del' - SetStyle('d', true); - - when 'b', 'i', 'u' - SetStyle(tag, true); - - when 'a' - @href = attrs['href']; - - when 'img' - if (!attrs['src'].nil?) - # Don't generates image inside table tag - if (@tdbegin) - @tdtext << attrs['src']; - return - end - # Only generates image include a pdf if RMagick is avalaible - unless Object.const_defined?(:Magick) - Write(@lasth, attrs['src'], '', fill); - return - end - file = getImageFilename(attrs['src']) - if (file.nil?) - Write(@lasth, attrs['src'], '', fill); - return - end - - if (attrs['width'].nil?) - attrs['width'] = 0; - end - if (attrs['height'].nil?) - attrs['height'] = 0; - end - - begin - Image(file, GetX(),GetY(), pixelsToMillimeters(attrs['width']), pixelsToMillimeters(attrs['height'])); - #SetX(@img_rb_x); - SetY(@img_rb_y); - rescue => err - logger.error "pdf: Image: error: #{err.message}" - Write(@lasth, attrs['src'], '', fill); - end - end - - when 'ul', 'ol' - if @li_count == 0 - Ln() if @prevquote_count == @quote_count; # insert Ln for keeping quote lines - @prevquote_count = @quote_count; - end - if @li_state == true - Ln(); - @li_state = false; - end - if tag == 'ul' - @list_ordered[@li_count] = false; - else - @list_ordered[@li_count] = true; - end - @list_count[@li_count] = 0; - @li_count += 1 - - when 'li' - Ln() if @li_state == true - if (@list_ordered[@li_count - 1]) - @list_count[@li_count - 1] += 1; - @li_spacer = " " * @li_count + (@list_count[@li_count - 1]).to_s + ". "; - else - #unordered list simbol - @li_spacer = " " * @li_count + "- "; - end - Write(@lasth, @spacer + @li_spacer, '', fill); - @li_state = true; - - when 'blockquote' - if (@quote_count == 0) - SetStyle('i', true); - @l_margin += 5; - else - @l_margin += 5 / 2; - end - @x = @l_margin; - @quote_top[@quote_count] = @y; - @quote_page[@quote_count] = @page; - @quote_count += 1 - when 'br' - if @tdbegin - @tdtext << "\n" - return - end - Ln(); - - if (@li_spacer.length > 0) - @x += GetStringWidth(@li_spacer); - end - - when 'p' - Ln(); - 0.upto(@quote_count - 1) do |i| - if @quote_page[i] == @page; - if @quote_top[i] == @y - @lasth; # fix start line - @quote_top[i] = @y; - end - else - if @quote_page[i] == @page - 1; - @quote_page[i] = @page; # fix start line - @quote_top[i] = @t_margin; - end - end - end - - when 'sup' - currentfont_size = @font_size; - @tempfontsize = @font_size_pt; - SetFontSize(@font_size_pt * @@k_small_ratio); - SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio))); - - when 'sub' - currentfont_size = @font_size; - @tempfontsize = @font_size_pt; - SetFontSize(@font_size_pt * @@k_small_ratio); - SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio))); - - when 'small' - currentfont_size = @font_size; - @tempfontsize = @font_size_pt; - SetFontSize(@font_size_pt * @@k_small_ratio); - SetXY(GetX(), GetY() + ((currentfont_size - @font_size)/3)); - - when 'font' - if (!attrs['color'].nil? and attrs['color']!='') - coul = convertColorHexToDec(attrs['color']); - SetTextColor(coul['R'], coul['G'], coul['B']); - @issetcolor=true; - end - if (!attrs['face'].nil? and @fontlist.include?(attrs['face'].downcase)) - SetFont(attrs['face'].downcase); - @issetfont=true; - end - if (!attrs['size'].nil?) - headsize = attrs['size'].to_i; - else - headsize = 0; - end - currentfont_size = @font_size; - @tempfontsize = @font_size_pt; - SetFontSize(@font_size_pt + headsize); - @lasth = @font_size * @@k_cell_height_ratio; - - when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' - Ln(); - headsize = (4 - tag[1,1].to_f) * 2 - @tempfontsize = @font_size_pt; - SetFontSize(@font_size_pt + headsize); - SetStyle('b', true); - @lasth = @font_size * @@k_cell_height_ratio; - - end - end - - # - # Process closing tags. - # @param string :tag tag name (in upcase) - # @access private - # - def closedHTMLTagHandler(tag) - #Closing tag - case (tag) - when 'pre' - @pre_state = false; - @l_margin -= 5; - @r_margin -= 5; - @x = @l_margin; - Ln(); - - when 'td','th' - base_page = @page; - base_x = @x; - base_y = @y; - - MultiCell(@tdwidth, @tdheight, unhtmlentities(@tdtext.strip), @tableborder, @tdalign, @tdfill, 1); - tr_end = @t_cells[@table_id][@tr_id][@td_id]['j1'] + 1; - if @max_td_page[tr_end].nil? or (@max_td_page[tr_end] < @page) - @max_td_page[tr_end] = @page - @max_td_y[tr_end] = @y - elsif (@max_td_page[tr_end] == @page) - @max_td_y[tr_end] = @y if @max_td_y[tr_end].nil? or (@max_td_y[tr_end] < @y) - end - - @page = base_page; - @x = base_x + @tdwidth; - @y = base_y; - @tdtext = ''; - @tdbegin = false; - @tdwidth = 0; - @tdheight = 0; - @tdalign = "L"; - SetStyle('b', false); - @tdfill = 0; - SetFillColor(@prevfill_color[0], @prevfill_color[1], @prevfill_color[2]); - - when 'tr' - @y = @max_td_y[@tr_id + 1]; - @x = @l_margin; - @page = @max_td_page[@tr_id + 1]; - - when 'table' - # Write Table Line - width = (@w - @l_margin - @r_margin) / @table_columns; - 0.upto(@t_cells[@table_id].size - 1) do |j| - 0.upto(@t_cells[@table_id][j].size - 1) do |i| - @page = @max_td_page[j] - i0=@t_cells[@table_id][j][i]['i0']; - j0=@t_cells[@table_id][j][i]['j0']; - i1=@t_cells[@table_id][j][i]['i1']; - j1=@t_cells[@table_id][j][i]['j1']; - - Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * (i1+1), @max_td_y[j0]) # top - if ( @page == @max_td_page[j1 + 1]) - Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * i0, @max_td_y[j1+1]) # left - Line(@l_margin + width * (i1+1), @max_td_y[j0], @l_margin + width * (i1+1), @max_td_y[j1+1]) # right - else - Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * i0, @page_break_trigger) # left - Line(@l_margin + width * (i1+1), @max_td_y[j0], @l_margin + width * (i1+1), @page_break_trigger) # right - @page += 1; - while @page < @max_td_page[j1 + 1] - Line(@l_margin + width * i0, @t_margin, @l_margin + width * i0, @page_break_trigger) # left - Line(@l_margin + width * (i1+1), @t_margin, @l_margin + width * (i1+1), @page_break_trigger) # right - @page += 1; - end - Line(@l_margin + width * i0, @t_margin, @l_margin + width * i0, @max_td_y[j1+1]) # left - Line(@l_margin + width * (i1+1), @t_margin, @l_margin + width * (i1+1), @max_td_y[j1+1]) # right - end - Line(@l_margin + width * i0, @max_td_y[j1+1], @l_margin + width * (i1+1), @max_td_y[j1+1]) # bottom - end - end - - @l_margin -= 5; - @r_margin -= 5; - @tableborder=0; - @table_id += 1; - - when 'strong' - SetStyle('b', false); - - when 'em' - SetStyle('i', false); - - when 'ins' - SetStyle('u', false); - - when 'del' - SetStyle('d', false); - - when 'b', 'i', 'u' - SetStyle(tag, false); - - when 'a' - @href = nil; - - when 'p' - Ln(); - - when 'sup' - currentfont_size = @font_size; - SetFontSize(@tempfontsize); - @tempfontsize = @font_size_pt; - SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio))); - - when 'sub' - currentfont_size = @font_size; - SetFontSize(@tempfontsize); - @tempfontsize = @font_size_pt; - SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio))); - - when 'small' - currentfont_size = @font_size; - SetFontSize(@tempfontsize); - @tempfontsize = @font_size_pt; - SetXY(GetX(), GetY() - ((@font_size - currentfont_size)/3)); - - when 'font' - if (@issetcolor == true) - SetTextColor(@prevtext_color[0], @prevtext_color[1], @prevtext_color[2]); - end - if (@issetfont) - @font_family = @prevfont_family; - @font_style = @prevfont_style; - SetFont(@font_family); - @issetfont = false; - end - currentfont_size = @font_size; - SetFontSize(@tempfontsize); - @tempfontsize = @font_size_pt; - #@text_color = @prevtext_color; - @lasth = @font_size * @@k_cell_height_ratio; - - when 'blockquote' - @quote_count -= 1 - if (@quote_page[@quote_count] == @page) - Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @y) # quoto line - else - cur_page = @page; - cur_y = @y; - @page = @quote_page[@quote_count]; - if (@quote_top[@quote_count] < @page_break_trigger) - Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @page_break_trigger) # quoto line - end - @page += 1; - while @page < cur_page - Line(@l_margin - 1, @t_margin, @l_margin - 1, @page_break_trigger) # quoto line - @page += 1; - end - @y = cur_y; - Line(@l_margin - 1, @t_margin, @l_margin - 1, @y) # quoto line - end - if (@quote_count <= 0) - SetStyle('i', false); - @l_margin -= 5; - else - @l_margin -= 5 / 2; - end - @x = @l_margin; - Ln() if @quote_count == 0 - - when 'ul', 'ol' - @li_count -= 1 - if @li_state == true - Ln(); - @li_state = false; - end - - when 'li' - @li_spacer = ""; - if @li_state == true - Ln(); - @li_state = false; - end - - when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' - SetFontSize(@tempfontsize); - @tempfontsize = @font_size_pt; - SetStyle('b', false); - Ln(); - @lasth = @font_size * @@k_cell_height_ratio; - - if tag == 'h1' or tag == 'h2' or tag == 'h3' or tag == 'h4' - margin = 1; - hrWidth = @w - @l_margin - @r_margin - margin; - if tag == 'h1' or tag == 'h2' - SetLineWidth(0.2); - else - SetLineWidth(0.1); - end - Line(@x + margin, @y, @x + hrWidth, @y); - end - end - end - - # - # Sets font style. - # @param string :tag tag name (in lowercase) - # @param boolean :enable - # @access private - # - def SetStyle(tag, enable) - #Modify style and select corresponding font - ['b', 'i', 'u', 'd'].each do |s| - if tag.downcase == s - if enable - @style << s if ! @style.include?(s) - else - @style = @style.gsub(s,'') - end - end - end - SetFont('', @style); - end - - # - # Output anchor link. - # @param string :url link URL - # @param string :name link name - # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. - # @access public - # - def addHtmlLink(url, name, fill=0) - #Put a hyperlink - SetTextColor(0, 0, 255); - SetStyle('u', true); - Write(@lasth, name, url, fill); - SetStyle('u', false); - SetTextColor(0); - end - - # - # Returns an associative array (keys: R,G,B) from - # a hex html code (e.g. #3FE5AA). - # @param string :color hexadecimal html color [#rrggbb] - # @return array - # @access private - # - def convertColorHexToDec(color = "#000000") - tbl_color = {} - tbl_color['R'] = color[1,2].hex.to_i; - tbl_color['G'] = color[3,2].hex.to_i; - tbl_color['B'] = color[5,2].hex.to_i; - return tbl_color; - end - - # - # Converts pixels to millimeters in 72 dpi. - # @param int :px pixels - # @return float millimeters - # @access private - # - def pixelsToMillimeters(px) - return px.to_f * 25.4 / 72; - end - - # - # Reverse function for htmlentities. - # Convert entities in UTF-8. - # - # @param :text_to_convert Text to convert. - # @return string converted - # - def unhtmlentities(string) - CGI.unescapeHTML(string) - end - -end # END OF CLASS - -#TODO 2007-05-25 (EJM) Level=0 - -#Handle special IE contype request -# if (!_SERVER['HTTP_USER_AGENT'].nil? and (_SERVER['HTTP_USER_AGENT']=='contype')) -# header('Content-Type: application/pdf'); -# exit; -# } diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/09/09608744e321935ef94b4022df67d37162d9f786.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09608744e321935ef94b4022df67d37162d9f786.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,757 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class MailerTest < ActiveSupport::TestCase + include Redmine::I18n + include ActionDispatch::Assertions::SelectorAssertions + fixtures :projects, :enabled_modules, :issues, :users, :members, + :member_roles, :roles, :documents, :attachments, :news, + :tokens, :journals, :journal_details, :changesets, + :trackers, :projects_trackers, + :issue_statuses, :enumerations, :messages, :boards, :repositories, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, + :versions, + :comments + + def setup + ActionMailer::Base.deliveries.clear + Setting.host_name = 'mydomain.foo' + Setting.protocol = 'http' + Setting.plain_text_mail = '0' + end + + def test_generated_links_in_emails + Setting.default_language = 'en' + Setting.host_name = 'mydomain.foo' + Setting.protocol = 'https' + + journal = Journal.find(3) + assert Mailer.deliver_issue_edit(journal) + + mail = last_email + assert_not_nil mail + + assert_select_email do + # link to the main ticket + assert_select 'a[href=?]', + 'https://mydomain.foo/issues/2#change-3', + :text => 'Feature request #2: Add ingredients categories' + # link to a referenced ticket + assert_select 'a[href=?][title=?]', + 'https://mydomain.foo/issues/1', + 'Can't print recipes (New)', + :text => '#1' + # link to a changeset + assert_select 'a[href=?][title=?]', + 'https://mydomain.foo/projects/ecookbook/repository/revisions/2', + 'This commit fixes #1, #2 and references #1 & #3', + :text => 'r2' + # link to a description diff + assert_select 'a[href=?][title=?]', + 'https://mydomain.foo/journals/diff/3?detail_id=4', + 'View differences', + :text => 'diff' + # link to an attachment + assert_select 'a[href=?]', + 'https://mydomain.foo/attachments/download/4/source.rb', + :text => 'source.rb' + end + end + + def test_generated_links_with_prefix + Setting.default_language = 'en' + relative_url_root = Redmine::Utils.relative_url_root + Setting.host_name = 'mydomain.foo/rdm' + Setting.protocol = 'http' + + journal = Journal.find(3) + assert Mailer.deliver_issue_edit(journal) + + mail = last_email + assert_not_nil mail + + assert_select_email do + # link to the main ticket + assert_select 'a[href=?]', + 'http://mydomain.foo/rdm/issues/2#change-3', + :text => 'Feature request #2: Add ingredients categories' + # link to a referenced ticket + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/issues/1', + 'Can't print recipes (New)', + :text => '#1' + # link to a changeset + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2', + 'This commit fixes #1, #2 and references #1 & #3', + :text => 'r2' + # link to a description diff + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4', + 'View differences', + :text => 'diff' + # link to an attachment + assert_select 'a[href=?]', + 'http://mydomain.foo/rdm/attachments/download/4/source.rb', + :text => 'source.rb' + end + end + + def test_issue_edit_should_generate_url_with_hostname_for_relations + journal = Journal.new(:journalized => Issue.find(1), :user => User.find(1), :created_on => Time.now) + journal.details << JournalDetail.new(:property => 'relation', :prop_key => 'label_relates_to', :value => 2) + Mailer.deliver_issue_edit(journal) + assert_not_nil last_email + assert_select_email do + assert_select 'a[href=?]', 'http://mydomain.foo/issues/2', :text => 'Feature request #2' + end + end + + def test_generated_links_with_prefix_and_no_relative_url_root + Setting.default_language = 'en' + relative_url_root = Redmine::Utils.relative_url_root + Setting.host_name = 'mydomain.foo/rdm' + Setting.protocol = 'http' + Redmine::Utils.relative_url_root = nil + + journal = Journal.find(3) + assert Mailer.deliver_issue_edit(journal) + + mail = last_email + assert_not_nil mail + + assert_select_email do + # link to the main ticket + assert_select 'a[href=?]', + 'http://mydomain.foo/rdm/issues/2#change-3', + :text => 'Feature request #2: Add ingredients categories' + # link to a referenced ticket + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/issues/1', + 'Can't print recipes (New)', + :text => '#1' + # link to a changeset + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2', + 'This commit fixes #1, #2 and references #1 & #3', + :text => 'r2' + # link to a description diff + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4', + 'View differences', + :text => 'diff' + # link to an attachment + assert_select 'a[href=?]', + 'http://mydomain.foo/rdm/attachments/download/4/source.rb', + :text => 'source.rb' + end + ensure + # restore it + Redmine::Utils.relative_url_root = relative_url_root + end + + def test_email_headers + issue = Issue.find(1) + Mailer.deliver_issue_add(issue) + mail = last_email + assert_not_nil mail + assert_equal 'OOF', mail.header['X-Auto-Response-Suppress'].to_s + assert_equal 'auto-generated', mail.header['Auto-Submitted'].to_s + assert_equal '', mail.header['List-Id'].to_s + end + + def test_email_headers_should_include_sender + issue = Issue.find(1) + Mailer.deliver_issue_add(issue) + mail = last_email + assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s + end + + def test_plain_text_mail + Setting.plain_text_mail = 1 + journal = Journal.find(2) + Mailer.deliver_issue_edit(journal) + mail = last_email + assert_equal "text/plain; charset=UTF-8", mail.content_type + assert_equal 0, mail.parts.size + assert !mail.encoded.include?('href') + end + + def test_html_mail + Setting.plain_text_mail = 0 + journal = Journal.find(2) + Mailer.deliver_issue_edit(journal) + mail = last_email + assert_equal 2, mail.parts.size + assert mail.encoded.include?('href') + end + + def test_from_header + with_settings :mail_from => 'redmine@example.net' do + Mailer.test_email(User.find(1)).deliver + end + mail = last_email + assert_equal 'redmine@example.net', mail.from_addrs.first + end + + def test_from_header_with_phrase + with_settings :mail_from => 'Redmine app ' do + Mailer.test_email(User.find(1)).deliver + end + mail = last_email + assert_equal 'redmine@example.net', mail.from_addrs.first + assert_equal 'Redmine app ', mail.header['From'].to_s + end + + def test_should_not_send_email_without_recipient + news = News.first + user = news.author + # Remove members except news author + news.project.memberships.each {|m| m.destroy unless m.user == user} + + user.pref.no_self_notified = false + user.pref.save + User.current = user + Mailer.news_added(news.reload).deliver + assert_equal 1, last_email.bcc.size + + # nobody to notify + user.pref.no_self_notified = true + user.pref.save + User.current = user + ActionMailer::Base.deliveries.clear + Mailer.news_added(news.reload).deliver + assert ActionMailer::Base.deliveries.empty? + end + + def test_issue_add_message_id + issue = Issue.find(2) + Mailer.deliver_issue_add(issue) + mail = last_email + assert_match /^redmine\.issue-2\.20060719190421\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.issue-2.20060719190421@example.net", mail.references + end + + def test_issue_edit_message_id + journal = Journal.find(3) + journal.issue = Issue.find(2) + + Mailer.deliver_issue_edit(journal) + mail = last_email + assert_match /^redmine\.journal-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.issue-2.20060719190421@example.net", mail.references + assert_select_email do + # link to the update + assert_select "a[href=?]", + "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}" + end + end + + def test_message_posted_message_id + message = Message.find(1) + Mailer.message_posted(message).deliver + mail = last_email + assert_match /^redmine\.message-1\.\d+\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.message-1.20070512151532@example.net", mail.references + assert_select_email do + # link to the message + assert_select "a[href=?]", + "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}", + :text => message.subject + end + end + + def test_reply_posted_message_id + message = Message.find(3) + Mailer.message_posted(message).deliver + mail = last_email + assert_match /^redmine\.message-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.message-1.20070512151532@example.net", mail.references + assert_select_email do + # link to the reply + assert_select "a[href=?]", + "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}", + :text => message.subject + end + end + + test "#issue_add should notify project members" do + issue = Issue.find(1) + assert Mailer.deliver_issue_add(issue) + assert last_email.bcc.include?('dlopper@somenet.foo') + end + + test "#issue_add should not notify project members that are not allow to view the issue" do + issue = Issue.find(1) + Role.find(2).remove_permission!(:view_issues) + assert Mailer.deliver_issue_add(issue) + assert !last_email.bcc.include?('dlopper@somenet.foo') + end + + test "#issue_add should notify issue watchers" do + issue = Issue.find(1) + user = User.find(9) + # minimal email notification options + user.pref.no_self_notified = '1' + user.pref.save + user.mail_notification = false + user.save + + Watcher.create!(:watchable => issue, :user => user) + assert Mailer.deliver_issue_add(issue) + assert last_email.bcc.include?(user.mail) + end + + test "#issue_add should not notify watchers not allowed to view the issue" do + issue = Issue.find(1) + user = User.find(9) + Watcher.create!(:watchable => issue, :user => user) + Role.non_member.remove_permission!(:view_issues) + assert Mailer.deliver_issue_add(issue) + assert !last_email.bcc.include?(user.mail) + end + + def test_issue_add_should_include_enabled_fields + Setting.default_language = 'en' + issue = Issue.find(2) + assert Mailer.deliver_issue_add(issue) + assert_mail_body_match '* Target version: 1.0', last_email + assert_select_email do + assert_select 'li', :text => 'Target version: 1.0' + end + end + + def test_issue_add_should_not_include_disabled_fields + Setting.default_language = 'en' + issue = Issue.find(2) + tracker = issue.tracker + tracker.core_fields -= ['fixed_version_id'] + tracker.save! + assert Mailer.deliver_issue_add(issue) + assert_mail_body_no_match 'Target version', last_email + assert_select_email do + assert_select 'li', :text => /Target version/, :count => 0 + end + end + + # test mailer methods for each language + def test_issue_add + issue = Issue.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.deliver_issue_add(issue) + end + end + + def test_issue_edit + journal = Journal.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.deliver_issue_edit(journal) + end + end + + def test_issue_edit_should_send_private_notes_to_users_with_permission_only + journal = Journal.find(1) + journal.private_notes = true + journal.save! + + Role.find(2).add_permission! :view_private_notes + Mailer.deliver_issue_edit(journal) + assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort + + Role.find(2).remove_permission! :view_private_notes + Mailer.deliver_issue_edit(journal) + assert_equal %w(jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort + end + + def test_issue_edit_should_send_private_notes_to_watchers_with_permission_only + Issue.find(1).set_watcher(User.find_by_login('someone')) + journal = Journal.find(1) + journal.private_notes = true + journal.save! + + Role.non_member.add_permission! :view_private_notes + Mailer.deliver_issue_edit(journal) + assert_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort + + Role.non_member.remove_permission! :view_private_notes + Mailer.deliver_issue_edit(journal) + assert_not_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort + end + + def test_issue_edit_should_mark_private_notes + journal = Journal.find(2) + journal.private_notes = true + journal.save! + + with_settings :default_language => 'en' do + Mailer.deliver_issue_edit(journal) + end + assert_mail_body_match '(Private notes)', last_email + end + + def test_issue_edit_with_relation_should_notify_users_who_can_see_the_related_issue + issue = Issue.generate! + private_issue = Issue.generate!(:is_private => true) + IssueRelation.create!(:issue_from => issue, :issue_to => private_issue, :relation_type => 'relates') + issue.reload + assert_equal 1, issue.journals.size + journal = issue.journals.first + ActionMailer::Base.deliveries.clear + + Mailer.deliver_issue_edit(journal) + last_email.bcc.each do |email| + user = User.find_by_mail(email) + assert private_issue.visible?(user), "Issue was not visible to #{user}" + end + end + + def test_document_added + document = Document.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.document_added(document).deliver + end + end + + def test_attachments_added + attachements = [ Attachment.find_by_container_type('Document') ] + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.attachments_added(attachements).deliver + end + end + + def test_version_file_added + attachements = [ Attachment.find_by_container_type('Version') ] + assert Mailer.attachments_added(attachements).deliver + assert_not_nil last_email.bcc + assert last_email.bcc.any? + assert_select_email do + assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files" + end + end + + def test_project_file_added + attachements = [ Attachment.find_by_container_type('Project') ] + assert Mailer.attachments_added(attachements).deliver + assert_not_nil last_email.bcc + assert last_email.bcc.any? + assert_select_email do + assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files" + end + end + + def test_news_added + news = News.first + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.news_added(news).deliver + end + end + + def test_news_comment_added + comment = Comment.find(2) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.news_comment_added(comment).deliver + end + end + + def test_message_posted + message = Message.first + recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author} + recipients = recipients.compact.uniq + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.message_posted(message).deliver + end + end + + def test_wiki_content_added + content = WikiContent.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert_difference 'ActionMailer::Base.deliveries.size' do + assert Mailer.wiki_content_added(content).deliver + assert_select_email do + assert_select 'a[href=?]', + 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation', + :text => 'CookBook documentation' + end + end + end + end + + def test_wiki_content_updated + content = WikiContent.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert_difference 'ActionMailer::Base.deliveries.size' do + assert Mailer.wiki_content_updated(content).deliver + assert_select_email do + assert_select 'a[href=?]', + 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation', + :text => 'CookBook documentation' + end + end + end + end + + def test_account_information + user = User.find(2) + valid_languages.each do |lang| + user.update_attribute :language, lang.to_s + user.reload + assert Mailer.account_information(user, 'pAsswORd').deliver + end + end + + def test_lost_password + token = Token.find(2) + valid_languages.each do |lang| + token.user.update_attribute :language, lang.to_s + token.reload + assert Mailer.lost_password(token).deliver + end + end + + def test_register + token = Token.find(1) + Setting.host_name = 'redmine.foo' + Setting.protocol = 'https' + + valid_languages.each do |lang| + token.user.update_attribute :language, lang.to_s + token.reload + ActionMailer::Base.deliveries.clear + assert Mailer.register(token).deliver + mail = last_email + assert_select_email do + assert_select "a[href=?]", + "https://redmine.foo/account/activate?token=#{token.value}", + :text => "https://redmine.foo/account/activate?token=#{token.value}" + end + end + end + + def test_test + user = User.find(1) + valid_languages.each do |lang| + user.update_attribute :language, lang.to_s + assert Mailer.test_email(user).deliver + end + end + + def test_reminders + Mailer.reminders(:days => 42) + assert_equal 1, ActionMailer::Base.deliveries.size + mail = last_email + assert mail.bcc.include?('dlopper@somenet.foo') + assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail + assert_equal '1 issue(s) due in the next 42 days', mail.subject + end + + def test_reminders_should_not_include_closed_issues + with_settings :default_language => 'en' do + Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5, + :subject => 'Closed issue', :assigned_to_id => 3, + :due_date => 5.days.from_now, + :author_id => 2) + ActionMailer::Base.deliveries.clear + + Mailer.reminders(:days => 42) + assert_equal 1, ActionMailer::Base.deliveries.size + mail = last_email + assert mail.bcc.include?('dlopper@somenet.foo') + assert_mail_body_no_match 'Closed issue', mail + end + end + + def test_reminders_for_users + Mailer.reminders(:days => 42, :users => ['5']) + assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper + Mailer.reminders(:days => 42, :users => ['3']) + assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper + mail = last_email + assert mail.bcc.include?('dlopper@somenet.foo') + assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail + end + + def test_reminder_should_include_issues_assigned_to_groups + with_settings :default_language => 'en' do + group = Group.generate! + group.users << User.find(2) + group.users << User.find(3) + + Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, + :subject => 'Assigned to group', :assigned_to => group, + :due_date => 5.days.from_now, + :author_id => 2) + ActionMailer::Base.deliveries.clear + + Mailer.reminders(:days => 7) + assert_equal 2, ActionMailer::Base.deliveries.size + assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.map(&:bcc).flatten.sort + ActionMailer::Base.deliveries.each do |mail| + assert_mail_body_match 'Assigned to group', mail + end + end + end + + def test_mailer_should_not_change_locale + Setting.default_language = 'en' + # Set current language to italian + set_language_if_valid 'it' + # Send an email to a french user + user = User.find(1) + user.language = 'fr' + Mailer.account_activated(user).deliver + mail = last_email + assert_mail_body_match 'Votre compte', mail + + assert_equal :it, current_language + end + + def test_with_deliveries_off + Mailer.with_deliveries false do + Mailer.test_email(User.find(1)).deliver + end + assert ActionMailer::Base.deliveries.empty? + # should restore perform_deliveries + assert ActionMailer::Base.perform_deliveries + end + + def test_layout_should_include_the_emails_header + with_settings :emails_header => "*Header content*" do + with_settings :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".header" do + assert_select "strong", :text => "Header content" + end + end + end + with_settings :plain_text_mail => 1 do + assert Mailer.test_email(User.find(1)).deliver + mail = last_email + assert_not_nil mail + assert_include "*Header content*", mail.body.decoded + end + end + end + + def test_layout_should_not_include_empty_emails_header + with_settings :emails_header => "", :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".header", false + end + end + end + + def test_layout_should_include_the_emails_footer + with_settings :emails_footer => "*Footer content*" do + with_settings :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".footer" do + assert_select "strong", :text => "Footer content" + end + end + end + with_settings :plain_text_mail => 1 do + assert Mailer.test_email(User.find(1)).deliver + mail = last_email + assert_not_nil mail + assert_include "\n-- \n", mail.body.decoded + assert_include "*Footer content*", mail.body.decoded + end + end + end + + def test_layout_should_not_include_empty_emails_footer + with_settings :emails_footer => "" do + with_settings :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".footer", false + end + end + with_settings :plain_text_mail => 1 do + assert Mailer.test_email(User.find(1)).deliver + mail = last_email + assert_not_nil mail + assert_not_include "\n-- \n", mail.body.decoded + end + end + end + + def test_should_escape_html_templates_only + Issue.generate!(:project_id => 1, :tracker_id => 1, :subject => 'Subject with a ') + mail = last_email + assert_equal 2, mail.parts.size + assert_include '', text_part.body.encoded + assert_include '<tag>', html_part.body.encoded + end + + def test_should_raise_delivery_errors_when_raise_delivery_errors_is_true + mail = Mailer.test_email(User.find(1)) + mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error")) + + ActionMailer::Base.raise_delivery_errors = true + assert_raise Exception, "delivery error" do + mail.deliver + end + ensure + ActionMailer::Base.raise_delivery_errors = false + end + + def test_should_log_delivery_errors_when_raise_delivery_errors_is_false + mail = Mailer.test_email(User.find(1)) + mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error")) + + Rails.logger.expects(:error).with("Email delivery error: delivery error") + ActionMailer::Base.raise_delivery_errors = false + assert_nothing_raised do + mail.deliver + end + end + + def test_mail_should_return_a_mail_message + assert_kind_of ::Mail::Message, Mailer.test_email(User.find(1)) + end + + private + + def last_email + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + mail + end + + def text_part + last_email.parts.detect {|part| part.content_type.include?('text/plain')} + end + + def html_part + last_email.parts.detect {|part| part.content_type.include?('text/html')} + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/09/099fe610435cd5107ad5a4a6139377c6eab6f41e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/099fe610435cd5107ad5a4a6139377c6eab6f41e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,46 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module WikiFormatting + module Textile + module Helper + def wikitoolbar_for(field_id) + heads_for_wiki_formatter + # Is there a simple way to link to a public resource? + url = "#{Redmine::Utils.relative_url_root}/help/#{current_language.to_s.downcase}/wiki_syntax.html" + javascript_tag("var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript url}'); wikiToolbar.draw();") + end + + def initial_page_content(page) + "h1. #{@page.pretty_title}" + end + + def heads_for_wiki_formatter + unless @heads_for_wiki_formatter_included + content_for :header_tags do + javascript_include_tag('jstoolbar/jstoolbar-textile.min') + + javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language.to_s.downcase}") + + stylesheet_link_tag('jstoolbar') + end + @heads_for_wiki_formatter_included = true + end + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/09/09c2ce49203d7971d2ffb1539d86601fbcb17a3b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09c2ce49203d7971d2ffb1539d86601fbcb17a3b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,93 @@ +
    + <%= link_to(l(:label_version_new), new_project_version_path(@project), + :class => 'icon icon-add') if User.current.allowed_to?(:manage_versions, @project) %> +
    + +

    <%=l(:label_roadmap)%>

    + +<% if @versions.empty? %> +

    <%= l(:label_no_data) %>

    +<% else %> +
    + <% @versions.each do |version| %> +

    <%= link_to_version version, :name => version_anchor(version) %>

    + <%= render :partial => 'versions/overview', :locals => {:version => version} %> + <%= render(:partial => "wiki/content", + :locals => {:content => version.wiki_page.content}) if version.wiki_page %> + <% if (issues = @issues_by_version[version]) && issues.size > 0 %> + <%= form_tag({}) do -%> + + + <% issues.each do |issue| -%> + + + + + <% end -%> + + <% end %> + <% end %> + <%= call_hook :view_projects_roadmap_version_bottom, :version => version %> + <% end %> +
    +<% end %> + +<% content_for :sidebar do %> +<%= form_tag({}, :method => :get) do %> +

    <%= l(:label_roadmap) %>

    +
      +<% @trackers.each do |tracker| %> +
    • + +
    • +<% end %> +
    +

    +
      +
    • + +
    • + <% if @project.descendants.active.any? %> +
    • + <%= hidden_field_tag 'with_subprojects', 0 %> + +
    • + <% end %> +
    +

    <%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %>

    +<% end %> + +

    <%= l(:label_version_plural) %>

    +
      +<% @versions.each do |version| %> +
    • + <%= link_to(format_version_name(version), "##{version_anchor(version)}") %> +
    • +<% end %> +
    +<% if @completed_versions.present? %> +

    + <%= link_to_function l(:label_completed_versions), + '$("#toggle-completed-versions").toggleClass("collapsed"); $("#completed-versions").toggle()', + :id => 'toggle-completed-versions', :class => 'collapsible collapsed' %> +

    +

    +<% end %> +<% end %> + +<% html_title(l(:label_roadmap)) %> + +<%= context_menu issues_context_menu_path %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/09/09db2139c5f23fc2a9c87d7a056e38c3f53388a9.svn-base --- a/.svn/pristine/09/09db2139c5f23fc2a9c87d7a056e38c3f53388a9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1096 +0,0 @@ -# Estonian localization for Redmine -# Copyright (C) 2012 Kaitseministeerium -# -# 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. - -et: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%d.%m.%Y" - short: "%d.%b" - long: "%d. %B %Y" - - day_names: [Pühapäev, Esmaspäev, Teisipäev, Kolmapäev, Neljapäev, Reede, Laupäev] - abbr_day_names: [P, E, T, K, N, R, L] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Jaanuar, Veebruar, Märts, Aprill, Mai, Juuni, Juuli, August, September, Oktoober, November, Detsember] - abbr_month_names: [~, jaan, veebr, märts, apr, mai, juuni, juuli, aug, sept, okt, nov, dets] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%d.%m.%Y %H:%M" - time: "%H:%M" - short: "%d.%b %H:%M" - long: "%d. %B %Y %H:%M %z" - am: "enne lõunat" - pm: "peale lõunat" - - datetime: - distance_in_words: - half_a_minute: "pool minutit" - less_than_x_seconds: - one: "vähem kui sekund" - other: "vähem kui %{count} sekundit" - x_seconds: - one: "1 sekund" - other: "%{count} sekundit" - less_than_x_minutes: - one: "vähem kui minut" - other: "vähem kui %{count} minutit" - x_minutes: - one: "1 minut" - other: "%{count} minutit" - about_x_hours: - one: "umbes tund" - other: "umbes %{count} tundi" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 päev" - other: "%{count} päeva" - about_x_months: - one: "umbes kuu" - other: "umbes %{count} kuud" - x_months: - one: "1 kuu" - other: "%{count} kuud" - about_x_years: - one: "umbes aasta" - other: "umbes %{count} aastat" - over_x_years: - one: "rohkem kui aasta" - other: "rohkem kui %{count} aastat" - almost_x_years: - one: "peaaegu aasta" - other: "peaaegu %{count} aastat" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "bait" - other: "baiti" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "ja" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 viga ei võimaldanud selle %{model} salvestamist" - other: "%{count} viga ei võimaldanud selle %{model} salvestamist" - messages: - inclusion: "ei ole nimekirjas" - exclusion: "on reserveeritud" - invalid: "ei sobi" - confirmation: "ei lange kinnitusega kokku" - accepted: "peab olema aktsepteeritud" - empty: "ei või olla tühi" - blank: "ei või olla täitmata" - too_long: "on liiga pikk (lubatud on kuni %{count} märki)" - too_short: "on liiga lühike (vaja on vähemalt %{count} märki)" - wrong_length: "on vale pikkusega (peaks olema %{count} märki)" - taken: "on juba võetud" - not_a_number: "ei ole arv" - not_a_date: "ei ole korrektne kuupäev" - greater_than: "peab olema suurem kui %{count}" - greater_than_or_equal_to: "peab olema võrdne või suurem kui %{count}" - equal_to: "peab võrduma %{count}-ga" - less_than: "peab olema väiksem kui %{count}" - less_than_or_equal_to: "peab olema võrdne või väiksem kui %{count}" - odd: "peab olema paaritu arv" - even: "peab olema paarisarv" - greater_than_start_date: "peab olema suurem kui alguskuupäev" - not_same_project: "ei kuulu sama projekti juurde" - circular_dependency: "See suhe looks vastastikuse sõltuvuse" - cant_link_an_issue_with_a_descendant: "Teemat ei saa sidustada tema enda alamteemaga" - - actionview_instancetag_blank_option: "Palun vali" - - general_text_No: "Ei" - general_text_Yes: "Jah" - general_text_no: "ei" - general_text_yes: "jah" - general_lang_name: "Eesti" - general_csv_separator: "," - general_csv_decimal_separator: "." - general_csv_encoding: ISO-8859-13 - general_pdf_encoding: UTF-8 - general_first_day_of_week: "1" - - notice_account_updated: "Konto uuendamine õnnestus." - notice_account_invalid_creditentials: "Sobimatu kasutajanimi või parool" - notice_account_password_updated: "Parooli uuendamine õnnestus." - notice_account_wrong_password: "Vale parool" - notice_account_register_done: "Konto loomine õnnestus. Konto aktiveerimiseks vajuta vastaval lingil Sulle saadetud e-kirjas." - notice_account_unknown_email: "Tundmatu kasutaja." - notice_can_t_change_password: "See konto kasutab välist autentimisallikat. Siin ei saa selle konto parooli vahetada." - notice_account_lost_email_sent: "Sulle saadeti e-kiri parooli vahetamise juhistega." - notice_account_activated: "Su konto on aktiveeritud. Saad nüüd sisse logida." - notice_successful_create: "Loomine õnnestus." - notice_successful_update: "Uuendamine õnnestus." - notice_successful_delete: "Kustutamine õnnestus." - notice_successful_connection: "Ühenduse loomine õnnestus." - notice_file_not_found: "Sellist lehte ei leitud." - notice_locking_conflict: "Teine kasutaja uuendas vahepeal neid andmeid." - notice_not_authorized: "Sul ei ole sellele lehele ligipääsuks õigusi." - notice_not_authorized_archived_project: "See projekt on arhiveeritud." - notice_email_sent: "%{value}-le saadeti kiri" - notice_email_error: "Kirja saatmisel tekkis viga (%{value})" - notice_feeds_access_key_reseted: "Sinu RSS juurdepääsuvõti nulliti." - notice_api_access_key_reseted: "Sinu API juurdepääsuvõti nulliti." - notice_failed_to_save_issues: "%{count} teemat %{total}-st ei õnnestunud salvestada: %{ids}." - notice_failed_to_save_time_entries: "%{count} ajakulu kannet %{total}-st ei õnnestunud salvestada: %{ids}." - notice_failed_to_save_members: "Liiget/liikmeid ei õnnestunud salvestada: %{errors}." - notice_no_issue_selected: "Ühtegi teemat ei ole valitud! Palun vali teema(d), mida soovid muuta." - notice_account_pending: "Sinu konto on loodud ja ootab nüüd administraatori kinnitust." - notice_default_data_loaded: "Algseadistuste laadimine õnnestus." - notice_unable_delete_version: "Versiooni kustutamine ei õnnestunud." - notice_unable_delete_time_entry: "Ajakulu kande kustutamine ei õnnestunud." - notice_issue_done_ratios_updated: "Teema edenemise astmed on uuendatud." - notice_gantt_chart_truncated: "Diagrammi kärbiti kuna ületati kuvatavate objektide suurim hulk (%{max})" - notice_issue_successful_create: "Teema %{id} loodud." - notice_issue_update_conflict: "Teine kasutaja uuendas seda teemat Sinuga samaaegselt." - notice_account_deleted: "Sinu konto on lõplikult kustutatud." - - error_can_t_load_default_data: "Algseadistusi ei saanud laadida: %{value}" - error_scm_not_found: "Seda sissekannet hoidlast ei leitud." - error_scm_command_failed: "Hoidla poole pöördumisel tekkis viga: %{value}" - error_scm_annotate: "Sissekannet ei eksisteeri või ei saa annoteerida." - error_scm_annotate_big_text_file: "Sissekannet ei saa annoteerida, kuna see on liiga pikk." - error_issue_not_found_in_project: "Teemat ei leitud või see ei kuulu siia projekti" - error_no_tracker_in_project: "Selle projektiga ei ole seostatud ühtegi valdkonda. Palun vaata üle projekti seaded." - error_no_default_issue_status: 'Teema algolek on määramata. Palun vaata asetused üle ("Seadistused -> Olekud").' - error_can_not_delete_custom_field: "Omaloodud välja kustutamine ei õnnestunud" - error_can_not_delete_tracker: "See valdkond on mõnes teemas kasutusel ja seda ei saa kustutada." - error_can_not_remove_role: "See roll on mõnes projektis kasutusel ja seda ei saa kustutada." - error_can_not_reopen_issue_on_closed_version: "Suletud versiooni juurde kuulunud teemat ei saa taasavada" - error_can_not_archive_project: "Seda projekti ei saa arhiveerida" - error_issue_done_ratios_not_updated: "Teema edenemise astmed jäid uuendamata." - error_workflow_copy_source: "Palun vali algne valdkond või roll" - error_workflow_copy_target: "Palun vali sihtvaldkon(na)d või -roll(id)" - error_unable_delete_issue_status: "Oleku kustutamine ei õnnestunud" - error_unable_to_connect: "Ühenduse loomine ei õnnestunud (%{value})" - error_attachment_too_big: "Faili ei saa üles laadida, sest see on lubatust (%{max_size}) pikem" - warning_attachments_not_saved: "%{count} faili salvestamine ei õnnestunud." - - mail_subject_lost_password: "Sinu %{value} parool" - mail_body_lost_password: "Et vahetada oma parooli, vajuta järgmisele lingile:" - mail_subject_register: "Sinu %{value} konto aktiveerimine" - mail_body_register: "Et aktiveerida oma kontot, vajuta järgmisele lingile:" - mail_body_account_information_external: "Sisse logimiseks saad kasutada oma %{value} kontot." - mail_body_account_information: "Sinu konto teave" - mail_subject_account_activation_request: "%{value} konto aktiveerimise nõue" - mail_body_account_activation_request: "Registreerus uus kasutaja (%{value}). Konto avamine ootab Sinu kinnitust:" - mail_subject_reminder: "%{count} teema tähtaeg jõuab kätte järgmise %{days} päeva jooksul" - mail_body_reminder: "%{count} Sulle määratud teema tähtaeg jõuab kätte järgmise %{days} päeva jooksul:" - mail_subject_wiki_content_added: "Lisati '%{id}' vikileht" - mail_body_wiki_content_added: "'%{id}' vikileht lisati %{author} poolt." - mail_subject_wiki_content_updated: "Uuendati '%{id}' vikilehte" - mail_body_wiki_content_updated: "'%{id}' vikilehte uuendati %{author} poolt." - - gui_validation_error: "1 viga" - gui_validation_error_plural: "%{count} viga" - - field_name: "Nimi" - field_description: "Kirjeldus" - field_summary: "Kokkuvõte" - field_is_required: "Kohustuslik" - field_firstname: "Eesnimi" - field_lastname: "Perekonnanimi" - field_mail: "E-post" - field_filename: "Fail" - field_filesize: "Pikkus" - field_downloads: "Allalaadimist" - field_author: "Autor" - field_created_on: "Loodud" - field_updated_on: "Uuendatud" - field_field_format: "Tüüp" - field_is_for_all: "Kõigile projektidele" - field_possible_values: "Võimalikud väärtused" - field_regexp: "Regulaarne avaldis" - field_min_length: "Vähim pikkus" - field_max_length: "Suurim pikkus" - field_value: "Väärtus" - field_category: "Kategooria" - field_title: "Pealkiri" - field_project: "Projekt" - field_issue: "Teema" - field_status: "Olek" - field_notes: "Märkused" - field_is_closed: "Sulgeb teema" - field_is_default: "Algolek" - field_tracker: "Valdkond" - field_subject: "Teema" - field_due_date: "Tähtaeg" - field_assigned_to: "Tegeleja" - field_priority: "Prioriteet" - field_fixed_version: "Sihtversioon" - field_user: "Kasutaja" - field_principal: "Vastutav isik" - field_role: "Roll" - field_homepage: "Koduleht" - field_is_public: "Avalik" - field_parent: "Emaprojekt" - field_is_in_roadmap: "Teemad on teekaardil näha" - field_login: "Kasutajanimi" - field_mail_notification: "Teated e-kirjaga" - field_admin: "Admin" - field_last_login_on: "Viimane ühendus" - field_language: "Keel" - field_effective_date: "Tähtaeg" - field_password: "Parool" - field_new_password: "Uus parool" - field_password_confirmation: "Kinnitus" - field_version: "Versioon" - field_type: "Tüüp" - field_host: "Server" - field_port: "Port" - field_account: "Konto" - field_base_dn: "Baas DN" - field_attr_login: "Kasutajanime atribuut" - field_attr_firstname: "Eesnime atribuut" - field_attr_lastname: "Perekonnanime atribuut" - field_attr_mail: "E-posti atribuut" - field_onthefly: "Kasutaja automaatne loomine" - field_start_date: "Alguskuupäev" - field_done_ratio: "% tehtud" - field_auth_source: "Autentimise viis" - field_hide_mail: "Peida e-posti aadress" - field_comments: "Kommentaar" - field_url: "URL" - field_start_page: "Esileht" - field_subproject: "Alamprojekt" - field_hours: "tundi" - field_activity: "Tegevus" - field_spent_on: "Kuupäev" - field_identifier: "Tunnus" - field_is_filter: "Kasutatakse filtrina" - field_issue_to: "Seotud teema" - field_delay: "Viivitus" - field_assignable: "Saab määrata teemadega tegelema" - field_redirect_existing_links: "Suuna olemasolevad lingid ringi" - field_estimated_hours: "Eeldatav ajakulu" - field_column_names: "Veerud" - field_time_entries: "Ajakulu" - field_time_zone: "Ajatsoon" - field_searchable: "Otsitav" - field_default_value: "Vaikimisi" - field_comments_sorting: "Kommentaaride järjestus" - field_parent_title: "Pärineb lehest" - field_editable: "Muudetav" - field_watcher: "Jälgija" - field_identity_url: "OpenID URL" - field_content: "Sisu" - field_group_by: "Grupeeri tulemus" - field_sharing: "Teemade jagamine" - field_parent_issue: "Pärineb teemast" - field_member_of_group: "Tegeleja grupp" - field_assigned_to_role: "Tegeleja roll" - field_text: "Tekstiväli" - field_visible: "Nähtav" - field_warn_on_leaving_unsaved: "Hoiata salvestamata sisuga lehtedelt lahkumisel" - field_issues_visibility: "See roll näeb" - field_is_private: "Privaatne" - field_commit_logs_encoding: "Sissekannete kodeering" - field_scm_path_encoding: "Teeraja märkide kodeering" - field_path_to_repository: "Hoidla teerada" - field_root_directory: "Juurkataloog" - field_cvsroot: "CVSROOT" - field_cvs_module: "Moodul" - field_repository_is_default: "Peamine hoidla" - field_multiple: "Korraga mitu väärtust" - field_auth_source_ldap_filter: "LDAP filter" - - setting_app_title: "Veebilehe pealkiri" - setting_app_subtitle: "Veebilehe alampealkiri" - setting_welcome_text: "Tervitustekst" - setting_default_language: "Vaikimisi keel" - setting_login_required: "Autentimine kohustuslik" - setting_self_registration: "Omaloodud konto aktiveerimine" - setting_attachment_max_size: "Manuse suurim pikkus" - setting_issues_export_limit: "Teemade ekspordi limiit" - setting_mail_from: "Saatja e-posti aadress" - setting_bcc_recipients: "Saajaid ei näidata (lähevad BCC reale)" - setting_plain_text_mail: "E-kiri tavalise tekstina (ilma HTML-ta)" - setting_host_name: "Serveri nimi ja teerada" - setting_text_formatting: "Vormindamise abi" - setting_wiki_compression: "Viki ajaloo pakkimine" - setting_feeds_limit: "Atom voogude suurim objektide arv" - setting_default_projects_public: "Uued projektid on vaikimisi avalikud" - setting_autofetch_changesets: "Lae uuendused automaatselt" - setting_sys_api_enabled: "Hoidlate haldamine veebiteenuse kaudu" - setting_commit_ref_keywords: "Viitade võtmesõnad" - setting_commit_fix_keywords: "Paranduste võtmesõnad" - setting_autologin: "Automaatne sisselogimine" - setting_date_format: "Kuupäevaformaat" - setting_time_format: "Ajaformaat" - setting_cross_project_issue_relations: "Luba siduda eri projektide teemasid" - setting_issue_list_default_columns: "Teemade nimekirja vaikimisi veerud" - setting_repositories_encodings: "Manuste ja hoidlate kodeering" - setting_emails_header: "E-kirja päis" - setting_emails_footer: "E-kirja jalus" - setting_protocol: "Protokoll" - setting_per_page_options: "Objekte lehe kohta variandid" - setting_user_format: "Kasutaja nime esitamise vorm" - setting_activity_days_default: "Projektide ajalugu näidatakse" - setting_display_subprojects_issues: "Näita projektis vaikimisi ka alamprojektide teemasid" - setting_enabled_scm: "Kasutatavad lähtekoodi haldusvahendid" - setting_mail_handler_body_delimiters: "Kärbi e-kirja lõpp peale sellist rida" - setting_mail_handler_api_enabled: "E-kirjade vastuvõtt veebiteenuse kaudu" - setting_mail_handler_api_key: "Veebiteenuse API võti" - setting_sequential_project_identifiers: "Genereeri järjestikused projektitunnused" - setting_gravatar_enabled: "Kasuta Gravatari kasutajaikoone" - setting_gravatar_default: "Vaikimisi kasutatav ikoon" - setting_diff_max_lines_displayed: "Enim korraga näidatavaid erinevusi" - setting_file_max_size_displayed: "Kuvatava tekstifaili suurim pikkus" - setting_repository_log_display_limit: "Enim ajaloos näidatavaid sissekandeid" - setting_openid: "Luba OpenID-ga registreerimine ja sisselogimine" - setting_password_min_length: "Lühima lubatud parooli pikkus" - setting_new_project_user_role_id: "Projekti looja roll oma projektis" - setting_default_projects_modules: "Vaikimisi moodulid uutes projektides" - setting_issue_done_ratio: "Määra teema edenemise aste" - setting_issue_done_ratio_issue_field: "kasutades vastavat välja" - setting_issue_done_ratio_issue_status: "kasutades teema olekut" - setting_start_of_week: "Nädala alguspäev" - setting_rest_api_enabled: "Luba REST API kasutamine" - setting_cache_formatted_text: "Puhverda vormindatud teksti" - setting_default_notification_option: "Vaikimisi teavitatakse" - setting_commit_logtime_enabled: "Luba ajakulu sisestamine" - setting_commit_logtime_activity_id: "Tegevus kulunud ajal" - setting_gantt_items_limit: "Gantti diagrammi objektide suurim hulk" - setting_issue_group_assignment: "Luba teemade andmine gruppidele" - setting_default_issue_start_date_to_creation_date: "Uute teemade alguskuupäevaks teema loomise päev" - setting_commit_cross_project_ref: "Luba viiteid ja parandusi ka kõigi teiste projektide teemadele" - setting_unsubscribe: "Luba kasutajal oma konto kustutada" - - permission_add_project: "Projekte luua" - permission_add_subprojects: "Alamprojekte luua" - permission_edit_project: "Projekte muuta" - permission_select_project_modules: "Projektimooduleid valida" - permission_manage_members: "Liikmeid hallata" - permission_manage_project_activities: "Projekti tegevusi hallata" - permission_manage_versions: "Versioone hallata" - permission_manage_categories: "Kategooriaid hallata" - permission_view_issues: "Teemasid näha" - permission_add_issues: "Teemasid lisada" - permission_edit_issues: "Teemasid uuendada" - permission_manage_issue_relations: "Teemade seoseid hallata" - permission_set_issues_private: "Teemasid avalikeks või privaatseiks seada" - permission_set_own_issues_private: "Omi teemasid avalikeks või privaatseiks seada" - permission_add_issue_notes: "Märkusi lisada" - permission_edit_issue_notes: "Märkusi muuta" - permission_edit_own_issue_notes: "Omi märkusi muuta" - permission_move_issues: "Teemasid teise projekti tõsta" - permission_delete_issues: "Teemasid kustutada" - permission_manage_public_queries: "Avalikke päringuid hallata" - permission_save_queries: "Päringuid salvestada" - permission_view_gantt: "Gantti diagramme näha" - permission_view_calendar: "Kalendrit näha" - permission_view_issue_watchers: "Jälgijate nimekirja näha" - permission_add_issue_watchers: "Jälgijaid lisada" - permission_delete_issue_watchers: "Jälgijaid kustutada" - permission_log_time: "Ajakulu sisestada" - permission_view_time_entries: "Ajakulu näha" - permission_edit_time_entries: "Ajakulu kandeid muuta" - permission_edit_own_time_entries: "Omi ajakulu kandeid muuta" - permission_manage_news: "Uudiseid hallata" - permission_comment_news: "Uudiseid kommenteerida" - permission_manage_documents: "Dokumente hallata" - permission_view_documents: "Dokumente näha" - permission_manage_files: "Faile hallata" - permission_view_files: "Faile näha" - permission_manage_wiki: "Vikit hallata" - permission_rename_wiki_pages: "Vikilehti ümber nimetada" - permission_delete_wiki_pages: "Vikilehti kustutada" - permission_view_wiki_pages: "Vikit näha" - permission_view_wiki_edits: "Viki ajalugu näha" - permission_edit_wiki_pages: "Vikilehti muuta" - permission_delete_wiki_pages_attachments: "Manuseid kustutada" - permission_protect_wiki_pages: "Vikilehti kaitsta" - permission_manage_repository: "Hoidlaid hallata" - permission_browse_repository: "Hoidlaid sirvida" - permission_view_changesets: "Sissekandeid näha" - permission_commit_access: "Sissekandeid teha" - permission_manage_boards: "Foorumeid hallata" - permission_view_messages: "Postitusi näha" - permission_add_messages: "Postitusi lisada" - permission_edit_messages: "Postitusi muuta" - permission_edit_own_messages: "Omi postitusi muuta" - permission_delete_messages: "Postitusi kustutada" - permission_delete_own_messages: "Omi postitusi kustutada" - permission_export_wiki_pages: "Vikilehti eksportida" - permission_manage_subtasks: "Alamteemasid hallata" - permission_manage_related_issues: "Seotud teemasid hallata" - - project_module_issue_tracking: "Teemade jälgimine" - project_module_time_tracking: "Ajakulu arvestus" - project_module_news: "Uudised" - project_module_documents: "Dokumendid" - project_module_files: "Failid" - project_module_wiki: "Viki" - project_module_repository: "Hoidlad" - project_module_boards: "Foorumid" - project_module_calendar: "Kalender" - project_module_gantt: "Gantt" - - label_user: "Kasutaja" - label_user_plural: "Kasutajad" - label_user_new: "Uus kasutaja" - label_user_anonymous: "Anonüümne" - label_project: "Projekt" - label_project_new: "Uus projekt" - label_project_plural: "Projektid" - label_x_projects: - zero: "pole projekte" - one: "1 projekt" - other: "%{count} projekti" - label_project_all: "Kõik projektid" - label_project_latest: "Viimased projektid" - label_issue: "Teema" - label_issue_new: "Uus teema" - label_issue_plural: "Teemad" - label_issue_view_all: "Teemade nimekiri" - label_issues_by: "Teemad %{value} järgi" - label_issue_added: "Teema lisatud" - label_issue_updated: "Teema uuendatud" - label_issue_note_added: "Märkus lisatud" - label_issue_status_updated: "Olek uuendatud" - label_issue_priority_updated: "Prioriteet uuendatud" - label_document: "Dokument" - label_document_new: "Uus dokument" - label_document_plural: "Dokumendid" - label_document_added: "Dokument lisatud" - label_role: "Roll" - label_role_plural: "Rollid" - label_role_new: "Uus roll" - label_role_and_permissions: "Rollid ja õigused" - label_role_anonymous: "Anonüümne" - label_role_non_member: "Mitteliige" - label_member: "Liige" - label_member_new: "Uus liige" - label_member_plural: "Liikmed" - label_tracker: "Valdkond" - label_tracker_plural: "Valdkonnad" - label_tracker_new: "Uus valdkond" - label_workflow: "Töövood" - label_issue_status: "Olek" - label_issue_status_plural: "Olekud" - label_issue_status_new: "Uus olek" - label_issue_category: "Kategooria" - label_issue_category_plural: "Kategooriad" - label_issue_category_new: "Uus kategooria" - label_custom_field: "Omaloodud väli" - label_custom_field_plural: "Omaloodud väljad" - label_custom_field_new: "Uus väli" - label_enumerations: "Loetelud" - label_enumeration_new: "Uus väärtus" - label_information: "Teave" - label_information_plural: "Teave" - label_please_login: "Palun logi sisse" - label_register: "Registreeru" - label_login_with_open_id_option: "või logi sisse OpenID-ga" - label_password_lost: "Kui parool on ununud..." - label_home: "Kodu" - label_my_page: "Oma leht" - label_my_account: "Oma konto" - label_my_projects: "Oma projektid" - label_my_page_block: "Uus blokk" - label_administration: "Seadistused" - label_login: "Logi sisse" - label_logout: "Logi välja" - label_help: "Abi" - label_reported_issues: "Minu poolt lisatud teemad" - label_assigned_to_me_issues: "Minu teha olevad teemad" - label_last_login: "Viimane ühendus" - label_registered_on: "Registreeritud" - label_activity: "Ajalugu" - label_overall_activity: "Üldine tegevuste ajalugu" - label_user_activity: "%{value} tegevuste ajalugu" - label_new: "Uus" - label_logged_as: "Sisse logitud kui" - label_environment: "Keskkond" - label_authentication: "Autentimine" - label_auth_source: "Autentimisallikas" - label_auth_source_new: "Uus autentimisallikas" - label_auth_source_plural: "Autentimisallikad" - label_subproject_plural: "Alamprojektid" - label_subproject_new: "Uus alamprojekt" - label_and_its_subprojects: "%{value} ja selle alamprojektid" - label_min_max_length: "Min.-maks. pikkus" - label_list: "Nimekiri" - label_date: "Kuupäev" - label_integer: "Täisarv" - label_float: "Ujukomaarv" - label_boolean: "Tõeväärtus" - label_string: "Tekst" - label_text: "Pikk tekst" - label_attribute: "Atribuut" - label_attribute_plural: "Atribuudid" - label_download: "%{count} allalaadimine" - label_download_plural: "%{count} allalaadimist" - label_no_data: "Pole" - label_change_status: "Muuda olekut" - label_history: "Ajalugu" - label_attachment: "Fail" - label_attachment_new: "Uus fail" - label_attachment_delete: "Kustuta fail" - label_attachment_plural: "Failid" - label_file_added: "Fail lisatud" - label_report: "Aruanne" - label_report_plural: "Aruanded" - label_news: "Uudised" - label_news_new: "Lisa uudis" - label_news_plural: "Uudised" - label_news_latest: "Viimased uudised" - label_news_view_all: "Kõik uudised" - label_news_added: "Uudis lisatud" - label_news_comment_added: "Kommentaar uudisele lisatud" - label_settings: "Seaded" - label_overview: "Ülevaade" - label_version: "Versioon" - label_version_new: "Uus versioon" - label_version_plural: "Versioonid" - label_close_versions: "Sulge lõpetatud versioonid" - label_confirmation: "Kinnitus" - label_export_to: "Samuti saadaval kujul:" - label_read: "Loe..." - label_public_projects: "Avalikud projektid" - label_open_issues: "avatud" - label_open_issues_plural: "avatud" - label_closed_issues: "suletud" - label_closed_issues_plural: "suletud" - label_x_open_issues_abbr_on_total: - zero: "0 avatud / %{total}" - one: "1 avatud / %{total}" - other: "%{count} avatud / %{total}" - label_x_open_issues_abbr: - zero: "0 avatud" - one: "1 avatud" - other: "%{count} avatud" - label_x_closed_issues_abbr: - zero: "0 suletud" - one: "1 suletud" - other: "%{count} suletud" - label_x_issues: - zero: "0 teemat" - one: "1 teema" - other: "%{count} teemat" - label_total: "Kokku" - label_permissions: "Õigused" - label_current_status: "Praegune olek" - label_new_statuses_allowed: "Uued lubatud olekud" - label_all: "kõik" - label_none: "pole" - label_nobody: "eikeegi" - label_next: "Järgmine" - label_previous: "Eelmine" - label_used_by: "Kasutab" - label_details: "Üksikasjad" - label_add_note: "Lisa märkus" - label_per_page: "Lehe kohta" - label_calendar: "Kalender" - label_months_from: "kuu kaugusel" - label_gantt: "Gantt" - label_internal: "Sisemine" - label_last_changes: "viimased %{count} muudatust" - label_change_view_all: "Kõik muudatused" - label_personalize_page: "Kujunda leht ümber" - label_comment: "Kommentaar" - label_comment_plural: "Kommentaarid" - label_x_comments: - zero: "kommentaare pole" - one: "1 kommentaar" - other: "%{count} kommentaari" - label_comment_add: "Lisa kommentaar" - label_comment_added: "Kommentaar lisatud" - label_comment_delete: "Kustuta kommentaar" - label_query: "Omaloodud päring" - label_query_plural: "Omaloodud päringud" - label_query_new: "Uus päring" - label_my_queries: "Mu omaloodud päringud" - label_filter_add: "Lisa filter" - label_filter_plural: "Filtrid" - label_equals: "on" - label_not_equals: "ei ole" - label_in_less_than: "on väiksem kui" - label_in_more_than: "on suurem kui" - label_greater_or_equal: "suurem-võrdne" - label_less_or_equal: "väiksem-võrdne" - label_between: "vahemikus" - label_in: "sisaldub hulgas" - label_today: "täna" - label_all_time: "piirideta" - label_yesterday: "eile" - label_this_week: "sel nädalal" - label_last_week: "eelmisel nädalal" - label_last_n_days: "viimase %{count} päeva jooksul" - label_this_month: "sel kuul" - label_last_month: "eelmisel kuul" - label_this_year: "sel aastal" - label_date_range: "Kuupäevavahemik" - label_less_than_ago: "uuem kui" - label_more_than_ago: "vanem kui" - label_ago: "vanus" - label_contains: "sisaldab" - label_not_contains: "ei sisalda" - label_day_plural: "päeva" - label_repository: "Hoidla" - label_repository_new: "Uus hoidla" - label_repository_plural: "Hoidlad" - label_browse: "Sirvi" - label_modification: "%{count} muudatus" - label_modification_plural: "%{count} muudatust" - label_branch: "Haru" - label_tag: "Sildiga" - label_revision: "Sissekanne" - label_revision_plural: "Sissekanded" - label_revision_id: "Sissekande kood %{value}" - label_associated_revisions: "Seotud sissekanded" - label_added: "lisatud" - label_modified: "muudetud" - label_copied: "kopeeritud" - label_renamed: "ümber nimetatud" - label_deleted: "kustutatud" - label_latest_revision: "Viimane sissekanne" - label_latest_revision_plural: "Viimased sissekanded" - label_view_revisions: "Haru ajalugu" - label_view_all_revisions: "Kogu ajalugu" - label_max_size: "Suurim pikkus" - label_sort_highest: "Nihuta esimeseks" - label_sort_higher: "Nihuta üles" - label_sort_lower: "Nihuta alla" - label_sort_lowest: "Nihuta viimaseks" - label_roadmap: "Teekaart" - label_roadmap_due_in: "Tähtaeg %{value}" - label_roadmap_overdue: "%{value} hiljaks jäänud" - label_roadmap_no_issues: "Selles versioonis ei ole teemasid" - label_search: "Otsi" - label_result_plural: "Tulemused" - label_all_words: "Kõik sõnad" - label_wiki: "Viki" - label_wiki_edit: "Viki muutmine" - label_wiki_edit_plural: "Viki muutmised" - label_wiki_page: "Vikileht" - label_wiki_page_plural: "Vikilehed" - label_index_by_title: "Järjesta pealkirja järgi" - label_index_by_date: "Järjesta kuupäeva järgi" - label_current_version: "Praegune versioon" - label_preview: "Eelvaade" - label_feed_plural: "Vood" - label_changes_details: "Kõigi muudatuste üksikasjad" - label_issue_tracking: "Teemade jälgimine" - label_spent_time: "Kulutatud aeg" - label_overall_spent_time: "Kokku kulutatud aeg" - label_f_hour: "%{value} tund" - label_f_hour_plural: "%{value} tundi" - label_time_tracking: "Ajakulu arvestus" - label_change_plural: "Muudatused" - label_statistics: "Statistika" - label_commits_per_month: "Sissekandeid kuu kohta" - label_commits_per_author: "Sissekandeid autori kohta" - label_diff: "erinevused" - label_view_diff: "Vaata erinevusi" - label_diff_inline: "teksti sees" - label_diff_side_by_side: "kõrvuti" - label_options: "Valikud" - label_copy_workflow_from: "Kopeeri see töövoog" - label_permissions_report: "Õiguste aruanne" - label_watched_issues: "Jälgitud teemad" - label_related_issues: "Seotud teemad" - label_applied_status: "Kehtestatud olek" - label_loading: "Laadimas..." - label_relation_new: "Uus seos" - label_relation_delete: "Kustuta seos" - label_relates_to: "seostub" - label_duplicates: "duplitseerib" - label_duplicated_by: "duplitseerija" - label_blocks: "blokeerib" - label_blocked_by: "blokeerija" - label_precedes: "eelneb" - label_follows: "järgneb" - label_end_to_start: "lõpust alguseni" - label_end_to_end: "lõpust lõpuni" - label_start_to_start: "algusest alguseni" - label_start_to_end: "algusest lõpuni" - label_stay_logged_in: "Püsi sisselogituna" - label_disabled: "pole võimalik" - label_show_completed_versions: "Näita lõpetatud versioone" - label_me: "mina" - label_board: "Foorum" - label_board_new: "Uus foorum" - label_board_plural: "Foorumid" - label_board_locked: "Lukus" - label_board_sticky: "Püsiteema" - label_topic_plural: "Teemad" - label_message_plural: "Postitused" - label_message_last: "Viimane postitus" - label_message_new: "Uus postitus" - label_message_posted: "Postitus lisatud" - label_reply_plural: "Vastused" - label_send_information: "Saada teave konto kasutajale" - label_year: "Aasta" - label_month: "Kuu" - label_week: "Nädal" - label_date_from: "Alates" - label_date_to: "Kuni" - label_language_based: "Kasutaja keele põhjal" - label_sort_by: "Sorteeri %{value} järgi" - label_send_test_email: "Saada kontrollkiri" - label_feeds_access_key: "RSS juurdepääsuvõti" - label_missing_feeds_access_key: "RSS juurdepääsuvõti on puudu" - label_feeds_access_key_created_on: "RSS juurdepääsuvõti loodi %{value} tagasi" - label_module_plural: "Moodulid" - label_added_time_by: "Lisatud %{author} poolt %{age} tagasi" - label_updated_time_by: "Uuendatud %{author} poolt %{age} tagasi" - label_updated_time: "Uuendatud %{value} tagasi" - label_jump_to_a_project: "Ava projekt..." - label_file_plural: "Failid" - label_changeset_plural: "Muudatused" - label_default_columns: "Vaikimisi veerud" - label_no_change_option: "(Ei muutu)" - label_bulk_edit_selected_issues: "Muuda valitud teemasid korraga" - label_bulk_edit_selected_time_entries: "Muuda valitud ajakandeid korraga" - label_theme: "Visuaalne teema" - label_default: "Tavaline" - label_search_titles_only: "Ainult pealkirjadest" - label_user_mail_option_all: "Kõigist tegevustest kõigis mu projektides" - label_user_mail_option_selected: "Kõigist tegevustest ainult valitud projektides..." - label_user_mail_option_none: "Teavitusi ei saadeta" - label_user_mail_option_only_my_events: "Ainult mu jälgitavatest või minuga seotud tegevustest" - label_user_mail_option_only_assigned: "Ainult minu teha olevate asjade kohta" - label_user_mail_option_only_owner: "Ainult mu oma asjade kohta" - label_user_mail_no_self_notified: "Ära teavita mind mu enda tehtud muudatustest" - label_registration_activation_by_email: "e-kirjaga" - label_registration_manual_activation: "käsitsi" - label_registration_automatic_activation: "automaatselt" - label_display_per_page: "Lehe kohta: %{value}" - label_age: "Vanus" - label_change_properties: "Muuda omadusi" - label_general: "Üldine" - label_more: "Rohkem" - label_scm: "Lähtekoodi haldusvahend" - label_plugins: "Lisamoodulid" - label_ldap_authentication: "LDAP autentimine" - label_downloads_abbr: "A/L" - label_optional_description: "Teave" - label_add_another_file: "Lisa veel üks fail" - label_preferences: "Eelistused" - label_chronological_order: "kronoloogiline" - label_reverse_chronological_order: "tagurpidi kronoloogiline" - label_planning: "Planeerimine" - label_incoming_emails: "Sissetulevad e-kirjad" - label_generate_key: "Genereeri võti" - label_issue_watchers: "Jälgijad" - label_example: "Näide" - label_display: "Kujundus" - label_sort: "Sorteeri" - label_ascending: "Kasvavalt" - label_descending: "Kahanevalt" - label_date_from_to: "Alates %{start} kuni %{end}" - label_wiki_content_added: "Vikileht lisatud" - label_wiki_content_updated: "Vikileht uuendatud" - label_group: "Grupp" - label_group_plural: "Grupid" - label_group_new: "Uus grupp" - label_time_entry_plural: "Kulutatud aeg" - label_version_sharing_none: "ei toimu" - label_version_sharing_descendants: "alamprojektidega" - label_version_sharing_hierarchy: "projektihierarhiaga" - label_version_sharing_tree: "projektipuuga" - label_version_sharing_system: "kõigi projektidega" - label_update_issue_done_ratios: "Uuenda edenemise astmeid" - label_copy_source: "Allikas" - label_copy_target: "Sihtkoht" - label_copy_same_as_target: "Sama mis sihtkoht" - label_display_used_statuses_only: "Näita ainult selles valdkonnas kasutusel olekuid" - label_api_access_key: "API juurdepääsuvõti" - label_missing_api_access_key: "API juurdepääsuvõti on puudu" - label_api_access_key_created_on: "API juurdepääsuvõti loodi %{value} tagasi" - label_profile: "Profiil" - label_subtask_plural: "Alamteemad" - label_project_copy_notifications: "Saada projekti kopeerimise kohta teavituskiri" - label_principal_search: "Otsi kasutajat või gruppi:" - label_user_search: "Otsi kasutajat:" - label_additional_workflow_transitions_for_author: "Luba ka järgmisi üleminekuid kui kasutaja on teema looja" - label_additional_workflow_transitions_for_assignee: "Luba ka järgmisi üleminekuid kui kasutaja on teemaga tegeleja" - label_issues_visibility_all: "kõiki teemasid" - label_issues_visibility_public: "kõiki mitteprivaatseid teemasid" - label_issues_visibility_own: "enda poolt loodud või enda teha teemasid" - label_git_report_last_commit: "Viimase sissekande teave otse failinimekirja" - label_parent_revision: "Eellane" - label_child_revision: "Järglane" - label_export_options: "%{export_format} ekspordivalikud" - label_copy_attachments: "Kopeeri manused" - label_item_position: "%{position}/%{count}" - label_completed_versions: "Lõpetatud versioonid" - label_search_for_watchers: "Otsi lisamiseks jälgijaid" - - button_login: "Logi sisse" - button_submit: "Sisesta" - button_save: "Salvesta" - button_check_all: "Märgi kõik" - button_uncheck_all: "Nulli valik" - button_collapse_all: "Voldi kõik kokku" - button_expand_all: "Voldi kõik lahti" - button_delete: "Kustuta" - button_create: "Loo" - button_create_and_continue: "Loo ja jätka" - button_test: "Testi" - button_edit: "Muuda" - button_edit_associated_wikipage: "Muuda seotud vikilehte: %{page_title}" - button_add: "Lisa" - button_change: "Muuda" - button_apply: "Lae" - button_clear: "Puhasta" - button_lock: "Lukusta" - button_unlock: "Ava lukust" - button_download: "Lae alla" - button_list: "Listi" - button_view: "Vaata" - button_move: "Tõsta" - button_move_and_follow: "Tõsta ja järgne" - button_back: "Tagasi" - button_cancel: "Katkesta" - button_activate: "Aktiveeri" - button_sort: "Sorteeri" - button_log_time: "Ajakulu" - button_rollback: "Rulli tagasi sellesse versiooni" - button_watch: "Jälgi" - button_unwatch: "Ära jälgi" - button_reply: "Vasta" - button_archive: "Arhiveeri" - button_unarchive: "Arhiivist tagasi" - button_reset: "Nulli" - button_rename: "Nimeta ümber" - button_change_password: "Vaheta parool" - button_copy: "Kopeeri" - button_copy_and_follow: "Kopeeri ja järgne" - button_annotate: "Annoteeri" - button_update: "Muuda" - button_configure: "Konfigureeri" - button_quote: "Tsiteeri" - button_duplicate: "Duplitseeri" - button_show: "Näita" - button_edit_section: "Muuda seda sektsiooni" - button_export: "Ekspordi" - button_delete_my_account: "Kustuta oma konto" - - status_active: "aktiivne" - status_registered: "registreeritud" - status_locked: "lukus" - - version_status_open: "avatud" - version_status_locked: "lukus" - version_status_closed: "suletud" - - field_active: "Aktiivne" - - text_select_mail_notifications: "Tegevused, millest peaks e-kirjaga teavitama" - text_regexp_info: "nt. ^[A-Z0-9]+$" - text_min_max_length_info: "0 tähendab, et piiranguid ei ole" - text_project_destroy_confirmation: "Oled Sa kindel oma soovis see projekt täielikult kustutada?" - text_subprojects_destroy_warning: "Alamprojekt(id) - %{value} - kustutatakse samuti." - text_workflow_edit: "Töövoo muutmiseks vali roll ja valdkond" - text_are_you_sure: "Oled Sa kindel?" - text_journal_changed: "%{label} muudetud %{old} -> %{new}" - text_journal_changed_no_detail: "%{label} uuendatud" - text_journal_set_to: "%{label} uus väärtus on %{value}" - text_journal_deleted: "%{label} kustutatud (%{old})" - text_journal_added: "%{label} %{value} lisatud" - text_tip_issue_begin_day: "teema avamise päev" - text_tip_issue_end_day: "teema sulgemise päev" - text_tip_issue_begin_end_day: "teema avati ja sulgeti samal päeval" - text_project_identifier_info: "Lubatud on ainult väikesed tähed (a-z), numbrid ja kriipsud.
    Peale salvestamist ei saa tunnust enam muuta." - text_caracters_maximum: "%{count} märki kõige rohkem." - text_caracters_minimum: "Peab olema vähemalt %{count} märki pikk." - text_length_between: "Pikkus %{min} kuni %{max} märki." - text_tracker_no_workflow: "Selle valdkonna jaoks ei ole ühtegi töövoogu kirjeldatud" - text_unallowed_characters: "Lubamatud märgid" - text_comma_separated: "Lubatud erinevad väärtused (komaga eraldatult)." - text_line_separated: "Lubatud erinevad väärtused (igaüks eraldi real)." - text_issues_ref_in_commit_messages: "Teemadele ja parandustele viitamine sissekannete märkustes" - text_issue_added: "%{author} lisas uue teema %{id}." - text_issue_updated: "%{author} uuendas teemat %{id}." - text_wiki_destroy_confirmation: "Oled Sa kindel oma soovis kustutada see Viki koos kogu sisuga?" - text_issue_category_destroy_question: "Kustutatavat kategooriat kasutab %{count} teema(t). Mis Sa soovid nendega ette võtta?" - text_issue_category_destroy_assignments: "Jäta teemadel kategooria määramata" - text_issue_category_reassign_to: "Määra teemad teise kategooriasse" - text_user_mail_option: "Valimata projektidest saad teavitusi ainult jälgitavate või Sinuga seotud asjade kohta (nt. Sinu loodud või teha teemad)." - text_no_configuration_data: "Rollid, valdkonnad, olekud ja töövood ei ole veel seadistatud.\nVäga soovitav on laadida vaikeasetused. Peale laadimist saad neid ise muuta." - text_load_default_configuration: "Lae vaikeasetused" - text_status_changed_by_changeset: "Kehtestatakse muudatuses %{value}." - text_time_logged_by_changeset: "Kehtestatakse muudatuses %{value}." - text_issues_destroy_confirmation: "Oled Sa kindel oma soovis valitud teema(d) kustutada?" - text_issues_destroy_descendants_confirmation: "See kustutab samuti %{count} alamteemat." - text_time_entries_destroy_confirmation: "Oled Sa kindel oma soovis valitud ajakulu kanne/kanded kustutada?" - text_select_project_modules: "Projektis kasutatavad moodulid" - text_default_administrator_account_changed: "Algne administraatori konto on muudetud" - text_file_repository_writable: "Manuste kataloog on kirjutatav" - text_plugin_assets_writable: "Lisamoodulite abifailide kataloog on kirjutatav" - text_rmagick_available: "RMagick on kasutatav (mittekohustuslik)" - text_destroy_time_entries_question: "Kustutatavatele teemadele oli kirja pandud %{hours} tundi. Mis Sa soovid ette võtta?" - text_destroy_time_entries: "Kustuta need tunnid" - text_assign_time_entries_to_project: "Vii tunnid üle teise projekti" - text_reassign_time_entries: "Määra tunnid sellele teemale:" - text_user_wrote: "%{value} kirjutas:" - text_enumeration_destroy_question: "Selle väärtusega on seotud %{count} objekt(i)." - text_enumeration_category_reassign_to: "Seo nad teise väärtuse külge:" - text_email_delivery_not_configured: "E-kirjade saatmine ei ole seadistatud ja teavitusi ei saadeta.\nKonfigureeri oma SMTP server failis config/configuration.yml ja taaskäivita Redmine." - text_repository_usernames_mapping: "Seosta Redmine kasutaja hoidlasse sissekannete tegijaga.\nSama nime või e-postiga kasutajad seostatakse automaatselt." - text_diff_truncated: "... Osa erinevusi jäi välja, sest neid on näitamiseks liiga palju." - text_custom_field_possible_values_info: "Üks rida iga väärtuse jaoks" - text_wiki_page_destroy_question: "Sel lehel on %{descendants} järglasleht(e) ja järeltulija(t). Mis Sa soovid ette võtta?" - text_wiki_page_nullify_children: "Muuda järglaslehed uuteks juurlehtedeks" - text_wiki_page_destroy_children: "Kustuta järglaslehed ja kõik nende järglased" - text_wiki_page_reassign_children: "Määra järglaslehed teise lehe külge" - text_own_membership_delete_confirmation: "Sa võtad endalt ära osa või kõik õigused ega saa edaspidi seda projekti võib-olla enam muuta.\nOled Sa jätkamises kindel?" - text_zoom_in: "Vaata lähemalt" - text_zoom_out: "Vaata kaugemalt" - text_warn_on_leaving_unsaved: "Sel lehel on salvestamata teksti, mis läheb kaduma, kui siit lehelt lahkud." - text_scm_path_encoding_note: "Vaikimisi UTF-8" - text_git_repository_note: "Hoidla peab olema paljas (bare) ja kohalik (nt. /gitrepo, c:\\gitrepo)" - text_mercurial_repository_note: "Hoidla peab olema kohalik (nt. /hgrepo, c:\\hgrepo)" - text_scm_command: "Hoidla poole pöördumise käsk" - text_scm_command_version: "Versioon" - text_scm_config: "Hoidlate poole pöördumist saab konfigureerida failis config/configuration.yml. Peale selle muutmist taaskäivita Redmine." - text_scm_command_not_available: "Hoidla poole pöördumine ebaõnnestus. Palun kontrolli seadistusi." - text_issue_conflict_resolution_overwrite: "Kehtesta oma muudatused (kõik märkused jäävad, ent muu võidakse üle kirjutada)" - text_issue_conflict_resolution_add_notes: "Lisa oma märkused, aga loobu teistest muudatustest" - text_issue_conflict_resolution_cancel: "Loobu kõigist muudatustest ja lae %{link} uuesti" - text_account_destroy_confirmation: "Oled Sa kindel?\nSu konto kustutatakse jäädavalt ja seda pole võimalik taastada." - - default_role_manager: "Haldaja" - default_role_developer: "Arendaja" - default_role_reporter: "Edastaja" - default_tracker_bug: "Veaparandus" - default_tracker_feature: "Täiendus" - default_tracker_support: "Klienditugi" - default_issue_status_new: "Avatud" - default_issue_status_in_progress: "Töös" - default_issue_status_resolved: "Lahendatud" - default_issue_status_feedback: "Tagasiside" - default_issue_status_closed: "Suletud" - default_issue_status_rejected: "Tagasi lükatud" - default_doc_category_user: "Juhend lõppkasutajale" - default_doc_category_tech: "Tehniline dokumentatsioon" - default_priority_low: "Aega on" - default_priority_normal: "Tavaline" - default_priority_high: "Pakiline" - default_priority_urgent: "Täna vaja" - default_priority_immediate: "Kohe vaja" - default_activity_design: "Kavandamine" - default_activity_development: "Arendamine" - - enumeration_issue_priorities: "Teemade prioriteedid" - enumeration_doc_categories: "Dokumentide kategooriad" - enumeration_activities: "Tegevused (ajakulu)" - enumeration_system_activity: "Süsteemi aktiivsus" - description_filter: "Filter" - description_search: "Otsinguväli" - description_choose_project: "Projektid" - description_project_scope: "Otsingu ulatus" - description_notes: "Märkused" - description_message_content: "Postituse sisu" - description_query_sort_criteria_attribute: "Sorteerimise kriteerium" - description_query_sort_criteria_direction: "Sorteerimise suund" - description_user_mail_notification: "E-kirjaga teavitamise seaded" - description_available_columns: "Kasutatavad veerud" - description_selected_columns: "Valitud veerud" - description_all_columns: "Kõik veerud" - description_issue_category_reassign: "Vali uus kategooria" - description_wiki_subpages_reassign: "Vali lehele uus vanem" - description_date_range_list: "Vali vahemik nimekirjast" - description_date_range_interval: "Vali vahemik algus- ja lõpukuupäeva abil" - description_date_from: "Sisesta alguskuupäev" - description_date_to: "Sisesta lõpukuupäev" - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: "Lubatud on ainult väikesed tähed (a-z), numbrid ja kriipsud.
    Peale salvestamist ei saa tunnust enam muuta." - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: "kõik" - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: "alamprojektidega" - label_cross_project_tree: "projektipuuga" - label_cross_project_hierarchy: "projektihierarhiaga" - label_cross_project_system: "kõigi projektidega" - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/09/09ea8a2e3065e6d0cfba8be5ebe32c3e0767f60e.svn-base --- a/.svn/pristine/09/09ea8a2e3065e6d0cfba8be5ebe32c3e0767f60e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Activity - - mattr_accessor :available_event_types, :default_event_types, :providers - - @@available_event_types = [] - @@default_event_types = [] - @@providers = Hash.new {|h,k| h[k]=[] } - - class << self - def map(&block) - yield self - end - - # Registers an activity provider - def register(event_type, options={}) - options.assert_valid_keys(:class_name, :default) - - event_type = event_type.to_s - providers = options[:class_name] || event_type.classify - providers = ([] << providers) unless providers.is_a?(Array) - - @@available_event_types << event_type unless @@available_event_types.include?(event_type) - @@default_event_types << event_type unless options[:default] == false - @@providers[event_type] += providers - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/09/09ee4c362357d89f421e143b7a604276903929a0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09ee4c362357d89f421e143b7a604276903929a0.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,94 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::PaginationTest < ActiveSupport::TestCase + + def setup + @klass = Redmine::Pagination::Paginator + end + + def test_count_is_zero + p = @klass.new 0, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + %w(first_page previous_page next_page last_page).each do |method| + assert_nil p.send(method), "#{method} was not nil" + end + assert_equal 0, p.first_item + assert_equal 0, p.last_item + assert_equal [], p.linked_pages + end + + def test_count_is_less_than_per_page + p = @klass.new 7, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + assert_equal 1, p.first_page + assert_nil p.previous_page + assert_nil p.next_page + assert_equal 1, p.last_page + assert_equal 1, p.first_item + assert_equal 7, p.last_item + assert_equal [], p.linked_pages + end + + def test_count_is_equal_to_per_page + p = @klass.new 10, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + assert_equal 1, p.first_page + assert_nil p.previous_page + assert_nil p.next_page + assert_equal 1, p.last_page + assert_equal 1, p.first_item + assert_equal 10, p.last_item + assert_equal [], p.linked_pages + end + + def test_2_pages + p = @klass.new 16, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + assert_equal 1, p.first_page + assert_nil p.previous_page + assert_equal 2, p.next_page + assert_equal 2, p.last_page + assert_equal 1, p.first_item + assert_equal 10, p.last_item + assert_equal [1, 2], p.linked_pages + end + + def test_many_pages + p = @klass.new 155, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + assert_equal 1, p.first_page + assert_nil p.previous_page + assert_equal 2, p.next_page + assert_equal 16, p.last_page + assert_equal 1, p.first_item + assert_equal 10, p.last_item + assert_equal [1, 2, 3, 16], p.linked_pages + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0a/0a22e26c056016673b5cd0813b12a72fc4e8cff7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0a22e26c056016673b5cd0813b12a72fc4e8cff7.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,145 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class IssueRelationsControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :issue_relations, + :enabled_modules, + :enumerations, + :trackers, + :projects_trackers + + def setup + User.current = nil + @request.session[:user_id] = 3 + end + + def test_create + assert_difference 'IssueRelation.count' do + post :create, :issue_id => 1, + :relation => {:issue_to_id => '2', :relation_type => 'relates', :delay => ''} + end + relation = IssueRelation.first(:order => 'id DESC') + assert_equal 1, relation.issue_from_id + assert_equal 2, relation.issue_to_id + assert_equal 'relates', relation.relation_type + end + + def test_create_xhr + assert_difference 'IssueRelation.count' do + xhr :post, :create, :issue_id => 3, :relation => {:issue_to_id => '1', :relation_type => 'relates', :delay => ''} + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + end + relation = IssueRelation.first(:order => 'id DESC') + assert_equal 3, relation.issue_from_id + assert_equal 1, relation.issue_to_id + + assert_match /Bug #1/, response.body + end + + def test_create_should_accept_id_with_hash + assert_difference 'IssueRelation.count' do + post :create, :issue_id => 1, + :relation => {:issue_to_id => '#2', :relation_type => 'relates', :delay => ''} + end + relation = IssueRelation.first(:order => 'id DESC') + assert_equal 2, relation.issue_to_id + end + + def test_create_should_strip_id + assert_difference 'IssueRelation.count' do + post :create, :issue_id => 1, + :relation => {:issue_to_id => ' 2 ', :relation_type => 'relates', :delay => ''} + end + relation = IssueRelation.first(:order => 'id DESC') + assert_equal 2, relation.issue_to_id + end + + def test_create_should_not_break_with_non_numerical_id + assert_no_difference 'IssueRelation.count' do + assert_nothing_raised do + post :create, :issue_id => 1, + :relation => {:issue_to_id => 'foo', :relation_type => 'relates', :delay => ''} + end + end + end + + def test_create_follows_relation_should_update_relations_list + issue1 = Issue.generate!(:subject => 'Followed issue', :start_date => Date.yesterday, :due_date => Date.today) + issue2 = Issue.generate! + + assert_difference 'IssueRelation.count' do + xhr :post, :create, :issue_id => issue2.id, + :relation => {:issue_to_id => issue1.id, :relation_type => 'follows', :delay => ''} + end + assert_match /Followed issue/, response.body + end + + def test_should_create_relations_with_visible_issues_only + Setting.cross_project_issue_relations = '1' + assert_nil Issue.visible(User.find(3)).find_by_id(4) + + assert_no_difference 'IssueRelation.count' do + post :create, :issue_id => 1, + :relation => {:issue_to_id => '4', :relation_type => 'relates', :delay => ''} + end + end + + def test_create_xhr_with_failure + assert_no_difference 'IssueRelation.count' do + xhr :post, :create, :issue_id => 3, :relation => {:issue_to_id => '999', :relation_type => 'relates', :delay => ''} + + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + end + + assert_match /errorExplanation/, response.body + end + + def test_destroy + assert_difference 'IssueRelation.count', -1 do + delete :destroy, :id => '2' + end + end + + def test_destroy_xhr + IssueRelation.create!(:relation_type => IssueRelation::TYPE_RELATES) do |r| + r.issue_from_id = 3 + r.issue_to_id = 1 + end + + assert_difference 'IssueRelation.count', -1 do + xhr :delete, :destroy, :id => '2' + + assert_response :success + assert_template 'destroy' + assert_equal 'text/javascript', response.content_type + assert_match /relation-2/, response.body + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0a/0a4b990585c7f952b85cc821c139a4f90df28825.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0a4b990585c7f952b85cc821c139a4f90df28825.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,113 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class TimelogCustomFieldsVisibilityTest < ActionController::TestCase + tests TimelogController + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issue_statuses, + :trackers, + :projects_trackers, + :enabled_modules, + :enumerations, + :workflows + + def setup + field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all} + @fields = [] + @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) + @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) + @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) + @issue = Issue.generate!( + :author_id => 1, + :project_id => 1, + :tracker_id => 1, + :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'} + ) + TimeEntry.generate!(:issue => @issue) + + @user_with_role_on_other_project = User.generate! + User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3)) + + @users_to_test = { + User.find(1) => [@field1, @field2, @field3], + User.find(3) => [@field1, @field2], + @user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 + User.generate! => [@field1], + User.anonymous => [@field1] + } + + Member.where(:project_id => 1).each do |member| + member.destroy unless @users_to_test.keys.include?(member.principal) + end + end + + def test_index_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :project_id => 1, :issue_id => @issue.id, :c => (['hours'] + @fields.map{|f| "issue.cf_#{f.id}"}) + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'td', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name}" + else + assert_select 'td', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name}" + end + end + end + end + + def test_index_as_csv_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :project_id => 1, :issue_id => @issue.id, :c => (['hours'] + @fields.map{|f| "issue.cf_#{f.id}"}), :format => 'csv' + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_include "Value#{i}", response.body, "User #{user.id} was not able to view #{field.name} in CSV" + else + assert_not_include "Value#{i}", response.body, "User #{user.id} was able to view #{field.name} in CSV" + end + end + end + end + + def test_index_with_partial_custom_field_visibility_should_show_visible_custom_fields_only + Issue.delete_all + TimeEntry.delete_all + p1 = Project.generate! + p2 = Project.generate! + user = User.generate! + User.add_to_project(user, p1, Role.find_all_by_id(1,3)) + User.add_to_project(user, p2, Role.find_all_by_id(3)) + TimeEntry.generate!(:issue => Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueA'})) + TimeEntry.generate!(:issue => Issue.generate!(:project => p2, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueB'})) + TimeEntry.generate!(:issue => Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueC'})) + + @request.session[:user_id] = user.id + get :index, :c => ["hours", "issue.cf_#{@field2.id}"] + assert_select 'td', :text => 'ValueA' + assert_select 'td', :text => 'ValueB', :count => 0 + assert_select 'td', :text => 'ValueC' + + get :index, :set_filter => '1', "issue.cf_#{@field2.id}" => '*' + assert_equal %w(ValueA ValueC), assigns(:entries).map{|i| i.issue.custom_field_value(@field2)}.sort + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0a/0a53e228ad2ca76e975fdb76cc443721ba07788d.svn-base --- a/.svn/pristine/0a/0a53e228ad2ca76e975fdb76cc443721ba07788d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Group < Principal - include Redmine::SafeAttributes - - has_and_belongs_to_many :users, :after_add => :user_added, - :after_remove => :user_removed - - acts_as_customizable - - validates_presence_of :lastname - validates_uniqueness_of :lastname, :case_sensitive => false - validates_length_of :lastname, :maximum => 30 - - before_destroy :remove_references_before_destroy - - scope :sorted, order("#{table_name}.lastname ASC") - - safe_attributes 'name', - 'user_ids', - 'custom_field_values', - 'custom_fields', - :if => lambda {|group, user| user.admin?} - - def to_s - lastname.to_s - end - - def name - lastname - end - - def name=(arg) - self.lastname = arg - end - - def user_added(user) - members.each do |member| - next if member.project.nil? - user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id) - member.member_roles.each do |member_role| - user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id) - end - user_member.save! - end - end - - def user_removed(user) - members.each do |member| - MemberRole.find(:all, :include => :member, - :conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy) - end - end - - def self.human_attribute_name(attribute_key_name, *args) - attr_name = attribute_key_name.to_s - if attr_name == 'lastname' - attr_name = "name" - end - super(attr_name, *args) - end - - private - - # Removes references that are not handled by associations - def remove_references_before_destroy - return if self.id.nil? - - Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id] - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0a/0a7193067757b43aa4a973fedb71a5a6507643df.svn-base --- a/.svn/pristine/0a/0a7193067757b43aa4a973fedb71a5a6507643df.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +0,0 @@ ---- -changesets_001: - commit_date: 2007-04-11 - committed_on: 2007-04-11 15:14:44 +02:00 - revision: 1 - id: 100 - comments: My very first commit - repository_id: 10 - committer: dlopper - user_id: 3 -changesets_002: - commit_date: 2007-04-12 - committed_on: 2007-04-12 15:14:44 +02:00 - revision: 2 - id: 101 - comments: 'This commit fixes #1, #2 and references #1 & #3' - repository_id: 10 - committer: dlopper - user_id: 3 -changesets_003: - commit_date: 2007-04-12 - committed_on: 2007-04-12 15:14:44 +02:00 - revision: 3 - id: 102 - comments: |- - A commit with wrong issue ids - IssueID #666 #3 - repository_id: 10 - committer: dlopper - user_id: 3 -changesets_004: - commit_date: 2007-04-12 - committed_on: 2007-04-12 15:14:44 +02:00 - revision: 4 - id: 103 - comments: |- - A commit with an issue id of an other project - IssueID 4 2 - repository_id: 10 - committer: dlopper - user_id: 3 -changesets_005: - commit_date: "2007-09-10" - comments: Modified one file in the folder. - committed_on: 2007-09-10 19:01:08 - revision: "5" - id: 104 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_006: - commit_date: "2007-09-10" - comments: Moved helloworld.rb from / to /folder. - committed_on: 2007-09-10 19:01:47 - revision: "6" - id: 105 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_007: - commit_date: "2007-09-10" - comments: Removed one file. - committed_on: 2007-09-10 19:02:16 - revision: "7" - id: 106 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_008: - commit_date: "2007-09-10" - comments: |- - This commits references an issue. - Refs #2 - committed_on: 2007-09-10 19:04:35 - revision: "8" - id: 107 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_009: - commit_date: "2009-09-10" - comments: One file added. - committed_on: 2009-09-10 19:04:35 - revision: "9" - id: 108 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_010: - commit_date: "2009-09-10" - comments: Same file modified. - committed_on: 2009-09-10 19:04:35 - revision: "10" - id: 109 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0a/0ab473e135d07b597fe923abcf5c9582a5bc4830.svn-base Binary file .svn/pristine/0a/0ab473e135d07b597fe923abcf5c9582a5bc4830.svn-base has changed diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0a/0ab85a80b71d453bc34359193f9b3a077fde14bb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0ab85a80b71d453bc34359193f9b3a077fde14bb.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,279 @@ +module ActiveRecord + module Acts #:nodoc: + module List #:nodoc: + def self.included(base) + base.extend(ClassMethods) + end + + # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list. + # The class that has this specified needs to have a +position+ column defined as an integer on + # the mapped database table. + # + # Todo list example: + # + # class TodoList < ActiveRecord::Base + # has_many :todo_items, :order => "position" + # end + # + # class TodoItem < ActiveRecord::Base + # belongs_to :todo_list + # acts_as_list :scope => :todo_list + # end + # + # todo_list.first.move_to_bottom + # todo_list.last.move_higher + module ClassMethods + # Configuration options are: + # + # * +column+ - specifies the column name to use for keeping the position integer (default: +position+) + # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach _id + # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible + # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key. + # Example: acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0' + def acts_as_list(options = {}) + configuration = { :column => "position", :scope => "1 = 1" } + configuration.update(options) if options.is_a?(Hash) + + configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/ + + if configuration[:scope].is_a?(Symbol) + scope_condition_method = %( + def scope_condition + if #{configuration[:scope].to_s}.nil? + "#{configuration[:scope].to_s} IS NULL" + else + "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}" + end + end + ) + else + scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end" + end + + class_eval <<-EOV + include ActiveRecord::Acts::List::InstanceMethods + + def acts_as_list_class + ::#{self.name} + end + + def position_column + '#{configuration[:column]}' + end + + #{scope_condition_method} + + before_destroy :remove_from_list + before_create :add_to_list_bottom + EOV + end + end + + # All the methods available to a record that has had acts_as_list specified. Each method works + # by assuming the object to be the item in the list, so chapter.move_lower would move that chapter + # lower in the list of all chapters. Likewise, chapter.first? would return +true+ if that chapter is + # the first in the list of all chapters. + module InstanceMethods + # Insert the item at the given position (defaults to the top position of 1). + def insert_at(position = 1) + insert_at_position(position) + end + + # Swap positions with the next lower item, if one exists. + def move_lower + return unless lower_item + + acts_as_list_class.transaction do + lower_item.decrement_position + increment_position + end + end + + # Swap positions with the next higher item, if one exists. + def move_higher + return unless higher_item + + acts_as_list_class.transaction do + higher_item.increment_position + decrement_position + end + end + + # Move to the bottom of the list. If the item is already in the list, the items below it have their + # position adjusted accordingly. + def move_to_bottom + return unless in_list? + acts_as_list_class.transaction do + decrement_positions_on_lower_items + assume_bottom_position + end + end + + # Move to the top of the list. If the item is already in the list, the items above it have their + # position adjusted accordingly. + def move_to_top + return unless in_list? + acts_as_list_class.transaction do + increment_positions_on_higher_items + assume_top_position + end + end + + # Move to the given position + def move_to=(pos) + case pos.to_s + when 'highest' + move_to_top + when 'higher' + move_higher + when 'lower' + move_lower + when 'lowest' + move_to_bottom + end + reset_positions_in_list + end + + def reset_positions_in_list + acts_as_list_class.where(scope_condition).reorder("#{position_column} ASC, id ASC").each_with_index do |item, i| + unless item.send(position_column) == (i + 1) + acts_as_list_class.update_all({position_column => (i + 1)}, {:id => item.id}) + end + end + end + + # Removes the item from the list. + def remove_from_list + if in_list? + decrement_positions_on_lower_items + update_attribute position_column, nil + end + end + + # Increase the position of this item without adjusting the rest of the list. + def increment_position + return unless in_list? + update_attribute position_column, self.send(position_column).to_i + 1 + end + + # Decrease the position of this item without adjusting the rest of the list. + def decrement_position + return unless in_list? + update_attribute position_column, self.send(position_column).to_i - 1 + end + + # Return +true+ if this object is the first in the list. + def first? + return false unless in_list? + self.send(position_column) == 1 + end + + # Return +true+ if this object is the last in the list. + def last? + return false unless in_list? + self.send(position_column) == bottom_position_in_list + end + + # Return the next higher item in the list. + def higher_item + return nil unless in_list? + acts_as_list_class.where( + "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}" + ).first + end + + # Return the next lower item in the list. + def lower_item + return nil unless in_list? + acts_as_list_class.where( + "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}" + ).first + end + + # Test if this record is in a list + def in_list? + !send(position_column).nil? + end + + private + def add_to_list_top + increment_positions_on_all_items + end + + def add_to_list_bottom + self[position_column] = bottom_position_in_list.to_i + 1 + end + + # Overwrite this method to define the scope of the list changes + def scope_condition() "1" end + + # Returns the bottom position number in the list. + # bottom_position_in_list # => 2 + def bottom_position_in_list(except = nil) + item = bottom_item(except) + item ? item.send(position_column) : 0 + end + + # Returns the bottom item + def bottom_item(except = nil) + conditions = scope_condition + conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except + acts_as_list_class.where(conditions).reorder("#{position_column} DESC").first + end + + # Forces item to assume the bottom position in the list. + def assume_bottom_position + update_attribute(position_column, bottom_position_in_list(self).to_i + 1) + end + + # Forces item to assume the top position in the list. + def assume_top_position + update_attribute(position_column, 1) + end + + # This has the effect of moving all the higher items up one. + def decrement_positions_on_higher_items(position) + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}" + ) + end + + # This has the effect of moving all the lower items up one. + def decrement_positions_on_lower_items + return unless in_list? + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}" + ) + end + + # This has the effect of moving all the higher items down one. + def increment_positions_on_higher_items + return unless in_list? + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}" + ) + end + + # This has the effect of moving all the lower items down one. + def increment_positions_on_lower_items(position) + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}" + ) + end + + # Increments position (position_column) of all items in the list. + def increment_positions_on_all_items + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} + 1)", "#{scope_condition}" + ) + end + + def insert_at_position(position) + remove_from_list + increment_positions_on_lower_items(position) + self.update_attribute(position_column, position) + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0a/0ac45c1808f5ee15f2487069dcbe64385e65e79f.svn-base --- a/.svn/pristine/0a/0ac45c1808f5ee15f2487069dcbe64385e65e79f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Tracker < ActiveRecord::Base - - CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject description priority_id is_private).freeze - # Fields that can be disabled - # Other (future) fields should be appended, not inserted! - CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours done_ratio).freeze - CORE_FIELDS_ALL = (CORE_FIELDS_UNDISABLABLE + CORE_FIELDS).freeze - - before_destroy :check_integrity - has_many :issues - has_many :workflow_rules, :dependent => :delete_all do - def copy(source_tracker) - WorkflowRule.copy(source_tracker, nil, proxy_association.owner, nil) - end - end - - has_and_belongs_to_many :projects - has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id' - acts_as_list - - attr_protected :field_bits - - validates_presence_of :name - validates_uniqueness_of :name - validates_length_of :name, :maximum => 30 - - scope :sorted, order("#{table_name}.position ASC") - scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} - - def to_s; name end - - def <=>(tracker) - position <=> tracker.position - end - - # Returns an array of IssueStatus that are used - # in the tracker's workflows - def issue_statuses - if @issue_statuses - return @issue_statuses - elsif new_record? - return [] - end - - ids = WorkflowTransition. - connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{WorkflowTransition.table_name} WHERE tracker_id = #{id} AND type = 'WorkflowTransition'"). - flatten. - uniq - - @issue_statuses = IssueStatus.find_all_by_id(ids).sort - end - - def disabled_core_fields - i = -1 - @disabled_core_fields ||= CORE_FIELDS.select { i += 1; (fields_bits || 0) & (2 ** i) != 0} - end - - def core_fields - CORE_FIELDS - disabled_core_fields - end - - def core_fields=(fields) - raise ArgumentError.new("Tracker.core_fields takes an array") unless fields.is_a?(Array) - - bits = 0 - CORE_FIELDS.each_with_index do |field, i| - unless fields.include?(field) - bits |= 2 ** i - end - end - self.fields_bits = bits - @disabled_core_fields = nil - core_fields - end - - # Returns the fields that are disabled for all the given trackers - def self.disabled_core_fields(trackers) - if trackers.present? - trackers.uniq.map(&:disabled_core_fields).reduce(:&) - else - [] - end - end - - # Returns the fields that are enabled for one tracker at least - def self.core_fields(trackers) - if trackers.present? - trackers.uniq.map(&:core_fields).reduce(:|) - else - CORE_FIELDS.dup - end - end - -private - def check_integrity - raise Exception.new("Can't delete tracker") if Issue.where(:tracker_id => self.id).any? - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0a/0ad2fa6a29d7061cdbc80b5e79aaef4886193bb6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0ad2fa6a29d7061cdbc80b5e79aaef4886193bb6.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,165 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'zlib' + +class WikiContent < ActiveRecord::Base + self.locking_column = 'version' + belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id' + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + validates_presence_of :text + validates_length_of :comments, :maximum => 255, :allow_nil => true + + acts_as_versioned + + after_save :send_notification + + def visible?(user=User.current) + page.visible?(user) + end + + def project + page.project + end + + def attachments + page.nil? ? [] : page.attachments + end + + # Returns the mail adresses of users that should be notified + def recipients + notified = project.notified_users + notified.reject! {|user| !visible?(user)} + notified.collect(&:mail) + end + + # Return true if the content is the current page content + def current_version? + true + end + + class Version + belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id' + belongs_to :author, :class_name => '::User', :foreign_key => 'author_id' + attr_protected :data + + acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"}, + :description => :comments, + :datetime => :updated_on, + :type => 'wiki-page', + :group => :page, + :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.page.wiki.project, :id => o.page.title, :version => o.version}} + + acts_as_activity_provider :type => 'wiki_edits', + :timestamp => "#{WikiContent.versioned_table_name}.updated_on", + :author_key => "#{WikiContent.versioned_table_name}.author_id", + :permission => :view_wiki_edits, + :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + + "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " + + "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " + + "#{WikiContent.versioned_table_name}.id", + :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " + + "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " + + "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"} + + after_destroy :page_update_after_destroy + + def text=(plain) + case Setting.wiki_compression + when 'gzip' + begin + self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION) + self.compression = 'gzip' + rescue + self.data = plain + self.compression = '' + end + else + self.data = plain + self.compression = '' + end + plain + end + + def text + @text ||= begin + str = case compression + when 'gzip' + Zlib::Inflate.inflate(data) + else + # uncompressed data + data + end + str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) + str + end + end + + def project + page.project + end + + # Return true if the content is the current page content + def current_version? + page.content.version == self.version + end + + # Returns the previous version or nil + def previous + @previous ||= WikiContent::Version. + reorder('version DESC'). + includes(:author). + where("wiki_content_id = ? AND version < ?", wiki_content_id, version).first + end + + # Returns the next version or nil + def next + @next ||= WikiContent::Version. + reorder('version ASC'). + includes(:author). + where("wiki_content_id = ? AND version > ?", wiki_content_id, version).first + end + + private + + # Updates page's content if the latest version is removed + # or destroys the page if it was the only version + def page_update_after_destroy + latest = page.content.versions.reorder("#{self.class.table_name}.version DESC").first + if latest && page.content.version != latest.version + raise ActiveRecord::Rollback unless page.content.revert_to!(latest) + elsif latest.nil? + raise ActiveRecord::Rollback unless page.destroy + end + end + end + + private + + def send_notification + # new_record? returns false in after_save callbacks + if id_changed? + if Setting.notified_events.include?('wiki_content_added') + Mailer.wiki_content_added(self).deliver + end + elsif text_changed? + if Setting.notified_events.include?('wiki_content_updated') + Mailer.wiki_content_updated(self).deliver + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0a/0aeabf349bacf0ce65e0c6107e082ed00e2f7277.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0aeabf349bacf0ce65e0c6107e082ed00e2f7277.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,79 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class Watcher < ActiveRecord::Base + belongs_to :watchable, :polymorphic => true + belongs_to :user + + validates_presence_of :user + validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] + validate :validate_user + + # Returns true if at least one object among objects is watched by user + def self.any_watched?(objects, user) + objects = objects.reject(&:new_record?) + if objects.any? + objects.group_by {|object| object.class.base_class}.each do |base_class, objects| + if Watcher.where(:watchable_type => base_class.name, :watchable_id => objects.map(&:id), :user_id => user.id).exists? + return true + end + end + end + false + end + + # Unwatch things that users are no longer allowed to view + def self.prune(options={}) + if options.has_key?(:user) + prune_single_user(options[:user], options) + else + pruned = 0 + User.where("id IN (SELECT DISTINCT user_id FROM #{table_name})").all.each do |user| + pruned += prune_single_user(user, options) + end + pruned + end + end + + protected + + def validate_user + errors.add :user_id, :invalid unless user.nil? || user.active? + end + + private + + def self.prune_single_user(user, options={}) + return unless user.is_a?(User) + pruned = 0 + where(:user_id => user.id).all.each do |watcher| + next if watcher.watchable.nil? + + if options.has_key?(:project) + next unless watcher.watchable.respond_to?(:project) && watcher.watchable.project == options[:project] + end + + if watcher.watchable.respond_to?(:visible?) + unless watcher.watchable.visible?(user) + watcher.destroy + pruned += 1 + end + end + end + pruned + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0b/0b1931e7310d3bdeabe0a2c171beeaecda67ff41.svn-base --- a/.svn/pristine/0b/0b1931e7310d3bdeabe0a2c171beeaecda67ff41.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Platform - class << self - def mswin? - (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || - (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i) - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0b/0b26e69e5ae731de14e1272a803c5ca8472b3a28.svn-base --- a/.svn/pristine/0b/0b26e69e5ae731de14e1272a803c5ca8472b3a28.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -class AddRepositoriesPermissions < ActiveRecord::Migration - # model removed - class Permission < ActiveRecord::Base; end - - def self.up - Permission.create :controller => "repositories", :action => "show", :description => "button_view", :sort => 1450, :is_public => true - Permission.create :controller => "repositories", :action => "browse", :description => "label_browse", :sort => 1460, :is_public => true - Permission.create :controller => "repositories", :action => "entry", :description => "entry", :sort => 1462, :is_public => true - Permission.create :controller => "repositories", :action => "revisions", :description => "label_view_revisions", :sort => 1470, :is_public => true - Permission.create :controller => "repositories", :action => "revision", :description => "label_view_revisions", :sort => 1472, :is_public => true - Permission.create :controller => "repositories", :action => "diff", :description => "diff", :sort => 1480, :is_public => true - end - - def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'show']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'browse']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'entry']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revisions']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revision']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'diff']).destroy - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0b/0b2cfb5e453d4c5b12b6d8b9ca9e37acb99e7715.svn-base --- a/.svn/pristine/0b/0b2cfb5e453d4c5b12b6d8b9ca9e37acb99e7715.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingDocumentsTest < ActionController::IntegrationTest - def test_documents_scoped_under_project - assert_routing( - { :method => 'get', :path => "/projects/567/documents" }, - { :controller => 'documents', :action => 'index', :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/documents/new" }, - { :controller => 'documents', :action => 'new', :project_id => '567' } - ) - assert_routing( - { :method => 'post', :path => "/projects/567/documents" }, - { :controller => 'documents', :action => 'create', :project_id => '567' } - ) - end - - def test_documents - assert_routing( - { :method => 'get', :path => "/documents/22" }, - { :controller => 'documents', :action => 'show', :id => '22' } - ) - assert_routing( - { :method => 'get', :path => "/documents/22/edit" }, - { :controller => 'documents', :action => 'edit', :id => '22' } - ) - assert_routing( - { :method => 'put', :path => "/documents/22" }, - { :controller => 'documents', :action => 'update', :id => '22' } - ) - assert_routing( - { :method => 'delete', :path => "/documents/22" }, - { :controller => 'documents', :action => 'destroy', :id => '22' } - ) - assert_routing( - { :method => 'post', :path => "/documents/22/add_attachment" }, - { :controller => 'documents', :action => 'add_attachment', :id => '22' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0b/0b8467ea6271bd6bbf31748ec5f34d49b8671c8b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0b/0b8467ea6271bd6bbf31748ec5f34d49b8671c8b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,23 @@ +/* Azerbaijani (UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* Written by Jamil Najafov (necefov33@gmail.com). */ +jQuery(function($) { + $.datepicker.regional['az'] = { + closeText: 'Bağla', + prevText: '<Geri', + nextText: 'İrəli>', + currentText: 'Bugün', + monthNames: ['Yanvar','Fevral','Mart','Aprel','May','İyun', + 'İyul','Avqust','Sentyabr','Oktyabr','Noyabr','Dekabr'], + monthNamesShort: ['Yan','Fev','Mar','Apr','May','İyun', + 'İyul','Avq','Sen','Okt','Noy','Dek'], + dayNames: ['Bazar','Bazar ertəsi','Çərşənbə axşamı','Çərşənbə','Cümə axşamı','Cümə','Şənbə'], + dayNamesShort: ['B','Be','Ça','Ç','Ca','C','Ş'], + dayNamesMin: ['B','B','Ç','С','Ç','C','Ş'], + weekHeader: 'Hf', + dateFormat: 'dd.mm.yy', + firstDay: 1, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['az']); +}); diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0b/0b8a89ac0b31721906c0bc4fded1743272e7b058.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0b/0b8a89ac0b31721906c0bc4fded1743272e7b058.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,114 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'redmine/scm/adapters/darcs_adapter' + +class Repository::Darcs < Repository + validates_presence_of :url, :log_encoding + + def self.human_attribute_name(attribute_key_name, *args) + attr_name = attribute_key_name.to_s + if attr_name == "url" + attr_name = "path_to_repository" + end + super(attr_name, *args) + end + + def self.scm_adapter_class + Redmine::Scm::Adapters::DarcsAdapter + end + + def self.scm_name + 'Darcs' + end + + def supports_directory_revisions? + true + end + + def entry(path=nil, identifier=nil) + patch = identifier.nil? ? nil : changesets.find_by_revision(identifier) + scm.entry(path, patch.nil? ? nil : patch.scmid) + end + + def entries(path=nil, identifier=nil) + patch = nil + if ! identifier.nil? + patch = changesets.find_by_revision(identifier) + return nil if patch.nil? + end + entries = scm.entries(path, patch.nil? ? nil : patch.scmid) + if entries + entries.each do |entry| + # Search the DB for the entry's last change + if entry.lastrev && !entry.lastrev.scmid.blank? + changeset = changesets.find_by_scmid(entry.lastrev.scmid) + end + if changeset + entry.lastrev.identifier = changeset.revision + entry.lastrev.name = changeset.revision + entry.lastrev.time = changeset.committed_on + entry.lastrev.author = changeset.committer + end + end + end + load_entries_changesets(entries) + entries + end + + def cat(path, identifier=nil) + patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s) + scm.cat(path, patch.nil? ? nil : patch.scmid) + end + + def diff(path, rev, rev_to) + patch_from = changesets.find_by_revision(rev) + return nil if patch_from.nil? + patch_to = changesets.find_by_revision(rev_to) if rev_to + if path.blank? + path = patch_from.filechanges.collect{|change| change.path}.join(' ') + end + patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil) : nil + end + + def fetch_changesets + scm_info = scm.info + if scm_info + db_last_id = latest_changeset ? latest_changeset.scmid : nil + next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1 + # latest revision in the repository + scm_revision = scm_info.lastrev.scmid + unless changesets.find_by_scmid(scm_revision) + revisions = scm.revisions('', db_last_id, nil, :with_path => true) + transaction do + revisions.reverse_each do |revision| + changeset = Changeset.create(:repository => self, + :revision => next_rev, + :scmid => revision.scmid, + :committer => revision.author, + :committed_on => revision.time, + :comments => revision.message) + revision.paths.each do |change| + changeset.create_change(change) + end + next_rev += 1 + end if revisions + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0b/0b99b1cce55da066eba41dff8f2bfa290c20b955.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0b/0b99b1cce55da066eba41dff8f2bfa290c20b955.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,71 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../../test_helper', __FILE__) + +begin + require 'mocha/setup' + + class SubversionAdapterTest < ActiveSupport::TestCase + + if repository_configured?('subversion') + def setup + @adapter = Redmine::Scm::Adapters::SubversionAdapter.new(self.class.subversion_repository_url) + end + + def test_client_version + v = Redmine::Scm::Adapters::SubversionAdapter.client_version + assert v.is_a?(Array) + end + + def test_scm_version + to_test = { "svn, version 1.6.13 (r1002816)\n" => [1,6,13], + "svn, versione 1.6.13 (r1002816)\n" => [1,6,13], + "1.6.1\n1.7\n1.8" => [1,6,1], + "1.6.2\r\n1.8.1\r\n1.9.1" => [1,6,2]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end + end + + def test_info_not_nil + assert_not_nil @adapter.info + end + + def test_info_nil + adpt = Redmine::Scm::Adapters::SubversionAdapter.new( + "file:///invalid/invalid/" + ) + assert_nil adpt.info + end + + private + + def test_scm_version_for(scm_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_version) + assert_equal version, @adapter.class.svn_binary_version + end + else + puts "Subversion test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end + end +rescue LoadError + class SubversionMochaFake < ActiveSupport::TestCase + def test_fake; assert(false, "Requires mocha to run those tests") end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0b/0bec89d5c0dbedaa5bc5e8099c531dc64fe9abe2.svn-base --- a/.svn/pristine/0b/0bec89d5c0dbedaa5bc5e8099c531dc64fe9abe2.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -<%= l(:text_issue_added, :id => "##{@issue.id}", :author => h(@issue.author)) %> -
    -<%= render :partial => 'issue', :formats => [:html], :locals => { :issue => @issue, :issue_url => @issue_url } %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0b/0bf8f19f5486edff5718369b36db7c358c6ec343.svn-base --- a/.svn/pristine/0b/0bf8f19f5486edff5718369b36db7c358c6ec343.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -<%= labelled_fields_for :issue, @issue do |f| %> - -
    -
    -<% if @issue.safe_attribute?('status_id') && @allowed_statuses.present? %> -

    <%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), {:required => true}, - :onchange => "updateIssueFrom('#{escape_javascript project_issue_form_path(@project, :id => @issue, :format => 'js')}')" %>

    - -<% else %> -

    <%= h(@issue.status.name) %>

    -<% end %> - -<% if @issue.safe_attribute? 'priority_id' %> -

    <%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %>

    -<% end %> - -<% if @issue.safe_attribute? 'assigned_to_id' %> -

    <%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true, :required => @issue.required_attribute?('assigned_to_id') %>

    -<% end %> - -<% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %> -

    <%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true, :required => @issue.required_attribute?('category_id') %> -<%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'), - new_project_issue_category_path(@issue.project), - :remote => true, - :method => 'get', - :title => l(:label_issue_category_new), - :tabindex => 200) if User.current.allowed_to?(:manage_categories, @issue.project) %>

    -<% end %> - -<% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %> -

    <%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true, :required => @issue.required_attribute?('fixed_version_id') %> -<%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'), - new_project_version_path(@issue.project), - :remote => true, - :method => 'get', - :title => l(:label_version_new), - :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %> -

    -<% end %> -
    - -
    -<% if @issue.safe_attribute? 'parent_issue_id' %> -

    <%= f.text_field :parent_issue_id, :size => 10, :required => @issue.required_attribute?('parent_issue_id') %>

    -<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path}')" %> -<% end %> - -<% if @issue.safe_attribute? 'start_date' %> -

    <%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('start_date') %><%= calendar_for('issue_start_date') if @issue.leaf? %>

    -<% end %> - -<% if @issue.safe_attribute? 'due_date' %> -

    <%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('due_date') %><%= calendar_for('issue_due_date') if @issue.leaf? %>

    -<% end %> - -<% if @issue.safe_attribute? 'estimated_hours' %> -

    <%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %>

    -<% end %> - -<% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %> -

    <%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %>

    -<% end %> -
    -
    - -<% if @issue.safe_attribute? 'custom_field_values' %> -<%= render :partial => 'issues/form_custom_fields' %> -<% end %> - -<% end %> - -<% include_calendar_headers_tags %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0c/0c006adf29e0abdbaa9644cee502d6882d303828.svn-base --- a/.svn/pristine/0c/0c006adf29e0abdbaa9644cee502d6882d303828.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module MimeType - - MIME_TYPES = { - 'text/plain' => 'txt,tpl,properties,patch,diff,ini,readme,install,upgrade', - 'text/css' => 'css', - 'text/html' => 'html,htm,xhtml', - 'text/jsp' => 'jsp', - 'text/x-c' => 'c,cpp,cc,h,hh', - 'text/x-csharp' => 'cs', - 'text/x-java' => 'java', - 'text/x-html-template' => 'rhtml', - 'text/x-perl' => 'pl,pm', - 'text/x-php' => 'php,php3,php4,php5', - 'text/x-python' => 'py', - 'text/x-ruby' => 'rb,rbw,ruby,rake,erb', - 'text/x-csh' => 'csh', - 'text/x-sh' => 'sh', - 'text/xml' => 'xml,xsd,mxml', - 'text/yaml' => 'yml,yaml', - 'text/csv' => 'csv', - 'text/x-po' => 'po', - 'image/gif' => 'gif', - 'image/jpeg' => 'jpg,jpeg,jpe', - 'image/png' => 'png', - 'image/tiff' => 'tiff,tif', - 'image/x-ms-bmp' => 'bmp', - 'image/x-xpixmap' => 'xpm', - 'image/svg+xml'=> 'svg', - 'application/javascript' => 'js', - 'application/pdf' => 'pdf', - 'application/rtf' => 'rtf', - 'application/msword' => 'doc', - 'application/vnd.ms-excel' => 'xls', - 'application/vnd.ms-powerpoint' => 'ppt,pps', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', - 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', - 'application/vnd.oasis.opendocument.text' => 'odt', - 'application/vnd.oasis.opendocument.presentation' => 'odp', - 'application/x-7z-compressed' => '7z', - 'application/x-rar-compressed' => 'rar', - 'application/x-tar' => 'tar', - 'application/zip' => 'zip', - 'application/x-gzip' => 'gz', - }.freeze - - EXTENSIONS = MIME_TYPES.inject({}) do |map, (type, exts)| - exts.split(',').each {|ext| map[ext.strip] = type} - map - end - - # returns mime type for name or nil if unknown - def self.of(name) - return nil unless name - m = name.to_s.match(/(^|\.)([^\.]+)$/) - EXTENSIONS[m[2].downcase] if m - end - - # Returns the css class associated to - # the mime type of name - def self.css_class_of(name) - mime = of(name) - mime && mime.gsub('/', '-') - end - - def self.main_mimetype_of(name) - mimetype = of(name) - mimetype.split('/').first if mimetype - end - - # return true if mime-type for name is type/* - # otherwise false - def self.is_type?(type, name) - main_mimetype = main_mimetype_of(name) - type.to_s == main_mimetype - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0c/0c1341a53592418e4a2dd8783234ed2bf47a5fde.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0c/0c1341a53592418e4a2dd8783234ed2bf47a5fde.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1 @@ +$('#principals_for_new_member').html('<%= escape_javascript(render_principals_for_new_members(@project)) %>'); diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0c/0c20b053aa02a18ebfa242903b54c5d2423345a1.svn-base --- a/.svn/pristine/0c/0c20b053aa02a18ebfa242903b54c5d2423345a1.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingRolesTest < ActionController::IntegrationTest - def test_roles - assert_routing( - { :method => 'get', :path => "/roles" }, - { :controller => 'roles', :action => 'index' } - ) - assert_routing( - { :method => 'get', :path => "/roles.xml" }, - { :controller => 'roles', :action => 'index', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/roles/2.xml" }, - { :controller => 'roles', :action => 'show', :id => '2', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/roles/new" }, - { :controller => 'roles', :action => 'new' } - ) - assert_routing( - { :method => 'post', :path => "/roles" }, - { :controller => 'roles', :action => 'create' } - ) - assert_routing( - { :method => 'get', :path => "/roles/2/edit" }, - { :controller => 'roles', :action => 'edit', :id => '2' } - ) - assert_routing( - { :method => 'put', :path => "/roles/2" }, - { :controller => 'roles', :action => 'update', :id => '2' } - ) - assert_routing( - { :method => 'delete', :path => "/roles/2" }, - { :controller => 'roles', :action => 'destroy', :id => '2' } - ) - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/roles/permissions" }, - { :controller => 'roles', :action => 'permissions' } - ) - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0c/0c52d5a82094593c338acef265e5dd3106b35b1d.svn-base --- a/.svn/pristine/0c/0c52d5a82094593c338acef265e5dd3106b35b1d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class ApplicationTest < ActionController::IntegrationTest - include Redmine::I18n - - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows - - def test_set_localization - Setting.default_language = 'en' - - # a french user - get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' - assert_response :success - assert_tag :tag => 'h2', :content => 'Projets' - assert_equal :fr, current_language - - # then an italien user - get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'it;q=0.8,en-us;q=0.5,en;q=0.3' - assert_response :success - assert_tag :tag => 'h2', :content => 'Progetti' - assert_equal :it, current_language - - # not a supported language: default language should be used - get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'zz' - assert_response :success - assert_tag :tag => 'h2', :content => 'Projects' - end - - def test_token_based_access_should_not_start_session - # issue of a private project - get 'issues/4.atom' - assert_response 302 - - rss_key = User.find(2).rss_key - get "issues/4.atom?key=#{rss_key}" - assert_response 200 - assert_nil session[:user_id] - end - - def test_missing_template_should_respond_with_404 - get '/login.png' - assert_response 404 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0c/0c62ded19e437ec59035548bf5b8cb434884827a.svn-base --- a/.svn/pristine/0c/0c62ded19e437ec59035548bf5b8cb434884827a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1395 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Issue < ActiveRecord::Base - include Redmine::SafeAttributes - include Redmine::Utils::DateCalculation - - belongs_to :project - belongs_to :tracker - belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' - belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' - belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id' - belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' - belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id' - belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' - - has_many :journals, :as => :journalized, :dependent => :destroy - has_many :visible_journals, - :class_name => 'Journal', - :as => :journalized, - :conditions => Proc.new { - ["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false] - }, - :readonly => true - - has_many :time_entries, :dependent => :delete_all - has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" - - has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all - has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all - - acts_as_nested_set :scope => 'root_id', :dependent => :destroy - acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed - acts_as_customizable - acts_as_watchable - acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"], - :include => [:project, :visible_journals], - # sort by id so that limited eager loading doesn't break with postgresql - :order_column => "#{table_name}.id" - acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"}, - :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}, - :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') } - - acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}, - :author_key => :author_id - - DONE_RATIO_OPTIONS = %w(issue_field issue_status) - - attr_reader :current_journal - delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true - - validates_presence_of :subject, :priority, :project, :tracker, :author, :status - - validates_length_of :subject, :maximum => 255 - validates_inclusion_of :done_ratio, :in => 0..100 - validates_numericality_of :estimated_hours, :allow_nil => true - validate :validate_issue, :validate_required_fields - - scope :visible, - lambda {|*args| { :include => :project, - :conditions => Issue.visible_condition(args.shift || User.current, *args) } } - - scope :open, lambda {|*args| - is_closed = args.size > 0 ? !args.first : false - {:conditions => ["#{IssueStatus.table_name}.is_closed = ?", is_closed], :include => :status} - } - - scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC" - scope :on_active_project, :include => [:status, :project, :tracker], - :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"] - - before_create :default_assign - before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change - after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?} - after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal - # Should be after_create but would be called before previous after_save callbacks - after_save :after_create_from_copy - after_destroy :update_parent_attributes - - # Returns a SQL conditions string used to find all issues visible by the specified user - def self.visible_condition(user, options={}) - Project.allowed_to_condition(user, :view_issues, options) do |role, user| - if user.logged? - case role.issues_visibility - when 'all' - nil - when 'default' - user_ids = [user.id] + user.groups.map(&:id) - "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))" - when 'own' - user_ids = [user.id] + user.groups.map(&:id) - "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))" - else - '1=0' - end - else - "(#{table_name}.is_private = #{connection.quoted_false})" - end - end - end - - # Returns true if usr or current user is allowed to view the issue - def visible?(usr=nil) - (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user| - if user.logged? - case role.issues_visibility - when 'all' - true - when 'default' - !self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to)) - when 'own' - self.author == user || user.is_or_belongs_to?(assigned_to) - else - false - end - else - !self.is_private? - end - end - end - - def initialize(attributes=nil, *args) - super - if new_record? - # set default values for new records only - self.status ||= IssueStatus.default - self.priority ||= IssuePriority.default - self.watcher_user_ids = [] - end - end - - # AR#Persistence#destroy would raise and RecordNotFound exception - # if the issue was already deleted or updated (non matching lock_version). - # This is a problem when bulk deleting issues or deleting a project - # (because an issue may already be deleted if its parent was deleted - # first). - # The issue is reloaded by the nested_set before being deleted so - # the lock_version condition should not be an issue but we handle it. - def destroy - super - rescue ActiveRecord::RecordNotFound - # Stale or already deleted - begin - reload - rescue ActiveRecord::RecordNotFound - # The issue was actually already deleted - @destroyed = true - return freeze - end - # The issue was stale, retry to destroy - super - end - - def reload(*args) - @workflow_rule_by_attribute = nil - @assignable_versions = nil - super - end - - # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields - def available_custom_fields - (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : [] - end - - # Copies attributes from another issue, arg can be an id or an Issue - def copy_from(arg, options={}) - issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg) - self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on") - self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} - self.status = issue.status - self.author = User.current - unless options[:attachments] == false - self.attachments = issue.attachments.map do |attachement| - attachement.copy(:container => self) - end - end - @copied_from = issue - @copy_options = options - self - end - - # Returns an unsaved copy of the issue - def copy(attributes=nil, copy_options={}) - copy = self.class.new.copy_from(self, copy_options) - copy.attributes = attributes if attributes - copy - end - - # Returns true if the issue is a copy - def copy? - @copied_from.present? - end - - # Moves/copies an issue to a new project and tracker - # Returns the moved/copied issue on success, false on failure - def move_to_project(new_project, new_tracker=nil, options={}) - ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead." - - if options[:copy] - issue = self.copy - else - issue = self - end - - issue.init_journal(User.current, options[:notes]) - - # Preserve previous behaviour - # #move_to_project doesn't change tracker automatically - issue.send :project=, new_project, true - if new_tracker - issue.tracker = new_tracker - end - # Allow bulk setting of attributes on the issue - if options[:attributes] - issue.attributes = options[:attributes] - end - - issue.save ? issue : false - end - - def status_id=(sid) - self.status = nil - result = write_attribute(:status_id, sid) - @workflow_rule_by_attribute = nil - result - end - - def priority_id=(pid) - self.priority = nil - write_attribute(:priority_id, pid) - end - - def category_id=(cid) - self.category = nil - write_attribute(:category_id, cid) - end - - def fixed_version_id=(vid) - self.fixed_version = nil - write_attribute(:fixed_version_id, vid) - end - - def tracker_id=(tid) - self.tracker = nil - result = write_attribute(:tracker_id, tid) - @custom_field_values = nil - @workflow_rule_by_attribute = nil - result - end - - def project_id=(project_id) - if project_id.to_s != self.project_id.to_s - self.project = (project_id.present? ? Project.find_by_id(project_id) : nil) - end - end - - def project=(project, keep_tracker=false) - project_was = self.project - write_attribute(:project_id, project ? project.id : nil) - association_instance_set('project', project) - if project_was && project && project_was != project - @assignable_versions = nil - - unless keep_tracker || project.trackers.include?(tracker) - self.tracker = project.trackers.first - end - # Reassign to the category with same name if any - if category - self.category = project.issue_categories.find_by_name(category.name) - end - # Keep the fixed_version if it's still valid in the new_project - if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version) - self.fixed_version = nil - end - # Clear the parent task if it's no longer valid - unless valid_parent_project? - self.parent_issue_id = nil - end - @custom_field_values = nil - end - end - - def description=(arg) - if arg.is_a?(String) - arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n") - end - write_attribute(:description, arg) - end - - # Overrides assign_attributes so that project and tracker get assigned first - def assign_attributes_with_project_and_tracker_first(new_attributes, *args) - return if new_attributes.nil? - attrs = new_attributes.dup - attrs.stringify_keys! - - %w(project project_id tracker tracker_id).each do |attr| - if attrs.has_key?(attr) - send "#{attr}=", attrs.delete(attr) - end - end - send :assign_attributes_without_project_and_tracker_first, attrs, *args - end - # Do not redefine alias chain on reload (see #4838) - alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first) - - def estimated_hours=(h) - write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) - end - - safe_attributes 'project_id', - :if => lambda {|issue, user| - if issue.new_record? - issue.copy? - elsif user.allowed_to?(:move_issues, issue.project) - projects = Issue.allowed_target_projects_on_move(user) - projects.include?(issue.project) && projects.size > 1 - end - } - - safe_attributes 'tracker_id', - 'status_id', - 'category_id', - 'assigned_to_id', - 'priority_id', - 'fixed_version_id', - 'subject', - 'description', - 'start_date', - 'due_date', - 'done_ratio', - 'estimated_hours', - 'custom_field_values', - 'custom_fields', - 'lock_version', - 'notes', - :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) } - - safe_attributes 'status_id', - 'assigned_to_id', - 'fixed_version_id', - 'done_ratio', - 'lock_version', - 'notes', - :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? } - - safe_attributes 'notes', - :if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)} - - safe_attributes 'private_notes', - :if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)} - - safe_attributes 'watcher_user_ids', - :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)} - - safe_attributes 'is_private', - :if => lambda {|issue, user| - user.allowed_to?(:set_issues_private, issue.project) || - (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project)) - } - - safe_attributes 'parent_issue_id', - :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) && - user.allowed_to?(:manage_subtasks, issue.project)} - - def safe_attribute_names(user=nil) - names = super - names -= disabled_core_fields - names -= read_only_attribute_names(user) - names - end - - # Safely sets attributes - # Should be called from controllers instead of #attributes= - # attr_accessible is too rough because we still want things like - # Issue.new(:project => foo) to work - def safe_attributes=(attrs, user=User.current) - return unless attrs.is_a?(Hash) - - attrs = attrs.dup - - # Project and Tracker must be set before since new_statuses_allowed_to depends on it. - if (p = attrs.delete('project_id')) && safe_attribute?('project_id') - if allowed_target_projects(user).collect(&:id).include?(p.to_i) - self.project_id = p - end - end - - if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id') - self.tracker_id = t - end - - if (s = attrs.delete('status_id')) && safe_attribute?('status_id') - if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i) - self.status_id = s - end - end - - attrs = delete_unsafe_attributes(attrs, user) - return if attrs.empty? - - unless leaf? - attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)} - end - - if attrs['parent_issue_id'].present? - s = attrs['parent_issue_id'].to_s - unless (m = s.match(%r{\A#?(\d+)\z})) && (m[1] == parent_id.to_s || Issue.visible(user).exists?(m[1])) - @invalid_parent_issue_id = attrs.delete('parent_issue_id') - end - end - - if attrs['custom_field_values'].present? - attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| read_only_attribute_names(user).include? k.to_s} - end - - if attrs['custom_fields'].present? - attrs['custom_fields'] = attrs['custom_fields'].reject {|c| read_only_attribute_names(user).include? c['id'].to_s} - end - - # mass-assignment security bypass - assign_attributes attrs, :without_protection => true - end - - def disabled_core_fields - tracker ? tracker.disabled_core_fields : [] - end - - # Returns the custom_field_values that can be edited by the given user - def editable_custom_field_values(user=nil) - custom_field_values.reject do |value| - read_only_attribute_names(user).include?(value.custom_field_id.to_s) - end - end - - # Returns the names of attributes that are read-only for user or the current user - # For users with multiple roles, the read-only fields are the intersection of - # read-only fields of each role - # The result is an array of strings where sustom fields are represented with their ids - # - # Examples: - # issue.read_only_attribute_names # => ['due_date', '2'] - # issue.read_only_attribute_names(user) # => [] - def read_only_attribute_names(user=nil) - workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys - end - - # Returns the names of required attributes for user or the current user - # For users with multiple roles, the required fields are the intersection of - # required fields of each role - # The result is an array of strings where sustom fields are represented with their ids - # - # Examples: - # issue.required_attribute_names # => ['due_date', '2'] - # issue.required_attribute_names(user) # => [] - def required_attribute_names(user=nil) - workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'required'}.keys - end - - # Returns true if the attribute is required for user - def required_attribute?(name, user=nil) - required_attribute_names(user).include?(name.to_s) - end - - # Returns a hash of the workflow rule by attribute for the given user - # - # Examples: - # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'} - def workflow_rule_by_attribute(user=nil) - return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil? - - user_real = user || User.current - roles = user_real.admin ? Role.all : user_real.roles_for_project(project) - return {} if roles.empty? - - result = {} - workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all - if workflow_permissions.any? - workflow_rules = workflow_permissions.inject({}) do |h, wp| - h[wp.field_name] ||= [] - h[wp.field_name] << wp.rule - h - end - workflow_rules.each do |attr, rules| - next if rules.size < roles.size - uniq_rules = rules.uniq - if uniq_rules.size == 1 - result[attr] = uniq_rules.first - else - result[attr] = 'required' - end - end - end - @workflow_rule_by_attribute = result if user.nil? - result - end - private :workflow_rule_by_attribute - - def done_ratio - if Issue.use_status_for_done_ratio? && status && status.default_done_ratio - status.default_done_ratio - else - read_attribute(:done_ratio) - end - end - - def self.use_status_for_done_ratio? - Setting.issue_done_ratio == 'issue_status' - end - - def self.use_field_for_done_ratio? - Setting.issue_done_ratio == 'issue_field' - end - - def validate_issue - if due_date.nil? && @attributes['due_date'].present? - errors.add :due_date, :not_a_date - end - - if start_date.nil? && @attributes['start_date'].present? - errors.add :start_date, :not_a_date - end - - if due_date && start_date && due_date < start_date - errors.add :due_date, :greater_than_start_date - end - - if start_date && soonest_start && start_date < soonest_start - errors.add :start_date, :invalid - end - - if fixed_version - if !assignable_versions.include?(fixed_version) - errors.add :fixed_version_id, :inclusion - elsif reopened? && fixed_version.closed? - errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version) - end - end - - # Checks that the issue can not be added/moved to a disabled tracker - if project && (tracker_id_changed? || project_id_changed?) - unless project.trackers.include?(tracker) - errors.add :tracker_id, :inclusion - end - end - - # Checks parent issue assignment - if @invalid_parent_issue_id.present? - errors.add :parent_issue_id, :invalid - elsif @parent_issue - if !valid_parent_project?(@parent_issue) - errors.add :parent_issue_id, :invalid - elsif !new_record? - # moving an existing issue - if @parent_issue.root_id != root_id - # we can always move to another tree - elsif move_possible?(@parent_issue) - # move accepted inside tree - else - errors.add :parent_issue_id, :invalid - end - end - end - end - - # Validates the issue against additional workflow requirements - def validate_required_fields - user = new_record? ? author : current_journal.try(:user) - - required_attribute_names(user).each do |attribute| - if attribute =~ /^\d+$/ - attribute = attribute.to_i - v = custom_field_values.detect {|v| v.custom_field_id == attribute } - if v && v.value.blank? - errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank') - end - else - if respond_to?(attribute) && send(attribute).blank? - errors.add attribute, :blank - end - end - end - end - - # Set the done_ratio using the status if that setting is set. This will keep the done_ratios - # even if the user turns off the setting later - def update_done_ratio_from_issue_status - if Issue.use_status_for_done_ratio? && status && status.default_done_ratio - self.done_ratio = status.default_done_ratio - end - end - - def init_journal(user, notes = "") - @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) - if new_record? - @current_journal.notify = false - else - @attributes_before_change = attributes.dup - @custom_values_before_change = {} - self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value } - end - @current_journal - end - - # Returns the id of the last journal or nil - def last_journal_id - if new_record? - nil - else - journals.maximum(:id) - end - end - - # Returns a scope for journals that have an id greater than journal_id - def journals_after(journal_id) - scope = journals.reorder("#{Journal.table_name}.id ASC") - if journal_id.present? - scope = scope.where("#{Journal.table_name}.id > ?", journal_id.to_i) - end - scope - end - - # Return true if the issue is closed, otherwise false - def closed? - self.status.is_closed? - end - - # Return true if the issue is being reopened - def reopened? - if !new_record? && status_id_changed? - status_was = IssueStatus.find_by_id(status_id_was) - status_new = IssueStatus.find_by_id(status_id) - if status_was && status_new && status_was.is_closed? && !status_new.is_closed? - return true - end - end - false - end - - # Return true if the issue is being closed - def closing? - if !new_record? && status_id_changed? - status_was = IssueStatus.find_by_id(status_id_was) - status_new = IssueStatus.find_by_id(status_id) - if status_was && status_new && !status_was.is_closed? && status_new.is_closed? - return true - end - end - false - end - - # Returns true if the issue is overdue - def overdue? - !due_date.nil? && (due_date < Date.today) && !status.is_closed? - end - - # Is the amount of work done less than it should for the due date - def behind_schedule? - return false if start_date.nil? || due_date.nil? - done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor - return done_date <= Date.today - end - - # Does this issue have children? - def children? - !leaf? - end - - # Users the issue can be assigned to - def assignable_users - users = project.assignable_users - users << author if author - users << assigned_to if assigned_to - users.uniq.sort - end - - # Versions that the issue can be assigned to - def assignable_versions - return @assignable_versions if @assignable_versions - - versions = project.shared_versions.open.all - if fixed_version - if fixed_version_id_changed? - # nothing to do - elsif project_id_changed? - if project.shared_versions.include?(fixed_version) - versions << fixed_version - end - else - versions << fixed_version - end - end - @assignable_versions = versions.uniq.sort - end - - # Returns true if this issue is blocked by another issue that is still open - def blocked? - !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil? - end - - # Returns an array of statuses that user is able to apply - def new_statuses_allowed_to(user=User.current, include_default=false) - if new_record? && @copied_from - [IssueStatus.default, @copied_from.status].compact.uniq.sort - else - initial_status = nil - if new_record? - initial_status = IssueStatus.default - elsif status_id_was - initial_status = IssueStatus.find_by_id(status_id_was) - end - initial_status ||= status - - statuses = initial_status.find_new_statuses_allowed_to( - user.admin ? Role.all : user.roles_for_project(project), - tracker, - author == user, - assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id - ) - statuses << initial_status unless statuses.empty? - statuses << IssueStatus.default if include_default - statuses = statuses.compact.uniq.sort - blocked? ? statuses.reject {|s| s.is_closed?} : statuses - end - end - - def assigned_to_was - if assigned_to_id_changed? && assigned_to_id_was.present? - @assigned_to_was ||= User.find_by_id(assigned_to_id_was) - end - end - - # Returns the users that should be notified - def notified_users - notified = [] - # Author and assignee are always notified unless they have been - # locked or don't want to be notified - notified << author if author - if assigned_to - notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to]) - end - if assigned_to_was - notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was]) - end - notified = notified.select {|u| u.active? && u.notify_about?(self)} - - notified += project.notified_users - notified.uniq! - # Remove users that can not view the issue - notified.reject! {|user| !visible?(user)} - notified - end - - # Returns the email addresses that should be notified - def recipients - notified_users.collect(&:mail) - end - - # Returns the number of hours spent on this issue - def spent_hours - @spent_hours ||= time_entries.sum(:hours) || 0 - end - - # Returns the total number of hours spent on this issue and its descendants - # - # Example: - # spent_hours => 0.0 - # spent_hours => 50.2 - def total_spent_hours - @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", - :joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0 - end - - def relations - @relations ||= IssueRelations.new(self, (relations_from + relations_to).sort) - end - - # Preloads relations for a collection of issues - def self.load_relations(issues) - if issues.any? - relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}]) - issues.each do |issue| - issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id} - end - end - end - - # Preloads visible spent time for a collection of issues - def self.load_visible_spent_hours(issues, user=User.current) - if issues.any? - hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id) - issues.each do |issue| - issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0) - end - end - end - - # Preloads visible relations for a collection of issues - def self.load_visible_relations(issues, user=User.current) - if issues.any? - issue_ids = issues.map(&:id) - # Relations with issue_from in given issues and visible issue_to - relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all - # Relations with issue_to in given issues and visible issue_from - relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all - - issues.each do |issue| - relations = - relations_from.select {|relation| relation.issue_from_id == issue.id} + - relations_to.select {|relation| relation.issue_to_id == issue.id} - - issue.instance_variable_set "@relations", IssueRelations.new(issue, relations.sort) - end - end - end - - # Finds an issue relation given its id. - def find_relation(relation_id) - IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id]) - end - - def all_dependent_issues(except=[]) - except << self - dependencies = [] - relations_from.each do |relation| - if relation.issue_to && !except.include?(relation.issue_to) - dependencies << relation.issue_to - dependencies += relation.issue_to.all_dependent_issues(except) - end - end - dependencies - end - - # Returns an array of issues that duplicate this one - def duplicates - relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from} - end - - # Returns the due date or the target due date if any - # Used on gantt chart - def due_before - due_date || (fixed_version ? fixed_version.effective_date : nil) - end - - # Returns the time scheduled for this issue. - # - # Example: - # Start Date: 2/26/09, End Date: 3/04/09 - # duration => 6 - def duration - (start_date && due_date) ? due_date - start_date : 0 - end - - # Returns the duration in working days - def working_duration - (start_date && due_date) ? working_days(start_date, due_date) : 0 - end - - def soonest_start(reload=false) - @soonest_start = nil if reload - @soonest_start ||= ( - relations_to(reload).collect{|relation| relation.successor_soonest_start} + - ancestors.collect(&:soonest_start) - ).compact.max - end - - # Sets start_date on the given date or the next working day - # and changes due_date to keep the same working duration. - def reschedule_on(date) - wd = working_duration - date = next_working_date(date) - self.start_date = date - self.due_date = add_working_days(date, wd) - end - - # Reschedules the issue on the given date or the next working day and saves the record. - # If the issue is a parent task, this is done by rescheduling its subtasks. - def reschedule_on!(date) - return if date.nil? - if leaf? - if start_date.nil? || start_date != date - if start_date && start_date > date - # Issue can not be moved earlier than its soonest start date - date = [soonest_start(true), date].compact.max - end - reschedule_on(date) - begin - save - rescue ActiveRecord::StaleObjectError - reload - reschedule_on(date) - save - end - end - else - leaves.each do |leaf| - if leaf.start_date - # Only move subtask if it starts at the same date as the parent - # or if it starts before the given date - if start_date == leaf.start_date || date > leaf.start_date - leaf.reschedule_on!(date) - end - else - leaf.reschedule_on!(date) - end - end - end - end - - def <=>(issue) - if issue.nil? - -1 - elsif root_id != issue.root_id - (root_id || 0) <=> (issue.root_id || 0) - else - (lft || 0) <=> (issue.lft || 0) - end - end - - def to_s - "#{tracker} ##{id}: #{subject}" - end - - # Returns a string of css classes that apply to the issue - def css_classes - s = "issue status-#{status_id} #{priority.try(:css_classes)}" - s << ' closed' if closed? - s << ' overdue' if overdue? - s << ' child' if child? - s << ' parent' unless leaf? - s << ' private' if is_private? - s << ' created-by-me' if User.current.logged? && author_id == User.current.id - s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id - s - end - - # Saves an issue and a time_entry from the parameters - def save_issue_with_child_records(params, existing_time_entry=nil) - Issue.transaction do - if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project) - @time_entry = existing_time_entry || TimeEntry.new - @time_entry.project = project - @time_entry.issue = self - @time_entry.user = User.current - @time_entry.spent_on = User.current.today - @time_entry.attributes = params[:time_entry] - self.time_entries << @time_entry - end - - # TODO: Rename hook - Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) - if save - # TODO: Rename hook - Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) - else - raise ActiveRecord::Rollback - end - end - end - - # Unassigns issues from +version+ if it's no longer shared with issue's project - def self.update_versions_from_sharing_change(version) - # Update issues assigned to the version - update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id]) - end - - # Unassigns issues from versions that are no longer shared - # after +project+ was moved - def self.update_versions_from_hierarchy_change(project) - moved_project_ids = project.self_and_descendants.reload.collect(&:id) - # Update issues of the moved projects and issues assigned to a version of a moved project - Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids]) - end - - def parent_issue_id=(arg) - s = arg.to_s.strip.presence - if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1])) - @parent_issue.id - else - @parent_issue = nil - @invalid_parent_issue_id = arg - end - end - - def parent_issue_id - if @invalid_parent_issue_id - @invalid_parent_issue_id - elsif instance_variable_defined? :@parent_issue - @parent_issue.nil? ? nil : @parent_issue.id - else - parent_id - end - end - - # Returns true if issue's project is a valid - # parent issue project - def valid_parent_project?(issue=parent) - return true if issue.nil? || issue.project_id == project_id - - case Setting.cross_project_subtasks - when 'system' - true - when 'tree' - issue.project.root == project.root - when 'hierarchy' - issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project) - when 'descendants' - issue.project.is_or_is_ancestor_of?(project) - else - false - end - end - - # Extracted from the ReportsController. - def self.by_tracker(project) - count_and_group_by(:project => project, - :field => 'tracker_id', - :joins => Tracker.table_name) - end - - def self.by_version(project) - count_and_group_by(:project => project, - :field => 'fixed_version_id', - :joins => Version.table_name) - end - - def self.by_priority(project) - count_and_group_by(:project => project, - :field => 'priority_id', - :joins => IssuePriority.table_name) - end - - def self.by_category(project) - count_and_group_by(:project => project, - :field => 'category_id', - :joins => IssueCategory.table_name) - end - - def self.by_assigned_to(project) - count_and_group_by(:project => project, - :field => 'assigned_to_id', - :joins => User.table_name) - end - - def self.by_author(project) - count_and_group_by(:project => project, - :field => 'author_id', - :joins => User.table_name) - end - - def self.by_subproject(project) - ActiveRecord::Base.connection.select_all("select s.id as status_id, - s.is_closed as closed, - #{Issue.table_name}.project_id as project_id, - count(#{Issue.table_name}.id) as total - from - #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s - where - #{Issue.table_name}.status_id=s.id - and #{Issue.table_name}.project_id = #{Project.table_name}.id - and #{visible_condition(User.current, :project => project, :with_subprojects => true)} - and #{Issue.table_name}.project_id <> #{project.id} - group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any? - end - # End ReportsController extraction - - # Returns an array of projects that user can assign the issue to - def allowed_target_projects(user=User.current) - if new_record? - Project.all(:conditions => Project.allowed_to_condition(user, :add_issues)) - else - self.class.allowed_target_projects_on_move(user) - end - end - - # Returns an array of projects that user can move issues to - def self.allowed_target_projects_on_move(user=User.current) - Project.all(:conditions => Project.allowed_to_condition(user, :move_issues)) - end - - private - - def after_project_change - # Update project_id on related time entries - TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id}) - - # Delete issue relations - unless Setting.cross_project_issue_relations? - relations_from.clear - relations_to.clear - end - - # Move subtasks that were in the same project - children.each do |child| - next unless child.project_id == project_id_was - # Change project and keep project - child.send :project=, project, true - unless child.save - raise ActiveRecord::Rollback - end - end - end - - # Callback for after the creation of an issue by copy - # * adds a "copied to" relation with the copied issue - # * copies subtasks from the copied issue - def after_create_from_copy - return unless copy? && !@after_create_from_copy_handled - - if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false - relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO) - unless relation.save - logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger - end - end - - unless @copied_from.leaf? || @copy_options[:subtasks] == false - @copied_from.children.each do |child| - unless child.visible? - # Do not copy subtasks that are not visible to avoid potential disclosure of private data - logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger - next - end - copy = Issue.new.copy_from(child, @copy_options) - copy.author = author - copy.project = project - copy.parent_issue_id = id - # Children subtasks are copied recursively - unless copy.save - logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger - end - end - end - @after_create_from_copy_handled = true - end - - def update_nested_set_attributes - if root_id.nil? - # issue was just created - self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id) - set_default_left_and_right - Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id]) - if @parent_issue - move_to_child_of(@parent_issue) - end - reload - elsif parent_issue_id != parent_id - former_parent_id = parent_id - # moving an existing issue - if @parent_issue && @parent_issue.root_id == root_id - # inside the same tree - move_to_child_of(@parent_issue) - else - # to another tree - unless root? - move_to_right_of(root) - reload - end - old_root_id = root_id - self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id ) - target_maxright = nested_set_scope.maximum(right_column_name) || 0 - offset = target_maxright + 1 - lft - Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}", - ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt]) - self[left_column_name] = lft + offset - self[right_column_name] = rgt + offset - if @parent_issue - move_to_child_of(@parent_issue) - end - end - reload - # delete invalid relations of all descendants - self_and_descendants.each do |issue| - issue.relations.each do |relation| - relation.destroy unless relation.valid? - end - end - # update former parent - recalculate_attributes_for(former_parent_id) if former_parent_id - end - remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue) - end - - def update_parent_attributes - recalculate_attributes_for(parent_id) if parent_id - end - - def recalculate_attributes_for(issue_id) - if issue_id && p = Issue.find_by_id(issue_id) - # priority = highest priority of children - if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority) - p.priority = IssuePriority.find_by_position(priority_position) - end - - # start/due dates = lowest/highest dates of children - p.start_date = p.children.minimum(:start_date) - p.due_date = p.children.maximum(:due_date) - if p.start_date && p.due_date && p.due_date < p.start_date - p.start_date, p.due_date = p.due_date, p.start_date - end - - # done ratio = weighted average ratio of leaves - unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio - leaves_count = p.leaves.count - if leaves_count > 0 - average = p.leaves.average(:estimated_hours).to_f - if average == 0 - average = 1 - end - done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f - progress = done / (average * leaves_count) - p.done_ratio = progress.round - end - end - - # estimate = sum of leaves estimates - p.estimated_hours = p.leaves.sum(:estimated_hours).to_f - p.estimated_hours = nil if p.estimated_hours == 0.0 - - # ancestors will be recursively updated - p.save(:validate => false) - end - end - - # Update issues so their versions are not pointing to a - # fixed_version that is not shared with the issue's project - def self.update_versions(conditions=nil) - # Only need to update issues with a fixed_version from - # a different project and that is not systemwide shared - Issue.scoped(:conditions => conditions).all( - :conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" + - " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" + - " AND #{Version.table_name}.sharing <> 'system'", - :include => [:project, :fixed_version] - ).each do |issue| - next if issue.project.nil? || issue.fixed_version.nil? - unless issue.project.shared_versions.include?(issue.fixed_version) - issue.init_journal(User.current) - issue.fixed_version = nil - issue.save - end - end - end - - # Callback on file attachment - def attachment_added(obj) - if @current_journal && !obj.new_record? - @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename) - end - end - - # Callback on attachment deletion - def attachment_removed(obj) - if @current_journal && !obj.new_record? - @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename) - @current_journal.save - end - end - - # Default assignment based on category - def default_assign - if assigned_to.nil? && category && category.assigned_to - self.assigned_to = category.assigned_to - end - end - - # Updates start/due dates of following issues - def reschedule_following_issues - if start_date_changed? || due_date_changed? - relations_from.each do |relation| - relation.set_issue_to_dates - end - end - end - - # Closes duplicates if the issue is being closed - def close_duplicates - if closing? - duplicates.each do |duplicate| - # Reload is need in case the duplicate was updated by a previous duplicate - duplicate.reload - # Don't re-close it if it's already closed - next if duplicate.closed? - # Same user and notes - if @current_journal - duplicate.init_journal(@current_journal.user, @current_journal.notes) - end - duplicate.update_attribute :status, self.status - end - end - end - - # Make sure updated_on is updated when adding a note - def force_updated_on_change - if @current_journal - self.updated_on = current_time_from_proper_timezone - end - end - - # Saves the changes in a Journal - # Called after_save - def create_journal - if @current_journal - # attributes changes - if @attributes_before_change - (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c| - before = @attributes_before_change[c] - after = send(c) - next if before == after || (before.blank? && after.blank?) - @current_journal.details << JournalDetail.new(:property => 'attr', - :prop_key => c, - :old_value => before, - :value => after) - } - end - if @custom_values_before_change - # custom fields changes - custom_field_values.each {|c| - before = @custom_values_before_change[c.custom_field_id] - after = c.value - next if before == after || (before.blank? && after.blank?) - - if before.is_a?(Array) || after.is_a?(Array) - before = [before] unless before.is_a?(Array) - after = [after] unless after.is_a?(Array) - - # values removed - (before - after).reject(&:blank?).each do |value| - @current_journal.details << JournalDetail.new(:property => 'cf', - :prop_key => c.custom_field_id, - :old_value => value, - :value => nil) - end - # values added - (after - before).reject(&:blank?).each do |value| - @current_journal.details << JournalDetail.new(:property => 'cf', - :prop_key => c.custom_field_id, - :old_value => nil, - :value => value) - end - else - @current_journal.details << JournalDetail.new(:property => 'cf', - :prop_key => c.custom_field_id, - :old_value => before, - :value => after) - end - } - end - @current_journal.save - # reset current journal - init_journal @current_journal.user, @current_journal.notes - end - end - - # Query generator for selecting groups of issue counts for a project - # based on specific criteria - # - # Options - # * project - Project to search in. - # * field - String. Issue field to key off of in the grouping. - # * joins - String. The table name to join against. - def self.count_and_group_by(options) - project = options.delete(:project) - select_field = options.delete(:field) - joins = options.delete(:joins) - - where = "#{Issue.table_name}.#{select_field}=j.id" - - ActiveRecord::Base.connection.select_all("select s.id as status_id, - s.is_closed as closed, - j.id as #{select_field}, - count(#{Issue.table_name}.id) as total - from - #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j - where - #{Issue.table_name}.status_id=s.id - and #{where} - and #{Issue.table_name}.project_id=#{Project.table_name}.id - and #{visible_condition(User.current, :project => project)} - group by s.id, s.is_closed, j.id") - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0c/0cb1b271165f09325935c33b90607362679e5b26.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0c/0cb1b271165f09325935c33b90607362679e5b26.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,23 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class UserCustomField < CustomField + def type_name + :label_user_plural + end +end + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0c/0cb33e746ce4443d0cb3bfc0a14c8cc1559914ac.svn-base --- a/.svn/pristine/0c/0cb33e746ce4443d0cb3bfc0a14c8cc1559914ac.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -<%= l(:text_issue_added, :id => "##{@issue.id}", :author => @issue.author) %> - ----------------------------------------- -<%= render :partial => 'issue', :formats => [:text], :locals => { :issue => @issue, :issue_url => @issue_url } %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0c/0cc35d9ebd9809126c44031aad1c4acc705d8aec.svn-base --- a/.svn/pristine/0c/0cc35d9ebd9809126c44031aad1c4acc705d8aec.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class IssueObserver < ActiveRecord::Observer - def after_create(issue) - Mailer.issue_add(issue).deliver if Setting.notified_events.include?('issue_added') - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0c/0ccef3aea9c789ba7ff6975c6015bb677b5a97a9.svn-base --- a/.svn/pristine/0c/0ccef3aea9c789ba7ff6975c6015bb677b5a97a9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Helpers - class Diff - include ERB::Util - include ActionView::Helpers::TagHelper - include ActionView::Helpers::TextHelper - attr_reader :diff, :words - - def initialize(content_to, content_from) - @words = content_to.to_s.split(/(\s+)/) - @words = @words.select {|word| word != ' '} - words_from = content_from.to_s.split(/(\s+)/) - words_from = words_from.select {|word| word != ' '} - @diff = words_from.diff @words - end - - def to_html - words = self.words.collect{|word| h(word)} - words_add = 0 - words_del = 0 - dels = 0 - del_off = 0 - diff.diffs.each do |diff| - add_at = nil - add_to = nil - del_at = nil - deleted = "" - diff.each do |change| - pos = change[1] - if change[0] == "+" - add_at = pos + dels unless add_at - add_to = pos + dels - words_add += 1 - else - del_at = pos unless del_at - deleted << ' ' unless deleted.empty? - deleted << h(change[2]) - words_del += 1 - end - end - if add_at - words[add_at] = ''.html_safe + words[add_at] - words[add_to] = words[add_to] + ''.html_safe - end - if del_at - words.insert del_at - del_off + dels + words_add, ''.html_safe + deleted + ''.html_safe - dels += 1 - del_off += words_del - words_del = 0 - end - end - words.join(' ').html_safe - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0d/0d0d229a445c7f425386e30fa2d56de79c1a0ed1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0d0d229a445c7f425386e30fa2d56de79c1a0ed1.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,48 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class GanttsController < ApplicationController + menu_item :gantt + before_filter :find_optional_project + + rescue_from Query::StatementInvalid, :with => :query_statement_invalid + + helper :gantt + helper :issues + helper :projects + helper :queries + include QueriesHelper + helper :sort + include SortHelper + include Redmine::Export::PDF + + def show + @gantt = Redmine::Helpers::Gantt.new(params) + @gantt.project = @project + retrieve_query + @query.group_by = nil + @gantt.query = @query if @query.valid? + + basename = (@project ? "#{@project.identifier}-" : '') + 'gantt' + + respond_to do |format| + format.html { render :action => "show", :layout => !request.xhr? } + format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image') + format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") } + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0d/0d408463a01e894d3f86946e8b5d1925b0335095.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0d408463a01e894d3f86946e8b5d1925b0335095.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,14 @@ +var fileSpan = $('#attachments_<%= j params[:attachment_id] %>'); +<% if @attachment.new_record? %> + fileSpan.hide(); + alert("<%= escape_javascript @attachment.errors.full_messages.join(', ') %>"); +<% else %> +$('', { type: 'hidden', name: 'attachments[<%= j params[:attachment_id] %>][token]' } ).val('<%= j @attachment.token %>').appendTo(fileSpan); +fileSpan.find('a.remove-upload') + .attr({ + "data-remote": true, + "data-method": 'delete', + href: '<%= j attachment_path(@attachment, :attachment_id => params[:attachment_id], :format => 'js') %>' + }) + .off('click'); +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0d/0d422b89acdac2bb8c5929f11e22f0904e9f370b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0d422b89acdac2bb8c5929f11e22f0904e9f370b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,74 @@ +api.issue do + api.id @issue.id + api.project(:id => @issue.project_id, :name => @issue.project.name) unless @issue.project.nil? + api.tracker(:id => @issue.tracker_id, :name => @issue.tracker.name) unless @issue.tracker.nil? + api.status(:id => @issue.status_id, :name => @issue.status.name) unless @issue.status.nil? + api.priority(:id => @issue.priority_id, :name => @issue.priority.name) unless @issue.priority.nil? + api.author(:id => @issue.author_id, :name => @issue.author.name) unless @issue.author.nil? + api.assigned_to(:id => @issue.assigned_to_id, :name => @issue.assigned_to.name) unless @issue.assigned_to.nil? + api.category(:id => @issue.category_id, :name => @issue.category.name) unless @issue.category.nil? + api.fixed_version(:id => @issue.fixed_version_id, :name => @issue.fixed_version.name) unless @issue.fixed_version.nil? + api.parent(:id => @issue.parent_id) unless @issue.parent.nil? + + api.subject @issue.subject + api.description @issue.description + api.start_date @issue.start_date + api.due_date @issue.due_date + api.done_ratio @issue.done_ratio + api.estimated_hours @issue.estimated_hours + api.spent_hours(@issue.spent_hours) if User.current.allowed_to?(:view_time_entries, @project) + + render_api_custom_values @issue.visible_custom_field_values, api + + api.created_on @issue.created_on + api.updated_on @issue.updated_on + api.closed_on @issue.closed_on + + render_api_issue_children(@issue, api) if include_in_api_response?('children') + + api.array :attachments do + @issue.attachments.each do |attachment| + render_api_attachment(attachment, api) + end + end if include_in_api_response?('attachments') + + api.array :relations do + @relations.each do |relation| + api.relation(:id => relation.id, :issue_id => relation.issue_from_id, :issue_to_id => relation.issue_to_id, :relation_type => relation.relation_type, :delay => relation.delay) + end + end if include_in_api_response?('relations') && @relations.present? + + api.array :changesets do + @issue.changesets.each do |changeset| + api.changeset :revision => changeset.revision do + api.user(:id => changeset.user_id, :name => changeset.user.name) unless changeset.user.nil? + api.comments changeset.comments + api.committed_on changeset.committed_on + end + end + end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project) + + api.array :journals do + @journals.each do |journal| + api.journal :id => journal.id do + api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil? + api.notes journal.notes + api.created_on journal.created_on + api.array :details do + journal.visible_details.each do |detail| + api.detail :property => detail.property, :name => detail.prop_key do + api.old_value detail.old_value + api.new_value detail.value + end + end + end + end + end + end if include_in_api_response?('journals') + + api.array :watchers do + @issue.watcher_users.each do |user| + api.user :id => user.id, :name => user.name + end + end if include_in_api_response?('watchers') && User.current.allowed_to?(:view_issue_watchers, @issue.project) +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0d/0d50a39088b6bd107e54b315b0bf77a7ffa63596.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0d50a39088b6bd107e54b315b0bf77a7ffa63596.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,110 @@ +### From http://svn.geekdaily.org/public/rails/plugins/generally_useful/tasks/coverage_via_rcov.rake + +namespace :test do + desc 'Measures test coverage' + task :coverage do + rm_f "coverage" + rm_f "coverage.data" + rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib --html --exclude gems/" + files = %w(unit functional integration).map {|dir| Dir.glob("test/#{dir}/**/*_test.rb")}.flatten.join(" ") + system("#{rcov} #{files}") + end + + desc 'Run unit and functional scm tests' + task :scm do + errors = %w(test:scm:units test:scm:functionals).collect do |task| + begin + Rake::Task[task].invoke + nil + rescue => e + task + end + end.compact + abort "Errors running #{errors.to_sentence(:locale => :en)}!" if errors.any? + end + + namespace :scm do + namespace :setup do + desc "Creates directory for test repositories" + task :create_dir do + FileUtils.mkdir_p Rails.root + '/tmp/test' + end + + supported_scms = [:subversion, :cvs, :bazaar, :mercurial, :git, :darcs, :filesystem] + + desc "Creates a test subversion repository" + task :subversion => :create_dir do + repo_path = "tmp/test/subversion_repository" + unless File.exists?(repo_path) + system "svnadmin create #{repo_path}" + system "gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load #{repo_path}" + end + end + + desc "Creates a test mercurial repository" + task :mercurial => :create_dir do + repo_path = "tmp/test/mercurial_repository" + unless File.exists?(repo_path) + bundle_path = "test/fixtures/repositories/mercurial_repository.hg" + system "hg init #{repo_path}" + system "hg -R #{repo_path} pull #{bundle_path}" + end + end + + (supported_scms - [:subversion, :mercurial]).each do |scm| + desc "Creates a test #{scm} repository" + task scm => :create_dir do + unless File.exists?("tmp/test/#{scm}_repository") + # system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test" + system "tar -xvz -C tmp/test -f test/fixtures/repositories/#{scm}_repository.tar.gz" + end + end + end + + desc "Creates all test repositories" + task :all => supported_scms + end + + desc "Updates installed test repositories" + task :update do + require 'fileutils' + Dir.glob("tmp/test/*_repository").each do |dir| + next unless File.basename(dir) =~ %r{^(.+)_repository$} && File.directory?(dir) + scm = $1 + next unless fixture = Dir.glob("test/fixtures/repositories/#{scm}_repository.*").first + next if File.stat(dir).ctime > File.stat(fixture).mtime + + FileUtils.rm_rf dir + Rake::Task["test:scm:setup:#{scm}"].execute + end + end + + Rake::TestTask.new(:units => "db:test:prepare") do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/unit/repository*_test.rb'] + FileList['test/unit/lib/redmine/scm/**/*_test.rb'] + end + Rake::Task['test:scm:units'].comment = "Run the scm unit tests" + + Rake::TestTask.new(:functionals => "db:test:prepare") do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/functional/repositories*_test.rb'] + end + Rake::Task['test:scm:functionals'].comment = "Run the scm functional tests" + end + + Rake::TestTask.new(:rdm_routing) do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/integration/routing/*_test.rb'] + end + Rake::Task['test:rdm_routing'].comment = "Run the routing tests" + + Rake::TestTask.new(:ui => "db:test:prepare") do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/ui/**/*_test.rb'] + end + Rake::Task['test:ui'].comment = "Run the UI tests with Capybara (PhantomJS listening on port 4444 is required)" +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0d/0d6f6e567915dec9c4fe1362362e16f727a01572.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0d6f6e567915dec9c4fe1362362e16f727a01572.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,38 @@ +

    <%= l(:label_board_plural) %>

    + + + + + + + + + +<% Board.board_tree(@boards) do |board, level| %> + + + + + + +<% end %> + +
    <%= l(:label_board) %><%= l(:label_topic_plural) %><%= l(:label_message_plural) %><%= l(:label_message_last) %>
    + <%= link_to h(board.name), project_board_path(board.project, board), :class => "board" %>
    + <%=h board.description %> +
    <%= board.topics_count %><%= board.messages_count %> + <% if board.last_message %> + <%= authoring board.last_message.created_on, board.last_message.author %>
    + <%= link_to_message board.last_message %> + <% end %> +
    + +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_messages => 1, :key => User.current.rss_key} %> +<% end %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :id => @project, :format => 'atom', :show_messages => 1, :key => User.current.rss_key}) %> +<% end %> + +<% html_title l(:label_board_plural) %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0d/0dbc51ba6d0f40060a5f81b2175600adf04e0452.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0dbc51ba6d0f40060a5f81b2175600adf04e0452.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,42 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::TokenAuthenticationTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + def setup + Setting.rest_api_enabled = '1' + Setting.login_required = '1' + end + + def teardown + Setting.rest_api_enabled = '0' + Setting.login_required = '0' + end + + # Using the NewsController because it's a simple API. + should_allow_key_based_auth(:get, "/news.xml") + should_allow_key_based_auth(:get, "/news.json") +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0d/0de0220c6abf526fd90b0775dd5b7d3c897e479b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0de0220c6abf526fd90b0775dd5b7d3c897e479b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,10 @@ +class AddTrackerPosition < ActiveRecord::Migration + def self.up + add_column :trackers, :position, :integer, :default => 1 + Tracker.all.each_with_index {|tracker, i| tracker.update_attribute(:position, i+1)} + end + + def self.down + remove_column :trackers, :position + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0e/0e54508d3830a714379a5549a38e89ff26176202.svn-base --- a/.svn/pristine/0e/0e54508d3830a714379a5549a38e89ff26176202.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -

    <%= @author.nil? ? l(:label_activity) : l(:label_user_activity, link_to_user(@author)).html_safe %>

    -

    <%= l(:label_date_from_to, :start => format_date(@date_to - @days), :end => format_date(@date_to-1)) %>

    - -
    -<% @events_by_day.keys.sort.reverse.each do |day| %> -

    <%= format_activity_day(day) %>

    -
    -<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> -
    - <%= avatar(e.event_author, :size => "24") if e.respond_to?(:event_author) %> - <%= format_time(e.event_datetime, false) %> - <%= content_tag('span', h(e.project), :class => 'project') if @project.nil? || @project != e.project %> - <%= link_to format_activity_title(e.event_title), e.event_url %>
    -
    <%= format_activity_description(e.event_description) %> - <%= link_to_user(e.event_author) if e.respond_to?(:event_author) %>
    -<% end -%> -
    -<% end -%> -
    - -<%= content_tag('p', l(:label_no_data), :class => 'nodata') if @events_by_day.empty? %> - -
    -<%= link_to_content_update("\xc2\xab " + l(:label_previous), - params.merge(:from => @date_to - @days - 1), - :title => l(:label_date_from_to, :start => format_date(@date_to - 2*@days), :end => format_date(@date_to - @days - 1))) %> -
    -
    -<%= link_to_content_update(l(:label_next) + " \xc2\xbb", - params.merge(:from => @date_to + @days - 1), - :title => l(:label_date_from_to, :start => format_date(@date_to), :end => format_date(@date_to + @days - 1))) unless @date_to >= Date.today %> -
    -  -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => params.merge(:from => nil, :key => User.current.rss_key) %> -<% end %> - -<% content_for :header_tags do %> -<%= auto_discovery_link_tag(:atom, params.merge(:format => 'atom', :from => nil, :key => User.current.rss_key)) %> -<% end %> - -<% content_for :sidebar do %> -<%= form_tag({}, :method => :get) do %> -

    <%= l(:label_activity) %>

    -

    <% @activity.event_types.each do |t| %> -<%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> - -
    -<% end %>

    -<% if @project && @project.descendants.active.any? %> - <%= hidden_field_tag 'with_subprojects', 0 %> -

    -<% end %> -<%= hidden_field_tag('user_id', params[:user_id]) unless params[:user_id].blank? %> -

    <%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %>

    -<% end %> -<% end %> - -<% html_title(l(:label_activity), @author) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0e/0e59eac074a412c3150c3135d0a1eec08baa4fba.svn-base --- a/.svn/pristine/0e/0e59eac074a412c3150c3135d0a1eec08baa4fba.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,265 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'issues_controller' - -class IssuesControllerTransactionTest < ActionController::TestCase - fixtures :projects, - :users, - :roles, - :members, - :member_roles, - :issues, - :issue_statuses, - :versions, - :trackers, - :projects_trackers, - :issue_categories, - :enabled_modules, - :enumerations, - :attachments, - :workflows, - :custom_fields, - :custom_values, - :custom_fields_projects, - :custom_fields_trackers, - :time_entries, - :journals, - :journal_details, - :queries - - self.use_transactional_fixtures = false - - def setup - @controller = IssuesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_update_stale_issue_should_not_update_the_issue - issue = Issue.find(2) - @request.session[:user_id] = 2 - - assert_no_difference 'Journal.count' do - assert_no_difference 'TimeEntry.count' do - put :update, - :id => issue.id, - :issue => { - :fixed_version_id => 4, - :notes => 'My notes', - :lock_version => (issue.lock_version - 1) - }, - :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id } - end - end - - assert_response :success - assert_template 'edit' - - assert_select 'div.conflict' - assert_select 'input[name=?][value=?]', 'conflict_resolution', 'overwrite' - assert_select 'input[name=?][value=?]', 'conflict_resolution', 'add_notes' - assert_select 'label' do - assert_select 'input[name=?][value=?]', 'conflict_resolution', 'cancel' - assert_select 'a[href=/issues/2]' - end - end - - def test_update_stale_issue_should_save_attachments - set_tmp_attachments_directory - issue = Issue.find(2) - @request.session[:user_id] = 2 - - assert_no_difference 'Journal.count' do - assert_no_difference 'TimeEntry.count' do - assert_difference 'Attachment.count' do - put :update, - :id => issue.id, - :issue => { - :fixed_version_id => 4, - :notes => 'My notes', - :lock_version => (issue.lock_version - 1) - }, - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}, - :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id } - end - end - end - - assert_response :success - assert_template 'edit' - attachment = Attachment.first(:order => 'id DESC') - assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ - end - - def test_update_stale_issue_without_notes_should_not_show_add_notes_option - issue = Issue.find(2) - @request.session[:user_id] = 2 - - put :update, :id => issue.id, - :issue => { - :fixed_version_id => 4, - :notes => '', - :lock_version => (issue.lock_version - 1) - } - - assert_tag 'div', :attributes => {:class => 'conflict'} - assert_tag 'input', :attributes => {:name => 'conflict_resolution', :value => 'overwrite'} - assert_no_tag 'input', :attributes => {:name => 'conflict_resolution', :value => 'add_notes'} - assert_tag 'input', :attributes => {:name => 'conflict_resolution', :value => 'cancel'} - end - - def test_update_stale_issue_should_show_conflicting_journals - @request.session[:user_id] = 2 - - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => '', - :lock_version => 2 - }, - :last_journal_id => 1 - - assert_not_nil assigns(:conflict_journals) - assert_equal 1, assigns(:conflict_journals).size - assert_equal 2, assigns(:conflict_journals).first.id - assert_tag 'div', :attributes => {:class => 'conflict'}, - :descendant => {:content => /Some notes with Redmine links/} - end - - def test_update_stale_issue_without_previous_journal_should_show_all_journals - @request.session[:user_id] = 2 - - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => '', - :lock_version => 2 - }, - :last_journal_id => '' - - assert_not_nil assigns(:conflict_journals) - assert_equal 2, assigns(:conflict_journals).size - assert_tag 'div', :attributes => {:class => 'conflict'}, - :descendant => {:content => /Some notes with Redmine links/} - assert_tag 'div', :attributes => {:class => 'conflict'}, - :descendant => {:content => /Journal notes/} - end - - def test_update_stale_issue_should_show_private_journals_with_permission_only - journal = Journal.create!(:journalized => Issue.find(1), :notes => 'Privates notes', :private_notes => true, :user_id => 1) - - @request.session[:user_id] = 2 - put :update, :id => 1, :issue => {:fixed_version_id => 4, :lock_version => 2}, :last_journal_id => '' - assert_include journal, assigns(:conflict_journals) - - Role.find(1).remove_permission! :view_private_notes - put :update, :id => 1, :issue => {:fixed_version_id => 4, :lock_version => 2}, :last_journal_id => '' - assert_not_include journal, assigns(:conflict_journals) - end - - def test_update_stale_issue_with_overwrite_conflict_resolution_should_update - @request.session[:user_id] = 2 - - assert_difference 'Journal.count' do - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => 'overwrite_conflict_resolution', - :lock_version => 2 - }, - :conflict_resolution => 'overwrite' - end - - assert_response 302 - issue = Issue.find(1) - assert_equal 4, issue.fixed_version_id - journal = Journal.first(:order => 'id DESC') - assert_equal 'overwrite_conflict_resolution', journal.notes - assert journal.details.any? - end - - def test_update_stale_issue_with_add_notes_conflict_resolution_should_update - @request.session[:user_id] = 2 - - assert_difference 'Journal.count' do - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => 'add_notes_conflict_resolution', - :lock_version => 2 - }, - :conflict_resolution => 'add_notes' - end - - assert_response 302 - issue = Issue.find(1) - assert_nil issue.fixed_version_id - journal = Journal.first(:order => 'id DESC') - assert_equal 'add_notes_conflict_resolution', journal.notes - assert journal.details.empty? - end - - def test_update_stale_issue_with_cancel_conflict_resolution_should_redirect_without_updating - @request.session[:user_id] = 2 - - assert_no_difference 'Journal.count' do - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => 'add_notes_conflict_resolution', - :lock_version => 2 - }, - :conflict_resolution => 'cancel' - end - - assert_redirected_to '/issues/1' - issue = Issue.find(1) - assert_nil issue.fixed_version_id - end - - def test_put_update_with_spent_time_and_failure_should_not_add_spent_time - @request.session[:user_id] = 2 - - assert_no_difference('TimeEntry.count') do - put :update, - :id => 1, - :issue => { :subject => '' }, - :time_entry => { :hours => '2.5', :comments => 'should not be added', :activity_id => TimeEntryActivity.first.id } - assert_response :success - end - - assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2.5' - assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'should not be added' - assert_select 'select[name=?]', 'time_entry[activity_id]' do - assert_select 'option[value=?][selected=selected]', TimeEntryActivity.first.id - end - end - - def test_index_should_rescue_invalid_sql_query - Query.any_instance.stubs(:statement).returns("INVALID STATEMENT") - - get :index - assert_response 500 - assert_tag 'p', :content => /An error occurred/ - assert_nil session[:query] - assert_nil session[:issues_index_sort] - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0e/0e6d154c5ead84b5d126f01a8ce10bcd16d83cbd.svn-base --- a/.svn/pristine/0e/0e6d154c5ead84b5d126f01a8ce10bcd16d83cbd.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class WikisController < ApplicationController - menu_item :settings - before_filter :find_project, :authorize - - # Create or update a project's wiki - def edit - @wiki = @project.wiki || Wiki.new(:project => @project) - @wiki.safe_attributes = params[:wiki] - @wiki.save if request.post? - end - - # Delete a project's wiki - def destroy - if request.post? && params[:confirm] && @project.wiki - @project.wiki.destroy - redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki' - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0e/0eb716c2e3251d0a363271012989e30efeb34f17.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0e/0eb716c2e3251d0a363271012989e30efeb34f17.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,3 @@ +<%= form_tag(signout_path) do %> +

    <%= submit_tag l(:label_logout) %>

    +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0e/0ed2a8155e8187c04d224caafbc3970f5a91d733.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0e/0ed2a8155e8187c04d224caafbc3970f5a91d733.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,198 @@ +

    <%= @copy ? l(:button_copy) : l(:label_bulk_edit_selected_issues) %>

    + +<% if @saved_issues && @unsaved_issues.present? %> +
    + + <%= l(:notice_failed_to_save_issues, + :count => @unsaved_issues.size, + :total => @saved_issues.size, + :ids => @unsaved_issues.map {|i| "##{i.id}"}.join(', ')) %> + +
      + <% bulk_edit_error_messages(@unsaved_issues).each do |message| %> +
    • <%= message %>
    • + <% end %> +
    +
    +<% end %> + +
      +<% @issues.each do |issue| %> + <%= content_tag 'li', link_to_issue(issue) %> +<% end %> +
    + +<%= form_tag(bulk_update_issues_path, :id => 'bulk_edit_form') do %> +<%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join("\n").html_safe %> +
    +
    +<%= l(:label_change_properties) %> + +
    +<% if @allowed_projects.present? %> +

    + + <%= select_tag('issue[project_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + project_tree_options_for_select(@allowed_projects, :selected => @target_project), + :onchange => "updateBulkEditFrom('#{escape_javascript url_for(:action => 'bulk_edit', :format => 'js')}')") %> +

    +<% end %> +

    + + <%= select_tag('issue[tracker_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + options_from_collection_for_select(@trackers, :id, :name, @issue_params[:tracker_id])) %> +

    +<% if @available_statuses.any? %> +

    + + <%= select_tag('issue[status_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + options_from_collection_for_select(@available_statuses, :id, :name, @issue_params[:status_id])) %> +

    +<% end %> + +<% if @safe_attributes.include?('priority_id') -%> +

    + + <%= select_tag('issue[priority_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + options_from_collection_for_select(IssuePriority.active, :id, :name, @issue_params[:priority_id])) %> +

    +<% end %> + +<% if @safe_attributes.include?('assigned_to_id') -%> +

    + + <%= select_tag('issue[assigned_to_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:label_nobody), :value => 'none', :selected => (@issue_params[:assigned_to_id] == 'none')) + + principals_options_for_select(@assignables, @issue_params[:assigned_to_id])) %> +

    +<% end %> + +<% if @safe_attributes.include?('category_id') -%> +

    + + <%= select_tag('issue[category_id]', content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:label_none), :value => 'none', :selected => (@issue_params[:category_id] == 'none')) + + options_from_collection_for_select(@categories, :id, :name, @issue_params[:category_id])) %> +

    +<% end %> + +<% if @safe_attributes.include?('fixed_version_id') -%> +

    + + <%= select_tag('issue[fixed_version_id]', content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:label_none), :value => 'none', :selected => (@issue_params[:fixed_version_id] == 'none')) + + version_options_for_select(@versions.sort, @issue_params[:fixed_version_id])) %> +

    +<% end %> + +<% @custom_fields.each do |custom_field| %> +

    + + <%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects, @issue_params[:custom_field_values][custom_field.id.to_s]) %> +

    +<% end %> + +<% if @copy && @attachments_present %> +<%= hidden_field_tag 'copy_attachments', '0' %> +

    + + <%= check_box_tag 'copy_attachments', '1', params[:copy_attachments] != '0' %> +

    +<% end %> + +<% if @copy && @subtasks_present %> +<%= hidden_field_tag 'copy_subtasks', '0' %> +

    + + <%= check_box_tag 'copy_subtasks', '1', params[:copy_subtasks] != '0' %> +

    +<% end %> + +<%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %> +
    + +
    +<% if @safe_attributes.include?('is_private') %> +

    + + <%= select_tag('issue[is_private]', content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:general_text_Yes), :value => '1', :selected => (@issue_params[:is_private] == '1')) + + content_tag('option', l(:general_text_No), :value => '0', :selected => (@issue_params[:is_private] == '0'))) %> +

    +<% end %> + +<% if @safe_attributes.include?('parent_issue_id') && @project %> +

    + + <%= text_field_tag 'issue[parent_issue_id]', '', :size => 10, :value => @issue_params[:parent_issue_id] %> + +

    +<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @project)}')" %> +<% end %> + +<% if @safe_attributes.include?('start_date') %> +

    + + <%= text_field_tag 'issue[start_date]', '', :value => @issue_params[:start_date], :size => 10 %><%= calendar_for('issue_start_date') %> + +

    +<% end %> + +<% if @safe_attributes.include?('due_date') %> +

    + + <%= text_field_tag 'issue[due_date]', '', :value => @issue_params[:due_date], :size => 10 %><%= calendar_for('issue_due_date') %> + +

    +<% end %> + +<% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %> +

    + + <%= select_tag 'issue[done_ratio]', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }, @issue_params[:done_ratio]) %> +

    +<% end %> +
    +
    + +
    +<%= l(:field_notes) %> +<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %> +<%= wikitoolbar_for 'notes' %> +
    +
    + +

    + <% if @copy %> + <%= hidden_field_tag 'copy', '1' %> + <%= submit_tag l(:button_copy) %> + <%= submit_tag l(:button_copy_and_follow), :name => 'follow' %> + <% elsif @target_project %> + <%= submit_tag l(:button_move) %> + <%= submit_tag l(:button_move_and_follow), :name => 'follow' %> + <% else %> + <%= submit_tag l(:button_submit) %> + <% end %> +

    + +<% end %> + +<%= javascript_tag do %> +$(window).load(function(){ + $(document).on('change', 'input[data-disables]', function(){ + if ($(this).attr('checked')){ + $($(this).data('disables')).attr('disabled', true).val(''); + } else { + $($(this).data('disables')).attr('disabled', false); + } + }); +}); +$(document).ready(function(){ + $('input[data-disables]').trigger('change'); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0e/0ef99b09fb86deecc2f7d49dd96b01429edf06cb.svn-base Binary file .svn/pristine/0e/0ef99b09fb86deecc2f7d49dd96b01429edf06cb.svn-base has changed diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0e/0efedaea2e714f5dbe3b6a1b0cd102c208875d70.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0e/0efedaea2e714f5dbe3b6a1b0cd102c208875d70.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1104 @@ +# Chinese (China) translations for Ruby on Rails +# by tsechingho (http://github.com/tsechingho) +zh: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + jquery: + locale: "zh-CN" + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%b%dæ—¥" + long: "%Yå¹´%b%dæ—¥" + + day_names: [星期天, 星期一, 星期二, 星期三, 星期四, 星期五, 星期六] + abbr_day_names: [æ—¥, 一, 二, 三, å››, 五, å…­] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, 一月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 乿œˆ, åæœˆ, å一月, å二月] + abbr_month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%Yå¹´%b%dæ—¥ %A %H:%M:%S" + time: "%H:%M" + short: "%b%dæ—¥ %H:%M" + long: "%Yå¹´%b%dæ—¥ %H:%M" + am: "上åˆ" + pm: "下åˆ" + + datetime: + distance_in_words: + half_a_minute: "åŠåˆ†é’Ÿ" + less_than_x_seconds: + one: "一秒内" + other: "少于 %{count} ç§’" + x_seconds: + one: "一秒" + other: "%{count} ç§’" + less_than_x_minutes: + one: "一分钟内" + other: "少于 %{count} 分钟" + x_minutes: + one: "一分钟" + other: "%{count} 分钟" + about_x_hours: + one: "å¤§çº¦ä¸€å°æ—¶" + other: "大约 %{count} å°æ—¶" + x_hours: + one: "1 å°æ—¶" + other: "%{count} å°æ—¶" + x_days: + one: "一天" + other: "%{count} 天" + about_x_months: + one: "大约一个月" + other: "大约 %{count} 个月" + x_months: + one: "一个月" + other: "%{count} 个月" + about_x_years: + one: "大约一年" + other: "大约 %{count} å¹´" + over_x_years: + one: "超过一年" + other: "超过 %{count} å¹´" + almost_x_years: + one: "将近 1 å¹´" + other: "将近 %{count} å¹´" + + number: + # Default format for numbers + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "å’Œ" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "由于å‘生了一个错误 %{model} 无法ä¿å­˜" + other: "%{count} 个错误使得 %{model} 无法ä¿å­˜" + messages: + inclusion: "ä¸åŒ…å«äºŽåˆ—表中" + exclusion: "是ä¿ç•™å…³é”®å­—" + invalid: "是无效的" + confirmation: "与确认值ä¸åŒ¹é…" + accepted: "必须是å¯è¢«æŽ¥å—çš„" + empty: "ä¸èƒ½ç•™ç©º" + blank: "ä¸èƒ½ä¸ºç©ºå­—符" + too_long: "过长(最长为 %{count} 个字符)" + too_short: "过短(最短为 %{count} 个字符)" + wrong_length: "é•¿åº¦éžæ³•(必须为 %{count} 个字符)" + taken: "å·²ç»è¢«ä½¿ç”¨" + not_a_number: "䏿˜¯æ•°å­—" + not_a_date: "䏿˜¯åˆæ³•日期" + greater_than: "必须大于 %{count}" + greater_than_or_equal_to: "必须大于或等于 %{count}" + equal_to: "必须等于 %{count}" + less_than: "å¿…é¡»å°äºŽ %{count}" + less_than_or_equal_to: "å¿…é¡»å°äºŽæˆ–等于 %{count}" + odd: "å¿…é¡»ä¸ºå•æ•°" + even: "å¿…é¡»ä¸ºåŒæ•°" + greater_than_start_date: "必须在起始日期之åŽ" + not_same_project: "ä¸å±žäºŽåŒä¸€ä¸ªé¡¹ç›®" + circular_dependency: "此关è”将导致循环ä¾èµ–" + cant_link_an_issue_with_a_descendant: "问题ä¸èƒ½å…³è”到它的å­ä»»åŠ¡" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: 请选择 + + general_text_No: 'å¦' + general_text_Yes: '是' + general_text_no: 'å¦' + general_text_yes: '是' + general_lang_name: 'Simplified Chinese (简体中文)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: gb18030 + general_pdf_encoding: gb18030 + general_first_day_of_week: '7' + + notice_account_updated: å¸å·æ›´æ–°æˆåŠŸ + notice_account_invalid_creditentials: æ— æ•ˆçš„ç”¨æˆ·åæˆ–å¯†ç  + notice_account_password_updated: å¯†ç æ›´æ–°æˆåŠŸ + notice_account_wrong_password: 密ç é”™è¯¯ + notice_account_register_done: å¸å·åˆ›å»ºæˆåŠŸï¼Œè¯·ä½¿ç”¨æ³¨å†Œç¡®è®¤é‚®ä»¶ä¸­çš„é“¾æŽ¥æ¥æ¿€æ´»æ‚¨çš„å¸å·ã€‚ + notice_account_unknown_email: 未知用户 + notice_can_t_change_password: 该å¸å·ä½¿ç”¨äº†å¤–部认è¯ï¼Œå› æ­¤æ— æ³•更改密ç ã€‚ + notice_account_lost_email_sent: 系统已将引导您设置新密ç çš„邮件å‘é€ç»™æ‚¨ã€‚ + notice_account_activated: 您的å¸å·å·²è¢«æ¿€æ´»ã€‚您现在å¯ä»¥ç™»å½•了。 + notice_successful_create: 创建æˆåŠŸ + notice_successful_update: æ›´æ–°æˆåŠŸ + notice_successful_delete: 删除æˆåŠŸ + notice_successful_connection: 连接æˆåŠŸ + notice_file_not_found: 您访问的页é¢ä¸å­˜åœ¨æˆ–已被删除。 + notice_locking_conflict: æ•°æ®å·²è¢«å¦ä¸€ä½ç”¨æˆ·æ›´æ–° + notice_not_authorized: 对ä¸èµ·ï¼Œæ‚¨æ— æƒè®¿é—®æ­¤é¡µé¢ã€‚ + notice_not_authorized_archived_project: è¦è®¿é—®çš„项目已ç»å½’档。 + notice_email_sent: "邮件已å‘é€è‡³ %{value}" + notice_email_error: "å‘é€é‚®ä»¶æ—¶å‘生错误 (%{value})" + notice_feeds_access_key_reseted: 您的Atomå­˜å–键已被é‡ç½®ã€‚ + notice_api_access_key_reseted: 您的API访问键已被é‡ç½®ã€‚ + notice_failed_to_save_issues: "%{count} 个问题ä¿å­˜å¤±è´¥ï¼ˆå…±é€‰æ‹© %{total} 个问题):%{ids}." + notice_failed_to_save_members: "æˆå‘˜ä¿å­˜å¤±è´¥: %{errors}." + notice_no_issue_selected: "未选择任何问题ï¼è¯·é€‰æ‹©æ‚¨è¦ç¼–辑的问题。" + notice_account_pending: "您的å¸å·å·²è¢«æˆåŠŸåˆ›å»ºï¼Œæ­£åœ¨ç­‰å¾…ç®¡ç†å‘˜çš„审核。" + notice_default_data_loaded: æˆåŠŸè½½å…¥é»˜è®¤è®¾ç½®ã€‚ + notice_unable_delete_version: 无法删除版本 + notice_unable_delete_time_entry: 无法删除工时 + notice_issue_done_ratios_updated: 问题完æˆåº¦å·²æ›´æ–°ã€‚ + notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})" + + error_can_t_load_default_data: "无法载入默认设置:%{value}" + error_scm_not_found: "版本库中ä¸å­˜åœ¨è¯¥æ¡ç›®å’Œï¼ˆæˆ–)其修订版本。" + error_scm_command_failed: "访问版本库时å‘生错误:%{value}" + error_scm_annotate: "该æ¡ç›®ä¸å­˜åœ¨æˆ–无法追溯。" + error_issue_not_found_in_project: '问题ä¸å­˜åœ¨æˆ–ä¸å±žäºŽæ­¤é¡¹ç›®' + error_no_tracker_in_project: 该项目未设定跟踪标签,请检查项目é…置。 + error_no_default_issue_status: 未设置默认的问题状æ€ã€‚请检查系统设置("管ç†" -> "问题状æ€")。 + error_can_not_delete_custom_field: 无法删除自定义属性 + error_can_not_delete_tracker: "该跟踪标签已包å«é—®é¢˜,无法删除" + error_can_not_remove_role: "该角色正在使用中,无法删除" + error_can_not_reopen_issue_on_closed_version: 该问题被关è”到一个已ç»å…³é—­çš„ç‰ˆæœ¬ï¼Œå› æ­¤æ— æ³•é‡æ–°æ‰“开。 + error_can_not_archive_project: 该项目无法被存档 + error_issue_done_ratios_not_updated: 问题完æˆåº¦æœªèƒ½è¢«æ›´æ–°ã€‚ + error_workflow_copy_source: 请选择一个æºè·Ÿè¸ªæ ‡ç­¾æˆ–者角色 + error_workflow_copy_target: 请选择目标跟踪标签和角色 + error_unable_delete_issue_status: '无法删除问题状æ€' + error_unable_to_connect: "无法连接 (%{value})" + warning_attachments_not_saved: "%{count} 个文件ä¿å­˜å¤±è´¥" + + mail_subject_lost_password: "您的 %{value} 密ç " + mail_body_lost_password: '请点击以下链接æ¥ä¿®æ”¹æ‚¨çš„密ç ï¼š' + mail_subject_register: "%{value}å¸å·æ¿€æ´»" + mail_body_register: 'è¯·ç‚¹å‡»ä»¥ä¸‹é“¾æŽ¥æ¥æ¿€æ´»æ‚¨çš„å¸å·ï¼š' + mail_body_account_information_external: "您å¯ä»¥ä½¿ç”¨æ‚¨çš„ %{value} å¸å·æ¥ç™»å½•。" + mail_body_account_information: 您的å¸å·ä¿¡æ¯ + mail_subject_account_activation_request: "%{value}å¸å·æ¿€æ´»è¯·æ±‚" + mail_body_account_activation_request: "新用户(%{value}ï¼‰å·²å®Œæˆæ³¨å†Œï¼Œæ­£åœ¨ç­‰å€™æ‚¨çš„审核:" + mail_subject_reminder: "%{count} 个问题需è¦å°½å¿«è§£å†³ (%{days})" + mail_body_reminder: "指派给您的 %{count} 个问题需è¦åœ¨ %{days} 天内完æˆï¼š" + mail_subject_wiki_content_added: "'%{id}' wiki页é¢å·²æ·»åŠ " + mail_body_wiki_content_added: "'%{id}' wiki页é¢å·²ç”± %{author} 添加。" + mail_subject_wiki_content_updated: "'%{id}' wiki页é¢å·²æ›´æ–°ã€‚" + mail_body_wiki_content_updated: "'%{id}' wiki页é¢å·²ç”± %{author} 更新。" + + + field_name: åç§° + field_description: æè¿° + field_summary: æ‘˜è¦ + field_is_required: å¿…å¡« + field_firstname: åå­— + field_lastname: å§“æ° + field_mail: é‚®ä»¶åœ°å€ + field_filename: 文件 + field_filesize: å¤§å° + field_downloads: 下载次数 + field_author: 作者 + field_created_on: 创建于 + field_updated_on: 更新于 + field_field_format: æ ¼å¼ + field_is_for_all: 用于所有项目 + field_possible_values: å¯èƒ½çš„值 + field_regexp: æ­£åˆ™è¡¨è¾¾å¼ + field_min_length: 最å°é•¿åº¦ + field_max_length: 最大长度 + field_value: 值 + field_category: 类别 + field_title: 标题 + field_project: 项目 + field_issue: 问题 + field_status: çŠ¶æ€ + field_notes: 说明 + field_is_closed: 已关闭的问题 + field_is_default: 默认值 + field_tracker: 跟踪 + field_subject: 主题 + field_due_date: è®¡åˆ’å®Œæˆæ—¥æœŸ + field_assigned_to: 指派给 + field_priority: 优先级 + field_fixed_version: 目标版本 + field_user: 用户 + field_principal: 用户/用户组 + field_role: 角色 + field_homepage: 主页 + field_is_public: 公开 + field_parent: 上级项目 + field_is_in_roadmap: 在路线图中显示 + field_login: 登录å + field_mail_notification: 邮件通知 + field_admin: 管ç†å‘˜ + field_last_login_on: 最åŽç™»å½• + field_language: 语言 + field_effective_date: 日期 + field_password: å¯†ç  + field_new_password: æ–°å¯†ç  + field_password_confirmation: 确认 + field_version: 版本 + field_type: 类型 + field_host: 主机 + field_port: ç«¯å£ + field_account: å¸å· + field_base_dn: Base DN + field_attr_login: 登录å属性 + field_attr_firstname: å字属性 + field_attr_lastname: å§“æ°å±žæ€§ + field_attr_mail: 邮件属性 + field_onthefly: 峿—¶ç”¨æˆ·ç”Ÿæˆ + field_start_date: 开始日期 + field_done_ratio: "% 完æˆ" + field_auth_source: è®¤è¯æ¨¡å¼ + field_hide_mail: éšè—æˆ‘çš„é‚®ä»¶åœ°å€ + field_comments: 注释 + field_url: URL + field_start_page: 起始页 + field_subproject: å­é¡¹ç›® + field_hours: å°æ—¶ + field_activity: 活动 + field_spent_on: 日期 + field_identifier: 标识 + field_is_filter: 作为过滤æ¡ä»¶ + field_issue_to: 相关问题 + field_delay: 延期 + field_assignable: é—®é¢˜å¯æŒ‡æ´¾ç»™æ­¤è§’色 + field_redirect_existing_links: é‡å®šå‘到现有链接 + field_estimated_hours: 预期时间 + field_column_names: 列 + field_time_entries: 工时 + field_time_zone: 时区 + field_searchable: å¯ç”¨ä½œæœç´¢æ¡ä»¶ + field_default_value: 默认值 + field_comments_sorting: 显示注释 + field_parent_title: ä¸Šçº§é¡µé¢ + field_editable: å¯ç¼–辑 + field_watcher: 跟踪者 + field_identity_url: OpenID URL + field_content: 内容 + field_group_by: æ ¹æ®æ­¤æ¡ä»¶åˆ†ç»„ + field_sharing: 共享 + field_parent_issue: 父任务 + field_member_of_group: 用户组的æˆå‘˜ + field_assigned_to_role: 角色的æˆå‘˜ + field_text: 文本字段 + field_visible: å¯è§çš„ + + setting_app_title: åº”ç”¨ç¨‹åºæ ‡é¢˜ + setting_app_subtitle: 应用程åºå­æ ‡é¢˜ + setting_welcome_text: 欢迎文字 + setting_default_language: 默认语言 + setting_login_required: è¦æ±‚è®¤è¯ + setting_self_registration: å…许自注册 + setting_attachment_max_size: 附件大å°é™åˆ¶ + setting_issues_export_limit: 问题导出æ¡ç›®çš„é™åˆ¶ + setting_mail_from: 邮件å‘ä»¶äººåœ°å€ + setting_bcc_recipients: ä½¿ç”¨å¯†ä»¶æŠ„é€ (bcc) + setting_plain_text_mail: 纯文本(无HTML) + setting_host_name: 主机åç§° + setting_text_formatting: æ–‡æœ¬æ ¼å¼ + setting_wiki_compression: 压缩WikiåŽ†å²æ–‡æ¡£ + setting_feeds_limit: Atom Feedå†…å®¹æ¡æ•°é™åˆ¶ + setting_default_projects_public: 新建项目默认为公开项目 + setting_autofetch_changesets: 自动获å–程åºå˜æ›´ + setting_sys_api_enabled: å¯ç”¨ç”¨äºŽç‰ˆæœ¬åº“管ç†çš„Web Service + setting_commit_ref_keywords: 用于引用问题的关键字 + setting_commit_fix_keywords: 用于解决问题的关键字 + setting_autologin: 自动登录 + setting_date_format: æ—¥æœŸæ ¼å¼ + setting_time_format: æ—¶é—´æ ¼å¼ + setting_cross_project_issue_relations: å…许ä¸åŒé¡¹ç›®ä¹‹é—´çš„é—®é¢˜å…³è” + setting_issue_list_default_columns: 问题列表中显示的默认列 + setting_emails_header: 邮件头 + setting_emails_footer: 邮件签å + setting_protocol: åè®® + setting_per_page_options: æ¯é¡µæ˜¾ç¤ºæ¡ç›®ä¸ªæ•°çš„设置 + setting_user_format: ç”¨æˆ·æ˜¾ç¤ºæ ¼å¼ + setting_activity_days_default: 在项目活动中显示的天数 + setting_display_subprojects_issues: 在项目页é¢ä¸Šé»˜è®¤æ˜¾ç¤ºå­é¡¹ç›®çš„问题 + setting_enabled_scm: å¯ç”¨ SCM + setting_mail_handler_body_delimiters: åœ¨è¿™äº›è¡Œä¹‹åŽæˆªæ–­é‚®ä»¶ + setting_mail_handler_api_enabled: å¯ç”¨ç”¨äºŽæŽ¥æ”¶é‚®ä»¶çš„æœåŠ¡ + setting_mail_handler_api_key: API key + setting_sequential_project_identifiers: 顺åºäº§ç”Ÿé¡¹ç›®æ ‡è¯† + setting_gravatar_enabled: 使用Gravatarç”¨æˆ·å¤´åƒ + setting_gravatar_default: 默认的Gravatarå¤´åƒ + setting_diff_max_lines_displayed: 查看差别页é¢ä¸Šæ˜¾ç¤ºçš„æœ€å¤§è¡Œæ•° + setting_file_max_size_displayed: å…许直接显示的最大文本文件 + setting_repository_log_display_limit: åœ¨æ–‡ä»¶å˜æ›´è®°å½•页é¢ä¸Šæ˜¾ç¤ºçš„æœ€å¤§ä¿®è®¢ç‰ˆæœ¬æ•°é‡ + setting_openid: å…许使用OpenID登录和注册 + setting_password_min_length: 最短密ç é•¿åº¦ + setting_new_project_user_role_id: éžç®¡ç†å‘˜ç”¨æˆ·æ–°å»ºé¡¹ç›®æ—¶å°†è¢«èµ‹äºˆçš„(在该项目中的)角色 + setting_default_projects_modules: 新建项目默认å¯ç”¨çš„æ¨¡å— + setting_issue_done_ratio: 计算问题完æˆåº¦ï¼š + setting_issue_done_ratio_issue_field: 使用问题(的完æˆåº¦ï¼‰å±žæ€§ + setting_issue_done_ratio_issue_status: ä½¿ç”¨é—®é¢˜çŠ¶æ€ + setting_start_of_week: 日历开始于 + setting_rest_api_enabled: å¯ç”¨REST web service + setting_cache_formatted_text: 缓存格å¼åŒ–文字 + setting_default_notification_option: 默认æé†’选项 + setting_commit_logtime_enabled: 激活时间日志 + setting_commit_logtime_activity_id: 记录的活动 + setting_gantt_items_limit: 在甘特图上显示的最大记录数 + + permission_add_project: 新建项目 + permission_add_subprojects: 新建å­é¡¹ç›® + permission_edit_project: 编辑项目 + permission_select_project_modules: é€‰æ‹©é¡¹ç›®æ¨¡å— + permission_manage_members: ç®¡ç†æˆå‘˜ + permission_manage_project_activities: 管ç†é¡¹ç›®æ´»åЍ + permission_manage_versions: 管ç†ç‰ˆæœ¬ + permission_manage_categories: 管ç†é—®é¢˜ç±»åˆ« + permission_view_issues: 查看问题 + permission_add_issues: 新建问题 + permission_edit_issues: 更新问题 + permission_manage_issue_relations: 管ç†é—®é¢˜å…³è” + permission_add_issue_notes: 添加说明 + permission_edit_issue_notes: 编辑说明 + permission_edit_own_issue_notes: 编辑自己的说明 + permission_move_issues: 移动问题 + permission_delete_issues: 删除问题 + permission_manage_public_queries: 管ç†å…¬å¼€çš„æŸ¥è¯¢ + permission_save_queries: ä¿å­˜æŸ¥è¯¢ + permission_view_gantt: 查看甘特图 + permission_view_calendar: 查看日历 + permission_view_issue_watchers: 查看跟踪者列表 + permission_add_issue_watchers: 添加跟踪者 + permission_delete_issue_watchers: 删除跟踪者 + permission_log_time: 登记工时 + permission_view_time_entries: 查看耗时 + permission_edit_time_entries: 编辑耗时 + permission_edit_own_time_entries: 编辑自己的耗时 + permission_manage_news: ç®¡ç†æ–°é—» + permission_comment_news: 为新闻添加评论 + permission_view_documents: 查看文档 + permission_manage_files: ç®¡ç†æ–‡ä»¶ + permission_view_files: 查看文件 + permission_manage_wiki: 管ç†Wiki + permission_rename_wiki_pages: é‡å®šå‘/é‡å‘½åWikié¡µé¢ + permission_delete_wiki_pages: 删除Wikié¡µé¢ + permission_view_wiki_pages: 查看Wiki + permission_view_wiki_edits: 查看Wiki历å²è®°å½• + permission_edit_wiki_pages: 编辑Wikié¡µé¢ + permission_delete_wiki_pages_attachments: 删除附件 + permission_protect_wiki_pages: ä¿æŠ¤Wikié¡µé¢ + permission_manage_repository: 管ç†ç‰ˆæœ¬åº“ + permission_browse_repository: æµè§ˆç‰ˆæœ¬åº“ + permission_view_changesets: æŸ¥çœ‹å˜æ›´ + permission_commit_access: 访问æäº¤ä¿¡æ¯ + permission_manage_boards: 管ç†è®¨è®ºåŒº + permission_view_messages: æŸ¥çœ‹å¸–å­ + permission_add_messages: å‘è¡¨å¸–å­ + permission_edit_messages: ç¼–è¾‘å¸–å­ + permission_edit_own_messages: ç¼–è¾‘è‡ªå·±çš„å¸–å­ + permission_delete_messages: åˆ é™¤å¸–å­ + permission_delete_own_messages: åˆ é™¤è‡ªå·±çš„å¸–å­ + permission_export_wiki_pages: 导出 wiki é¡µé¢ + permission_manage_subtasks: 管ç†å­ä»»åŠ¡ + + project_module_issue_tracking: 问题跟踪 + project_module_time_tracking: 时间跟踪 + project_module_news: æ–°é—» + project_module_documents: 文档 + project_module_files: 文件 + project_module_wiki: Wiki + project_module_repository: 版本库 + project_module_boards: 讨论区 + project_module_calendar: 日历 + project_module_gantt: 甘特图 + + label_user: 用户 + label_user_plural: 用户 + label_user_new: 新建用户 + label_user_anonymous: 匿å用户 + label_project: 项目 + label_project_new: 新建项目 + label_project_plural: 项目 + label_x_projects: + zero: 无项目 + one: 1 个项目 + other: "%{count} 个项目" + label_project_all: 所有的项目 + label_project_latest: 最近的项目 + label_issue: 问题 + label_issue_new: 新建问题 + label_issue_plural: 问题 + label_issue_view_all: 查看所有问题 + label_issues_by: "按 %{value} 分组显示问题" + label_issue_added: 问题已添加 + label_issue_updated: 问题已更新 + label_document: 文档 + label_document_new: 新建文档 + label_document_plural: 文档 + label_document_added: 文档已添加 + label_role: 角色 + label_role_plural: 角色 + label_role_new: 新建角色 + label_role_and_permissions: 角色和æƒé™ + label_member: æˆå‘˜ + label_member_new: 新建æˆå‘˜ + label_member_plural: æˆå‘˜ + label_tracker: 跟踪标签 + label_tracker_plural: 跟踪标签 + label_tracker_new: 新建跟踪标签 + label_workflow: 工作æµç¨‹ + label_issue_status: é—®é¢˜çŠ¶æ€ + label_issue_status_plural: é—®é¢˜çŠ¶æ€ + label_issue_status_new: æ–°å»ºé—®é¢˜çŠ¶æ€ + label_issue_category: 问题类别 + label_issue_category_plural: 问题类别 + label_issue_category_new: 新建问题类别 + label_custom_field: 自定义属性 + label_custom_field_plural: 自定义属性 + label_custom_field_new: 新建自定义属性 + label_enumerations: 枚举值 + label_enumeration_new: 新建枚举值 + label_information: ä¿¡æ¯ + label_information_plural: ä¿¡æ¯ + label_please_login: 请登录 + label_register: 注册 + label_login_with_open_id_option: 或使用OpenID登录 + label_password_lost: å¿˜è®°å¯†ç  + label_home: 主页 + label_my_page: æˆ‘çš„å·¥ä½œå° + label_my_account: 我的å¸å· + label_my_projects: 我的项目 + label_my_page_block: æˆ‘çš„å·¥ä½œå°æ¨¡å— + label_administration: ç®¡ç† + label_login: 登录 + label_logout: 退出 + label_help: 帮助 + label_reported_issues: 已报告的问题 + label_assigned_to_me_issues: 指派给我的问题 + label_last_login: 最åŽç™»å½• + label_registered_on: 注册于 + label_activity: 活动 + label_overall_activity: 活动概览 + label_user_activity: "%{value} 的活动" + label_new: 新建 + label_logged_as: 登录为 + label_environment: 环境 + label_authentication: è®¤è¯ + label_auth_source: è®¤è¯æ¨¡å¼ + label_auth_source_new: æ–°å»ºè®¤è¯æ¨¡å¼ + label_auth_source_plural: è®¤è¯æ¨¡å¼ + label_subproject_plural: å­é¡¹ç›® + label_subproject_new: 新建å­é¡¹ç›® + label_and_its_subprojects: "%{value} åŠå…¶å­é¡¹ç›®" + label_min_max_length: æœ€å° - 最大 长度 + label_list: 列表 + label_date: 日期 + label_integer: æ•´æ•° + label_float: 浮点数 + label_boolean: 布尔值 + label_string: 字符串 + label_text: 文本 + label_attribute: 属性 + label_attribute_plural: 属性 + label_no_data: 没有任何数æ®å¯ä¾›æ˜¾ç¤º + label_change_status: å˜æ›´çŠ¶æ€ + label_history: 历å²è®°å½• + label_attachment: 文件 + label_attachment_new: 新建文件 + label_attachment_delete: 删除文件 + label_attachment_plural: 文件 + label_file_added: 文件已添加 + label_report: 报表 + label_report_plural: 报表 + label_news: æ–°é—» + label_news_new: 添加新闻 + label_news_plural: æ–°é—» + label_news_latest: 最近的新闻 + label_news_view_all: 查看所有新闻 + label_news_added: 新闻已添加 + label_settings: é…ç½® + label_overview: 概述 + label_version: 版本 + label_version_new: 新建版本 + label_version_plural: 版本 + label_close_versions: 关闭已完æˆçš„版本 + label_confirmation: 确认 + label_export_to: 导出 + label_read: 读å–... + label_public_projects: 公开的项目 + label_open_issues: 打开 + label_open_issues_plural: 打开 + label_closed_issues: 已关闭 + label_closed_issues_plural: 已关闭 + label_x_open_issues_abbr_on_total: + zero: 0 打开 / %{total} + one: 1 打开 / %{total} + other: "%{count} 打开 / %{total}" + label_x_open_issues_abbr: + zero: 0 打开 + one: 1 打开 + other: "%{count} 打开" + label_x_closed_issues_abbr: + zero: 0 已关闭 + one: 1 已关闭 + other: "%{count} 已关闭" + label_total: åˆè®¡ + label_permissions: æƒé™ + label_current_status: 当å‰çŠ¶æ€ + label_new_statuses_allowed: å…è®¸çš„æ–°çŠ¶æ€ + label_all: 全部 + label_none: æ—  + label_nobody: 无人 + label_next: 下一页 + label_previous: 上一页 + label_used_by: 使用中 + label_details: 详情 + label_add_note: 添加说明 + label_per_page: æ¯é¡µ + label_calendar: 日历 + label_months_from: ä¸ªæœˆä»¥æ¥ + label_gantt: 甘特图 + label_internal: 内部 + label_last_changes: "最近的 %{count} æ¬¡å˜æ›´" + label_change_view_all: æŸ¥çœ‹æ‰€æœ‰å˜æ›´ + label_personalize_page: 个性化定制本页 + label_comment: 评论 + label_comment_plural: 评论 + label_x_comments: + zero: 无评论 + one: 1 æ¡è¯„论 + other: "%{count} æ¡è¯„论" + label_comment_add: 添加评论 + label_comment_added: 评论已添加 + label_comment_delete: 删除评论 + label_query: 自定义查询 + label_query_plural: 自定义查询 + label_query_new: 新建查询 + label_filter_add: 增加过滤器 + label_filter_plural: 过滤器 + label_equals: 等于 + label_not_equals: ä¸ç­‰äºŽ + label_in_less_than: 剩余天数å°äºŽ + label_in_more_than: 剩余天数大于 + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: 剩余天数 + label_today: 今天 + label_all_time: 全部时间 + label_yesterday: 昨天 + label_this_week: 本周 + label_last_week: 上周 + label_last_n_days: "æœ€åŽ %{count} 天" + label_this_month: 本月 + label_last_month: 上月 + label_this_year: 今年 + label_date_range: 日期范围 + label_less_than_ago: 之å‰å¤©æ•°å°‘于 + label_more_than_ago: 之å‰å¤©æ•°å¤§äºŽ + label_ago: 之å‰å¤©æ•° + label_contains: åŒ…å« + label_not_contains: ä¸åŒ…å« + label_day_plural: 天 + label_repository: 版本库 + label_repository_plural: 版本库 + label_browse: æµè§ˆ + label_branch: 分支 + label_tag: 标签 + label_revision: 修订 + label_revision_plural: 修订 + label_revision_id: 修订 %{value} + label_associated_revisions: 相关修订版本 + label_added: 已添加 + label_modified: 已修改 + label_copied: å·²å¤åˆ¶ + label_renamed: å·²é‡å‘½å + label_deleted: 已删除 + label_latest_revision: 最近的修订版本 + label_latest_revision_plural: 最近的修订版本 + label_view_revisions: 查看修订 + label_view_all_revisions: 查看所有修订 + label_max_size: 最大尺寸 + label_sort_highest: 置顶 + label_sort_higher: 上移 + label_sort_lower: 下移 + label_sort_lowest: 置底 + label_roadmap: 路线图 + label_roadmap_due_in: "截止日期到 %{value}" + label_roadmap_overdue: "%{value} 延期" + label_roadmap_no_issues: 该版本没有问题 + label_search: æœç´¢ + label_result_plural: 结果 + label_all_words: 所有å•è¯ + label_wiki: Wiki + label_wiki_edit: Wiki 编辑 + label_wiki_edit_plural: Wiki 编辑记录 + label_wiki_page: Wiki é¡µé¢ + label_wiki_page_plural: Wiki é¡µé¢ + label_index_by_title: 按标题索引 + label_index_by_date: 按日期索引 + label_current_version: 当å‰ç‰ˆæœ¬ + label_preview: 预览 + label_feed_plural: Feeds + label_changes_details: æ‰€æœ‰å˜æ›´çš„详情 + label_issue_tracking: 问题跟踪 + label_spent_time: 耗时 + label_overall_spent_time: 总体耗时 + label_f_hour: "%{value} å°æ—¶" + label_f_hour_plural: "%{value} å°æ—¶" + label_time_tracking: 时间跟踪 + label_change_plural: å˜æ›´ + label_statistics: 统计 + label_commits_per_month: æ¯æœˆæäº¤æ¬¡æ•° + label_commits_per_author: æ¯ç”¨æˆ·æäº¤æ¬¡æ•° + label_view_diff: 查看差别 + label_diff_inline: 直列 + label_diff_side_by_side: 并排 + label_options: 选项 + label_copy_workflow_from: 从以下选项å¤åˆ¶å·¥ä½œæµç¨‹ + label_permissions_report: æƒé™æŠ¥è¡¨ + label_watched_issues: 跟踪的问题 + label_related_issues: 相关的问题 + label_applied_status: 应用åŽçš„çŠ¶æ€ + label_loading: 载入中... + label_relation_new: æ–°å»ºå…³è” + label_relation_delete: åˆ é™¤å…³è” + label_relates_to: å…³è”到 + label_duplicates: é‡å¤ + label_duplicated_by: 与其é‡å¤ + label_blocks: 阻挡 + label_blocked_by: 被阻挡 + label_precedes: 优先于 + label_follows: è·ŸéšäºŽ + label_end_to_start: 结æŸ-开始 + label_end_to_end: 结æŸ-ç»“æŸ + label_start_to_start: 开始-开始 + label_start_to_end: 开始-ç»“æŸ + label_stay_logged_in: ä¿æŒç™»å½•çŠ¶æ€ + label_disabled: ç¦ç”¨ + label_show_completed_versions: 显示已完æˆçš„版本 + label_me: 我 + label_board: 讨论区 + label_board_new: 新建讨论区 + label_board_plural: 讨论区 + label_board_locked: é”定 + label_board_sticky: 置顶 + label_topic_plural: 主题 + label_message_plural: å¸–å­ + label_message_last: æœ€æ–°çš„å¸–å­ + label_message_new: æ–°è´´ + label_message_posted: å‘帖æˆåŠŸ + label_reply_plural: å›žå¤ + label_send_information: 给用户å‘é€å¸å·ä¿¡æ¯ + label_year: å¹´ + label_month: 月 + label_week: 周 + label_date_from: 从 + label_date_to: 到 + label_language_based: æ ¹æ®ç”¨æˆ·çš„语言 + label_sort_by: "æ ¹æ® %{value} 排åº" + label_send_test_email: å‘逿µ‹è¯•邮件 + label_feeds_access_key: Atomå­˜å–é”® + label_missing_feeds_access_key: 缺少Atomå­˜å–é”® + label_feeds_access_key_created_on: "Atomå­˜å–键是在 %{value} 之å‰å»ºç«‹çš„" + label_module_plural: æ¨¡å— + label_added_time_by: "ç”± %{author} 在 %{age} 之剿·»åŠ " + label_updated_time: " 更新于 %{value} 之å‰" + label_updated_time_by: "ç”± %{author} 更新于 %{age} 之å‰" + label_jump_to_a_project: 选择一个项目... + label_file_plural: 文件 + label_changeset_plural: å˜æ›´ + label_default_columns: 默认列 + label_no_change_option: (ä¸å˜) + label_bulk_edit_selected_issues: 批é‡ä¿®æ”¹é€‰ä¸­çš„问题 + label_theme: 主题 + label_default: 默认 + label_search_titles_only: 仅在标题中æœç´¢ + label_user_mail_option_all: "æ”¶å–æˆ‘的项目的所有通知" + label_user_mail_option_selected: "æ”¶å–选中项目的所有通知..." + label_user_mail_option_none: "䏿”¶å–任何通知" + label_user_mail_option_only_my_events: "åªæ”¶å–我跟踪或å‚与的项目的通知" + label_user_mail_option_only_assigned: "åªæ”¶å–分é…给我的" + label_user_mail_option_only_owner: åªæ”¶å–由我创建的 + label_user_mail_no_self_notified: "ä¸è¦å‘é€å¯¹æˆ‘自己æäº¤çš„修改的通知" + label_registration_activation_by_email: é€šè¿‡é‚®ä»¶è®¤è¯æ¿€æ´»å¸å· + label_registration_manual_activation: 手动激活å¸å· + label_registration_automatic_activation: 自动激活å¸å· + label_display_per_page: "æ¯é¡µæ˜¾ç¤ºï¼š%{value}" + label_age: æäº¤æ—¶é—´ + label_change_properties: 修改属性 + label_general: 一般 + label_more: 更多 + label_scm: SCM + label_plugins: æ’ä»¶ + label_ldap_authentication: LDAP è®¤è¯ + label_downloads_abbr: D/L + label_optional_description: å¯é€‰çš„æè¿° + label_add_another_file: 添加其它文件 + label_preferences: 首选项 + label_chronological_order: æŒ‰æ—¶é—´é¡ºåº + label_reverse_chronological_order: 按时间顺åºï¼ˆå€’åºï¼‰ + label_planning: 计划 + label_incoming_emails: 接收邮件 + label_generate_key: 生æˆä¸€ä¸ªkey + label_issue_watchers: 跟踪者 + label_example: 示例 + label_display: 显示 + label_sort: æŽ’åº + label_ascending: å‡åº + label_descending: é™åº + label_date_from_to: 从 %{start} 到 %{end} + label_wiki_content_added: Wiki 页é¢å·²æ·»åŠ  + label_wiki_content_updated: Wiki 页é¢å·²æ›´æ–° + label_group: 组 + label_group_plural: 组 + label_group_new: 新建组 + label_time_entry_plural: 耗时 + label_version_sharing_none: ä¸å…±äº« + label_version_sharing_descendants: 与å­é¡¹ç›®å…±äº« + label_version_sharing_hierarchy: 与项目继承层次共享 + label_version_sharing_tree: 与项目树共享 + label_version_sharing_system: 与所有项目共享 + label_update_issue_done_ratios: 更新问题的完æˆåº¦ + label_copy_source: æº + label_copy_target: 目标 + label_copy_same_as_target: 与目标一致 + label_display_used_statuses_only: åªæ˜¾ç¤ºè¢«æ­¤è·Ÿè¸ªæ ‡ç­¾ä½¿ç”¨çš„çŠ¶æ€ + label_api_access_key: API访问键 + label_missing_api_access_key: 缺少API访问键 + label_api_access_key_created_on: API访问键是在 %{value} 之å‰å»ºç«‹çš„ + label_profile: 简介 + label_subtask_plural: å­ä»»åŠ¡ + label_project_copy_notifications: å¤åˆ¶é¡¹ç›®æ—¶å‘é€é‚®ä»¶é€šçŸ¥ + label_principal_search: "æœç´¢ç”¨æˆ·æˆ–组:" + label_user_search: "æœç´¢ç”¨æˆ·ï¼š" + + button_login: 登录 + button_submit: æäº¤ + button_save: ä¿å­˜ + button_check_all: 全选 + button_uncheck_all: 清除 + button_delete: 删除 + button_create: 创建 + button_create_and_continue: 创建并继续 + button_test: 测试 + button_edit: 编辑 + button_edit_associated_wikipage: "编辑相关wiki页é¢: %{page_title}" + button_add: 新增 + button_change: 修改 + button_apply: 应用 + button_clear: 清除 + button_lock: é”定 + button_unlock: è§£é” + button_download: 下载 + button_list: 列表 + button_view: 查看 + button_move: 移动 + button_move_and_follow: 移动并转到新问题 + button_back: 返回 + button_cancel: å–æ¶ˆ + button_activate: 激活 + button_sort: æŽ’åº + button_log_time: 登记工时 + button_rollback: æ¢å¤åˆ°è¿™ä¸ªç‰ˆæœ¬ + button_watch: 跟踪 + button_unwatch: å–æ¶ˆè·Ÿè¸ª + button_reply: å›žå¤ + button_archive: 存档 + button_unarchive: å–æ¶ˆå­˜æ¡£ + button_reset: é‡ç½® + button_rename: é‡å‘½å/é‡å®šå‘ + button_change_password: ä¿®æ”¹å¯†ç  + button_copy: å¤åˆ¶ + button_copy_and_follow: å¤åˆ¶å¹¶è½¬åˆ°æ–°é—®é¢˜ + button_annotate: 追溯 + button_update: æ›´æ–° + button_configure: é…ç½® + button_quote: 引用 + button_duplicate: 副本 + button_show: 显示 + + status_active: 活动的 + status_registered: 已注册 + status_locked: å·²é”定 + + version_status_open: 打开 + version_status_locked: é”定 + version_status_closed: 关闭 + + field_active: 活动 + + text_select_mail_notifications: 选择需è¦å‘é€é‚®ä»¶é€šçŸ¥çš„动作 + text_regexp_info: 例如:^[A-Z0-9]+$ + text_min_max_length_info: 0 表示没有é™åˆ¶ + text_project_destroy_confirmation: 您确信è¦åˆ é™¤è¿™ä¸ªé¡¹ç›®ä»¥åŠæ‰€æœ‰ç›¸å…³çš„æ•°æ®å—? + text_subprojects_destroy_warning: "以下å­é¡¹ç›®ä¹Ÿå°†è¢«åŒæ—¶åˆ é™¤ï¼š%{value}" + text_workflow_edit: 选择角色和跟踪标签æ¥ç¼–辑工作æµç¨‹ + text_are_you_sure: 您确定? + text_journal_changed: "%{label} 从 %{old} å˜æ›´ä¸º %{new}" + text_journal_set_to: "%{label} 被设置为 %{value}" + text_journal_deleted: "%{label} 已删除 (%{old})" + text_journal_added: "%{label} %{value} 已添加" + text_tip_issue_begin_day: 今天开始的任务 + text_tip_issue_end_day: 今天结æŸçš„任务 + text_tip_issue_begin_end_day: 今天开始并结æŸçš„任务 + text_caracters_maximum: "最多 %{count} 个字符。" + text_caracters_minimum: "è‡³å°‘éœ€è¦ %{count} 个字符。" + text_length_between: "长度必须在 %{min} 到 %{max} 个字符之间。" + text_tracker_no_workflow: 此跟踪标签未定义工作æµç¨‹ + text_unallowed_characters: éžæ³•字符 + text_comma_separated: å¯ä»¥ä½¿ç”¨å¤šä¸ªå€¼ï¼ˆç”¨é€—å·,分开)。 + text_line_separated: å¯ä»¥ä½¿ç”¨å¤šä¸ªå€¼ï¼ˆæ¯è¡Œä¸€ä¸ªå€¼ï¼‰ã€‚ + text_issues_ref_in_commit_messages: 在æäº¤ä¿¡æ¯ä¸­å¼•用和解决问题 + text_issue_added: "问题 %{id} 已由 %{author} æäº¤ã€‚" + text_issue_updated: "问题 %{id} 已由 %{author} 更新。" + text_wiki_destroy_confirmation: 您确定è¦åˆ é™¤è¿™ä¸ª wiki åŠå…¶æ‰€æœ‰å†…容å—? + text_issue_category_destroy_question: "有一些问题(%{count} ä¸ªï¼‰å±žäºŽæ­¤ç±»åˆ«ã€‚æ‚¨æƒ³è¿›è¡Œå“ªç§æ“作?" + text_issue_category_destroy_assignments: 删除问题的所属类别(问题å˜ä¸ºæ— ç±»åˆ«ï¼‰ + text_issue_category_reassign_to: 为问题选择其它类别 + text_user_mail_option: "对于没有选中的项目,您将åªä¼šæ”¶åˆ°æ‚¨è·Ÿè¸ªæˆ–å‚与的项目的通知(比如说,您是问题的报告者, 或被指派解决此问题)。" + text_no_configuration_data: "角色ã€è·Ÿè¸ªæ ‡ç­¾ã€é—®é¢˜çжæ€å’Œå·¥ä½œæµç¨‹è¿˜æ²¡æœ‰è®¾ç½®ã€‚\n强烈建议您先载入默认设置,然åŽåœ¨æ­¤åŸºç¡€ä¸Šè¿›è¡Œä¿®æ”¹ã€‚" + text_load_default_configuration: 载入默认设置 + text_status_changed_by_changeset: "å·²åº”ç”¨åˆ°å˜æ›´åˆ—表 %{value}." + text_time_logged_by_changeset: "已应用到修订版本 %{value}." + text_issues_destroy_confirmation: '您确定è¦åˆ é™¤é€‰ä¸­çš„问题å—?' + text_select_project_modules: '请选择此项目å¯ä»¥ä½¿ç”¨çš„æ¨¡å—:' + text_default_administrator_account_changed: 默认的管ç†å‘˜å¸å·å·²æ”¹å˜ + text_file_repository_writable: 附件路径å¯å†™ + text_plugin_assets_writable: æ’件的附件路径å¯å†™ + text_rmagick_available: RMagick å¯ç”¨ï¼ˆå¯é€‰çš„) + text_destroy_time_entries_question: 您è¦åˆ é™¤çš„问题已ç»ä¸ŠæŠ¥äº† %{hours} å°æ—¶çš„工作é‡ã€‚æ‚¨æƒ³è¿›è¡Œé‚£ç§æ“作? + text_destroy_time_entries: åˆ é™¤ä¸ŠæŠ¥çš„å·¥ä½œé‡ + text_assign_time_entries_to_project: å°†å·²ä¸ŠæŠ¥çš„å·¥ä½œé‡æäº¤åˆ°é¡¹ç›®ä¸­ + text_reassign_time_entries: 'å°†å·²ä¸ŠæŠ¥çš„å·¥ä½œé‡æŒ‡å®šåˆ°æ­¤é—®é¢˜ï¼š' + text_user_wrote: "%{value} 写到:" + text_enumeration_destroy_question: "%{count} 个对象被关è”到了这个枚举值。" + text_enumeration_category_reassign_to: '将它们关è”到新的枚举值:' + text_email_delivery_not_configured: "邮件傿•°å°šæœªé…置,因此邮件通知功能已被ç¦ç”¨ã€‚\n请在config/configuration.yml中é…置您的SMTPæœåŠ¡å™¨ä¿¡æ¯å¹¶é‡æ–°å¯åŠ¨ä»¥ä½¿å…¶ç”Ÿæ•ˆã€‚" + text_repository_usernames_mapping: "选择或更新与版本库中的用户å对应的Redmine用户。\n版本库中与Redmine中的åŒå用户将被自动对应。" + text_diff_truncated: '... å·®åˆ«å†…å®¹è¶…è¿‡äº†å¯æ˜¾ç¤ºçš„æœ€å¤§è¡Œæ•°å¹¶å·²è¢«æˆªæ–­' + text_custom_field_possible_values_info: 'æ¯é¡¹æ•°å€¼ä¸€è¡Œ' + text_wiki_page_destroy_question: æ­¤é¡µé¢æœ‰ %{descendants} 个å­é¡µé¢å’Œä¸‹çº§é¡µé¢ã€‚æ‚¨æƒ³è¿›è¡Œé‚£ç§æ“作? + text_wiki_page_nullify_children: å°†å­é¡µé¢ä¿ç•™ä¸ºæ ¹é¡µé¢ + text_wiki_page_destroy_children: 删除å­é¡µé¢åŠå…¶æ‰€æœ‰ä¸‹çº§é¡µé¢ + text_wiki_page_reassign_children: å°†å­é¡µé¢çš„上级页é¢è®¾ç½®ä¸º + text_own_membership_delete_confirmation: 你正在删除你现有的æŸäº›æˆ–全部æƒé™ï¼Œå¦‚果这样åšäº†ä½ å¯èƒ½å°†ä¼šå†ä¹Ÿæ— æ³•编辑该项目了。你确定è¦ç»§ç»­å—? + text_zoom_in: 放大 + text_zoom_out: ç¼©å° + + default_role_manager: 管ç†äººå‘˜ + default_role_developer: å¼€å‘人员 + default_role_reporter: 报告人员 + default_tracker_bug: 错误 + default_tracker_feature: 功能 + default_tracker_support: æ”¯æŒ + default_issue_status_new: 新建 + default_issue_status_in_progress: 进行中 + default_issue_status_resolved: 已解决 + default_issue_status_feedback: å馈 + default_issue_status_closed: 已关闭 + default_issue_status_rejected: å·²æ‹’ç» + default_doc_category_user: 用户文档 + default_doc_category_tech: 技术文档 + default_priority_low: 低 + default_priority_normal: 普通 + default_priority_high: 高 + default_priority_urgent: 紧急 + default_priority_immediate: 立刻 + default_activity_design: 设计 + default_activity_development: å¼€å‘ + + enumeration_issue_priorities: 问题优先级 + enumeration_doc_categories: 文档类别 + enumeration_activities: 活动(时间跟踪) + enumeration_system_activity: 系统活动 + + field_warn_on_leaving_unsaved: 当离开未ä¿å­˜å†…å®¹çš„é¡µé¢æ—¶ï¼Œæç¤ºæˆ‘ + text_warn_on_leaving_unsaved: 若离开当å‰é¡µé¢ï¼Œåˆ™è¯¥é¡µé¢å†…未ä¿å­˜çš„内容将丢失。 + label_my_queries: 我的自定义查询 + text_journal_changed_no_detail: "%{label} 已更新。" + label_news_comment_added: 添加到新闻的评论 + button_expand_all: 展开所有 + button_collapse_all: åˆæ‹¢æ‰€æœ‰ + label_additional_workflow_transitions_for_assignee: 当用户是问题的分é…对象时所å…许的问题状æ€è½¬æ¢ + label_additional_workflow_transitions_for_author: 当用户是问题作者时所å…许的问题状æ€è½¬æ¢ + label_bulk_edit_selected_time_entries: 批é‡ä¿®æ”¹é€‰å®šçš„æ—¶é—´æ¡ç›® + text_time_entries_destroy_confirmation: 是å¦ç¡®å®šè¦åˆ é™¤é€‰å®šçš„æ—¶é—´æ¡ç›®ï¼Ÿ + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: 问题备注已添加 + label_issue_status_updated: é—®é¢˜çŠ¶æ€æ›´æ–° + label_issue_priority_updated: 问题优先级更新 + label_issues_visibility_own: 创建或分é…给用户的问题 + field_issues_visibility: 问题å¯è§ + label_issues_visibility_all: 全部问题 + permission_set_own_issues_private: è®¾ç½®è‡ªå·±çš„é—®é¢˜ä¸ºå…¬å¼€æˆ–ç§æœ‰ + field_is_private: ç§æœ‰ + permission_set_issues_private: è®¾ç½®é—®é¢˜ä¸ºå…¬å¼€æˆ–ç§æœ‰ + label_issues_visibility_public: 全部éžç§æœ‰é—®é¢˜ + text_issues_destroy_descendants_confirmation: æ­¤æ“ä½œåŒæ—¶ä¼šåˆ é™¤ %{count} 个å­ä»»åŠ¡ã€‚ + + field_commit_logs_encoding: æäº¤æ³¨é‡Šçš„ç¼–ç  + field_scm_path_encoding: è·¯å¾„ç¼–ç  + text_scm_path_encoding_note: "默认: UTF-8" + field_path_to_repository: 库路径 + field_root_directory: 根目录 + field_cvs_module: CVS Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: 本地库 (e.g. /hgrepo, c:\hgrepo) + text_scm_command: 命令 + text_scm_command_version: 版本 + label_git_report_last_commit: 报告最åŽä¸€æ¬¡æ–‡ä»¶/目录æäº¤ + text_scm_config: 您å¯ä»¥åœ¨config/configuration.yml中é…置您的SCM命令。 请在编辑åŽï¼Œé‡å¯Redmine应用。 + text_scm_command_not_available: Scm命令ä¸å¯ç”¨ã€‚ 请检查管ç†é¢æ¿çš„é…置。 + text_git_repository_note: 库中无内容。(e.g. /gitrepo, c:\gitrepo) + notice_issue_successful_create: 问题 %{id} 已创建。 + label_between: 介于 + setting_issue_group_assignment: å…许问题被分é…给组 + label_diff: diff + description_query_sort_criteria_direction: æŽ’åºæ–¹å¼ + description_project_scope: æœç´¢èŒƒå›´ + description_filter: 过滤器 + description_user_mail_notification: 邮件通知设置 + description_date_from: 输入开始日期 + description_message_content: ä¿¡æ¯å†…容 + description_available_columns: 备选列 + description_date_range_interval: æŒ‰å¼€å§‹æ—¥æœŸå’Œç»“æŸæ—¥æœŸé€‰æ‹©èŒƒå›´ + description_issue_category_reassign: 选择问题类别 + description_search: æœç´¢å­—段 + description_notes: 批注 + description_date_range_list: 从列表中选择范围 + description_choose_project: 项目 + description_date_to: è¾“å…¥ç»“æŸæ—¥æœŸ + description_query_sort_criteria_attribute: æŽ’åºæ–¹å¼ + description_wiki_subpages_reassign: é€‰æ‹©çˆ¶é¡µé¢ + description_selected_columns: 已选列 + label_parent_revision: 父修订 + label_child_revision: å­ä¿®è®¢ + error_scm_annotate_big_text_file: 输入文本内容超长,无法输入。 + setting_default_issue_start_date_to_creation_date: ä½¿ç”¨å½“å‰æ—¥æœŸä½œä¸ºæ–°é—®é¢˜çš„开始日期 + button_edit_section: 编辑此区域 + setting_repositories_encodings: é™„ä»¶å’Œç‰ˆæœ¬åº“ç¼–ç  + description_all_columns: 所有列 + button_export: 导出 + label_export_options: "%{export_format} 导出选项" + error_attachment_too_big: 该文件无法上传。超过文件大å°é™åˆ¶ (%{max_size}) + notice_failed_to_save_time_entries: "无法ä¿å­˜ä¸‹åˆ—所选å–çš„ %{total} 个项目中的 %{count} 工时: %{ids}。" + label_x_issues: + zero: 0 问题 + one: 1 问题 + other: "%{count} 问题" + label_repository_new: 新建版本库 + field_repository_is_default: 主版本库 + label_copy_attachments: å¤åˆ¶é™„ä»¶ + label_item_position: "%{position}/%{count}" + label_completed_versions: 已完æˆçš„版本 + text_project_identifier_info: ä»…å°å†™å­—æ¯ï¼ˆa-zï¼‰ã€æ•°å­—ã€ç ´æŠ˜å·ï¼ˆ-)和下划线(_)å¯ä»¥ä½¿ç”¨ã€‚
    一旦ä¿å­˜ï¼Œæ ‡è¯†æ— æ³•修改。 + field_multiple: 多é‡å–值 + setting_commit_cross_project_ref: å…许引用/ä¿®å¤æ‰€æœ‰å…¶ä»–项目的问题 + text_issue_conflict_resolution_add_notes: æ·»åŠ è¯´æ˜Žå¹¶å–æ¶ˆæˆ‘çš„å…¶ä»–å˜æ›´å¤„ç†ã€‚ + text_issue_conflict_resolution_overwrite: ç›´æŽ¥å¥—ç”¨æˆ‘çš„å˜æ›´ (先å‰çš„说明将被ä¿ç•™ï¼Œä½†æ˜¯æŸäº›å˜æ›´å†…容å¯èƒ½ä¼šè¢«è¦†ç›–) + notice_issue_update_conflict: 当您正在编辑这个问题的时候,它已ç»è¢«å…¶ä»–人抢先一步更新过了。 + text_issue_conflict_resolution_cancel: å–æ¶ˆæˆ‘æ‰€æœ‰çš„å˜æ›´å¹¶é‡æ–°åˆ·æ–°æ˜¾ç¤º %{link} 。 + permission_manage_related_issues: ç›¸å…³é—®é¢˜ç®¡ç† + field_auth_source_ldap_filter: LDAP 过滤器 + label_search_for_watchers: é€šè¿‡æŸ¥æ‰¾æ–¹å¼æ·»åŠ è·Ÿè¸ªè€… + notice_account_deleted: 您的账å·å·²è¢«æ°¸ä¹…删除(账å·å·²æ— æ³•æ¢å¤ï¼‰ã€‚ + setting_unsubscribe: å…许用户退订 + button_delete_my_account: åˆ é™¤æˆ‘çš„è´¦å· + text_account_destroy_confirmation: |- + 确定继续处ç†ï¼Ÿ + 您的账å·ä¸€æ—¦åˆ é™¤ï¼Œå°†æ— æ³•冿¬¡æ¿€æ´»ä½¿ç”¨ã€‚ + error_session_expired: 您的会è¯å·²è¿‡æœŸã€‚è¯·é‡æ–°ç™»é™†ã€‚ + text_session_expiration_settings: "警告: 更改这些设置将会使包括你在内的当å‰ä¼šè¯å¤±æ•ˆã€‚" + setting_session_lifetime: ä¼šè¯æœ€å¤§æœ‰æ•ˆæ—¶é—´ + setting_session_timeout: 会è¯é—²ç½®è¶…æ—¶ + label_session_expiration: 会è¯è¿‡æœŸ + permission_close_project: 关闭/é‡å¼€é¡¹ç›® + label_show_closed_projects: 查看已关闭的项目 + button_close: 关闭 + button_reopen: é‡å¼€ + project_status_active: 已激活 + project_status_closed: 已关闭 + project_status_archived: 已存档 + text_project_closed: 当å‰é¡¹ç›®å·²è¢«å…³é—­ã€‚当å‰é¡¹ç›®åªè¯»ã€‚ + notice_user_successful_create: 用户 %{id} 已创建。 + field_core_fields: 标准字段 + field_timeout: è¶…æ—¶ (ç§’) + setting_thumbnails_enabled: 显示附件略缩图 + setting_thumbnails_size: 略缩图尺寸 (åƒç´ ) + label_status_transitions: 状æ€è½¬æ¢ + label_fields_permissions: 字段æƒé™ + label_readonly: åªè¯» + label_required: å¿…å¡« + text_repository_identifier_info: ä»…å°å†™å­—æ¯ï¼ˆa-zï¼‰ã€æ•°å­—ã€ç ´æŠ˜å·ï¼ˆ-)和下划线(_)å¯ä»¥ä½¿ç”¨ã€‚
    一旦ä¿å­˜ï¼Œæ ‡è¯†æ— æ³•修改。 + field_board_parent: çˆ¶è®ºå› + label_attribute_of_project: 项目 %{name} + label_attribute_of_author: 作者 %{name} + label_attribute_of_assigned_to: 分é…ç»™ %{name} + label_attribute_of_fixed_version: 目标版本 %{name} + label_copy_subtasks: å¤åˆ¶å­ä»»åŠ¡ + label_copied_to: å¤åˆ¶åˆ° + label_copied_from: å¤åˆ¶äºŽ + label_any_issues_in_project: 项目内任æ„问题 + label_any_issues_not_in_project: 项目外任æ„问题 + field_private_notes: ç§æœ‰æ³¨è§£ + permission_view_private_notes: æŸ¥çœ‹ç§æœ‰æ³¨è§£ + permission_set_notes_private: è®¾ç½®ä¸ºç§æœ‰æ³¨è§£ + label_no_issues_in_project: 项目内无相关问题 + label_any: 全部 + label_last_n_weeks: 上 %{count} å‘¨å‰ + setting_cross_project_subtasks: 支æŒè·¨é¡¹ç›®å­ä»»åŠ¡ + label_cross_project_descendants: 与å­é¡¹ç›®å…±äº« + label_cross_project_tree: 与项目树共享 + label_cross_project_hierarchy: 与项目继承层次共享 + label_cross_project_system: 与所有项目共享 + button_hide: éšè— + setting_non_working_week_days: éžå·¥ä½œæ—¥ + label_in_the_next_days: 在未æ¥å‡ å¤©ä¹‹å†… + label_in_the_past_days: 在过去几天之内 + label_attribute_of_user: 用户是 %{name} + text_turning_multiple_off: 如果您åœç”¨å¤šé‡å€¼è®¾å®šï¼Œé‡å¤çš„值将被移除,以使æ¯ä¸ªé¡¹ç›®ä»…ä¿ç•™ä¸€ä¸ªå€¼ + label_attribute_of_issue: 问题是 %{name} + permission_add_documents: 添加文档 + permission_edit_documents: 编辑文档 + permission_delete_documents: 删除文档 + label_gantt_progress_line: 进度线 + setting_jsonp_enabled: å¯ç”¨JSONPæ”¯æŒ + field_inherit_members: 继承父项目æˆå‘˜ + field_closed_on: ç»“æŸæ—¥æœŸ + field_generate_password: 生æˆå¯†ç  + setting_default_projects_tracker_ids: 新建项目默认跟踪标签 + label_total_time: åˆè®¡ + notice_account_not_activated_yet: 您的账å·å°šæœªæ¿€æ´». 若您è¦é‡æ–°æ”¶å–激活邮件, 请å•击此链接. + notice_account_locked: 您的å¸å·å·²è¢«é”定 + label_hidden: éšè— + label_visibility_private: 仅对我å¯è§ + label_visibility_roles: 仅对选å–角色å¯è§ + label_visibility_public: 对任何人å¯è§ + field_must_change_passwd: ä¸‹æ¬¡ç™»å½•æ—¶å¿…é¡»ä¿®æ”¹å¯†ç  + notice_new_password_must_be_different: 新密ç å¿…须和旧密ç ä¸åŒ + setting_mail_handler_excluded_filenames: 移除符åˆä¸‹åˆ—å称的附件 + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0f/0f6a1a5f91b1625dba02a90214d3f1e2572096f8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0f/0f6a1a5f91b1625dba02a90214d3f1e2572096f8.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本 ++日本語 + bbbb diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0f/0f9b26db420557a0a5b8bf781aba4ca50d51d3d0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0f/0f9b26db420557a0a5b8bf781aba4ca50d51d3d0.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,80 @@ + + + + +<%=h html_title %> + + +<%= csrf_meta_tag %> +<%= favicon %> +<%= stylesheet_link_tag 'jquery/jquery-ui-1.9.2', 'application', :media => 'all' %> +<%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %> +<%= javascript_heads %> +<%= heads_for_theme %> +<%= call_hook :view_layouts_base_html_head %> + +<%= yield :header_tags -%> + + +
    +
    +
    +
    +
    + <%= render_menu :account_menu -%> +
    + <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}".html_safe, :id => 'loggedas') if User.current.logged? %> + <%= render_menu :top_menu if User.current.logged? || !Setting.login_required? -%> +
    + + + + +
    + + + + + +
    +
    +<%= call_hook :view_layouts_base_body_bottom %> + + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/0f/0fe8309158c952d8ecd963cac89b3329eaf84e52.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0f/0fe8309158c952d8ecd963cac89b3329eaf84e52.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,89 @@ +<%= error_messages_for 'member' %> +<% roles = Role.find_all_givable + members = @project.member_principals.includes(:member_roles, :roles, :principal).all.sort %> + +
    +<% if members.any? %> + + + + + + <%= call_hook(:view_projects_settings_members_table_header, :project => @project) %> + + + <% members.each do |member| %> + <% next if member.new_record? %> + + + + + <%= call_hook(:view_projects_settings_members_table_row, { :project => @project, :member => member}) %> + +<% end; reset_cycle %> + +
    <%= l(:label_user) %> / <%= l(:label_group) %><%= l(:label_role_plural) %>
    <%= link_to_user member.principal %> + <%= member.roles.sort.collect(&:to_s).join(', ') %> + <%= form_for(member, + {:as => :membership, :remote => true, + :url => membership_path(member), + :method => :put, + :html => { :id => "member-#{member.id}-roles-form", :class => 'hol' }} + ) do |f| %> +

    + <% roles.each do |role| %> +
    + <% end %> +

    + <%= hidden_field_tag 'membership[role_ids][]', '' %> +

    + <%= submit_tag l(:button_save), :class => "small" %> + <%= link_to_function(l(:button_cancel), + "$('#member-#{member.id}-roles').show(); $('#member-#{member.id}-roles-form').hide(); return false;") %> +

    + <% end %> +
    + <%= link_to_function l(:button_edit), + "$('#member-#{member.id}-roles').hide(); $('#member-#{member.id}-roles-form').show(); return false;", + :class => 'icon icon-edit' %> + <%= delete_link membership_path(member), + :remote => true, + :data => (!User.current.admin? && member.include?(User.current) ? {:confirm => l(:text_own_membership_delete_confirmation)} : {}) if member.deletable? %> +
    +<% else %> +

    <%= l(:label_no_data) %>

    +<% end %> +
    + +
    +<% if roles.any? %> + <%= form_for(@member, + {:as => :membership, :url => project_memberships_path(@project), + :remote => true, :method => :post}) do |f| %> +
    + <%=l(:label_member_new)%> +

    + <%= label_tag("principal_search", l(:label_principal_search)) %> + <%= text_field_tag('principal_search', nil) %> +

    + <%= javascript_tag "observeSearchfield('principal_search', null, '#{ escape_javascript autocomplete_project_memberships_path(@project, :format => 'js') }')" %> +
    + <%= render_principals_for_new_members(@project) %> +
    +

    + <%= l(:label_role_plural) %>: + <% roles.each do |role| %> + + <% end %> +

    +

    <%= submit_tag l(:button_add), :id => 'member-add-submit' %>

    +
    + <% end %> +<% end %> +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/10/105399d73cdd64dbe994d874bdf78e986ecd631d.svn-base --- a/.svn/pristine/10/105399d73cdd64dbe994d874bdf78e986ecd631d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class TrackersController < ApplicationController - layout 'admin' - - before_filter :require_admin, :except => :index - before_filter :require_admin_or_api_request, :only => :index - accept_api_auth :index - - def index - respond_to do |format| - format.html { - @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position' - render :action => "index", :layout => false if request.xhr? - } - format.api { - @trackers = Tracker.sorted.all - } - end - end - - def new - @tracker ||= Tracker.new(params[:tracker]) - @trackers = Tracker.find :all, :order => 'position' - @projects = Project.find(:all) - end - - def create - @tracker = Tracker.new(params[:tracker]) - if request.post? and @tracker.save - # workflow copy - if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from])) - @tracker.workflow_rules.copy(copy_from) - end - flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' - return - end - new - render :action => 'new' - end - - def edit - @tracker ||= Tracker.find(params[:id]) - @projects = Project.find(:all) - end - - def update - @tracker = Tracker.find(params[:id]) - if request.put? and @tracker.update_attributes(params[:tracker]) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' - return - end - edit - render :action => 'edit' - end - - def destroy - @tracker = Tracker.find(params[:id]) - unless @tracker.issues.empty? - flash[:error] = l(:error_can_not_delete_tracker) - else - @tracker.destroy - end - redirect_to :action => 'index' - end - - def fields - if request.post? && params[:trackers] - params[:trackers].each do |tracker_id, tracker_params| - tracker = Tracker.find_by_id(tracker_id) - if tracker - tracker.core_fields = tracker_params[:core_fields] - tracker.custom_field_ids = tracker_params[:custom_field_ids] - tracker.save - end - end - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'fields' - return - end - @trackers = Tracker.sorted.all - @custom_fields = IssueCustomField.all.sort - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/11/111c3259932e5ffb4a858defd183b9c4499d1ac5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/11/111c3259932e5ffb4a858defd183b9c4499d1ac5.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1162 @@ +# Lithuanian translations for Ruby on Rails +# by Laurynas Butkus (laurynas.butkus@gmail.com) +# Redmine translation by Gediminas Muižis gediminas.muizis@gmail.com +# and Sergej Jegorov sergej.jegorov@gmail.com +# and Gytis Gurklys gytis.gurklys@gmail.com +# and Andrius KriuÄkovas andrius.kriuckovas@gmail.com + +lt: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [sekmadienis, pirmadienis, antradienis, treÄiadienis, ketvirtadienis, penktadienis, Å¡eÅ¡tadienis] +# standalone_day_names: [Sekmadienis, Pirmadienis, Antradienis, TreÄiadienis, Ketvirtadienis, Penktadienis, Å eÅ¡tadienis] + abbr_day_names: [Sek, Pir, Ant, Tre, Ket, Pen, Å eÅ¡] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, sausio, vasario, kovo, balandžio, gegužės, birželio, liepos, rugpjÅ«Äio, rugsÄ—jo, spalio, lapkriÄio, gruodžio] + abbr_month_names: [~, Sau, Vas, Kov, Bal, Geg, Bir, Lie, Rgp, Rgs, Spa, Lap, Grd] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "ryto" + pm: "vakaro" + + datetime: + distance_in_words: + half_a_minute: "pusÄ— minutÄ—s" + less_than_x_seconds: + one: "mažiau nei %{count} sekundÄ™" + few: "mažiau nei %{count} sekundes" + many: "mažiau nei %{count} sekundžių" + other: "mažiau nei %{count} sekundžių" + x_seconds: + one: "%{count} sekundÄ—" + few: "%{count} sekundÄ—s" + many: "%{count} sekundžių" + other: "%{count} sekundžių" + less_than_x_minutes: + one: "mažiau nei minutÄ™" + other: "mažiau nei %{count} minutes(Äių)" + x_minutes: + one: "1 minutÄ™" + other: "%{count} minutes(Äių)" + about_x_hours: + one: "apie 1 valandÄ…" + other: "apie %{count} valandas(ų)" + x_hours: + one: "1 valandÄ…" + other: "%{count} valandas(ų)" + x_days: + one: "1 dienÄ…" + other: "%{count} dienas(ų)" + about_x_months: + one: "apie 1 mÄ—nuo" + other: "apie %{count} mÄ—n." + x_months: + one: "1 mÄ—nuo" + other: "%{count} mÄ—n." + about_x_years: + one: "apie 1 metus" + other: "apie %{count} metų" + over_x_years: + one: "virÅ¡ 1 metų" + other: "virÅ¡ %{count} metų" + almost_x_years: + one: "beveik 1 metus" + other: "beveik %{count} metai(us)" + prompts: + year: "Metai" + month: "MÄ—nuo" + day: "Diena" + hour: "Valanda" + minute: "MinutÄ—" + second: "SekundÄ—s" + + number: + format: + separator: "," + delimiter: " " + precision: 3 + + currency: + format: + format: "%n %u" + unit: "Lt" + separator: "," + delimiter: " " + precision: 2 + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + storage_units: + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u" + units: + byte: + one: "baitas" + few: "baitų(ai)" + many: "baitų(ai)" + other: "baitų(ai)" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + # Rails 2.2 + sentence_connector: "ir" + skip_last_comma: true + # Rails 2.3 + words_connector: ", " + two_words_connector: " ir " + last_word_connector: " ir " + + activerecord: + errors: + template: + header: + one: "IÅ¡saugant objektÄ… %{model} rasta %{count} klaida" + few: "IÅ¡saugant objektÄ… %{model} rasta %{count} klaidų" + many: "IÅ¡saugant objektÄ… %{model} rastos %{count} klaidos" + other: "IÅ¡saugant objektÄ… %{model} rastos %{count} klaidos" + body: "Å iuose laukuose yra klaidų:" + + messages: + inclusion: "nenumatyta reikÅ¡mÄ—" + exclusion: "užimtas" + invalid: "neteisingas" + confirmation: "neteisingai pakartotas" + accepted: "turi bÅ«ti patvirtintas" + empty: "negali bÅ«ti tuÅ¡Äias" + blank: "negali bÅ«ti tuÅ¡Äias" + too_long: + one: "per ilgas (daugiausiai %{count} simbolius)" + few: "per ilgas (daugiausiai %{count} simboliu)" + many: "per ilgas (daugiausiai %{count} simboliu)" + other: "per ilgas (daugiausiai %{count} simboliai)" + too_short: + one: "per trumpas (mažiausiai %{count} simbolius)" + few: "per trumpas (mažiausiai %{count} simboliu)" + many: "per trumpas (mažiausiai %{count} simboliu)" + other: "per trumpas (mažiausiai %{count} simboliai)" + wrong_length: + one: "neteisingo ilgio (turi bÅ«ti lygiai %{count} simbolius)" + few: "neteisingo ilgio (turi bÅ«ti lygiai %{count} simboliu)" + many: "neteisingo ilgio (turi bÅ«ti lygiai %{count} simboliu)" + other: "neteisingo ilgio (turi bÅ«ti lygiai %{count} simboliai)" + taken: "jau užimtas" + not_a_number: "ne skaiÄius" + not_a_date: "is not a valid date" + greater_than: "turi bÅ«ti didesnis už %{count}" + greater_than_or_equal_to: "turi bÅ«ti didesnis arba lygus %{count}" + equal_to: "turi bÅ«ti lygus %{count}" + less_than: "turi bÅ«ti mažesnis už %{count}" + less_than_or_equal_to: "turi bÅ«ti mažesnis arba lygus %{count}" + odd: "turi bÅ«ti nelyginis" + even: "turi bÅ«ti lyginis" + greater_than_start_date: "turi bÅ«ti didesnÄ— negu pradžios data" + not_same_project: "nepriklauso tam paÄiam projektui" + circular_dependency: "Å is ryÅ¡ys sukurtų ciklinÄ™ priklausomybÄ™" + cant_link_an_issue_with_a_descendant: "Darbas negali bÅ«ti susietas su viena iÅ¡ savo darbo dalių" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: PraÅ¡om parinkti + + general_text_No: 'Ne' + general_text_Yes: 'Taip' + general_text_no: 'ne' + general_text_yes: 'taip' + general_lang_name: 'Lithuanian (lietuvių)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Paskyra buvo sÄ—kmingai atnaujinta. + notice_account_invalid_creditentials: Negaliojantis vartotojo vardas ar slaptažodis + notice_account_password_updated: Slaptažodis buvo sÄ—kmingai atnaujintas. + notice_account_wrong_password: Neteisingas slaptažodis + notice_account_register_done: Paskyra buvo sÄ—kmingai sukurta. Kad aktyvintumÄ—te savo paskyrÄ…, paspauskite nuorodÄ…, kuri jums buvo siųsta elektroniniu paÅ¡tu. + notice_account_unknown_email: Nežinomas vartotojas. + notice_can_t_change_password: Å is praneÅ¡imas naudoja iÅ¡orinį autentiÅ¡kumo nustatymo Å¡altinį. Neįmanoma pakeisti slaptažodį. + notice_account_lost_email_sent: Ä® JÅ«sų paÅ¡tÄ… iÅ¡siųstas laiÅ¡kas su naujo slaptažodžio pasirinkimo instrukcija. + notice_account_activated: JÅ«sų paskyra aktyvuota. Galite prisijungti. + notice_successful_create: SÄ—kmingas sukÅ«rimas. + notice_successful_update: SÄ—kmingas atnaujinimas. + notice_successful_delete: SÄ—kmingas panaikinimas. + notice_successful_connection: SÄ—kmingas susijungimas. + notice_file_not_found: Puslapis, į kurį ketinate įeiti, neegzistuoja arba yra paÅ¡alintas. + notice_locking_conflict: Duomenys atnaujinti kito vartotojo. + notice_not_authorized: JÅ«s neturite teisių gauti prieigÄ… prie Å¡io puslapio. + notice_not_authorized_archived_project: Projektas, kurį bandote atidaryti, buvo suarchyvuotas. + notice_email_sent: "LaiÅ¡kas iÅ¡siųstas %{value}" + notice_email_error: "LaiÅ¡ko siuntimo metu įvyko klaida (%{value})" + notice_feeds_access_key_reseted: JÅ«sų Atom raktas buvo atnaujintas. + notice_api_access_key_reseted: JÅ«sų API prieigos raktas buvo atnaujintas. + notice_failed_to_save_issues: "Nepavyko iÅ¡saugoti %{count} problemos(ų) iÅ¡ %{total} pasirinkto: %{ids}." + notice_failed_to_save_members: "Nepavyko iÅ¡saugoti nario(ių): %{errors}." + notice_no_issue_selected: "Nepasirinkta nÄ— viena problema! PraÅ¡om pažymÄ—ti problemÄ…, kuriÄ… norite redaguoti." + notice_account_pending: "JÅ«sų paskyra buvo sukurta ir dabar laukiama administratoriaus patvirtinimo." + notice_default_data_loaded: Numatytoji konfigÅ«racija sÄ—kmingai įkrauta. + notice_unable_delete_version: Neįmanoma panaikinti versijÄ…. + notice_unable_delete_time_entry: Neįmano iÅ¡trinti laiko žurnalo įrašą. + notice_issue_done_ratios_updated: Problemos baigtumo rodikliai atnaujinti. + notice_gantt_chart_truncated: Grafikas buvo sutrumpintas, kadangi jis virÅ¡ija maksimalų (%{max}) leistinų atvaizduoti elementų kiekį + notice_issue_successful_create: Darbas %{id} sukurtas. + + error_can_t_load_default_data: "Numatytoji konfigÅ«racija negali bÅ«ti užkrauta: %{value}" + error_scm_not_found: "Duomenys ir/ar pakeitimai saugykloje (repozitorijoje) neegzistuoja." + error_scm_command_failed: "Ä®vyko klaida jungiantis prie saugyklos: %{value}" + error_scm_annotate: "Ä®raÅ¡as neegzistuoja arba negalima jo atvaizduoti." + error_scm_annotate_big_text_file: "Ä®raÅ¡o negalima atvaizduoti, nes jis virÅ¡ija maksimalų tekstinio failo dydį." + error_issue_not_found_in_project: 'Darbas nerastas arba nesuriÅ¡tas su Å¡iuo projektu' + error_no_tracker_in_project: 'Joks pÄ—dsekys nesusietas su Å¡iuo projektu. PraÅ¡om patikrinti Projekto nustatymus.' + error_no_default_issue_status: Nenustatyta numatytoji darbų bÅ«sena. PraÅ¡ome patikrinti konfigÅ«ravimÄ… ("Administravimas -> Darbų bÅ«senos"). + error_can_not_delete_custom_field: Negalima iÅ¡trinti kliento lauko + error_can_not_delete_tracker: "Å is pÄ—dsekys turi įraÅ¡us ir todÄ—l negali bÅ«ti iÅ¡trintas." + error_can_not_remove_role: "Å i rolÄ— yra naudojama ir negali bÅ«ti iÅ¡trinta." + error_can_not_reopen_issue_on_closed_version: Uždarytai versijai priskirtas darbas negali bÅ«ti atnaujintas. + error_can_not_archive_project: Å io projekto negalima suarchyvuoti + error_issue_done_ratios_not_updated: "Ä®raÅ¡o baigtumo rodikliai nebuvo atnaujinti. " + error_workflow_copy_source: 'PraÅ¡ome pasirinkti pirminį Å¡altinio seklį arba rolÄ™' + error_workflow_copy_target: 'PraÅ¡ome pasirinkti galutinį paskirties seklį(-ius) arba rolÄ™(-s)' + error_unable_delete_issue_status: 'Negalima iÅ¡trinti darbo statuso' + error_unable_to_connect: Negalima prisijungti (%{value}) + error_attachment_too_big: "Å i byla negali bÅ«ti įkelta, nes virÅ¡ija maksimaliÄ… (%{max_size}) leistinÄ… bylos apimtį" + warning_attachments_not_saved: "%{count} byla(-ų) negali bÅ«ti iÅ¡saugota." + + mail_subject_lost_password: "JÅ«sų %{value} slaptažodis" + mail_body_lost_password: 'NorÄ—dami pakeisti slaptažodį, spauskite nuorodÄ…:' + mail_subject_register: "JÅ«sų %{value} paskyros aktyvavimas" + mail_body_register: 'NorÄ—dami aktyvuoti paskyrÄ…, spauskite nuorodÄ…:' + mail_body_account_information_external: "JÅ«s galite naudoti JÅ«sų %{value} paskyrÄ…, norÄ—dami prisijungti." + mail_body_account_information: Informacija apie JÅ«sų paskyrÄ… + mail_subject_account_activation_request: "%{value} paskyros aktyvavimo praÅ¡ymas" + mail_body_account_activation_request: "Užsiregistravo naujas vartotojas (%{value}). Jo paskyra laukia jÅ«sų patvirtinimo:" + mail_subject_reminder: "%{count} jums priskirti darbai per artimiausias %{days} dienų(as)" + mail_body_reminder: "%{count} darbas(ai), kurie yra jums priskirti, baigiasi per artimiausias %{days} dienų(as):" + mail_subject_wiki_content_added: "'%{id}' pridÄ—tas wiki puslapis" + mail_body_wiki_content_added: "'%{id}' wiki puslapį pridÄ—jo %{author}." + mail_subject_wiki_content_updated: "'%{id}' atnaujintas wiki puslapis" + mail_body_wiki_content_updated: "'%{id}' wiki puslapį atnaujino %{author}." + + + field_name: Pavadinimas + field_description: ApraÅ¡as + field_summary: Santrauka + field_is_required: Reikalaujama + field_firstname: Vardas + field_lastname: PavardÄ— + field_mail: El. paÅ¡tas + field_filename: Failas + field_filesize: Dydis + field_downloads: Atsiuntimai + field_author: Autorius + field_created_on: Sukurta + field_updated_on: Atnaujintas(a) + field_field_format: Formatas + field_is_for_all: Visiems projektams + field_possible_values: Galimos reikÅ¡mÄ—s + field_regexp: Pastovi iÅ¡raiÅ¡ka + field_min_length: Minimalus ilgis + field_max_length: Maksimalus ilgis + field_value: VertÄ— + field_category: Kategorija + field_title: Pavadinimas + field_project: Projektas + field_issue: Darbas + field_status: BÅ«sena + field_notes: Pastabos + field_is_closed: Darbas uždarytas + field_is_default: Numatytoji vertÄ— + field_tracker: PÄ—dsekys + field_subject: Tema + field_due_date: Užbaigimo data + field_assigned_to: Paskirtas + field_priority: Prioritetas + field_fixed_version: TikslinÄ— versija + field_user: Vartotojas + field_principal: Vardas + field_role: Vaidmuo + field_homepage: Pagrindinis puslapis + field_is_public: VieÅ¡as + field_parent: Priklauso projektui + field_is_in_roadmap: Darbai rodomi veiklos grafike + field_login: Registracijos vardas + field_mail_notification: Elektroninio paÅ¡to praneÅ¡imai + field_admin: Administratorius + field_last_login_on: Paskutinis prisijungimas + field_language: Kalba + field_effective_date: Data + field_password: Slaptažodis + field_new_password: Naujas slaptažodis + field_password_confirmation: Patvirtinimas + field_version: Versija + field_type: Tipas + field_host: Pagrindinis kompiuteris + field_port: Prievadas + field_account: Paskyra + field_base_dn: Bazinis skiriamasis vardas (base DN) + field_attr_login: Registracijos vardo požymis (login) + field_attr_firstname: Vardo požymis + field_attr_lastname: PavardÄ—s požymis + field_attr_mail: Elektroninio paÅ¡to požymis + field_onthefly: Automatinis vartotojų registravimas + field_start_date: PradÄ—ti + field_done_ratio: "% atlikta" + field_auth_source: AutentiÅ¡kumo nustatymo bÅ«das + field_hide_mail: SlÄ—pti mano elektroninio paÅ¡to adresÄ… + field_comments: Komentaras + field_url: URL + field_start_page: Pradžios puslapis + field_subproject: Sub-projektas + field_hours: valandos + field_activity: Veikla + field_spent_on: Data + field_identifier: Identifikatorius + field_is_filter: Panaudotas kaip filtras + field_issue_to: SusijÄ™s darbas + field_delay: Užlaikymas + field_assignable: Darbai gali bÅ«ti paskirti Å¡iam vaidmeniui + field_redirect_existing_links: Peradresuokite egzistuojanÄias sÄ…sajas + field_estimated_hours: Numatyta trukmÄ— + field_column_names: Skiltys + field_time_entries: Praleistas laikas + field_time_zone: Laiko juosta + field_searchable: Randamas + field_default_value: Numatytoji vertÄ— + field_comments_sorting: Rodyti komentarus + field_parent_title: AukÅ¡tesnio lygio puslapis + field_editable: Redaguojamas + field_watcher: StebÄ—tojas + field_identity_url: OpenID URL + field_content: Turinys + field_group_by: Sugrupuoti pagal + field_sharing: Dalijimasis + field_parent_issue: PagrindinÄ— užduotis + field_member_of_group: "Priskirtojo grupÄ—" + field_assigned_to_role: "Priskirtojo rolÄ—" + field_text: Teksto laukas + field_visible: Matomas + field_warn_on_leaving_unsaved: "Ä®spÄ—ti mane, kai paliekamas puslapis su neiÅ¡saugotu tekstu" + field_issues_visibility: Darbų matomumas + field_is_private: Privatus + field_commit_logs_encoding: Commit žurnalų koduotÄ— + field_scm_path_encoding: SCM kelio koduotÄ— + field_path_to_repository: Saugyklos kelias + field_root_directory: Å akninis katalogas + field_cvsroot: CVSROOT + field_cvs_module: Modulis + + setting_app_title: Programos pavadinimas + setting_app_subtitle: Programos paantraÅ¡tÄ— + setting_welcome_text: Pasveikinimas + setting_default_language: Numatytoji kalba + setting_login_required: Reikalingas autentiÅ¡kumo nustatymas + setting_self_registration: Savi-registracija + setting_attachment_max_size: Priedo maksimalus dydis + setting_issues_export_limit: Darbų eksportavimo riba + setting_mail_from: IÅ¡leidimo elektroninio paÅ¡to adresas + setting_bcc_recipients: Akli tikslios kopijos gavÄ—jai (bcc) + setting_plain_text_mail: Tik tekstas (be HTML) + setting_host_name: Pagrindinio kompiuterio vardas + setting_text_formatting: Teksto formatavimas + setting_wiki_compression: Wiki istorijos suspaudimas + setting_feeds_limit: Perdavimo turinio maksimali riba + setting_default_projects_public: Nauji projektai vieÅ¡i pagal nutylÄ—jimÄ… + setting_autofetch_changesets: Automatinis pakeitimų siuntimas + setting_sys_api_enabled: Ä®galinti WS sandÄ—lio valdymui + setting_commit_ref_keywords: Nurodymo reikÅ¡miniai žodžiai + setting_commit_fix_keywords: Fiksavimo reikÅ¡miniai žodžiai + setting_autologin: Automatinis prisijungimas + setting_date_format: Datos formatas + setting_time_format: Laiko formatas + setting_cross_project_issue_relations: Leisti tarp-projektinius darbų ryÅ¡ius + setting_issue_list_default_columns: Numatytosios skiltys darbų sÄ…raÅ¡e + setting_repositories_encodings: PridÄ—tų failų ir saugyklų Å¡ifravimas + setting_emails_header: LaiÅ¡ko antraÅ¡tÄ— + setting_emails_footer: LaiÅ¡ko paraÅ¡tÄ— + setting_protocol: Protokolas + setting_per_page_options: Ä®rašų puslapyje nustatymas + setting_user_format: Vartotojo atvaizdavimo formatas + setting_activity_days_default: Atvaizduojamos dienos projekto veikloje + setting_display_subprojects_issues: Pagal nutylÄ—jimÄ… rodyti sub-projektų darbus pagrindiniame projekte + setting_enabled_scm: Ä®galinti SCM + setting_mail_handler_body_delimiters: "Trumpinti laiÅ¡kus po vienos iÅ¡ Å¡ių eiluÄių" + setting_mail_handler_api_enabled: Ä®galinti WS įeinantiems laiÅ¡kams + setting_mail_handler_api_key: API raktas + setting_sequential_project_identifiers: Generuoti nuoseklius projekto identifikatorius + setting_gravatar_enabled: Naudoti Gravatar vartotojo paveiksliukus + setting_gravatar_default: Gravatar paveiksliukas pagal nutylÄ—jimÄ… + setting_diff_max_lines_displayed: Maksimalus rodomas pakeitimų eiluÄių skaiÄius + setting_file_max_size_displayed: Maksimalus testinių failų dydis rodomas vienoje eilutÄ—je + setting_repository_log_display_limit: Maksimalus revizijų skaiÄius rodomas žurnale + setting_openid: Leisti OpenID prisijungimÄ… ir registracijÄ… + setting_password_min_length: Minimalus slaptažodžio ilgis + setting_new_project_user_role_id: Vartotojo vaidmuo, suteikiamas ne administratoriui, kuris sukuria projektÄ… + setting_default_projects_modules: Pagal nutylÄ—jimÄ… naujam projektui priskirti moduliai + setting_issue_done_ratio: DArbo įvykdymo progresÄ… skaiÄiuoti su + setting_issue_done_ratio_issue_field: Naudoti darbo laukÄ… + setting_issue_done_ratio_issue_status: Naudoti darbo statusÄ… + setting_start_of_week: SavaitÄ—s pradžios diena + setting_rest_api_enabled: Ä®jungti REST tinklo servisÄ… + setting_cache_formatted_text: PaslÄ—pti formatuotÄ… tekstÄ… + setting_default_notification_option: Numatytosios praneÅ¡imų nuostatos + setting_commit_logtime_enabled: Ä®jungti laiko registravimÄ… + setting_commit_logtime_activity_id: Laiko įrašų veikla + setting_gantt_items_limit: Maksimalus rodmenų skaiÄius rodomas Gantt'o grafike + setting_issue_group_assignment: Leisti darbo priskirimÄ… grupÄ—ms + setting_default_issue_start_date_to_creation_date: Naudoti dabartinÄ™ datÄ… kaip naujų darbų pradžios datÄ… + + permission_add_project: Sukurti projektÄ… + permission_add_subprojects: Kurti sub-projektus + permission_edit_project: Taisyti projektÄ… + permission_select_project_modules: Parinkti projekto modulius + permission_manage_members: Valdyti narius + permission_manage_project_activities: Valdyti projekto veiklas + permission_manage_versions: Valdyti versijas + permission_manage_categories: Valdyti darbų kategorijas + permission_view_issues: UžduoÄių peržiÅ«ra + permission_add_issues: Sukurti darbus + permission_edit_issues: Redaguoti darbus + permission_manage_issue_relations: Valdyti darbų ryÅ¡ius + permission_set_issues_private: Nustatyti darbÄ… vieÅ¡u ar privaÄiu + permission_set_own_issues_private: Nustatyti savo darbus vieÅ¡ais ar privaÄiais + permission_add_issue_notes: Sukurti pastabas + permission_edit_issue_notes: Redaguoti pastabas + permission_edit_own_issue_notes: Redaguoti savo pastabas + permission_move_issues: Perkelti darbus + permission_delete_issues: PaÅ¡alinti darbus + permission_manage_public_queries: Valdyti vieÅ¡as užklausas + permission_save_queries: IÅ¡saugoti užklausas + permission_view_gantt: Matyti Gantt grafikÄ… + permission_view_calendar: Matyti kalendorių + permission_view_issue_watchers: Matyti stebÄ—tojų sÄ…rašą + permission_add_issue_watchers: PridÄ—ti stebÄ—tojus + permission_delete_issue_watchers: PaÅ¡alinti stebÄ—tojus + permission_log_time: Regsitruoti dirbtÄ… laikÄ… + permission_view_time_entries: Matyti dirbtÄ… laikÄ… + permission_edit_time_entries: Redaguoti laiko įraÅ¡us + permission_edit_own_time_entries: Redaguoti savo laiko įraÅ¡us + permission_manage_news: Valdyti naujienas + permission_comment_news: Komentuoti naujienas + permission_view_documents: Matyti dokumentus + permission_manage_files: Valdyti failus + permission_view_files: Matyti failus + permission_manage_wiki: Valdyti wiki + permission_rename_wiki_pages: Pervadinti wiki puslapius + permission_delete_wiki_pages: PaÅ¡alinti wiki puslapius + permission_view_wiki_pages: Matyti wiki + permission_view_wiki_edits: Matyti wiki istorijÄ… + permission_edit_wiki_pages: Redaguoti wiki puslapius + permission_delete_wiki_pages_attachments: PaÅ¡alinti priedus + permission_protect_wiki_pages: Apsaugoti wiki puslapius + permission_manage_repository: Valdyti saugyklÄ… + permission_browse_repository: PeržiÅ«rÄ—ti saugyklÄ… + permission_view_changesets: Matyti pakeitimus + permission_commit_access: Prieiga prie pakeitimų + permission_manage_boards: Valdyti forumus + permission_view_messages: Matyti praneÅ¡imus + permission_add_messages: Skelbti praneÅ¡imus + permission_edit_messages: Redaguoti praneÅ¡imus + permission_edit_own_messages: Redaguoti savo praneÅ¡imus + permission_delete_messages: PaÅ¡alinti praneÅ¡imus + permission_delete_own_messages: PaÅ¡alinti savo praneÅ¡imus + permission_export_wiki_pages: Eksportuoti wiki puslapius + permission_manage_subtasks: Valdyti darbo dalis + + project_module_issue_tracking: Darbų pÄ—dsekys + project_module_time_tracking: Laiko pÄ—dsekys + project_module_news: Naujienos + project_module_documents: Dokumentai + project_module_files: Failai + project_module_wiki: Wiki + project_module_repository: Saugykla + project_module_boards: Forumai + project_module_calendar: Kalendorius + project_module_gantt: Gantt + + label_user: Vartotojas + label_user_plural: Vartotojai + label_user_new: Naujas vartotojas + label_user_anonymous: Anonimas + label_project: Projektas + label_project_new: Naujas projektas + label_project_plural: Projektai + label_x_projects: + zero: nÄ—ra projektų + one: 1 projektas + other: "%{count} projektų" + label_project_all: Visi Projektai + label_project_latest: Naujausi projektai + label_issue: Darbas + label_issue_new: Naujas darbas + label_issue_plural: Darbai + label_issue_view_all: PeržiÅ«rÄ—ti visus darbus + label_issues_by: "Darbai pagal %{value}" + label_issue_added: Darbas pridÄ—tas + label_issue_updated: Darbas atnaujintas + label_issue_note_added: Pastaba pridÄ—ta + label_issue_status_updated: Statusas atnaujintas + label_issue_priority_updated: Prioritetas atnaujintas + label_document: Dokumentas + label_document_new: Naujas dokumentas + label_document_plural: Dokumentai + label_document_added: Dokumentas pridÄ—tas + label_role: Vaidmuo + label_role_plural: Vaidmenys + label_role_new: Naujas vaidmuo + label_role_and_permissions: Vaidmenys ir leidimai + label_role_anonymous: Anonimas + label_role_non_member: NÄ—ra narys + label_member: Narys + label_member_new: Naujas narys + label_member_plural: Nariai + label_tracker: PÄ—dsekys + label_tracker_plural: PÄ—dsekiai + label_tracker_new: Naujas pÄ—dsekys + label_workflow: Darbų eiga + label_issue_status: Darbo bÅ«sena + label_issue_status_plural: Darbų bÅ«senos + label_issue_status_new: Nauja bÅ«sena + label_issue_category: Darbo kategorija + label_issue_category_plural: Darbo kategorijos + label_issue_category_new: Nauja kategorija + label_custom_field: Kliento laukas + label_custom_field_plural: Kliento laukai + label_custom_field_new: Naujas kliento laukas + label_enumerations: IÅ¡vardinimai + label_enumeration_new: Nauja vertÄ— + label_information: Informacija + label_information_plural: Informacija + label_please_login: PraÅ¡om prisijungti + label_register: Užsiregistruoti + label_login_with_open_id_option: arba prisijunkite su OpenID + label_password_lost: Prarastas slaptažodis + label_home: Pagrindinis + label_my_page: Mano puslapis + label_my_account: Mano paskyra + label_my_projects: Mano projektai + label_my_page_block: Mano puslapio blokas + label_administration: Administravimas + label_login: Prisijungti + label_logout: Atsijungti + label_help: Pagalba + label_reported_issues: PraneÅ¡ti darbai + label_assigned_to_me_issues: Darbai, priskirti man + label_last_login: Paskutinis prisijungimas + label_registered_on: Užregistruota + label_activity: Veikla + label_overall_activity: Visa veikla + label_user_activity: "%{value} veikla" + label_new: Naujas + label_logged_as: PrisijungÄ™s kaip + label_environment: Aplinka + label_authentication: AutentiÅ¡kumo nustatymas + label_auth_source: AutentiÅ¡kumo nustatymo bÅ«das + label_auth_source_new: Naujas autentiÅ¡kumo nustatymo bÅ«das + label_auth_source_plural: AutentiÅ¡kumo nustatymo bÅ«dai + label_subproject_plural: Sub-projektai + label_subproject_new: Naujas sub-projektas + label_and_its_subprojects: "%{value} projektas ir jo sub-projektai" + label_min_max_length: Min - Maks ilgis + label_list: SÄ…raÅ¡as + label_date: Data + label_integer: Sveikasis skaiÄius + label_float: Slankiojo kablelio skaiÄius + label_boolean: Loginis + label_string: Tekstas + label_text: Ilgas tekstas + label_attribute: Požymis + label_attribute_plural: Požymiai + label_no_data: NÄ—ra kÄ… atvaizduoti + label_change_status: Pakeitimo bÅ«sena + label_history: Istorija + label_attachment: Failas + label_attachment_new: Naujas failas + label_attachment_delete: PaÅ¡alinkite failÄ… + label_attachment_plural: Failai + label_file_added: Failas pridÄ—tas + label_report: Ataskaita + label_report_plural: Ataskaitos + label_news: Naujiena + label_news_new: PridÄ—ti naujienas + label_news_plural: Naujienos + label_news_latest: PaskutinÄ—s naujienos + label_news_view_all: PeržiÅ«rÄ—ti visas naujienas + label_news_added: Naujiena pridÄ—ta + label_news_comment_added: Prie naujienos pridÄ—tas komentaras + label_settings: Nustatymai + label_overview: Apžvalga + label_version: Versija + label_version_new: Nauja versija + label_version_plural: Versijos + label_close_versions: Uždaryti užbaigtas versijas + label_confirmation: Patvirtinimas + label_export_to: 'Eksportuoti į:' + label_read: Skaitykite... + label_public_projects: VieÅ¡i projektai + label_open_issues: atidaryta + label_open_issues_plural: atidaryti + label_closed_issues: uždaryta + label_closed_issues_plural: uždaryti + label_x_open_issues_abbr_on_total: + zero: 0 atvirų / %{total} + one: 1 atviras / %{total} + other: "%{count} atviri / %{total}" + label_x_open_issues_abbr: + zero: 0 atvirų + one: 1 atviras + other: "%{count} atviri" + label_x_closed_issues_abbr: + zero: 0 uždarytų + one: 1 uždarytas + other: "%{count} uždarytų" + label_total: IÅ¡ viso + label_permissions: Leidimai + label_current_status: DabartinÄ— bÅ«sena + label_new_statuses_allowed: Naujos bÅ«senos galimos + label_all: visi(os) + label_none: joks + label_nobody: niekas + label_next: Kitas + label_previous: Ankstesnis + label_used_by: Naudotas + label_details: DetalÄ—s + label_add_note: PridÄ—kite pastabÄ… + label_per_page: Puslapyje + label_calendar: Kalendorius + label_months_from: mÄ—nesiai nuo + label_gantt: Gantt + label_internal: Vidinis + label_last_changes: "paskutiniai %{count} pokyÄiai(-ių)" + label_change_view_all: PeržiÅ«rÄ—ti visus pakeitimus + label_personalize_page: Suasmeninti šį puslapį + label_comment: Komentaras + label_comment_plural: Komentarai + label_x_comments: + zero: nÄ—ra komentarų + one: 1 komentaras + other: "%{count} komentarų" + label_comment_add: PridÄ—kite komentarÄ… + label_comment_added: Komentaras pridÄ—tas + label_comment_delete: PaÅ¡alinti komentarus + label_query: Užklausa + label_query_plural: Užklausos + label_query_new: Nauja užklausa + label_my_queries: Mano sukurtos užklausos + label_filter_add: PridÄ—ti filtrÄ… + label_filter_plural: Filtrai + label_equals: yra + label_not_equals: nÄ—ra + label_in_less_than: anksÄiau nei po + label_in_more_than: vÄ—liau nei po + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_between: tarp + label_in: per + label_today: Å¡iandien + label_all_time: visas laikas + label_yesterday: vakar + label_this_week: Å¡iÄ… savaitÄ™ + label_last_week: praeita savaitÄ— + label_last_n_days: "paskutinių %{count} dienų" + label_this_month: Å¡is mÄ—nuo + label_last_month: praeitas mÄ—nuo + label_this_year: Å¡iemet + label_date_range: Dienų diapazonas + label_less_than_ago: vÄ—liau nei prieÅ¡ + label_more_than_ago: anksÄiau nei prieÅ¡ + label_ago: prieÅ¡ + label_contains: turi + label_not_contains: neturi + label_day_plural: dienų(-os) + label_repository: Saugykla + label_repository_plural: Saugyklos + label_browse: NarÅ¡yti + label_branch: Å aka + label_tag: Tag + label_revision: Revizija + label_revision_plural: Revizijos + label_revision_id: Revizija %{value} + label_associated_revisions: Susijusios revizijos + label_added: pridÄ—tas + label_modified: pakeistas + label_copied: nukopijuotas + label_renamed: pervardintas + label_deleted: paÅ¡alintas + label_latest_revision: PaskutinÄ— revizija + label_latest_revision_plural: PaskutinÄ—s revizijos + label_view_revisions: PeržiÅ«rÄ—ti revizijas + label_view_all_revisions: PeržiÅ«rÄ—ti visas revizijas + label_max_size: Maksimalus dydis + label_sort_highest: Perkelti į viršūnÄ™ + label_sort_higher: Perkelti į viršų + label_sort_lower: Perkelti žemyn + label_sort_lowest: Perkelti į apaÄiÄ… + label_roadmap: Veiklos grafikas + label_roadmap_due_in: "Baigiasi po %{value}" + label_roadmap_overdue: "%{value} vÄ—luojama" + label_roadmap_no_issues: Å iai versijai nepriskirtas koks darbas + label_search: IeÅ¡koti + label_result_plural: Rezultatai + label_all_words: Visi žodžiai + label_wiki: Wiki + label_wiki_edit: Wiki redakcija + label_wiki_edit_plural: Wiki redakcijos + label_wiki_page: Wiki puslapis + label_wiki_page_plural: Wiki puslapiai + label_index_by_title: Indeksuoti pagal pavadinimÄ… + label_index_by_date: Indeksuoti pagal datÄ… + label_current_version: Einamoji versija + label_preview: PeržiÅ«ra + label_feed_plural: Kanalai + label_changes_details: Visų pakeitimų detalÄ—s + label_issue_tracking: Darbų sekimas + label_spent_time: Dirbtas laikas + label_overall_spent_time: Visas dirbtas laikas + label_f_hour: "%{value} valanda" + label_f_hour_plural: "%{value} valandų(-os)" + label_time_tracking: Laiko sekimas + label_change_plural: Pakeitimai + label_statistics: Statistika + label_commits_per_month: Ä®kÄ—limai per mÄ—nesį + label_commits_per_author: Ä®kÄ—limai pagal autorių + label_diff: skirt + label_view_diff: Skirtumų peržiÅ«ra + label_diff_inline: įterptas + label_diff_side_by_side: Å¡alia + label_options: Pasirinkimai + label_copy_workflow_from: Kopijuoti darbų eiga iÅ¡ + label_permissions_report: Leidimų praneÅ¡imas + label_watched_issues: Stebimi darbai + label_related_issues: SusijÄ™ darbai + label_applied_status: Taikomoji bÅ«sena + label_loading: Kraunama... + label_relation_new: Naujas ryÅ¡ys + label_relation_delete: PaÅ¡alinti ryšį + label_relates_to: susietas su + label_duplicates: dubliuoja + label_duplicated_by: dubliuojasi su + label_blocks: blokuoja + label_blocked_by: blokuojamas + label_precedes: ankstesnÄ—(-is) + label_follows: seka + label_end_to_start: užbaigti, kad pradÄ—ti + label_end_to_end: užbaigti, kad pabaigti + label_start_to_start: pradÄ—kite pradÄ—ti + label_start_to_end: pradÄ—kite užbaigti + label_stay_logged_in: Likti prisijungus + label_disabled: iÅ¡jungta(-as) + label_show_completed_versions: Rodyti užbaigtas versijas + label_me: aÅ¡ + label_board: Forumas + label_board_new: Naujas forumas + label_board_plural: Forumai + label_board_locked: Užrakinta + label_board_sticky: Lipnus + label_topic_plural: Temos + label_message_plural: PraneÅ¡imai + label_message_last: Paskutinis praneÅ¡imas + label_message_new: Naujas praneÅ¡imas + label_message_posted: PraneÅ¡imas pridÄ—tas + label_reply_plural: Atsakymai + label_send_information: Nusiųsti paskyros informacijÄ… vartotojui + label_year: Metai + label_month: MÄ—nuo + label_week: SavaitÄ— + label_date_from: Nuo + label_date_to: Iki + label_language_based: Pagrįsta vartotojo kalba + label_sort_by: "Rūšiuoti pagal %{value}" + label_send_test_email: Nusiųsti bandomÄ…jį laiÅ¡kÄ… + label_feeds_access_key: Atom prieigos raktas + label_missing_feeds_access_key: TrÅ«ksta Atom prieigos rakto + label_feeds_access_key_created_on: "Atom prieigos raktas sukurtas prieÅ¡ %{value}" + label_module_plural: Moduliai + label_added_time_by: "PridÄ—jo %{author} prieÅ¡ %{age}" + label_updated_time_by: "Atnaujino %{author} prieÅ¡ %{age}" + label_updated_time: "Atnaujinta prieÅ¡ %{value}" + label_jump_to_a_project: Å uolis į projektÄ…... + label_file_plural: Failai + label_changeset_plural: Pakeitimų rinkiniai + label_default_columns: Numatytieji stulpeliai + label_no_change_option: (Jokio pakeitimo) + label_bulk_edit_selected_issues: MasiÅ¡kai redaguoti pasirinktus darbus + label_bulk_edit_selected_time_entries: MasiÅ¡kai redaguotumÄ—te pasirinktus laiko įraÅ¡us + label_theme: Tema + label_default: Numatyta(-as) + label_search_titles_only: IeÅ¡koti tiktai pavadinimų + label_user_mail_option_all: "Bet kokiam įvykiui visuose mano projektuose" + label_user_mail_option_selected: "Bet kokiam įvykiui tiktai pasirinktuose projektuose ..." + label_user_mail_option_none: "NÄ—ra įvykių" + label_user_mail_option_only_my_events: "Tiktai įvykiai, kuriuos stebiu arba esu įtrauktas" + label_user_mail_option_only_assigned: "Tiktai įvykiai, kuriems esu priskirtas" + label_user_mail_option_only_owner: "Tiktai įvykiai, kurių Å¡eimininkas esu aÅ¡" + label_user_mail_no_self_notified: "Nenoriu bÅ«ti informuotas apie pakeitimus, kuriuos pats atlieku" + label_registration_activation_by_email: paskyros aktyvacija per e-paÅ¡tÄ… + label_registration_manual_activation: rankinÄ— paskyros aktyvacija + label_registration_automatic_activation: automatinÄ— paskyros aktyvacija + label_display_per_page: "%{value} įrašų puslapyje" + label_age: Amžius + label_change_properties: Pakeisti nustatymus + label_general: Bendri(-as) + label_more: Daugiau + label_scm: SCM + label_plugins: Ä®skiepiai + label_ldap_authentication: LDAP autentifikacija + label_downloads_abbr: siunt. + label_optional_description: ApibÅ«dinimas (laisvai pasirenkamas) + label_add_another_file: PridÄ—ti kitÄ… failÄ… + label_preferences: SavybÄ—s + label_chronological_order: Chronologine tvarka + label_reverse_chronological_order: Atbuline chronologine tvarka + label_planning: Planavimas + label_incoming_emails: Ä®einantys laiÅ¡kai + label_generate_key: Generuoti raktÄ… + label_issue_watchers: StebÄ—tojai + label_example: Pavyzdys + label_display: Demonstruoti + label_sort: Rūšiuoti + label_ascending: DidÄ—jantis + label_descending: Mažėjantis + label_date_from_to: Nuo %{start} iki %{end} + label_wiki_content_added: Wiki puslapis pridÄ—tas + label_wiki_content_updated: Wiki puslapis atnaujintas + label_group: GrupÄ— + label_group_plural: GrupÄ—s + label_group_new: Nauja grupÄ— + label_time_entry_plural: Sprendimo laikas + label_version_sharing_none: Nesidalinama + label_version_sharing_descendants: Su sub-projektais + label_version_sharing_hierarchy: Su projekto hierarchija + label_version_sharing_tree: Su projekto medžiu + label_version_sharing_system: Su visais projektais + label_update_issue_done_ratios: Atnaujinti darbo atlikimo progresÄ… + label_copy_source: Å altinis + label_copy_target: Tikslas + label_copy_same_as_target: Toks pat kaip tikslas + label_display_used_statuses_only: Rodyti tik tuos statusus, kurie naudojami Å¡io pÄ—dsekio + label_api_access_key: API prieigos raktas + label_missing_api_access_key: TrÅ«ksta API prieigos rakto + label_api_access_key_created_on: "API prieigos raktas sukurtas prieÅ¡ %{value}" + label_profile: Profilis + label_subtask_plural: Darbo dalys + label_project_copy_notifications: Siųsti paÅ¡to praneÅ¡imus kopijuojant projektÄ… + label_principal_search: "IeÅ¡koti vartotojo arba grupÄ—s:" + label_user_search: "IeÅ¡koti vartotojo:" + label_issues_visibility_all: Visi darbai + label_issues_visibility_public: Visi vieÅ¡i darbai + label_issues_visibility_own: Darbai, sukurti vartotojo arba jam priskirti + label_git_report_last_commit: Nurodyti paskutinį failų ir katalogų pakeitimÄ… + label_parent_revision: PirminÄ— revizija + label_child_revision: Sekanti revizija + label_export_options: "%{export_format} eksportavimo nustatymai" + + button_login: Registruotis + button_submit: Pateikti + button_save: IÅ¡saugoti + button_check_all: ŽymÄ—ti visus + button_uncheck_all: AtžymÄ—ti visus + button_collapse_all: Sutraukti visus + button_expand_all: IÅ¡skleisti visus + button_delete: PaÅ¡alinti + button_create: Sukurti + button_create_and_continue: Sukurti ir tÄ™sti + button_test: Testas + button_edit: Redaguoti + button_edit_associated_wikipage: "Redaguoti susijusį Wiki puslapį: %{page_title}" + button_add: PridÄ—ti + button_change: Keisti + button_apply: Pritaikyti + button_clear: IÅ¡valyti + button_lock: Rakinti + button_unlock: Atrakinti + button_download: Atsisiųsti + button_list: SÄ…raÅ¡as + button_view: ŽiÅ«rÄ—ti + button_move: Perkelti + button_move_and_follow: Perkelti ir sekti + button_back: Atgal + button_cancel: AtÅ¡aukti + button_activate: Aktyvinti + button_sort: Rūšiuoti + button_log_time: Registruoti laikÄ… + button_rollback: Grįžti į Å¡iÄ… versijÄ… + button_watch: StebÄ—ti + button_unwatch: NestebÄ—ti + button_reply: Atsakyti + button_archive: Archyvuoti + button_unarchive: IÅ¡pakuoti + button_reset: Atstatyti + button_rename: Pervadinti + button_change_password: Pakeisti slaptažodį + button_copy: Kopijuoti + button_copy_and_follow: Kopijuoti ir laikytis + button_annotate: RaÅ¡yti pastabÄ… + button_update: Atnaujinti + button_configure: KonfigÅ«ruoti + button_quote: Cituoti + button_duplicate: Dubliuoti + button_show: Rodyti + button_edit_section: Redaguoti šį skirsnį + button_export: Eksportuoti + + status_active: aktyvus + status_registered: užregistruotas + status_locked: užrakintas + + version_status_open: atidaryta + version_status_locked: užrakinta + version_status_closed: uždaryta + + field_active: Aktyvus + + text_select_mail_notifications: IÅ¡rinkite veiksmus, apie kuriuos bÅ«tų praneÅ¡ta elektroniniu paÅ¡tu. + text_regexp_info: pvz. ^[A-Z0-9]+$ + text_min_max_length_info: 0 reiÅ¡kia jokių apribojimų + text_project_destroy_confirmation: Ar esate įsitikinÄ™s, kad norite paÅ¡alinti šį projektÄ… ir visus susijusius duomenis? + text_subprojects_destroy_warning: "Å is(-ie) sub-projektas(-ai): %{value} taip pat bus iÅ¡trintas(-i)." + text_workflow_edit: IÅ¡rinkite vaidmenį ir pÄ—dsekį, kad redaguotumÄ—te darbų eigÄ… + text_are_you_sure: Ar esate įsitikinÄ™s? + text_journal_changed: "%{label} pakeistas(a) iÅ¡ %{old} į %{new}" + text_journal_changed_no_detail: "%{label} atnaujintas(a)" + text_journal_set_to: "%{label} nustatytas(a) į %{value}" + text_journal_deleted: "%{label} iÅ¡trintas(a) (%{old})" + text_journal_added: "%{label} pridÄ—tas(a) %{value}" + text_tip_issue_begin_day: užduotis, prasidedanti Å¡iÄ… dienÄ… + text_tip_issue_end_day: užduotis, pasibaigianti Å¡iÄ… dienÄ… + text_tip_issue_begin_end_day: užduotis, prasidedanti ir pasibaigianti Å¡iÄ… dienÄ… + text_project_identifier_info: 'Mažosios raidÄ—s (a-z), skaiÄiai ir brÅ«kÅ¡niai galimi.
    IÅ¡saugojus, identifikatorius negali bÅ«ti keiÄiamas.' + text_caracters_maximum: "%{count} simbolių maksimumas." + text_caracters_minimum: "Turi bÅ«ti mažiausiai %{count} simbolių ilgio." + text_length_between: "Ilgis tarp %{min} ir %{max} simbolių." + text_tracker_no_workflow: Jokia darbų eiga neapibrėžta Å¡iam pÄ—dsekiui + text_unallowed_characters: Neleistini simboliai + text_comma_separated: Leistinos kelios reikÅ¡mÄ—s (atskirtos kableliu). + text_line_separated: Galimos kelios reikÅ¡mÄ—s (viena linija vienai vertei). + text_issues_ref_in_commit_messages: Darbų susiejimas ir fiksavimas pavedimų žinutÄ—se + text_issue_added: "Darbas %{id} buvo praneÅ¡tas (by %{author})." + text_issue_updated: "Darbas %{id} buvo atnaujintas (by %{author})." + text_wiki_destroy_confirmation: Ar esate įsitikinÄ™s, kad norite paÅ¡alinti šį wiki puslapį ir visÄ… jo turinį? + text_issue_category_destroy_question: "Kai kurie darbai (%{count}) yra paskirti Å¡iai kategorijai. KÄ… jÅ«s norite daryti?" + text_issue_category_destroy_assignments: PaÅ¡alinti kategorijos užduotis + text_issue_category_reassign_to: IÅ¡ naujo priskirti darbus Å¡iai kategorijai + text_user_mail_option: "NeiÅ¡rinktiems projektams, jÅ«s gausite tiktai praneÅ¡imus apie tuos įvykius, kuriuos jÅ«s stebite, arba į kuriuos esate įtrauktas (pvz. darbai, kurių autorius jÅ«s esate ar esate priskirtas)." + text_no_configuration_data: "Vaidmenys, pÄ—dsekiai, darbų bÅ«senos ir darbų eiga dar nebuvo konfigÅ«ruoti.\nGriežtai rekomenduojam užkrauti numatytÄ…jÄ… (default) konfigÅ«racijÄ…. Užkrovus, galÄ—site jÄ… modifikuoti." + text_load_default_configuration: Užkrauti numatytÄ…jÄ… konfigÅ«racijÄ… + text_status_changed_by_changeset: "Pakeista %{value} revizijoje." + text_time_logged_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Ar jÅ«s tikrai norite sunaikinti pažymÄ—tÄ…(-us) darbÄ…(-us)?' + text_issues_destroy_descendants_confirmation: Taip pat bus iÅ¡trinta(-os) %{count} darbo dalis(ys). + text_time_entries_destroy_confirmation: 'Ar jÅ«s tikrai norite iÅ¡trinti pasirinktÄ…(-us) laiko įrašą(-us)?' + text_select_project_modules: 'Parinkite modulius, kuriuos norite naudoti Å¡iame projekte:' + text_default_administrator_account_changed: Administratoriaus numatytoji paskyra pakeista + text_file_repository_writable: Ä® failų saugyklÄ… saugoti galima (RW) + text_plugin_assets_writable: Ä® įskiepių apraÅ¡o punktų katalogÄ… įraÅ¡yti galima + text_rmagick_available: RMagick pasiekiamas (pasirinktinai) + text_destroy_time_entries_question: "Naikinamam darbui priskirta %{hours} valandų. KÄ… norite su jomis daryti?" + text_destroy_time_entries: IÅ¡trinti įraÅ¡ytas valandas + text_assign_time_entries_to_project: Priskirti įraÅ¡ytas valandas prie projekto + text_reassign_time_entries: 'Priskirti įraÅ¡ytas valandas Å¡iam darbui:' + text_user_wrote: "%{value} parašė:" + text_enumeration_destroy_question: "%{count} objektai(ų) priskirti Å¡iai reikÅ¡mei." + text_enumeration_category_reassign_to: 'Priskirti juos Å¡iai reikÅ¡mei:' + text_email_delivery_not_configured: "El.paÅ¡to siuntimas nesukonfigÅ«ruotas, ir perspÄ—jimai neaktyvus.\nSukonfigÅ«ruokite savo SMTP serverį byloje config/configuration.yml ir perleiskite programÄ… norÄ—dami pritaikyti pakeitimus." + text_repository_usernames_mapping: "Parinkite ar atnaujinkite Redmine vartotojÄ…, kuris paminÄ—tas saugyklos žurnale.\nVartotojai, turintys tÄ… patį Redmine ir saugyklos vardÄ… ar el. paÅ¡tÄ… yra automatiÅ¡kai suriÅ¡ti." + text_diff_truncated: "... Å is diff'as nukarpytas, nes jis virÅ¡ijo maksimalų rodomų eiluÄių skaiÄių." + text_custom_field_possible_values_info: 'Po vienÄ… eilutÄ™ kiekvienai reikÅ¡mei' + text_wiki_page_destroy_question: "Å is puslapis turi %{descendants} susijusių arba iÅ¡vestinių puslapių. KÄ… norÄ—tumÄ—te daryti?" + text_wiki_page_nullify_children: Laikyti susijusius puslapius kaip pagrindinius puslapius + text_wiki_page_destroy_children: "PaÅ¡alinti susijusius puslapius ir jų palikuonis" + text_wiki_page_reassign_children: "Priskirkite iÅ¡ naujo 'susijusius' puslapius Å¡iam pagrindiniam puslapiui" + text_own_membership_delete_confirmation: "JÅ«s esate pasiruošęs panaikinti dalį arba visus leidimus ir po Å¡io pakeitimo galite prarasti Å¡io projekto redagavimo galimybÄ™.\nAr jÅ«s esate įsitikinÄ™s ir tÄ™sti?" + text_zoom_in: Priartinti + text_zoom_out: Nutolinti + text_warn_on_leaving_unsaved: "Dabartinis puslapis turi neiÅ¡saugoto teksto, kuris bus prarastas, jeigu paliksite šį puslapį." + text_scm_path_encoding_note: "Numatytasis: UTF-8" + text_git_repository_note: Saugykla (repository) yra plika ir vietinÄ— (pvz. /gitrepo, c:\gitrepo) + text_mercurial_repository_note: VietinÄ— saugykla (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Komanda + text_scm_command_version: Versija + + default_role_manager: Vadovas + default_role_developer: Projektuotojas + default_role_reporter: Pranešėjas + default_tracker_bug: Klaida + default_tracker_feature: YpatybÄ— + default_tracker_support: Palaikymas + default_issue_status_new: Naujas + default_issue_status_in_progress: Vykdomas + default_issue_status_resolved: IÅ¡sprÄ™stas + default_issue_status_feedback: Grįžtamasis ryÅ¡ys + default_issue_status_closed: Uždarytas + default_issue_status_rejected: Atmestas + default_doc_category_user: Vartotojo dokumentacija + default_doc_category_tech: TechninÄ— dokumentacija + default_priority_low: Žemas + default_priority_normal: Normalus + default_priority_high: AukÅ¡tas + default_priority_urgent: Skubus + default_priority_immediate: NeatidÄ—liotinas + default_activity_design: Projektavimas + default_activity_development: Vystymas + + enumeration_issue_priorities: Darbo prioritetai + enumeration_doc_categories: Dokumento kategorijos + enumeration_activities: Veiklos (laiko) sekimas + enumeration_system_activity: Sistemos veikla + + description_filter: Filtras + description_search: PaieÅ¡kos laukas + description_choose_project: Projektai + description_project_scope: PaieÅ¡kos sritis + description_notes: Pastabos + description_message_content: ŽinutÄ—s turinys + description_query_sort_criteria_attribute: Rūšiuoti atributÄ… + description_query_sort_criteria_direction: Rūšiuoti kryptį + description_user_mail_notification: PaÅ¡to praneÅ¡imų nustatymai + description_available_columns: Galimi Stulpeliai + description_selected_columns: Pasirinkti Stulpeliai + description_all_columns: Visi stulpeliai + description_issue_category_reassign: Pasirinkti darbo kategorijÄ… + description_wiki_subpages_reassign: Pasirinkti naujÄ… pagrindinį puslapį + description_date_range_list: PasirinkitÄ™ diapazonÄ… iÅ¡ sÄ…raÅ¡o + description_date_range_interval: Pasirinkite diapazonÄ… pasirinkdami pradžios ir pabaigos datas + description_date_from: Ä®vesti pradžios datÄ… + description_date_to: Ä®vesti pabaigos datÄ… + + label_additional_workflow_transitions_for_assignee: Papildomi darbų eigos variantai kai darbas paskirtas vartotojui + label_additional_workflow_transitions_for_author: Papildomi darbų eigos variantai kai vartotojas yra darbo autorius + notice_failed_to_save_time_entries: "Nepavyko iÅ¡saugoti %{count} laiko žurnalo įrašų iÅ¡ %{total} parinktų: %{ids}." + label_x_issues: + zero: 0 darbas + one: 1 darbas + other: "%{count} darbai(ų)" + label_repository_new: Nauja saugykla + field_repository_is_default: PagrindinÄ— saugykla + label_copy_attachments: Kopijuoti priedus + label_item_position: "%{position}/%{count}" + label_completed_versions: Užbaigtos versijos + text_project_identifier_info: Leidžiamos tik mažosios raidÄ—s (a-z), skaitmenys, brÅ«kÅ¡neliai ir pabraukimo simboliai.
    KartÄ… iÅ¡saugojus pakeitimai negalimi + field_multiple: Keletas reikÅ¡mių + setting_commit_cross_project_ref: Leisti visų kitų projektų įraÅ¡us susieti nuorodomis ir sutaisyti + text_issue_conflict_resolution_add_notes: IÅ¡saugoti mano žinutÄ™ ir atmesti likusius mano pataisymus + text_issue_conflict_resolution_overwrite: IÅ¡saugoti mano pakeitimus (ankstesnių pakeitimų žinutÄ—s bus iÅ¡saugotos, taÄiau kai kurie pakeitimai bus perraÅ¡yti) + notice_issue_update_conflict: Darbas buvo pakoreguotas kito vartotojo kol jÅ«s atlikote pakeitimus. + text_issue_conflict_resolution_cancel: Atmesti visus mano pakeitimus ir iÅ¡ naujo rodyti %{link} + permission_manage_related_issues: Tvarkyti susietus darbus + field_auth_source_ldap_filter: LDAP filtras + label_search_for_watchers: IeÅ¡koti vartotojų kuriuos įtraukti kaip stebÄ—tojus + notice_account_deleted: JÅ«sų paskyra panaikinta. + setting_unsubscribe: Leisti vartotojams panaikinti savo paskyrÄ… + button_delete_my_account: Panaikinti savo paskyrÄ… + text_account_destroy_confirmation: |- + Ar tikrai norite tÄ™sti? + JÅ«sų paskyra bus panaikinta ir nebus galimybÄ—s jos atkurti. + error_session_expired: JÅ«sų sesija pasibaigÄ—. PraÅ¡ome prisijunti iÅ¡ naujo. + text_session_expiration_settings: "Ä®spÄ—jimas: atlikus Å¡iuos pakeitimus visos aktyvios sesijos gali nustoti galiojusios (įskaitant jÅ«sų sesijÄ…)." + setting_session_lifetime: Sesijos maksimalus galiojimas + setting_session_timeout: Sesijos neveiklumo laiko tarpas + label_session_expiration: BaigÄ—si sujungimo sesija + permission_close_project: Uždaryti / atnaujinti projektÄ… + label_show_closed_projects: Matyti uždarytus projektus + button_close: Uždaryti + button_reopen: Atnaujinti + project_status_active: aktyvus + project_status_closed: uždarytas + project_status_archived: archyvuotas + text_project_closed: Å is projektas yra uždarytas, prieinamas tik peržiÅ«rai. + notice_user_successful_create: Vartotojas %{id} sukurtas. + field_core_fields: Standartiniai laukai + field_timeout: Timeout (po sek.) + setting_thumbnails_enabled: Rodyti sumažintus priedų atvaizdus + setting_thumbnails_size: Sumažinto atvaizdo dydis (taÅ¡keliais) + label_status_transitions: Darbų eiga + label_fields_permissions: Leidimai + label_readonly: Tik peržiÅ«ra + label_required: Privaloma(s) + text_repository_identifier_info: Leidžiamos tik mažosios raidÄ—s (a-z), skaitmenys, brÅ«kÅ¡neliai ir pabraukimo simboliai.
    KartÄ… iÅ¡saugojus pakeitimai negalimi + field_board_parent: Pagrindinis forumas + label_attribute_of_project: Projekto pavadinimas %{name} + label_attribute_of_author: Autorius %{name} + label_attribute_of_assigned_to: Paskirtas %{name} + label_attribute_of_fixed_version: Versijos %{name} + label_copy_subtasks: Kopijuoti darbo dalis + label_copied_to: kopijuota į + label_copied_from: kopijuota iÅ¡ + label_any_issues_in_project: bet kurie projekto darbai + label_any_issues_not_in_project: bet kurie ne Å¡io projekto darbai + field_private_notes: PrivaÄios žinutÄ—s + permission_view_private_notes: Matyti privaÄias žinutes + permission_set_notes_private: Pakeisti žinutÄ™ privaÄia + label_no_issues_in_project: projekte nÄ—ra darbų + label_any: visi + label_last_n_weeks: prieÅ¡ %{count} sav. + setting_cross_project_subtasks: Leisti susieti skirtingų projektų užduoÄių dalis + label_cross_project_descendants: Su sub-projektais + label_cross_project_tree: Su projekto medžiu + label_cross_project_hierarchy: Su projekto hierarchija + label_cross_project_system: Su visais projektais + button_hide: SlÄ—pti + setting_non_working_week_days: Nedarbo dienos + label_in_the_next_days: per ateinanÄias + label_in_the_past_days: per paskutines + label_attribute_of_user: Vartotojo %{name} + text_turning_multiple_off: Jei jÅ«s iÅ¡jungsite kelių reikÅ¡mių pasirinkimÄ…, visos iÅ¡vardintos reikÅ¡mÄ—s bus paÅ¡alintos ir palikta tik viena reikÅ¡mÄ— kiekvienam laukui. + label_attribute_of_issue: Ä®raÅ¡ai %{name} + permission_add_documents: PridÄ—ti dokumentus + permission_edit_documents: Redaguoti dokumentus + permission_delete_documents: Trinti dokumentus + label_gantt_progress_line: Progreso linija + setting_jsonp_enabled: Ä®galinti JSONP palaikymÄ… + field_inherit_members: PaveldÄ—ti narius + field_closed_on: Uždarytas + field_generate_password: Sugeneruoti slaptažodį + setting_default_projects_tracker_ids: Sekliai pagal nutylÄ—jimÄ… naujiems projektams + label_total_time: IÅ¡ viso + text_scm_config: JÅ«s galite pakeisti SCM komandas byloje config/configuration.yml. PraÅ¡ome perkrauti programÄ… po redagavimo, idant įgalinti pakeitimus. + text_scm_command_not_available: SCM komanda nepasiekiama. Patikrinkite administravimo skydelio nustatymus. + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/11/11389dcb8c49b70343e3b80fa2a021abf64753ca.svn-base --- a/.svn/pristine/11/11389dcb8c49b70343e3b80fa2a021abf64753ca.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingContextMenusTest < ActionController::IntegrationTest - def test_context_menus_time_entries - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/time_entries/context_menu" }, - { :controller => 'context_menus', :action => 'time_entries' } - ) - end - end - - def test_context_menus_issues - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/issues/context_menu" }, - { :controller => 'context_menus', :action => 'issues' } - ) - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/11/1143136ec6943c91201441e4e743a0264ddfa810.svn-base --- a/.svn/pristine/11/1143136ec6943c91201441e4e743a0264ddfa810.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -<%= form_tag({:action => 'edit', :tab => 'mail_handler'}) do %> - -
    -

    - <%= setting_text_area :mail_handler_body_delimiters, :rows => 5 %> - <%= l(:text_line_separated) %> -

    -
    - -
    -

    <%= setting_check_box :mail_handler_api_enabled, - :onclick => "if (this.checked) { $('#settings_mail_handler_api_key').removeAttr('disabled'); } else { $('#settings_mail_handler_api_key').attr('disabled', true); }"%>

    - -

    <%= setting_text_field :mail_handler_api_key, :size => 30, - :id => 'settings_mail_handler_api_key', - :disabled => !Setting.mail_handler_api_enabled? %> - <%= link_to_function l(:label_generate_key), "if (!$('#settings_mail_handler_api_key').attr('disabled')) { $('#settings_mail_handler_api_key').val(randomKey(20)) }" %> -

    -
    - -<%= submit_tag l(:button_save) %> - -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/11/1176a107aff15323df8f650ff2c14a601d13f571.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/11/1176a107aff15323df8f650ff2c14a601d13f571.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,38 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class PatchesTest < ActiveSupport::TestCase + include Redmine::I18n + + def setup + Setting.default_language = 'en' + end + + test "ActiveRecord::Base.human_attribute_name should transform name to field_name" do + assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on') + end + + test "ActiveRecord::Base.human_attribute_name should cut extra _id suffix for better validation" do + assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on_id') + end + + test "ActiveRecord::Base.human_attribute_name should default to humanized value if no translation has been found (useful for custom fields)" do + assert_equal 'Patch name', ActiveRecord::Base.human_attribute_name('Patch name') + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/11/11a5507f6b6077a6e73fb852952ddb0da8dc089e.svn-base --- a/.svn/pristine/11/11a5507f6b6077a6e73fb852952ddb0da8dc089e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class CustomFieldVersionFormatTest < ActiveSupport::TestCase - fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues, :versions - - def setup - @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'version') - end - - def test_possible_values_with_no_arguments - assert_equal [], @field.possible_values - assert_equal [], @field.possible_values(nil) - end - - def test_possible_values_with_project_resource - project = Project.find(1) - possible_values = @field.possible_values(project.issues.first) - assert possible_values.any? - assert_equal project.shared_versions.sort.collect(&:id).map(&:to_s), possible_values - end - - def test_possible_values_with_nil_project_resource - assert_equal [], @field.possible_values(Issue.new) - end - - def test_possible_values_options_with_no_arguments - assert_equal [], @field.possible_values_options - assert_equal [], @field.possible_values_options(nil) - end - - def test_possible_values_options_with_project_resource - project = Project.find(1) - possible_values_options = @field.possible_values_options(project.issues.first) - assert possible_values_options.any? - assert_equal project.shared_versions.sort.map {|u| [u.name, u.id.to_s]}, possible_values_options - end - - def test_possible_values_options_with_array - projects = Project.find([1, 2]) - possible_values_options = @field.possible_values_options(projects) - assert possible_values_options.any? - assert_equal (projects.first.shared_versions & projects.last.shared_versions).sort.map {|u| [u.name, u.id.to_s]}, possible_values_options - end - - def test_cast_blank_value - assert_equal nil, @field.cast_value(nil) - assert_equal nil, @field.cast_value("") - end - - def test_cast_valid_value - version = @field.cast_value("2") - assert_kind_of Version, version - assert_equal Version.find(2), version - end - - def test_cast_invalid_value - assert_equal nil, @field.cast_value("187") - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/11/11c16631f55588a98678a93f0bb375404c2e534c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/11/11c16631f55588a98678a93f0bb375404c2e534c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,33 @@ +# Settings specified here will take precedence over those in config/application.rb +RedmineApp::Application.configure do + # The production environment is meant for finished, "live" apps. + # Code is not reloaded between requests + config.cache_classes = true + + ##### + # Customize the default logger + # http://www.ruby-doc.org/stdlib-1.8.7/libdoc/logger/rdoc/Logger.html + # + # Use a different logger for distributed setups + # config.logger = SyslogLogger.new + # + # Rotate logs bigger than 1MB, keeps no more than 7 rotated logs around. + # When setting a new Logger, make sure to set it's log level too. + # + # config.logger = Logger.new(config.log_path, 7, 1048576) + # config.logger.level = Logger::INFO + + # Full error reports are disabled and caching is turned on + config.action_controller.perform_caching = true + + # Enable serving of images, stylesheets, and javascripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Disable delivery errors if you bad email addresses should just be ignored + config.action_mailer.raise_delivery_errors = false + + # No email in production log + config.action_mailer.logger = nil + + config.active_support.deprecation = :log +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/11/11c49a871d148b89615d8c37f2abca7b6149921b.svn-base --- a/.svn/pristine/11/11c49a871d148b89615d8c37f2abca7b6149921b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,377 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class IssueNestedSetTest < ActiveSupport::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, - :trackers, :projects_trackers, - :versions, - :issue_statuses, :issue_categories, :issue_relations, :workflows, - :enumerations, - :issues, - :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, - :time_entries - - def test_create_root_issue - issue1 = Issue.generate! - issue2 = Issue.generate! - issue1.reload - issue2.reload - - assert_equal [issue1.id, nil, 1, 2], [issue1.root_id, issue1.parent_id, issue1.lft, issue1.rgt] - assert_equal [issue2.id, nil, 1, 2], [issue2.root_id, issue2.parent_id, issue2.lft, issue2.rgt] - end - - def test_create_child_issue - parent = Issue.generate! - child = Issue.generate!(:parent_issue_id => parent.id) - parent.reload - child.reload - - assert_equal [parent.id, nil, 1, 4], [parent.root_id, parent.parent_id, parent.lft, parent.rgt] - assert_equal [parent.id, parent.id, 2, 3], [child.root_id, child.parent_id, child.lft, child.rgt] - end - - def test_creating_a_child_in_a_subproject_should_validate - issue = Issue.generate! - child = Issue.new(:project_id => 3, :tracker_id => 2, :author_id => 1, - :subject => 'child', :parent_issue_id => issue.id) - assert_save child - assert_equal issue, child.reload.parent - end - - def test_creating_a_child_in_an_invalid_project_should_not_validate - issue = Issue.generate! - child = Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1, - :subject => 'child', :parent_issue_id => issue.id) - assert !child.save - assert_not_nil child.errors[:parent_issue_id] - end - - def test_move_a_root_to_child - parent1 = Issue.generate! - parent2 = Issue.generate! - child = Issue.generate!(:parent_issue_id => parent1.id) - - parent2.parent_issue_id = parent1.id - parent2.save! - child.reload - parent1.reload - parent2.reload - - assert_equal [parent1.id, 1, 6], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent1.id, 4, 5], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [parent1.id, 2, 3], [child.root_id, child.lft, child.rgt] - end - - def test_move_a_child_to_root - parent1 = Issue.generate! - parent2 = Issue.generate! - child = Issue.generate!(:parent_issue_id => parent1.id) - - child.parent_issue_id = nil - child.save! - child.reload - parent1.reload - parent2.reload - - assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent2.id, 1, 2], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [child.id, 1, 2], [child.root_id, child.lft, child.rgt] - end - - def test_move_a_child_to_another_issue - parent1 = Issue.generate! - parent2 = Issue.generate! - child = Issue.generate!(:parent_issue_id => parent1.id) - - child.parent_issue_id = parent2.id - child.save! - child.reload - parent1.reload - parent2.reload - - assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent2.id, 1, 4], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [parent2.id, 2, 3], [child.root_id, child.lft, child.rgt] - end - - def test_move_a_child_with_descendants_to_another_issue - parent1 = Issue.generate! - parent2 = Issue.generate! - child = Issue.generate!(:parent_issue_id => parent1.id) - grandchild = Issue.generate!(:parent_issue_id => child.id) - - parent1.reload - parent2.reload - child.reload - grandchild.reload - - assert_equal [parent1.id, 1, 6], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent2.id, 1, 2], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [parent1.id, 2, 5], [child.root_id, child.lft, child.rgt] - assert_equal [parent1.id, 3, 4], [grandchild.root_id, grandchild.lft, grandchild.rgt] - - child.reload.parent_issue_id = parent2.id - child.save! - child.reload - grandchild.reload - parent1.reload - parent2.reload - - assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent2.id, 1, 6], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [parent2.id, 2, 5], [child.root_id, child.lft, child.rgt] - assert_equal [parent2.id, 3, 4], [grandchild.root_id, grandchild.lft, grandchild.rgt] - end - - def test_move_a_child_with_descendants_to_another_project - parent1 = Issue.generate! - child = Issue.generate!(:parent_issue_id => parent1.id) - grandchild = Issue.generate!(:parent_issue_id => child.id) - - child.reload - child.project = Project.find(2) - assert child.save - child.reload - grandchild.reload - parent1.reload - - assert_equal [1, parent1.id, 1, 2], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [2, child.id, 1, 4], [child.project_id, child.root_id, child.lft, child.rgt] - assert_equal [2, child.id, 2, 3], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt] - end - - def test_moving_an_issue_to_a_descendant_should_not_validate - parent1 = Issue.generate! - parent2 = Issue.generate! - child = Issue.generate!(:parent_issue_id => parent1.id) - grandchild = Issue.generate!(:parent_issue_id => child.id) - - child.reload - child.parent_issue_id = grandchild.id - assert !child.save - assert_not_nil child.errors[:parent_issue_id] - end - - def test_moving_an_issue_should_keep_valid_relations_only - issue1 = Issue.generate! - issue2 = Issue.generate! - issue3 = Issue.generate!(:parent_issue_id => issue2.id) - issue4 = Issue.generate! - r1 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES) - r2 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue3, :relation_type => IssueRelation::TYPE_PRECEDES) - r3 = IssueRelation.create!(:issue_from => issue2, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES) - issue2.reload - issue2.parent_issue_id = issue1.id - issue2.save! - assert !IssueRelation.exists?(r1.id) - assert !IssueRelation.exists?(r2.id) - assert IssueRelation.exists?(r3.id) - end - - def test_destroy_should_destroy_children - issue1 = Issue.generate! - issue2 = Issue.generate! - issue3 = Issue.generate!(:parent_issue_id => issue2.id) - issue4 = Issue.generate!(:parent_issue_id => issue1.id) - - issue3.init_journal(User.find(2)) - issue3.subject = 'child with journal' - issue3.save! - - assert_difference 'Issue.count', -2 do - assert_difference 'Journal.count', -1 do - assert_difference 'JournalDetail.count', -1 do - Issue.find(issue2.id).destroy - end - end - end - - issue1.reload - issue4.reload - assert !Issue.exists?(issue2.id) - assert !Issue.exists?(issue3.id) - assert_equal [issue1.id, 1, 4], [issue1.root_id, issue1.lft, issue1.rgt] - assert_equal [issue1.id, 2, 3], [issue4.root_id, issue4.lft, issue4.rgt] - end - - def test_destroy_child_should_update_parent - issue = Issue.generate! - child1 = Issue.generate!(:parent_issue_id => issue.id) - child2 = Issue.generate!(:parent_issue_id => issue.id) - - issue.reload - assert_equal [issue.id, 1, 6], [issue.root_id, issue.lft, issue.rgt] - - child2.reload.destroy - - issue.reload - assert_equal [issue.id, 1, 4], [issue.root_id, issue.lft, issue.rgt] - end - - def test_destroy_parent_issue_updated_during_children_destroy - parent = Issue.generate! - Issue.generate!(:start_date => Date.today, :parent_issue_id => parent.id) - Issue.generate!(:start_date => 2.days.from_now, :parent_issue_id => parent.id) - - assert_difference 'Issue.count', -3 do - Issue.find(parent.id).destroy - end - end - - def test_destroy_child_issue_with_children - root = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'root') - child = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => root.id) - leaf = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'leaf', :parent_issue_id => child.id) - leaf.init_journal(User.find(2)) - leaf.subject = 'leaf with journal' - leaf.save! - - assert_difference 'Issue.count', -2 do - assert_difference 'Journal.count', -1 do - assert_difference 'JournalDetail.count', -1 do - Issue.find(child.id).destroy - end - end - end - - root = Issue.find(root.id) - assert root.leaf?, "Root issue is not a leaf (lft: #{root.lft}, rgt: #{root.rgt})" - end - - def test_destroy_issue_with_grand_child - parent = Issue.generate! - issue = Issue.generate!(:parent_issue_id => parent.id) - child = Issue.generate!(:parent_issue_id => issue.id) - grandchild1 = Issue.generate!(:parent_issue_id => child.id) - grandchild2 = Issue.generate!(:parent_issue_id => child.id) - - assert_difference 'Issue.count', -4 do - Issue.find(issue.id).destroy - parent.reload - assert_equal [1, 2], [parent.lft, parent.rgt] - end - end - - def test_parent_priority_should_be_the_highest_child_priority - parent = Issue.generate!(:priority => IssuePriority.find_by_name('Normal')) - # Create children - child1 = Issue.generate!(:priority => IssuePriority.find_by_name('High'), :parent_issue_id => parent.id) - assert_equal 'High', parent.reload.priority.name - child2 = Issue.generate!(:priority => IssuePriority.find_by_name('Immediate'), :parent_issue_id => child1.id) - assert_equal 'Immediate', child1.reload.priority.name - assert_equal 'Immediate', parent.reload.priority.name - child3 = Issue.generate!(:priority => IssuePriority.find_by_name('Low'), :parent_issue_id => parent.id) - assert_equal 'Immediate', parent.reload.priority.name - # Destroy a child - child1.destroy - assert_equal 'Low', parent.reload.priority.name - # Update a child - child3.reload.priority = IssuePriority.find_by_name('Normal') - child3.save! - assert_equal 'Normal', parent.reload.priority.name - end - - def test_parent_dates_should_be_lowest_start_and_highest_due_dates - parent = Issue.generate! - Issue.generate!(:start_date => '2010-01-25', :due_date => '2010-02-15', :parent_issue_id => parent.id) - Issue.generate!( :due_date => '2010-02-13', :parent_issue_id => parent.id) - Issue.generate!(:start_date => '2010-02-01', :due_date => '2010-02-22', :parent_issue_id => parent.id) - parent.reload - assert_equal Date.parse('2010-01-25'), parent.start_date - assert_equal Date.parse('2010-02-22'), parent.due_date - end - - def test_parent_done_ratio_should_be_average_done_ratio_of_leaves - parent = Issue.generate! - Issue.generate!(:done_ratio => 20, :parent_issue_id => parent.id) - assert_equal 20, parent.reload.done_ratio - Issue.generate!(:done_ratio => 70, :parent_issue_id => parent.id) - assert_equal 45, parent.reload.done_ratio - - child = Issue.generate!(:done_ratio => 0, :parent_issue_id => parent.id) - assert_equal 30, parent.reload.done_ratio - - Issue.generate!(:done_ratio => 30, :parent_issue_id => child.id) - assert_equal 30, child.reload.done_ratio - assert_equal 40, parent.reload.done_ratio - end - - def test_parent_done_ratio_should_be_weighted_by_estimated_times_if_any - parent = Issue.generate! - Issue.generate!(:estimated_hours => 10, :done_ratio => 20, :parent_issue_id => parent.id) - assert_equal 20, parent.reload.done_ratio - Issue.generate!(:estimated_hours => 20, :done_ratio => 50, :parent_issue_id => parent.id) - assert_equal (50 * 20 + 20 * 10) / 30, parent.reload.done_ratio - end - - def test_parent_estimate_should_be_sum_of_leaves - parent = Issue.generate! - Issue.generate!(:estimated_hours => nil, :parent_issue_id => parent.id) - assert_equal nil, parent.reload.estimated_hours - Issue.generate!(:estimated_hours => 5, :parent_issue_id => parent.id) - assert_equal 5, parent.reload.estimated_hours - Issue.generate!(:estimated_hours => 7, :parent_issue_id => parent.id) - assert_equal 12, parent.reload.estimated_hours - end - - def test_move_parent_updates_old_parent_attributes - first_parent = Issue.generate! - second_parent = Issue.generate! - child = Issue.generate!(:estimated_hours => 5, :parent_issue_id => first_parent.id) - assert_equal 5, first_parent.reload.estimated_hours - child.update_attributes(:estimated_hours => 7, :parent_issue_id => second_parent.id) - assert_equal 7, second_parent.reload.estimated_hours - assert_nil first_parent.reload.estimated_hours - end - - def test_reschuling_a_parent_should_reschedule_subtasks - parent = Issue.generate! - c1 = Issue.generate!(:start_date => '2010-05-12', :due_date => '2010-05-18', :parent_issue_id => parent.id) - c2 = Issue.generate!(:start_date => '2010-06-03', :due_date => '2010-06-10', :parent_issue_id => parent.id) - parent.reload - parent.reschedule_on!(Date.parse('2010-06-02')) - c1.reload - assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-08')], [c1.start_date, c1.due_date] - c2.reload - assert_equal [Date.parse('2010-06-03'), Date.parse('2010-06-10')], [c2.start_date, c2.due_date] # no change - parent.reload - assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-10')], [parent.start_date, parent.due_date] - end - - def test_project_copy_should_copy_issue_tree - p = Project.create!(:name => 'Tree copy', :identifier => 'tree-copy', :tracker_ids => [1, 2]) - i1 = Issue.generate!(:project => p, :subject => 'i1') - i2 = Issue.generate!(:project => p, :subject => 'i2', :parent_issue_id => i1.id) - i3 = Issue.generate!(:project => p, :subject => 'i3', :parent_issue_id => i1.id) - i4 = Issue.generate!(:project => p, :subject => 'i4', :parent_issue_id => i2.id) - i5 = Issue.generate!(:project => p, :subject => 'i5') - c = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1, 2]) - c.copy(p, :only => 'issues') - c.reload - - assert_equal 5, c.issues.count - ic1, ic2, ic3, ic4, ic5 = c.issues.find(:all, :order => 'subject') - assert ic1.root? - assert_equal ic1, ic2.parent - assert_equal ic1, ic3.parent - assert_equal ic2, ic4.parent - assert ic5.root? - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/11/11eb00b887f31790fc6e46fc8c8f7f620e8e7372.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/11/11eb00b887f31790fc6e46fc8c8f7f620e8e7372.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,199 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class CustomFieldsControllerTest < ActionController::TestCase + fixtures :custom_fields, :custom_values, :trackers, :users, :projects + + def setup + @request.session[:user_id] = 1 + end + + def test_index + get :index + assert_response :success + assert_template 'index' + end + + def test_new + custom_field_classes.each do |klass| + get :new, :type => klass.name + assert_response :success + assert_template 'new' + assert_kind_of klass, assigns(:custom_field) + assert_select 'form#custom_field_form' do + assert_select 'select#custom_field_field_format[name=?]', 'custom_field[field_format]' + assert_select 'input[type=hidden][name=type][value=?]', klass.name + end + end + end + + def test_new_issue_custom_field + get :new, :type => 'IssueCustomField' + assert_response :success + assert_template 'new' + assert_select 'form#custom_field_form' do + assert_select 'select#custom_field_field_format[name=?]', 'custom_field[field_format]' do + assert_select 'option[value=user]', :text => 'User' + assert_select 'option[value=version]', :text => 'Version' + end + assert_select 'input[type=checkbox][name=?]', 'custom_field[project_ids][]', Project.count + assert_select 'input[type=hidden][name=?]', 'custom_field[project_ids][]', 1 + assert_select 'input[type=hidden][name=type][value=IssueCustomField]' + end + end + + def test_new_time_entry_custom_field_should_not_show_trackers_and_projects + get :new, :type => 'TimeEntryCustomField' + assert_response :success + assert_template 'new' + assert_select 'form#custom_field_form' do + assert_select 'input[name=?]', 'custom_field[tracker_ids][]', 0 + assert_select 'input[name=?]', 'custom_field[project_ids][]', 0 + end + end + + def test_default_value_should_be_an_input_for_string_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'string'} + assert_response :success + assert_select 'input[name=?]', 'custom_field[default_value]' + end + + def test_default_value_should_be_a_textarea_for_text_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'text'} + assert_response :success + assert_select 'textarea[name=?]', 'custom_field[default_value]' + end + + def test_default_value_should_be_a_checkbox_for_bool_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'bool'} + assert_response :success + assert_select 'input[name=?][type=checkbox]', 'custom_field[default_value]' + end + + def test_default_value_should_not_be_present_for_user_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'user'} + assert_response :success + assert_select '[name=?]', 'custom_field[default_value]', 0 + end + + def test_new_js + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'list'}, :format => 'js' + assert_response :success + assert_template 'new' + assert_equal 'text/javascript', response.content_type + + field = assigns(:custom_field) + assert_equal 'list', field.field_format + end + + def test_new_with_invalid_custom_field_class_should_render_404 + get :new, :type => 'UnknownCustomField' + assert_response 404 + end + + def test_create_list_custom_field + assert_difference 'CustomField.count' do + post :create, :type => "IssueCustomField", + :custom_field => {:name => "test_post_new_list", + :default_value => "", + :min_length => "0", + :searchable => "0", + :regexp => "", + :is_for_all => "1", + :possible_values => "0.1\n0.2\n", + :max_length => "0", + :is_filter => "0", + :is_required =>"0", + :field_format => "list", + :tracker_ids => ["1", ""]} + end + assert_redirected_to '/custom_fields?tab=IssueCustomField' + field = IssueCustomField.find_by_name('test_post_new_list') + assert_not_nil field + assert_equal ["0.1", "0.2"], field.possible_values + assert_equal 1, field.trackers.size + end + + def test_create_with_project_ids + assert_difference 'CustomField.count' do + post :create, :type => "IssueCustomField", :custom_field => { + :name => "foo", :field_format => "string", :is_for_all => "0", :project_ids => ["1", "3", ""] + } + assert_response 302 + end + field = IssueCustomField.order("id desc").first + assert_equal [1, 3], field.projects.map(&:id).sort + end + + def test_create_with_failure + assert_no_difference 'CustomField.count' do + post :create, :type => "IssueCustomField", :custom_field => {:name => ''} + end + assert_response :success + assert_template 'new' + end + + def test_edit + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + assert_tag 'input', :attributes => {:name => 'custom_field[name]', :value => 'Database'} + end + + def test_edit_invalid_custom_field_should_render_404 + get :edit, :id => 99 + assert_response 404 + end + + def test_update + put :update, :id => 1, :custom_field => {:name => 'New name'} + assert_redirected_to '/custom_fields?tab=IssueCustomField' + + field = CustomField.find(1) + assert_equal 'New name', field.name + end + + def test_update_with_failure + put :update, :id => 1, :custom_field => {:name => ''} + assert_response :success + assert_template 'edit' + end + + def test_destroy + custom_values_count = CustomValue.where(:custom_field_id => 1).count + assert custom_values_count > 0 + + assert_difference 'CustomField.count', -1 do + assert_difference 'CustomValue.count', - custom_values_count do + delete :destroy, :id => 1 + end + end + + assert_redirected_to '/custom_fields?tab=IssueCustomField' + assert_nil CustomField.find_by_id(1) + assert_nil CustomValue.find_by_custom_field_id(1) + end + + def custom_field_classes + files = Dir.glob(File.join(Rails.root, 'app/models/*_custom_field.rb')).map {|f| File.basename(f).sub(/\.rb$/, '') } + classes = files.map(&:classify).map(&:constantize) + assert classes.size > 0 + classes + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/12/120f067fdcb658f3716efac6a31070d12015a618.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/12/120f067fdcb658f3716efac6a31070d12015a618.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,53 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class CommentsController < ApplicationController + default_search_scope :news + model_object News + before_filter :find_model_object + before_filter :find_project_from_association + before_filter :authorize + + def create + raise Unauthorized unless @news.commentable? + + @comment = Comment.new + @comment.safe_attributes = params[:comment] + @comment.author = User.current + if @news.comments << @comment + flash[:notice] = l(:label_comment_added) + end + + redirect_to news_path(@news) + end + + def destroy + @news.comments.find(params[:comment_id]).destroy + redirect_to news_path(@news) + end + + private + + # ApplicationController's find_model_object sets it based on the controller + # name so it needs to be overriden and set to @news instead + def find_model_object + super + @news = @object + @comment = nil + @news + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/12/121eeed4fcb10e023ef41b53336708e73d10e3e6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/12/121eeed4fcb10e023ef41b53336708e73d10e3e6.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,38 @@ +<%= form_tag({:action => 'edit', :tab => 'authentication'}) do %> + +
    +

    <%= setting_check_box :login_required %>

    + +

    <%= setting_select :autologin, [[l(:label_disabled), 0]] + [1, 7, 30, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), days.to_s]} %>

    + +

    <%= setting_select :self_registration, [[l(:label_disabled), "0"], + [l(:label_registration_activation_by_email), "1"], + [l(:label_registration_manual_activation), "2"], + [l(:label_registration_automatic_activation), "3"]] %>

    + +

    <%= setting_check_box :unsubscribe %>

    + +

    <%= setting_text_field :password_min_length, :size => 6 %>

    + +

    <%= setting_check_box :lost_password, :label => :label_password_lost %>

    + +

    <%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %>

    + +

    <%= setting_check_box :rest_api_enabled %>

    + +

    <%= setting_check_box :jsonp_enabled %>

    +
    + +
    + <%= l(:label_session_expiration) %> + +
    +

    <%= setting_select :session_lifetime, [[l(:label_disabled), 0]] + [1, 7, 30, 60, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), (days * 60 * 24).to_s]} %>

    +

    <%= setting_select :session_timeout, [[l(:label_disabled), 0]] + [1, 2, 4, 8, 12, 24, 48].collect{|hours| [l('datetime.distance_in_words.x_hours', :count => hours), (hours * 60).to_s]} %>

    +
    + +

    <%= l(:text_session_expiration_settings) %>

    +
    + +<%= submit_tag l(:button_save) %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/12/1250d373a782657c3a0120825ac45c4635fdccf2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/12/1250d373a782657c3a0120825ac45c4635fdccf2.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-07-27 05:52:11.415223830 +0900 ++++ b.txt 2013-07-27 05:52:18.249190358 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本記ok ++日本誘ok + bbbb diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/12/1255b1abb30e02b5520a0d75d153507b1306bc5d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/12/1255b1abb30e02b5520a0d75d153507b1306bc5d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,57 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class CommentTest < ActiveSupport::TestCase + fixtures :users, :news, :comments, :projects, :enabled_modules + + def setup + @jsmith = User.find(2) + @news = News.find(1) + end + + def test_create + comment = Comment.new(:commented => @news, :author => @jsmith, :comments => "my comment") + assert comment.save + @news.reload + assert_equal 2, @news.comments_count + end + + def test_create_should_send_notification + Watcher.create!(:watchable => @news, :user => @jsmith) + + with_settings :notified_events => %w(news_comment_added) do + assert_difference 'ActionMailer::Base.deliveries.size' do + Comment.create!(:commented => @news, :author => @jsmith, :comments => "my comment") + end + end + end + + def test_validate + comment = Comment.new(:commented => @news) + assert !comment.save + assert_equal 2, comment.errors.count + end + + def test_destroy + comment = Comment.find(1) + assert comment.destroy + @news.reload + assert_equal 0, @news.comments_count + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/12/129828b625f30815a786616d216ef1dba321d685.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/12/129828b625f30815a786616d216ef1dba321d685.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,101 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class WikiTest < ActiveSupport::TestCase + fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + + def test_create + wiki = Wiki.new(:project => Project.find(2)) + assert !wiki.save + assert_equal 1, wiki.errors.count + + wiki.start_page = "Start page" + assert wiki.save + end + + def test_update + @wiki = Wiki.find(1) + @wiki.start_page = "Another start page" + assert @wiki.save + @wiki.reload + assert_equal "Another start page", @wiki.start_page + end + + def test_find_page_should_not_be_case_sensitive + wiki = Wiki.find(1) + page = WikiPage.find(2) + + assert_equal page, wiki.find_page('Another_page') + assert_equal page, wiki.find_page('Another page') + assert_equal page, wiki.find_page('ANOTHER page') + end + + def test_find_page_with_cyrillic_characters + wiki = Wiki.find(1) + page = WikiPage.find(10) + assert_equal page, wiki.find_page('Этика_менеджмента') + end + + def test_find_page_with_backslashes + wiki = Wiki.find(1) + page = WikiPage.create!(:wiki => wiki, :title => '2009\\02\\09') + assert_equal page, wiki.find_page('2009\\02\\09') + end + + def test_find_page_without_redirect + wiki = Wiki.find(1) + page = wiki.find_page('Another_page') + assert_not_nil page + assert_equal 'Another_page', page.title + assert_equal false, wiki.page_found_with_redirect? + end + + def test_find_page_with_redirect + wiki = Wiki.find(1) + WikiRedirect.create!(:wiki => wiki, :title => 'Old_title', :redirects_to => 'Another_page') + page = wiki.find_page('Old_title') + assert_not_nil page + assert_equal 'Another_page', page.title + assert_equal true, wiki.page_found_with_redirect? + end + + def test_titleize + ja_test = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" + ja_test.force_encoding('UTF-8') if ja_test.respond_to?(:force_encoding) + assert_equal 'Page_title_with_CAPITALES', Wiki.titleize('page title with CAPITALES') + assert_equal ja_test, Wiki.titleize(ja_test) + end + + def test_sidebar_should_return_nil_if_undefined + @wiki = Wiki.find(1) + assert_nil @wiki.sidebar + end + + def test_sidebar_should_return_a_wiki_page_if_defined + @wiki = Wiki.find(1) + page = @wiki.pages.new(:title => 'Sidebar') + page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar') + page.save! + + assert_kind_of WikiPage, @wiki.sidebar + assert_equal 'Sidebar', @wiki.sidebar.title + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/12/12e2bd6b115cd00dd763ce32dbfae0382d38cea8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/12/12e2bd6b115cd00dd763ce32dbfae0382d38cea8.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,183 @@ +desc 'Updates and checks locales against en.yml' +task :locales do + %w(locales:update locales:check_interpolation).collect do |task| + Rake::Task[task].invoke + end +end + +namespace :locales do + desc 'Updates language files based on en.yml content (only works for new top level keys).' + task :update do + dir = ENV['DIR'] || './config/locales' + + en_strings = YAML.load(File.read(File.join(dir,'en.yml')))['en'] + + files = Dir.glob(File.join(dir,'*.{yaml,yml}')) + files.sort.each do |file| + puts "Updating file #{file}" + file_strings = YAML.load(File.read(file)) + file_strings = file_strings[file_strings.keys.first] + + missing_keys = en_strings.keys - file_strings.keys + next if missing_keys.empty? + + puts "==> Missing #{missing_keys.size} keys (#{missing_keys.join(', ')})" + lang = File.open(file, 'a') + + missing_keys.each do |key| + {key => en_strings[key]}.to_yaml.each_line do |line| + next if line =~ /^---/ || line.empty? + puts " #{line}" + lang << " #{line}" + end + end + + lang.close + end + end + + desc 'Checks interpolation arguments in locals against en.yml' + task :check_interpolation do + dir = ENV['DIR'] || './config/locales' + en_strings = YAML.load(File.read(File.join(dir,'en.yml')))['en'] + files = Dir.glob(File.join(dir,'*.{yaml,yml}')) + files.sort.each do |file| + puts "parsing #{file}..." + file_strings = YAML.load_file(file) + unless file_strings.is_a?(Hash) + puts "#{file}: content is not a Hash (#{file_strings.class.name})" + next + end + unless file_strings.keys.size == 1 + puts "#{file}: content has multiple keys (#{file_strings.keys.size})" + next + end + file_strings = file_strings[file_strings.keys.first] + + file_strings.each do |key, string| + next unless string.is_a?(String) + string.scan /%\{\w+\}/ do |match| + unless en_strings[key].nil? || en_strings[key].include?(match) + puts "#{file}: #{key} uses #{match} not found in en.yml" + end + end + end + end + end + + desc <<-END_DESC +Removes a translation string from all locale file (only works for top-level childless non-multiline keys, probably doesn\'t work on windows). + +This task does not work on Ruby 1.8.6. +You need to use Ruby 1.8.7 or later. + +Options: + key=key_1,key_2 Comma-separated list of keys to delete + skip=en,de Comma-separated list of locale files to ignore (filename without extension) +END_DESC + + task :remove_key do + dir = ENV['DIR'] || './config/locales' + files = Dir.glob(File.join(dir,'*.yml')) + skips = ENV['skip'] ? Regexp.union(ENV['skip'].split(',')) : nil + deletes = ENV['key'] ? Regexp.union(ENV['key'].split(',')) : nil + # Ignore multiline keys (begin with | or >) and keys with children (nothing meaningful after :) + delete_regex = /\A #{deletes}: +[^\|>\s#].*\z/ + + files.each do |path| + # Skip certain locales + (puts "Skipping #{path}"; next) if File.basename(path, ".yml") =~ skips + puts "Deleting selected keys from #{path}" + orig_content = File.open(path, 'r') {|file| file.read} + File.open(path, 'w') {|file| orig_content.each_line {|line| file.puts line unless line.chomp =~ delete_regex}} + end + end + + desc <<-END_DESC +Adds a new top-level translation string to all locale file (only works for childless keys, probably doesn\'t work on windows, doesn't check for duplicates). + +Options: + key="some_key=foo" + key1="another_key=bar" + key_fb="foo=bar" Keys to add in the form key=value, every option of the form key[,\\d,_*] will be recognised + skip=en,de Comma-separated list of locale files to ignore (filename without extension) +END_DESC + + task :add_key do + dir = ENV['DIR'] || './config/locales' + files = Dir.glob(File.join(dir,'*.yml')) + skips = ENV['skip'] ? Regexp.union(ENV['skip'].split(',')) : nil + keys_regex = /\Akey(\d+|_.+)?\z/ + adds = ENV.reject {|k,v| !(k =~ keys_regex)}.values.collect {|v| Array.new v.split("=",2)} + key_list = adds.collect {|v| v[0]}.join(", ") + + files.each do |path| + # Skip certain locales + (puts "Skipping #{path}"; next) if File.basename(path, ".yml") =~ skips + # TODO: Check for dupliate/existing keys + puts "Adding #{key_list} to #{path}" + File.open(path, 'a') do |file| + adds.each do |kv| + Hash[*kv].to_yaml.each_line do |line| + file.puts " #{line}" unless (line =~ /^---/ || line.empty?) + end + end + end + end + end + + desc 'Duplicates a key. Exemple rake locales:dup key=foo new_key=bar' + task :dup do + dir = ENV['DIR'] || './config/locales' + files = Dir.glob(File.join(dir,'*.yml')) + skips = ENV['skip'] ? Regexp.union(ENV['skip'].split(',')) : nil + key = ENV['key'] + new_key = ENV['new_key'] + abort "Missing key argument" if key.blank? + abort "Missing new_key argument" if new_key.blank? + + files.each do |path| + # Skip certain locales + (puts "Skipping #{path}"; next) if File.basename(path, ".yml") =~ skips + puts "Adding #{new_key} to #{path}" + + strings = File.read(path) + unless strings =~ /^( #{key}: .+)$/ + puts "Key not found in #{path}" + next + end + line = $1 + + File.open(path, 'a') do |file| + file.puts(line.sub(key, new_key)) + end + end + end + + desc 'Check parsing yaml by psych library on Ruby 1.9.' + + # On Fedora 12 and 13, if libyaml-devel is available, + # in case of installing by rvm, + # Ruby 1.9 default yaml library is psych. + + task :check_parsing_by_psych do + begin + require 'psych' + parser = Psych::Parser.new + dir = ENV['DIR'] || './config/locales' + files = Dir.glob(File.join(dir,'*.yml')) + files.sort.each do |filename| + next if File.directory? filename + puts "parsing #{filename}..." + begin + parser.parse File.open(filename) + rescue Exception => e1 + puts(e1.message) + puts("") + end + end + rescue Exception => e + puts(e.message) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/12/12ef2ae313a0833c5606f02c0f4c890cafaa550f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/12/12ef2ae313a0833c5606f02c0f4c890cafaa550f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,36 @@ +desc 'Load Redmine default configuration data. Language is chosen interactively or by setting REDMINE_LANG environment variable.' + +namespace :redmine do + task :load_default_data => :environment do + require 'custom_field' + include Redmine::I18n + set_language_if_valid('en') + + envlang = ENV['REDMINE_LANG'] + if !envlang || !set_language_if_valid(envlang) + puts + while true + print "Select language: " + print valid_languages.collect(&:to_s).sort.join(", ") + print " [#{current_language}] " + STDOUT.flush + lang = STDIN.gets.chomp! + break if lang.empty? + break if set_language_if_valid(lang) + puts "Unknown language!" + end + STDOUT.flush + puts "====================================" + end + + begin + Redmine::DefaultData::Loader.load(current_language) + puts "Default configuration data loaded." + rescue Redmine::DefaultData::DataAlreadyLoaded => error + puts error.message + rescue => error + puts "Error: " + error.message + puts "Default configuration data was not loaded." + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/12/12f45ed7c5d768c433966a08f88e9ccd503d9a14.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/12/12f45ed7c5d768c433966a08f88e9ccd503d9a14.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1104 @@ +# Redmine EU language +# Author: Ales Zabala Alava (Shagi), +# 2010-01-25 +# Distributed under the same terms as the Redmine itself. +eu: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y/%m/%d" + short: "%b %d" + long: "%Y %B %d" + + day_names: [Igandea, Astelehena, Asteartea, Asteazkena, Osteguna, Ostirala, Larunbata] + abbr_day_names: [Ig., Al., Ar., Az., Og., Or., La.] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Urtarrila, Otsaila, Martxoa, Apirila, Maiatza, Ekaina, Uztaila, Abuztua, Iraila, Urria, Azaroa, Abendua] + abbr_month_names: [~, Urt, Ots, Mar, Api, Mai, Eka, Uzt, Abu, Ira, Urr, Aza, Abe] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%Y/%m/%d %H:%M" + time: "%H:%M" + short: "%b %d %H:%M" + long: "%Y %B %d %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "minutu erdi" + less_than_x_seconds: + one: "segundu bat baino gutxiago" + other: "%{count} segundu baino gutxiago" + x_seconds: + one: "segundu 1" + other: "%{count} segundu" + less_than_x_minutes: + one: "minutu bat baino gutxiago" + other: "%{count} minutu baino gutxiago" + x_minutes: + one: "minutu 1" + other: "%{count} minutu" + about_x_hours: + one: "ordu 1 inguru" + other: "%{count} ordu inguru" + x_hours: + one: "ordu 1" + other: "%{count} ordu" + x_days: + one: "egun 1" + other: "%{count} egun" + about_x_months: + one: "hilabete 1 inguru" + other: "%{count} hilabete inguru" + x_months: + one: "hilabete 1" + other: "%{count} hilabete" + about_x_years: + one: "urte 1 inguru" + other: "%{count} urte inguru" + over_x_years: + one: "urte 1 baino gehiago" + other: "%{count} urte baino gehiago" + almost_x_years: + one: "ia urte 1" + other: "ia %{count} urte" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Byte" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "eta" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "Errore batek %{model} hau godetzea galarazi du." + other: "%{count} errorek %{model} hau gordetzea galarazi dute." + messages: + inclusion: "ez dago zerrendan" + exclusion: "erreserbatuta dago" + invalid: "baliogabea da" + confirmation: "ez du berrespenarekin bat egiten" + accepted: "onartu behar da" + empty: "ezin da hutsik egon" + blank: "ezin da hutsik egon" + too_long: "luzeegia da (maximoa %{count} karaktere dira)" + too_short: "laburregia da (minimoa %{count} karaktere dira)" + wrong_length: "luzera ezegokia da (%{count} karakter izan beharko litzake)" + taken: "dagoeneko hartuta dago" + not_a_number: "ez da zenbaki bat" + not_a_date: "ez da baliozko data" + greater_than: "%{count} baino handiagoa izan behar du" + greater_than_or_equal_to: "%{count} edo handiagoa izan behar du" + equal_to: "%{count} izan behar du" + less_than: "%{count} baino gutxiago izan behar du" + less_than_or_equal_to: "%{count} edo gutxiago izan behar du" + odd: "bakoitia izan behar du" + even: "bikoitia izan behar du" + greater_than_start_date: "hasiera data baino handiagoa izan behar du" + not_same_project: "ez dago proiektu berdinean" + circular_dependency: "Erlazio honek mendekotasun zirkular bat sortuko luke" + cant_link_an_issue_with_a_descendant: "Zeregin bat ezin da bere azpiataza batekin estekatu." + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Hautatu mesedez + + general_text_No: 'Ez' + general_text_Yes: 'Bai' + general_text_no: 'ez' + general_text_yes: 'bai' + general_lang_name: 'Euskara' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Kontua ongi eguneratu da. + notice_account_invalid_creditentials: Erabiltzaile edo pasahitz ezegokia + notice_account_password_updated: Pasahitza ongi eguneratu da. + notice_account_wrong_password: Pasahitz ezegokia. + notice_account_register_done: Kontua ongi sortu da. Kontua gaitzeko klikatu epostan adierazi zaizun estekan. + notice_account_unknown_email: Erabiltzaile ezezaguna. + notice_can_t_change_password: Kontu honek kanpoko autentikazio bat erabiltzen du. Ezinezkoa da pasahitza aldatzea. + notice_account_lost_email_sent: Pasahitz berria aukeratzeko jarraibideak dituen eposta bat bidali zaizu. + notice_account_activated: Zure kontua gaituta dago. Orain saioa has dezakezu + notice_successful_create: Sortze arrakastatsua. + notice_successful_update: Eguneratze arrakastatsua. + notice_successful_delete: Ezabaketa arrakastatsua. + notice_successful_connection: Konexio arrakastatsua. + notice_file_not_found: Atzitu nahi duzun orria ez da exisitzen edo ezabatua izan da. + notice_locking_conflict: Beste erabiltzaile batek datuak eguneratu ditu. + notice_not_authorized: Ez duzu orri hau atzitzeko baimenik. + notice_email_sent: "%{value} helbidera eposta bat bidali da" + notice_email_error: "Errorea eposta bidaltzean (%{value})" + notice_feeds_access_key_reseted: Zure Atom atzipen giltza berrezarri da. + notice_api_access_key_reseted: Zure API atzipen giltza berrezarri da. + notice_failed_to_save_issues: "Hautatutako %{total} zereginetatik %{count} ezin izan dira konpondu: %{ids}." + notice_no_issue_selected: "Ez da zereginik hautatu! Mesedez, editatu nahi dituzun arazoak markatu." + notice_account_pending: "Zure kontua sortu da, orain kudeatzailearen onarpenaren zain dago." + notice_default_data_loaded: Lehenetsitako konfigurazioa ongi kargatu da. + notice_unable_delete_version: Ezin da bertsioa ezabatu. + notice_issue_done_ratios_updated: Burututako zereginen erlazioa eguneratu da. + + error_can_t_load_default_data: "Ezin izan da lehenetsitako konfigurazioa kargatu: %{value}" + error_scm_not_found: "Sarrera edo berrikuspena ez da biltegian topatu." + error_scm_command_failed: "Errorea gertatu da biltegia atzitzean: %{value}" + error_scm_annotate: "Sarrera ez da existitzen edo ezin da anotatu." + error_issue_not_found_in_project: 'Zeregina ez da topatu edo ez da proiektu honetakoa' + error_no_tracker_in_project: 'Proiektu honek ez du aztarnaririk esleituta. Mesedez egiaztatu Proiektuaren ezarpenak.' + error_no_default_issue_status: 'Zereginek ez dute lehenetsitako egoerarik. Mesedez egiaztatu zure konfigurazioa ("Kudeaketa -> Arazoen egoerak" atalera joan).' + error_can_not_reopen_issue_on_closed_version: 'Itxitako bertsio batera esleitutako zereginak ezin dira berrireki' + error_can_not_archive_project: Proiektu hau ezin da artxibatu + error_issue_done_ratios_not_updated: "Burututako zereginen erlazioa ez da eguneratu." + error_workflow_copy_source: 'Mesedez hautatu iturburuko aztarnari edo rola' + error_workflow_copy_target: 'Mesedez hautatu helburuko aztarnari(ak) edo rola(k)' + + warning_attachments_not_saved: "%{count} fitxategi ezin izan d(ir)a gorde." + + mail_subject_lost_password: "Zure %{value} pasahitza" + mail_body_lost_password: 'Zure pasahitza aldatzeko hurrengo estekan klikatu:' + mail_subject_register: "Zure %{value} kontuaren gaitzea" + mail_body_register: 'Zure kontua gaitzeko hurrengo estekan klikatu:' + mail_body_account_information_external: "Zure %{value} kontua erabil dezakezu saioa hasteko." + mail_body_account_information: Zure kontuaren informazioa + mail_subject_account_activation_request: "%{value} kontu gaitzeko eskaera" + mail_body_account_activation_request: "Erabiltzaile berri bat (%{value}) erregistratu da. Kontua zure onarpenaren zain dago:" + mail_subject_reminder: "%{count} arazo hurrengo %{days} egunetan amaitzen d(ir)a" + mail_body_reminder: "Zuri esleituta dauden %{count} arazo hurrengo %{days} egunetan amaitzen d(ir)a:" + mail_subject_wiki_content_added: "'%{id}' wiki orria gehitu da" + mail_body_wiki_content_added: "%{author}-(e)k '%{id}' wiki orria gehitu du." + mail_subject_wiki_content_updated: "'%{id}' wiki orria eguneratu da" + mail_body_wiki_content_updated: "%{author}-(e)k '%{id}' wiki orria eguneratu du." + + + field_name: Izena + field_description: Deskribapena + field_summary: Laburpena + field_is_required: Beharrezkoa + field_firstname: Izena + field_lastname: Abizenak + field_mail: Eposta + field_filename: Fitxategia + field_filesize: Tamaina + field_downloads: Deskargak + field_author: Egilea + field_created_on: Sortuta + field_updated_on: Eguneratuta + field_field_format: Formatua + field_is_for_all: Proiektu guztietarako + field_possible_values: Balio posibleak + field_regexp: Expresio erregularra + field_min_length: Luzera minimoa + field_max_length: Luzera maxioma + field_value: Balioa + field_category: Kategoria + field_title: Izenburua + field_project: Proiektua + field_issue: Zeregina + field_status: Egoera + field_notes: Oharrak + field_is_closed: Itxitako arazoa + field_is_default: Lehenetsitako balioa + field_tracker: Aztarnaria + field_subject: Gaia + field_due_date: Amaiera data + field_assigned_to: Esleituta + field_priority: Lehentasuna + field_fixed_version: Helburuko bertsioa + field_user: Erabiltzilea + field_role: Rola + field_homepage: Orri nagusia + field_is_public: Publikoa + field_parent: "Honen azpiproiektua:" + field_is_in_roadmap: Arazoak ibilbide-mapan erakutsi + field_login: Erabiltzaile izena + field_mail_notification: Eposta jakinarazpenak + field_admin: Kudeatzailea + field_last_login_on: Azken konexioa + field_language: Hizkuntza + field_effective_date: Data + field_password: Pasahitza + field_new_password: Pasahitz berria + field_password_confirmation: Berrespena + field_version: Bertsioa + field_type: Mota + field_host: Ostalaria + field_port: Portua + field_account: Kontua + field_base_dn: Base DN + field_attr_login: Erabiltzaile atributua + field_attr_firstname: Izena atributua + field_attr_lastname: Abizenak atributua + field_attr_mail: Eposta atributua + field_onthefly: Zuzeneko erabiltzaile sorrera + field_start_date: Hasiera + field_done_ratio: Egindako % + field_auth_source: Autentikazio modua + field_hide_mail: Nire eposta helbidea ezkutatu + field_comments: Iruzkina + field_url: URL + field_start_page: Hasierako orria + field_subproject: Azpiproiektua + field_hours: Ordu + field_activity: Jarduera + field_spent_on: Data + field_identifier: Identifikatzailea + field_is_filter: Iragazki moduan erabilita + field_issue_to: Erlazionatutako zereginak + field_delay: Atzerapena + field_assignable: Arazoak rol honetara esleitu daitezke + field_redirect_existing_links: Existitzen diren estekak berbideratu + field_estimated_hours: Estimatutako denbora + field_column_names: Zutabeak + field_time_zone: Ordu zonaldea + field_searchable: Bilagarria + field_default_value: Lehenetsitako balioa + field_comments_sorting: Iruzkinak erakutsi + field_parent_title: Orri gurasoa + field_editable: Editagarria + field_watcher: Behatzailea + field_identity_url: OpenID URLa + field_content: Edukia + field_group_by: Emaitzak honegatik taldekatu + field_sharing: Partekatzea + + setting_app_title: Aplikazioaren izenburua + setting_app_subtitle: Aplikazioaren azpizenburua + setting_welcome_text: Ongietorriko testua + setting_default_language: Lehenetsitako hizkuntza + setting_login_required: Autentikazioa derrigorrezkoa + setting_self_registration: Norberak erregistratu + setting_attachment_max_size: Eranskinen tamaina max. + setting_issues_export_limit: Zereginen esportatze limitea + setting_mail_from: Igorlearen eposta helbidea + setting_bcc_recipients: Hartzaileak ezkutuko kopian (bcc) + setting_plain_text_mail: Testu soileko epostak (HTML-rik ez) + setting_host_name: Ostalari izena eta bidea + setting_text_formatting: Testu formatua + setting_wiki_compression: Wikiaren historia konprimitu + setting_feeds_limit: Jarioaren edukiera limitea + setting_default_projects_public: Proiektu berriak defektuz publikoak dira + setting_autofetch_changesets: Commit-ak automatikoki hartu + setting_sys_api_enabled: Biltegien kudeaketarako WS gaitu + setting_commit_ref_keywords: Erreferentzien gako-hitzak + setting_commit_fix_keywords: Konpontze gako-hitzak + setting_autologin: Saioa automatikoki hasi + setting_date_format: Data formatua + setting_time_format: Ordu formatua + setting_cross_project_issue_relations: Zereginak proiektuen artean erlazionatzea baimendu + setting_issue_list_default_columns: Zereginen zerrendan defektuz ikusten diren zutabeak + setting_emails_footer: Eposten oina + setting_protocol: Protokoloa + setting_per_page_options: Orriko objektuen aukerak + setting_user_format: Erabiltzaileak erakusteko formatua + setting_activity_days_default: Proiektuen jardueran erakusteko egunak + setting_display_subprojects_issues: Azpiproiektuen zereginak proiektu nagusian erakutsi defektuz + setting_enabled_scm: Gaitutako IKKak + setting_mail_handler_body_delimiters: "Lerro hauteko baten ondoren epostak moztu" + setting_mail_handler_api_enabled: Sarrerako epostentzako WS gaitu + setting_mail_handler_api_key: API giltza + setting_sequential_project_identifiers: Proiektuen identifikadore sekuentzialak sortu + setting_gravatar_enabled: Erabili Gravatar erabiltzaile ikonoak + setting_gravatar_default: Lehenetsitako Gravatar irudia + setting_diff_max_lines_displayed: Erakutsiko diren diff lerro kopuru maximoa + setting_file_max_size_displayed: Barnean erakuzten diren testu fitxategien tamaina maximoa + setting_repository_log_display_limit: Egunkari fitxategian erakutsiko diren berrikuspen kopuru maximoa. + setting_openid: Baimendu OpenID saio hasiera eta erregistatzea + setting_password_min_length: Pasahitzen luzera minimoa + setting_new_project_user_role_id: Proiektu berriak sortzerakoan kudeatzaile ez diren erabiltzaileei esleitutako rola + setting_default_projects_modules: Proiektu berrientzako defektuz gaitutako moduluak + setting_issue_done_ratio: "Zereginen burututako tasa kalkulatzean erabili:" + setting_issue_done_ratio_issue_field: Zeregin eremua erabili + setting_issue_done_ratio_issue_status: Zeregin egoera erabili + setting_start_of_week: "Egutegiak noiz hasi:" + setting_rest_api_enabled: Gaitu REST web zerbitzua + + permission_add_project: Proiektua sortu + permission_add_subprojects: Azpiproiektuak sortu + permission_edit_project: Proiektua editatu + permission_select_project_modules: Proiektuaren moduluak hautatu + permission_manage_members: Kideak kudeatu + permission_manage_versions: Bertsioak kudeatu + permission_manage_categories: Arazoen kategoriak kudeatu + permission_view_issues: Zereginak ikusi + permission_add_issues: Zereginak gehitu + permission_edit_issues: Zereginak aldatu + permission_manage_issue_relations: Zereginen erlazioak kudeatu + permission_add_issue_notes: Oharrak gehitu + permission_edit_issue_notes: Oharrak aldatu + permission_edit_own_issue_notes: Nork bere oharrak aldatu + permission_move_issues: Zereginak mugitu + permission_delete_issues: Zereginak ezabatu + permission_manage_public_queries: Galdera publikoak kudeatu + permission_save_queries: Galderak gorde + permission_view_gantt: Gantt grafikoa ikusi + permission_view_calendar: Egutegia ikusi + permission_view_issue_watchers: Behatzaileen zerrenda ikusi + permission_add_issue_watchers: Behatzaileak gehitu + permission_delete_issue_watchers: Behatzaileak ezabatu + permission_log_time: Igarotako denbora erregistratu + permission_view_time_entries: Igarotako denbora ikusi + permission_edit_time_entries: Denbora egunkariak editatu + permission_edit_own_time_entries: Nork bere denbora egunkariak editatu + permission_manage_news: Berriak kudeatu + permission_comment_news: Berrien iruzkinak egin + permission_view_documents: Dokumentuak ikusi + permission_manage_files: Fitxategiak kudeatu + permission_view_files: Fitxategiak ikusi + permission_manage_wiki: Wikia kudeatu + permission_rename_wiki_pages: Wiki orriak berrizendatu + permission_delete_wiki_pages: Wiki orriak ezabatu + permission_view_wiki_pages: Wikia ikusi + permission_view_wiki_edits: Wikiaren historia ikusi + permission_edit_wiki_pages: Wiki orriak editatu + permission_delete_wiki_pages_attachments: Eranskinak ezabatu + permission_protect_wiki_pages: Wiki orriak babestu + permission_manage_repository: Biltegiak kudeatu + permission_browse_repository: Biltegia arakatu + permission_view_changesets: Aldaketak ikusi + permission_commit_access: Commit atzipena + permission_manage_boards: Foroak kudeatu + permission_view_messages: Mezuak ikusi + permission_add_messages: Mezuak bidali + permission_edit_messages: Mezuak aldatu + permission_edit_own_messages: Nork bere mezuak aldatu + permission_delete_messages: Mezuak ezabatu + permission_delete_own_messages: Nork bere mezuak ezabatu + + project_module_issue_tracking: Zereginen jarraipena + project_module_time_tracking: Denbora jarraipena + project_module_news: Berriak + project_module_documents: Dokumentuak + project_module_files: Fitxategiak + project_module_wiki: Wiki + project_module_repository: Biltegia + project_module_boards: Foroak + + label_user: Erabiltzailea + label_user_plural: Erabiltzaileak + label_user_new: Erabiltzaile berria + label_user_anonymous: Ezezaguna + label_project: Proiektua + label_project_new: Proiektu berria + label_project_plural: Proiektuak + label_x_projects: + zero: proiekturik ez + one: proiektu bat + other: "%{count} proiektu" + label_project_all: Proiektu guztiak + label_project_latest: Azken proiektuak + label_issue: Zeregina + label_issue_new: Zeregin berria + label_issue_plural: Zereginak + label_issue_view_all: Zeregin guztiak ikusi + label_issues_by: "Zereginak honengatik: %{value}" + label_issue_added: Zeregina gehituta + label_issue_updated: Zeregina eguneratuta + label_document: Dokumentua + label_document_new: Dokumentu berria + label_document_plural: Dokumentuak + label_document_added: Dokumentua gehituta + label_role: Rola + label_role_plural: Rolak + label_role_new: Rol berria + label_role_and_permissions: Rolak eta baimenak + label_member: Kidea + label_member_new: Kide berria + label_member_plural: Kideak + label_tracker: Aztarnaria + label_tracker_plural: Aztarnariak + label_tracker_new: Aztarnari berria + label_workflow: Lan-fluxua + label_issue_status: Zeregin egoera + label_issue_status_plural: Zeregin egoerak + label_issue_status_new: Egoera berria + label_issue_category: Zeregin kategoria + label_issue_category_plural: Zeregin kategoriak + label_issue_category_new: Kategoria berria + label_custom_field: Eremu pertsonalizatua + label_custom_field_plural: Eremu pertsonalizatuak + label_custom_field_new: Eremu pertsonalizatu berria + label_enumerations: Enumerazioak + label_enumeration_new: Balio berria + label_information: Informazioa + label_information_plural: Informazioa + label_please_login: Saioa hasi mesedez + label_register: Erregistratu + label_login_with_open_id_option: edo OpenID-rekin saioa hasi + label_password_lost: Pasahitza galduta + label_home: Hasiera + label_my_page: Nire orria + label_my_account: Nire kontua + label_my_projects: Nire proiektuak + label_administration: Kudeaketa + label_login: Saioa hasi + label_logout: Saioa bukatu + label_help: Laguntza + label_reported_issues: Berri emandako zereginak + label_assigned_to_me_issues: Niri esleitutako arazoak + label_last_login: Azken konexioa + label_registered_on: Noiz erregistratuta + label_activity: Jarduerak + label_overall_activity: Jarduera guztiak + label_user_activity: "%{value}-(r)en jarduerak" + label_new: Berria + label_logged_as: "Sartutako erabiltzailea:" + label_environment: Ingurune + label_authentication: Autentikazioa + label_auth_source: Autentikazio modua + label_auth_source_new: Autentikazio modu berria + label_auth_source_plural: Autentikazio moduak + label_subproject_plural: Azpiproiektuak + label_subproject_new: Azpiproiektu berria + label_and_its_subprojects: "%{value} eta bere azpiproiektuak" + label_min_max_length: Luzera min - max + label_list: Zerrenda + label_date: Data + label_integer: Osokoa + label_float: Koma higikorrekoa + label_boolean: Boolearra + label_string: Testua + label_text: Testu luzea + label_attribute: Atributua + label_attribute_plural: Atributuak + label_no_data: Ez dago erakusteko daturik + label_change_status: Egoera aldatu + label_history: Historikoa + label_attachment: Fitxategia + label_attachment_new: Fitxategi berria + label_attachment_delete: Fitxategia ezabatu + label_attachment_plural: Fitxategiak + label_file_added: Fitxategia gehituta + label_report: Berri ematea + label_report_plural: Berri emateak + label_news: Berria + label_news_new: Berria gehitu + label_news_plural: Berriak + label_news_latest: Azken berriak + label_news_view_all: Berri guztiak ikusi + label_news_added: Berria gehituta + label_settings: Ezarpenak + label_overview: Gainbegirada + label_version: Bertsioa + label_version_new: Bertsio berria + label_version_plural: Bertsioak + label_close_versions: Burututako bertsioak itxi + label_confirmation: Baieztapena + label_export_to: 'Eskuragarri baita:' + label_read: Irakurri... + label_public_projects: Proiektu publikoak + label_open_issues: irekita + label_open_issues_plural: irekiak + label_closed_issues: itxita + label_closed_issues_plural: itxiak + label_x_open_issues_abbr_on_total: + zero: 0 irekita / %{total} + one: 1 irekita / %{total} + other: "%{count} irekiak / %{total}" + label_x_open_issues_abbr: + zero: 0 irekita + one: 1 irekita + other: "%{count} irekiak" + label_x_closed_issues_abbr: + zero: 0 itxita + one: 1 itxita + other: "%{count} itxiak" + label_total: Guztira + label_permissions: Baimenak + label_current_status: Uneko egoera + label_new_statuses_allowed: Baimendutako egoera berriak + label_all: guztiak + label_none: ezer + label_nobody: inor + label_next: Hurrengoa + label_previous: Aurrekoak + label_used_by: Erabilita + label_details: Xehetasunak + label_add_note: Oharra gehitu + label_per_page: Orriko + label_calendar: Egutegia + label_months_from: hilabete noiztik + label_gantt: Gantt + label_internal: Barnekoa + label_last_changes: "azken %{count} aldaketak" + label_change_view_all: Aldaketa guztiak ikusi + label_personalize_page: Orri hau pertsonalizatu + label_comment: Iruzkin + label_comment_plural: Iruzkinak + label_x_comments: + zero: iruzkinik ez + one: iruzkin 1 + other: "%{count} iruzkin" + label_comment_add: Iruzkina gehitu + label_comment_added: Iruzkina gehituta + label_comment_delete: Iruzkinak ezabatu + label_query: Galdera pertsonalizatua + label_query_plural: Pertsonalizatutako galderak + label_query_new: Galdera berria + label_filter_add: Iragazkia gehitu + label_filter_plural: Iragazkiak + label_equals: da + label_not_equals: ez da + label_in_less_than: baino gutxiagotan + label_in_more_than: baino gehiagotan + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: hauetan + label_today: gaur + label_all_time: denbora guztia + label_yesterday: atzo + label_this_week: aste honetan + label_last_week: pasadan astean + label_last_n_days: "azken %{count} egunetan" + label_this_month: hilabete hau + label_last_month: pasadan hilabetea + label_this_year: urte hau + label_date_range: Data tartea + label_less_than_ago: egun hauek baino gutxiago + label_more_than_ago: egun hauek baino gehiago + label_ago: orain dela + label_contains: dauka + label_not_contains: ez dauka + label_day_plural: egun + label_repository: Biltegia + label_repository_plural: Biltegiak + label_browse: Arakatu + label_branch: Adarra + label_tag: Etiketa + label_revision: Berrikuspena + label_revision_plural: Berrikuspenak + label_revision_id: "%{value} berrikuspen" + label_associated_revisions: Elkartutako berrikuspenak + label_added: gehituta + label_modified: aldatuta + label_copied: kopiatuta + label_renamed: berrizendatuta + label_deleted: ezabatuta + label_latest_revision: Azken berrikuspena + label_latest_revision_plural: Azken berrikuspenak + label_view_revisions: Berrikuspenak ikusi + label_view_all_revisions: Berrikuspen guztiak ikusi + label_max_size: Tamaina maximoa + label_sort_highest: Goraino mugitu + label_sort_higher: Gora mugitu + label_sort_lower: Behera mugitu + label_sort_lowest: Beheraino mugitu + label_roadmap: Ibilbide-mapa + label_roadmap_due_in: "Epea: %{value}" + label_roadmap_overdue: "%{value} berandu" + label_roadmap_no_issues: Ez dago zereginik bertsio honetan + label_search: Bilatu + label_result_plural: Emaitzak + label_all_words: hitz guztiak + label_wiki: Wikia + label_wiki_edit: Wiki edizioa + label_wiki_edit_plural: Wiki edizioak + label_wiki_page: Wiki orria + label_wiki_page_plural: Wiki orriak + label_index_by_title: Izenburuaren araberako indizea + label_index_by_date: Dataren araberako indizea + label_current_version: Uneko bertsioa + label_preview: Aurreikusi + label_feed_plural: Jarioak + label_changes_details: Aldaketa guztien xehetasunak + label_issue_tracking: Zeregin jarraipena + label_spent_time: Igarotako denbora + label_f_hour: "ordu %{value}" + label_f_hour_plural: "%{value} ordu" + label_time_tracking: Denbora jarraipena + label_change_plural: Aldaketak + label_statistics: Estatistikak + label_commits_per_month: Commit-ak hilabeteka + label_commits_per_author: Commit-ak egileka + label_view_diff: Ezberdintasunak ikusi + label_diff_inline: barnean + label_diff_side_by_side: aldez alde + label_options: Aukerak + label_copy_workflow_from: Kopiatu workflow-a hemendik + label_permissions_report: Baimenen txostena + label_watched_issues: Behatutako zereginak + label_related_issues: Erlazionatutako zereginak + label_applied_status: Aplikatutako egoera + label_loading: Kargatzen... + label_relation_new: Erlazio berria + label_relation_delete: Erlazioa ezabatu + label_relates_to: erlazionatuta dago + label_duplicates: bikoizten du + label_duplicated_by: honek bikoiztuta + label_blocks: blokeatzen du + label_blocked_by: honek blokeatuta + label_precedes: aurretik doa + label_follows: jarraitzen du + label_end_to_start: bukaeratik hasierara + label_end_to_end: bukaeratik bukaerara + label_start_to_start: hasieratik hasierhasieratik bukaerara + label_start_to_end: hasieratik bukaerara + label_stay_logged_in: Saioa mantendu + label_disabled: ezgaituta + label_show_completed_versions: Bukatutako bertsioak ikusi + label_me: ni + label_board: Foroa + label_board_new: Foro berria + label_board_plural: Foroak + label_topic_plural: Gaiak + label_message_plural: Mezuak + label_message_last: Azken mezua + label_message_new: Mezu berria + label_message_posted: Mezua gehituta + label_reply_plural: Erantzunak + label_send_information: Erabiltzaileai kontuaren informazioa bidali + label_year: Urtea + label_month: Hilabetea + label_week: Astea + label_date_from: Nork + label_date_to: Nori + label_language_based: Erabiltzailearen hizkuntzaren arabera + label_sort_by: "Ordenazioa: %{value}" + label_send_test_email: Frogako mezua bidali + label_feeds_access_key: Atom atzipen giltza + label_missing_feeds_access_key: Atom atzipen giltza falta da + label_feeds_access_key_created_on: "Atom atzipen giltza orain dela %{value} sortuta" + label_module_plural: Moduluak + label_added_time_by: "%{author}, orain dela %{age} gehituta" + label_updated_time_by: "%{author}, orain dela %{age} eguneratuta" + label_updated_time: "Orain dela %{value} eguneratuta" + label_jump_to_a_project: Joan proiektura... + label_file_plural: Fitxategiak + label_changeset_plural: Aldaketak + label_default_columns: Lehenetsitako zutabeak + label_no_change_option: (Aldaketarik ez) + label_bulk_edit_selected_issues: Hautatutako zereginak batera editatu + label_theme: Itxura + label_default: Lehenetsia + label_search_titles_only: Izenburuetan bakarrik bilatu + label_user_mail_option_all: "Nire proiektu guztietako gertakari guztientzat" + label_user_mail_option_selected: "Hautatutako proiektuetako edozein gertakarientzat..." + label_user_mail_no_self_notified: "Ez dut nik egiten ditudan aldeketen jakinarazpenik jaso nahi" + label_registration_activation_by_email: kontuak epostaz gaitu + label_registration_manual_activation: kontuak eskuz gaitu + label_registration_automatic_activation: kontuak automatikoki gaitu + label_display_per_page: "Orriko: %{value}" + label_age: Adina + label_change_properties: Propietateak aldatu + label_general: Orokorra + label_more: Gehiago + label_scm: IKK + label_plugins: Pluginak + label_ldap_authentication: LDAP autentikazioa + label_downloads_abbr: Desk. + label_optional_description: Aukerako deskribapena + label_add_another_file: Beste fitxategia gehitu + label_preferences: Hobespenak + label_chronological_order: Orden kronologikoan + label_reverse_chronological_order: Alderantzizko orden kronologikoan + label_planning: Planifikazioa + label_incoming_emails: Sarrerako epostak + label_generate_key: Giltza sortu + label_issue_watchers: Behatzaileak + label_example: Adibidea + label_display: Bistaratzea + label_sort: Ordenatu + label_ascending: Gorantz + label_descending: Beherantz + label_date_from_to: "%{start}-tik %{end}-ra" + label_wiki_content_added: Wiki orria gehituta + label_wiki_content_updated: Wiki orria eguneratuta + label_group: Taldea + label_group_plural: Taldeak + label_group_new: Talde berria + label_time_entry_plural: Igarotako denbora + label_version_sharing_none: Ez partekatuta + label_version_sharing_descendants: Azpiproiektuekin + label_version_sharing_hierarchy: Proiektu Hierarkiarekin + label_version_sharing_tree: Proiektu zuhaitzarekin + label_version_sharing_system: Proiektu guztiekin + label_update_issue_done_ratios: Zereginen burututako erlazioa eguneratu + label_copy_source: Iturburua + label_copy_target: Helburua + label_copy_same_as_target: Helburuaren berdina + label_display_used_statuses_only: Aztarnari honetan erabiltzen diren egoerak bakarrik erakutsi + label_api_access_key: API atzipen giltza + label_missing_api_access_key: API atzipen giltza falta da + label_api_access_key_created_on: "API atzipen giltza sortuta orain dela %{value}" + + button_login: Saioa hasi + button_submit: Bidali + button_save: Gorde + button_check_all: Guztiak markatu + button_uncheck_all: Guztiak desmarkatu + button_delete: Ezabatu + button_create: Sortu + button_create_and_continue: Sortu eta jarraitu + button_test: Frogatu + button_edit: Editatu + button_add: Gehitu + button_change: Aldatu + button_apply: Aplikatu + button_clear: Garbitu + button_lock: Blokeatu + button_unlock: Desblokeatu + button_download: Deskargatu + button_list: Zerrenda + button_view: Ikusi + button_move: Mugitu + button_move_and_follow: Mugitu eta jarraitu + button_back: Atzera + button_cancel: Ezeztatu + button_activate: Gahitu + button_sort: Ordenatu + button_log_time: Denbora erregistratu + button_rollback: Itzuli bertsio honetara + button_watch: Behatu + button_unwatch: Behatzen utzi + button_reply: Erantzun + button_archive: Artxibatu + button_unarchive: Desartxibatu + button_reset: Berrezarri + button_rename: Berrizendatu + button_change_password: Pasahitza aldatu + button_copy: Kopiatu + button_copy_and_follow: Kopiatu eta jarraitu + button_annotate: Anotatu + button_update: Eguneratu + button_configure: Konfiguratu + button_quote: Aipatu + button_duplicate: Bikoiztu + button_show: Ikusi + + status_active: gaituta + status_registered: izena emanda + status_locked: blokeatuta + + version_status_open: irekita + version_status_locked: blokeatuta + version_status_closed: itxita + + field_active: Gaituta + + text_select_mail_notifications: Jakinarazpenak zein ekintzetarako bidaliko diren hautatu. + text_regexp_info: adib. ^[A-Z0-9]+$ + text_min_max_length_info: 0k mugarik gabe esan nahi du + text_project_destroy_confirmation: Ziur zaude proiektu hau eta erlazionatutako datu guztiak ezabatu nahi dituzula? + text_subprojects_destroy_warning: "%{value} azpiproiektuak ere ezabatuko dira." + text_workflow_edit: Hautatu rola eta aztarnaria workflow-a editatzeko + text_are_you_sure: Ziur zaude? + text_journal_changed: "%{label} %{old}-(e)tik %{new}-(e)ra aldatuta" + text_journal_set_to: "%{label}-k %{value} balioa hartu du" + text_journal_deleted: "%{label} ezabatuta (%{old})" + text_journal_added: "%{label} %{value} gehituta" + text_tip_issue_begin_day: gaur hasten diren zereginak + text_tip_issue_end_day: gaur bukatzen diren zereginak + text_tip_issue_begin_end_day: gaur hasi eta bukatzen diren zereginak + text_caracters_maximum: "%{count} karaktere gehienez." + text_caracters_minimum: "Gutxienez %{count} karaktereetako luzerakoa izan behar du." + text_length_between: "Luzera %{min} eta %{max} karaktereen artekoa." + text_tracker_no_workflow: Ez da workflow-rik definitu aztarnari honentzako + text_unallowed_characters: Debekatutako karaktereak + text_comma_separated: Balio anitz izan daitezke (komaz banatuta). + text_line_separated: Balio anitz izan daitezke (balio bakoitza lerro batean). + text_issues_ref_in_commit_messages: Commit-en mezuetan zereginak erlazionatu eta konpontzen + text_issue_added: "%{id} zeregina %{author}-(e)k jakinarazi du." + text_issue_updated: "%{id} zeregina %{author}-(e)k eguneratu du." + text_wiki_destroy_confirmation: Ziur zaude wiki hau eta bere eduki guztiak ezabatu nahi dituzula? + text_issue_category_destroy_question: "Zeregin batzuk (%{count}) kategoria honetara esleituta daude. Zer egin nahi duzu?" + text_issue_category_destroy_assignments: Kategoria esleipenak kendu + text_issue_category_reassign_to: Zereginak kategoria honetara esleitu + text_user_mail_option: "Hautatu gabeko proiektuetan, behatzen edo parte hartzen duzun gauzei buruzko jakinarazpenak jasoko dituzu (adib. zu egile zaren edo esleituta dituzun zereginak)." + text_no_configuration_data: "Rolak, aztarnariak, zeregin egoerak eta workflow-ak ez dira oraindik konfiguratu.\nOso gomendagarria de lehenetsitako kkonfigurazioa kargatzea. Kargatu eta gero aldatu ahalko duzu." + text_load_default_configuration: Lehenetsitako konfigurazioa kargatu + text_status_changed_by_changeset: "%{value} aldaketan aplikatuta." + text_issues_destroy_confirmation: 'Ziur zaude hautatutako zeregina(k) ezabatu nahi dituzula?' + text_select_project_modules: 'Hautatu proiektu honetan gaitu behar diren moduluak:' + text_default_administrator_account_changed: Lehenetsitako kudeatzaile kontua aldatuta + text_file_repository_writable: Eranskinen direktorioan idatz daiteke + text_plugin_assets_writable: Pluginen baliabideen direktorioan idatz daiteke + text_rmagick_available: RMagick eskuragarri (aukerazkoa) + text_destroy_time_entries_question: "%{hours} orduei buruz berri eman zen zuk ezabatzera zoazen zereginean. Zer egin nahi duzu?" + text_destroy_time_entries: Ezabatu berri emandako orduak + text_assign_time_entries_to_project: Berri emandako orduak proiektura esleitu + text_reassign_time_entries: 'Berri emandako orduak zeregin honetara esleitu:' + text_user_wrote: "%{value}-(e)k idatzi zuen:" + text_enumeration_destroy_question: "%{count} objetu balio honetara esleituta daude." + text_enumeration_category_reassign_to: 'Beste balio honetara esleitu:' + text_email_delivery_not_configured: "Eposta bidalketa ez dago konfiguratuta eta jakinarazpenak ezgaituta daude.\nKonfiguratu zure SMTP zerbitzaria config/configuration.yml-n eta aplikazioa berrabiarazi hauek gaitzeko." + text_repository_usernames_mapping: "Hautatu edo eguneratu Redmineko erabiltzailea biltegiko egunkarietan topatzen diren erabiltzaile izenekin erlazionatzeko.\nRedmine-n eta biltegian erabiltzaile izen edo eposta berdina duten erabiltzaileak automatikoki erlazionatzen dira." + text_diff_truncated: '... Diff hau moztua izan da erakus daitekeen tamaina maximoa gainditu duelako.' + text_custom_field_possible_values_info: 'Lerro bat balio bakoitzeko' + text_wiki_page_destroy_question: "Orri honek %{descendants} orri seme eta ondorengo ditu. Zer egin nahi duzu?" + text_wiki_page_nullify_children: "Orri semeak erro orri moduan mantendu" + text_wiki_page_destroy_children: "Orri semeak eta beraien ondorengo guztiak ezabatu" + text_wiki_page_reassign_children: "Orri semeak orri guraso honetara esleitu" + text_own_membership_delete_confirmation: "Zure baimen batzuk (edo guztiak) kentzera zoaz eta baliteke horren ondoren proiektu hau ezin editatzea.\n Ziur zaude jarraitu nahi duzula?" + + default_role_manager: Kudeatzailea + default_role_developer: Garatzailea + default_role_reporter: Berriemailea + default_tracker_bug: Errorea + default_tracker_feature: Eginbidea + default_tracker_support: Laguntza + default_issue_status_new: Berria + default_issue_status_in_progress: Lanean + default_issue_status_resolved: Ebatzita + default_issue_status_feedback: Berrelikadura + default_issue_status_closed: Itxita + default_issue_status_rejected: Baztertua + default_doc_category_user: Erabiltzaile dokumentazioa + default_doc_category_tech: Dokumentazio teknikoa + default_priority_low: Baxua + default_priority_normal: Normala + default_priority_high: Altua + default_priority_urgent: Larria + default_priority_immediate: Berehalakoa + default_activity_design: Diseinua + default_activity_development: Garapena + + enumeration_issue_priorities: Zeregin lehentasunak + enumeration_doc_categories: Dokumentu kategoriak + enumeration_activities: Jarduerak (denbora kontrola)) + enumeration_system_activity: Sistemako Jarduera + label_board_sticky: Itsaskorra + label_board_locked: Blokeatuta + permission_export_wiki_pages: Wiki orriak esportatu + setting_cache_formatted_text: Formatudun testua katxeatu + permission_manage_project_activities: Proiektuaren jarduerak kudeatu + error_unable_delete_issue_status: Ezine da zereginaren egoera ezabatu + label_profile: Profila + permission_manage_subtasks: Azpiatazak kudeatu + field_parent_issue: Zeregin gurasoa + label_subtask_plural: Azpiatazak + label_project_copy_notifications: Proiektua kopiatzen den bitartean eposta jakinarazpenak bidali + error_can_not_delete_custom_field: Ezin da eremu pertsonalizatua ezabatu + error_unable_to_connect: Ezin da konektatu (%{value}) + error_can_not_remove_role: Rol hau erabiltzen hari da eta ezin da ezabatu. + error_can_not_delete_tracker: Aztarnari honek zereginak ditu eta ezin da ezabatu. + field_principal: Ekintzaile + label_my_page_block: "Nire orriko blokea" + notice_failed_to_save_members: "Kidea(k) gordetzean errorea: %{errors}." + text_zoom_out: Zooma txikiagotu + text_zoom_in: Zooma handiagotu + notice_unable_delete_time_entry: "Ezin da hautatutako denbora erregistroa ezabatu." + label_overall_spent_time: Igarotako denbora guztira + field_time_entries: "Denbora erregistratu" + project_module_gantt: Gantt + project_module_calendar: Egutegia + button_edit_associated_wikipage: "Esleitutako wiki orria editatu: %{page_title}" + field_text: Testu eremua + label_user_mail_option_only_owner: "Jabea naizen gauzetarako barrarik" + setting_default_notification_option: "Lehenetsitako ohartarazpen aukera" + label_user_mail_option_only_my_events: "Behatzen ditudan edo partaide naizen gauzetarako bakarrik" + label_user_mail_option_only_assigned: "Niri esleitutako gauzentzat bakarrik" + label_user_mail_option_none: "Gertakaririk ez" + field_member_of_group: "Esleituta duenaren taldea" + field_assigned_to_role: "Esleituta duenaren rola" + notice_not_authorized_archived_project: "Atzitu nahi duzun proiektua artxibatua izan da." + label_principal_search: "Bilatu erabiltzaile edo taldea:" + label_user_search: "Erabiltzailea bilatu:" + field_visible: Ikusgai + setting_emails_header: "Eposten goiburua" + setting_commit_logtime_activity_id: "Erregistratutako denboraren jarduera" + text_time_logged_by_changeset: "%{value} aldaketan egindakoa." + setting_commit_logtime_enabled: "Erregistrutako denbora gaitu" + notice_gantt_chart_truncated: Grafikoa moztu da bistara daitekeen elementuen kopuru maximoa gainditu delako (%{max}) + setting_gantt_items_limit: "Gantt grafikoan bistara daitekeen elementu kopuru maximoa" + field_warn_on_leaving_unsaved: Gorde gabeko testua duen orri batetik ateratzen naizenean ohartarazi + text_warn_on_leaving_unsaved: Uneko orritik joaten bazara gorde gabeko testua galduko da. + label_my_queries: Nire galdera pertsonalizatuak + text_journal_changed_no_detail: "%{label} eguneratuta" + label_news_comment_added: Berri batera iruzkina gehituta + button_expand_all: Guztia zabaldu + button_collapse_all: Guztia tolestu + label_additional_workflow_transitions_for_assignee: Erabiltzaileak esleitua duenean baimendutako transtsizio gehigarriak + label_additional_workflow_transitions_for_author: Erabiltzailea egilea denean baimendutako transtsizio gehigarriak + label_bulk_edit_selected_time_entries: Hautatutako denbora egunkariak batera editatu + text_time_entries_destroy_confirmation: Ziur zaude hautatutako denbora egunkariak ezabatu nahi dituzula? + label_role_anonymous: Ezezaguna + label_role_non_member: Ez kidea + label_issue_note_added: Oharra gehituta + label_issue_status_updated: Egoera eguneratuta + label_issue_priority_updated: Lehentasuna eguneratuta + label_issues_visibility_own: Erabiltzaileak sortu edo esleituta dituen zereginak + field_issues_visibility: Zeregin ikusgarritasuna + label_issues_visibility_all: Zeregin guztiak + permission_set_own_issues_private: Nork bere zereginak publiko edo pribatu jarri + field_is_private: Pribatu + permission_set_issues_private: Zereginak publiko edo pribatu jarri + label_issues_visibility_public: Pribatu ez diren zeregin guztiak + text_issues_destroy_descendants_confirmation: Honek %{count} azpiataza ezabatuko ditu baita ere. + field_commit_logs_encoding: Commit-en egunkarien kodetzea + field_scm_path_encoding: Bidearen kodeketa + text_scm_path_encoding_note: "Lehentsita: UTF-8" + field_path_to_repository: Biltegirako bidea + field_root_directory: Erro direktorioa + field_cvs_module: Modulua + field_cvsroot: CVSROOT + text_mercurial_repository_note: Biltegi locala (adib. /hgrepo, c:\hgrepo) + text_scm_command: Komandoa + text_scm_command_version: Bertsioa + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 zeregina + one: 1 zeregina + other: "%{count} zereginak" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: guztiak + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Azpiproiektuekin + label_cross_project_tree: Proiektu zuhaitzarekin + label_cross_project_hierarchy: Proiektu Hierarkiarekin + label_cross_project_system: Proiektu guztiekin + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Guztira + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/13/132a841db337d7fa641930666ad83dea5d0196ed.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/13/132a841db337d7fa641930666ad83dea5d0196ed.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class Tracker < ActiveRecord::Base + + CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject description priority_id is_private).freeze + # Fields that can be disabled + # Other (future) fields should be appended, not inserted! + CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours done_ratio).freeze + CORE_FIELDS_ALL = (CORE_FIELDS_UNDISABLABLE + CORE_FIELDS).freeze + + before_destroy :check_integrity + has_many :issues + has_many :workflow_rules, :dependent => :delete_all do + def copy(source_tracker) + WorkflowRule.copy(source_tracker, nil, proxy_association.owner, nil) + end + end + + has_and_belongs_to_many :projects + has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id' + acts_as_list + + attr_protected :fields_bits + + validates_presence_of :name + validates_uniqueness_of :name + validates_length_of :name, :maximum => 30 + + scope :sorted, lambda { order("#{table_name}.position ASC") } + scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} + + def to_s; name end + + def <=>(tracker) + position <=> tracker.position + end + + # Returns an array of IssueStatus that are used + # in the tracker's workflows + def issue_statuses + if @issue_statuses + return @issue_statuses + elsif new_record? + return [] + end + + ids = WorkflowTransition. + connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{WorkflowTransition.table_name} WHERE tracker_id = #{id} AND type = 'WorkflowTransition'"). + flatten. + uniq + + @issue_statuses = IssueStatus.find_all_by_id(ids).sort + end + + def disabled_core_fields + i = -1 + @disabled_core_fields ||= CORE_FIELDS.select { i += 1; (fields_bits || 0) & (2 ** i) != 0} + end + + def core_fields + CORE_FIELDS - disabled_core_fields + end + + def core_fields=(fields) + raise ArgumentError.new("Tracker.core_fields takes an array") unless fields.is_a?(Array) + + bits = 0 + CORE_FIELDS.each_with_index do |field, i| + unless fields.include?(field) + bits |= 2 ** i + end + end + self.fields_bits = bits + @disabled_core_fields = nil + core_fields + end + + # Returns the fields that are disabled for all the given trackers + def self.disabled_core_fields(trackers) + if trackers.present? + trackers.uniq.map(&:disabled_core_fields).reduce(:&) + else + [] + end + end + + # Returns the fields that are enabled for one tracker at least + def self.core_fields(trackers) + if trackers.present? + trackers.uniq.map(&:core_fields).reduce(:|) + else + CORE_FIELDS.dup + end + end + +private + def check_integrity + raise Exception.new("Can't delete tracker") if Issue.where(:tracker_id => self.id).any? + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/13/135f3b3b4d0bbdb13b1af83c8e387d0dfa750b59.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/13/135f3b3b4d0bbdb13b1af83c8e387d0dfa750b59.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,43 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module ContextMenusHelper + def context_menu_link(name, url, options={}) + options[:class] ||= '' + if options.delete(:selected) + options[:class] << ' icon-checked disabled' + options[:disabled] = true + end + if options.delete(:disabled) + options.delete(:method) + options.delete(:data) + options[:onclick] = 'return false;' + options[:class] << ' disabled' + url = '#' + end + link_to h(name), url, options + end + + def bulk_update_custom_field_context_menu_link(field, text, value) + context_menu_link h(text), + bulk_update_issues_path(:ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back), + :method => :post, + :selected => (@issue && @issue.custom_field_value(field) == value) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/13/138f43d777efffd6ecf4f9d5f88f24c526eb7287.svn-base --- a/.svn/pristine/13/138f43d777efffd6ecf4f9d5f88f24c526eb7287.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -module ActionView - module Helpers - # Provides methods for linking to ActionController::Pagination objects using a simple generator API. You can optionally - # also build your links manually using ActionView::Helpers::AssetHelper#link_to like so: - # - # <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %> - # <%= link_to "Next page", { :page => paginator.current.next } if paginator.current.next %> - module PaginationHelper - unless const_defined?(:DEFAULT_OPTIONS) - DEFAULT_OPTIONS = { - :name => :page, - :window_size => 2, - :always_show_anchors => true, - :link_to_current_page => false, - :params => {} - } - end - - # Creates a basic HTML link bar for the given +paginator+. Links will be created - # for the next and/or previous page and for a number of other pages around the current - # pages position. The +html_options+ hash is passed to +link_to+ when the links are created. - # - # ==== Options - # :name:: the routing name for this paginator - # (defaults to +page+) - # :prefix:: prefix for pagination links - # (i.e. Older Pages: 1 2 3 4) - # :suffix:: suffix for pagination links - # (i.e. 1 2 3 4 <- Older Pages) - # :window_size:: the number of pages to show around - # the current page (defaults to 2) - # :always_show_anchors:: whether or not the first and last - # pages should always be shown - # (defaults to +true+) - # :link_to_current_page:: whether or not the current page - # should be linked to (defaults to - # +false+) - # :params:: any additional routing parameters - # for page URLs - # - # ==== Examples - # # We'll assume we have a paginator setup in @person_pages... - # - # pagination_links(@person_pages) - # # => 1 2 3 ... 10 - # - # pagination_links(@person_pages, :link_to_current_page => true) - # # => 1 2 3 ... 10 - # - # pagination_links(@person_pages, :always_show_anchors => false) - # # => 1 2 3 - # - # pagination_links(@person_pages, :window_size => 1) - # # => 1 2 ... 10 - # - # pagination_links(@person_pages, :params => { :viewer => "flash" }) - # # => 1 2 3 ... - # # 10 - def pagination_links(paginator, options={}, html_options={}) - name = options[:name] || DEFAULT_OPTIONS[:name] - params = (options[:params] || DEFAULT_OPTIONS[:params]).clone - - prefix = options[:prefix] || '' - suffix = options[:suffix] || '' - - pagination_links_each(paginator, options, prefix, suffix) do |n| - params[name] = n - link_to(n.to_s, params, html_options) - end - end - - # Iterate through the pages of a given +paginator+, invoking a - # block for each page number that needs to be rendered as a link. - # - # ==== Options - # :window_size:: the number of pages to show around - # the current page (defaults to +2+) - # :always_show_anchors:: whether or not the first and last - # pages should always be shown - # (defaults to +true+) - # :link_to_current_page:: whether or not the current page - # should be linked to (defaults to - # +false+) - # - # ==== Example - # # Turn paginated links into an Ajax call - # pagination_links_each(paginator, page_options) do |link| - # options = { :url => {:action => 'list'}, :update => 'results' } - # html_options = { :href => url_for(:action => 'list') } - # - # link_to_remote(link.to_s, options, html_options) - # end - def pagination_links_each(paginator, options, prefix = nil, suffix = nil) - options = DEFAULT_OPTIONS.merge(options) - link_to_current_page = options[:link_to_current_page] - always_show_anchors = options[:always_show_anchors] - - current_page = paginator.current_page - window_pages = current_page.window(options[:window_size]).pages - return if window_pages.length <= 1 unless link_to_current_page - - first, last = paginator.first, paginator.last - - html = '' - - html << prefix if prefix - - if always_show_anchors and not (wp_first = window_pages[0]).first? - html << yield(first.number) - html << ' ... ' if wp_first.number - first.number > 1 - html << ' ' - end - - window_pages.each do |page| - if current_page == page && !link_to_current_page - html << page.number.to_s - else - html << yield(page.number) - end - html << ' ' - end - - if always_show_anchors and not (wp_last = window_pages[-1]).last? - html << ' ... ' if last.number - wp_last.number > 1 - html << yield(last.number) - end - - html << suffix if suffix - - html - end - - end # PaginationHelper - end # Helpers -end # ActionView diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/13/13a70fe72c228213d71cc1e6f52274fd3abf3062.svn-base --- a/.svn/pristine/13/13a70fe72c228213d71cc1e6f52274fd3abf3062.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Enumeration < ActiveRecord::Base - include Redmine::SubclassFactory - - default_scope :order => "#{Enumeration.table_name}.position ASC" - - belongs_to :project - - acts_as_list :scope => 'type = \'#{type}\'' - acts_as_customizable - acts_as_tree :order => 'position ASC' - - before_destroy :check_integrity - before_save :check_default - - attr_protected :type - - validates_presence_of :name - validates_uniqueness_of :name, :scope => [:type, :project_id] - validates_length_of :name, :maximum => 30 - - scope :shared, where(:project_id => nil) - scope :sorted, order("#{table_name}.position ASC") - scope :active, where(:active => true) - scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} - - def self.default - # Creates a fake default scope so Enumeration.default will check - # it's type. STI subclasses will automatically add their own - # types to the finder. - if self.descends_from_active_record? - where(:is_default => true, :type => 'Enumeration').first - else - # STI classes are - where(:is_default => true).first - end - end - - # Overloaded on concrete classes - def option_name - nil - end - - def check_default - if is_default? && is_default_changed? - Enumeration.update_all({:is_default => false}, {:type => type}) - end - end - - # Overloaded on concrete classes - def objects_count - 0 - end - - def in_use? - self.objects_count != 0 - end - - # Is this enumeration overiding a system level enumeration? - def is_override? - !self.parent.nil? - end - - alias :destroy_without_reassign :destroy - - # Destroy the enumeration - # If a enumeration is specified, objects are reassigned - def destroy(reassign_to = nil) - if reassign_to && reassign_to.is_a?(Enumeration) - self.transfer_relations(reassign_to) - end - destroy_without_reassign - end - - def <=>(enumeration) - position <=> enumeration.position - end - - def to_s; name end - - # Returns the Subclasses of Enumeration. Each Subclass needs to be - # required in development mode. - # - # Note: subclasses is protected in ActiveRecord - def self.get_subclasses - subclasses - end - - # Does the +new+ Hash override the previous Enumeration? - def self.overridding_change?(new, previous) - if (same_active_state?(new['active'], previous.active)) && same_custom_values?(new,previous) - return false - else - return true - end - end - - # Does the +new+ Hash have the same custom values as the previous Enumeration? - def self.same_custom_values?(new, previous) - previous.custom_field_values.each do |custom_value| - if custom_value.value != new["custom_field_values"][custom_value.custom_field_id.to_s] - return false - end - end - - return true - end - - # Are the new and previous fields equal? - def self.same_active_state?(new, previous) - new = (new == "1" ? true : false) - return new == previous - end - -private - def check_integrity - raise "Can't delete enumeration" if self.in_use? - end - -end - -# Force load the subclasses in development mode -require_dependency 'time_entry_activity' -require_dependency 'document_category' -require_dependency 'issue_priority' diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/13/13f1c7e824023a07ff2178b5bb3f5f1ee4de792e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/13/13f1c7e824023a07ff2178b5bb3f5f1ee4de792e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,25 @@ + + +Redmine 500 error + + +

    Internal error

    +

    An error occurred on the page you were trying to access.
    + If you continue to experience problems please contact your Redmine administrator for assistance.

    +

    If you are the Redmine administrator, check your log files for details about the error.

    +

    Back

    + + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/14/141194219711b0b953dd51beb008d5ce7e421ba0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/14/141194219711b0b953dd51beb008d5ce7e421ba0.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,211 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * This file is part of DotClear. + * Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All + * rights reserved. + * + * DotClear 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. + * + * DotClear 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 DotClear; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ***** END LICENSE BLOCK ***** +*/ + +/* Modified by JP LANG for textile formatting */ + +// strong +jsToolBar.prototype.elements.strong = { + type: 'button', + title: 'Strong', + fn: { + wiki: function() { this.singleTag('*') } + } +} + +// em +jsToolBar.prototype.elements.em = { + type: 'button', + title: 'Italic', + fn: { + wiki: function() { this.singleTag("_") } + } +} + +// ins +jsToolBar.prototype.elements.ins = { + type: 'button', + title: 'Underline', + fn: { + wiki: function() { this.singleTag('+') } + } +} + +// del +jsToolBar.prototype.elements.del = { + type: 'button', + title: 'Deleted', + fn: { + wiki: function() { this.singleTag('-') } + } +} + +// code +jsToolBar.prototype.elements.code = { + type: 'button', + title: 'Code', + fn: { + wiki: function() { this.singleTag('@') } + } +} + +// spacer +jsToolBar.prototype.elements.space1 = {type: 'space'} + +// headings +jsToolBar.prototype.elements.h1 = { + type: 'button', + title: 'Heading 1', + fn: { + wiki: function() { + this.encloseLineSelection('h1. ', '',function(str) { + str = str.replace(/^h\d+\.\s+/, '') + return str; + }); + } + } +} +jsToolBar.prototype.elements.h2 = { + type: 'button', + title: 'Heading 2', + fn: { + wiki: function() { + this.encloseLineSelection('h2. ', '',function(str) { + str = str.replace(/^h\d+\.\s+/, '') + return str; + }); + } + } +} +jsToolBar.prototype.elements.h3 = { + type: 'button', + title: 'Heading 3', + fn: { + wiki: function() { + this.encloseLineSelection('h3. ', '',function(str) { + str = str.replace(/^h\d+\.\s+/, '') + return str; + }); + } + } +} + +// spacer +jsToolBar.prototype.elements.space2 = {type: 'space'} + +// ul +jsToolBar.prototype.elements.ul = { + type: 'button', + title: 'Unordered list', + fn: { + wiki: function() { + this.encloseLineSelection('','',function(str) { + str = str.replace(/\r/g,''); + return str.replace(/(\n|^)[#-]?\s*/g,"$1* "); + }); + } + } +} + +// ol +jsToolBar.prototype.elements.ol = { + type: 'button', + title: 'Ordered list', + fn: { + wiki: function() { + this.encloseLineSelection('','',function(str) { + str = str.replace(/\r/g,''); + return str.replace(/(\n|^)[*-]?\s*/g,"$1# "); + }); + } + } +} + +// spacer +jsToolBar.prototype.elements.space3 = {type: 'space'} + +// bq +jsToolBar.prototype.elements.bq = { + type: 'button', + title: 'Quote', + fn: { + wiki: function() { + this.encloseLineSelection('','',function(str) { + str = str.replace(/\r/g,''); + return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2"); + }); + } + } +} + +// unbq +jsToolBar.prototype.elements.unbq = { + type: 'button', + title: 'Unquote', + fn: { + wiki: function() { + this.encloseLineSelection('','',function(str) { + str = str.replace(/\r/g,''); + return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2"); + }); + } + } +} + +// pre +jsToolBar.prototype.elements.pre = { + type: 'button', + title: 'Preformatted text', + fn: { + wiki: function() { this.encloseLineSelection('
    \n', '\n
    ') } + } +} + +// spacer +jsToolBar.prototype.elements.space4 = {type: 'space'} + +// wiki page +jsToolBar.prototype.elements.link = { + type: 'button', + title: 'Wiki link', + fn: { + wiki: function() { this.encloseSelection("[[", "]]") } + } +} +// image +jsToolBar.prototype.elements.img = { + type: 'button', + title: 'Image', + fn: { + wiki: function() { this.encloseSelection("!", "!") } + } +} + +// spacer +jsToolBar.prototype.elements.space5 = {type: 'space'} +// help +jsToolBar.prototype.elements.help = { + type: 'button', + title: 'Help', + fn: { + wiki: function() { window.open(this.help_link, '', 'resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes') } + } +} diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/14/1411d2a90c9093fc6ada5ec42e5d250259f58396.svn-base --- a/.svn/pristine/14/1411d2a90c9093fc6ada5ec42e5d250259f58396.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class SettingsController < ApplicationController - layout 'admin' - menu_item :plugins, :only => :plugin - - before_filter :require_admin - - def index - edit - render :action => 'edit' - end - - def edit - @notifiables = Redmine::Notifiable.all - if request.post? && params[:settings] && params[:settings].is_a?(Hash) - settings = (params[:settings] || {}).dup.symbolize_keys - settings.each do |name, value| - # remove blank values in array settings - value.delete_if {|v| v.blank? } if value.is_a?(Array) - Setting[name] = value - end - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'edit', :tab => params[:tab] - else - @options = {} - user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]} - @options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]} - @deliveries = ActionMailer::Base.perform_deliveries - - @guessed_host_and_path = request.host_with_port.dup - @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? - - Redmine::Themes.rescan - end - end - - def plugin - @plugin = Redmine::Plugin.find(params[:id]) - if request.post? - Setting.send "plugin_#{@plugin.id}=", params[:settings] - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'plugin', :id => @plugin.id - else - @partial = @plugin.settings[:partial] - @settings = Setting.send "plugin_#{@plugin.id}" - end - rescue Redmine::PluginNotFound - render_404 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/14/14189ddfaeae5a33a107c23246aa22c4efcabc20.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/14/14189ddfaeae5a33a107c23246aa22c4efcabc20.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,169 @@ +--- +users_004: + created_on: 2006-07-19 19:34:07 +02:00 + status: 1 + last_login_on: + language: en + # password = foo + salt: 3126f764c3c5ac61cbfc103f25f934cf + hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b + updated_on: 2006-07-19 19:34:07 +02:00 + admin: false + mail: rhill@somenet.foo + lastname: Hill + firstname: Robert + id: 4 + auth_source_id: + mail_notification: all + login: rhill + type: User +users_001: + created_on: 2006-07-19 19:12:21 +02:00 + status: 1 + last_login_on: 2006-07-19 22:57:52 +02:00 + language: en + # password = admin + salt: 82090c953c4a0000a7db253b0691a6b4 + hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150 + updated_on: 2006-07-19 22:57:52 +02:00 + admin: true + mail: admin@somenet.foo + lastname: Admin + firstname: Redmine + id: 1 + auth_source_id: + mail_notification: all + login: admin + type: User +users_002: + created_on: 2006-07-19 19:32:09 +02:00 + status: 1 + last_login_on: 2006-07-19 22:42:15 +02:00 + language: en + # password = jsmith + salt: 67eb4732624d5a7753dcea7ce0bb7d7d + hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc + updated_on: 2006-07-19 22:42:15 +02:00 + admin: false + mail: jsmith@somenet.foo + lastname: Smith + firstname: John + id: 2 + auth_source_id: + mail_notification: all + login: jsmith + type: User +users_003: + created_on: 2006-07-19 19:33:19 +02:00 + status: 1 + last_login_on: + language: en + # password = foo + salt: 7599f9963ec07b5a3b55b354407120c0 + hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: dlopper@somenet.foo + lastname: Lopper + firstname: Dave + id: 3 + auth_source_id: + mail_notification: all + login: dlopper + type: User +users_005: + id: 5 + created_on: 2006-07-19 19:33:19 +02:00 + # Locked + status: 3 + last_login_on: + language: en + hashed_password: 1 + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: dlopper2@somenet.foo + lastname: Lopper2 + firstname: Dave2 + auth_source_id: + mail_notification: all + login: dlopper2 + type: User +users_006: + id: 6 + created_on: 2006-07-19 19:33:19 +02:00 + status: 0 + last_login_on: + language: '' + hashed_password: 1 + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: '' + lastname: Anonymous + firstname: '' + auth_source_id: + mail_notification: only_my_events + login: '' + type: AnonymousUser +users_007: + # A user who does not belong to any project + id: 7 + created_on: 2006-07-19 19:33:19 +02:00 + status: 1 + last_login_on: + language: 'en' + # password = foo + salt: 7599f9963ec07b5a3b55b354407120c0 + hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: someone@foo.bar + lastname: One + firstname: Some + auth_source_id: + mail_notification: only_my_events + login: someone + type: User +users_008: + id: 8 + created_on: 2006-07-19 19:33:19 +02:00 + status: 1 + last_login_on: + language: 'it' + # password = foo + salt: 7599f9963ec07b5a3b55b354407120c0 + hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: miscuser8@foo.bar + lastname: Misc + firstname: User + auth_source_id: + mail_notification: only_my_events + login: miscuser8 + type: User +users_009: + id: 9 + created_on: 2006-07-19 19:33:19 +02:00 + status: 1 + last_login_on: + language: 'it' + hashed_password: 1 + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: miscuser9@foo.bar + lastname: Misc + firstname: User + auth_source_id: + mail_notification: only_my_events + login: miscuser9 + type: User +groups_010: + id: 10 + lastname: A Team + type: Group +groups_011: + id: 11 + lastname: B Team + type: Group + + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/14/14309d64e537e5e954feb549559e6200d128036a.svn-base --- a/.svn/pristine/14/14309d64e537e5e954feb549559e6200d128036a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,326 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class CustomField < ActiveRecord::Base - include Redmine::SubclassFactory - - has_many :custom_values, :dependent => :delete_all - acts_as_list :scope => 'type = \'#{self.class}\'' - serialize :possible_values - - validates_presence_of :name, :field_format - validates_uniqueness_of :name, :scope => :type - validates_length_of :name, :maximum => 30 - validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats - - validate :validate_custom_field - before_validation :set_searchable - - CUSTOM_FIELDS_TABS = [ - {:name => 'IssueCustomField', :partial => 'custom_fields/index', - :label => :label_issue_plural}, - {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', - :label => :label_spent_time}, - {:name => 'ProjectCustomField', :partial => 'custom_fields/index', - :label => :label_project_plural}, - {:name => 'VersionCustomField', :partial => 'custom_fields/index', - :label => :label_version_plural}, - {:name => 'UserCustomField', :partial => 'custom_fields/index', - :label => :label_user_plural}, - {:name => 'GroupCustomField', :partial => 'custom_fields/index', - :label => :label_group_plural}, - {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', - :label => TimeEntryActivity::OptionName}, - {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', - :label => IssuePriority::OptionName}, - {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', - :label => DocumentCategory::OptionName} - ] - - CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]} - - def field_format=(arg) - # cannot change format of a saved custom field - super if new_record? - end - - def set_searchable - # make sure these fields are not searchable - self.searchable = false if %w(int float date bool).include?(field_format) - # make sure only these fields can have multiple values - self.multiple = false unless %w(list user version).include?(field_format) - true - end - - def validate_custom_field - if self.field_format == "list" - errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty? - errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array - end - - if regexp.present? - begin - Regexp.new(regexp) - rescue - errors.add(:regexp, :invalid) - end - end - - if default_value.present? && !valid_field_value?(default_value) - errors.add(:default_value, :invalid) - end - end - - def possible_values_options(obj=nil) - case field_format - when 'user', 'version' - if obj.respond_to?(:project) && obj.project - case field_format - when 'user' - obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]} - when 'version' - obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]} - end - elsif obj.is_a?(Array) - obj.collect {|o| possible_values_options(o)}.reduce(:&) - else - [] - end - when 'bool' - [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']] - else - possible_values || [] - end - end - - def possible_values(obj=nil) - case field_format - when 'user', 'version' - possible_values_options(obj).collect(&:last) - when 'bool' - ['1', '0'] - else - values = super() - if values.is_a?(Array) - values.each do |value| - value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) - end - end - values || [] - end - end - - # Makes possible_values accept a multiline string - def possible_values=(arg) - if arg.is_a?(Array) - super(arg.compact.collect(&:strip).select {|v| !v.blank?}) - else - self.possible_values = arg.to_s.split(/[\n\r]+/) - end - end - - def cast_value(value) - casted = nil - unless value.blank? - case field_format - when 'string', 'text', 'list' - casted = value - when 'date' - casted = begin; value.to_date; rescue; nil end - when 'bool' - casted = (value == '1' ? true : false) - when 'int' - casted = value.to_i - when 'float' - casted = value.to_f - when 'user', 'version' - casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i)) - end - end - casted - end - - def value_from_keyword(keyword, customized) - possible_values_options = possible_values_options(customized) - if possible_values_options.present? - keyword = keyword.to_s.downcase - if v = possible_values_options.detect {|text, id| text.downcase == keyword} - if v.is_a?(Array) - v.last - else - v - end - end - else - keyword - end - end - - # Returns a ORDER BY clause that can used to sort customized - # objects by their value of the custom field. - # Returns nil if the custom field can not be used for sorting. - def order_statement - return nil if multiple? - case field_format - when 'string', 'text', 'list', 'date', 'bool' - # COALESCE is here to make sure that blank and NULL values are sorted equally - "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" + - " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + - " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + - " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" - when 'int', 'float' - # Make the database cast values into numeric - # Postgresql will raise an error if a value can not be casted! - # CustomValue validations should ensure that it doesn't occur - "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" + - " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + - " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + - " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)" - when 'user', 'version' - value_class.fields_for_order_statement(value_join_alias) - else - nil - end - end - - # Returns a GROUP BY clause that can used to group by custom value - # Returns nil if the custom field can not be used for grouping. - def group_statement - return nil if multiple? - case field_format - when 'list', 'date', 'bool', 'int' - order_statement - when 'user', 'version' - "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" + - " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + - " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + - " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" - else - nil - end - end - - def join_for_order_statement - case field_format - when 'user', 'version' - "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + - " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + - " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + - " AND #{join_alias}.custom_field_id = #{id}" + - " AND #{join_alias}.value <> ''" + - " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + - " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + - " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + - " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + - " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" + - " ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id" - else - nil - end - end - - def join_alias - "cf_#{id}" - end - - def value_join_alias - join_alias + "_" + field_format - end - - def <=>(field) - position <=> field.position - end - - # Returns the class that values represent - def value_class - case field_format - when 'user', 'version' - field_format.classify.constantize - else - nil - end - end - - def self.customized_class - self.name =~ /^(.+)CustomField$/ - begin; $1.constantize; rescue nil; end - end - - # to move in project_custom_field - def self.for_all - find(:all, :conditions => ["is_for_all=?", true], :order => 'position') - end - - def type_name - nil - end - - # Returns the error messages for the given value - # or an empty array if value is a valid value for the custom field - def validate_field_value(value) - errs = [] - if value.is_a?(Array) - if !multiple? - errs << ::I18n.t('activerecord.errors.messages.invalid') - end - if is_required? && value.detect(&:present?).nil? - errs << ::I18n.t('activerecord.errors.messages.blank') - end - value.each {|v| errs += validate_field_value_format(v)} - else - if is_required? && value.blank? - errs << ::I18n.t('activerecord.errors.messages.blank') - end - errs += validate_field_value_format(value) - end - errs - end - - # Returns true if value is a valid value for the custom field - def valid_field_value?(value) - validate_field_value(value).empty? - end - - def format_in?(*args) - args.include?(field_format) - end - - protected - - # Returns the error message for the given value regarding its format - def validate_field_value_format(value) - errs = [] - if value.present? - errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp) - errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length - errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length - - # Format specific validations - case field_format - when 'int' - errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/ - when 'float' - begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end - when 'date' - errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end - when 'list' - errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value) - end - end - errs - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/14/14672d79390f5ddff335388aeceb3a8564ea9087.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/14/14672d79390f5ddff335388aeceb3a8564ea9087.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,209 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class Role < ActiveRecord::Base + # Custom coder for the permissions attribute that should be an + # array of symbols. Rails 3 uses Psych which can be *unbelievably* + # slow on some platforms (eg. mingw32). + class PermissionsAttributeCoder + def self.load(str) + str.to_s.scan(/:([a-z0-9_]+)/).flatten.map(&:to_sym) + end + + def self.dump(value) + YAML.dump(value) + end + end + + # Built-in roles + BUILTIN_NON_MEMBER = 1 + BUILTIN_ANONYMOUS = 2 + + ISSUES_VISIBILITY_OPTIONS = [ + ['all', :label_issues_visibility_all], + ['default', :label_issues_visibility_public], + ['own', :label_issues_visibility_own] + ] + + scope :sorted, lambda { order("#{table_name}.builtin ASC, #{table_name}.position ASC") } + scope :givable, lambda { order("#{table_name}.position ASC").where(:builtin => 0) } + scope :builtin, lambda { |*args| + compare = (args.first == true ? 'not' : '') + where("#{compare} builtin = 0") + } + + before_destroy :check_deletable + has_many :workflow_rules, :dependent => :delete_all do + def copy(source_role) + WorkflowRule.copy(nil, source_role, nil, proxy_association.owner) + end + end + has_and_belongs_to_many :custom_fields, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "role_id" + + has_many :member_roles, :dependent => :destroy + has_many :members, :through => :member_roles + acts_as_list + + serialize :permissions, ::Role::PermissionsAttributeCoder + attr_protected :builtin + + validates_presence_of :name + validates_uniqueness_of :name + validates_length_of :name, :maximum => 30 + validates_inclusion_of :issues_visibility, + :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first), + :if => lambda {|role| role.respond_to?(:issues_visibility)} + + # Copies attributes from another role, arg can be an id or a Role + def copy_from(arg, options={}) + return unless arg.present? + role = arg.is_a?(Role) ? arg : Role.find_by_id(arg.to_s) + self.attributes = role.attributes.dup.except("id", "name", "position", "builtin", "permissions") + self.permissions = role.permissions.dup + self + end + + def permissions=(perms) + perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms + write_attribute(:permissions, perms) + end + + def add_permission!(*perms) + self.permissions = [] unless permissions.is_a?(Array) + + permissions_will_change! + perms.each do |p| + p = p.to_sym + permissions << p unless permissions.include?(p) + end + save! + end + + def remove_permission!(*perms) + return unless permissions.is_a?(Array) + permissions_will_change! + perms.each { |p| permissions.delete(p.to_sym) } + save! + end + + # Returns true if the role has the given permission + def has_permission?(perm) + !permissions.nil? && permissions.include?(perm.to_sym) + end + + def <=>(role) + if role + if builtin == role.builtin + position <=> role.position + else + builtin <=> role.builtin + end + else + -1 + end + end + + def to_s + name + end + + def name + case builtin + when 1; l(:label_role_non_member, :default => read_attribute(:name)) + when 2; l(:label_role_anonymous, :default => read_attribute(:name)) + else; read_attribute(:name) + end + end + + # Return true if the role is a builtin role + def builtin? + self.builtin != 0 + end + + # Return true if the role is the anonymous role + def anonymous? + builtin == 2 + end + + # Return true if the role is a project member role + def member? + !self.builtin? + end + + # Return true if role is allowed to do the specified action + # action can be: + # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') + # * a permission Symbol (eg. :edit_project) + def allowed_to?(action) + if action.is_a? Hash + allowed_actions.include? "#{action[:controller]}/#{action[:action]}" + else + allowed_permissions.include? action + end + end + + # Return all the permissions that can be given to the role + def setable_permissions + setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions + setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER + setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS + setable_permissions + end + + # Find all the roles that can be given to a project member + def self.find_all_givable + Role.givable.all + end + + # Return the builtin 'non member' role. If the role doesn't exist, + # it will be created on the fly. + def self.non_member + find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member') + end + + # Return the builtin 'anonymous' role. If the role doesn't exist, + # it will be created on the fly. + def self.anonymous + find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous') + end + +private + + def allowed_permissions + @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name} + end + + def allowed_actions + @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten + end + + def check_deletable + raise "Can't delete role" if members.any? + raise "Can't delete builtin role" if builtin? + end + + def self.find_or_create_system_role(builtin, name) + role = where(:builtin => builtin).first + if role.nil? + role = create(:name => name, :position => 0) do |r| + r.builtin = builtin + end + raise "Unable to create the #{name} role." if role.new_record? + end + role + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/14/146e04b787ce58184275bdd027f7e85c4c9ff45f.svn-base --- a/.svn/pristine/14/146e04b787ce58184275bdd027f7e85c4c9ff45f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -

    <%=l(:label_information_plural)%>

    - -

    <%= Redmine::Info.versioned_name %>

    - - -<% @checklist.each do |label, result| %> - - - - -<% end %> -
    <%= l(label) %><%= image_tag((result ? 'true.png' : 'exclamation.png'), - :style => "vertical-align:bottom;") %>
    -
    -
    -
    <%= Redmine::Info.environment %>
    -
    - -<% html_title(l(:label_information_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/14/149fe7d9ecdbec895382a22950f9b721ccb3953a.svn-base --- a/.svn/pristine/14/149fe7d9ecdbec895382a22950f9b721ccb3953a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class EnumerationsController < ApplicationController - layout 'admin' - - before_filter :require_admin, :except => :index - before_filter :require_admin_or_api_request, :only => :index - before_filter :build_new_enumeration, :only => [:new, :create] - before_filter :find_enumeration, :only => [:edit, :update, :destroy] - accept_api_auth :index - - helper :custom_fields - - def index - respond_to do |format| - format.html - format.api { - @klass = Enumeration.get_subclass(params[:type]) - if @klass - @enumerations = @klass.shared.sorted.all - else - render_404 - end - } - end - end - - def new - end - - def create - if request.post? && @enumeration.save - flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' - else - render :action => 'new' - end - end - - def edit - end - - def update - if request.put? && @enumeration.update_attributes(params[:enumeration]) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' - else - render :action => 'edit' - end - end - - def destroy - if !@enumeration.in_use? - # No associated objects - @enumeration.destroy - redirect_to :action => 'index' - return - elsif params[:reassign_to_id] - if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id]) - @enumeration.destroy(reassign_to) - redirect_to :action => 'index' - return - end - end - @enumerations = @enumeration.class.all - [@enumeration] - end - - private - - def build_new_enumeration - class_name = params[:enumeration] && params[:enumeration][:type] || params[:type] - @enumeration = Enumeration.new_subclass_instance(class_name, params[:enumeration]) - if @enumeration.nil? - render_404 - end - end - - def find_enumeration - @enumeration = Enumeration.find(params[:id]) - rescue ActiveRecord::RecordNotFound - render_404 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/14/14e3e22e1b23a096d9a03825e5ed303e8453da90.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/14/14e3e22e1b23a096d9a03825e5ed303e8453da90.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,77 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class UserPreferenceTest < ActiveSupport::TestCase + fixtures :users, :user_preferences + + def test_create + user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") + user.login = "newuser" + user.password, user.password_confirmation = "password", "password" + assert user.save + + assert_kind_of UserPreference, user.pref + assert_kind_of Hash, user.pref.others + assert user.pref.save + end + + def test_update + user = User.find(1) + assert_equal true, user.pref.hide_mail + user.pref['preftest'] = 'value' + assert user.pref.save + + user.reload + assert_equal 'value', user.pref['preftest'] + end + + def test_others_hash + user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") + user.login = "newuser" + user.password, user.password_confirmation = "password", "password" + assert user.save + assert_nil user.preference + up = UserPreference.new(:user => user) + assert_kind_of Hash, up.others + up.others = nil + assert_nil up.others + assert up.save + assert_kind_of Hash, up.others + end + + def test_others_should_be_blank_after_initialization + pref = User.new.pref + assert_equal({}, pref.others) + end + + def test_reading_value_from_nil_others_hash + up = UserPreference.new(:user => User.new) + up.others = nil + assert_nil up.others + assert_nil up[:foo] + end + + def test_writing_value_to_nil_others_hash + up = UserPreference.new(:user => User.new) + up.others = nil + assert_nil up.others + up[:foo] = 'bar' + assert_equal 'bar', up[:foo] + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/14/14fc658e5b9713c5a1ccf4d137bc5d9602c668ab.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/14/14fc658e5b9713c5a1ccf4d137bc5d9602c668ab.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,105 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class IssueStatus < ActiveRecord::Base + before_destroy :check_integrity + has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id" + acts_as_list + + before_destroy :delete_workflow_rules + after_save :update_default + + validates_presence_of :name + validates_uniqueness_of :name + validates_length_of :name, :maximum => 30 + validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true + + scope :sorted, lambda { order("#{table_name}.position ASC") } + scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} + + def update_default + IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default? + end + + # Returns the default status for new issues + def self.default + where(:is_default => true).first + end + + # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+ + def self.update_issue_done_ratios + if Issue.use_status_for_done_ratio? + IssueStatus.where("default_done_ratio >= 0").all.each do |status| + Issue.update_all({:done_ratio => status.default_done_ratio}, {:status_id => status.id}) + end + end + + return Issue.use_status_for_done_ratio? + end + + # Returns an array of all statuses the given role can switch to + # Uses association cache when called more than one time + def new_statuses_allowed_to(roles, tracker, author=false, assignee=false) + if roles && tracker + role_ids = roles.collect(&:id) + transitions = workflows.select do |w| + role_ids.include?(w.role_id) && + w.tracker_id == tracker.id && + ((!w.author && !w.assignee) || (author && w.author) || (assignee && w.assignee)) + end + transitions.map(&:new_status).compact.sort + else + [] + end + end + + # Same thing as above but uses a database query + # More efficient than the previous method if called just once + def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false) + if roles.present? && tracker + conditions = "(author = :false AND assignee = :false)" + conditions << " OR author = :true" if author + conditions << " OR assignee = :true" if assignee + + workflows. + includes(:new_status). + where(["role_id IN (:role_ids) AND tracker_id = :tracker_id AND (#{conditions})", + {:role_ids => roles.collect(&:id), :tracker_id => tracker.id, :true => true, :false => false} + ]).all. + map(&:new_status).compact.sort + else + [] + end + end + + def <=>(status) + position <=> status.position + end + + def to_s; name end + + private + + def check_integrity + raise "Can't delete status" if Issue.where(:status_id => id).any? + end + + # Deletes associated workflows + def delete_workflow_rules + WorkflowRule.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}]) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/15/155d97c62e3af9eb00c14de47a9d0bc572afcd07.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/15/155d97c62e3af9eb00c14de47a9d0bc572afcd07.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,75 @@ +
    +<% if @editable %> +<% if @content.current_version? %> + <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> + <%= watcher_link(@page, User.current) %> + <%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %> + <%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %> + <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') %> + <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page.title}, :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon icon-del') %> +<% else %> + <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page.title, :version => @content.version }, :class => 'icon icon-cancel') %> +<% end %> +<% end %> +<%= link_to_if_authorized(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history') %> +
    + +<%= wiki_page_breadcrumb(@page) %> + +<% unless @content.current_version? %> + <%= title [@page.pretty_title, project_wiki_page_path(@page.project, @page.title, :version => nil)], + [l(:label_history), history_project_wiki_page_path(@page.project, @page.title)], + "#{l(:label_version)} #{@content.version}" %> + +

    + <%= link_to(("\xc2\xab " + l(:label_previous)), + :action => 'show', :id => @page.title, :project_id => @page.project, + :version => @content.previous.version) + " - " if @content.previous %> + <%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %> + <%= '('.html_safe + link_to(l(:label_diff), :controller => 'wiki', :action => 'diff', + :id => @page.title, :project_id => @page.project, + :version => @content.version) + ')'.html_safe if @content.previous %> - + <%= link_to((l(:label_next) + " \xc2\xbb"), :action => 'show', + :id => @page.title, :project_id => @page.project, + :version => @content.next.version) + " - " if @content.next %> + <%= link_to(l(:label_current_version), :action => 'show', :id => @page.title, :project_id => @page.project, :version => nil) %> +
    + <%= @content.author ? link_to_user(@content.author) : l(:label_user_anonymous) + %>, <%= format_time(@content.updated_on) %>
    + <%=h @content.comments %> +

    +
    +<% end %> + +<%= render(:partial => "wiki/content", :locals => {:content => @content}) %> + +<%= link_to_attachments @page %> + +<% if @editable && authorize_for('wiki', 'add_attachment') %> +
    +

    <%= link_to l(:label_attachment_new), {}, :onclick => "$('#add_attachment_form').show(); return false;", + :id => 'attach_files_link' %>

    +<%= form_tag({:controller => 'wiki', :action => 'add_attachment', + :project_id => @project, :id => @page.title}, + :multipart => true, :id => "add_attachment_form", + :style => "display:none;") do %> +
    +

    <%= render :partial => 'attachments/form' %>

    +
    +<%= submit_tag l(:button_add) %> +<%= link_to l(:button_cancel), {}, :onclick => "$('#add_attachment_form').hide(); return false;" %> +<% end %> +
    +<% end %> + +<% other_formats_links do |f| %> + <%= f.link_to 'PDF', :url => {:id => @page.title, :version => params[:version]} %> + <%= f.link_to 'HTML', :url => {:id => @page.title, :version => params[:version]} %> + <%= f.link_to 'TXT', :url => {:id => @page.title, :version => params[:version]} %> +<% end if User.current.allowed_to?(:export_wiki_pages, @project) %> + +<% content_for :sidebar do %> + <%= render :partial => 'sidebar' %> +<% end %> + +<% html_title @page.pretty_title %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/15/1562dffbb640b06caf248c7277e18410c01feded.svn-base --- a/.svn/pristine/15/1562dffbb640b06caf248c7277e18410c01feded.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class Redmine::CodesetUtilTest < ActiveSupport::TestCase - - def test_to_utf8_by_setting_from_latin1 - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - s1 = "Texte encod\xc3\xa9" - s2 = "Texte encod\xe9" - s3 = s2.dup - if s1.respond_to?(:force_encoding) - s1.force_encoding("UTF-8") - s2.force_encoding("ASCII-8BIT") - s3.force_encoding("UTF-8") - end - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2) - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3) - end - end - - def test_to_utf8_by_setting_from_euc_jp - with_settings :repositories_encodings => 'UTF-8,EUC-JP' do - s1 = "\xe3\x83\xac\xe3\x83\x83\xe3\x83\x89\xe3\x83\x9e\xe3\x82\xa4\xe3\x83\xb3" - s2 = "\xa5\xec\xa5\xc3\xa5\xc9\xa5\xde\xa5\xa4\xa5\xf3" - s3 = s2.dup - if s1.respond_to?(:force_encoding) - s1.force_encoding("UTF-8") - s2.force_encoding("ASCII-8BIT") - s3.force_encoding("UTF-8") - end - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2) - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3) - end - end - - def test_to_utf8_by_setting_should_be_converted_all_latin1 - with_settings :repositories_encodings => 'ISO-8859-1' do - s1 = "\xc3\x82\xc2\x80" - s2 = "\xC2\x80" - s3 = s2.dup - if s1.respond_to?(:force_encoding) - s1.force_encoding("UTF-8") - s2.force_encoding("ASCII-8BIT") - s3.force_encoding("UTF-8") - end - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2) - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3) - end - end - - def test_to_utf8_by_setting_blank_string - assert_equal "", Redmine::CodesetUtil.to_utf8_by_setting("") - assert_equal nil, Redmine::CodesetUtil.to_utf8_by_setting(nil) - end - - def test_to_utf8_by_setting_returns_ascii_as_utf8 - s1 = "ASCII" - s2 = s1.dup - if s1.respond_to?(:force_encoding) - s1.force_encoding("UTF-8") - s2.force_encoding("ISO-8859-1") - end - str1 = Redmine::CodesetUtil.to_utf8_by_setting(s1) - str2 = Redmine::CodesetUtil.to_utf8_by_setting(s2) - assert_equal s1, str1 - assert_equal s1, str2 - if s1.respond_to?(:force_encoding) - assert_equal "UTF-8", str1.encoding.to_s - assert_equal "UTF-8", str2.encoding.to_s - end - end - - def test_to_utf8_by_setting_invalid_utf8_sequences_should_be_stripped - with_settings :repositories_encodings => '' do - # s1 = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") - s1 = "Texte encod\xe9 en ISO-8859-1." - s1.force_encoding("ASCII-8BIT") if s1.respond_to?(:force_encoding) - str = Redmine::CodesetUtil.to_utf8_by_setting(s1) - if str.respond_to?(:force_encoding) - assert str.valid_encoding? - assert_equal "UTF-8", str.encoding.to_s - end - assert_equal "Texte encod? en ISO-8859-1.", str - end - end - - def test_to_utf8_by_setting_invalid_utf8_sequences_should_be_stripped_ja_jis - with_settings :repositories_encodings => 'ISO-2022-JP' do - s1 = "test\xb5\xfetest\xb5\xfe" - s1.force_encoding("ASCII-8BIT") if s1.respond_to?(:force_encoding) - str = Redmine::CodesetUtil.to_utf8_by_setting(s1) - if str.respond_to?(:force_encoding) - assert str.valid_encoding? - assert_equal "UTF-8", str.encoding.to_s - end - assert_equal "test??test??", str - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/15/1563561117c58a8d11c2e6605c5ed92c8394ca39.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/15/1563561117c58a8d11c2e6605c5ed92c8394ca39.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,70 @@ +Installing gems for testing +=========================== + +Remove your .bundle/config if you've already installed Redmine without +the test dependencies. Then, run `bundle install`. + +Running Tests +============= + +Run `rake --tasks test` to see available tests. +Run `rake test` to run the entire test suite (except the tests for the +Apache perl module Redmine.pm and Capybara tests, see below). + +You can run `ruby test/unit/issue_test.rb` for running a single test case and +`ruby test/unit/issue_test.rb -n test_create` for running a single test. + +Before running tests, you need to configure both development +and test databases. + +Creating test repositories +========================== + +Redmine supports a wide array of different version control systems. +To test the support, a test repository needs to be created for each of those. + +Run `rake --tasks test:scm:setup` for a list of available test-repositories or +run `rake test:scm:setup:all` to set up all of them. The repositories are +unpacked into {redmine_root}/tmp/test. + +If the test repositories are not present, the tests that need them will be +skipped. + +Creating a test ldap database +============================= + +Redmine supports using LDAP for user authentications. To test LDAP +with Redmine, load the LDAP export from test/fixtures/ldap/test-ldap.ldif +into a testing LDAP server. Make sure that the LDAP server can be accessed +at 127.0.0.1 on port 389. + +Setting up the test LDAP server is beyond the scope of this documentation. +The OpenLDAP project provides a simple LDAP implementation that should work +good as a test server. + +If the LDAP is not available, the tests that need it will be skipped. + +Running Redmine.pm tests +======================== + +(work in progress) + +Running the tests for the Redmine.pm perl module needs a bit more setup. +You need an Apache server with mod_perl, mod_dav_svn and Redmine.pm configured. +See: http://www.redmine.org/projects/redmine/wiki/Repositories_access_control_with_apache_mod_dav_svn_and_mod_perl + +You need an empty repository accessible at http://127.0.0.1/svn/ecookbook +Then, you can run the tests with: +`ruby test\extra\redmine_pm\repository_subversion_test.rb` + +If you svn server is not running on localhost, you can use the REDMINE_TEST_DAV_SERVER +environment variable to specify another host. + +Running Capybara tests +====================== + +You need to have PhantomJS WebDriver listening on port 4444: +`phantomjs --webdriver 4444` + +Capybara tests can be run with: +`rake test:ui` diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/15/15a8c97e1386ace05f99c6101e9b7756be58a55c.svn-base --- a/.svn/pristine/15/15a8c97e1386ace05f99c6101e9b7756be58a55c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class CommentObserver < ActiveRecord::Observer - def after_create(comment) - if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added') - Mailer.news_comment_added(comment).deliver - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/15/15c23dcf913596b86dcad4baa04c5641b5253d49.svn-base --- a/.svn/pristine/15/15c23dcf913596b86dcad4baa04c5641b5253d49.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1122 +0,0 @@ -# Swedish translation for Ruby on Rails -# by Johan Lundström (johanlunds@gmail.com), -# with parts taken from http://github.com/daniel/swe_rails - -sv: - number: - # Used in number_with_delimiter() - # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' - format: - # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) - separator: "," - # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) - delimiter: "." - # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) - precision: 2 - - # Used in number_to_currency() - currency: - format: - # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) - format: "%n %u" - unit: "kr" - # These three are to override number.format and are optional - # separator: "." - # delimiter: "," - # precision: 2 - - # Used in number_to_percentage() - percentage: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_precision() - precision: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_human_size() - human: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() - datetime: - distance_in_words: - half_a_minute: "en halv minut" - less_than_x_seconds: - one: "mindre än en sekund" - other: "mindre än %{count} sekunder" - x_seconds: - one: "en sekund" - other: "%{count} sekunder" - less_than_x_minutes: - one: "mindre än en minut" - other: "mindre än %{count} minuter" - x_minutes: - one: "en minut" - other: "%{count} minuter" - about_x_hours: - one: "ungefär en timme" - other: "ungefär %{count} timmar" - x_hours: - one: "1 timme" - other: "%{count} timmar" - x_days: - one: "en dag" - other: "%{count} dagar" - about_x_months: - one: "ungefär en månad" - other: "ungefär %{count} månader" - x_months: - one: "en månad" - other: "%{count} månader" - about_x_years: - one: "ungefär ett år" - other: "ungefär %{count} år" - over_x_years: - one: "mer än ett år" - other: "mer än %{count} år" - almost_x_years: - one: "nästan 1 år" - other: "nästan %{count} år" - - activerecord: - errors: - template: - header: - one: "Ett fel förhindrade denna %{model} från att sparas" - other: "%{count} fel förhindrade denna %{model} från att sparas" - # The variable :count is also available - body: "Det var problem med följande fält:" - # The values :model, :attribute and :value are always available for interpolation - # The value :count is available when applicable. Can be used for pluralization. - messages: - inclusion: "finns inte i listan" - exclusion: "är reserverat" - invalid: "är ogiltigt" - confirmation: "stämmer inte överens" - accepted : "måste vara accepterad" - empty: "får ej vara tom" - blank: "måste anges" - too_long: "är för lång (maximum är %{count} tecken)" - too_short: "är för kort (minimum är %{count} tecken)" - wrong_length: "har fel längd (ska vara %{count} tecken)" - taken: "har redan tagits" - not_a_number: "är inte ett nummer" - greater_than: "måste vara större än %{count}" - greater_than_or_equal_to: "måste vara större än eller lika med %{count}" - equal_to: "måste vara samma som" - less_than: "måste vara mindre än %{count}" - less_than_or_equal_to: "måste vara mindre än eller lika med %{count}" - odd: "måste vara udda" - even: "måste vara jämnt" - greater_than_start_date: "måste vara senare än startdatumet" - not_same_project: "tillhör inte samma projekt" - circular_dependency: "Denna relation skulle skapa ett cirkulärt beroende" - cant_link_an_issue_with_a_descendant: "Ett ärende kan inte länkas till ett av dess underärenden" - - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%e %b" - long: "%e %B, %Y" - - day_names: [söndag, måndag, tisdag, onsdag, torsdag, fredag, lördag] - abbr_day_names: [sön, mån, tis, ons, tor, fre, lör] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, januari, februari, mars, april, maj, juni, juli, augusti, september, oktober, november, december] - abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, aug, sep, okt, nov, dec] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%Y-%m-%d %H:%M" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%d %B, %Y %H:%M" - am: "" - pm: "" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "och" - skip_last_comma: true - - actionview_instancetag_blank_option: Var god välj - - general_text_No: 'Nej' - general_text_Yes: 'Ja' - general_text_no: 'nej' - general_text_yes: 'ja' - general_lang_name: 'Svenska' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Kontot har uppdaterats - notice_account_invalid_creditentials: Fel användarnamn eller lösenord - notice_account_password_updated: Lösenordet har uppdaterats - notice_account_wrong_password: Fel lösenord - notice_account_register_done: Kontot har skapats. För att aktivera kontot, klicka på länken i mailet som skickades till dig. - notice_account_unknown_email: Okänd användare. - notice_can_t_change_password: Detta konto använder en extern autentiseringskälla. Det går inte att byta lösenord. - notice_account_lost_email_sent: Ett mail med instruktioner om hur man väljer ett nytt lösenord har skickats till dig. - notice_account_activated: Ditt konto har blivit aktiverat. Du kan nu logga in. - notice_successful_create: Skapades korrekt. - notice_successful_update: Uppdatering lyckades. - notice_successful_delete: Borttagning lyckades. - notice_successful_connection: Uppkoppling lyckades. - notice_file_not_found: Sidan du försökte komma åt existerar inte eller är borttagen. - notice_locking_conflict: Data har uppdaterats av en annan användare. - notice_not_authorized: Du saknar behörighet att komma åt den här sidan. - notice_not_authorized_archived_project: Projektet du försöker komma åt har arkiverats. - notice_email_sent: "Ett mail skickades till %{value}" - notice_email_error: "Ett fel inträffade när mail skickades (%{value})" - notice_feeds_access_key_reseted: Din RSS-nyckel återställdes. - notice_api_access_key_reseted: Din API-nyckel återställdes. - notice_failed_to_save_issues: "Misslyckades med att spara %{count} ärende(n) på %{total} valda: %{ids}." - notice_failed_to_save_time_entries: "Misslyckades med att spara %{count} tidloggning(ar) på %{total} valda: %{ids}." - notice_failed_to_save_members: "Misslyckades med att spara medlem(mar): %{errors}." - notice_no_issue_selected: "Inget ärende är markerat! Var vänlig, markera de ärenden du vill ändra." - notice_account_pending: "Ditt konto skapades och avvaktar nu administratörens godkännande." - notice_default_data_loaded: Standardkonfiguration inläst. - notice_unable_delete_version: Denna version var inte möjlig att ta bort. - notice_unable_delete_time_entry: Tidloggning kunde inte tas bort. - notice_issue_done_ratios_updated: "% klart uppdaterade." - notice_gantt_chart_truncated: "Schemat förminskades eftersom det överskrider det maximala antalet aktiviteter som får visas (%{max})" - notice_issue_successful_create: Ärende %{id} skapades. - notice_issue_update_conflict: Detta ärende har uppdaterats av en annan användare samtidigt som du redigerade det. - notice_account_deleted: Ditt konto har avslutats permanent. - notice_user_successful_create: "Användare %{id} skapad." - - error_can_t_load_default_data: "Standardkonfiguration gick inte att läsa in: %{value}" - error_scm_not_found: "Inlägg och/eller revision finns inte i detta versionsarkiv." - error_scm_command_failed: "Ett fel inträffade vid försök att nå versionsarkivet: %{value}" - error_scm_annotate: "Inlägget existerar inte eller kan inte kommenteras." - error_scm_annotate_big_text_file: Inlägget kan inte annoteras eftersom det överskrider maximal storlek för textfiler. - error_issue_not_found_in_project: 'Ärendet hittades inte eller så tillhör det inte detta projekt' - error_no_tracker_in_project: 'Ingen ärendetyp är associerad med projektet. Vänligen kontrollera projektinställningarna.' - error_no_default_issue_status: 'Ingen status är definierad som standard för nya ärenden. Vänligen kontrollera din konfiguration (Gå till "Administration -> Ärendestatus").' - error_can_not_delete_custom_field: Kan inte ta bort användardefinerat fält - error_can_not_delete_tracker: "Det finns ärenden av denna typ och den är därför inte möjlig att ta bort." - error_can_not_remove_role: "Denna roll används och den är därför inte möjlig att ta bort." - error_can_not_reopen_issue_on_closed_version: 'Ett ärende tilldelat en stängd version kan inte öppnas på nytt' - error_can_not_archive_project: Detta projekt kan inte arkiveras - error_issue_done_ratios_not_updated: "% klart inte uppdaterade." - error_workflow_copy_source: 'Vänligen välj källans ärendetyp eller roll' - error_workflow_copy_target: 'Vänligen välj ärendetyp(er) och roll(er) för mål' - error_unable_delete_issue_status: 'Ärendestatus kunde inte tas bort' - error_unable_to_connect: "Kan inte ansluta (%{value})" - error_attachment_too_big: Denna fil kan inte laddas upp eftersom den överstiger maximalt tillåten filstorlek (%{max_size}) - error_session_expired: "Din session har gått ut. Vänligen logga in på nytt." - warning_attachments_not_saved: "%{count} fil(er) kunde inte sparas." - - mail_subject_lost_password: "Ditt %{value} lösenord" - mail_body_lost_password: 'För att ändra ditt lösenord, klicka på följande länk:' - mail_subject_register: "Din %{value} kontoaktivering" - mail_body_register: 'För att aktivera ditt konto, klicka på följande länk:' - mail_body_account_information_external: "Du kan använda ditt %{value}-konto för att logga in." - mail_body_account_information: Din kontoinformation - mail_subject_account_activation_request: "%{value} begäran om kontoaktivering" - mail_body_account_activation_request: "En ny användare (%{value}) har registrerat sig och avvaktar ditt godkännande:" - mail_subject_reminder: "%{count} ärende(n) har deadline under de kommande %{days} dagarna" - mail_body_reminder: "%{count} ärende(n) som är tilldelat dig har deadline under de %{days} dagarna:" - mail_subject_wiki_content_added: "'%{id}' wikisida has lagts till" - mail_body_wiki_content_added: "The '%{id}' wikisida has lagts till av %{author}." - mail_subject_wiki_content_updated: "'%{id}' wikisida har uppdaterats" - mail_body_wiki_content_updated: "The '%{id}' wikisida har uppdaterats av %{author}." - - gui_validation_error: 1 fel - gui_validation_error_plural: "%{count} fel" - - field_name: Namn - field_description: Beskrivning - field_summary: Sammanfattning - field_is_required: Obligatorisk - field_firstname: Förnamn - field_lastname: Efternamn - field_mail: Mail - field_filename: Fil - field_filesize: Storlek - field_downloads: Nerladdningar - field_author: Författare - field_created_on: Skapad - field_updated_on: Uppdaterad - field_field_format: Format - field_is_for_all: För alla projekt - field_possible_values: Möjliga värden - field_regexp: Reguljärt uttryck - field_min_length: Minimilängd - field_max_length: Maxlängd - field_value: Värde - field_category: Kategori - field_title: Titel - field_project: Projekt - field_issue: Ärende - field_status: Status - field_notes: Anteckningar - field_is_closed: Ärendet är stängt - field_is_default: Standardvärde - field_tracker: Ärendetyp - field_subject: Ämne - field_due_date: Deadline - field_assigned_to: Tilldelad till - field_priority: Prioritet - field_fixed_version: Versionsmål - field_user: Användare - field_principal: Principal - field_role: Roll - field_homepage: Hemsida - field_is_public: Publik - field_parent: Underprojekt till - field_is_in_roadmap: Visa ärenden i roadmap - field_login: Användarnamn - field_mail_notification: Mailnotifieringar - field_admin: Administratör - field_last_login_on: Senaste inloggning - field_language: Språk - field_effective_date: Datum - field_password: Lösenord - field_new_password: Nytt lösenord - field_password_confirmation: Bekräfta lösenord - field_version: Version - field_type: Typ - field_host: Värddator - field_port: Port - field_account: Konto - field_base_dn: Bas-DN - field_attr_login: Inloggningsattribut - field_attr_firstname: Förnamnsattribut - field_attr_lastname: Efternamnsattribut - field_attr_mail: Mailattribut - field_onthefly: Skapa användare on-the-fly - field_start_date: Startdatum - field_done_ratio: "% Klart" - field_auth_source: Autentiseringsläge - field_hide_mail: Dölj min mailadress - field_comments: Kommentar - field_url: URL - field_start_page: Startsida - field_subproject: Underprojekt - field_hours: Timmar - field_activity: Aktivitet - field_spent_on: Datum - field_identifier: Identifierare - field_is_filter: Använd som filter - field_issue_to: Relaterade ärenden - field_delay: Fördröjning - field_assignable: Ärenden kan tilldelas denna roll - field_redirect_existing_links: Omdirigera existerande länkar - field_estimated_hours: Estimerad tid - field_column_names: Kolumner - field_time_entries: Spenderad tid - field_time_zone: Tidszon - field_searchable: Sökbar - field_default_value: Standardvärde - field_comments_sorting: Visa kommentarer - field_parent_title: Föräldersida - field_editable: Redigerbar - field_watcher: Bevakare - field_identity_url: OpenID URL - field_content: Innehåll - field_group_by: Gruppera resultat efter - field_sharing: Delning - field_parent_issue: Förälderaktivitet - field_member_of_group: "Tilldelad användares grupp" - field_assigned_to_role: "Tilldelad användares roll" - field_text: Textfält - field_visible: Synlig - field_warn_on_leaving_unsaved: Varna om jag lämnar en sida med osparad text - field_issues_visibility: Ärendesynlighet - field_is_private: Privat - field_commit_logs_encoding: Teckenuppsättning för commit-meddelanden - field_scm_path_encoding: Sökvägskodning - field_path_to_repository: Sökväg till versionsarkiv - field_root_directory: Rotmapp - field_cvsroot: CVSROOT - field_cvs_module: Modul - field_repository_is_default: Huvudarkiv - field_multiple: Flera värden - field_auth_source_ldap_filter: LDAP-filter - field_core_fields: Standardfält - field_timeout: "Timeout (i sekunder)" - field_board_parent: Förälderforum - field_private_notes: Privata anteckningar - - setting_app_title: Applikationsrubrik - setting_app_subtitle: Applikationsunderrubrik - setting_welcome_text: Välkomsttext - setting_default_language: Standardspråk - setting_login_required: Kräver inloggning - setting_self_registration: Självregistrering - setting_attachment_max_size: Maxstorlek på bilaga - setting_issues_export_limit: Exportgräns för ärenden - setting_mail_from: Avsändaradress - setting_bcc_recipients: Hemlig kopia (bcc) till mottagare - setting_plain_text_mail: Oformaterad text i mail (ingen HTML) - setting_host_name: Värddatornamn - setting_text_formatting: Textformatering - setting_wiki_compression: Komprimering av wikihistorik - setting_feeds_limit: Innehållsgräns för Feed - setting_default_projects_public: Nya projekt är publika - setting_autofetch_changesets: Automatisk hämtning av commits - setting_sys_api_enabled: Aktivera WS för versionsarkivhantering - setting_commit_ref_keywords: Referens-nyckelord - setting_commit_fix_keywords: Fix-nyckelord - setting_autologin: Automatisk inloggning - setting_date_format: Datumformat - setting_time_format: Tidsformat - setting_cross_project_subtasks: Tillåt underaktiviteter mellan projekt - setting_cross_project_issue_relations: Tillåt ärenderelationer mellan projekt - setting_issue_list_default_columns: Standardkolumner i ärendelistan - setting_repositories_encodings: Encoding för bilagor och versionsarkiv - setting_emails_header: Mail-header - setting_emails_footer: Signatur - setting_protocol: Protokoll - setting_per_page_options: Alternativ, objekt per sida - setting_user_format: Visningsformat för användare - setting_activity_days_default: Dagar som visas på projektaktivitet - setting_display_subprojects_issues: Visa ärenden från underprojekt i huvudprojekt - setting_enabled_scm: Aktivera SCM - setting_mail_handler_body_delimiters: "Trunkera mail efter en av följande rader" - setting_mail_handler_api_enabled: Aktivera WS för inkommande mail - setting_mail_handler_api_key: API-nyckel - setting_sequential_project_identifiers: Generera projektidentifierare sekventiellt - setting_gravatar_enabled: Använd Gravatar-avatarer - setting_gravatar_default: Förvald Gravatar-bild - setting_diff_max_lines_displayed: Maximalt antal synliga rader i diff - setting_file_max_size_displayed: Maxstorlek på textfiler som visas inline - setting_repository_log_display_limit: Maximalt antal revisioner i filloggen - setting_openid: Tillåt inloggning och registrering med OpenID - setting_password_min_length: Minsta tillåtna lösenordslängd - setting_new_project_user_role_id: Tilldelad roll för en icke-administratör som skapar ett projekt - setting_default_projects_modules: Aktiverade moduler för nya projekt - setting_issue_done_ratio: Beräkna % klart med - setting_issue_done_ratio_issue_field: Använd ärendefältet - setting_issue_done_ratio_issue_status: Använd ärendestatus - setting_start_of_week: Första dagen i veckan - setting_rest_api_enabled: Aktivera REST webbtjänst - setting_cache_formatted_text: Cacha formaterad text - setting_default_notification_option: Standard notifieringsalternativ - setting_commit_logtime_enabled: Aktivera tidloggning - setting_commit_logtime_activity_id: Aktivitet för loggad tid - setting_gantt_items_limit: Maximalt antal aktiviteter som visas i gantt-schemat - setting_issue_group_assignment: Tillåt att ärenden tilldelas till grupper - setting_default_issue_start_date_to_creation_date: Använd dagens datum som startdatum för nya ärenden - setting_commit_cross_project_ref: Tillåt ärende i alla de andra projekten att bli refererade och fixade - setting_unsubscribe: Tillåt användare att avsluta prenumereration - setting_session_lifetime: Maximal sessionslivslängd - setting_session_timeout: Tidsgräns för sessionsinaktivitet - setting_thumbnails_enabled: Visa miniatyrbilder av bilagor - setting_thumbnails_size: Storlek på miniatyrbilder (i pixlar) - setting_non_working_week_days: Lediga dagar - - permission_add_project: Skapa projekt - permission_add_subprojects: Skapa underprojekt - permission_edit_project: Ändra projekt - permission_close_project: Stänga / återöppna projektet - permission_select_project_modules: Välja projektmoduler - permission_manage_members: Hantera medlemmar - permission_manage_project_activities: Hantera projektaktiviteter - permission_manage_versions: Hantera versioner - permission_manage_categories: Hantera ärendekategorier - permission_add_issues: Lägga till ärenden - permission_edit_issues: Ändra ärenden - permission_view_issues: Visa ärenden - permission_manage_issue_relations: Hantera ärenderelationer - permission_set_issues_private: Sätta ärenden publika eller privata - permission_set_own_issues_private: Sätta egna ärenden publika eller privata - permission_add_issue_notes: Lägga till ärendenotering - permission_edit_issue_notes: Ändra ärendenoteringar - permission_edit_own_issue_notes: Ändra egna ärendenoteringar - permission_view_private_notes: Visa privata anteckningar - permission_set_notes_private: Ställa in anteckningar som privata - permission_move_issues: Flytta ärenden - permission_delete_issues: Ta bort ärenden - permission_manage_public_queries: Hantera publika frågor - permission_save_queries: Spara frågor - permission_view_gantt: Visa Gantt-schema - permission_view_calendar: Visa kalender - permission_view_issue_watchers: Visa bevakarlista - permission_add_issue_watchers: Lägga till bevakare - permission_delete_issue_watchers: Ta bort bevakare - permission_log_time: Logga spenderad tid - permission_view_time_entries: Visa spenderad tid - permission_edit_time_entries: Ändra tidloggningar - permission_edit_own_time_entries: Ändra egna tidloggningar - permission_manage_news: Hantera nyheter - permission_comment_news: Kommentera nyheter - permission_manage_documents: Hantera dokument - permission_view_documents: Visa dokument - permission_manage_files: Hantera filer - permission_view_files: Visa filer - permission_manage_wiki: Hantera wiki - permission_rename_wiki_pages: Byta namn på wikisidor - permission_delete_wiki_pages: Ta bort wikisidor - permission_view_wiki_pages: Visa wiki - permission_view_wiki_edits: Visa wikihistorik - permission_edit_wiki_pages: Ändra wikisidor - permission_delete_wiki_pages_attachments: Ta bort bilagor - permission_protect_wiki_pages: Skydda wikisidor - permission_manage_repository: Hantera versionsarkiv - permission_browse_repository: Bläddra i versionsarkiv - permission_view_changesets: Visa changesets - permission_commit_access: Commit-åtkomst - permission_manage_boards: Hantera forum - permission_view_messages: Visa meddelanden - permission_add_messages: Lägg till meddelanden - permission_edit_messages: Ändra meddelanden - permission_edit_own_messages: Ändra egna meddelanden - permission_delete_messages: Ta bort meddelanden - permission_delete_own_messages: Ta bort egna meddelanden - permission_export_wiki_pages: Exportera wikisidor - permission_manage_subtasks: Hantera underaktiviteter - permission_manage_related_issues: Hantera relaterade ärenden - - project_module_issue_tracking: Ärendeuppföljning - project_module_time_tracking: Tidsuppföljning - project_module_news: Nyheter - project_module_documents: Dokument - project_module_files: Filer - project_module_wiki: Wiki - project_module_repository: Versionsarkiv - project_module_boards: Forum - project_module_calendar: Kalender - project_module_gantt: Gantt - - label_user: Användare - label_user_plural: Användare - label_user_new: Ny användare - label_user_anonymous: Anonym - label_project: Projekt - label_project_new: Nytt projekt - label_project_plural: Projekt - label_x_projects: - zero: inga projekt - one: 1 projekt - other: "%{count} projekt" - label_project_all: Alla projekt - label_project_latest: Senaste projekt - label_issue: Ärende - label_issue_new: Nytt ärende - label_issue_plural: Ärenden - label_issue_view_all: Visa alla ärenden - label_issues_by: "Ärenden %{value}" - label_issue_added: Ärende tillagt - label_issue_updated: Ärende uppdaterat - label_issue_note_added: Anteckning tillagd - label_issue_status_updated: Status uppdaterad - label_issue_priority_updated: Prioritet uppdaterad - label_document: Dokument - label_document_new: Nytt dokument - label_document_plural: Dokument - label_document_added: Dokument tillagt - label_role: Roll - label_role_plural: Roller - label_role_new: Ny roll - label_role_and_permissions: Roller och behörigheter - label_role_anonymous: Anonym - label_role_non_member: Icke-medlem - label_member: Medlem - label_member_new: Ny medlem - label_member_plural: Medlemmar - label_tracker: Ärendetyp - label_tracker_plural: Ärendetyper - label_tracker_new: Ny ärendetyp - label_workflow: Arbetsflöde - label_issue_status: Ärendestatus - label_issue_status_plural: Ärendestatus - label_issue_status_new: Ny status - label_issue_category: Ärendekategori - label_issue_category_plural: Ärendekategorier - label_issue_category_new: Ny kategori - label_custom_field: Användardefinerat fält - label_custom_field_plural: Användardefinerade fält - label_custom_field_new: Nytt användardefinerat fält - label_enumerations: Uppräkningar - label_enumeration_new: Nytt värde - label_information: Information - label_information_plural: Information - label_please_login: Var god logga in - label_register: Registrera - label_login_with_open_id_option: eller logga in med OpenID - label_password_lost: Glömt lösenord - label_home: Hem - label_my_page: Min sida - label_my_account: Mitt konto - label_my_projects: Mina projekt - label_my_page_block: '"Min sida"-block' - label_administration: Administration - label_login: Logga in - label_logout: Logga ut - label_help: Hjälp - label_reported_issues: Rapporterade ärenden - label_assigned_to_me_issues: Ärenden tilldelade till mig - label_last_login: Senaste inloggning - label_registered_on: Registrerad - label_activity: Aktivitet - label_overall_activity: All aktivitet - label_user_activity: "Aktiviteter för %{value}" - label_new: Ny - label_logged_as: Inloggad som - label_environment: Miljö - label_authentication: Autentisering - label_auth_source: Autentiseringsläge - label_auth_source_new: Nytt autentiseringsläge - label_auth_source_plural: Autentiseringslägen - label_subproject_plural: Underprojekt - label_subproject_new: Nytt underprojekt - label_and_its_subprojects: "%{value} och dess underprojekt" - label_min_max_length: Min./Max.-längd - label_list: Lista - label_date: Datum - label_integer: Heltal - label_float: Flyttal - label_boolean: Boolean - label_string: Text - label_text: Lång text - label_attribute: Attribut - label_attribute_plural: Attribut - label_download: "%{count} Nerladdning" - label_download_plural: "%{count} Nerladdningar" - label_no_data: Ingen data att visa - label_change_status: Ändra status - label_history: Historia - label_attachment: Fil - label_attachment_new: Ny fil - label_attachment_delete: Ta bort fil - label_attachment_plural: Filer - label_file_added: Fil tillagd - label_report: Rapport - label_report_plural: Rapporter - label_news: Nyhet - label_news_new: Lägg till nyhet - label_news_plural: Nyheter - label_news_latest: Senaste nyheterna - label_news_view_all: Visa alla nyheter - label_news_added: Nyhet tillagd - label_news_comment_added: Kommentar tillagd till en nyhet - label_settings: Inställningar - label_overview: Översikt - label_version: Version - label_version_new: Ny version - label_version_plural: Versioner - label_close_versions: Stäng klara versioner - label_confirmation: Bekräftelse - label_export_to: 'Finns även som:' - label_read: Läs... - label_public_projects: Publika projekt - label_open_issues: öppen - label_open_issues_plural: öppna - label_closed_issues: stängd - label_closed_issues_plural: stängda - label_x_open_issues_abbr_on_total: - zero: 0 öppna av %{total} - one: 1 öppen av %{total} - other: "%{count} öppna av %{total}" - label_x_open_issues_abbr: - zero: 0 öppna - one: 1 öppen - other: "%{count} öppna" - label_x_closed_issues_abbr: - zero: 0 stängda - one: 1 stängd - other: "%{count} stängda" - label_x_issues: - zero: 0 ärenden - one: 1 ärende - other: "%{count} ärenden" - label_total: Total - label_permissions: Behörigheter - label_current_status: Nuvarande status - label_new_statuses_allowed: Nya tillåtna statusvärden - label_all: alla - label_none: ingen - label_nobody: ingen - label_next: Nästa - label_previous: Föregående - label_used_by: Använd av - label_details: Detaljer - label_add_note: Lägg till anteckning - label_per_page: Per sida - label_calendar: Kalender - label_months_from: månader från - label_gantt: Gantt - label_internal: Intern - label_last_changes: "senaste %{count} ändringar" - label_change_view_all: Visa alla ändringar - label_personalize_page: Anpassa denna sida - label_comment: Kommentar - label_comment_plural: Kommentarer - label_x_comments: - zero: inga kommentarer - one: 1 kommentar - other: "%{count} kommentarer" - label_comment_add: Lägg till kommentar - label_comment_added: Kommentar tillagd - label_comment_delete: Ta bort kommentar - label_query: Användardefinerad fråga - label_query_plural: Användardefinerade frågor - label_query_new: Ny fråga - label_my_queries: Mina egna frågor - label_filter_add: Lägg till filter - label_filter_plural: Filter - label_equals: är - label_not_equals: är inte - label_in_less_than: om mindre än - label_in_more_than: om mer än - label_in_the_next_days: under kommande - label_in_the_past_days: under föregående - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_between: mellan - label_in: om - label_today: idag - label_all_time: närsom - label_yesterday: igår - label_this_week: denna vecka - label_last_week: senaste veckan - label_last_n_weeks: "senaste %{count} veckorna" - label_last_n_days: "senaste %{count} dagarna" - label_this_month: denna månad - label_last_month: senaste månaden - label_this_year: detta året - label_date_range: Datumintervall - label_less_than_ago: mindre än dagar sedan - label_more_than_ago: mer än dagar sedan - label_ago: dagar sedan - label_contains: innehåller - label_not_contains: innehåller inte - label_any_issues_in_project: några ärenden i projektet - label_any_issues_not_in_project: några ärenden utanför projektet - label_no_issues_in_project: inga ärenden i projektet - label_day_plural: dagar - label_repository: Versionsarkiv - label_repository_new: Nytt versionsarkiv - label_repository_plural: Versionsarkiv - label_browse: Bläddra - label_modification: "%{count} ändring" - label_modification_plural: "%{count} ändringar" - label_branch: Branch - label_tag: Tag - label_revision: Revision - label_revision_plural: Revisioner - label_revision_id: "Revision %{value}" - label_associated_revisions: Associerade revisioner - label_added: tillagd - label_modified: modifierad - label_copied: kopierad - label_renamed: omdöpt - label_deleted: borttagen - label_latest_revision: Senaste revisionen - label_latest_revision_plural: Senaste revisionerna - label_view_revisions: Visa revisioner - label_view_all_revisions: Visa alla revisioner - label_max_size: Maxstorlek - label_sort_highest: Flytta till toppen - label_sort_higher: Flytta upp - label_sort_lower: Flytta ner - label_sort_lowest: Flytta till botten - label_roadmap: Roadmap - label_roadmap_due_in: "Färdig om %{value}" - label_roadmap_overdue: "%{value} sen" - label_roadmap_no_issues: Inga ärenden för denna version - label_search: Sök - label_result_plural: Resultat - label_all_words: Alla ord - label_wiki: Wiki - label_wiki_edit: Wikiändring - label_wiki_edit_plural: Wikiändringar - label_wiki_page: Wikisida - label_wiki_page_plural: Wikisidor - label_index_by_title: Innehåll efter titel - label_index_by_date: Innehåll efter datum - label_current_version: Nuvarande version - label_preview: Förhandsgranska - label_feed_plural: Feeds - label_changes_details: Detaljer om alla ändringar - label_issue_tracking: Ärendeuppföljning - label_spent_time: Spenderad tid - label_overall_spent_time: Total tid spenderad - label_f_hour: "%{value} timme" - label_f_hour_plural: "%{value} timmar" - label_time_tracking: Tidsuppföljning - label_change_plural: Ändringar - label_statistics: Statistik - label_commits_per_month: Commits per månad - label_commits_per_author: Commits per författare - label_diff: diff - label_view_diff: Visa skillnader - label_diff_inline: i texten - label_diff_side_by_side: sida vid sida - label_options: Inställningar - label_copy_workflow_from: Kopiera arbetsflöde från - label_permissions_report: Behörighetsrapport - label_watched_issues: Bevakade ärenden - label_related_issues: Relaterade ärenden - label_applied_status: Tilldelad status - label_loading: Laddar... - label_relation_new: Ny relation - label_relation_delete: Ta bort relation - label_relates_to: relaterar till - label_duplicates: kopierar - label_duplicated_by: kopierad av - label_blocks: blockerar - label_blocked_by: blockerad av - label_precedes: kommer före - label_follows: följer - label_copied_to: Kopierad till - label_copied_from: Kopierad från - label_end_to_start: slut till start - label_end_to_end: slut till slut - label_start_to_start: start till start - label_start_to_end: start till slut - label_stay_logged_in: Förbli inloggad - label_disabled: inaktiverad - label_show_completed_versions: Visa färdiga versioner - label_me: mig - label_board: Forum - label_board_new: Nytt forum - label_board_plural: Forum - label_board_locked: Låst - label_board_sticky: Sticky - label_topic_plural: Ämnen - label_message_plural: Meddelanden - label_message_last: Senaste meddelande - label_message_new: Nytt meddelande - label_message_posted: Meddelande tillagt - label_reply_plural: Svar - label_send_information: Skicka kontoinformation till användaren - label_year: År - label_month: Månad - label_week: Vecka - label_date_from: Från - label_date_to: Till - label_language_based: Språkbaserad - label_sort_by: "Sortera på %{value}" - label_send_test_email: Skicka testmail - label_feeds_access_key: RSS-nyckel - label_missing_feeds_access_key: Saknar en RSS-nyckel - label_feeds_access_key_created_on: "RSS-nyckel skapad för %{value} sedan" - label_module_plural: Moduler - label_added_time_by: "Tillagd av %{author} för %{age} sedan" - label_updated_time_by: "Uppdaterad av %{author} för %{age} sedan" - label_updated_time: "Uppdaterad för %{value} sedan" - label_jump_to_a_project: Gå till projekt... - label_file_plural: Filer - label_changeset_plural: Changesets - label_default_columns: Standardkolumner - label_no_change_option: (Ingen ändring) - label_bulk_edit_selected_issues: Gemensam ändring av markerade ärenden - label_bulk_edit_selected_time_entries: Gruppredigera valda tidloggningar - label_theme: Tema - label_default: Standard - label_search_titles_only: Sök endast i titlar - label_user_mail_option_all: "För alla händelser i mina projekt" - label_user_mail_option_selected: "För alla händelser i markerade projekt..." - label_user_mail_option_none: "Inga händelser" - label_user_mail_option_only_my_events: "Endast för saker jag bevakar eller är inblandad i" - label_user_mail_option_only_assigned: "Endast för saker jag är tilldelad" - label_user_mail_option_only_owner: "Endast för saker jag äger" - label_user_mail_no_self_notified: "Jag vill inte bli underrättad om ändringar som jag har gjort" - label_registration_activation_by_email: kontoaktivering med mail - label_registration_manual_activation: manuell kontoaktivering - label_registration_automatic_activation: automatisk kontoaktivering - label_display_per_page: "Per sida: %{value}" - label_age: Ålder - label_change_properties: Ändra inställningar - label_general: Allmänt - label_more: Mer - label_scm: SCM - label_plugins: Tillägg - label_ldap_authentication: LDAP-autentisering - label_downloads_abbr: Nerl. - label_optional_description: Valfri beskrivning - label_add_another_file: Lägg till ytterligare en fil - label_preferences: Användarinställningar - label_chronological_order: I kronologisk ordning - label_reverse_chronological_order: I omvänd kronologisk ordning - label_planning: Planering - label_incoming_emails: Inkommande mail - label_generate_key: Generera en nyckel - label_issue_watchers: Bevakare - label_example: Exempel - label_display: Visa - label_sort: Sortera - label_descending: Fallande - label_ascending: Stigande - label_date_from_to: Från %{start} till %{end} - label_wiki_content_added: Wikisida tillagd - label_wiki_content_updated: Wikisida uppdaterad - label_group: Grupp - label_group_plural: Grupper - label_group_new: Ny grupp - label_time_entry_plural: Spenderad tid - label_version_sharing_none: Inte delad - label_version_sharing_descendants: Med underprojekt - label_version_sharing_hierarchy: Med projekthierarki - label_version_sharing_tree: Med projektträd - label_version_sharing_system: Med alla projekt - label_update_issue_done_ratios: Uppdatera % klart - label_copy_source: Källa - label_copy_target: Mål - label_copy_same_as_target: Samma som mål - label_display_used_statuses_only: Visa endast status som används av denna ärendetyp - label_api_access_key: API-nyckel - label_missing_api_access_key: Saknar en API-nyckel - label_api_access_key_created_on: "API-nyckel skapad för %{value} sedan" - label_profile: Profil - label_subtask_plural: Underaktiviteter - label_project_copy_notifications: Skicka mailnotifieringar när projektet kopieras - label_principal_search: "Sök efter användare eller grupp:" - label_user_search: "Sök efter användare:" - label_additional_workflow_transitions_for_author: Ytterligare övergångar tillåtna när användaren är den som skapat ärendet - label_additional_workflow_transitions_for_assignee: Ytterligare övergångar tillåtna när användaren är den som tilldelats ärendet - label_issues_visibility_all: Alla ärenden - label_issues_visibility_public: Alla icke-privata ärenden - label_issues_visibility_own: Ärenden skapade av eller tilldelade till användaren - label_git_report_last_commit: Rapportera senaste commit av filer och mappar - label_parent_revision: Förälder - label_child_revision: Barn - label_export_options: "%{export_format} exportalternativ" - label_copy_attachments: Kopiera bilagor - label_copy_subtasks: Kopiera underaktiviteter - label_item_position: "%{position}/%{count}" - label_completed_versions: Klara versioner - label_search_for_watchers: Sök efter bevakare att lägga till - label_session_expiration: Sessionsutgång - label_show_closed_projects: Visa stängda projekt - label_status_transitions: Statusövergångar - label_fields_permissions: Fältbehörigheter - label_readonly: Skrivskyddad - label_required: Nödvändig - label_attribute_of_project: Projektets %{name} - label_attribute_of_author: Författarens %{name} - label_attribute_of_assigned_to: Tilldelads %{name} - label_attribute_of_fixed_version: Målversionens %{name} - - button_login: Logga in - button_submit: Skicka - button_save: Spara - button_check_all: Markera alla - button_uncheck_all: Avmarkera alla - button_collapse_all: Kollapsa alla - button_expand_all: Expandera alla - button_delete: Ta bort - button_create: Skapa - button_create_and_continue: Skapa och fortsätt - button_test: Testa - button_edit: Ändra - button_edit_associated_wikipage: "Ändra associerad Wikisida: %{page_title}" - button_add: Lägg till - button_change: Ändra - button_apply: Verkställ - button_clear: Återställ - button_lock: Lås - button_unlock: Lås upp - button_download: Ladda ner - button_list: Lista - button_view: Visa - button_move: Flytta - button_move_and_follow: Flytta och följ efter - button_back: Tillbaka - button_cancel: Avbryt - button_activate: Aktivera - button_sort: Sortera - button_log_time: Logga tid - button_rollback: Återställ till denna version - button_watch: Bevaka - button_unwatch: Stoppa bevakning - button_reply: Svara - button_archive: Arkivera - button_unarchive: Ta bort från arkiv - button_reset: Återställ - button_rename: Byt namn - button_change_password: Ändra lösenord - button_copy: Kopiera - button_copy_and_follow: Kopiera och följ efter - button_annotate: Kommentera - button_update: Uppdatera - button_configure: Konfigurera - button_quote: Citera - button_duplicate: Duplicera - button_show: Visa - button_hide: Göm - button_edit_section: Redigera denna sektion - button_export: Exportera - button_delete_my_account: Ta bort mitt konto - button_close: Stäng - button_reopen: Återöppna - - status_active: aktiv - status_registered: registrerad - status_locked: låst - - project_status_active: aktiv - project_status_closed: stängd - project_status_archived: arkiverad - - version_status_open: öppen - version_status_locked: låst - version_status_closed: stängd - - field_active: Aktiv - - text_select_mail_notifications: Välj för vilka händelser mail ska skickas. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 betyder ingen gräns - text_project_destroy_confirmation: Är du säker på att du vill ta bort detta projekt och all relaterad data? - text_subprojects_destroy_warning: "Alla underprojekt: %{value} kommer också tas bort." - text_workflow_edit: Välj en roll och en ärendetyp för att ändra arbetsflöde - text_are_you_sure: Är du säker ? - text_journal_changed: "%{label} ändrad från %{old} till %{new}" - text_journal_changed_no_detail: "%{label} uppdaterad" - text_journal_set_to: "%{label} satt till %{value}" - text_journal_deleted: "%{label} borttagen (%{old})" - text_journal_added: "%{label} %{value} tillagd" - text_tip_issue_begin_day: ärende som börjar denna dag - text_tip_issue_end_day: ärende som slutar denna dag - text_tip_issue_begin_end_day: ärende som börjar och slutar denna dag - text_project_identifier_info: Ändast gemener (a-z), siffror, streck och understreck är tillåtna.
    När identifieraren sparats kan den inte ändras. - text_caracters_maximum: "max %{count} tecken." - text_caracters_minimum: "Måste vara minst %{count} tecken lång." - text_length_between: "Längd mellan %{min} och %{max} tecken." - text_tracker_no_workflow: Inget arbetsflöde definerat för denna ärendetyp - text_unallowed_characters: Otillåtna tecken - text_comma_separated: Flera värden tillåtna (kommaseparerade). - text_line_separated: Flera värden tillåtna (ett värde per rad). - text_issues_ref_in_commit_messages: Referera och fixa ärenden i commit-meddelanden - text_issue_added: "Ärende %{id} har rapporterats (av %{author})." - text_issue_updated: "Ärende %{id} har uppdaterats (av %{author})." - text_wiki_destroy_confirmation: Är du säker på att du vill ta bort denna wiki och allt dess innehåll ? - text_issue_category_destroy_question: "Några ärenden (%{count}) är tilldelade till denna kategori. Vad vill du göra ?" - text_issue_category_destroy_assignments: Ta bort kategoritilldelningar - text_issue_category_reassign_to: Återtilldela ärenden till denna kategori - text_user_mail_option: "För omarkerade projekt kommer du bara bli underrättad om saker du bevakar eller är inblandad i (T.ex. ärenden du skapat eller tilldelats)." - text_no_configuration_data: "Roller, ärendetyper, ärendestatus och arbetsflöden har inte konfigurerats ännu.\nDet rekommenderas att läsa in standardkonfigurationen. Du kommer att kunna göra ändringar efter att den blivit inläst." - text_load_default_configuration: Läs in standardkonfiguration - text_status_changed_by_changeset: "Tilldelad i changeset %{value}." - text_time_logged_by_changeset: "Tilldelad i changeset %{value}." - text_issues_destroy_confirmation: 'Är du säker på att du vill radera markerade ärende(n) ?' - text_issues_destroy_descendants_confirmation: Detta kommer även ta bort %{count} underaktivitet(er). - text_time_entries_destroy_confirmation: Är du säker på att du vill ta bort valda tidloggningar? - text_select_project_modules: 'Välj vilka moduler som ska vara aktiva för projektet:' - text_default_administrator_account_changed: Standardadministratörens konto ändrat - text_file_repository_writable: Arkivet för bifogade filer är skrivbart - text_plugin_assets_writable: Arkivet för plug-ins är skrivbart - text_rmagick_available: RMagick tillgängligt (ej obligatoriskt) - text_destroy_time_entries_question: "%{hours} timmar har rapporterats på ärendena du är på väg att ta bort. Vad vill du göra ?" - text_destroy_time_entries: Ta bort rapporterade timmar - text_assign_time_entries_to_project: Tilldela rapporterade timmar till projektet - text_reassign_time_entries: 'Återtilldela rapporterade timmar till detta ärende:' - text_user_wrote: "%{value} skrev:" - text_enumeration_destroy_question: "%{count} objekt är tilldelade till detta värde." - text_enumeration_category_reassign_to: 'Återtilldela till detta värde:' - text_email_delivery_not_configured: "Mailfunktionen har inte konfigurerats, och notifieringar via mail kan därför inte skickas.\nKonfigurera din SMTP-server i config/configuration.yml och starta om applikationen för att aktivera dem." - text_repository_usernames_mapping: "Välj eller uppdatera den Redmine-användare som är mappad till varje användarnamn i versionarkivloggen.\nAnvändare med samma användarnamn eller mailadress i både Redmine och versionsarkivet mappas automatiskt." - text_diff_truncated: '... Denna diff har förminskats eftersom den överskrider den maximala storlek som kan visas.' - text_custom_field_possible_values_info: 'Ett värde per rad' - text_wiki_page_destroy_question: "Denna sida har %{descendants} underliggande sidor. Vad vill du göra?" - text_wiki_page_nullify_children: "Behåll undersidor som rotsidor" - text_wiki_page_destroy_children: "Ta bort alla underliggande sidor" - text_wiki_page_reassign_children: "Flytta undersidor till denna föräldersida" - text_own_membership_delete_confirmation: "Några av, eller alla, dina behörigheter kommer att tas bort och du kanske inte längre kommer kunna göra ändringar i det här projektet.\nVill du verkligen fortsätta?" - text_zoom_out: Zooma ut - text_zoom_in: Zooma in - text_warn_on_leaving_unsaved: Nuvarande sida innehåller osparad text som kommer försvinna om du lämnar sidan. - text_scm_path_encoding_note: "Standard: UTF-8" - text_git_repository_note: Versionsarkiv är tomt och lokalt (t.ex. /gitrepo, c:\gitrepo) - text_mercurial_repository_note: Lokalt versionsarkiv (t.ex. /hgrepo, c:\hgrepo) - text_scm_command: Kommando - text_scm_command_version: Version - text_scm_config: Du kan konfigurera dina scm-kommando i config/configuration.yml. Vänligen starta om applikationen när ändringar gjorts. - text_scm_command_not_available: Scm-kommando är inte tillgängligt. Vänligen kontrollera inställningarna i administratörspanelen. - text_issue_conflict_resolution_overwrite: Använd mina ändringar i alla fall (tidigare anteckningar kommer behållas men några ändringar kan bli överskrivna) - text_issue_conflict_resolution_add_notes: Lägg till mina anteckningar och kasta mina andra ändringar - text_issue_conflict_resolution_cancel: Kasta alla mina ändringar och visa igen %{link} - text_account_destroy_confirmation: "Är du säker på att du vill fortsätta?\nDitt konto kommer tas bort permanent, utan möjlighet att återaktivera det." - text_session_expiration_settings: "Varning: ändring av dessa inställningar kan få alla nuvarande sessioner, inklusive din egen, att gå ut." - text_project_closed: Detta projekt är stängt och skrivskyddat. - - default_role_manager: Projektledare - default_role_developer: Utvecklare - default_role_reporter: Rapportör - default_tracker_bug: Bugg - default_tracker_feature: Funktionalitet - default_tracker_support: Support - default_issue_status_new: Ny - default_issue_status_in_progress: Pågår - default_issue_status_resolved: Löst - default_issue_status_feedback: Återkoppling - default_issue_status_closed: Stängd - default_issue_status_rejected: Avslagen - default_doc_category_user: Användardokumentation - default_doc_category_tech: Teknisk dokumentation - default_priority_low: Låg - default_priority_normal: Normal - default_priority_high: Hög - default_priority_urgent: Brådskande - default_priority_immediate: Omedelbar - default_activity_design: Design - default_activity_development: Utveckling - - enumeration_issue_priorities: Ärendeprioriteter - enumeration_doc_categories: Dokumentkategorier - enumeration_activities: Aktiviteter (tidsuppföljning) - enumeration_system_activity: Systemaktivitet - description_filter: Filter - description_search: Sökfält - description_choose_project: Projekt - description_project_scope: Sökomfång - description_notes: Anteckningar - description_message_content: Meddelandeinnehåll - description_query_sort_criteria_attribute: Sorteringsattribut - description_query_sort_criteria_direction: Sorteringsriktning - description_user_mail_notification: Mailnotifieringsinställningar - description_available_columns: Tillgängliga Kolumner - description_selected_columns: Valda Kolumner - description_all_columns: Alla kolumner - description_issue_category_reassign: Välj ärendekategori - description_wiki_subpages_reassign: Välj ny föräldersida - description_date_range_list: Välj intervall från listan - description_date_range_interval: Ange intervall genom att välja start- och slutdatum - description_date_from: Ange startdatum - description_date_to: Ange slutdatum - text_repository_identifier_info: Ändast gemener (a-z), siffror, streck och understreck är tillåtna.
    När identifieraren sparats kan den inte ändras. - label_any: alla - label_cross_project_descendants: Med underprojekt - label_cross_project_tree: Med projektträd - label_cross_project_hierarchy: Med projekthierarki - label_cross_project_system: Med alla projekt diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/15/15d709b3f6a05dc0aade44cd45030b0d995c1fdc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/15/15d709b3f6a05dc0aade44cd45030b0d995c1fdc.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,23 @@ + + +Redmine 404 error + + +

    Page not found

    +

    The page you were trying to access doesn't exist or has been removed.

    +

    Back

    + + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/15/15d89251506baa28cfc583d29fd309d36051242b.svn-base --- a/.svn/pristine/15/15d89251506baa28cfc583d29fd309d36051242b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module CalendarsHelper - def link_to_previous_month(year, month, options={}) - target_year, target_month = if month == 1 - [year - 1, 12] - else - [year, month - 1] - end - - name = if target_month == 12 - "#{month_name(target_month)} #{target_year}" - else - "#{month_name(target_month)}" - end - - # \xc2\xab(utf-8) = « - link_to_month(("\xc2\xab " + name), target_year, target_month, options) - end - - def link_to_next_month(year, month, options={}) - target_year, target_month = if month == 12 - [year + 1, 1] - else - [year, month + 1] - end - - name = if target_month == 1 - "#{month_name(target_month)} #{target_year}" - else - "#{month_name(target_month)}" - end - - # \xc2\xbb(utf-8) = » - link_to_month((name + " \xc2\xbb"), target_year, target_month, options) - end - - def link_to_month(link_name, year, month, options={}) - link_to_content_update(h(link_name), params.merge(:year => year, :month => month)) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/15/15eea4ed19b710f600770704c4c93d19262f8634.svn-base --- a/.svn/pristine/15/15eea4ed19b710f600770704c4c93d19262f8634.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class TimeEntryCustomField < CustomField - def type_name - :label_spent_time - end -end - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/15/15f48af3113e93bba083d75f20a6f8b663df8337.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/15/15f48af3113e93bba083d75f20a6f8b663df8337.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,73 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class WorkflowRule < ActiveRecord::Base + self.table_name = "#{table_name_prefix}workflows#{table_name_suffix}" + + belongs_to :role + belongs_to :tracker + belongs_to :old_status, :class_name => 'IssueStatus', :foreign_key => 'old_status_id' + belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id' + + validates_presence_of :role, :tracker, :old_status + + # Copies workflows from source to targets + def self.copy(source_tracker, source_role, target_trackers, target_roles) + unless source_tracker.is_a?(Tracker) || source_role.is_a?(Role) + raise ArgumentError.new("source_tracker or source_role must be specified") + end + + target_trackers = [target_trackers].flatten.compact + target_roles = [target_roles].flatten.compact + + target_trackers = Tracker.sorted.all if target_trackers.empty? + target_roles = Role.all if target_roles.empty? + + target_trackers.each do |target_tracker| + target_roles.each do |target_role| + copy_one(source_tracker || target_tracker, + source_role || target_role, + target_tracker, + target_role) + end + end + end + + # Copies a single set of workflows from source to target + def self.copy_one(source_tracker, source_role, target_tracker, target_role) + unless source_tracker.is_a?(Tracker) && !source_tracker.new_record? && + source_role.is_a?(Role) && !source_role.new_record? && + target_tracker.is_a?(Tracker) && !target_tracker.new_record? && + target_role.is_a?(Role) && !target_role.new_record? + + raise ArgumentError.new("arguments can not be nil or unsaved objects") + end + + if source_tracker == target_tracker && source_role == target_role + false + else + transaction do + delete_all :tracker_id => target_tracker.id, :role_id => target_role.id + connection.insert "INSERT INTO #{WorkflowRule.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type)" + + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type" + + " FROM #{WorkflowRule.table_name}" + + " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" + end + true + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/16/1611b5791962624ae0d84516d78fd057b4a95ed3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/16/1611b5791962624ae0d84516d78fd057b4a95ed3.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,136 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class GroupTest < ActiveSupport::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, + :projects_trackers, + :roles, + :member_roles, + :members, + :groups_users + + include Redmine::I18n + + def test_create + g = Group.new(:name => 'New group') + assert g.save + g.reload + assert_equal 'New group', g.name + end + + def test_name_should_accept_255_characters + name = 'a' * 255 + g = Group.new(:name => name) + assert g.save + g.reload + assert_equal name, g.name + end + + def test_blank_name_error_message + set_language_if_valid 'en' + g = Group.new + assert !g.save + assert_include "Name can't be blank", g.errors.full_messages + end + + def test_blank_name_error_message_fr + set_language_if_valid 'fr' + str = "Nom doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + g = Group.new + assert !g.save + assert_include str, g.errors.full_messages + end + + def test_group_roles_should_be_given_to_added_user + group = Group.find(11) + user = User.find(9) + project = Project.first + + Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) + group.users << user + assert user.member_of?(project) + end + + def test_new_roles_should_be_given_to_existing_user + group = Group.find(11) + user = User.find(9) + project = Project.first + + group.users << user + m = Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) + assert user.member_of?(project) + end + + def test_user_roles_should_updated_when_updating_user_ids + group = Group.find(11) + user = User.find(9) + project = Project.first + + Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) + group.user_ids = [user.id] + group.save! + assert User.find(9).member_of?(project) + + group.user_ids = [1] + group.save! + assert !User.find(9).member_of?(project) + end + + def test_user_roles_should_updated_when_updating_group_roles + group = Group.find(11) + user = User.find(9) + project = Project.first + group.users << user + m = Member.create!(:principal => group, :project => project, :role_ids => [1]) + assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort + + m.role_ids = [1, 2] + assert_equal [1, 2], user.reload.roles_for_project(project).collect(&:id).sort + + m.role_ids = [2] + assert_equal [2], user.reload.roles_for_project(project).collect(&:id).sort + + m.role_ids = [1] + assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort + end + + def test_user_memberships_should_be_removed_when_removing_group_membership + assert User.find(8).member_of?(Project.find(5)) + Member.find_by_project_id_and_user_id(5, 10).destroy + assert !User.find(8).member_of?(Project.find(5)) + end + + def test_user_roles_should_be_removed_when_removing_user_from_group + assert User.find(8).member_of?(Project.find(5)) + User.find(8).groups = [] + assert !User.find(8).member_of?(Project.find(5)) + end + + def test_destroy_should_unassign_issues + group = Group.first + Issue.update_all(["assigned_to_id = ?", group.id], 'id = 1') + + assert group.destroy + assert group.destroyed? + + assert_equal nil, Issue.find(1).assigned_to_id + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/16/1611dde1fb0ec5f844a52d9ac947a7c023328e24.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/16/1611dde1fb0ec5f844a52d9ac947a7c023328e24.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,38 @@ +# Redmine runs tests on own continuous integration server. +# http://www.redmine.org/projects/redmine/wiki/Continuous_integration +# You can also run tests on your environment. +language: ruby +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 + - 2.0 + - jruby +matrix: + allow_failures: + # SCM tests fail randomly due to IO.popen(). + # https://github.com/jruby/jruby/issues/779 + - rvm: jruby +env: + - "TEST_SUITE=units DATABASE_ADAPTER=postgresql" + - "TEST_SUITE=functionals DATABASE_ADAPTER=postgresql" + - "TEST_SUITE=integration DATABASE_ADAPTER=postgresql" + - "TEST_SUITE=units DATABASE_ADAPTER=mysql" + - "TEST_SUITE=functionals DATABASE_ADAPTER=mysql" + - "TEST_SUITE=integration DATABASE_ADAPTER=mysql" + - "TEST_SUITE=units DATABASE_ADAPTER=sqlite3" + - "TEST_SUITE=functionals DATABASE_ADAPTER=sqlite3" + - "TEST_SUITE=integration DATABASE_ADAPTER=sqlite3" +before_install: + - "sudo apt-get update -qq" + - "sudo apt-get --no-install-recommends install bzr cvs git mercurial subversion" +script: + - "SCMS=bazaar,cvs,subversion,git,mercurial,filesystem" + - "export SCMS" + - "git --version" + - "bundle install" + - "RUN_ON_NOT_OFFICIAL='' RUBY_VER=1.9 BRANCH=trunk bundle exec rake config/database.yml" + - "bundle install" + - "JRUBY_OPTS=-J-Xmx1024m bundle exec rake ci" +notifications: + email: false diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/16/164226a6305a1cc9df96d889179a566dfaf3eb26.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/16/164226a6305a1cc9df96d889179a566dfaf3eb26.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,98 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module MimeType + + MIME_TYPES = { + 'text/plain' => 'txt,tpl,properties,patch,diff,ini,readme,install,upgrade', + 'text/css' => 'css', + 'text/html' => 'html,htm,xhtml', + 'text/jsp' => 'jsp', + 'text/x-c' => 'c,cpp,cc,h,hh', + 'text/x-csharp' => 'cs', + 'text/x-java' => 'java', + 'text/x-html-template' => 'rhtml', + 'text/x-perl' => 'pl,pm', + 'text/x-php' => 'php,php3,php4,php5', + 'text/x-python' => 'py', + 'text/x-ruby' => 'rb,rbw,ruby,rake,erb', + 'text/x-csh' => 'csh', + 'text/x-sh' => 'sh', + 'text/xml' => 'xml,xsd,mxml', + 'text/yaml' => 'yml,yaml', + 'text/csv' => 'csv', + 'text/x-po' => 'po', + 'image/gif' => 'gif', + 'image/jpeg' => 'jpg,jpeg,jpe', + 'image/png' => 'png', + 'image/tiff' => 'tiff,tif', + 'image/x-ms-bmp' => 'bmp', + 'image/x-xpixmap' => 'xpm', + 'image/svg+xml'=> 'svg', + 'application/javascript' => 'js', + 'application/pdf' => 'pdf', + 'application/rtf' => 'rtf', + 'application/msword' => 'doc', + 'application/vnd.ms-excel' => 'xls', + 'application/vnd.ms-powerpoint' => 'ppt,pps', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/x-7z-compressed' => '7z', + 'application/x-rar-compressed' => 'rar', + 'application/x-tar' => 'tar', + 'application/zip' => 'zip', + 'application/x-gzip' => 'gz', + }.freeze + + EXTENSIONS = MIME_TYPES.inject({}) do |map, (type, exts)| + exts.split(',').each {|ext| map[ext.strip] = type} + map + end + + # returns mime type for name or nil if unknown + def self.of(name) + return nil unless name + m = name.to_s.match(/(^|\.)([^\.]+)$/) + EXTENSIONS[m[2].downcase] if m + end + + # Returns the css class associated to + # the mime type of name + def self.css_class_of(name) + mime = of(name) + mime && mime.gsub('/', '-') + end + + def self.main_mimetype_of(name) + mimetype = of(name) + mimetype.split('/').first if mimetype + end + + # return true if mime-type for name is type/* + # otherwise false + def self.is_type?(type, name) + main_mimetype = main_mimetype_of(name) + type.to_s == main_mimetype + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/16/16931f56b13ff8b173c90b0717bce65066b04a78.svn-base --- a/.svn/pristine/16/16931f56b13ff8b173c90b0717bce65066b04a78.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,244 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'my_controller' - -# Re-raise errors caught by the controller. -class MyController; def rescue_action(e) raise e end; end - -class MyControllerTest < ActionController::TestCase - fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, - :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, :auth_sources - - def setup - @controller = MyController.new - @request = ActionController::TestRequest.new - @request.session[:user_id] = 2 - @response = ActionController::TestResponse.new - end - - def test_index - get :index - assert_response :success - assert_template 'page' - end - - def test_page - get :page - assert_response :success - assert_template 'page' - end - - def test_page_with_timelog_block - preferences = User.find(2).pref - preferences[:my_page_layout] = {'top' => ['timelog']} - preferences.save! - TimeEntry.create!(:user => User.find(2), :spent_on => Date.yesterday, :issue_id => 1, :hours => 2.5, :activity_id => 10) - - get :page - assert_response :success - assert_select 'tr.time-entry' do - assert_select 'td.subject a[href=/issues/1]' - assert_select 'td.hours', :text => '2.50' - end - end - - def test_my_account_should_show_editable_custom_fields - get :account - assert_response :success - assert_template 'account' - assert_equal User.find(2), assigns(:user) - - assert_tag :input, :attributes => { :name => 'user[custom_field_values][4]'} - end - - def test_my_account_should_not_show_non_editable_custom_fields - UserCustomField.find(4).update_attribute :editable, false - - get :account - assert_response :success - assert_template 'account' - assert_equal User.find(2), assigns(:user) - - assert_no_tag :input, :attributes => { :name => 'user[custom_field_values][4]'} - end - - def test_update_account - post :account, - :user => { - :firstname => "Joe", - :login => "root", - :admin => 1, - :group_ids => ['10'], - :custom_field_values => {"4" => "0100562500"} - } - - assert_redirected_to '/my/account' - user = User.find(2) - assert_equal user, assigns(:user) - assert_equal "Joe", user.firstname - assert_equal "jsmith", user.login - assert_equal "0100562500", user.custom_value_for(4).value - # ignored - assert !user.admin? - assert user.groups.empty? - end - - def test_my_account_should_show_destroy_link - get :account - assert_select 'a[href=/my/account/destroy]' - end - - def test_get_destroy_should_display_the_destroy_confirmation - get :destroy - assert_response :success - assert_template 'destroy' - assert_select 'form[action=/my/account/destroy]' do - assert_select 'input[name=confirm]' - end - end - - def test_post_destroy_without_confirmation_should_not_destroy_account - assert_no_difference 'User.count' do - post :destroy - end - assert_response :success - assert_template 'destroy' - end - - def test_post_destroy_without_confirmation_should_destroy_account - assert_difference 'User.count', -1 do - post :destroy, :confirm => '1' - end - assert_redirected_to '/' - assert_match /deleted/i, flash[:notice] - end - - def test_post_destroy_with_unsubscribe_not_allowed_should_not_destroy_account - User.any_instance.stubs(:own_account_deletable?).returns(false) - - assert_no_difference 'User.count' do - post :destroy, :confirm => '1' - end - assert_redirected_to '/my/account' - end - - def test_change_password - get :password - assert_response :success - assert_template 'password' - - # non matching password confirmation - post :password, :password => 'jsmith', - :new_password => 'secret123', - :new_password_confirmation => 'secret1234' - assert_response :success - assert_template 'password' - assert_error_tag :content => /Password doesn't match confirmation/ - - # wrong password - post :password, :password => 'wrongpassword', - :new_password => 'secret123', - :new_password_confirmation => 'secret123' - assert_response :success - assert_template 'password' - assert_equal 'Wrong password', flash[:error] - - # good password - post :password, :password => 'jsmith', - :new_password => 'secret123', - :new_password_confirmation => 'secret123' - assert_redirected_to '/my/account' - assert User.try_to_login('jsmith', 'secret123') - end - - def test_change_password_should_redirect_if_user_cannot_change_its_password - User.find(2).update_attribute(:auth_source_id, 1) - - get :password - assert_not_nil flash[:error] - assert_redirected_to '/my/account' - end - - def test_page_layout - get :page_layout - assert_response :success - assert_template 'page_layout' - end - - def test_add_block - post :add_block, :block => 'issuesreportedbyme' - assert_redirected_to '/my/page_layout' - assert User.find(2).pref[:my_page_layout]['top'].include?('issuesreportedbyme') - end - - def test_add_invalid_block_should_redirect - post :add_block, :block => 'invalid' - assert_redirected_to '/my/page_layout' - end - - def test_remove_block - post :remove_block, :block => 'issuesassignedtome' - assert_redirected_to '/my/page_layout' - assert !User.find(2).pref[:my_page_layout].values.flatten.include?('issuesassignedtome') - end - - def test_order_blocks - xhr :post, :order_blocks, :group => 'left', 'blocks' => ['documents', 'calendar', 'latestnews'] - assert_response :success - assert_equal ['documents', 'calendar', 'latestnews'], User.find(2).pref[:my_page_layout]['left'] - end - - def test_reset_rss_key_with_existing_key - @previous_token_value = User.find(2).rss_key # Will generate one if it's missing - post :reset_rss_key - - assert_not_equal @previous_token_value, User.find(2).rss_key - assert User.find(2).rss_token - assert_match /reset/, flash[:notice] - assert_redirected_to '/my/account' - end - - def test_reset_rss_key_without_existing_key - assert_nil User.find(2).rss_token - post :reset_rss_key - - assert User.find(2).rss_token - assert_match /reset/, flash[:notice] - assert_redirected_to '/my/account' - end - - def test_reset_api_key_with_existing_key - @previous_token_value = User.find(2).api_key # Will generate one if it's missing - post :reset_api_key - - assert_not_equal @previous_token_value, User.find(2).api_key - assert User.find(2).api_token - assert_match /reset/, flash[:notice] - assert_redirected_to '/my/account' - end - - def test_reset_api_key_without_existing_key - assert_nil User.find(2).api_token - post :reset_api_key - - assert User.find(2).api_token - assert_match /reset/, flash[:notice] - assert_redirected_to '/my/account' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/16/16c33740f5a772b206a960577ab56df891c0f33f.svn-base --- a/.svn/pristine/16/16c33740f5a772b206a960577ab56df891c0f33f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -CREATE TABLE 'companies' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'name' TEXT DEFAULT NULL, - 'rating' INTEGER DEFAULT 1 -); - -CREATE TABLE 'replies' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'content' text, - 'created_at' datetime, - 'updated_at' datetime, - 'topic_id' integer -); - -CREATE TABLE 'topics' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'title' varchar(255), - 'subtitle' varchar(255), - 'content' text, - 'created_at' datetime, - 'updated_at' datetime -); - -CREATE TABLE 'developers' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'name' TEXT DEFAULT NULL, - 'salary' INTEGER DEFAULT 70000, - 'created_at' DATETIME DEFAULT NULL, - 'updated_at' DATETIME DEFAULT NULL -); - -CREATE TABLE 'projects' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'name' TEXT DEFAULT NULL -); - -CREATE TABLE 'developers_projects' ( - 'developer_id' INTEGER NOT NULL, - 'project_id' INTEGER NOT NULL, - 'joined_on' DATE DEFAULT NULL, - 'access_level' INTEGER DEFAULT 1 -); diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/16/16e91dd78763ecc12e60528188f6cb6512517aa6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/16/16e91dd78763ecc12e60528188f6cb6512517aa6.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,79 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingTrackersTest < ActionController::IntegrationTest + def test_trackers + assert_routing( + { :method => 'get', :path => "/trackers" }, + { :controller => 'trackers', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/trackers.xml" }, + { :controller => 'trackers', :action => 'index', :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/trackers" }, + { :controller => 'trackers', :action => 'create' } + ) + assert_routing( + { :method => 'post', :path => "/trackers.xml" }, + { :controller => 'trackers', :action => 'create', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/trackers/new" }, + { :controller => 'trackers', :action => 'new' } + ) + assert_routing( + { :method => 'get', :path => "/trackers/new.xml" }, + { :controller => 'trackers', :action => 'new', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/trackers/1/edit" }, + { :controller => 'trackers', :action => 'edit', :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/trackers/1" }, + { :controller => 'trackers', :action => 'update', + :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/trackers/1.xml" }, + { :controller => 'trackers', :action => 'update', + :format => 'xml', :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/trackers/1" }, + { :controller => 'trackers', :action => 'destroy', + :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/trackers/1.xml" }, + { :controller => 'trackers', :action => 'destroy', + :format => 'xml', :id => '1' } + ) + assert_routing( + { :method => 'get', :path => "/trackers/fields" }, + { :controller => 'trackers', :action => 'fields' } + ) + assert_routing( + { :method => 'post', :path => "/trackers/fields" }, + { :controller => 'trackers', :action => 'fields' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/17/1760e8859f909defc3298edd73c13960cad4c773.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/17/1760e8859f909defc3298edd73c13960cad4c773.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,181 @@ +module ObjectHelpers + def User.generate!(attributes={}) + @generated_user_login ||= 'user0' + @generated_user_login.succ! + user = User.new(attributes) + user.login = @generated_user_login.dup if user.login.blank? + user.mail = "#{@generated_user_login}@example.com" if user.mail.blank? + user.firstname = "Bob" if user.firstname.blank? + user.lastname = "Doe" if user.lastname.blank? + yield user if block_given? + user.save! + user + end + + def User.add_to_project(user, project, roles=nil) + roles = Role.find(1) if roles.nil? + roles = [roles] unless roles.is_a?(Array) + Member.create!(:principal => user, :project => project, :roles => roles) + end + + def Group.generate!(attributes={}) + @generated_group_name ||= 'Group 0' + @generated_group_name.succ! + group = Group.new(attributes) + group.name = @generated_group_name.dup if group.name.blank? + yield group if block_given? + group.save! + group + end + + def Project.generate!(attributes={}) + @generated_project_identifier ||= 'project-0000' + @generated_project_identifier.succ! + project = Project.new(attributes) + project.name = @generated_project_identifier.dup if project.name.blank? + project.identifier = @generated_project_identifier.dup if project.identifier.blank? + yield project if block_given? + project.save! + project + end + + def Project.generate_with_parent!(parent, attributes={}) + project = Project.generate!(attributes) + project.set_parent!(parent) + project + end + + def Tracker.generate!(attributes={}) + @generated_tracker_name ||= 'Tracker 0' + @generated_tracker_name.succ! + tracker = Tracker.new(attributes) + tracker.name = @generated_tracker_name.dup if tracker.name.blank? + yield tracker if block_given? + tracker.save! + tracker + end + + def Role.generate!(attributes={}) + @generated_role_name ||= 'Role 0' + @generated_role_name.succ! + role = Role.new(attributes) + role.name = @generated_role_name.dup if role.name.blank? + yield role if block_given? + role.save! + role + end + + # Generates an unsaved Issue + def Issue.generate(attributes={}) + issue = Issue.new(attributes) + issue.project ||= Project.find(1) + issue.tracker ||= issue.project.trackers.first + issue.subject = 'Generated' if issue.subject.blank? + issue.author ||= User.find(2) + yield issue if block_given? + issue + end + + # Generates a saved Issue + def Issue.generate!(attributes={}, &block) + issue = Issue.generate(attributes, &block) + issue.save! + issue + end + + # Generates an issue with 2 children and a grandchild + def Issue.generate_with_descendants!(attributes={}) + issue = Issue.generate!(attributes) + child = Issue.generate!(:project => issue.project, :subject => 'Child1', :parent_issue_id => issue.id) + Issue.generate!(:project => issue.project, :subject => 'Child2', :parent_issue_id => issue.id) + Issue.generate!(:project => issue.project, :subject => 'Child11', :parent_issue_id => child.id) + issue.reload + end + + def Journal.generate!(attributes={}) + journal = Journal.new(attributes) + journal.user ||= User.first + journal.journalized ||= Issue.first + yield journal if block_given? + journal.save! + journal + end + + def Version.generate!(attributes={}) + @generated_version_name ||= 'Version 0' + @generated_version_name.succ! + version = Version.new(attributes) + version.name = @generated_version_name.dup if version.name.blank? + yield version if block_given? + version.save! + version + end + + def TimeEntry.generate!(attributes={}) + entry = TimeEntry.new(attributes) + entry.user ||= User.find(2) + entry.issue ||= Issue.find(1) unless entry.project + entry.project ||= entry.issue.project + entry.activity ||= TimeEntryActivity.first + entry.spent_on ||= Date.today + entry.hours ||= 1.0 + entry.save! + entry + end + + def AuthSource.generate!(attributes={}) + @generated_auth_source_name ||= 'Auth 0' + @generated_auth_source_name.succ! + source = AuthSource.new(attributes) + source.name = @generated_auth_source_name.dup if source.name.blank? + yield source if block_given? + source.save! + source + end + + def Board.generate!(attributes={}) + @generated_board_name ||= 'Forum 0' + @generated_board_name.succ! + board = Board.new(attributes) + board.name = @generated_board_name.dup if board.name.blank? + board.description = @generated_board_name.dup if board.description.blank? + yield board if block_given? + board.save! + board + end + + def Attachment.generate!(attributes={}) + @generated_filename ||= 'testfile0' + @generated_filename.succ! + attributes = attributes.dup + attachment = Attachment.new(attributes) + attachment.container ||= Issue.find(1) + attachment.author ||= User.find(2) + attachment.filename = @generated_filename.dup if attachment.filename.blank? + attachment.save! + attachment + end + + def CustomField.generate!(attributes={}) + @generated_custom_field_name ||= 'Custom field 0' + @generated_custom_field_name.succ! + field = new(attributes) + field.name = @generated_custom_field_name.dup if field.name.blank? + field.field_format = 'string' if field.field_format.blank? + yield field if block_given? + field.save! + field + end + + def Changeset.generate!(attributes={}) + @generated_changeset_rev ||= '123456' + @generated_changeset_rev.succ! + changeset = new(attributes) + changeset.repository ||= Project.find(1).repository + changeset.revision ||= @generated_changeset_rev + changeset.committed_on ||= Time.now + yield changeset if block_given? + changeset.save! + changeset + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/17/176ab48d54517bf4b7c43dc6852d904f4bfffc5a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/17/176ab48d54517bf4b7c43dc6852d904f4bfffc5a.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,54 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class IssueTransactionTest < ActiveSupport::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, + :trackers, :projects_trackers, + :versions, + :issue_statuses, :issue_categories, :issue_relations, :workflows, + :enumerations, + :issues, + :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, + :time_entries + + self.use_transactional_fixtures = false + + def test_invalid_move_to_another_project + parent1 = Issue.generate! + child = Issue.generate!(:parent_issue_id => parent1.id) + grandchild = Issue.generate!(:parent_issue_id => child.id, :tracker_id => 2) + Project.find(2).tracker_ids = [1] + + parent1.reload + assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] + + # child can not be moved to Project 2 because its child is on a disabled tracker + child = Issue.find(child.id) + child.project = Project.find(2) + assert !child.save + child.reload + grandchild.reload + parent1.reload + + # no change + assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] + assert_equal [1, parent1.id, 2, 5], [child.project_id, child.root_id, child.lft, child.rgt] + assert_equal [1, parent1.id, 3, 4], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt] + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/17/176d1dc144c5bd598057e3a7c94346dee9aedc08.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/17/176d1dc144c5bd598057e3a7c94346dee9aedc08.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,13 @@ +class AddViewIssuesPermission < ActiveRecord::Migration + def self.up + Role.all.each do |r| + r.add_permission!(:view_issues) + end + end + + def self.down + Role.all.each do |r| + r.remove_permission!(:view_issues) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/17/178baed3b8d7cd58a7e5bbe97a67c4757201c153.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/17/178baed3b8d7cd58a7e5bbe97a67c4757201c153.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,53 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class PreviewsController < ApplicationController + before_filter :find_project, :find_attachments + + def issue + @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? + if @issue + @description = params[:issue] && params[:issue][:description] + if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") + @description = nil + end + # params[:notes] is useful for preview of notes in issue history + @notes = params[:notes] || (params[:issue] ? params[:issue][:notes] : nil) + else + @description = (params[:issue] ? params[:issue][:description] : nil) + end + render :layout => false + end + + def news + if params[:id].present? && news = News.visible.find_by_id(params[:id]) + @previewed = news + end + @text = (params[:news] ? params[:news][:description] : nil) + render :partial => 'common/preview' + end + + private + + def find_project + project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id] + @project = Project.find(project_id) + rescue ActiveRecord::RecordNotFound + render_404 + end + +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/17/17973741c668d43d267b734fcb018ab64a0d87f9.svn-base --- a/.svn/pristine/17/17973741c668d43d267b734fcb018ab64a0d87f9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -

    <%= l(:label_plugins) %>

    - -<% if @plugins.any? %> - - <% @plugins.each do |plugin| %> - - - - - - - <% end %> -
    <%=h plugin.name %> - <%= content_tag('span', h(plugin.description), :class => 'description') unless plugin.description.blank? %> - <%= content_tag('span', link_to(h(plugin.url), plugin.url), :class => 'url') unless plugin.url.blank? %> - <%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %><%=h plugin.version %><%= link_to(l(:button_configure), :controller => 'settings', :action => 'plugin', :id => plugin.id) if plugin.configurable? %>
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/17/17c033d2aba8bd48a0c5240eb040a0c6c95834ea.svn-base --- a/.svn/pristine/17/17c033d2aba8bd48a0c5240eb040a0c6c95834ea.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -class IssueMove < ActiveRecord::Migration - # model removed - class Permission < ActiveRecord::Base; end - - def self.up - Permission.create :controller => "projects", :action => "move_issues", :description => "button_move", :sort => 1061, :mail_option => 0, :mail_enabled => 0 - end - - def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'move_issues']).destroy - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/17/17c53021398a51ccae878d86cdd4f0a66880e996.svn-base --- a/.svn/pristine/17/17c53021398a51ccae878d86cdd4f0a66880e996.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -class AddCommentsPermissions < ActiveRecord::Migration - # model removed - class Permission < ActiveRecord::Base; end - - def self.up - Permission.create :controller => "news", :action => "add_comment", :description => "label_comment_add", :sort => 1130, :is_public => false, :mail_option => 0, :mail_enabled => 0 - Permission.create :controller => "news", :action => "destroy_comment", :description => "label_comment_delete", :sort => 1133, :is_public => false, :mail_option => 0, :mail_enabled => 0 - end - - def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'news', 'add_comment']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'news', 'destroy_comment']).destroy - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/18/1834715ab6ce08d2e0bc1e49b4f06e012364d78b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/18/1834715ab6ce08d2e0bc1e49b4f06e012364d78b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module AccountHelper +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/18/1847d8107fa008755837ae110701a96cb77fd2c3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/18/1847d8107fa008755837ae110701a96cb77fd2c3.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,23 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class IssuePriorityCustomField < CustomField + def type_name + :enumeration_issue_priorities + end +end + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/18/18e8c38332487b5baaa2a884c66c256e8b7f9e68.svn-base --- a/.svn/pristine/18/18e8c38332487b5baaa2a884c66c256e8b7f9e68.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::HttpBasicLoginWithApiTokenTest < ActionController::IntegrationTest - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows - - def setup - Setting.rest_api_enabled = '1' - Setting.login_required = '1' - end - - def teardown - Setting.rest_api_enabled = '0' - Setting.login_required = '0' - end - - # Using the NewsController because it's a simple API. - context "get /news" do - - context "in :xml format" do - should_allow_http_basic_auth_with_key(:get, "/news.xml") - end - - context "in :json format" do - should_allow_http_basic_auth_with_key(:get, "/news.json") - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/18/18ef0fecd81e51c21a37f84582d5c50bacf3c380.svn-base --- a/.svn/pristine/18/18ef0fecd81e51c21a37f84582d5c50bacf3c380.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class WikiContentVersionTest < ActiveSupport::TestCase - fixtures :projects, :users, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions - - def setup - end - - def test_destroy - v = WikiContent::Version.find(2) - - assert_difference 'WikiContent::Version.count', -1 do - v.destroy - end - end - - def test_destroy_last_version_should_revert_content - v = WikiContent::Version.find(3) - - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_difference 'WikiContent::Version.count', -1 do - assert v.destroy - end - end - end - c = WikiContent.find(1) - v = c.versions.last - assert_equal 2, c.version - assert_equal v.version, c.version - assert_equal v.comments, c.comments - assert_equal v.text, c.text - assert_equal v.author, c.author - assert_equal v.updated_on, c.updated_on - end - - def test_destroy_all_versions_should_delete_page - WikiContent::Version.find(1).destroy - WikiContent::Version.find(2).destroy - v = WikiContent::Version.find(3) - - assert_difference 'WikiPage.count', -1 do - assert_difference 'WikiContent.count', -1 do - assert_difference 'WikiContent::Version.count', -1 do - assert v.destroy - end - end - end - assert_nil WikiPage.find_by_id(1) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/19/1905c04f7ae0c6da24070033b03c6ae55e5dcb3e.svn-base --- a/.svn/pristine/19/1905c04f7ae0c6da24070033b03c6ae55e5dcb3e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -<%= render :partial => 'action_menu' %> - -

    <%=l(:label_workflow)%>

    - -
    -
      -
    • <%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker} %>
    • -
    • <%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %>
    • -
    -
    - -

    <%=l(:text_workflow_edit)%>:

    - -<%= form_tag({}, :method => 'get') do %> -

    - - - - - <%= submit_tag l(:button_edit), :name => nil %> - - <%= hidden_field_tag 'used_statuses_only', '0' %> - -

    -<% end %> - -<% if @tracker && @role && @statuses.any? %> - <%= form_tag({}, :id => 'workflow_form' ) do %> - <%= hidden_field_tag 'tracker_id', @tracker.id %> - <%= hidden_field_tag 'role_id', @role.id %> - <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %> -
    - - - - - - - - - <% for status in @statuses %> - - <% end %> - - - - - - - <% @fields.each do |field, name| %> - "> - - <% for status in @statuses -%> - - <% end -%> - - <% end %> - <% if @custom_fields.any? %> - - - - <% @custom_fields.each do |field| %> - "> - - <% for status in @statuses -%> - - <% end -%> - - <% end %> - <% end %> - -
    - <%=l(:label_issue_status)%>
    - <%=h status.name %> -
    -   - <%= l(:field_core_fields) %> -
    - <%=h name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %> - - <%= field_permission_tag(@permissions, status, field) %> -
    -   - <%= l(:label_custom_field_plural) %> -
    - <%=h field.name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %> - - <%= field_permission_tag(@permissions, status, field) %> -
    -
    - <%= submit_tag l(:button_save) %> - <% end %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/19/1932ee9bec078be0ff0c0caab9f5e072b2fcecac.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/19/1932ee9bec078be0ff0c0caab9f5e072b2fcecac.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,238 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class VersionsControllerTest < ActionController::TestCase + fixtures :projects, :versions, :issues, :users, :roles, :members, + :member_roles, :enabled_modules, :issue_statuses, + :issue_categories + + def setup + User.current = nil + end + + def test_index + get :index, :project_id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:versions) + # Version with no date set appears + assert assigns(:versions).include?(Version.find(3)) + # Completed version doesn't appear + assert !assigns(:versions).include?(Version.find(1)) + # Context menu on issues + assert_select "script", :text => Regexp.new(Regexp.escape("contextMenuInit('/issues/context_menu')")) + assert_select "div#sidebar" do + # Links to versions anchors + assert_select 'a[href=?]', '#2.0' + # Links to completed versions in the sidebar + assert_select 'a[href=?]', '/versions/1' + end + end + + def test_index_with_completed_versions + get :index, :project_id => 1, :completed => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:versions) + # Version with no date set appears + assert assigns(:versions).include?(Version.find(3)) + # Completed version appears + assert assigns(:versions).include?(Version.find(1)) + end + + def test_index_with_tracker_ids + get :index, :project_id => 1, :tracker_ids => [1, 3] + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues_by_version) + assert_nil assigns(:issues_by_version).values.flatten.detect {|issue| issue.tracker_id == 2} + end + + def test_index_showing_subprojects_versions + @subproject_version = Version.create!(:project => Project.find(3), :name => "Subproject version") + get :index, :project_id => 1, :with_subprojects => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:versions) + + assert assigns(:versions).include?(Version.find(4)), "Shared version not found" + assert assigns(:versions).include?(@subproject_version), "Subproject version not found" + end + + def test_index_should_prepend_shared_versions + get :index, :project_id => 1 + assert_response :success + + assert_select '#sidebar' do + assert_select 'a[href=?]', '#2.0', :text => '2.0' + assert_select 'a[href=?]', '#subproject1-2.0', :text => 'eCookbook Subproject 1 - 2.0' + end + assert_select '#content' do + assert_select 'a[name=?]', '2.0', :text => '2.0' + assert_select 'a[name=?]', 'subproject1-2.0', :text => 'eCookbook Subproject 1 - 2.0' + end + end + + def test_show + get :show, :id => 2 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:version) + + assert_tag :tag => 'h2', :content => /1.0/ + end + + def test_show_should_display_nil_counts + with_settings :default_language => 'en' do + get :show, :id => 2, :status_by => 'category' + assert_response :success + assert_select 'div#status_by' do + assert_select 'select[name=status_by]' do + assert_select 'option[value=category][selected=selected]' + end + assert_select 'a', :text => 'none' + end + end + end + + def test_new + @request.session[:user_id] = 2 + get :new, :project_id => '1' + assert_response :success + assert_template 'new' + end + + def test_new_from_issue_form + @request.session[:user_id] = 2 + xhr :get, :new, :project_id => '1' + assert_response :success + assert_template 'new' + assert_equal 'text/javascript', response.content_type + end + + def test_create + @request.session[:user_id] = 2 # manager + assert_difference 'Version.count' do + post :create, :project_id => '1', :version => {:name => 'test_add_version'} + end + assert_redirected_to '/projects/ecookbook/settings/versions' + version = Version.find_by_name('test_add_version') + assert_not_nil version + assert_equal 1, version.project_id + end + + def test_create_from_issue_form + @request.session[:user_id] = 2 + assert_difference 'Version.count' do + xhr :post, :create, :project_id => '1', :version => {:name => 'test_add_version_from_issue_form'} + end + version = Version.find_by_name('test_add_version_from_issue_form') + assert_not_nil version + assert_equal 1, version.project_id + + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + assert_include 'test_add_version_from_issue_form', response.body + end + + def test_create_from_issue_form_with_failure + @request.session[:user_id] = 2 + assert_no_difference 'Version.count' do + xhr :post, :create, :project_id => '1', :version => {:name => ''} + end + assert_response :success + assert_template 'new' + assert_equal 'text/javascript', response.content_type + end + + def test_get_edit + @request.session[:user_id] = 2 + get :edit, :id => 2 + assert_response :success + assert_template 'edit' + end + + def test_close_completed + Version.update_all("status = 'open'") + @request.session[:user_id] = 2 + put :close_completed, :project_id => 'ecookbook' + assert_redirected_to :controller => 'projects', :action => 'settings', + :tab => 'versions', :id => 'ecookbook' + assert_not_nil Version.find_by_status('closed') + end + + def test_post_update + @request.session[:user_id] = 2 + put :update, :id => 2, + :version => {:name => 'New version name', + :effective_date => Date.today.strftime("%Y-%m-%d")} + assert_redirected_to :controller => 'projects', :action => 'settings', + :tab => 'versions', :id => 'ecookbook' + version = Version.find(2) + assert_equal 'New version name', version.name + assert_equal Date.today, version.effective_date + end + + def test_post_update_with_validation_failure + @request.session[:user_id] = 2 + put :update, :id => 2, + :version => { :name => '', + :effective_date => Date.today.strftime("%Y-%m-%d")} + assert_response :success + assert_template 'edit' + end + + def test_destroy + @request.session[:user_id] = 2 + assert_difference 'Version.count', -1 do + delete :destroy, :id => 3 + end + assert_redirected_to :controller => 'projects', :action => 'settings', + :tab => 'versions', :id => 'ecookbook' + assert_nil Version.find_by_id(3) + end + + def test_destroy_version_in_use_should_fail + @request.session[:user_id] = 2 + assert_no_difference 'Version.count' do + delete :destroy, :id => 2 + end + assert_redirected_to :controller => 'projects', :action => 'settings', + :tab => 'versions', :id => 'ecookbook' + assert flash[:error].match(/Unable to delete version/) + assert Version.find_by_id(2) + end + + def test_issue_status_by + xhr :get, :status_by, :id => 2 + assert_response :success + assert_template 'status_by' + assert_template '_issue_counts' + end + + def test_issue_status_by_status + xhr :get, :status_by, :id => 2, :status_by => 'status' + assert_response :success + assert_template 'status_by' + assert_template '_issue_counts' + assert_include 'Assigned', response.body + assert_include 'Closed', response.body + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/19/194013fb324f2790d7cffbbc3147b775a3c4dd89.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/19/194013fb324f2790d7cffbbc3147b775a3c4dd89.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,809 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'tcpdf' +require 'fpdf/chinese' +require 'fpdf/japanese' +require 'fpdf/korean' + +if RUBY_VERSION < '1.9' + require 'iconv' +end + +module Redmine + module Export + module PDF + include ActionView::Helpers::TextHelper + include ActionView::Helpers::NumberHelper + include IssuesHelper + + class ITCPDF < TCPDF + include Redmine::I18n + attr_accessor :footer_date + + def initialize(lang, orientation='P') + @@k_path_cache = Rails.root.join('tmp', 'pdf') + FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache) + set_language_if_valid lang + pdf_encoding = l(:general_pdf_encoding).upcase + super(orientation, 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding) + case current_language.to_s.downcase + when 'vi' + @font_for_content = 'DejaVuSans' + @font_for_footer = 'DejaVuSans' + else + case pdf_encoding + when 'UTF-8' + @font_for_content = 'FreeSans' + @font_for_footer = 'FreeSans' + when 'CP949' + extend(PDF_Korean) + AddUHCFont() + @font_for_content = 'UHC' + @font_for_footer = 'UHC' + when 'CP932', 'SJIS', 'SHIFT_JIS' + extend(PDF_Japanese) + AddSJISFont() + @font_for_content = 'SJIS' + @font_for_footer = 'SJIS' + when 'GB18030' + extend(PDF_Chinese) + AddGBFont() + @font_for_content = 'GB' + @font_for_footer = 'GB' + when 'BIG5' + extend(PDF_Chinese) + AddBig5Font() + @font_for_content = 'Big5' + @font_for_footer = 'Big5' + else + @font_for_content = 'Arial' + @font_for_footer = 'Helvetica' + end + end + SetCreator(Redmine::Info.app_name) + SetFont(@font_for_content) + @outlines = [] + @outlineRoot = nil + end + + def SetFontStyle(style, size) + SetFont(@font_for_content, style, size) + end + + def SetTitle(txt) + txt = begin + utf16txt = to_utf16(txt) + hextxt = "" + rescue + txt + end || '' + super(txt) + end + + def textstring(s) + # Format a text string + if s =~ /^\{\{([<>]?)toc\}\}<\/p>/i, '') + html + end + + # Encodes an UTF-8 string to UTF-16BE + def to_utf16(str) + if str.respond_to?(:encode) + str.encode('UTF-16BE') + else + Iconv.conv('UTF-16BE', 'UTF-8', str) + end + end + + def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='') + Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link) + end + + def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1) + MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln) + end + + def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0) + @attachments = attachments + writeHTMLCell(w, h, x, y, + fix_text_encoding(formatted_text(txt)), + border, ln, fill) + end + + def getImageFilename(attrname) + # attrname: general_pdf_encoding string file/uri name + atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding)) + if atta + return atta.diskfile + else + return nil + end + end + + def Footer + SetFont(@font_for_footer, 'I', 8) + SetY(-15) + SetX(15) + RDMCell(0, 5, @footer_date, 0, 0, 'L') + SetY(-15) + SetX(-30) + RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') + end + + def Bookmark(txt, level=0, y=0) + if (y == -1) + y = GetY() + end + @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k} + end + + def bookmark_title(txt) + txt = begin + utf16txt = to_utf16(txt) + hextxt = "" + rescue + txt + end || '' + end + + def putbookmarks + nb=@outlines.size + return if (nb==0) + lru=[] + level=0 + @outlines.each_with_index do |o, i| + if(o[:l]>0) + parent=lru[o[:l]-1] + #Set parent and last pointers + @outlines[i][:parent]=parent + @outlines[parent][:last]=i + if (o[:l]>level) + #Level increasing: set first pointer + @outlines[parent][:first]=i + end + else + @outlines[i][:parent]=nb + end + if (o[:l]<=level && i>0) + #Set prev and next pointers + prev=lru[o[:l]] + @outlines[prev][:next]=i + @outlines[i][:prev]=prev + end + lru[o[:l]]=i + level=o[:l] + end + #Outline items + n=self.n+1 + @outlines.each_with_index do |o, i| + newobj() + out('<>') + out('endobj') + end + #Outline root + newobj() + @outlineRoot=self.n + out("<>"); + out('endobj'); + end + + def putresources() + super + putbookmarks() + end + + def putcatalog() + super + if(@outlines.size > 0) + out("/Outlines #{@outlineRoot} 0 R"); + out('/PageMode /UseOutlines'); + end + end + end + + # fetch row values + def fetch_row_values(issue, query, level) + query.inline_columns.collect do |column| + s = if column.is_a?(QueryCustomFieldColumn) + cv = issue.visible_custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} + show_value(cv) + else + value = issue.send(column.name) + if column.name == :subject + value = " " * level + value + end + if value.is_a?(Date) + format_date(value) + elsif value.is_a?(Time) + format_time(value) + else + value + end + end + s.to_s + end + end + + # calculate columns width + def calc_col_width(issues, query, table_width, pdf) + # calculate statistics + # by captions + pdf.SetFontStyle('B',8) + col_padding = pdf.GetStringWidth('OO') + col_width_min = query.inline_columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding} + col_width_max = Array.new(col_width_min) + col_width_avg = Array.new(col_width_min) + word_width_max = query.inline_columns.map {|c| + n = 10 + c.caption.split.each {|w| + x = pdf.GetStringWidth(w) + col_padding + n = x if n < x + } + n + } + + # by properties of issues + pdf.SetFontStyle('',8) + col_padding = pdf.GetStringWidth('OO') + k = 1 + issue_list(issues) {|issue, level| + k += 1 + values = fetch_row_values(issue, query, level) + values.each_with_index {|v,i| + n = pdf.GetStringWidth(v) + col_padding + col_width_max[i] = n if col_width_max[i] < n + col_width_min[i] = n if col_width_min[i] > n + col_width_avg[i] += n + v.split.each {|w| + x = pdf.GetStringWidth(w) + col_padding + word_width_max[i] = x if word_width_max[i] < x + } + } + } + col_width_avg.map! {|x| x / k} + + # calculate columns width + ratio = table_width / col_width_avg.inject(0, :+) + col_width = col_width_avg.map {|w| w * ratio} + + # correct max word width if too many columns + ratio = table_width / word_width_max.inject(0, :+) + word_width_max.map! {|v| v * ratio} if ratio < 1 + + # correct and lock width of some columns + done = 1 + col_fix = [] + col_width.each_with_index do |w,i| + if w > col_width_max[i] + col_width[i] = col_width_max[i] + col_fix[i] = 1 + done = 0 + elsif w < word_width_max[i] + col_width[i] = word_width_max[i] + col_fix[i] = 1 + done = 0 + else + col_fix[i] = 0 + end + end + + # iterate while need to correct and lock coluns width + while done == 0 + # calculate free & locked columns width + done = 1 + fix_col_width = 0 + free_col_width = 0 + col_width.each_with_index do |w,i| + if col_fix[i] == 1 + fix_col_width += w + else + free_col_width += w + end + end + + # calculate column normalizing ratio + if free_col_width == 0 + ratio = table_width / col_width.inject(0, :+) + else + ratio = (table_width - fix_col_width) / free_col_width + end + + # correct columns width + col_width.each_with_index do |w,i| + if col_fix[i] == 0 + col_width[i] = w * ratio + + # check if column width less then max word width + if col_width[i] < word_width_max[i] + col_width[i] = word_width_max[i] + col_fix[i] = 1 + done = 0 + elsif col_width[i] > col_width_max[i] + col_width[i] = col_width_max[i] + col_fix[i] = 1 + done = 0 + end + end + end + end + col_width + end + + def render_table_header(pdf, query, col_width, row_height, table_width) + # headers + pdf.SetFontStyle('B',8) + pdf.SetFillColor(230, 230, 230) + + # render it background to find the max height used + base_x = pdf.GetX + base_y = pdf.GetY + max_height = issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) + pdf.Rect(base_x, base_y, table_width, max_height, 'FD'); + pdf.SetXY(base_x, base_y); + + # write the cells on page + issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) + issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) + pdf.SetY(base_y + max_height); + + # rows + pdf.SetFontStyle('',8) + pdf.SetFillColor(255, 255, 255) + end + + # Returns a PDF string of a list of issues + def issues_to_pdf(issues, project, query) + pdf = ITCPDF.new(current_language, "L") + title = query.new_record? ? l(:label_issue_plural) : query.name + title = "#{project} - #{title}" if project + pdf.SetTitle(title) + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.SetAutoPageBreak(false) + pdf.AddPage("L") + + # Landscape A4 = 210 x 297 mm + page_height = 210 + page_width = 297 + left_margin = 10 + right_margin = 10 + bottom_margin = 20 + row_height = 4 + + # column widths + table_width = page_width - right_margin - left_margin + col_width = [] + unless query.inline_columns.empty? + col_width = calc_col_width(issues, query, table_width, pdf) + table_width = col_width.inject(0, :+) + end + + # use full width if the description is displayed + if table_width > 0 && query.has_column?(:description) + col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width} + table_width = col_width.inject(0, :+) + end + + # title + pdf.SetFontStyle('B',11) + pdf.RDMCell(190,10, title) + pdf.Ln + render_table_header(pdf, query, col_width, row_height, table_width) + previous_group = false + issue_list(issues) do |issue, level| + if query.grouped? && + (group = query.group_by_column.value(issue)) != previous_group + pdf.SetFontStyle('B',10) + group_label = group.blank? ? 'None' : group.to_s.dup + group_label << " (#{query.issue_count_by_group[group]})" + pdf.Bookmark group_label, 0, -1 + pdf.RDMCell(table_width, row_height * 2, group_label, 1, 1, 'L') + pdf.SetFontStyle('',8) + previous_group = group + end + + # fetch row values + col_values = fetch_row_values(issue, query, level) + + # render it off-page to find the max height used + base_x = pdf.GetX + base_y = pdf.GetY + pdf.SetY(2 * page_height) + max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) + pdf.SetXY(base_x, base_y) + + # make new page if it doesn't fit on the current one + space_left = page_height - base_y - bottom_margin + if max_height > space_left + pdf.AddPage("L") + render_table_header(pdf, query, col_width, row_height, table_width) + base_x = pdf.GetX + base_y = pdf.GetY + end + + # write the cells on page + issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) + issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) + pdf.SetY(base_y + max_height); + + if query.has_column?(:description) && issue.description? + pdf.SetX(10) + pdf.SetAutoPageBreak(true, 20) + pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT") + pdf.SetAutoPageBreak(false) + end + end + + if issues.size == Setting.issues_export_limit.to_i + pdf.SetFontStyle('B',10) + pdf.RDMCell(0, row_height, '...') + end + pdf.Output + end + + # Renders MultiCells and returns the maximum height used + def issues_to_pdf_write_cells(pdf, col_values, col_widths, row_height, head=false) + base_y = pdf.GetY + max_height = row_height + col_values.each_with_index do |column, i| + col_x = pdf.GetX + if head == true + pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1) + else + pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1) + end + max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height + pdf.SetXY(col_x + col_widths[i], base_y); + end + return max_height + end + + # Draw lines to close the row (MultiCell border drawing in not uniform) + # + # parameter "col_id_width" is not used. it is kept for compatibility. + def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y, + col_id_width, col_widths) + col_x = top_x + pdf.Line(col_x, top_y, col_x, lower_y) # id right border + col_widths.each do |width| + col_x += width + pdf.Line(col_x, top_y, col_x, lower_y) # columns right border + end + pdf.Line(top_x, top_y, top_x, lower_y) # left border + pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border + end + + # Returns a PDF string of a single issue + def issue_to_pdf(issue, assoc={}) + pdf = ITCPDF.new(current_language) + pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}") + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.AddPage + pdf.SetFontStyle('B',11) + buf = "#{issue.project} - #{issue.tracker} ##{issue.id}" + pdf.RDMMultiCell(190, 5, buf) + pdf.SetFontStyle('',8) + base_x = pdf.GetX + i = 1 + issue.ancestors.visible.each do |ancestor| + pdf.SetX(base_x + i) + buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" + pdf.RDMMultiCell(190 - i, 5, buf) + i += 1 if i < 35 + end + pdf.SetFontStyle('B',11) + pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s) + pdf.SetFontStyle('',8) + pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}") + pdf.Ln + + left = [] + left << [l(:field_status), issue.status] + left << [l(:field_priority), issue.priority] + left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') + left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') + left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') + + right = [] + right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') + right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') + right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') + right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') + right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) + + rows = left.size > right.size ? left.size : right.size + while left.size < rows + left << nil + end + while right.size < rows + right << nil + end + + half = (issue.visible_custom_field_values.size / 2.0).ceil + issue.visible_custom_field_values.each_with_index do |custom_value, i| + (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)] + end + + rows = left.size > right.size ? left.size : right.size + rows.times do |i| + item = left[i] + pdf.SetFontStyle('B',9) + pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") + pdf.SetFontStyle('',9) + pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") + + item = right[i] + pdf.SetFontStyle('B',9) + pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") + pdf.SetFontStyle('',9) + pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") + pdf.Ln + end + + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) + pdf.SetFontStyle('',9) + + # Set resize image scale + pdf.SetImageScale(1.6) + pdf.RDMwriteHTMLCell(35+155, 5, 0, 0, + issue.description.to_s, issue.attachments, "LRB") + + unless issue.leaf? + # for CJK + truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 ) + + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") + pdf.Ln + issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| + buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}", + :length => truncate_length) + level = 10 if level >= 10 + pdf.SetFontStyle('',8) + pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L") + pdf.SetFontStyle('B',8) + pdf.RDMCell(20,5, child.status.to_s, "R") + pdf.Ln + end + end + + relations = issue.relations.select { |r| r.other_issue(issue).visible? } + unless relations.empty? + # for CJK + truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 ) + + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") + pdf.Ln + relations.each do |relation| + buf = "" + buf += "#{l(relation.label_for(issue))} " + if relation.delay && relation.delay != 0 + buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) " + end + if Setting.cross_project_issue_relations? + buf += "#{relation.other_issue(issue).project} - " + end + buf += "#{relation.other_issue(issue).tracker}" + + " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}" + buf = truncate(buf, :length => truncate_length) + pdf.SetFontStyle('', 8) + pdf.RDMCell(35+155-60, 5, buf, "L") + pdf.SetFontStyle('B',8) + pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "") + pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "") + pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R") + pdf.Ln + end + end + pdf.RDMCell(190,5, "", "T") + pdf.Ln + + if issue.changesets.any? && + User.current.allowed_to?(:view_changesets, issue.project) + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_associated_revisions), "B") + pdf.Ln + for changeset in issue.changesets + pdf.SetFontStyle('B',8) + csstr = "#{l(:label_revision)} #{changeset.format_identifier} - " + csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s + pdf.RDMCell(190, 5, csstr) + pdf.Ln + unless changeset.comments.blank? + pdf.SetFontStyle('',8) + pdf.RDMwriteHTMLCell(190,5,0,0, + changeset.comments.to_s, issue.attachments, "") + end + pdf.Ln + end + end + + if assoc[:journals].present? + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_history), "B") + pdf.Ln + assoc[:journals].each do |journal| + pdf.SetFontStyle('B',8) + title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}" + title << " (#{l(:field_private_notes)})" if journal.private_notes? + pdf.RDMCell(190,5, title) + pdf.Ln + pdf.SetFontStyle('I',8) + details_to_strings(journal.visible_details, true).each do |string| + pdf.RDMMultiCell(190,5, "- " + string) + end + if journal.notes? + pdf.Ln unless journal.details.empty? + pdf.SetFontStyle('',8) + pdf.RDMwriteHTMLCell(190,5,0,0, + journal.notes.to_s, issue.attachments, "") + end + pdf.Ln + end + end + + if issue.attachments.any? + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_attachment_plural), "B") + pdf.Ln + for attachment in issue.attachments + pdf.SetFontStyle('',8) + pdf.RDMCell(80,5, attachment.filename) + pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") + pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") + pdf.RDMCell(65,5, attachment.author.name,0,0,"R") + pdf.Ln + end + end + pdf.Output + end + + # Returns a PDF string of a set of wiki pages + def wiki_pages_to_pdf(pages, project) + pdf = ITCPDF.new(current_language) + pdf.SetTitle(project.name) + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.AddPage + pdf.SetFontStyle('B',11) + pdf.RDMMultiCell(190,5, project.name) + pdf.Ln + # Set resize image scale + pdf.SetImageScale(1.6) + pdf.SetFontStyle('',9) + write_page_hierarchy(pdf, pages.group_by(&:parent_id)) + pdf.Output + end + + # Returns a PDF string of a single wiki page + def wiki_page_to_pdf(page, project) + pdf = ITCPDF.new(current_language) + pdf.SetTitle("#{project} - #{page.title}") + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.AddPage + pdf.SetFontStyle('B',11) + pdf.RDMMultiCell(190,5, + "#{project} - #{page.title} - # #{page.content.version}") + pdf.Ln + # Set resize image scale + pdf.SetImageScale(1.6) + pdf.SetFontStyle('',9) + write_wiki_page(pdf, page) + pdf.Output + end + + def write_page_hierarchy(pdf, pages, node=nil, level=0) + if pages[node] + pages[node].each do |page| + if @new_page + pdf.AddPage + else + @new_page = true + end + pdf.Bookmark page.title, level + write_wiki_page(pdf, page) + write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id] + end + end + end + + def write_wiki_page(pdf, page) + pdf.RDMwriteHTMLCell(190,5,0,0, + page.content.text.to_s, page.attachments, 0) + if page.attachments.any? + pdf.Ln + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_attachment_plural), "B") + pdf.Ln + for attachment in page.attachments + pdf.SetFontStyle('',8) + pdf.RDMCell(80,5, attachment.filename) + pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") + pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") + pdf.RDMCell(65,5, attachment.author.name,0,0,"R") + pdf.Ln + end + end + end + + class RDMPdfEncoding + def self.rdm_from_utf8(txt, encoding) + txt ||= '' + txt = Redmine::CodesetUtil.from_utf8(txt, encoding) + if txt.respond_to?(:force_encoding) + txt.force_encoding('ASCII-8BIT') + end + txt + end + + def self.attach(attachments, filename, encoding) + filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding) + atta = nil + if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i + atta = Attachment.latest_attach(attachments, filename_utf8) + end + if atta && atta.readable? && atta.visible? + return atta + else + return nil + end + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/19/1959b21aed080e49e7eb19db5f9d559c39f78f7a.svn-base --- a/.svn/pristine/19/1959b21aed080e49e7eb19db5f9d559c39f78f7a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'journals_controller' - -# Re-raise errors caught by the controller. -class JournalsController; def rescue_action(e) raise e end; end - -class JournalsControllerTest < ActionController::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules, - :trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects - - def setup - @controller = JournalsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_index - get :index, :project_id => 1 - assert_response :success - assert_not_nil assigns(:journals) - assert_equal 'application/atom+xml', @response.content_type - end - - def test_index_should_return_privates_notes_with_permission_only - journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1) - @request.session[:user_id] = 2 - - get :index, :project_id => 1 - assert_response :success - assert_include journal, assigns(:journals) - - Role.find(1).remove_permission! :view_private_notes - get :index, :project_id => 1 - assert_response :success - assert_not_include journal, assigns(:journals) - end - - def test_diff - get :diff, :id => 3, :detail_id => 4 - assert_response :success - assert_template 'diff' - - assert_tag 'span', - :attributes => {:class => 'diff_out'}, - :content => /removed/ - assert_tag 'span', - :attributes => {:class => 'diff_in'}, - :content => /added/ - end - - def test_reply_to_issue - @request.session[:user_id] = 2 - xhr :get, :new, :id => 6 - assert_response :success - assert_template 'new' - assert_equal 'text/javascript', response.content_type - assert_include '> This is an issue', response.body - end - - def test_reply_to_issue_without_permission - @request.session[:user_id] = 7 - xhr :get, :new, :id => 6 - assert_response 403 - end - - def test_reply_to_note - @request.session[:user_id] = 2 - xhr :get, :new, :id => 6, :journal_id => 4 - assert_response :success - assert_template 'new' - assert_equal 'text/javascript', response.content_type - assert_include '> A comment with a private version', response.body - end - - def test_reply_to_private_note_should_fail_without_permission - journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true) - @request.session[:user_id] = 2 - - xhr :get, :new, :id => 2, :journal_id => journal.id - assert_response :success - assert_template 'new' - assert_equal 'text/javascript', response.content_type - assert_include '> Privates notes', response.body - - Role.find(1).remove_permission! :view_private_notes - xhr :get, :new, :id => 2, :journal_id => journal.id - assert_response 404 - end - - def test_edit_xhr - @request.session[:user_id] = 1 - xhr :get, :edit, :id => 2 - assert_response :success - assert_template 'edit' - assert_equal 'text/javascript', response.content_type - assert_include 'textarea', response.body - end - - def test_edit_private_note_should_fail_without_permission - journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true) - @request.session[:user_id] = 2 - Role.find(1).add_permission! :edit_issue_notes - - xhr :get, :edit, :id => journal.id - assert_response :success - assert_template 'edit' - assert_equal 'text/javascript', response.content_type - assert_include 'textarea', response.body - - Role.find(1).remove_permission! :view_private_notes - xhr :get, :edit, :id => journal.id - assert_response 404 - end - - def test_update_xhr - @request.session[:user_id] = 1 - xhr :post, :edit, :id => 2, :notes => 'Updated notes' - assert_response :success - assert_template 'update' - assert_equal 'text/javascript', response.content_type - assert_equal 'Updated notes', Journal.find(2).notes - assert_include 'journal-2-notes', response.body - end - - def test_update_xhr_with_empty_notes_should_delete_the_journal - @request.session[:user_id] = 1 - assert_difference 'Journal.count', -1 do - xhr :post, :edit, :id => 2, :notes => '' - assert_response :success - assert_template 'update' - assert_equal 'text/javascript', response.content_type - end - assert_nil Journal.find_by_id(2) - assert_include 'change-2', response.body - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/19/196c7c4e4ef7e50cda7944736d264f90424a0cab.svn-base --- a/.svn/pristine/19/196c7c4e4ef7e50cda7944736d264f90424a0cab.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,145 +0,0 @@ -#!/usr/bin/env ruby - -require 'net/http' -require 'net/https' -require 'uri' -require 'optparse' - -module Net - class HTTPS < HTTP - def self.post_form(url, params, headers, options={}) - request = Post.new(url.path) - request.form_data = params - request.initialize_http_header(headers) - request.basic_auth url.user, url.password if url.user - http = new(url.host, url.port) - http.use_ssl = (url.scheme == 'https') - if options[:no_check_certificate] - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - http.start {|h| h.request(request) } - end - end -end - -class RedmineMailHandler - VERSION = '0.2.1' - - attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :no_permission_check, :url, :key, :no_check_certificate - - def initialize - self.issue_attributes = {} - - optparse = OptionParser.new do |opts| - opts.banner = "Usage: rdm-mailhandler.rb [options] --url= --key=" - opts.separator("") - opts.separator("Reads an email from standard input and forward it to a Redmine server through a HTTP request.") - opts.separator("") - opts.separator("Required arguments:") - opts.on("-u", "--url URL", "URL of the Redmine server") {|v| self.url = v} - opts.on("-k", "--key KEY", "Redmine API key") {|v| self.key = v} - opts.separator("") - opts.separator("General options:") - opts.on("--unknown-user ACTION", "how to handle emails from an unknown user", - "ACTION can be one of the following values:", - "* ignore: email is ignored (default)", - "* accept: accept as anonymous user", - "* create: create a user account") {|v| self.unknown_user = v} - opts.on("--no-permission-check", "disable permission checking when receiving", - "the email") {self.no_permission_check = '1'} - opts.on("--key-file FILE", "path to a file that contains the Redmine", - "API key (use this option instead of --key", - "if you don't the key to appear in the", - "command line)") {|v| read_key_from_file(v)} - opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true} - opts.on("-h", "--help", "show this help") {puts opts; exit 1} - opts.on("-v", "--verbose", "show extra information") {self.verbose = true} - opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit} - opts.separator("") - opts.separator("Issue attributes control options:") - opts.on("-p", "--project PROJECT", "identifier of the target project") {|v| self.issue_attributes['project'] = v} - opts.on("-s", "--status STATUS", "name of the target status") {|v| self.issue_attributes['status'] = v} - opts.on("-t", "--tracker TRACKER", "name of the target tracker") {|v| self.issue_attributes['tracker'] = v} - opts.on( "--category CATEGORY", "name of the target category") {|v| self.issue_attributes['category'] = v} - opts.on( "--priority PRIORITY", "name of the target priority") {|v| self.issue_attributes['priority'] = v} - opts.on("-o", "--allow-override ATTRS", "allow email content to override attributes", - "specified by previous options", - "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v} - opts.separator("") - opts.separator("Examples:") - opts.separator("No project specified. Emails MUST contain the 'Project' keyword:") - opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret") - opts.separator("") - opts.separator("Fixed project and default tracker specified, but emails can override") - opts.separator("both tracker and priority attributes using keywords:") - opts.separator(" rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\") - opts.separator(" --project foo \\") - opts.separator(" --tracker bug \\") - opts.separator(" --allow-override tracker,priority") - - opts.summary_width = 27 - end - optparse.parse! - - unless url && key - puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help." - exit 1 - end - end - - def submit(email) - uri = url.gsub(%r{/*$}, '') + '/mail_handler' - - headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" } - - data = { 'key' => key, 'email' => email, - 'allow_override' => allow_override, - 'unknown_user' => unknown_user, - 'no_permission_check' => no_permission_check} - issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value } - - debug "Posting to #{uri}..." - response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate) - debug "Response received: #{response.code}" - - case response.code.to_i - when 403 - warn "Request was denied by your Redmine server. " + - "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key." - return 77 - when 422 - warn "Request was denied by your Redmine server. " + - "Possible reasons: email is sent from an invalid email address or is missing some information." - return 77 - when 400..499 - warn "Request was denied by your Redmine server (#{response.code})." - return 77 - when 500..599 - warn "Failed to contact your Redmine server (#{response.code})." - return 75 - when 201 - debug "Proccessed successfully" - return 0 - else - return 1 - end - end - - private - - def debug(msg) - puts msg if verbose - end - - def read_key_from_file(filename) - begin - self.key = File.read(filename).strip - rescue Exception => e - $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}" - exit 1 - end - end -end - -handler = RedmineMailHandler.new -exit(handler.submit(STDIN.read)) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/19/19c2933293d5536cb86e9ab59b2a501d3507d1a0.svn-base --- a/.svn/pristine/19/19c2933293d5536cb86e9ab59b2a501d3507d1a0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingWikiTest < ActionController::IntegrationTest - def test_wiki_matching - assert_routing( - { :method => 'get', :path => "/projects/567/wiki" }, - { :controller => 'wiki', :action => 'show', :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/lalala" }, - { :controller => 'wiki', :action => 'show', :project_id => '567', - :id => 'lalala' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/lalala.pdf" }, - { :controller => 'wiki', :action => 'show', :project_id => '567', - :id => 'lalala', :format => 'pdf' } - ) - assert_routing( - { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff" }, - { :controller => 'wiki', :action => 'diff', :project_id => '1', - :id => 'CookBook_documentation' } - ) - assert_routing( - { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2" }, - { :controller => 'wiki', :action => 'show', :project_id => '1', - :id => 'CookBook_documentation', :version => '2' } - ) - assert_routing( - { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/diff" }, - { :controller => 'wiki', :action => 'diff', :project_id => '1', - :id => 'CookBook_documentation', :version => '2' } - ) - assert_routing( - { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/annotate" }, - { :controller => 'wiki', :action => 'annotate', :project_id => '1', - :id => 'CookBook_documentation', :version => '2' } - ) - end - - def test_wiki_misc - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/date_index" }, - { :controller => 'wiki', :action => 'date_index', :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/export" }, - { :controller => 'wiki', :action => 'export', :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/export.pdf" }, - { :controller => 'wiki', :action => 'export', :project_id => '567', :format => 'pdf' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/index" }, - { :controller => 'wiki', :action => 'index', :project_id => '567' } - ) - end - - def test_wiki_resources - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/my_page/edit" }, - { :controller => 'wiki', :action => 'edit', :project_id => '567', - :id => 'my_page' } - ) - assert_routing( - { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/history" }, - { :controller => 'wiki', :action => 'history', :project_id => '1', - :id => 'CookBook_documentation' } - ) - assert_routing( - { :method => 'get', :path => "/projects/22/wiki/ladida/rename" }, - { :controller => 'wiki', :action => 'rename', :project_id => '22', - :id => 'ladida' } - ) - ["post", "put"].each do |method| - assert_routing( - { :method => method, :path => "/projects/567/wiki/CookBook_documentation/preview" }, - { :controller => 'wiki', :action => 'preview', :project_id => '567', - :id => 'CookBook_documentation' } - ) - end - assert_routing( - { :method => 'post', :path => "/projects/22/wiki/ladida/rename" }, - { :controller => 'wiki', :action => 'rename', :project_id => '22', - :id => 'ladida' } - ) - assert_routing( - { :method => 'post', :path => "/projects/22/wiki/ladida/protect" }, - { :controller => 'wiki', :action => 'protect', :project_id => '22', - :id => 'ladida' } - ) - assert_routing( - { :method => 'post', :path => "/projects/22/wiki/ladida/add_attachment" }, - { :controller => 'wiki', :action => 'add_attachment', :project_id => '22', - :id => 'ladida' } - ) - assert_routing( - { :method => 'put', :path => "/projects/567/wiki/my_page" }, - { :controller => 'wiki', :action => 'update', :project_id => '567', - :id => 'my_page' } - ) - assert_routing( - { :method => 'delete', :path => "/projects/22/wiki/ladida" }, - { :controller => 'wiki', :action => 'destroy', :project_id => '22', - :id => 'ladida' } - ) - assert_routing( - { :method => 'delete', :path => "/projects/22/wiki/ladida/3" }, - { :controller => 'wiki', :action => 'destroy_version', :project_id => '22', - :id => 'ladida', :version => '3' } - ) - end - - def test_api - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/my_page.xml" }, - { :controller => 'wiki', :action => 'show', :project_id => '567', - :id => 'my_page', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/my_page.json" }, - { :controller => 'wiki', :action => 'show', :project_id => '567', - :id => 'my_page', :format => 'json' } - ) - assert_routing( - { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2.xml" }, - { :controller => 'wiki', :action => 'show', :project_id => '1', - :id => 'CookBook_documentation', :version => '2', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2.json" }, - { :controller => 'wiki', :action => 'show', :project_id => '1', - :id => 'CookBook_documentation', :version => '2', :format => 'json' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/index.xml" }, - { :controller => 'wiki', :action => 'index', :project_id => '567', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/wiki/index.json" }, - { :controller => 'wiki', :action => 'index', :project_id => '567', :format => 'json' } - ) - assert_routing( - { :method => 'put', :path => "/projects/567/wiki/my_page.xml" }, - { :controller => 'wiki', :action => 'update', :project_id => '567', - :id => 'my_page', :format => 'xml' } - ) - assert_routing( - { :method => 'put', :path => "/projects/567/wiki/my_page.json" }, - { :controller => 'wiki', :action => 'update', :project_id => '567', - :id => 'my_page', :format => 'json' } - ) - assert_routing( - { :method => 'delete', :path => "/projects/567/wiki/my_page.xml" }, - { :controller => 'wiki', :action => 'destroy', :project_id => '567', - :id => 'my_page', :format => 'xml' } - ) - assert_routing( - { :method => 'delete', :path => "/projects/567/wiki/my_page.json" }, - { :controller => 'wiki', :action => 'destroy', :project_id => '567', - :id => 'my_page', :format => 'json' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1a021d35a6845d33a0f5158ab203eef12d7b8200.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1a/1a021d35a6845d33a0f5158ab203eef12d7b8200.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,67 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../../test_helper', __FILE__) + +class Redmine::Views::Builders::XmlTest < ActiveSupport::TestCase + + def test_hash + assert_xml_output('Ryan32') do |b| + b.person do + b.name 'Ryan' + b.age 32 + end + end + end + + def test_array + assert_xml_output('') do |b| + b.array :books do |b| + b.book :title => 'Book 1' + b.book :title => 'Book 2' + end + end + end + + def test_array_with_content_tags + assert_xml_output('Book 1Book 2') do |b| + b.array :books do |b| + b.book 'Book 1', :author => 'B. Smith' + b.book 'Book 2', :author => 'G. Cooper' + end + end + end + + def test_nested_arrays + assert_xml_output('B. SmithG. Cooper') do |b| + b.array :books do |books| + books.book do |book| + book.array :authors do |authors| + authors.author 'B. Smith' + authors.author 'G. Cooper' + end + end + end + end + end + + def assert_xml_output(expected, &block) + builder = Redmine::Views::Builders::Xml.new(ActionDispatch::TestRequest.new, ActionDispatch::TestResponse.new) + block.call(builder) + assert_equal('' + expected, builder.output) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1a0df8758a6961e9243b5416c8d2489913c4510a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1a/1a0df8758a6961e9243b5416c8d2489913c4510a.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,96 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class EnumerationsController < ApplicationController + layout 'admin' + + before_filter :require_admin, :except => :index + before_filter :require_admin_or_api_request, :only => :index + before_filter :build_new_enumeration, :only => [:new, :create] + before_filter :find_enumeration, :only => [:edit, :update, :destroy] + accept_api_auth :index + + helper :custom_fields + + def index + respond_to do |format| + format.html + format.api { + @klass = Enumeration.get_subclass(params[:type]) + if @klass + @enumerations = @klass.shared.sorted.all + else + render_404 + end + } + end + end + + def new + end + + def create + if request.post? && @enumeration.save + flash[:notice] = l(:notice_successful_create) + redirect_to enumerations_path + else + render :action => 'new' + end + end + + def edit + end + + def update + if request.put? && @enumeration.update_attributes(params[:enumeration]) + flash[:notice] = l(:notice_successful_update) + redirect_to enumerations_path + else + render :action => 'edit' + end + end + + def destroy + if !@enumeration.in_use? + # No associated objects + @enumeration.destroy + redirect_to enumerations_path + return + elsif params[:reassign_to_id].present? && (reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id].to_i)) + @enumeration.destroy(reassign_to) + redirect_to enumerations_path + return + end + @enumerations = @enumeration.class.system.all - [@enumeration] + end + + private + + def build_new_enumeration + class_name = params[:enumeration] && params[:enumeration][:type] || params[:type] + @enumeration = Enumeration.new_subclass_instance(class_name, params[:enumeration]) + if @enumeration.nil? + render_404 + end + end + + def find_enumeration + @enumeration = Enumeration.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1a39e16716c41d7a0a0fcc3821bef50938cf1bfa.svn-base --- a/.svn/pristine/1a/1a39e16716c41d7a0a0fcc3821bef50938cf1bfa.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class GroupCustomField < CustomField - def type_name - :label_group_plural - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1a8c919f646bcf8d04d61e354ffe1fb44ea0d6e4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1a/1a8c919f646bcf8d04d61e354ffe1fb44ea0d6e4.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,41 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module BoardsHelper + def board_breadcrumb(item) + board = item.is_a?(Message) ? item.board : item + links = [link_to(l(:label_board_plural), project_boards_path(item.project))] + boards = board.ancestors.reverse + if item.is_a?(Message) + boards << board + end + links += boards.map {|ancestor| link_to(h(ancestor.name), project_board_path(ancestor.project, ancestor))} + breadcrumb links + end + + def boards_options_for_select(boards) + options = [] + Board.board_tree(boards) do |board, level| + label = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe + label << board.name + options << [label, board.id] + end + options + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1a9c163fd859224f6bc9ce5b56705d82e3f87035.svn-base --- a/.svn/pristine/1a/1a9c163fd859224f6bc9ce5b56705d82e3f87035.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

    <%= link_to l(@enumeration.option_name), enumerations_path %> » <%=h @enumeration %>

    - -<%= labelled_form_for :enumeration, @enumeration, :url => enumeration_path(@enumeration), :html => {:method => :put} do |f| %> - <%= render :partial => 'form', :locals => {:f => f} %> - <%= submit_tag l(:button_save) %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1a9d1a49e8d55feb048c04fbca5352f495ad3bfd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1a/1a9d1a49e8d55feb048c04fbca5352f495ad3bfd.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,43 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::CustomFieldsTest < Redmine::ApiTest::Base + fixtures :users, :custom_fields + + def setup + Setting.rest_api_enabled = '1' + end + + test "GET /custom_fields.xml should return custom fields" do + get '/custom_fields.xml', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'custom_fields' do + assert_select 'custom_field' do + assert_select 'name', :text => 'Database' + assert_select 'id', :text => '2' + assert_select 'customized_type', :text => 'issue' + assert_select 'possible_values[type=array]' do + assert_select 'possible_value>value', :text => 'PostgreSQL' + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1a9dc03dd504c9d145f93f49fdb8e42950ff1c74.svn-base --- a/.svn/pristine/1a/1a9dc03dd504c9d145f93f49fdb8e42950ff1c74.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingIssueRelationsTest < ActionController::IntegrationTest - def test_issue_relations - assert_routing( - { :method => 'get', :path => "/issues/1/relations" }, - { :controller => 'issue_relations', :action => 'index', - :issue_id => '1' } - ) - assert_routing( - { :method => 'get', :path => "/issues/1/relations.xml" }, - { :controller => 'issue_relations', :action => 'index', - :issue_id => '1', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/issues/1/relations.json" }, - { :controller => 'issue_relations', :action => 'index', - :issue_id => '1', :format => 'json' } - ) - assert_routing( - { :method => 'post', :path => "/issues/1/relations" }, - { :controller => 'issue_relations', :action => 'create', - :issue_id => '1' } - ) - assert_routing( - { :method => 'post', :path => "/issues/1/relations.xml" }, - { :controller => 'issue_relations', :action => 'create', - :issue_id => '1', :format => 'xml' } - ) - assert_routing( - { :method => 'post', :path => "/issues/1/relations.json" }, - { :controller => 'issue_relations', :action => 'create', - :issue_id => '1', :format => 'json' } - ) - assert_routing( - { :method => 'get', :path => "/relations/23" }, - { :controller => 'issue_relations', :action => 'show', :id => '23' } - ) - assert_routing( - { :method => 'get', :path => "/relations/23.xml" }, - { :controller => 'issue_relations', :action => 'show', :id => '23', - :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/relations/23.json" }, - { :controller => 'issue_relations', :action => 'show', :id => '23', - :format => 'json' } - ) - assert_routing( - { :method => 'delete', :path => "/relations/23" }, - { :controller => 'issue_relations', :action => 'destroy', :id => '23' } - ) - assert_routing( - { :method => 'delete', :path => "/relations/23.xml" }, - { :controller => 'issue_relations', :action => 'destroy', :id => '23', - :format => 'xml' } - ) - assert_routing( - { :method => 'delete', :path => "/relations/23.json" }, - { :controller => 'issue_relations', :action => 'destroy', :id => '23', - :format => 'json' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1ab06441767b89700eccb7f62767523259a141ff.svn-base --- a/.svn/pristine/1a/1ab06441767b89700eccb7f62767523259a141ff.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -class Company < ActiveRecord::Base - attr_protected :rating - set_sequence_name :companies_nonstd_seq - - validates_presence_of :name - def validate - errors.add('rating', 'rating should not be 2') if rating == 2 - end -end \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1ad76dc367c4343626856549804aea889d0fbd2d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1a/1ad76dc367c4343626856549804aea889d0fbd2d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,62 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class DocumentTest < ActiveSupport::TestCase + fixtures :projects, :enumerations, :documents, :attachments, + :enabled_modules, + :users, :members, :member_roles, :roles, + :groups_users + + def test_create + doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation')) + assert doc.save + end + + def test_create_should_send_email_notification + ActionMailer::Base.deliveries.clear + + with_settings :notified_events => %w(document_added) do + doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation')) + assert doc.save + end + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_create_with_default_category + # Sets a default category + e = Enumeration.find_by_name('Technical documentation') + e.update_attributes(:is_default => true) + + doc = Document.new(:project => Project.find(1), :title => 'New document') + assert_equal e, doc.category + assert doc.save + end + + def test_updated_on_with_attachments + d = Document.find(1) + assert d.attachments.any? + assert_equal d.attachments.map(&:created_on).max, d.updated_on + end + + def test_updated_on_without_attachments + d = Document.find(2) + assert d.attachments.empty? + assert_equal d.created_on, d.updated_on + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1ae20ea87464b5769eed93f455558d2b0e19460e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1a/1ae20ea87464b5769eed93f455558d2b0e19460e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,24 @@ +class AddUniqueIndexOnCustomFieldsProjects < ActiveRecord::Migration + def up + table_name = "#{CustomField.table_name_prefix}custom_fields_projects#{CustomField.table_name_suffix}" + duplicates = CustomField.connection.select_rows("SELECT custom_field_id, project_id FROM #{table_name} GROUP BY custom_field_id, project_id HAVING COUNT(*) > 1") + duplicates.each do |custom_field_id, project_id| + # Removes duplicate rows + CustomField.connection.execute("DELETE FROM #{table_name} WHERE custom_field_id=#{custom_field_id} AND project_id=#{project_id}") + # And insert one + CustomField.connection.execute("INSERT INTO #{table_name} (custom_field_id, project_id) VALUES (#{custom_field_id}, #{project_id})") + end + + if index_exists? :custom_fields_projects, [:custom_field_id, :project_id] + remove_index :custom_fields_projects, [:custom_field_id, :project_id] + end + add_index :custom_fields_projects, [:custom_field_id, :project_id], :unique => true + end + + def down + if index_exists? :custom_fields_projects, [:custom_field_id, :project_id] + remove_index :custom_fields_projects, [:custom_field_id, :project_id] + end + add_index :custom_fields_projects, [:custom_field_id, :project_id] + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1ae8ac309393cfb207f1a6763cafea128ad11893.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1a/1ae8ac309393cfb207f1a6763cafea128ad11893.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1107 @@ +# Indonesian translations +# by Raden Prabowo (cakbowo@gmail.com) + +id: + direction: ltr + date: + formats: + default: "%d-%m-%Y" + short: "%d %b" + long: "%d %B %Y" + + day_names: [Minggu, Senin, Selasa, Rabu, Kamis, Jumat, Sabtu] + abbr_day_names: [Ming, Sen, Sel, Rab, Kam, Jum, Sab] + + month_names: [~, Januari, Februari, Maret, April, Mei, Juni, Juli, Agustus, September, Oktober, November, Desember] + abbr_month_names: [~, Jan, Feb, Mar, Apr, Mei, Jun, Jul, Agu, Sep, Okt, Nov, Des] + order: + - :day + - :month + - :year + + time: + formats: + default: "%a %d %b %Y, %H:%M:%S" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%d %B %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "setengah menit" + less_than_x_seconds: + one: "kurang dari sedetik" + other: "kurang dari %{count} detik" + x_seconds: + one: "sedetik" + other: "%{count} detik" + less_than_x_minutes: + one: "kurang dari semenit" + other: "kurang dari %{count} menit" + x_minutes: + one: "semenit" + other: "%{count} menit" + about_x_hours: + one: "sekitar sejam" + other: "sekitar %{count} jam" + x_hours: + one: "1 jam" + other: "%{count} jam" + x_days: + one: "sehari" + other: "%{count} hari" + about_x_months: + one: "sekitar sebulan" + other: "sekitar %{count} bulan" + x_months: + one: "sebulan" + other: "%{count} bulan" + about_x_years: + one: "sekitar setahun" + other: "sekitar %{count} tahun" + over_x_years: + one: "lebih dari setahun" + other: "lebih dari %{count} tahun" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + + number: + format: + precision: 3 + separator: ',' + delimiter: '.' + currency: + format: + unit: 'Rp' + precision: 2 + format: '%n %u' + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + support: + array: + sentence_connector: "dan" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "tidak termasuk dalam daftar" + exclusion: "sudah dicadangkan" + invalid: "salah" + confirmation: "tidak sesuai konfirmasi" + accepted: "harus disetujui" + empty: "tidak boleh kosong" + blank: "tidak boleh kosong" + too_long: "terlalu panjang (maksimum %{count} karakter)" + too_short: "terlalu pendek (minimum %{count} karakter)" + wrong_length: "panjangnya salah (seharusnya %{count} karakter)" + taken: "sudah diambil" + not_a_number: "bukan angka" + not_a_date: "bukan tanggal" + greater_than: "harus lebih besar dari %{count}" + greater_than_or_equal_to: "harus lebih besar atau sama dengan %{count}" + equal_to: "harus sama dengan %{count}" + less_than: "harus kurang dari %{count}" + less_than_or_equal_to: "harus kurang atau sama dengan %{count}" + odd: "harus ganjil" + even: "harus genap" + greater_than_start_date: "harus lebih besar dari tanggal mulai" + not_same_project: "tidak tergabung dalam proyek yang sama" + circular_dependency: "kaitan ini akan menghasilkan circular dependency" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Silakan pilih + + general_text_No: 'Tidak' + general_text_Yes: 'Ya' + general_text_no: 'tidak' + general_text_yes: 'ya' + general_lang_name: 'Indonesia' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_updated: Akun sudah berhasil diperbarui. + notice_account_invalid_creditentials: Pengguna atau kata sandi salah + notice_account_password_updated: Kata sandi sudah berhasil diperbarui. + notice_account_wrong_password: Kata sandi salah. + notice_account_register_done: Akun sudah berhasil dibuat. Untuk mengaktifkan akun anda, silakan klik tautan (link) yang dikirim kepada anda melalui e-mail. + notice_account_unknown_email: Pengguna tidak dikenal. + notice_can_t_change_password: Akun ini menggunakan sumber otentikasi eksternal yang tidak dikenal. Kata sandi tidak bisa diubah. + notice_account_lost_email_sent: Email berisi instruksi untuk memilih kata sandi baru sudah dikirimkan kepada anda. + notice_account_activated: Akun anda sudah diaktifasi. Sekarang anda bisa login. + notice_successful_create: Berhasil dibuat. + notice_successful_update: Berhasil diperbarui. + notice_successful_delete: Berhasil dihapus. + notice_successful_connection: Berhasil terhubung. + notice_file_not_found: Berkas yang anda buka tidak ada atau sudah dihapus. + notice_locking_conflict: Data sudah diubah oleh pengguna lain. + notice_not_authorized: Anda tidak memiliki akses ke halaman ini. + notice_email_sent: "Email sudah dikirim ke %{value}" + notice_email_error: "Terjadi kesalahan pada saat pengiriman email (%{value})" + notice_feeds_access_key_reseted: Atom access key anda sudah direset. + notice_failed_to_save_issues: "Gagal menyimpan %{count} masalah dari %{total} yang dipilih: %{ids}." + notice_no_issue_selected: "Tidak ada masalah yang dipilih! Silakan pilih masalah yang akan anda sunting." + notice_account_pending: "Akun anda sudah dibuat dan sekarang sedang menunggu persetujuan administrator." + notice_default_data_loaded: Konfigurasi default sudah berhasil dimuat. + notice_unable_delete_version: Tidak bisa menghapus versi. + + error_can_t_load_default_data: "Konfigurasi default tidak bisa dimuat: %{value}" + error_scm_not_found: "Entri atau revisi tidak terdapat pada repositori." + error_scm_command_failed: "Terjadi kesalahan pada saat mengakses repositori: %{value}" + error_scm_annotate: "Entri tidak ada, atau tidak dapat di anotasi." + error_issue_not_found_in_project: 'Masalah tidak ada atau tidak tergabung dalam proyek ini.' + error_no_tracker_in_project: 'Tidak ada pelacak yang diasosiasikan pada proyek ini. Silakan pilih Pengaturan Proyek.' + error_no_default_issue_status: 'Nilai default untuk Status masalah belum didefinisikan. Periksa kembali konfigurasi anda (Pilih "Administrasi --> Status masalah").' + error_can_not_reopen_issue_on_closed_version: 'Masalah yang ditujukan pada versi tertutup tidak bisa dibuka kembali' + error_can_not_archive_project: Proyek ini tidak bisa diarsipkan + + warning_attachments_not_saved: "%{count} berkas tidak bisa disimpan." + + mail_subject_lost_password: "Kata sandi %{value} anda" + mail_body_lost_password: 'Untuk mengubah kata sandi anda, klik tautan berikut::' + mail_subject_register: "Aktivasi akun %{value} anda" + mail_body_register: 'Untuk mengaktifkan akun anda, klik tautan berikut:' + mail_body_account_information_external: "Anda dapat menggunakan akun %{value} anda untuk login." + mail_body_account_information: Informasi akun anda + mail_subject_account_activation_request: "Permintaan aktivasi akun %{value} " + mail_body_account_activation_request: "Pengguna baru (%{value}) sudan didaftarkan. Akun tersebut menunggu persetujuan anda:" + mail_subject_reminder: "%{count} masalah harus selesai pada hari berikutnya (%{days})" + mail_body_reminder: "%{count} masalah yang ditugaskan pada anda harus selesai dalam %{days} hari kedepan:" + mail_subject_wiki_content_added: "'%{id}' halaman wiki sudah ditambahkan" + mail_body_wiki_content_added: "The '%{id}' halaman wiki sudah ditambahkan oleh %{author}." + mail_subject_wiki_content_updated: "'%{id}' halaman wiki sudah diperbarui" + mail_body_wiki_content_updated: "The '%{id}' halaman wiki sudah diperbarui oleh %{author}." + + + field_name: Nama + field_description: Deskripsi + field_summary: Ringkasan + field_is_required: Dibutuhkan + field_firstname: Nama depan + field_lastname: Nama belakang + field_mail: Email + field_filename: Berkas + field_filesize: Ukuran + field_downloads: Unduhan + field_author: Pengarang + field_created_on: Dibuat + field_updated_on: Diperbarui + field_field_format: Format + field_is_for_all: Untuk semua proyek + field_possible_values: Nilai yang mungkin + field_regexp: Regular expression + field_min_length: Panjang minimum + field_max_length: Panjang maksimum + field_value: Nilai + field_category: Kategori + field_title: Judul + field_project: Proyek + field_issue: Masalah + field_status: Status + field_notes: Catatan + field_is_closed: Masalah ditutup + field_is_default: Nilai default + field_tracker: Pelacak + field_subject: Perihal + field_due_date: Harus selesai + field_assigned_to: Ditugaskan ke + field_priority: Prioritas + field_fixed_version: Versi target + field_user: Pengguna + field_role: Peran + field_homepage: Halaman web + field_is_public: Publik + field_parent: Subproyek dari + field_is_in_roadmap: Masalah ditampilkan di rencana kerja + field_login: Login + field_mail_notification: Notifikasi email + field_admin: Administrator + field_last_login_on: Terakhir login + field_language: Bahasa + field_effective_date: Tanggal + field_password: Kata sandi + field_new_password: Kata sandi baru + field_password_confirmation: Konfirmasi + field_version: Versi + field_type: Tipe + field_host: Host + field_port: Port + field_account: Akun + field_base_dn: Base DN + field_attr_login: Atribut login + field_attr_firstname: Atribut nama depan + field_attr_lastname: Atribut nama belakang + field_attr_mail: Atribut email + field_onthefly: Pembuatan pengguna seketika + field_start_date: Mulai + field_done_ratio: "% Selesai" + field_auth_source: Mode otentikasi + field_hide_mail: Sembunyikan email saya + field_comments: Komentar + field_url: URL + field_start_page: Halaman awal + field_subproject: Subproyek + field_hours: Jam + field_activity: Kegiatan + field_spent_on: Tanggal + field_identifier: Pengenal + field_is_filter: Digunakan sebagai penyaring + field_issue_to: Masalah terkait + field_delay: Tertunday + field_assignable: Masalah dapat ditugaskan pada peran ini + field_redirect_existing_links: Alihkan tautan yang ada + field_estimated_hours: Perkiraan waktu + field_column_names: Kolom + field_time_zone: Zona waktu + field_searchable: Dapat dicari + field_default_value: Nilai default + field_comments_sorting: Tampilkan komentar + field_parent_title: Halaman induk + field_editable: Dapat disunting + field_watcher: Pemantau + field_identity_url: OpenID URL + field_content: Isi + field_group_by: Dikelompokkan berdasar + field_sharing: Berbagi + + setting_app_title: Judul aplikasi + setting_app_subtitle: Subjudul aplikasi + setting_welcome_text: Teks sambutan + setting_default_language: Bahasa Default + setting_login_required: Butuhkan otentikasi + setting_self_registration: Swa-pendaftaran + setting_attachment_max_size: Ukuran maksimum untuk lampiran + setting_issues_export_limit: Batasan ukuran export masalah + setting_mail_from: Emisi alamat email + setting_bcc_recipients: Blind carbon copy recipients (bcc) + setting_plain_text_mail: Plain text mail (no HTML) + setting_host_name: Nama host dan path + setting_text_formatting: Format teks + setting_wiki_compression: Kompresi untuk riwayat wiki + setting_feeds_limit: Batasan isi feed + setting_default_projects_public: Proyek baru defaultnya adalah publik + setting_autofetch_changesets: Autofetch commits + setting_sys_api_enabled: Aktifkan WS untuk pengaturan repositori + setting_commit_ref_keywords: Referensi kaca kunci + setting_commit_fix_keywords: Pembetulan kaca kunci + setting_autologin: Autologin + setting_date_format: Format tanggal + setting_time_format: Format waktu + setting_cross_project_issue_relations: Perbolehkan kaitan masalah proyek berbeda + setting_issue_list_default_columns: Kolom default ditampilkan di daftar masalah + setting_emails_footer: Footer untuk email + setting_protocol: Protokol + setting_per_page_options: Pilihan obyek per halaman + setting_user_format: Format tampilan untuk pengguna + setting_activity_days_default: Hari tertampil pada kegiatan proyek + setting_display_subprojects_issues: Secara default, tampilkan masalah subproyek pada proyek utama + setting_enabled_scm: Enabled SCM + setting_mail_handler_api_enabled: Enable WS for incoming emails + setting_mail_handler_api_key: API key + setting_sequential_project_identifiers: Buat pengenal proyek terurut + setting_gravatar_enabled: Gunakan icon pengguna dari Gravatar + setting_gravatar_default: Gambar default untuk Gravatar + setting_diff_max_lines_displayed: Maksimum perbedaan baris tertampil + setting_file_max_size_displayed: Maksimum berkas tertampil secara inline + setting_repository_log_display_limit: Nilai maksimum dari revisi ditampilkan di log berkas + setting_openid: Perbolehkan Login dan pendaftaran melalui OpenID + setting_password_min_length: Panjang minimum untuk kata sandi + setting_new_project_user_role_id: Peran diberikan pada pengguna non-admin yang membuat proyek + setting_default_projects_modules: Modul yang diaktifkan pada proyek baru + + permission_add_project: Tambahkan proyek + permission_edit_project: Sunting proyek + permission_select_project_modules: Pilih modul proyek + permission_manage_members: Atur anggota + permission_manage_versions: Atur versi + permission_manage_categories: Atur kategori masalah + permission_add_issues: Tambahkan masalah + permission_edit_issues: Sunting masalah + permission_manage_issue_relations: Atur kaitan masalah + permission_add_issue_notes: Tambahkan catatan + permission_edit_issue_notes: Sunting catatan + permission_edit_own_issue_notes: Sunting catatan saya + permission_move_issues: Pindahkan masalah + permission_delete_issues: Hapus masalah + permission_manage_public_queries: Atur query publik + permission_save_queries: Simpan query + permission_view_gantt: Tampilkan gantt chart + permission_view_calendar: Tampilkan kalender + permission_view_issue_watchers: Tampilkan daftar pemantau + permission_add_issue_watchers: Tambahkan pemantau + permission_delete_issue_watchers: Hapus pemantau + permission_log_time: Log waktu terpakai + permission_view_time_entries: Tampilkan waktu terpakai + permission_edit_time_entries: Sunting catatan waktu + permission_edit_own_time_entries: Sunting catatan waktu saya + permission_manage_news: Atur berita + permission_comment_news: Komentari berita + permission_view_documents: Tampilkan dokumen + permission_manage_files: Atur berkas + permission_view_files: Tampilkan berkas + permission_manage_wiki: Atur wiki + permission_rename_wiki_pages: Ganti nama halaman wiki + permission_delete_wiki_pages: Hapus halaman wiki + permission_view_wiki_pages: Tampilkan wiki + permission_view_wiki_edits: Tampilkan riwayat wiki + permission_edit_wiki_pages: Sunting halaman wiki + permission_delete_wiki_pages_attachments: Hapus lampiran + permission_protect_wiki_pages: Proteksi halaman wiki + permission_manage_repository: Atur repositori + permission_browse_repository: Jelajah repositori + permission_view_changesets: Tampilkan set perubahan + permission_commit_access: Commit akses + permission_manage_boards: Atur forum + permission_view_messages: Tampilkan pesan + permission_add_messages: Tambahkan pesan + permission_edit_messages: Sunting pesan + permission_edit_own_messages: Sunting pesan saya + permission_delete_messages: Hapus pesan + permission_delete_own_messages: Hapus pesan saya + + project_module_issue_tracking: Pelacak masalah + project_module_time_tracking: Pelacak waktu + project_module_news: Berita + project_module_documents: Dokumen + project_module_files: Berkas + project_module_wiki: Wiki + project_module_repository: Repositori + project_module_boards: Forum + + label_user: Pengguna + label_user_plural: Pengguna + label_user_new: Pengguna baru + label_user_anonymous: Anonymous + label_project: Proyek + label_project_new: Proyek baru + label_project_plural: Proyek + label_x_projects: + zero: tidak ada proyek + one: 1 proyek + other: "%{count} proyek" + label_project_all: Semua Proyek + label_project_latest: Proyek terakhir + label_issue: Masalah + label_issue_new: Masalah baru + label_issue_plural: Masalah + label_issue_view_all: tampilkan semua masalah + label_issues_by: "Masalah ditambahkan oleh %{value}" + label_issue_added: Masalah ditambahan + label_issue_updated: Masalah diperbarui + label_document: Dokumen + label_document_new: Dokumen baru + label_document_plural: Dokumen + label_document_added: Dokumen ditambahkan + label_role: Peran + label_role_plural: Peran + label_role_new: Peran baru + label_role_and_permissions: Peran dan perijinan + label_member: Anggota + label_member_new: Anggota baru + label_member_plural: Anggota + label_tracker: Pelacak + label_tracker_plural: Pelacak + label_tracker_new: Pelacak baru + label_workflow: Alur kerja + label_issue_status: Status masalah + label_issue_status_plural: Status masalah + label_issue_status_new: Status baru + label_issue_category: Kategori masalah + label_issue_category_plural: Kategori masalah + label_issue_category_new: Kategori baru + label_custom_field: Field kustom + label_custom_field_plural: Field kustom + label_custom_field_new: Field kustom + label_enumerations: Enumerasi + label_enumeration_new: Buat baru + label_information: Informasi + label_information_plural: Informasi + label_please_login: Silakan login + label_register: mendaftar + label_login_with_open_id_option: atau login menggunakan OpenID + label_password_lost: Lupa password + label_home: Halaman depan + label_my_page: Beranda + label_my_account: Akun saya + label_my_projects: Proyek saya + label_administration: Administrasi + label_login: Login + label_logout: Keluar + label_help: Bantuan + label_reported_issues: Masalah terlapor + label_assigned_to_me_issues: Masalah yang ditugaskan pada saya + label_last_login: Terakhir login + label_registered_on: Terdaftar pada + label_activity: Kegiatan + label_overall_activity: Kegiatan umum + label_user_activity: "kegiatan %{value}" + label_new: Baru + label_logged_as: Login sebagai + label_environment: Lingkungan + label_authentication: Otentikasi + label_auth_source: Mode Otentikasi + label_auth_source_new: Mode otentikasi baru + label_auth_source_plural: Mode Otentikasi + label_subproject_plural: Subproyek + label_and_its_subprojects: "%{value} dan subproyeknya" + label_min_max_length: Panjang Min - Maks + label_list: Daftar + label_date: Tanggal + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: Text + label_text: Long text + label_attribute: Atribut + label_attribute_plural: Atribut + label_no_data: Tidak ada data untuk ditampilkan + label_change_status: Status perubahan + label_history: Riwayat + label_attachment: Berkas + label_attachment_new: Berkas baru + label_attachment_delete: Hapus Berkas + label_attachment_plural: Berkas + label_file_added: Berkas ditambahkan + label_report: Laporan + label_report_plural: Laporan + label_news: Berita + label_news_new: Tambahkan berita + label_news_plural: Berita + label_news_latest: Berita terakhir + label_news_view_all: Tampilkan semua berita + label_news_added: Berita ditambahkan + label_settings: Pengaturan + label_overview: Umum + label_version: Versi + label_version_new: Versi baru + label_version_plural: Versi + label_confirmation: Konfirmasi + label_export_to: 'Juga tersedia dalam:' + label_read: Baca... + label_public_projects: Proyek publik + label_open_issues: belum selesai + label_open_issues_plural: belum selesai + label_closed_issues: selesai + label_closed_issues_plural: selesai + label_x_open_issues_abbr_on_total: + zero: 0 belum selesai / %{total} + one: 1 belum selesai / %{total} + other: "%{count} terbuka / %{total}" + label_x_open_issues_abbr: + zero: 0 belum selesai + one: 1 belum selesai + other: "%{count} belum selesai" + label_x_closed_issues_abbr: + zero: 0 selesai + one: 1 selesai + other: "%{count} selesai" + label_total: Total + label_permissions: Perijinan + label_current_status: Status sekarang + label_new_statuses_allowed: Status baru yang diijinkan + label_all: semua + label_none: tidak ada + label_nobody: tidak ada + label_next: Berikut + label_previous: Sebelum + label_used_by: Digunakan oleh + label_details: Rincian + label_add_note: Tambahkan catatan + label_per_page: Per halaman + label_calendar: Kalender + label_months_from: dari bulan + label_gantt: Gantt + label_internal: Internal + label_last_changes: "%{count} perubahan terakhir" + label_change_view_all: Tampilkan semua perubahan + label_personalize_page: Personalkan halaman ini + label_comment: Komentar + label_comment_plural: Komentar + label_x_comments: + zero: tak ada komentar + one: 1 komentar + other: "%{count} komentar" + label_comment_add: Tambahkan komentar + label_comment_added: Komentar ditambahkan + label_comment_delete: Hapus komentar + label_query: Custom query + label_query_plural: Custom queries + label_query_new: Query baru + label_filter_add: Tambahkan filter + label_filter_plural: Filter + label_equals: sama dengan + label_not_equals: tidak sama dengan + label_in_less_than: kurang dari + label_in_more_than: lebih dari + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: pada + label_today: hari ini + label_all_time: semua waktu + label_yesterday: kemarin + label_this_week: minggu ini + label_last_week: minggu lalu + label_last_n_days: "%{count} hari terakhir" + label_this_month: bulan ini + label_last_month: bulan lalu + label_this_year: this year + label_date_range: Jangkauan tanggal + label_less_than_ago: kurang dari hari yang lalu + label_more_than_ago: lebih dari hari yang lalu + label_ago: hari yang lalu + label_contains: berisi + label_not_contains: tidak berisi + label_day_plural: hari + label_repository: Repositori + label_repository_plural: Repositori + label_browse: Jelajah + label_branch: Cabang + label_tag: Tag + label_revision: Revisi + label_revision_plural: Revisi + label_associated_revisions: Revisi terkait + label_added: ditambahkan + label_modified: diubah + label_copied: disalin + label_renamed: diganti nama + label_deleted: dihapus + label_latest_revision: Revisi terakhir + label_latest_revision_plural: Revisi terakhir + label_view_revisions: Tampilkan revisi + label_view_all_revisions: Tampilkan semua revisi + label_max_size: Ukuran maksimum + label_sort_highest: Ke paling atas + label_sort_higher: Ke atas + label_sort_lower: Ke bawah + label_sort_lowest: Ke paling bawah + label_roadmap: Rencana kerja + label_roadmap_due_in: "Harus selesai dalam %{value}" + label_roadmap_overdue: "%{value} terlambat" + label_roadmap_no_issues: Tak ada masalah pada versi ini + label_search: Cari + label_result_plural: Hasil + label_all_words: Semua kata + label_wiki: Wiki + label_wiki_edit: Sunting wiki + label_wiki_edit_plural: Sunting wiki + label_wiki_page: Halaman wiki + label_wiki_page_plural: Halaman wiki + label_index_by_title: Indeks menurut judul + label_index_by_date: Indeks menurut tanggal + label_current_version: Versi sekarang + label_preview: Tinjauan + label_feed_plural: Feeds + label_changes_details: Rincian semua perubahan + label_issue_tracking: Pelacak masalah + label_spent_time: Waktu terpakai + label_f_hour: "%{value} jam" + label_f_hour_plural: "%{value} jam" + label_time_tracking: Pelacak waktu + label_change_plural: Perubahan + label_statistics: Statistik + label_commits_per_month: Komit per bulan + label_commits_per_author: Komit per pengarang + label_view_diff: Tampilkan perbedaan + label_diff_inline: inline + label_diff_side_by_side: berdampingan + label_options: Pilihan + label_copy_workflow_from: Salin alur kerja dari + label_permissions_report: Laporan perijinan + label_watched_issues: Masalah terpantau + label_related_issues: Masalah terkait + label_applied_status: Status teraplikasi + label_loading: Memuat... + label_relation_new: Kaitan baru + label_relation_delete: Hapus kaitan + label_relates_to: terkait pada + label_duplicates: salinan + label_duplicated_by: disalin oleh + label_blocks: blok + label_blocked_by: diblok oleh + label_precedes: mendahului + label_follows: mengikuti + label_end_to_start: akhir ke awal + label_end_to_end: akhir ke akhir + label_start_to_start: awal ke awal + label_start_to_end: awal ke akhir + label_stay_logged_in: Tetap login + label_disabled: tidak diaktifkan + label_show_completed_versions: Tampilkan versi lengkap + label_me: saya + label_board: Forum + label_board_new: Forum baru + label_board_plural: Forum + label_topic_plural: Topik + label_message_plural: Pesan + label_message_last: Pesan terakhir + label_message_new: Pesan baru + label_message_posted: Pesan ditambahkan + label_reply_plural: Balasan + label_send_information: Kirim informasi akun ke pengguna + label_year: Tahun + label_month: Bulan + label_week: Minggu + label_date_from: Dari + label_date_to: Sampai + label_language_based: Berdasarkan bahasa pengguna + label_sort_by: "Urut berdasarkan %{value}" + label_send_test_email: Kirim email percobaan + label_feeds_access_key_created_on: "Atom access key dibuat %{value} yang lalu" + label_module_plural: Modul + label_added_time_by: "Ditambahkan oleh %{author} %{age} yang lalu" + label_updated_time_by: "Diperbarui oleh %{author} %{age} yang lalu" + label_updated_time: "Diperbarui oleh %{value} yang lalu" + label_jump_to_a_project: Pilih proyek... + label_file_plural: Berkas + label_changeset_plural: Set perubahan + label_default_columns: Kolom default + label_no_change_option: (Tak ada perubahan) + label_bulk_edit_selected_issues: Ubah masalah terpilih secara masal + label_theme: Tema + label_default: Default + label_search_titles_only: Cari judul saja + label_user_mail_option_all: "Untuk semua kejadian pada semua proyek saya" + label_user_mail_option_selected: "Hanya untuk semua kejadian pada proyek yang saya pilih ..." + label_user_mail_no_self_notified: "Saya tak ingin diberitahu untuk perubahan yang saya buat sendiri" + label_user_mail_assigned_only_mail_notification: "Kirim email hanya bila saya ditugaskan untuk masalah terkait" + label_user_mail_block_mail_notification: "Saya tidak ingin menerima email. Terima kasih." + label_registration_activation_by_email: aktivasi akun melalui email + label_registration_manual_activation: aktivasi akun secara manual + label_registration_automatic_activation: aktivasi akun secara otomatis + label_display_per_page: "Per halaman: %{value}" + label_age: Umur + label_change_properties: Rincian perubahan + label_general: Umum + label_more: Lanjut + label_scm: SCM + label_plugins: Plugin + label_ldap_authentication: Otentikasi LDAP + label_downloads_abbr: Unduh + label_optional_description: Deskripsi optional + label_add_another_file: Tambahkan berkas lain + label_preferences: Preferensi + label_chronological_order: Urut sesuai kronologis + label_reverse_chronological_order: Urut dari yang terbaru + label_planning: Perencanaan + label_incoming_emails: Email masuk + label_generate_key: Buat kunci + label_issue_watchers: Pemantau + label_example: Contoh + label_display: Tampilan + label_sort: Urut + label_ascending: Menaik + label_descending: Menurun + label_date_from_to: Dari %{start} sampai %{end} + label_wiki_content_added: Halaman wiki ditambahkan + label_wiki_content_updated: Halaman wiki diperbarui + label_group: Kelompok + label_group_plural: Kelompok + label_group_new: Kelompok baru + label_time_entry_plural: Waktu terpakai + label_version_sharing_none: Tidak dibagi + label_version_sharing_descendants: Dengan subproyek + label_version_sharing_hierarchy: Dengan hirarki proyek + label_version_sharing_tree: Dengan pohon proyek + label_version_sharing_system: Dengan semua proyek + + + button_login: Login + button_submit: Kirim + button_save: Simpan + button_check_all: Contreng semua + button_uncheck_all: Hilangkan semua contreng + button_delete: Hapus + button_create: Buat + button_create_and_continue: Buat dan lanjutkan + button_test: Test + button_edit: Sunting + button_add: Tambahkan + button_change: Ubah + button_apply: Terapkan + button_clear: Bersihkan + button_lock: Kunci + button_unlock: Buka kunci + button_download: Unduh + button_list: Daftar + button_view: Tampilkan + button_move: Pindah + button_move_and_follow: Pindah dan ikuti + button_back: Kembali + button_cancel: Batal + button_activate: Aktifkan + button_sort: Urut + button_log_time: Rekam waktu + button_rollback: Kembali ke versi ini + button_watch: Pantau + button_unwatch: Tidak Memantau + button_reply: Balas + button_archive: Arsip + button_unarchive: Batalkan arsip + button_reset: Reset + button_rename: Ganti nama + button_change_password: Ubah kata sandi + button_copy: Salin + button_copy_and_follow: Salin dan ikuti + button_annotate: Anotasi + button_update: Perbarui + button_configure: Konfigur + button_quote: Kutip + button_duplicate: Duplikat + + status_active: aktif + status_registered: terdaftar + status_locked: terkunci + + version_status_open: terbuka + version_status_locked: terkunci + version_status_closed: tertutup + + field_active: Aktif + + text_select_mail_notifications: Pilih aksi dimana email notifikasi akan dikirimkan. + text_regexp_info: mis. ^[A-Z0-9]+$ + text_min_max_length_info: 0 berarti tidak ada pembatasan + text_project_destroy_confirmation: Apakah anda benar-benar akan menghapus proyek ini beserta data terkait ? + text_subprojects_destroy_warning: "Subproyek: %{value} juga akan dihapus." + text_workflow_edit: Pilih peran dan pelacak untuk menyunting alur kerja + text_are_you_sure: Anda yakin ? + text_journal_changed: "%{label} berubah dari %{old} menjadi %{new}" + text_journal_set_to: "%{label} di set ke %{value}" + text_journal_deleted: "%{label} dihapus (%{old})" + text_journal_added: "%{label} %{value} ditambahkan" + text_tip_issue_begin_day: tugas dimulai hari itu + text_tip_issue_end_day: tugas berakhir hari itu + text_tip_issue_begin_end_day: tugas dimulai dan berakhir hari itu + text_caracters_maximum: "maximum %{count} karakter." + text_caracters_minimum: "Setidaknya harus sepanjang %{count} karakter." + text_length_between: "Panjang diantara %{min} dan %{max} karakter." + text_tracker_no_workflow: Tidak ada alur kerja untuk pelacak ini + text_unallowed_characters: Karakter tidak diperbolehkan + text_comma_separated: Beberapa nilai diperbolehkan (dipisahkan koma). + text_issues_ref_in_commit_messages: Mereferensikan dan membetulkan masalah pada pesan komit + text_issue_added: "Masalah %{id} sudah dilaporkan oleh %{author}." + text_issue_updated: "Masalah %{id} sudah diperbarui oleh %{author}." + text_wiki_destroy_confirmation: Apakah anda benar-benar akan menghapus wiki ini beserta semua isinya ? + text_issue_category_destroy_question: "Beberapa masalah (%{count}) ditugaskan pada kategori ini. Apa yang anda lakukan ?" + text_issue_category_destroy_assignments: Hapus kategori penugasan + text_issue_category_reassign_to: Tugaskan kembali masalah untuk kategori ini + text_user_mail_option: "Untuk proyek yang tidak dipilih, anda hanya akan menerima notifikasi hal-hal yang anda pantau atau anda terlibat di dalamnya (misalnya masalah yang anda tulis atau ditugaskan pada anda)." + text_no_configuration_data: "Peran, pelacak, status masalah dan alur kerja belum dikonfigur.\nSangat disarankan untuk memuat konfigurasi default. Anda akan bisa mengubahnya setelah konfigurasi dimuat." + text_load_default_configuration: Muat konfigurasi default + text_status_changed_by_changeset: "Diterapkan di set perubahan %{value}." + text_issues_destroy_confirmation: 'Apakah anda yakin untuk menghapus masalah terpilih ?' + text_select_project_modules: 'Pilih modul untuk diaktifkan pada proyek ini:' + text_default_administrator_account_changed: Akun administrator default sudah berubah + text_file_repository_writable: Direktori yang bisa ditulisi untuk lampiran + text_plugin_assets_writable: Direktori yang bisa ditulisi untuk plugin asset + text_rmagick_available: RMagick tersedia (optional) + text_destroy_time_entries_question: "%{hours} jam sudah dilaporkan pada masalah yang akan anda hapus. Apa yang akan anda lakukan ?" + text_destroy_time_entries: Hapus jam yang terlapor + text_assign_time_entries_to_project: Tugaskan jam terlapor pada proyek + text_reassign_time_entries: 'Tugaskan kembali jam terlapor pada masalah ini:' + text_user_wrote: "%{value} menulis:" + text_enumeration_destroy_question: "%{count} obyek ditugaskan untuk nilai ini." + text_enumeration_category_reassign_to: 'Tugaskan kembali untuk nilai ini:' + text_email_delivery_not_configured: "Pengiriman email belum dikonfigurasi, notifikasi tidak diaktifkan.\nAnda harus mengkonfigur SMTP server anda pada config/configuration.yml dan restart kembali aplikasi untuk mengaktifkan." + text_repository_usernames_mapping: "Pilih atau perbarui pengguna Redmine yang terpetakan ke setiap nama pengguna yang ditemukan di log repositori.\nPengguna dengan nama pengguna dan repositori atau email yang sama secara otomasit akan dipetakan." + text_diff_truncated: '... Perbedaan terpotong karena melebihi batas maksimum yang bisa ditampilkan.' + text_custom_field_possible_values_info: 'Satu baris untuk setiap nilai' + text_wiki_page_destroy_question: "Halaman ini mempunyai %{descendants} halaman anak dan turunannya. Apa yang akan anda lakukan ?" + text_wiki_page_nullify_children: "Biarkan halaman anak sebagai halaman teratas (root)" + text_wiki_page_destroy_children: "Hapus halaman anak dan semua turunannya" + text_wiki_page_reassign_children: "Tujukan halaman anak ke halaman induk yang ini" + + default_role_manager: Manager + default_role_developer: Pengembang + default_role_reporter: Pelapor + default_tracker_bug: Bug + default_tracker_feature: Fitur + default_tracker_support: Dukungan + default_issue_status_new: Baru + default_issue_status_in_progress: Dalam proses + default_issue_status_resolved: Resolved + default_issue_status_feedback: Umpan balik + default_issue_status_closed: Ditutup + default_issue_status_rejected: Ditolak + default_doc_category_user: Dokumentasi pengguna + default_doc_category_tech: Dokumentasi teknis + default_priority_low: Rendah + default_priority_normal: Normal + default_priority_high: Tinggi + default_priority_urgent: Penting + default_priority_immediate: Segera + default_activity_design: Rancangan + default_activity_development: Pengembangan + + enumeration_issue_priorities: Prioritas masalah + enumeration_doc_categories: Kategori dokumen + enumeration_activities: Kegiatan + enumeration_system_activity: Kegiatan Sistem + label_copy_source: Source + label_update_issue_done_ratios: Update issue done ratios + setting_issue_done_ratio: Calculate the issue done ratio with + label_api_access_key: API access key + text_line_separated: Multiple values allowed (one line for each value). + label_revision_id: Revision %{value} + permission_view_issues: View Issues + setting_issue_done_ratio_issue_status: Use the issue status + error_issue_done_ratios_not_updated: Issue done ratios not updated. + label_display_used_statuses_only: Only display statuses that are used by this tracker + error_workflow_copy_target: Please select target tracker(s) and role(s) + label_api_access_key_created_on: API access key created %{value} ago + label_feeds_access_key: Atom access key + notice_api_access_key_reseted: Your API access key was reset. + setting_rest_api_enabled: Enable REST web service + label_copy_same_as_target: Same as target + button_show: Show + setting_issue_done_ratio_issue_field: Use the issue field + label_missing_api_access_key: Missing an API access key + label_copy_target: Target + label_missing_feeds_access_key: Missing a Atom access key + notice_issue_done_ratios_updated: Issue done ratios updated. + error_workflow_copy_source: Please select a source tracker or role + setting_start_of_week: Start calendars on + setting_mail_handler_body_delimiters: Truncate emails after one of these lines + permission_add_subprojects: Create subprojects + label_subproject_new: New subproject + text_own_membership_delete_confirmation: |- + You are about to remove some or all of your permissions and may no longer be able to edit this project after that. + Are you sure you want to continue? + label_close_versions: Close completed versions + label_board_sticky: Sticky + label_board_locked: Locked + permission_export_wiki_pages: Export wiki pages + setting_cache_formatted_text: Cache formatted text + permission_manage_project_activities: Manage project activities + error_unable_delete_issue_status: Unable to delete issue status + label_profile: Profile + permission_manage_subtasks: Manage subtasks + field_parent_issue: Parent task + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + error_can_not_delete_custom_field: Unable to delete custom field + error_unable_to_connect: Unable to connect (%{value}) + error_can_not_remove_role: This role is in use and can not be deleted. + error_can_not_delete_tracker: This tracker contains issues and can't be deleted. + field_principal: Principal + label_my_page_block: My page block + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + text_zoom_out: Zoom out + text_zoom_in: Zoom in + notice_unable_delete_time_entry: Unable to delete time log entry. + label_overall_spent_time: Overall spent time + field_time_entries: Log time + project_module_gantt: Gantt + project_module_calendar: Calendar + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Commit messages encoding + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 masalah + one: 1 masalah + other: "%{count} masalah" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: semua + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Dengan subproyek + label_cross_project_tree: Dengan pohon proyek + label_cross_project_hierarchy: Dengan hirarki proyek + label_cross_project_system: Dengan semua proyek + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1a/1afc6535efb2815b924fdefa8ab10dacd7ba3c18.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1a/1afc6535efb2815b924fdefa8ab10dacd7ba3c18.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddQueriesOptions < ActiveRecord::Migration + def up + add_column :queries, :options, :text + end + + def down + remove_column :queries, :options + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1b/1b1f0da73986061f36bbd7cd1895fc7ac1182885.svn-base --- a/.svn/pristine/1b/1b1f0da73986061f36bbd7cd1895fc7ac1182885.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class MessagesController < ApplicationController - menu_item :boards - default_search_scope :messages - before_filter :find_board, :only => [:new, :preview] - before_filter :find_message, :except => [:new, :preview] - before_filter :authorize, :except => [:preview, :edit, :destroy] - - helper :boards - helper :watchers - helper :attachments - include AttachmentsHelper - - REPLIES_PER_PAGE = 25 unless const_defined?(:REPLIES_PER_PAGE) - - # Show a topic and its replies - def show - page = params[:page] - # Find the page of the requested reply - if params[:r] && page.nil? - offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i]) - page = 1 + offset / REPLIES_PER_PAGE - end - - @reply_count = @topic.children.count - @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page - @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}], - :order => "#{Message.table_name}.created_on ASC", - :limit => @reply_pages.items_per_page, - :offset => @reply_pages.current.offset) - - @reply = Message.new(:subject => "RE: #{@message.subject}") - render :action => "show", :layout => false if request.xhr? - end - - # Create a new topic - def new - @message = Message.new - @message.author = User.current - @message.board = @board - @message.safe_attributes = params[:message] - if request.post? - @message.save_attachments(params[:attachments]) - if @message.save - call_hook(:controller_messages_new_after_save, { :params => params, :message => @message}) - render_attachment_warning_if_needed(@message) - redirect_to board_message_path(@board, @message) - end - end - end - - # Reply to a topic - def reply - @reply = Message.new - @reply.author = User.current - @reply.board = @board - @reply.safe_attributes = params[:reply] - @topic.children << @reply - if !@reply.new_record? - call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply}) - attachments = Attachment.attach_files(@reply, params[:attachments]) - render_attachment_warning_if_needed(@reply) - end - redirect_to board_message_path(@board, @topic, :r => @reply) - end - - # Edit a message - def edit - (render_403; return false) unless @message.editable_by?(User.current) - @message.safe_attributes = params[:message] - if request.post? && @message.save - attachments = Attachment.attach_files(@message, params[:attachments]) - render_attachment_warning_if_needed(@message) - flash[:notice] = l(:notice_successful_update) - @message.reload - redirect_to board_message_path(@message.board, @message.root, :r => (@message.parent_id && @message.id)) - end - end - - # Delete a messages - def destroy - (render_403; return false) unless @message.destroyable_by?(User.current) - r = @message.to_param - @message.destroy - if @message.parent - redirect_to board_message_path(@board, @message.parent, :r => r) - else - redirect_to project_board_path(@project, @board) - end - end - - def quote - @subject = @message.subject - @subject = "RE: #{@subject}" unless @subject.starts_with?('RE:') - - @content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> " - @content << @message.content.to_s.strip.gsub(%r{
    ((.|\s)*?)
    }m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" - end - - def preview - message = @board.messages.find_by_id(params[:id]) - @attachements = message.attachments if message - @text = (params[:message] || params[:reply])[:content] - @previewed = message - render :partial => 'common/preview' - end - -private - def find_message - return unless find_board - @message = @board.messages.find(params[:id], :include => :parent) - @topic = @message.root - rescue ActiveRecord::RecordNotFound - render_404 - end - - def find_board - @board = Board.find(params[:board_id], :include => :project) - @project = @board.project - rescue ActiveRecord::RecordNotFound - render_404 - nil - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1b/1b2837ef400afd5bc210e52e7203d86ba25be2b4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1b/1b2837ef400afd5bc210e52e7203d86ba25be2b4.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,12 @@ +<%= "(#{l(:field_private_notes)}) " if @journal.private_notes? -%><%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %> + +<% details_to_strings(@journal_details, true).each do |string| -%> +<%= string %> +<% end -%> + +<% if @journal.notes? -%> +<%= @journal.notes %> + +<% end -%> +---------------------------------------- +<%= render :partial => 'issue', :formats => [:text], :locals => { :issue => @issue, :users => @users, :issue_url => @issue_url } %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1b/1b29dadeac443afcb6fd27a4a9c92482c01b0a1c.svn-base --- a/.svn/pristine/1b/1b29dadeac443afcb6fd27a4a9c92482c01b0a1c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1084 +0,0 @@ -# Redmine EU language -# Author: Ales Zabala Alava (Shagi), -# 2010-01-25 -# Distributed under the same terms as the Redmine itself. -eu: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y/%m/%d" - short: "%b %d" - long: "%Y %B %d" - - day_names: [Igandea, Astelehena, Asteartea, Asteazkena, Osteguna, Ostirala, Larunbata] - abbr_day_names: [Ig., Al., Ar., Az., Og., Or., La.] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Urtarrila, Otsaila, Martxoa, Apirila, Maiatza, Ekaina, Uztaila, Abuztua, Iraila, Urria, Azaroa, Abendua] - abbr_month_names: [~, Urt, Ots, Mar, Api, Mai, Eka, Uzt, Abu, Ira, Urr, Aza, Abe] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%Y/%m/%d %H:%M" - time: "%H:%M" - short: "%b %d %H:%M" - long: "%Y %B %d %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "minutu erdi" - less_than_x_seconds: - one: "segundu bat baino gutxiago" - other: "%{count} segundu baino gutxiago" - x_seconds: - one: "segundu 1" - other: "%{count} segundu" - less_than_x_minutes: - one: "minutu bat baino gutxiago" - other: "%{count} minutu baino gutxiago" - x_minutes: - one: "minutu 1" - other: "%{count} minutu" - about_x_hours: - one: "ordu 1 inguru" - other: "%{count} ordu inguru" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "egun 1" - other: "%{count} egun" - about_x_months: - one: "hilabete 1 inguru" - other: "%{count} hilabete inguru" - x_months: - one: "hilabete 1" - other: "%{count} hilabete" - about_x_years: - one: "urte 1 inguru" - other: "%{count} urte inguru" - over_x_years: - one: "urte 1 baino gehiago" - other: "%{count} urte baino gehiago" - almost_x_years: - one: "ia urte 1" - other: "ia %{count} urte" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Byte" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "eta" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "Errore batek %{model} hau godetzea galarazi du." - other: "%{count} errorek %{model} hau gordetzea galarazi dute." - messages: - inclusion: "ez dago zerrendan" - exclusion: "erreserbatuta dago" - invalid: "baliogabea da" - confirmation: "ez du berrespenarekin bat egiten" - accepted: "onartu behar da" - empty: "ezin da hutsik egon" - blank: "ezin da hutsik egon" - too_long: "luzeegia da (maximoa %{count} karaktere dira)" - too_short: "laburregia da (minimoa %{count} karaktere dira)" - wrong_length: "luzera ezegokia da (%{count} karakter izan beharko litzake)" - taken: "dagoeneko hartuta dago" - not_a_number: "ez da zenbaki bat" - not_a_date: "ez da baliozko data" - greater_than: "%{count} baino handiagoa izan behar du" - greater_than_or_equal_to: "%{count} edo handiagoa izan behar du" - equal_to: "%{count} izan behar du" - less_than: "%{count} baino gutxiago izan behar du" - less_than_or_equal_to: "%{count} edo gutxiago izan behar du" - odd: "bakoitia izan behar du" - even: "bikoitia izan behar du" - greater_than_start_date: "hasiera data baino handiagoa izan behar du" - not_same_project: "ez dago proiektu berdinean" - circular_dependency: "Erlazio honek mendekotasun zirkular bat sortuko luke" - cant_link_an_issue_with_a_descendant: "Zeregin bat ezin da bere azpiataza batekin estekatu." - - actionview_instancetag_blank_option: Hautatu mesedez - - general_text_No: 'Ez' - general_text_Yes: 'Bai' - general_text_no: 'ez' - general_text_yes: 'bai' - general_lang_name: 'Euskara' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Kontua ongi eguneratu da. - notice_account_invalid_creditentials: Erabiltzaile edo pasahitz ezegokia - notice_account_password_updated: Pasahitza ongi eguneratu da. - notice_account_wrong_password: Pasahitz ezegokia. - notice_account_register_done: Kontua ongi sortu da. Kontua gaitzeko klikatu epostan adierazi zaizun estekan. - notice_account_unknown_email: Erabiltzaile ezezaguna. - notice_can_t_change_password: Kontu honek kanpoko autentikazio bat erabiltzen du. Ezinezkoa da pasahitza aldatzea. - notice_account_lost_email_sent: Pasahitz berria aukeratzeko jarraibideak dituen eposta bat bidali zaizu. - notice_account_activated: Zure kontua gaituta dago. Orain saioa has dezakezu - notice_successful_create: Sortze arrakastatsua. - notice_successful_update: Eguneratze arrakastatsua. - notice_successful_delete: Ezabaketa arrakastatsua. - notice_successful_connection: Konexio arrakastatsua. - notice_file_not_found: Atzitu nahi duzun orria ez da exisitzen edo ezabatua izan da. - notice_locking_conflict: Beste erabiltzaile batek datuak eguneratu ditu. - notice_not_authorized: Ez duzu orri hau atzitzeko baimenik. - notice_email_sent: "%{value} helbidera eposta bat bidali da" - notice_email_error: "Errorea eposta bidaltzean (%{value})" - notice_feeds_access_key_reseted: Zure RSS atzipen giltza berrezarri da. - notice_api_access_key_reseted: Zure API atzipen giltza berrezarri da. - notice_failed_to_save_issues: "Hautatutako %{total} zereginetatik %{count} ezin izan dira konpondu: %{ids}." - notice_no_issue_selected: "Ez da zereginik hautatu! Mesedez, editatu nahi dituzun arazoak markatu." - notice_account_pending: "Zure kontua sortu da, orain kudeatzailearen onarpenaren zain dago." - notice_default_data_loaded: Lehenetsitako konfigurazioa ongi kargatu da. - notice_unable_delete_version: Ezin da bertsioa ezabatu. - notice_issue_done_ratios_updated: Burututako zereginen erlazioa eguneratu da. - - error_can_t_load_default_data: "Ezin izan da lehenetsitako konfigurazioa kargatu: %{value}" - error_scm_not_found: "Sarrera edo berrikuspena ez da biltegian topatu." - error_scm_command_failed: "Errorea gertatu da biltegia atzitzean: %{value}" - error_scm_annotate: "Sarrera ez da existitzen edo ezin da anotatu." - error_issue_not_found_in_project: 'Zeregina ez da topatu edo ez da proiektu honetakoa' - error_no_tracker_in_project: 'Proiektu honek ez du aztarnaririk esleituta. Mesedez egiaztatu Proiektuaren ezarpenak.' - error_no_default_issue_status: 'Zereginek ez dute lehenetsitako egoerarik. Mesedez egiaztatu zure konfigurazioa ("Kudeaketa -> Arazoen egoerak" atalera joan).' - error_can_not_reopen_issue_on_closed_version: 'Itxitako bertsio batera esleitutako zereginak ezin dira berrireki' - error_can_not_archive_project: Proiektu hau ezin da artxibatu - error_issue_done_ratios_not_updated: "Burututako zereginen erlazioa ez da eguneratu." - error_workflow_copy_source: 'Mesedez hautatu iturburuko aztarnari edo rola' - error_workflow_copy_target: 'Mesedez hautatu helburuko aztarnari(ak) edo rola(k)' - - warning_attachments_not_saved: "%{count} fitxategi ezin izan d(ir)a gorde." - - mail_subject_lost_password: "Zure %{value} pasahitza" - mail_body_lost_password: 'Zure pasahitza aldatzeko hurrengo estekan klikatu:' - mail_subject_register: "Zure %{value} kontuaren gaitzea" - mail_body_register: 'Zure kontua gaitzeko hurrengo estekan klikatu:' - mail_body_account_information_external: "Zure %{value} kontua erabil dezakezu saioa hasteko." - mail_body_account_information: Zure kontuaren informazioa - mail_subject_account_activation_request: "%{value} kontu gaitzeko eskaera" - mail_body_account_activation_request: "Erabiltzaile berri bat (%{value}) erregistratu da. Kontua zure onarpenaren zain dago:" - mail_subject_reminder: "%{count} arazo hurrengo %{days} egunetan amaitzen d(ir)a" - mail_body_reminder: "Zuri esleituta dauden %{count} arazo hurrengo %{days} egunetan amaitzen d(ir)a:" - mail_subject_wiki_content_added: "'%{id}' wiki orria gehitu da" - mail_body_wiki_content_added: "%{author}-(e)k '%{id}' wiki orria gehitu du." - mail_subject_wiki_content_updated: "'%{id}' wiki orria eguneratu da" - mail_body_wiki_content_updated: "%{author}-(e)k '%{id}' wiki orria eguneratu du." - - gui_validation_error: akats 1 - gui_validation_error_plural: "%{count} akats" - - field_name: Izena - field_description: Deskribapena - field_summary: Laburpena - field_is_required: Beharrezkoa - field_firstname: Izena - field_lastname: Abizenak - field_mail: Eposta - field_filename: Fitxategia - field_filesize: Tamaina - field_downloads: Deskargak - field_author: Egilea - field_created_on: Sortuta - field_updated_on: Eguneratuta - field_field_format: Formatua - field_is_for_all: Proiektu guztietarako - field_possible_values: Balio posibleak - field_regexp: Expresio erregularra - field_min_length: Luzera minimoa - field_max_length: Luzera maxioma - field_value: Balioa - field_category: Kategoria - field_title: Izenburua - field_project: Proiektua - field_issue: Zeregina - field_status: Egoera - field_notes: Oharrak - field_is_closed: Itxitako arazoa - field_is_default: Lehenetsitako balioa - field_tracker: Aztarnaria - field_subject: Gaia - field_due_date: Amaiera data - field_assigned_to: Esleituta - field_priority: Lehentasuna - field_fixed_version: Helburuko bertsioa - field_user: Erabiltzilea - field_role: Rola - field_homepage: Orri nagusia - field_is_public: Publikoa - field_parent: "Honen azpiproiektua:" - field_is_in_roadmap: Arazoak ibilbide-mapan erakutsi - field_login: Erabiltzaile izena - field_mail_notification: Eposta jakinarazpenak - field_admin: Kudeatzailea - field_last_login_on: Azken konexioa - field_language: Hizkuntza - field_effective_date: Data - field_password: Pasahitza - field_new_password: Pasahitz berria - field_password_confirmation: Berrespena - field_version: Bertsioa - field_type: Mota - field_host: Ostalaria - field_port: Portua - field_account: Kontua - field_base_dn: Base DN - field_attr_login: Erabiltzaile atributua - field_attr_firstname: Izena atributua - field_attr_lastname: Abizenak atributua - field_attr_mail: Eposta atributua - field_onthefly: Zuzeneko erabiltzaile sorrera - field_start_date: Hasiera - field_done_ratio: Egindako % - field_auth_source: Autentikazio modua - field_hide_mail: Nire eposta helbidea ezkutatu - field_comments: Iruzkina - field_url: URL - field_start_page: Hasierako orria - field_subproject: Azpiproiektua - field_hours: Ordu - field_activity: Jarduera - field_spent_on: Data - field_identifier: Identifikatzailea - field_is_filter: Iragazki moduan erabilita - field_issue_to: Erlazionatutako zereginak - field_delay: Atzerapena - field_assignable: Arazoak rol honetara esleitu daitezke - field_redirect_existing_links: Existitzen diren estekak berbideratu - field_estimated_hours: Estimatutako denbora - field_column_names: Zutabeak - field_time_zone: Ordu zonaldea - field_searchable: Bilagarria - field_default_value: Lehenetsitako balioa - field_comments_sorting: Iruzkinak erakutsi - field_parent_title: Orri gurasoa - field_editable: Editagarria - field_watcher: Behatzailea - field_identity_url: OpenID URLa - field_content: Edukia - field_group_by: Emaitzak honegatik taldekatu - field_sharing: Partekatzea - - setting_app_title: Aplikazioaren izenburua - setting_app_subtitle: Aplikazioaren azpizenburua - setting_welcome_text: Ongietorriko testua - setting_default_language: Lehenetsitako hizkuntza - setting_login_required: Autentikazioa derrigorrezkoa - setting_self_registration: Norberak erregistratu - setting_attachment_max_size: Eranskinen tamaina max. - setting_issues_export_limit: Zereginen esportatze limitea - setting_mail_from: Igorlearen eposta helbidea - setting_bcc_recipients: Hartzaileak ezkutuko kopian (bcc) - setting_plain_text_mail: Testu soileko epostak (HTML-rik ez) - setting_host_name: Ostalari izena eta bidea - setting_text_formatting: Testu formatua - setting_wiki_compression: Wikiaren historia konprimitu - setting_feeds_limit: Jarioaren edukiera limitea - setting_default_projects_public: Proiektu berriak defektuz publikoak dira - setting_autofetch_changesets: Commit-ak automatikoki hartu - setting_sys_api_enabled: Biltegien kudeaketarako WS gaitu - setting_commit_ref_keywords: Erreferentzien gako-hitzak - setting_commit_fix_keywords: Konpontze gako-hitzak - setting_autologin: Saioa automatikoki hasi - setting_date_format: Data formatua - setting_time_format: Ordu formatua - setting_cross_project_issue_relations: Zereginak proiektuen artean erlazionatzea baimendu - setting_issue_list_default_columns: Zereginen zerrendan defektuz ikusten diren zutabeak - setting_emails_footer: Eposten oina - setting_protocol: Protokoloa - setting_per_page_options: Orriko objektuen aukerak - setting_user_format: Erabiltzaileak erakusteko formatua - setting_activity_days_default: Proiektuen jardueran erakusteko egunak - setting_display_subprojects_issues: Azpiproiektuen zereginak proiektu nagusian erakutsi defektuz - setting_enabled_scm: Gaitutako IKKak - setting_mail_handler_body_delimiters: "Lerro hauteko baten ondoren epostak moztu" - setting_mail_handler_api_enabled: Sarrerako epostentzako WS gaitu - setting_mail_handler_api_key: API giltza - setting_sequential_project_identifiers: Proiektuen identifikadore sekuentzialak sortu - setting_gravatar_enabled: Erabili Gravatar erabiltzaile ikonoak - setting_gravatar_default: Lehenetsitako Gravatar irudia - setting_diff_max_lines_displayed: Erakutsiko diren diff lerro kopuru maximoa - setting_file_max_size_displayed: Barnean erakuzten diren testu fitxategien tamaina maximoa - setting_repository_log_display_limit: Egunkari fitxategian erakutsiko diren berrikuspen kopuru maximoa. - setting_openid: Baimendu OpenID saio hasiera eta erregistatzea - setting_password_min_length: Pasahitzen luzera minimoa - setting_new_project_user_role_id: Proiektu berriak sortzerakoan kudeatzaile ez diren erabiltzaileei esleitutako rola - setting_default_projects_modules: Proiektu berrientzako defektuz gaitutako moduluak - setting_issue_done_ratio: "Zereginen burututako tasa kalkulatzean erabili:" - setting_issue_done_ratio_issue_field: Zeregin eremua erabili - setting_issue_done_ratio_issue_status: Zeregin egoera erabili - setting_start_of_week: "Egutegiak noiz hasi:" - setting_rest_api_enabled: Gaitu REST web zerbitzua - - permission_add_project: Proiektua sortu - permission_add_subprojects: Azpiproiektuak sortu - permission_edit_project: Proiektua editatu - permission_select_project_modules: Proiektuaren moduluak hautatu - permission_manage_members: Kideak kudeatu - permission_manage_versions: Bertsioak kudeatu - permission_manage_categories: Arazoen kategoriak kudeatu - permission_view_issues: Zereginak ikusi - permission_add_issues: Zereginak gehitu - permission_edit_issues: Zereginak aldatu - permission_manage_issue_relations: Zereginen erlazioak kudeatu - permission_add_issue_notes: Oharrak gehitu - permission_edit_issue_notes: Oharrak aldatu - permission_edit_own_issue_notes: Nork bere oharrak aldatu - permission_move_issues: Zereginak mugitu - permission_delete_issues: Zereginak ezabatu - permission_manage_public_queries: Galdera publikoak kudeatu - permission_save_queries: Galderak gorde - permission_view_gantt: Gantt grafikoa ikusi - permission_view_calendar: Egutegia ikusi - permission_view_issue_watchers: Behatzaileen zerrenda ikusi - permission_add_issue_watchers: Behatzaileak gehitu - permission_delete_issue_watchers: Behatzaileak ezabatu - permission_log_time: Igarotako denbora erregistratu - permission_view_time_entries: Igarotako denbora ikusi - permission_edit_time_entries: Denbora egunkariak editatu - permission_edit_own_time_entries: Nork bere denbora egunkariak editatu - permission_manage_news: Berriak kudeatu - permission_comment_news: Berrien iruzkinak egin - permission_manage_documents: Dokumentuak kudeatu - permission_view_documents: Dokumentuak ikusi - permission_manage_files: Fitxategiak kudeatu - permission_view_files: Fitxategiak ikusi - permission_manage_wiki: Wikia kudeatu - permission_rename_wiki_pages: Wiki orriak berrizendatu - permission_delete_wiki_pages: Wiki orriak ezabatu - permission_view_wiki_pages: Wikia ikusi - permission_view_wiki_edits: Wikiaren historia ikusi - permission_edit_wiki_pages: Wiki orriak editatu - permission_delete_wiki_pages_attachments: Eranskinak ezabatu - permission_protect_wiki_pages: Wiki orriak babestu - permission_manage_repository: Biltegiak kudeatu - permission_browse_repository: Biltegia arakatu - permission_view_changesets: Aldaketak ikusi - permission_commit_access: Commit atzipena - permission_manage_boards: Foroak kudeatu - permission_view_messages: Mezuak ikusi - permission_add_messages: Mezuak bidali - permission_edit_messages: Mezuak aldatu - permission_edit_own_messages: Nork bere mezuak aldatu - permission_delete_messages: Mezuak ezabatu - permission_delete_own_messages: Nork bere mezuak ezabatu - - project_module_issue_tracking: Zereginen jarraipena - project_module_time_tracking: Denbora jarraipena - project_module_news: Berriak - project_module_documents: Dokumentuak - project_module_files: Fitxategiak - project_module_wiki: Wiki - project_module_repository: Biltegia - project_module_boards: Foroak - - label_user: Erabiltzailea - label_user_plural: Erabiltzaileak - label_user_new: Erabiltzaile berria - label_user_anonymous: Ezezaguna - label_project: Proiektua - label_project_new: Proiektu berria - label_project_plural: Proiektuak - label_x_projects: - zero: proiekturik ez - one: proiektu bat - other: "%{count} proiektu" - label_project_all: Proiektu guztiak - label_project_latest: Azken proiektuak - label_issue: Zeregina - label_issue_new: Zeregin berria - label_issue_plural: Zereginak - label_issue_view_all: Zeregin guztiak ikusi - label_issues_by: "Zereginak honengatik: %{value}" - label_issue_added: Zeregina gehituta - label_issue_updated: Zeregina eguneratuta - label_document: Dokumentua - label_document_new: Dokumentu berria - label_document_plural: Dokumentuak - label_document_added: Dokumentua gehituta - label_role: Rola - label_role_plural: Rolak - label_role_new: Rol berria - label_role_and_permissions: Rolak eta baimenak - label_member: Kidea - label_member_new: Kide berria - label_member_plural: Kideak - label_tracker: Aztarnaria - label_tracker_plural: Aztarnariak - label_tracker_new: Aztarnari berria - label_workflow: Lan-fluxua - label_issue_status: Zeregin egoera - label_issue_status_plural: Zeregin egoerak - label_issue_status_new: Egoera berria - label_issue_category: Zeregin kategoria - label_issue_category_plural: Zeregin kategoriak - label_issue_category_new: Kategoria berria - label_custom_field: Eremu pertsonalizatua - label_custom_field_plural: Eremu pertsonalizatuak - label_custom_field_new: Eremu pertsonalizatu berria - label_enumerations: Enumerazioak - label_enumeration_new: Balio berria - label_information: Informazioa - label_information_plural: Informazioa - label_please_login: Saioa hasi mesedez - label_register: Erregistratu - label_login_with_open_id_option: edo OpenID-rekin saioa hasi - label_password_lost: Pasahitza galduta - label_home: Hasiera - label_my_page: Nire orria - label_my_account: Nire kontua - label_my_projects: Nire proiektuak - label_administration: Kudeaketa - label_login: Saioa hasi - label_logout: Saioa bukatu - label_help: Laguntza - label_reported_issues: Berri emandako zereginak - label_assigned_to_me_issues: Niri esleitutako arazoak - label_last_login: Azken konexioa - label_registered_on: Noiz erregistratuta - label_activity: Jarduerak - label_overall_activity: Jarduera guztiak - label_user_activity: "%{value}-(r)en jarduerak" - label_new: Berria - label_logged_as: "Sartutako erabiltzailea:" - label_environment: Ingurune - label_authentication: Autentikazioa - label_auth_source: Autentikazio modua - label_auth_source_new: Autentikazio modu berria - label_auth_source_plural: Autentikazio moduak - label_subproject_plural: Azpiproiektuak - label_subproject_new: Azpiproiektu berria - label_and_its_subprojects: "%{value} eta bere azpiproiektuak" - label_min_max_length: Luzera min - max - label_list: Zerrenda - label_date: Data - label_integer: Osokoa - label_float: Koma higikorrekoa - label_boolean: Boolearra - label_string: Testua - label_text: Testu luzea - label_attribute: Atributua - label_attribute_plural: Atributuak - label_download: "Deskarga %{count}" - label_download_plural: "%{count} Deskarga" - label_no_data: Ez dago erakusteko daturik - label_change_status: Egoera aldatu - label_history: Historikoa - label_attachment: Fitxategia - label_attachment_new: Fitxategi berria - label_attachment_delete: Fitxategia ezabatu - label_attachment_plural: Fitxategiak - label_file_added: Fitxategia gehituta - label_report: Berri ematea - label_report_plural: Berri emateak - label_news: Berria - label_news_new: Berria gehitu - label_news_plural: Berriak - label_news_latest: Azken berriak - label_news_view_all: Berri guztiak ikusi - label_news_added: Berria gehituta - label_settings: Ezarpenak - label_overview: Gainbegirada - label_version: Bertsioa - label_version_new: Bertsio berria - label_version_plural: Bertsioak - label_close_versions: Burututako bertsioak itxi - label_confirmation: Baieztapena - label_export_to: 'Eskuragarri baita:' - label_read: Irakurri... - label_public_projects: Proiektu publikoak - label_open_issues: irekita - label_open_issues_plural: irekiak - label_closed_issues: itxita - label_closed_issues_plural: itxiak - label_x_open_issues_abbr_on_total: - zero: 0 irekita / %{total} - one: 1 irekita / %{total} - other: "%{count} irekiak / %{total}" - label_x_open_issues_abbr: - zero: 0 irekita - one: 1 irekita - other: "%{count} irekiak" - label_x_closed_issues_abbr: - zero: 0 itxita - one: 1 itxita - other: "%{count} itxiak" - label_total: Guztira - label_permissions: Baimenak - label_current_status: Uneko egoera - label_new_statuses_allowed: Baimendutako egoera berriak - label_all: guztiak - label_none: ezer - label_nobody: inor - label_next: Hurrengoa - label_previous: Aurrekoak - label_used_by: Erabilita - label_details: Xehetasunak - label_add_note: Oharra gehitu - label_per_page: Orriko - label_calendar: Egutegia - label_months_from: hilabete noiztik - label_gantt: Gantt - label_internal: Barnekoa - label_last_changes: "azken %{count} aldaketak" - label_change_view_all: Aldaketa guztiak ikusi - label_personalize_page: Orri hau pertsonalizatu - label_comment: Iruzkin - label_comment_plural: Iruzkinak - label_x_comments: - zero: iruzkinik ez - one: iruzkin 1 - other: "%{count} iruzkin" - label_comment_add: Iruzkina gehitu - label_comment_added: Iruzkina gehituta - label_comment_delete: Iruzkinak ezabatu - label_query: Galdera pertsonalizatua - label_query_plural: Pertsonalizatutako galderak - label_query_new: Galdera berria - label_filter_add: Iragazkia gehitu - label_filter_plural: Iragazkiak - label_equals: da - label_not_equals: ez da - label_in_less_than: baino gutxiagotan - label_in_more_than: baino gehiagotan - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: hauetan - label_today: gaur - label_all_time: denbora guztia - label_yesterday: atzo - label_this_week: aste honetan - label_last_week: pasadan astean - label_last_n_days: "azken %{count} egunetan" - label_this_month: hilabete hau - label_last_month: pasadan hilabetea - label_this_year: urte hau - label_date_range: Data tartea - label_less_than_ago: egun hauek baino gutxiago - label_more_than_ago: egun hauek baino gehiago - label_ago: orain dela - label_contains: dauka - label_not_contains: ez dauka - label_day_plural: egun - label_repository: Biltegia - label_repository_plural: Biltegiak - label_browse: Arakatu - label_modification: "aldaketa %{count}" - label_modification_plural: "%{count} aldaketa" - label_branch: Adarra - label_tag: Etiketa - label_revision: Berrikuspena - label_revision_plural: Berrikuspenak - label_revision_id: "%{value} berrikuspen" - label_associated_revisions: Elkartutako berrikuspenak - label_added: gehituta - label_modified: aldatuta - label_copied: kopiatuta - label_renamed: berrizendatuta - label_deleted: ezabatuta - label_latest_revision: Azken berrikuspena - label_latest_revision_plural: Azken berrikuspenak - label_view_revisions: Berrikuspenak ikusi - label_view_all_revisions: Berrikuspen guztiak ikusi - label_max_size: Tamaina maximoa - label_sort_highest: Goraino mugitu - label_sort_higher: Gora mugitu - label_sort_lower: Behera mugitu - label_sort_lowest: Beheraino mugitu - label_roadmap: Ibilbide-mapa - label_roadmap_due_in: "Epea: %{value}" - label_roadmap_overdue: "%{value} berandu" - label_roadmap_no_issues: Ez dago zereginik bertsio honetan - label_search: Bilatu - label_result_plural: Emaitzak - label_all_words: hitz guztiak - label_wiki: Wikia - label_wiki_edit: Wiki edizioa - label_wiki_edit_plural: Wiki edizioak - label_wiki_page: Wiki orria - label_wiki_page_plural: Wiki orriak - label_index_by_title: Izenburuaren araberako indizea - label_index_by_date: Dataren araberako indizea - label_current_version: Uneko bertsioa - label_preview: Aurreikusi - label_feed_plural: Jarioak - label_changes_details: Aldaketa guztien xehetasunak - label_issue_tracking: Zeregin jarraipena - label_spent_time: Igarotako denbora - label_f_hour: "ordu %{value}" - label_f_hour_plural: "%{value} ordu" - label_time_tracking: Denbora jarraipena - label_change_plural: Aldaketak - label_statistics: Estatistikak - label_commits_per_month: Commit-ak hilabeteka - label_commits_per_author: Commit-ak egileka - label_view_diff: Ezberdintasunak ikusi - label_diff_inline: barnean - label_diff_side_by_side: aldez alde - label_options: Aukerak - label_copy_workflow_from: Kopiatu workflow-a hemendik - label_permissions_report: Baimenen txostena - label_watched_issues: Behatutako zereginak - label_related_issues: Erlazionatutako zereginak - label_applied_status: Aplikatutako egoera - label_loading: Kargatzen... - label_relation_new: Erlazio berria - label_relation_delete: Erlazioa ezabatu - label_relates_to: erlazionatuta dago - label_duplicates: bikoizten du - label_duplicated_by: honek bikoiztuta - label_blocks: blokeatzen du - label_blocked_by: honek blokeatuta - label_precedes: aurretik doa - label_follows: jarraitzen du - label_end_to_start: bukaeratik hasierara - label_end_to_end: bukaeratik bukaerara - label_start_to_start: hasieratik hasierhasieratik bukaerara - label_start_to_end: hasieratik bukaerara - label_stay_logged_in: Saioa mantendu - label_disabled: ezgaituta - label_show_completed_versions: Bukatutako bertsioak ikusi - label_me: ni - label_board: Foroa - label_board_new: Foro berria - label_board_plural: Foroak - label_topic_plural: Gaiak - label_message_plural: Mezuak - label_message_last: Azken mezua - label_message_new: Mezu berria - label_message_posted: Mezua gehituta - label_reply_plural: Erantzunak - label_send_information: Erabiltzaileai kontuaren informazioa bidali - label_year: Urtea - label_month: Hilabetea - label_week: Astea - label_date_from: Nork - label_date_to: Nori - label_language_based: Erabiltzailearen hizkuntzaren arabera - label_sort_by: "Ordenazioa: %{value}" - label_send_test_email: Frogako mezua bidali - label_feeds_access_key: RSS atzipen giltza - label_missing_feeds_access_key: RSS atzipen giltza falta da - label_feeds_access_key_created_on: "RSS atzipen giltza orain dela %{value} sortuta" - label_module_plural: Moduluak - label_added_time_by: "%{author}, orain dela %{age} gehituta" - label_updated_time_by: "%{author}, orain dela %{age} eguneratuta" - label_updated_time: "Orain dela %{value} eguneratuta" - label_jump_to_a_project: Joan proiektura... - label_file_plural: Fitxategiak - label_changeset_plural: Aldaketak - label_default_columns: Lehenetsitako zutabeak - label_no_change_option: (Aldaketarik ez) - label_bulk_edit_selected_issues: Hautatutako zereginak batera editatu - label_theme: Itxura - label_default: Lehenetsia - label_search_titles_only: Izenburuetan bakarrik bilatu - label_user_mail_option_all: "Nire proiektu guztietako gertakari guztientzat" - label_user_mail_option_selected: "Hautatutako proiektuetako edozein gertakarientzat..." - label_user_mail_no_self_notified: "Ez dut nik egiten ditudan aldeketen jakinarazpenik jaso nahi" - label_registration_activation_by_email: kontuak epostaz gaitu - label_registration_manual_activation: kontuak eskuz gaitu - label_registration_automatic_activation: kontuak automatikoki gaitu - label_display_per_page: "Orriko: %{value}" - label_age: Adina - label_change_properties: Propietateak aldatu - label_general: Orokorra - label_more: Gehiago - label_scm: IKK - label_plugins: Pluginak - label_ldap_authentication: LDAP autentikazioa - label_downloads_abbr: Desk. - label_optional_description: Aukerako deskribapena - label_add_another_file: Beste fitxategia gehitu - label_preferences: Hobespenak - label_chronological_order: Orden kronologikoan - label_reverse_chronological_order: Alderantzizko orden kronologikoan - label_planning: Planifikazioa - label_incoming_emails: Sarrerako epostak - label_generate_key: Giltza sortu - label_issue_watchers: Behatzaileak - label_example: Adibidea - label_display: Bistaratzea - label_sort: Ordenatu - label_ascending: Gorantz - label_descending: Beherantz - label_date_from_to: "%{start}-tik %{end}-ra" - label_wiki_content_added: Wiki orria gehituta - label_wiki_content_updated: Wiki orria eguneratuta - label_group: Taldea - label_group_plural: Taldeak - label_group_new: Talde berria - label_time_entry_plural: Igarotako denbora - label_version_sharing_none: Ez partekatuta - label_version_sharing_descendants: Azpiproiektuekin - label_version_sharing_hierarchy: Proiektu Hierarkiarekin - label_version_sharing_tree: Proiektu zuhaitzarekin - label_version_sharing_system: Proiektu guztiekin - label_update_issue_done_ratios: Zereginen burututako erlazioa eguneratu - label_copy_source: Iturburua - label_copy_target: Helburua - label_copy_same_as_target: Helburuaren berdina - label_display_used_statuses_only: Aztarnari honetan erabiltzen diren egoerak bakarrik erakutsi - label_api_access_key: API atzipen giltza - label_missing_api_access_key: API atzipen giltza falta da - label_api_access_key_created_on: "API atzipen giltza sortuta orain dela %{value}" - - button_login: Saioa hasi - button_submit: Bidali - button_save: Gorde - button_check_all: Guztiak markatu - button_uncheck_all: Guztiak desmarkatu - button_delete: Ezabatu - button_create: Sortu - button_create_and_continue: Sortu eta jarraitu - button_test: Frogatu - button_edit: Editatu - button_add: Gehitu - button_change: Aldatu - button_apply: Aplikatu - button_clear: Garbitu - button_lock: Blokeatu - button_unlock: Desblokeatu - button_download: Deskargatu - button_list: Zerrenda - button_view: Ikusi - button_move: Mugitu - button_move_and_follow: Mugitu eta jarraitu - button_back: Atzera - button_cancel: Ezeztatu - button_activate: Gahitu - button_sort: Ordenatu - button_log_time: Denbora erregistratu - button_rollback: Itzuli bertsio honetara - button_watch: Behatu - button_unwatch: Behatzen utzi - button_reply: Erantzun - button_archive: Artxibatu - button_unarchive: Desartxibatu - button_reset: Berrezarri - button_rename: Berrizendatu - button_change_password: Pasahitza aldatu - button_copy: Kopiatu - button_copy_and_follow: Kopiatu eta jarraitu - button_annotate: Anotatu - button_update: Eguneratu - button_configure: Konfiguratu - button_quote: Aipatu - button_duplicate: Bikoiztu - button_show: Ikusi - - status_active: gaituta - status_registered: izena emanda - status_locked: blokeatuta - - version_status_open: irekita - version_status_locked: blokeatuta - version_status_closed: itxita - - field_active: Gaituta - - text_select_mail_notifications: Jakinarazpenak zein ekintzetarako bidaliko diren hautatu. - text_regexp_info: adib. ^[A-Z0-9]+$ - text_min_max_length_info: 0k mugarik gabe esan nahi du - text_project_destroy_confirmation: Ziur zaude proiektu hau eta erlazionatutako datu guztiak ezabatu nahi dituzula? - text_subprojects_destroy_warning: "%{value} azpiproiektuak ere ezabatuko dira." - text_workflow_edit: Hautatu rola eta aztarnaria workflow-a editatzeko - text_are_you_sure: Ziur zaude? - text_journal_changed: "%{label} %{old}-(e)tik %{new}-(e)ra aldatuta" - text_journal_set_to: "%{label}-k %{value} balioa hartu du" - text_journal_deleted: "%{label} ezabatuta (%{old})" - text_journal_added: "%{label} %{value} gehituta" - text_tip_issue_begin_day: gaur hasten diren zereginak - text_tip_issue_end_day: gaur bukatzen diren zereginak - text_tip_issue_begin_end_day: gaur hasi eta bukatzen diren zereginak - text_caracters_maximum: "%{count} karaktere gehienez." - text_caracters_minimum: "Gutxienez %{count} karaktereetako luzerakoa izan behar du." - text_length_between: "Luzera %{min} eta %{max} karaktereen artekoa." - text_tracker_no_workflow: Ez da workflow-rik definitu aztarnari honentzako - text_unallowed_characters: Debekatutako karaktereak - text_comma_separated: Balio anitz izan daitezke (komaz banatuta). - text_line_separated: Balio anitz izan daitezke (balio bakoitza lerro batean). - text_issues_ref_in_commit_messages: Commit-en mezuetan zereginak erlazionatu eta konpontzen - text_issue_added: "%{id} zeregina %{author}-(e)k jakinarazi du." - text_issue_updated: "%{id} zeregina %{author}-(e)k eguneratu du." - text_wiki_destroy_confirmation: Ziur zaude wiki hau eta bere eduki guztiak ezabatu nahi dituzula? - text_issue_category_destroy_question: "Zeregin batzuk (%{count}) kategoria honetara esleituta daude. Zer egin nahi duzu?" - text_issue_category_destroy_assignments: Kategoria esleipenak kendu - text_issue_category_reassign_to: Zereginak kategoria honetara esleitu - text_user_mail_option: "Hautatu gabeko proiektuetan, behatzen edo parte hartzen duzun gauzei buruzko jakinarazpenak jasoko dituzu (adib. zu egile zaren edo esleituta dituzun zereginak)." - text_no_configuration_data: "Rolak, aztarnariak, zeregin egoerak eta workflow-ak ez dira oraindik konfiguratu.\nOso gomendagarria de lehenetsitako kkonfigurazioa kargatzea. Kargatu eta gero aldatu ahalko duzu." - text_load_default_configuration: Lehenetsitako konfigurazioa kargatu - text_status_changed_by_changeset: "%{value} aldaketan aplikatuta." - text_issues_destroy_confirmation: 'Ziur zaude hautatutako zeregina(k) ezabatu nahi dituzula?' - text_select_project_modules: 'Hautatu proiektu honetan gaitu behar diren moduluak:' - text_default_administrator_account_changed: Lehenetsitako kudeatzaile kontua aldatuta - text_file_repository_writable: Eranskinen direktorioan idatz daiteke - text_plugin_assets_writable: Pluginen baliabideen direktorioan idatz daiteke - text_rmagick_available: RMagick eskuragarri (aukerazkoa) - text_destroy_time_entries_question: "%{hours} orduei buruz berri eman zen zuk ezabatzera zoazen zereginean. Zer egin nahi duzu?" - text_destroy_time_entries: Ezabatu berri emandako orduak - text_assign_time_entries_to_project: Berri emandako orduak proiektura esleitu - text_reassign_time_entries: 'Berri emandako orduak zeregin honetara esleitu:' - text_user_wrote: "%{value}-(e)k idatzi zuen:" - text_enumeration_destroy_question: "%{count} objetu balio honetara esleituta daude." - text_enumeration_category_reassign_to: 'Beste balio honetara esleitu:' - text_email_delivery_not_configured: "Eposta bidalketa ez dago konfiguratuta eta jakinarazpenak ezgaituta daude.\nKonfiguratu zure SMTP zerbitzaria config/configuration.yml-n eta aplikazioa berrabiarazi hauek gaitzeko." - text_repository_usernames_mapping: "Hautatu edo eguneratu Redmineko erabiltzailea biltegiko egunkarietan topatzen diren erabiltzaile izenekin erlazionatzeko.\nRedmine-n eta biltegian erabiltzaile izen edo eposta berdina duten erabiltzaileak automatikoki erlazionatzen dira." - text_diff_truncated: '... Diff hau moztua izan da erakus daitekeen tamaina maximoa gainditu duelako.' - text_custom_field_possible_values_info: 'Lerro bat balio bakoitzeko' - text_wiki_page_destroy_question: "Orri honek %{descendants} orri seme eta ondorengo ditu. Zer egin nahi duzu?" - text_wiki_page_nullify_children: "Orri semeak erro orri moduan mantendu" - text_wiki_page_destroy_children: "Orri semeak eta beraien ondorengo guztiak ezabatu" - text_wiki_page_reassign_children: "Orri semeak orri guraso honetara esleitu" - text_own_membership_delete_confirmation: "Zure baimen batzuk (edo guztiak) kentzera zoaz eta baliteke horren ondoren proiektu hau ezin editatzea.\n Ziur zaude jarraitu nahi duzula?" - - default_role_manager: Kudeatzailea - default_role_developer: Garatzailea - default_role_reporter: Berriemailea - default_tracker_bug: Errorea - default_tracker_feature: Eginbidea - default_tracker_support: Laguntza - default_issue_status_new: Berria - default_issue_status_in_progress: Lanean - default_issue_status_resolved: Ebatzita - default_issue_status_feedback: Berrelikadura - default_issue_status_closed: Itxita - default_issue_status_rejected: Baztertua - default_doc_category_user: Erabiltzaile dokumentazioa - default_doc_category_tech: Dokumentazio teknikoa - default_priority_low: Baxua - default_priority_normal: Normala - default_priority_high: Altua - default_priority_urgent: Larria - default_priority_immediate: Berehalakoa - default_activity_design: Diseinua - default_activity_development: Garapena - - enumeration_issue_priorities: Zeregin lehentasunak - enumeration_doc_categories: Dokumentu kategoriak - enumeration_activities: Jarduerak (denbora kontrola)) - enumeration_system_activity: Sistemako Jarduera - label_board_sticky: Itsaskorra - label_board_locked: Blokeatuta - permission_export_wiki_pages: Wiki orriak esportatu - setting_cache_formatted_text: Formatudun testua katxeatu - permission_manage_project_activities: Proiektuaren jarduerak kudeatu - error_unable_delete_issue_status: Ezine da zereginaren egoera ezabatu - label_profile: Profila - permission_manage_subtasks: Azpiatazak kudeatu - field_parent_issue: Zeregin gurasoa - label_subtask_plural: Azpiatazak - label_project_copy_notifications: Proiektua kopiatzen den bitartean eposta jakinarazpenak bidali - error_can_not_delete_custom_field: Ezin da eremu pertsonalizatua ezabatu - error_unable_to_connect: Ezin da konektatu (%{value}) - error_can_not_remove_role: Rol hau erabiltzen hari da eta ezin da ezabatu. - error_can_not_delete_tracker: Aztarnari honek zereginak ditu eta ezin da ezabatu. - field_principal: Ekintzaile - label_my_page_block: "Nire orriko blokea" - notice_failed_to_save_members: "Kidea(k) gordetzean errorea: %{errors}." - text_zoom_out: Zooma txikiagotu - text_zoom_in: Zooma handiagotu - notice_unable_delete_time_entry: "Ezin da hautatutako denbora erregistroa ezabatu." - label_overall_spent_time: Igarotako denbora guztira - field_time_entries: "Denbora erregistratu" - project_module_gantt: Gantt - project_module_calendar: Egutegia - button_edit_associated_wikipage: "Esleitutako wiki orria editatu: %{page_title}" - field_text: Testu eremua - label_user_mail_option_only_owner: "Jabea naizen gauzetarako barrarik" - setting_default_notification_option: "Lehenetsitako ohartarazpen aukera" - label_user_mail_option_only_my_events: "Behatzen ditudan edo partaide naizen gauzetarako bakarrik" - label_user_mail_option_only_assigned: "Niri esleitutako gauzentzat bakarrik" - label_user_mail_option_none: "Gertakaririk ez" - field_member_of_group: "Esleituta duenaren taldea" - field_assigned_to_role: "Esleituta duenaren rola" - notice_not_authorized_archived_project: "Atzitu nahi duzun proiektua artxibatua izan da." - label_principal_search: "Bilatu erabiltzaile edo taldea:" - label_user_search: "Erabiltzailea bilatu:" - field_visible: Ikusgai - setting_emails_header: "Eposten goiburua" - setting_commit_logtime_activity_id: "Erregistratutako denboraren jarduera" - text_time_logged_by_changeset: "%{value} aldaketan egindakoa." - setting_commit_logtime_enabled: "Erregistrutako denbora gaitu" - notice_gantt_chart_truncated: Grafikoa moztu da bistara daitekeen elementuen kopuru maximoa gainditu delako (%{max}) - setting_gantt_items_limit: "Gantt grafikoan bistara daitekeen elementu kopuru maximoa" - field_warn_on_leaving_unsaved: Gorde gabeko testua duen orri batetik ateratzen naizenean ohartarazi - text_warn_on_leaving_unsaved: Uneko orritik joaten bazara gorde gabeko testua galduko da. - label_my_queries: Nire galdera pertsonalizatuak - text_journal_changed_no_detail: "%{label} eguneratuta" - label_news_comment_added: Berri batera iruzkina gehituta - button_expand_all: Guztia zabaldu - button_collapse_all: Guztia tolestu - label_additional_workflow_transitions_for_assignee: Erabiltzaileak esleitua duenean baimendutako transtsizio gehigarriak - label_additional_workflow_transitions_for_author: Erabiltzailea egilea denean baimendutako transtsizio gehigarriak - label_bulk_edit_selected_time_entries: Hautatutako denbora egunkariak batera editatu - text_time_entries_destroy_confirmation: Ziur zaude hautatutako denbora egunkariak ezabatu nahi dituzula? - label_role_anonymous: Ezezaguna - label_role_non_member: Ez kidea - label_issue_note_added: Oharra gehituta - label_issue_status_updated: Egoera eguneratuta - label_issue_priority_updated: Lehentasuna eguneratuta - label_issues_visibility_own: Erabiltzaileak sortu edo esleituta dituen zereginak - field_issues_visibility: Zeregin ikusgarritasuna - label_issues_visibility_all: Zeregin guztiak - permission_set_own_issues_private: Nork bere zereginak publiko edo pribatu jarri - field_is_private: Pribatu - permission_set_issues_private: Zereginak publiko edo pribatu jarri - label_issues_visibility_public: Pribatu ez diren zeregin guztiak - text_issues_destroy_descendants_confirmation: Honek %{count} azpiataza ezabatuko ditu baita ere. - field_commit_logs_encoding: Commit-en egunkarien kodetzea - field_scm_path_encoding: Bidearen kodeketa - text_scm_path_encoding_note: "Lehentsita: UTF-8" - field_path_to_repository: Biltegirako bidea - field_root_directory: Erro direktorioa - field_cvs_module: Modulua - field_cvsroot: CVSROOT - text_mercurial_repository_note: Biltegi locala (adib. /hgrepo, c:\hgrepo) - text_scm_command: Komandoa - text_scm_command_version: Bertsioa - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 zeregina - one: 1 zeregina - other: "%{count} zereginak" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: guztiak - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Azpiproiektuekin - label_cross_project_tree: Proiektu zuhaitzarekin - label_cross_project_hierarchy: Proiektu Hierarkiarekin - label_cross_project_system: Proiektu guztiekin - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1b/1bbdb7187bcb352854ea61beba1bb597d2b8fddb.svn-base --- a/.svn/pristine/1b/1bbdb7187bcb352854ea61beba1bb597d2b8fddb.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1080 +0,0 @@ -th: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] - abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] - abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%a, %d %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "less than 1 second" - other: "less than %{count} seconds" - x_seconds: - one: "1 second" - other: "%{count} seconds" - less_than_x_minutes: - one: "less than a minute" - other: "less than %{count} minutes" - x_minutes: - one: "1 minute" - other: "%{count} minutes" - about_x_hours: - one: "about 1 hour" - other: "about %{count} hours" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 day" - other: "%{count} days" - about_x_months: - one: "about 1 month" - other: "about %{count} months" - x_months: - one: "1 month" - other: "%{count} months" - about_x_years: - one: "about 1 year" - other: "about %{count} years" - over_x_years: - one: "over 1 year" - other: "over %{count} years" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - precision: 3 - delimiter: "" - storage_units: - format: "%n %u" - units: - kb: KB - tb: TB - gb: GB - byte: - one: Byte - other: Bytes - mb: MB - -# Used in array.to_sentence. - support: - array: - sentence_connector: "and" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "ไม่อยู่ในรายà¸à¸²à¸£" - exclusion: "ถูà¸à¸ªà¸‡à¸§à¸™à¹„ว้" - invalid: "ไม่ถูà¸à¸•้อง" - confirmation: "พิมพ์ไม่เหมือนเดิม" - accepted: "ต้องยอมรับ" - empty: "ต้องเติม" - blank: "ต้องเติม" - too_long: "ยาวเà¸à¸´à¸™à¹„ป" - too_short: "สั้นเà¸à¸´à¸™à¹„ป" - wrong_length: "ความยาวไม่ถูà¸à¸•้อง" - taken: "ถูà¸à¹ƒà¸Šà¹‰à¹„ปà¹à¸¥à¹‰à¸§" - not_a_number: "ไม่ใช่ตัวเลข" - not_a_date: "ไม่ใช่วันที่ ที่ถูà¸à¸•้อง" - greater_than: "must be greater than %{count}" - greater_than_or_equal_to: "must be greater than or equal to %{count}" - equal_to: "must be equal to %{count}" - less_than: "must be less than %{count}" - less_than_or_equal_to: "must be less than or equal to %{count}" - odd: "must be odd" - even: "must be even" - greater_than_start_date: "ต้องมาà¸à¸à¸§à¹ˆà¸²à¸§à¸±à¸™à¹€à¸£à¸´à¹ˆà¸¡" - not_same_project: "ไม่ได้อยู่ในโครงà¸à¸²à¸£à¹€à¸”ียวà¸à¸±à¸™" - circular_dependency: "ความสัมพันธ์อ้างอิงเป็นวงà¸à¸¥à¸¡" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: à¸à¸£à¸¸à¸“าเลือภ- - general_text_No: 'ไม่' - general_text_Yes: 'ใช่' - general_text_no: 'ไม่' - general_text_yes: 'ใช่' - general_lang_name: 'Thai (ไทย)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: Windows-874 - general_pdf_encoding: cp874 - general_first_day_of_week: '1' - - notice_account_updated: บัà¸à¸Šà¸µà¹„ด้ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹à¸¥à¹‰à¸§. - notice_account_invalid_creditentials: ชื้ผู้ใช้หรือรหัสผ่านไม่ถูà¸à¸•้อง - notice_account_password_updated: รหัสได้ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹à¸¥à¹‰à¸§. - notice_account_wrong_password: รหัสผ่านไม่ถูà¸à¸•้อง - notice_account_register_done: บัà¸à¸Šà¸µà¸–ูà¸à¸ªà¸£à¹‰à¸²à¸‡à¹à¸¥à¹‰à¸§. à¸à¸£à¸¸à¸“าเช็คเมล์ à¹à¸¥à¹‰à¸§à¸„ลิ๊à¸à¸—ี่ลิงค์ในอีเมล์เพื่อเปิดใช้บัà¸à¸Šà¸µ - notice_account_unknown_email: ไม่มีผู้ใช้ที่ใช้อีเมล์นี้. - notice_can_t_change_password: บัà¸à¸Šà¸µà¸™à¸µà¹‰à¹ƒà¸Šà¹‰à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนจาà¸à¹à¸«à¸¥à¹ˆà¸‡à¸ à¸²à¸¢à¸™à¸­à¸. ไม่สามารถปลี่ยนรหัสผ่านได้. - notice_account_lost_email_sent: เราได้ส่งอีเมล์พร้อมวิธีà¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸£à¸«à¸±à¸µà¸ªà¸œà¹ˆà¸²à¸™à¹ƒà¸«à¸¡à¹ˆà¹ƒà¸«à¹‰à¸„ุณà¹à¸¥à¹‰à¸§ à¸à¸£à¸¸à¸“าเช็คเมล์. - notice_account_activated: บัà¸à¸Šà¸µà¸‚องคุณได้เปิดใช้à¹à¸¥à¹‰à¸§. ตอนนี้คุณสามารถเข้าสู่ระบบได้à¹à¸¥à¹‰à¸§. - notice_successful_create: สร้างเสร็จà¹à¸¥à¹‰à¸§. - notice_successful_update: ปรับปรุงเสร็จà¹à¸¥à¹‰à¸§. - notice_successful_delete: ลบเสร็จà¹à¸¥à¹‰à¸§. - notice_successful_connection: ติดต่อสำเร็จà¹à¸¥à¹‰à¸§. - notice_file_not_found: หน้าที่คุณต้องà¸à¸²à¸£à¸”ูไม่มีอยู่จริง หรือถูà¸à¸¥à¸šà¹„ปà¹à¸¥à¹‰à¸§. - notice_locking_conflict: ข้อมูลถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹‚ดยผู้ใช้คนอื่น. - notice_not_authorized: คุณไม่มีสิทธิเข้าถึงหน้านี้. - notice_email_sent: "อีเมล์ได้ถูà¸à¸ªà¹ˆà¸‡à¸–ึง %{value}" - notice_email_error: "เà¸à¸´à¸”ความผิดพลาดขณะà¸à¸³à¸ªà¹ˆà¸‡à¸­à¸µà¹€à¸¡à¸¥à¹Œ (%{value})" - notice_feeds_access_key_reseted: RSS access key ของคุณถูภreset à¹à¸¥à¹‰à¸§. - notice_failed_to_save_issues: "%{count} ปัà¸à¸«à¸²à¸ˆà¸²à¸ %{total} ปัà¸à¸«à¸²à¸—ี่ถูà¸à¹€à¸¥à¸·à¸­à¸à¹„ม่สามารถจัดเà¸à¹‡à¸š: %{ids}." - notice_no_issue_selected: "ไม่มีปัà¸à¸«à¸²à¸—ี่ถูà¸à¹€à¸¥à¸·à¸­à¸! à¸à¸£à¸¸à¸“าเลือà¸à¸›à¸±à¸à¸«à¸²à¸—ี่คุณต้องà¸à¸²à¸£à¹à¸à¹‰à¹„ข." - notice_account_pending: "บัà¸à¸Šà¸µà¸‚องคุณสร้างเสร็จà¹à¸¥à¹‰à¸§ ขณะนี้รอà¸à¸²à¸£à¸­à¸™à¸¸à¸¡à¸±à¸•ิจาà¸à¸œà¸¹à¹‰à¸šà¸£à¸´à¸«à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£." - notice_default_data_loaded: ค่าเริ่มต้นโหลดเสร็จà¹à¸¥à¹‰à¸§. - - error_can_t_load_default_data: "ค่าเริ่มต้นโหลดไม่สำเร็จ: %{value}" - error_scm_not_found: "ไม่พบรุ่นที่ต้องà¸à¸²à¸£à¹ƒà¸™à¹à¸«à¸¥à¹ˆà¸‡à¹€à¸à¹‡à¸šà¸•้นฉบับ." - error_scm_command_failed: "เà¸à¸´à¸”ความผิดพลาดในà¸à¸²à¸£à¹€à¸‚้าถึงà¹à¸«à¸¥à¹ˆà¸‡à¹€à¸à¹‡à¸šà¸•้นฉบับ: %{value}" - error_scm_annotate: "entry ไม่มีอยู่จริง หรือไม่สามารถเขียนหมายเหตุประà¸à¸­à¸š." - error_issue_not_found_in_project: 'ไม่พบปัà¸à¸«à¸²à¸™à¸µà¹‰ หรือปัà¸à¸«à¸²à¹„ม่ได้อยู่ในโครงà¸à¸²à¸£à¸™à¸µà¹‰' - - mail_subject_lost_password: "รหัสผ่าน %{value} ของคุณ" - mail_body_lost_password: 'คลิ๊à¸à¸—ี่ลิงค์ต่อไปนี้เพื่อเปลี่ยนรหัสผ่าน:' - mail_subject_register: "เปิดบัà¸à¸Šà¸µ %{value} ของคุณ" - mail_body_register: 'คลิ๊à¸à¸—ี่ลิงค์ต่อไปนี้เพื่อเปลี่ยนรหัสผ่าน:' - mail_body_account_information_external: "คุณสามารถใช้บัà¸à¸Šà¸µ %{value} เพื่อเข้าสู่ระบบ." - mail_body_account_information: ข้อมูลบัà¸à¸Šà¸µà¸‚องคุณ - mail_subject_account_activation_request: "à¸à¸£à¸¸à¸“าเปิดบัà¸à¸Šà¸µ %{value}" - mail_body_account_activation_request: "ผู้ใช้ใหม่ (%{value}) ได้ลงทะเบียน. บัà¸à¸Šà¸µà¸‚องเขาà¸à¸³à¸¥à¸±à¸‡à¸£à¸­à¸­à¸™à¸¸à¸¡à¸±à¸•ิ:" - - gui_validation_error: 1 ข้อผิดพลาด - gui_validation_error_plural: "%{count} ข้อผิดพลาด" - - field_name: ชื่อ - field_description: รายละเอียด - field_summary: สรุปย่อ - field_is_required: ต้องใส่ - field_firstname: ชื่อ - field_lastname: นามสà¸à¸¸à¸¥ - field_mail: อีเมล์ - field_filename: à¹à¸Ÿà¹‰à¸¡ - field_filesize: ขนาด - field_downloads: ดาวน์โหลด - field_author: ผู้à¹à¸•่ง - field_created_on: สร้าง - field_updated_on: ปรับปรุง - field_field_format: รูปà¹à¸šà¸š - field_is_for_all: สำหรับทุà¸à¹‚ครงà¸à¸²à¸£ - field_possible_values: ค่าที่เป็นไปได้ - field_regexp: Regular expression - field_min_length: สั้นสุด - field_max_length: ยาวสุด - field_value: ค่า - field_category: ประเภท - field_title: ชื่อเรื่อง - field_project: โครงà¸à¸²à¸£ - field_issue: ปัà¸à¸«à¸² - field_status: สถานะ - field_notes: บันทึภ- field_is_closed: ปัà¸à¸«à¸²à¸ˆà¸š - field_is_default: ค่าเริ่มต้น - field_tracker: à¸à¸²à¸£à¸•ิดตาม - field_subject: เรื่อง - field_due_date: วันครบà¸à¸³à¸«à¸™à¸” - field_assigned_to: มอบหมายให้ - field_priority: ความสำคัภ- field_fixed_version: รุ่น - field_user: ผู้ใช้ - field_role: บทบาท - field_homepage: หน้าà¹à¸£à¸ - field_is_public: สาธารณะ - field_parent: โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢à¸‚อง - field_is_in_roadmap: ปัà¸à¸«à¸²à¹à¸ªà¸”งใน à¹à¸œà¸™à¸‡à¸²à¸™ - field_login: ชื่อที่ใช้เข้าระบบ - field_mail_notification: à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ือนทางอีเมล์ - field_admin: ผู้บริหารจัดà¸à¸²à¸£ - field_last_login_on: เข้าระบบครั้งสุดท้าย - field_language: ภาษา - field_effective_date: วันที่ - field_password: รหัสผ่าน - field_new_password: รหัสผ่านใหม่ - field_password_confirmation: ยืนยันรหัสผ่าน - field_version: รุ่น - field_type: ชนิด - field_host: โฮสต์ - field_port: พอร์ต - field_account: บัà¸à¸Šà¸µ - field_base_dn: Base DN - field_attr_login: เข้าระบบ attribute - field_attr_firstname: ชื่อ attribute - field_attr_lastname: นามสà¸à¸¸à¸¥ attribute - field_attr_mail: อีเมล์ attribute - field_onthefly: สร้างผู้ใช้ทันที - field_start_date: เริ่ม - field_done_ratio: "% สำเร็จ" - field_auth_source: วิธีà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตน - field_hide_mail: ซ่อนอีเมล์ของฉัน - field_comments: ความเห็น - field_url: URL - field_start_page: หน้าเริ่มต้น - field_subproject: โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢ - field_hours: ชั่วโมง - field_activity: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ - field_spent_on: วันที่ - field_identifier: ชื่อเฉพาะ - field_is_filter: ใช้เป็นตัวà¸à¸£à¸­à¸‡ - field_issue_to: ปัà¸à¸«à¸²à¸—ี่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง - field_delay: เลื่อน - field_assignable: ปัà¸à¸«à¸²à¸ªà¸²à¸¡à¸²à¸£à¸–มอบหมายให้คนที่ทำบทบาทนี้ - field_redirect_existing_links: ย้ายจุดเชื่อมโยงนี้ - field_estimated_hours: เวลาที่ใช้โดยประมาณ - field_column_names: สดมภ์ - field_time_zone: ย่านเวลา - field_searchable: ค้นหาได้ - field_default_value: ค่าเริ่มต้น - field_comments_sorting: à¹à¸ªà¸”งความเห็น - - setting_app_title: ชื่อโปรà¹à¸à¸£à¸¡ - setting_app_subtitle: ชื่อโปรà¹à¸à¸£à¸¡à¸£à¸­à¸‡ - setting_welcome_text: ข้อความต้อนรับ - setting_default_language: ภาษาเริ่มต้น - setting_login_required: ต้องป้อนผู้ใช้-รหัสผ่าน - setting_self_registration: ลงทะเบียนด้วยตนเอง - setting_attachment_max_size: ขนาดà¹à¸Ÿà¹‰à¸¡à¹à¸™à¸šà¸ªà¸¹à¸‡à¸ªà¸¸à¸” - setting_issues_export_limit: à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸­à¸­à¸à¸›à¸±à¸à¸«à¸²à¸ªà¸¹à¸‡à¸ªà¸¸à¸” - setting_mail_from: อีเมล์ที่ใช้ส่ง - setting_bcc_recipients: ไม่ระบุชื่อผู้รับ (bcc) - setting_host_name: ชื่อโฮสต์ - setting_text_formatting: à¸à¸²à¸£à¸ˆà¸±à¸”รูปà¹à¸šà¸šà¸‚้อความ - setting_wiki_compression: บีบอัดประวัติ Wiki - setting_feeds_limit: จำนวน Feed - setting_default_projects_public: โครงà¸à¸²à¸£à¹ƒà¸«à¸¡à¹ˆà¸¡à¸µà¸„่าเริ่มต้นเป็น สาธารณะ - setting_autofetch_changesets: ดึง commits อัตโนมัติ - setting_sys_api_enabled: เปิดใช้ WS สำหรับà¸à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£à¸—ี่เà¸à¹‡à¸šà¸•้นฉบับ - setting_commit_ref_keywords: คำสำคัภReferencing - setting_commit_fix_keywords: คำสำคัภFixing - setting_autologin: เข้าระบบอัตโนมัติ - setting_date_format: รูปà¹à¸šà¸šà¸§à¸±à¸™à¸—ี่ - setting_time_format: รูปà¹à¸šà¸šà¹€à¸§à¸¥à¸² - setting_cross_project_issue_relations: อนุà¸à¸²à¸•ให้ระบุปัà¸à¸«à¸²à¸‚้ามโครงà¸à¸²à¸£ - setting_issue_list_default_columns: สดมภ์เริ่มต้นà¹à¸ªà¸”งในรายà¸à¸²à¸£à¸›à¸±à¸à¸«à¸² - setting_emails_footer: คำลงท้ายอีเมล์ - setting_protocol: Protocol - setting_per_page_options: ตัวเลือà¸à¸ˆà¸³à¸™à¸§à¸™à¸•่อหน้า - setting_user_format: รูปà¹à¸šà¸šà¸à¸²à¸£à¹à¸ªà¸”งชื่อผู้ใช้ - setting_activity_days_default: จำนวนวันที่à¹à¸ªà¸”งในà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚องโครงà¸à¸²à¸£ - setting_display_subprojects_issues: à¹à¸ªà¸”งปัà¸à¸«à¸²à¸‚องโครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢à¹ƒà¸™à¹‚ครงà¸à¸²à¸£à¸«à¸¥à¸±à¸ - - project_module_issue_tracking: à¸à¸²à¸£à¸•ิดตามปัà¸à¸«à¸² - project_module_time_tracking: à¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸² - project_module_news: ข่าว - project_module_documents: เอà¸à¸ªà¸²à¸£ - project_module_files: à¹à¸Ÿà¹‰à¸¡ - project_module_wiki: Wiki - project_module_repository: ที่เà¸à¹‡à¸šà¸•้นฉบับ - project_module_boards: à¸à¸£à¸°à¸”านข้อความ - - label_user: ผู้ใช้ - label_user_plural: ผู้ใช้ - label_user_new: ผู้ใช้ใหม่ - label_project: โครงà¸à¸²à¸£ - label_project_new: โครงà¸à¸²à¸£à¹ƒà¸«à¸¡à¹ˆ - label_project_plural: โครงà¸à¸²à¸£ - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: โครงà¸à¸²à¸£à¸—ั้งหมด - label_project_latest: โครงà¸à¸²à¸£à¸¥à¹ˆà¸²à¸ªà¸¸à¸” - label_issue: ปัà¸à¸«à¸² - label_issue_new: ปัà¸à¸«à¸²à¹ƒà¸«à¸¡à¹ˆ - label_issue_plural: ปัà¸à¸«à¸² - label_issue_view_all: ดูปัà¸à¸«à¸²à¸—ั้งหมด - label_issues_by: "ปัà¸à¸«à¸²à¹‚ดย %{value}" - label_issue_added: ปัà¸à¸«à¸²à¸–ูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_issue_updated: ปัà¸à¸«à¸²à¸–ูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡ - label_document: เอà¸à¸ªà¸²à¸£ - label_document_new: เอà¸à¸ªà¸²à¸£à¹ƒà¸«à¸¡à¹ˆ - label_document_plural: เอà¸à¸ªà¸²à¸£ - label_document_added: เอà¸à¸ªà¸²à¸£à¸–ูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_role: บทบาท - label_role_plural: บทบาท - label_role_new: บทบาทใหม่ - label_role_and_permissions: บทบาทà¹à¸¥à¸°à¸ªà¸´à¸—ธิ - label_member: สมาชิภ- label_member_new: สมาชิà¸à¹ƒà¸«à¸¡à¹ˆ - label_member_plural: สมาชิภ- label_tracker: à¸à¸²à¸£à¸•ิดตาม - label_tracker_plural: à¸à¸²à¸£à¸•ิดตาม - label_tracker_new: à¸à¸²à¸£à¸•ิดตามใหม่ - label_workflow: ลำดับงาน - label_issue_status: สถานะของปัà¸à¸«à¸² - label_issue_status_plural: สถานะของปัà¸à¸«à¸² - label_issue_status_new: สถานะใหม - label_issue_category: ประเภทของปัà¸à¸«à¸² - label_issue_category_plural: ประเภทของปัà¸à¸«à¸² - label_issue_category_new: ประเภทใหม่ - label_custom_field: เขตข้อมูลà¹à¸šà¸šà¸£à¸°à¸šà¸¸à¹€à¸­à¸‡ - label_custom_field_plural: เขตข้อมูลà¹à¸šà¸šà¸£à¸°à¸šà¸¸à¹€à¸­à¸‡ - label_custom_field_new: สร้างเขตข้อมูลà¹à¸šà¸šà¸£à¸°à¸šà¸¸à¹€à¸­à¸‡ - label_enumerations: รายà¸à¸²à¸£ - label_enumeration_new: สร้างใหม่ - label_information: ข้อมูล - label_information_plural: ข้อมูล - label_please_login: à¸à¸£à¸¸à¸“าเข้าระบบà¸à¹ˆà¸­à¸™ - label_register: ลงทะเบียน - label_password_lost: ลืมรหัสผ่าน - label_home: หน้าà¹à¸£à¸ - label_my_page: หน้าของฉัน - label_my_account: บัà¸à¸Šà¸µà¸‚องฉัน - label_my_projects: โครงà¸à¸²à¸£à¸‚องฉัน - label_administration: บริหารจัดà¸à¸²à¸£ - label_login: เข้าระบบ - label_logout: ออà¸à¸£à¸°à¸šà¸š - label_help: ช่วยเหลือ - label_reported_issues: ปัà¸à¸«à¸²à¸—ี่à¹à¸ˆà¹‰à¸‡à¹„ว้ - label_assigned_to_me_issues: ปัà¸à¸«à¸²à¸—ี่มอบหมายให้ฉัน - label_last_login: ติดต่อครั้งสุดท้าย - label_registered_on: ลงทะเบียนเมื่อ - label_activity: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ - label_activity_plural: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ - label_activity_latest: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸¥à¹ˆà¸²à¸ªà¸¸à¸” - label_overall_activity: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¹‚ดยรวม - label_new: ใหม่ - label_logged_as: เข้าระบบในชื่อ - label_environment: สภาพà¹à¸§à¸”ล้อม - label_authentication: à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตน - label_auth_source: วิธีà¸à¸²à¸£à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตน - label_auth_source_new: สร้างวิธีà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนใหม่ - label_auth_source_plural: วิธีà¸à¸²à¸£ Authentication - label_subproject_plural: โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢ - label_min_max_length: สั้น-ยาว สุดที่ - label_list: รายà¸à¸²à¸£ - label_date: วันที่ - label_integer: จำนวนเต็ม - label_float: จำนวนจริง - label_boolean: ถูà¸à¸œà¸´à¸” - label_string: ข้อความ - label_text: ข้อความขนาดยาว - label_attribute: คุณลัà¸à¸©à¸“ะ - label_attribute_plural: คุณลัà¸à¸©à¸“ะ - label_download: "%{count} ดาวน์โหลด" - label_download_plural: "%{count} ดาวน์โหลด" - label_no_data: จำนวนข้อมูลที่à¹à¸ªà¸”ง - label_change_status: เปลี่ยนสถานะ - label_history: ประวัติ - label_attachment: à¹à¸Ÿà¹‰à¸¡ - label_attachment_new: à¹à¸Ÿà¹‰à¸¡à¹ƒà¸«à¸¡à¹ˆ - label_attachment_delete: ลบà¹à¸Ÿà¹‰à¸¡ - label_attachment_plural: à¹à¸Ÿà¹‰à¸¡ - label_file_added: à¹à¸Ÿà¹‰à¸¡à¸–ูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_report: รายงาน - label_report_plural: รายงาน - label_news: ข่าว - label_news_new: เพิ่มข่าว - label_news_plural: ข่าว - label_news_latest: ข่าวล่าสุด - label_news_view_all: ดูข่าวทั้งหมด - label_news_added: ข่าวถูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_settings: ปรับà¹à¸•่ง - label_overview: ภาพรวม - label_version: รุ่น - label_version_new: รุ่นใหม่ - label_version_plural: รุ่น - label_confirmation: ยืนยัน - label_export_to: 'รูปà¹à¸šà¸šà¸­à¸·à¹ˆà¸™à¹† :' - label_read: อ่าน... - label_public_projects: โครงà¸à¸²à¸£à¸ªà¸²à¸˜à¸²à¸£à¸“ะ - label_open_issues: เปิด - label_open_issues_plural: เปิด - label_closed_issues: ปิด - label_closed_issues_plural: ปิด - label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_total: จำนวนรวม - label_permissions: สิทธิ - label_current_status: สถานะปัจจุบัน - label_new_statuses_allowed: อนุà¸à¸²à¸•ให้มีสถานะใหม่ - label_all: ทั้งหมด - label_none: ไม่มี - label_nobody: ไม่มีใคร - label_next: ต่อไป - label_previous: à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² - label_used_by: ถูà¸à¹ƒà¸Šà¹‰à¹‚ดย - label_details: รายละเอียด - label_add_note: เพิ่มบันทึภ- label_per_page: ต่อหน้า - label_calendar: ปà¸à¸´à¸—ิน - label_months_from: เดือนจาภ- label_gantt: Gantt - label_internal: ภายใน - label_last_changes: "last %{count} เปลี่ยนà¹à¸›à¸¥à¸‡" - label_change_view_all: ดูà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸—ั้งหมด - label_personalize_page: ปรับà¹à¸•่งหน้านี้ - label_comment: ความเห็น - label_comment_plural: ความเห็น - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: เพิ่มความเห็น - label_comment_added: ความเห็นถูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_comment_delete: ลบความเห็น - label_query: à¹à¸šà¸šà¸ªà¸­à¸šà¸–ามà¹à¸šà¸šà¸à¸³à¸«à¸™à¸”เอง - label_query_plural: à¹à¸šà¸šà¸ªà¸­à¸šà¸–ามà¹à¸šà¸šà¸à¸³à¸«à¸™à¸”เอง - label_query_new: à¹à¸šà¸šà¸ªà¸­à¸šà¸–ามใหม่ - label_filter_add: เพิ่มตัวà¸à¸£à¸­à¸‡ - label_filter_plural: ตัวà¸à¸£à¸­à¸‡ - label_equals: คือ - label_not_equals: ไม่ใช่ - label_in_less_than: น้อยà¸à¸§à¹ˆà¸² - label_in_more_than: มาà¸à¸à¸§à¹ˆà¸² - label_in: ในช่วง - label_today: วันนี้ - label_all_time: ตลอดเวลา - label_yesterday: เมื่อวาน - label_this_week: อาทิตย์นี้ - label_last_week: อาทิตย์ที่à¹à¸¥à¹‰à¸§ - label_last_n_days: "%{count} วันย้อนหลัง" - label_this_month: เดือนนี้ - label_last_month: เดือนที่à¹à¸¥à¹‰à¸§ - label_this_year: ปีนี้ - label_date_range: ช่วงวันที่ - label_less_than_ago: น้อยà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¸§à¸±à¸™ - label_more_than_ago: มาà¸à¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¸§à¸±à¸™ - label_ago: วันผ่านมาà¹à¸¥à¹‰à¸§ - label_contains: มี... - label_not_contains: ไม่มี... - label_day_plural: วัน - label_repository: ที่เà¸à¹‡à¸šà¸•้นฉบับ - label_repository_plural: ที่เà¸à¹‡à¸šà¸•้นฉบับ - label_browse: เปิดหา - label_modification: "%{count} เปลี่ยนà¹à¸›à¸¥à¸‡" - label_modification_plural: "%{count} เปลี่ยนà¹à¸›à¸¥à¸‡" - label_revision: à¸à¸²à¸£à¹à¸à¹‰à¹„ข - label_revision_plural: à¸à¸²à¸£à¹à¸à¹‰à¹„ข - label_associated_revisions: à¸à¸²à¸£à¹à¸à¹‰à¹„ขที่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง - label_added: ถูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_modified: ถูà¸à¹à¸à¹‰à¹„ข - label_deleted: ถูà¸à¸¥à¸š - label_latest_revision: รุ่นà¸à¸²à¸£à¹à¸à¹‰à¹„ขล่าสุด - label_latest_revision_plural: รุ่นà¸à¸²à¸£à¹à¸à¹‰à¹„ขล่าสุด - label_view_revisions: ดูà¸à¸²à¸£à¹à¸à¹‰à¹„ข - label_max_size: ขนาดใหà¸à¹ˆà¸ªà¸¸à¸” - label_sort_highest: ย้ายไปบนสุด - label_sort_higher: ย้ายขึ้น - label_sort_lower: ย้ายลง - label_sort_lowest: ย้ายไปล่างสุด - label_roadmap: à¹à¸œà¸™à¸‡à¸²à¸™ - label_roadmap_due_in: "ถึงà¸à¸³à¸«à¸™à¸”ใน %{value}" - label_roadmap_overdue: "%{value} ช้าà¸à¸§à¹ˆà¸²à¸à¸³à¸«à¸™à¸”" - label_roadmap_no_issues: ไม่มีปัà¸à¸«à¸²à¸ªà¸³à¸«à¸£à¸±à¸šà¸£à¸¸à¹ˆà¸™à¸™à¸µà¹‰ - label_search: ค้นหา - label_result_plural: ผลà¸à¸²à¸£à¸„้นหา - label_all_words: ทุà¸à¸„ำ - label_wiki: Wiki - label_wiki_edit: à¹à¸à¹‰à¹„ข Wiki - label_wiki_edit_plural: à¹à¸à¹‰à¹„ข Wiki - label_wiki_page: หน้า Wiki - label_wiki_page_plural: หน้า Wiki - label_index_by_title: เรียงตามชื่อเรื่อง - label_index_by_date: เรียงตามวัน - label_current_version: รุ่นปัจจุบัน - label_preview: ตัวอย่างà¸à¹ˆà¸­à¸™à¸ˆà¸±à¸”เà¸à¹‡à¸š - label_feed_plural: Feeds - label_changes_details: รายละเอียดà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸—ั้งหมด - label_issue_tracking: ติดตามปัà¸à¸«à¸² - label_spent_time: เวลาที่ใช้ - label_f_hour: "%{value} ชั่วโมง" - label_f_hour_plural: "%{value} ชั่วโมง" - label_time_tracking: ติดตามà¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸² - label_change_plural: เปลี่ยนà¹à¸›à¸¥à¸‡ - label_statistics: สถิติ - label_commits_per_month: Commits ต่อเดือน - label_commits_per_author: Commits ต่อผู้à¹à¸•่ง - label_view_diff: ดูความà¹à¸•à¸à¸•่าง - label_diff_inline: inline - label_diff_side_by_side: side by side - label_options: ตัวเลือภ- label_copy_workflow_from: คัดลอà¸à¸¥à¸³à¸”ับงานจาภ- label_permissions_report: รายงานสิทธิ - label_watched_issues: เà¸à¹‰à¸²à¸”ูปัà¸à¸«à¸² - label_related_issues: ปัà¸à¸«à¸²à¸—ี่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง - label_applied_status: จัดเà¸à¹‡à¸šà¸ªà¸–านะ - label_loading: à¸à¸³à¸¥à¸±à¸‡à¹‚หลด... - label_relation_new: ความสัมพันธ์ใหม่ - label_relation_delete: ลบความสัมพันธ์ - label_relates_to: สัมพันธ์à¸à¸±à¸š - label_duplicates: ซ้ำ - label_blocks: à¸à¸µà¸”à¸à¸±à¸™ - label_blocked_by: à¸à¸µà¸”à¸à¸±à¸™à¹‚ดย - label_precedes: นำหน้า - label_follows: ตามหลัง - label_end_to_start: จบ-เริ่ม - label_end_to_end: จบ-จบ - label_start_to_start: เริ่ม-เริ่ม - label_start_to_end: เริ่ม-จบ - label_stay_logged_in: อยู่ในระบบต่อ - label_disabled: ไม่ใช้งาน - label_show_completed_versions: à¹à¸ªà¸”งรุ่นที่สมบูรณ์ - label_me: ฉัน - label_board: สภาà¸à¸²à¹à¸Ÿ - label_board_new: สร้างสภาà¸à¸²à¹à¸Ÿ - label_board_plural: สภาà¸à¸²à¹à¸Ÿ - label_topic_plural: หัวข้อ - label_message_plural: ข้อความ - label_message_last: ข้อความล่าสุด - label_message_new: เขียนข้อความใหม่ - label_message_posted: ข้อความถูà¸à¹€à¸žà¸´à¹ˆà¸¡à¹à¸¥à¹‰à¸§ - label_reply_plural: ตอบà¸à¸¥à¸±à¸š - label_send_information: ส่งรายละเอียดของบัà¸à¸Šà¸µà¹ƒà¸«à¹‰à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰ - label_year: ปี - label_month: เดือน - label_week: สัปดาห์ - label_date_from: จาภ- label_date_to: ถึง - label_language_based: ขึ้นอยู่à¸à¸±à¸šà¸ à¸²à¸©à¸²à¸‚องผู้ใช้ - label_sort_by: "เรียงโดย %{value}" - label_send_test_email: ส่งจดหมายทดสอบ - label_feeds_access_key_created_on: "RSS access key สร้างเมื่อ %{value} ที่ผ่านมา" - label_module_plural: ส่วนประà¸à¸­à¸š - label_added_time_by: "เพิ่มโดย %{author} %{age} ที่ผ่านมา" - label_updated_time: "ปรับปรุง %{value} ที่ผ่านมา" - label_jump_to_a_project: ไปที่โครงà¸à¸²à¸£... - label_file_plural: à¹à¸Ÿà¹‰à¸¡ - label_changeset_plural: à¸à¸¥à¸¸à¹ˆà¸¡à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ - label_default_columns: สดมภ์เริ่มต้น - label_no_change_option: (ไม่เปลี่ยนà¹à¸›à¸¥à¸‡) - label_bulk_edit_selected_issues: à¹à¸à¹‰à¹„ขปัà¸à¸«à¸²à¸—ี่เลือà¸à¸—ั้งหมด - label_theme: ชุดรูปà¹à¸šà¸š - label_default: ค่าเริ่มต้น - label_search_titles_only: ค้นหาจาà¸à¸Šà¸·à¹ˆà¸­à¹€à¸£à¸·à¹ˆà¸­à¸‡à¹€à¸—่านั้น - label_user_mail_option_all: "ทุà¸à¹† เหตุà¸à¸²à¸£à¸“์ในโครงà¸à¸²à¸£à¸‚องฉัน" - label_user_mail_option_selected: "ทุà¸à¹† เหตุà¸à¸²à¸£à¸“์ในโครงà¸à¸²à¸£à¸—ี่เลือà¸..." - label_user_mail_no_self_notified: "ฉันไม่ต้องà¸à¸²à¸£à¹„ด้รับà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ือนในสิ่งที่ฉันทำเอง" - label_registration_activation_by_email: เปิดบัà¸à¸Šà¸µà¸œà¹ˆà¸²à¸™à¸­à¸µà¹€à¸¡à¸¥à¹Œ - label_registration_manual_activation: อนุมัติโดยผู้บริหารจัดà¸à¸²à¸£ - label_registration_automatic_activation: เปิดบัà¸à¸Šà¸µà¸­à¸±à¸•โนมัติ - label_display_per_page: "ต่อหน้า: %{value}" - label_age: อายุ - label_change_properties: เปลี่ยนคุณสมบัติ - label_general: ทั่วๆ ไป - label_more: อื่น ๆ - label_scm: ตัวจัดà¸à¸²à¸£à¸•้นฉบับ - label_plugins: ส่วนเสริม - label_ldap_authentication: à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนโดยใช้ LDAP - label_downloads_abbr: D/L - label_optional_description: รายละเอียดเพิ่มเติม - label_add_another_file: เพิ่มà¹à¸Ÿà¹‰à¸¡à¸­à¸·à¹ˆà¸™à¹† - label_preferences: ค่าที่ชอบใจ - label_chronological_order: เรียงจาà¸à¹€à¸à¹ˆà¸²à¹„ปใหม่ - label_reverse_chronological_order: เรียงจาà¸à¹ƒà¸«à¸¡à¹ˆà¹„ปเà¸à¹ˆà¸² - label_planning: à¸à¸²à¸£à¸§à¸²à¸‡à¹à¸œà¸™ - - button_login: เข้าระบบ - button_submit: จัดส่งข้อมูล - button_save: จัดเà¸à¹‡à¸š - button_check_all: เลือà¸à¸—ั้งหมด - button_uncheck_all: ไม่เลือà¸à¸—ั้งหมด - button_delete: ลบ - button_create: สร้าง - button_test: ทดสอบ - button_edit: à¹à¸à¹‰à¹„ข - button_add: เพิ่ม - button_change: เปลี่ยนà¹à¸›à¸¥à¸‡ - button_apply: ประยุà¸à¸•์ใช้ - button_clear: ล้างข้อความ - button_lock: ล็อค - button_unlock: ยà¸à¹€à¸¥à¸´à¸à¸à¸²à¸£à¸¥à¹‡à¸­à¸„ - button_download: ดาวน์โหลด - button_list: รายà¸à¸²à¸£ - button_view: มุมมอง - button_move: ย้าย - button_back: à¸à¸¥à¸±à¸š - button_cancel: ยà¸à¹€à¸¥à¸´à¸ - button_activate: เปิดใช้ - button_sort: จัดเรียง - button_log_time: บันทึà¸à¹€à¸§à¸¥à¸² - button_rollback: ถอยà¸à¸¥à¸±à¸šà¸¡à¸²à¸—ี่รุ่นนี้ - button_watch: เà¸à¹‰à¸²à¸”ู - button_unwatch: เลิà¸à¹€à¸à¹‰à¸²à¸”ู - button_reply: ตอบà¸à¸¥à¸±à¸š - button_archive: เà¸à¹‡à¸šà¹€à¸‚้าโà¸à¸”ัง - button_unarchive: เอาออà¸à¸ˆà¸²à¸à¹‚à¸à¸”ัง - button_reset: เริ่มใหมท - button_rename: เปลี่ยนชื่อ - button_change_password: เปลี่ยนรหัสผ่าน - button_copy: คัดลอภ- button_annotate: หมายเหตุประà¸à¸­à¸š - button_update: ปรับปรุง - button_configure: ปรับà¹à¸•่ง - - status_active: เปิดใช้งานà¹à¸¥à¹‰à¸§ - status_registered: รอà¸à¸²à¸£à¸­à¸™à¸¸à¸¡à¸±à¸•ิ - status_locked: ล็อค - - text_select_mail_notifications: เลือà¸à¸à¸²à¸£à¸à¸£à¸°à¸—ำที่ต้องà¸à¸²à¸£à¹ƒà¸«à¹‰à¸ªà¹ˆà¸‡à¸­à¸µà¹€à¸¡à¸¥à¹Œà¹à¸ˆà¹‰à¸‡. - text_regexp_info: ตัวอย่าง ^[A-Z0-9]+$ - text_min_max_length_info: 0 หมายถึงไม่จำà¸à¸±à¸” - text_project_destroy_confirmation: คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¹„หมว่าต้องà¸à¸²à¸£à¸¥à¸šà¹‚ครงà¸à¸²à¸£à¹à¸¥à¸°à¸‚้อมูลที่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้่อง ? - text_subprojects_destroy_warning: "โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢: %{value} จะถูà¸à¸¥à¸šà¸”้วย." - text_workflow_edit: เลือà¸à¸šà¸—บาทà¹à¸¥à¸°à¸à¸²à¸£à¸•ิดตาม เพื่อà¹à¸à¹‰à¹„ขลำดับงาน - text_are_you_sure: คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¹„หม ? - text_tip_issue_begin_day: งานที่เริ่มวันนี้ - text_tip_issue_end_day: งานที่จบวันนี้ - text_tip_issue_begin_end_day: งานที่เริ่มà¹à¸¥à¸°à¸ˆà¸šà¸§à¸±à¸™à¸™à¸µà¹‰ - text_caracters_maximum: "สูงสุด %{count} ตัวอัà¸à¸©à¸£." - text_caracters_minimum: "ต้องยาวอย่างน้อย %{count} ตัวอัà¸à¸©à¸£." - text_length_between: "ความยาวระหว่าง %{min} ถึง %{max} ตัวอัà¸à¸©à¸£." - text_tracker_no_workflow: ไม่ได้บัà¸à¸à¸±à¸•ิลำดับงานสำหรับà¸à¸²à¸£à¸•ิดตามนี้ - text_unallowed_characters: ตัวอัà¸à¸©à¸£à¸•้องห้าม - text_comma_separated: ใส่ได้หลายค่า โดยคั่นด้วยลูà¸à¸™à¹‰à¸³( ,). - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_issue_added: "ปัà¸à¸«à¸² %{id} ถูà¸à¹à¸ˆà¹‰à¸‡à¹‚ดย %{author}." - text_issue_updated: "ปัà¸à¸«à¸² %{id} ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹‚ดย %{author}." - text_wiki_destroy_confirmation: คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸«à¸£à¸·à¸­à¸§à¹ˆà¸²à¸•้องà¸à¸²à¸£à¸¥à¸š wiki นี้พร้อมทั้งเนี้อหา? - text_issue_category_destroy_question: "บางปัà¸à¸«à¸² (%{count}) อยู่ในประเภทนี้. คุณต้องà¸à¸²à¸£à¸—ำอย่างไร ?" - text_issue_category_destroy_assignments: ลบประเภทนี้ - text_issue_category_reassign_to: ระบุปัà¸à¸«à¸²à¹ƒà¸™à¸›à¸£à¸°à¹€à¸ à¸—นี้ - text_user_mail_option: "ในโครงà¸à¸²à¸£à¸—ี่ไม่ได้เลือà¸, คุณจะได้รับà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸—ี่คุณเà¸à¹‰à¸²à¸”ูหรือมีส่วนเà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง (เช่นปัà¸à¸«à¸²à¸—ี่คุณà¹à¸ˆà¹‰à¸‡à¹„ว้หรือได้รับมอบหมาย)." - text_no_configuration_data: "บทบาท, à¸à¸²à¸£à¸•ิดตาม, สถานะปัà¸à¸«à¸² à¹à¸¥à¸°à¸¥à¸³à¸”ับงานยังไม่ได้ถูà¸à¸•ั้งค่า.\nขอà¹à¸™à¸°à¸™à¸³à¹ƒà¸«à¹‰à¹‚หลดค่าเริ่มต้น. คุณสามารถà¹à¸à¹‰à¹„ขค่าได้หลังจาà¸à¹‚หลดà¹à¸¥à¹‰à¸§." - text_load_default_configuration: โหลดค่าเริ่มต้น - text_status_changed_by_changeset: "ประยุà¸à¸•์ใช้ในà¸à¸¥à¸¸à¹ˆà¸¡à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ %{value}." - text_issues_destroy_confirmation: 'คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¹„หมว่าต้องà¸à¸²à¸£à¸¥à¸šà¸›à¸±à¸à¸«à¸²(ทั้งหลาย)ที่เลือà¸à¹„ว้?' - text_select_project_modules: 'เลือà¸à¸ªà¹ˆà¸§à¸™à¸›à¸£à¸°à¸à¸­à¸šà¸—ี่ต้องà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¸ªà¸³à¸«à¸£à¸±à¸šà¹‚ครงà¸à¸²à¸£à¸™à¸µà¹‰:' - text_default_administrator_account_changed: ค่าเริ่มต้นของบัà¸à¸Šà¸µà¸œà¸¹à¹‰à¸šà¸£à¸´à¸«à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£à¸–ูà¸à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ - text_file_repository_writable: ที่เà¸à¹‡à¸šà¸•้นฉบับสามารถเขียนได้ - text_rmagick_available: RMagick มีให้ใช้ (เป็นตัวเลือà¸) - text_destroy_time_entries_question: "%{hours} ชั่วโมงที่ถูà¸à¹à¸ˆà¹‰à¸‡à¹ƒà¸™à¸›à¸±à¸à¸«à¸²à¸™à¸µà¹‰à¸ˆà¸°à¹‚ดนลบ. คุณต้องà¸à¸²à¸£à¸—ำอย่างไร?" - text_destroy_time_entries: ลบเวลาที่รายงานไว้ - text_assign_time_entries_to_project: ระบุเวลาที่ใช้ในโครงà¸à¸²à¸£à¸™à¸µà¹‰ - text_reassign_time_entries: 'ระบุเวลาที่ใช้ในโครงà¸à¸²à¸£à¸™à¸µà¹ˆà¸­à¸µà¸à¸„รั้ง:' - - default_role_manager: ผู้จัดà¸à¸²à¸£ - default_role_developer: ผู้พัฒนา - default_role_reporter: ผู้รายงาน - default_tracker_bug: บั๊ภ- default_tracker_feature: ลัà¸à¸©à¸“ะเด่น - default_tracker_support: สนับสนุน - default_issue_status_new: เà¸à¸´à¸”ขึ้น - default_issue_status_in_progress: In Progress - default_issue_status_resolved: ดำเนินà¸à¸²à¸£ - default_issue_status_feedback: รอคำตอบ - default_issue_status_closed: จบ - default_issue_status_rejected: ยà¸à¹€à¸¥à¸´à¸ - default_doc_category_user: เอà¸à¸ªà¸²à¸£à¸‚องผู้ใช้ - default_doc_category_tech: เอà¸à¸ªà¸²à¸£à¸—างเทคนิค - default_priority_low: ต่ำ - default_priority_normal: ปà¸à¸•ิ - default_priority_high: สูง - default_priority_urgent: เร่งด่วน - default_priority_immediate: ด่วนมาภ- default_activity_design: ออà¸à¹à¸šà¸š - default_activity_development: พัฒนา - - enumeration_issue_priorities: ความสำคัà¸à¸‚องปัà¸à¸«à¸² - enumeration_doc_categories: ประเภทเอà¸à¸ªà¸²à¸£ - enumeration_activities: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ (ใช้ในà¸à¸²à¸£à¸•ิดตามเวลา) - label_and_its_subprojects: "%{value} and its subprojects" - mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" - mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" - text_user_wrote: "%{value} wrote:" - label_duplicated_by: duplicated by - setting_enabled_scm: Enabled SCM - text_enumeration_category_reassign_to: 'Reassign them to this value:' - text_enumeration_destroy_question: "%{count} objects are assigned to this value." - label_incoming_emails: Incoming emails - label_generate_key: Generate a key - setting_mail_handler_api_enabled: Enable WS for incoming emails - setting_mail_handler_api_key: API key - text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." - field_parent_title: Parent page - label_issue_watchers: Watchers - button_quote: Quote - setting_sequential_project_identifiers: Generate sequential project identifiers - notice_unable_delete_version: Unable to delete version - label_renamed: renamed - label_copied: copied - setting_plain_text_mail: plain text only (no HTML) - permission_view_files: View files - permission_edit_issues: Edit issues - permission_edit_own_time_entries: Edit own time logs - permission_manage_public_queries: Manage public queries - permission_add_issues: Add issues - permission_log_time: Log spent time - permission_view_changesets: View changesets - permission_view_time_entries: View spent time - permission_manage_versions: Manage versions - permission_manage_wiki: Manage wiki - permission_manage_categories: Manage issue categories - permission_protect_wiki_pages: Protect wiki pages - permission_comment_news: Comment news - permission_delete_messages: Delete messages - permission_select_project_modules: Select project modules - permission_manage_documents: Manage documents - permission_edit_wiki_pages: Edit wiki pages - permission_add_issue_watchers: Add watchers - permission_view_gantt: View gantt chart - permission_move_issues: Move issues - permission_manage_issue_relations: Manage issue relations - permission_delete_wiki_pages: Delete wiki pages - permission_manage_boards: Manage boards - permission_delete_wiki_pages_attachments: Delete attachments - permission_view_wiki_edits: View wiki history - permission_add_messages: Post messages - permission_view_messages: View messages - permission_manage_files: Manage files - permission_edit_issue_notes: Edit notes - permission_manage_news: Manage news - permission_view_calendar: View calendrier - permission_manage_members: Manage members - permission_edit_messages: Edit messages - permission_delete_issues: Delete issues - permission_view_issue_watchers: View watchers list - permission_manage_repository: Manage repository - permission_commit_access: Commit access - permission_browse_repository: Browse repository - permission_view_documents: View documents - permission_edit_project: Edit project - permission_add_issue_notes: Add notes - permission_save_queries: Save queries - permission_view_wiki_pages: View wiki - permission_rename_wiki_pages: Rename wiki pages - permission_edit_time_entries: Edit time logs - permission_edit_own_issue_notes: Edit own notes - setting_gravatar_enabled: Use Gravatar user icons - label_example: Example - text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." - permission_edit_own_messages: Edit own messages - permission_delete_own_messages: Delete own messages - label_user_activity: "%{value}'s activity" - label_updated_time_by: "Updated by %{author} %{age} ago" - text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' - setting_diff_max_lines_displayed: Max number of diff lines displayed - text_plugin_assets_writable: Plugin assets directory writable - warning_attachments_not_saved: "%{count} file(s) could not be saved." - button_create_and_continue: Create and continue - text_custom_field_possible_values_info: 'One line for each value' - label_display: Display - field_editable: Editable - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_file_max_size_displayed: Max size of text files displayed inline - field_watcher: Watcher - setting_openid: Allow OpenID login and registration - field_identity_url: OpenID URL - label_login_with_open_id_option: or login with OpenID - field_content: Content - label_descending: Descending - label_sort: Sort - label_ascending: Ascending - label_date_from_to: From %{start} to %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? - text_wiki_page_reassign_children: Reassign child pages to this parent page - text_wiki_page_nullify_children: Keep child pages as root pages - text_wiki_page_destroy_children: Delete child pages and all their descendants - setting_password_min_length: Minimum password length - field_group_by: Group results by - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - label_wiki_content_added: Wiki page added - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: The '%{id}' wiki page has been added by %{author}. - label_wiki_content_updated: Wiki page updated - mail_body_wiki_content_updated: The '%{id}' wiki page has been updated by %{author}. - permission_add_project: Create project - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - label_view_all_revisions: View all revisions - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. - error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). - text_journal_changed: "%{label} changed from %{old} to %{new}" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} deleted (%{old})" - label_group_plural: Groups - label_group: Group - label_group_new: New group - label_time_entry_plural: Spent time - text_journal_added: "%{label} %{value} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Commit messages encoding - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 ปัà¸à¸«à¸² - one: 1 ปัà¸à¸«à¸² - other: "%{count} ปัà¸à¸«à¸²" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: ทั้งหมด - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: With subprojects - label_cross_project_tree: With project tree - label_cross_project_hierarchy: With project hierarchy - label_cross_project_system: With all projects - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1b/1bcf725235890aaeb11816218989eb6ac291e46f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1b/1bcf725235890aaeb11816218989eb6ac291e46f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,81 @@ +<%= labelled_fields_for :issue, @issue do |f| %> + +
    +
    +<% if @issue.safe_attribute?('status_id') && @allowed_statuses.present? %> +

    <%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), {:required => true}, + :onchange => "updateIssueFrom('#{escape_javascript project_issue_form_path(@project, :id => @issue, :format => 'js')}')" %>

    + +<% else %> +

    <%= h(@issue.status.name) %>

    +<% end %> + +<% if @issue.safe_attribute? 'priority_id' %> +

    <%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %>

    +<% end %> + +<% if @issue.safe_attribute? 'assigned_to_id' %> +

    <%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true, :required => @issue.required_attribute?('assigned_to_id') %>

    +<% end %> + +<% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %> +

    <%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true, :required => @issue.required_attribute?('category_id') %> +<%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'), + new_project_issue_category_path(@issue.project), + :remote => true, + :method => 'get', + :title => l(:label_issue_category_new), + :tabindex => 200) if User.current.allowed_to?(:manage_categories, @issue.project) %>

    +<% end %> + +<% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %> +

    <%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true, :required => @issue.required_attribute?('fixed_version_id') %> +<%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'), + new_project_version_path(@issue.project), + :remote => true, + :method => 'get', + :title => l(:label_version_new), + :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %> +

    +<% end %> +
    + +
    +<% if @issue.safe_attribute? 'parent_issue_id' %> +

    <%= f.text_field :parent_issue_id, :size => 10, :required => @issue.required_attribute?('parent_issue_id') %>

    +<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path}')" %> +<% end %> + +<% if @issue.safe_attribute? 'start_date' %> +

    + <%= f.text_field(:start_date, :size => 10, :disabled => !@issue.leaf?, + :required => @issue.required_attribute?('start_date')) %> + <%= calendar_for('issue_start_date') if @issue.leaf? %> +

    +<% end %> + +<% if @issue.safe_attribute? 'due_date' %> +

    + <%= f.text_field(:due_date, :size => 10, :disabled => !@issue.leaf?, + :required => @issue.required_attribute?('due_date')) %> + <%= calendar_for('issue_due_date') if @issue.leaf? %> +

    +<% end %> + +<% if @issue.safe_attribute? 'estimated_hours' %> +

    <%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %>

    +<% end %> + +<% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %> +

    <%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %>

    +<% end %> +
    +
    + +<% if @issue.safe_attribute? 'custom_field_values' %> +<%= render :partial => 'issues/form_custom_fields' %> +<% end %> + +<% end %> + +<% include_calendar_headers_tags %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1b/1bd16d41e7893f1a65ab6da01777dc7a3b6b060d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1b/1bd16d41e7893f1a65ab6da01777dc7a3b6b060d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,112 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class Principal < ActiveRecord::Base + self.table_name = "#{table_name_prefix}users#{table_name_suffix}" + + # Account statuses + STATUS_ANONYMOUS = 0 + STATUS_ACTIVE = 1 + STATUS_REGISTERED = 2 + STATUS_LOCKED = 3 + + has_many :members, :foreign_key => 'user_id', :dependent => :destroy + has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}", :order => "#{Project.table_name}.name" + has_many :projects, :through => :memberships + has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify + + # Groups and active users + scope :active, lambda { where(:status => STATUS_ACTIVE) } + + scope :like, lambda {|q| + q = q.to_s + if q.blank? + where({}) + else + pattern = "%#{q}%" + sql = %w(login firstname lastname mail).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ") + params = {:p => pattern} + if q =~ /^(.+)\s+(.+)$/ + a, b = "#{$1}%", "#{$2}%" + sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))" + sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))" + params.merge!(:a => a, :b => b) + end + where(sql, params) + end + } + + # Principals that are members of a collection of projects + scope :member_of, lambda {|projects| + projects = [projects] unless projects.is_a?(Array) + if projects.empty? + where("1=0") + else + ids = projects.map(&:id) + active.uniq.joins(:members).where("#{Member.table_name}.project_id IN (?)", ids) + end + } + # Principals that are not members of projects + scope :not_member_of, lambda {|projects| + projects = [projects] unless projects.is_a?(Array) + if projects.empty? + where("1=0") + else + ids = projects.map(&:id) + where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids) + end + } + scope :sorted, lambda { order(*Principal.fields_for_order_statement)} + + before_create :set_default_empty_values + + def name(formatter = nil) + to_s + end + + def <=>(principal) + if principal.nil? + -1 + elsif self.class.name == principal.class.name + self.to_s.downcase <=> principal.to_s.downcase + else + # groups after users + principal.class.name <=> self.class.name + end + end + + # Returns an array of fields names than can be used to make an order statement for principals. + # Users are sorted before Groups. + # Examples: + def self.fields_for_order_statement(table=nil) + table ||= table_name + columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id'] + columns.uniq.map {|field| "#{table}.#{field}"} + end + + protected + + # Make sure we don't try to insert NULL values (see #4632) + def set_default_empty_values + self.login ||= '' + self.hashed_password ||= '' + self.firstname ||= '' + self.lastname ||= '' + self.mail ||= '' + true + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1b/1bd851612612b2edf1175e12bbce9b893a16c216.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1b/1bd851612612b2edf1175e12bbce9b893a16c216.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,52 @@ +<%= error_messages_for 'tracker' %> + +
    +
    + +

    <%= f.text_field :name, :required => true %>

    +

    <%= f.check_box :is_in_roadmap %>

    + +

    + + <% Tracker::CORE_FIELDS.each do |field| %> + + <% end %> +

    +<%= hidden_field_tag 'tracker[core_fields][]', '' %> + +<% if IssueCustomField.all.any? %> +

    + + <% IssueCustomField.all.each do |field| %> + + <% end %> +

    +<%= hidden_field_tag 'tracker[custom_field_ids][]', '' %> +<% end %> + +<% if @tracker.new_record? && @trackers.any? %> +

    +<%= select_tag(:copy_workflow_from, content_tag("option") + options_from_collection_for_select(@trackers, :id, :name)) %>

    +<% end %> + +
    +<%= submit_tag l(@tracker.new_record? ? :button_create : :button_save) %> +
    + +
    +<% if @projects.any? %> +
    <%= l(:label_project_plural) %> +<%= render_project_nested_lists(@projects) do |p| + content_tag('label', check_box_tag('tracker[project_ids][]', p.id, @tracker.projects.to_a.include?(p), :id => nil) + ' ' + h(p)) +end %> +<%= hidden_field_tag('tracker[project_ids][]', '', :id => nil) %> +

    <%= check_all_links 'tracker_project_ids' %>

    +
    +<% end %> +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1c/1c38bd1806aa4d1bf50821953dfb345b483fc9ed.svn-base --- a/.svn/pristine/1c/1c38bd1806aa4d1bf50821953dfb345b483fc9ed.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class PatchesTest < ActiveSupport::TestCase - include Redmine::I18n - - context "ActiveRecord::Base.human_attribute_name" do - setup do - Setting.default_language = 'en' - end - - should "transform name to field_name" do - assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on') - end - - should "cut extra _id suffix for better validation" do - assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on_id') - end - - should "default to humanized value if no translation has been found (useful for custom fields)" do - assert_equal 'Patch name', ActiveRecord::Base.human_attribute_name('Patch name') - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1c/1c60d8632b51110591957f2fbcb2673042f354f8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1c/1c60d8632b51110591957f2fbcb2673042f354f8.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,99 @@ +source 'https://rubygems.org' + +gem "rails", "3.2.17" +gem "jquery-rails", "~> 2.0.2" +gem "coderay", "~> 1.1.0" +gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] +gem "builder", "3.0.0" + +# Optional gem for LDAP authentication +group :ldap do + gem "net-ldap", "~> 0.3.1" +end + +# Optional gem for OpenID authentication +group :openid do + gem "ruby-openid", "~> 2.3.0", :require => "openid" + gem "rack-openid" +end + +# Optional gem for exporting the gantt to a PNG file, not supported with jruby +platforms :mri, :mingw do + group :rmagick do + # RMagick 2 supports ruby 1.9 + # RMagick 1 would be fine for ruby 1.8 but Bundler does not support + # different requirements for the same gem on different platforms + gem "rmagick", ">= 2.0.0" + end +end + +platforms :jruby do + # jruby-openssl is bundled with JRuby 1.7.0 + gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0' + gem "activerecord-jdbc-adapter", "~> 1.3.2" +end + +# Include database gems for the adapters found in the database +# configuration file +require 'erb' +require 'yaml' +database_file = File.join(File.dirname(__FILE__), "config/database.yml") +if File.exist?(database_file) + database_config = YAML::load(ERB.new(IO.read(database_file)).result) + adapters = database_config.values.map {|c| c['adapter']}.compact.uniq + if adapters.any? + adapters.each do |adapter| + case adapter + when 'mysql2' + gem "mysql2", "~> 0.3.11", :platforms => [:mri, :mingw] + gem "activerecord-jdbcmysql-adapter", :platforms => :jruby + when 'mysql' + gem "mysql", "~> 2.8.1", :platforms => [:mri, :mingw] + gem "activerecord-jdbcmysql-adapter", :platforms => :jruby + when /postgresql/ + gem "pg", ">= 0.11.0", :platforms => [:mri, :mingw] + gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby + when /sqlite3/ + gem "sqlite3", :platforms => [:mri, :mingw] + gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby + when /sqlserver/ + gem "tiny_tds", "~> 0.5.1", :platforms => [:mri, :mingw] + gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw] + else + warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems") + end + end + else + warn("No adapter found in config/database.yml, please configure it first") + end +else + warn("Please configure your config/database.yml first") +end + +group :development do + gem "rdoc", ">= 2.4.2" + gem "yard" +end + +group :test do + gem "shoulda", "~> 3.3.2" + gem "mocha", ">= 0.14", :require => 'mocha/api' + if RUBY_VERSION >= '1.9.3' + gem "capybara", "~> 2.1.0" + gem "selenium-webdriver" + gem "database_cleaner" + end +end + +local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") +if File.exists?(local_gemfile) + puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v` + instance_eval File.read(local_gemfile) +end + +# Load plugins' Gemfiles +Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file| + puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v` + #TODO: switch to "eval_gemfile file" when bundler >= 1.2.0 will be required (rails 4) + instance_eval File.read(file), file +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1c/1ca8f284dbc1327ae70fe2ea5473249d28c671c0.svn-base --- a/.svn/pristine/1c/1ca8f284dbc1327ae70fe2ea5473249d28c671c0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class NewsTest < ActiveSupport::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news - - def valid_news - { :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.find(:first) } - end - - def setup - end - - def test_create_should_send_email_notification - ActionMailer::Base.deliveries.clear - news = Project.find(1).news.new(valid_news) - - with_settings :notified_events => %w(news_added) do - assert news.save - end - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_should_include_news_for_projects_with_news_enabled - project = projects(:projects_001) - assert project.enabled_modules.any?{ |em| em.name == 'news' } - - # News.latest should return news from projects_001 - assert News.latest.any? { |news| news.project == project } - end - - def test_should_not_include_news_for_projects_with_news_disabled - EnabledModule.delete_all(["project_id = ? AND name = ?", 2, 'news']) - project = Project.find(2) - - # Add a piece of news to the project - news = project.news.create(valid_news) - - # News.latest should not return that new piece of news - assert News.latest.include?(news) == false - end - - def test_should_only_include_news_from_projects_visibly_to_the_user - assert News.latest(User.anonymous).all? { |news| news.project.is_public? } - end - - def test_should_limit_the_amount_of_returned_news - # Make sure we have a bunch of news stories - 10.times { projects(:projects_001).news.create(valid_news) } - assert_equal 2, News.latest(users(:users_002), 2).size - assert_equal 6, News.latest(users(:users_002), 6).size - end - - def test_should_return_5_news_stories_by_default - # Make sure we have a bunch of news stories - 10.times { projects(:projects_001).news.create(valid_news) } - assert_equal 5, News.latest(users(:users_004)).size - end - - def test_attachments_should_be_visible - assert News.find(1).attachments_visible?(User.anonymous) - end - - def test_attachments_should_be_deletable_with_manage_news_permission - manager = User.find(2) - assert News.find(1).attachments_deletable?(manager) - end - - def test_attachments_should_not_be_deletable_without_manage_news_permission - manager = User.find(2) - Role.find_by_name('Manager').remove_permission!(:manage_news) - assert !News.find(1).attachments_deletable?(manager) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1c/1ce7c095bdee57845b24106ac69ceda35b831a10.svn-base --- a/.svn/pristine/1c/1ce7c095bdee57845b24106ac69ceda35b831a10.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -

    <%= link_to l(:label_user_plural), users_path %> » <%=l(:label_user_new)%>

    - -<%= labelled_form_for @user do |f| %> - <%= render :partial => 'form', :locals => { :f => f } %> - <% if email_delivery_enabled? %> -

    - <% end %> -

    - <%= submit_tag l(:button_create) %> - <%= submit_tag l(:button_create_and_continue), :name => 'continue' %> -

    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1d/1d1a40d68d66d098f38161f4b90f0e887e1b73d9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1d/1d1a40d68d66d098f38161f4b90f0e887e1b73d9.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,15 @@ +class AddUniqueIndexOnTokensValue < ActiveRecord::Migration + def up + say_with_time "Adding unique index on tokens, this may take some time..." do + # Just in case + duplicates = Token.connection.select_values("SELECT value FROM #{Token.table_name} GROUP BY value HAVING COUNT(id) > 1") + Token.where(:value => duplicates).delete_all + + add_index :tokens, :value, :unique => true, :name => 'tokens_value' + end + end + + def down + remove_index :tokens, :name => 'tokens_value' + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1d/1d23ef14041c621865426d5030ff2b1bdf3b4a55.svn-base --- a/.svn/pristine/1d/1d23ef14041c621865426d5030ff2b1bdf3b4a55.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,291 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'redmine/scm/adapters/abstract_adapter' -require 'uri' - -module Redmine - module Scm - module Adapters - class SubversionAdapter < AbstractAdapter - - # SVN executable name - SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn" - - class << self - def client_command - @@bin ||= SVN_BIN - end - - def sq_bin - @@sq_bin ||= shell_quote_command - end - - def client_version - @@client_version ||= (svn_binary_version || []) - end - - def client_available - # --xml options are introduced in 1.3. - # http://subversion.apache.org/docs/release-notes/1.3.html - client_version_above?([1, 3]) - end - - def svn_binary_version - scm_version = scm_version_from_command_line.dup - if scm_version.respond_to?(:force_encoding) - scm_version.force_encoding('ASCII-8BIT') - end - if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) - m[2].scan(%r{\d+}).collect(&:to_i) - end - end - - def scm_version_from_command_line - shellout("#{sq_bin} --version") { |io| io.read }.to_s - end - end - - # Get info about the svn repository - def info - cmd = "#{self.class.sq_bin} info --xml #{target}" - cmd << credentials_string - info = nil - shellout(cmd) do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - doc = parse_xml(output) - # root_url = doc.elements["info/entry/repository/root"].text - info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'], - :lastrev => Revision.new({ - :identifier => doc['info']['entry']['commit']['revision'], - :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime, - :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "") - }) - }) - rescue - end - end - return nil if $? && $?.exitstatus != 0 - info - rescue CommandFailed - return nil - end - - # Returns an Entries collection - # or nil if the given path doesn't exist in the repository - def entries(path=nil, identifier=nil, options={}) - path ||= '' - identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" - entries = Entries.new - cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}" - cmd << credentials_string - shellout(cmd) do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - doc = parse_xml(output) - each_xml_element(doc['lists']['list'], 'entry') do |entry| - commit = entry['commit'] - commit_date = commit['date'] - # Skip directory if there is no commit date (usually that - # means that we don't have read access to it) - next if entry['kind'] == 'dir' && commit_date.nil? - name = entry['name']['__content__'] - entries << Entry.new({:name => URI.unescape(name), - :path => ((path.empty? ? "" : "#{path}/") + name), - :kind => entry['kind'], - :size => ((s = entry['size']) ? s['__content__'].to_i : nil), - :lastrev => Revision.new({ - :identifier => commit['revision'], - :time => Time.parse(commit_date['__content__'].to_s).localtime, - :author => ((a = commit['author']) ? a['__content__'] : nil) - }) - }) - end - rescue Exception => e - logger.error("Error parsing svn output: #{e.message}") - logger.error("Output was:\n #{output}") - end - end - return nil if $? && $?.exitstatus != 0 - logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug? - entries.sort_by_name - end - - def properties(path, identifier=nil) - # proplist xml output supported in svn 1.5.0 and higher - return nil unless self.class.client_version_above?([1, 5, 0]) - - identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" - cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}" - cmd << credentials_string - properties = {} - shellout(cmd) do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - doc = parse_xml(output) - each_xml_element(doc['properties']['target'], 'property') do |property| - properties[ property['name'] ] = property['__content__'].to_s - end - rescue - end - end - return nil if $? && $?.exitstatus != 0 - properties - end - - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) - path ||= '' - identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD" - identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1 - revisions = Revisions.new - cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}" - cmd << credentials_string - cmd << " --verbose " if options[:with_paths] - cmd << " --limit #{options[:limit].to_i}" if options[:limit] - cmd << ' ' + target(path) - shellout(cmd) do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - doc = parse_xml(output) - each_xml_element(doc['log'], 'logentry') do |logentry| - paths = [] - each_xml_element(logentry['paths'], 'path') do |path| - paths << {:action => path['action'], - :path => path['__content__'], - :from_path => path['copyfrom-path'], - :from_revision => path['copyfrom-rev'] - } - end if logentry['paths'] && logentry['paths']['path'] - paths.sort! { |x,y| x[:path] <=> y[:path] } - - revisions << Revision.new({:identifier => logentry['revision'], - :author => (logentry['author'] ? logentry['author']['__content__'] : ""), - :time => Time.parse(logentry['date']['__content__'].to_s).localtime, - :message => logentry['msg']['__content__'], - :paths => paths - }) - end - rescue - end - end - return nil if $? && $?.exitstatus != 0 - revisions - end - - def diff(path, identifier_from, identifier_to=nil) - path ||= '' - identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : '' - - identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1) - - cmd = "#{self.class.sq_bin} diff -r " - cmd << "#{identifier_to}:" - cmd << "#{identifier_from}" - cmd << " #{target(path)}@#{identifier_from}" - cmd << credentials_string - diff = [] - shellout(cmd) do |io| - io.each_line do |line| - diff << line - end - end - return nil if $? && $?.exitstatus != 0 - diff - end - - def cat(path, identifier=nil) - identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" - cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}" - cmd << credentials_string - cat = nil - shellout(cmd) do |io| - io.binmode - cat = io.read - end - return nil if $? && $?.exitstatus != 0 - cat - end - - def annotate(path, identifier=nil) - identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" - cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}" - cmd << credentials_string - blame = Annotate.new - shellout(cmd) do |io| - io.each_line do |line| - next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$} - rev = $1 - blame.add_line($3.rstrip, - Revision.new( - :identifier => rev, - :revision => rev, - :author => $2.strip - )) - end - end - return nil if $? && $?.exitstatus != 0 - blame - end - - private - - def credentials_string - str = '' - str << " --username #{shell_quote(@login)}" unless @login.blank? - str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank? - str << " --no-auth-cache --non-interactive" - str - end - - # Helper that iterates over the child elements of a xml node - # MiniXml returns a hash when a single child is found - # or an array of hashes for multiple children - def each_xml_element(node, name) - if node && node[name] - if node[name].is_a?(Hash) - yield node[name] - else - node[name].each do |element| - yield element - end - end - end - end - - def target(path = '') - base = path.match(/^\//) ? root_url : url - uri = "#{base}/#{path}" - uri = URI.escape(URI.escape(uri), '[]') - shell_quote(uri.gsub(/[?<>\*]/, '')) - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1d/1d4c356684e7bc2f59df7e93707ab136f908ac26.svn-base --- a/.svn/pristine/1d/1d4c356684e7bc2f59df7e93707ab136f908ac26.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingAutoCompletesTest < ActionController::IntegrationTest - def test_auto_completes - assert_routing( - { :method => 'get', :path => "/issues/auto_complete" }, - { :controller => 'auto_completes', :action => 'issues' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1d/1d70cf0be89de5fcd0a7d79fde026912fb2a0dcb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1d/1d70cf0be89de5fcd0a7d79fde026912fb2a0dcb.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1104 @@ +# Serbian translations for Redmine +# by Vladimir Medarović (vlada@medarovic.com) +sr: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d.%m.%Y." + short: "%e %b" + long: "%B %e, %Y" + + day_names: [недеља, понедељак, уторак, Ñреда, четвртак, петак, Ñубота] + abbr_day_names: [нед, пон, уто, Ñре, чет, пет, Ñуб] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, јануар, фебруар, март, април, мај, јун, јул, авгуÑÑ‚, Ñептембар, октобар, новембар, децембар] + abbr_month_names: [~, јан, феб, мар, апр, мај, јун, јул, авг, Ñеп, окт, нов, дец] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%d.%m.%Y. у %H:%M" + time: "%H:%M" + short: "%d. %b у %H:%M" + long: "%d. %B %Y у %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "пола минута" + less_than_x_seconds: + one: "мање од једне Ñекунде" + other: "мање од %{count} Ñек." + x_seconds: + one: "једна Ñекунда" + other: "%{count} Ñек." + less_than_x_minutes: + one: "мање од минута" + other: "мање од %{count} мин." + x_minutes: + one: "један минут" + other: "%{count} мин." + about_x_hours: + one: "приближно један Ñат" + other: "приближно %{count} Ñати" + x_hours: + one: "1 Ñат" + other: "%{count} Ñати" + x_days: + one: "један дан" + other: "%{count} дана" + about_x_months: + one: "приближно један меÑец" + other: "приближно %{count} меÑеци" + x_months: + one: "један меÑец" + other: "%{count} меÑеци" + about_x_years: + one: "приближно годину дана" + other: "приближно %{count} год." + over_x_years: + one: "преко годину дана" + other: "преко %{count} год." + almost_x_years: + one: "Ñкоро годину дана" + other: "Ñкоро %{count} год." + + number: + format: + separator: "," + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + +# Used in array.to_sentence. + support: + array: + sentence_connector: "и" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "није укључен у ÑпиÑак" + exclusion: "је резервиÑан" + invalid: "је неиÑправан" + confirmation: "потврда не одговара" + accepted: "мора бити прихваћен" + empty: "не може бити празно" + blank: "не може бити празно" + too_long: "је предугачка (макÑимум знакова је %{count})" + too_short: "је прекратка (минимум знакова је %{count})" + wrong_length: "је погрешне дужине (број знакова мора бити %{count})" + taken: "је већ у употреби" + not_a_number: "није број" + not_a_date: "није иÑправан датум" + greater_than: "мора бити већи од %{count}" + greater_than_or_equal_to: "мора бити већи или једнак %{count}" + equal_to: "мора бити једнак %{count}" + less_than: "мора бити мањи од %{count}" + less_than_or_equal_to: "мора бити мањи или једнак %{count}" + odd: "мора бити паран" + even: "мора бити непаран" + greater_than_start_date: "мора бити већи од почетног датума" + not_same_project: "не припада иÑтом пројекту" + circular_dependency: "Ова веза ће Ñтворити кружну референцу" + cant_link_an_issue_with_a_descendant: "Проблем не може бити повезан Ñа једним од Ñвојих подзадатака" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Молим одаберите + + general_text_No: 'Ðе' + general_text_Yes: 'Да' + general_text_no: 'не' + general_text_yes: 'да' + general_lang_name: 'Serbian Cyrillic (СрпÑки)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Ðалог је уÑпешно ажуриран. + notice_account_invalid_creditentials: ÐеиÑправно кориÑничко име или лозинка. + notice_account_password_updated: Лозинка је уÑпешно ажурирана. + notice_account_wrong_password: Погрешна лозинка + notice_account_register_done: КориÑнички налог је уÑпешно креиран. Кликните на линк који Ñте добили у е-поруци за активацију. + notice_account_unknown_email: Ðепознат кориÑник. + notice_can_t_change_password: Овај кориÑнички налог за потврду идентитета кориÑти Ñпољни извор. Ðемогуће је променити лозинку. + notice_account_lost_email_sent: ПоÑлата вам је е-порука Ñа упутÑтвом за избор нове лозинке + notice_account_activated: Ваш кориÑнички налог је активиран. Сада Ñе можете пријавити. + notice_successful_create: УÑпешно креирање. + notice_successful_update: УÑпешно ажурирање. + notice_successful_delete: УÑпешно бриÑање. + notice_successful_connection: УÑпешно повезивање. + notice_file_not_found: Страна којој желите приÑтупити не поÑтоји или је уклоњена. + notice_locking_conflict: Податак је ажуриран од Ñтране другог кориÑника. + notice_not_authorized: ÐиÑте овлашћени за приÑтуп овој Ñтрани. + notice_email_sent: "E-порука је поÑлата на %{value}" + notice_email_error: "Догодила Ñе грешка приликом Ñлања е-поруке (%{value})" + notice_feeds_access_key_reseted: Ваш Atom приÑтупни кључ је поништен. + notice_api_access_key_reseted: Ваш API приÑтупни кључ је поништен. + notice_failed_to_save_issues: "ÐеуÑпешно Ñнимање %{count} проблема од %{total} одабраних: %{ids}." + notice_failed_to_save_members: "ÐеуÑпешно Ñнимање члана(ова): %{errors}." + notice_no_issue_selected: "Ðи један проблем није одабран! Молимо, одаберите проблем који желите да мењате." + notice_account_pending: "Ваш налог је креиран и чека на одобрење админиÑтратора." + notice_default_data_loaded: Подразумевано конфигуриÑање је уÑпешно учитано. + notice_unable_delete_version: Верзију је немогуће избриÑати. + notice_unable_delete_time_entry: Ставку евиденције времена је немогуће избриÑати. + notice_issue_done_ratios_updated: ÐžÐ´Ð½Ð¾Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ… проблема је ажуриран. + + error_can_t_load_default_data: "Подразумевано конфигуриÑање је немогуће учитати: %{value}" + error_scm_not_found: "Ставка или иÑправка ниÑу пронађене у Ñпремишту." + error_scm_command_failed: "Грешка Ñе јавила приликом покушаја приÑтупа Ñпремишту: %{value}" + error_scm_annotate: "Ставка не поÑтоји или не може бити означена." + error_issue_not_found_in_project: 'Проблем није пронађен или не припада овом пројекту.' + error_no_tracker_in_project: 'Ðи једно праћење није повезано Ñа овим пројектом. Молимо проверите подешавања пројекта.' + error_no_default_issue_status: 'Подразумевани ÑÑ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° није дефиниÑан. Молимо проверите ваше конфигуриÑање (идите на "ÐдминиÑтрација -> СтатуÑи проблема").' + error_can_not_delete_custom_field: Ðемогуће је избриÑати прилагођено поље + error_can_not_delete_tracker: "Ово праћење Ñадржи проблеме и не може бити обриÑано." + error_can_not_remove_role: "Ова улога је у употреби и не може бити обриÑана." + error_can_not_reopen_issue_on_closed_version: 'Проблем додељен затвореној верзији не може бити поново отворен' + error_can_not_archive_project: Овај пројекат Ñе не може архивирати + error_issue_done_ratios_not_updated: "ÐžÐ´Ð½Ð¾Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ… проблема није ажуриран." + error_workflow_copy_source: 'Молимо одаберите изворно праћење или улогу' + error_workflow_copy_target: 'Молимо одаберите одредишно праћење и улогу' + error_unable_delete_issue_status: 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° је немогуће обриÑати' + error_unable_to_connect: "Повезивање Ñа (%{value}) је немогуће" + warning_attachments_not_saved: "%{count} датотека не може бити Ñнимљена." + + mail_subject_lost_password: "Ваша %{value} лозинка" + mail_body_lost_password: 'За промену ваше лозинке, кликните на Ñледећи линк:' + mail_subject_register: "Ðктивација вашег %{value} налога" + mail_body_register: 'За активацију вашег налога, кликните на Ñледећи линк:' + mail_body_account_information_external: "Ваш налог %{value} можете кориÑтити за пријаву." + mail_body_account_information: Информације о вашем налогу + mail_subject_account_activation_request: "Захтев за активацију налога %{value}" + mail_body_account_activation_request: "Ðови кориÑник (%{value}) је региÑтрован. Ðалог чека на ваше одобрење:" + mail_subject_reminder: "%{count} проблема доÑпева наредних %{days} дана" + mail_body_reminder: "%{count} проблема додељених вама доÑпева у наредних %{days} дана:" + mail_subject_wiki_content_added: "Wiki Ñтраница '%{id}' је додата" + mail_body_wiki_content_added: "%{author} је додао wiki Ñтраницу '%{id}'." + mail_subject_wiki_content_updated: "Wiki Ñтраница '%{id}' је ажурирана" + mail_body_wiki_content_updated: "%{author} је ажурирао wiki Ñтраницу '%{id}'." + + + field_name: Ðазив + field_description: ÐžÐ¿Ð¸Ñ + field_summary: Резиме + field_is_required: Обавезно + field_firstname: Име + field_lastname: Презиме + field_mail: Е-адреÑа + field_filename: Датотека + field_filesize: Величина + field_downloads: Преузимања + field_author: Ðутор + field_created_on: Креирано + field_updated_on: Ðжурирано + field_field_format: Формат + field_is_for_all: За Ñве пројекте + field_possible_values: Могуће вредноÑти + field_regexp: Регуларан израз + field_min_length: Минимална дужина + field_max_length: МакÑимална дужина + field_value: ВредноÑÑ‚ + field_category: Категорија + field_title: ÐаÑлов + field_project: Пројекат + field_issue: Проблем + field_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ + field_notes: Белешке + field_is_closed: Затворен проблем + field_is_default: Подразумевана вредноÑÑ‚ + field_tracker: Праћење + field_subject: Предмет + field_due_date: Крајњи рок + field_assigned_to: Додељено + field_priority: Приоритет + field_fixed_version: Одредишна верзија + field_user: КориÑник + field_principal: Главни + field_role: Улога + field_homepage: Почетна Ñтраница + field_is_public: Јавно објављивање + field_parent: Потпројекат од + field_is_in_roadmap: Проблеми приказани у плану рада + field_login: КориÑничко име + field_mail_notification: Обавештења путем е-поште + field_admin: ÐдминиÑтратор + field_last_login_on: ПоÑледње повезивање + field_language: Језик + field_effective_date: Датум + field_password: Лозинка + field_new_password: Ðова лозинка + field_password_confirmation: Потврда лозинке + field_version: Верзија + field_type: Тип + field_host: Главни рачунар + field_port: Порт + field_account: КориÑнички налог + field_base_dn: Базни DN + field_attr_login: Ðтрибут пријављивања + field_attr_firstname: Ðтрибут имена + field_attr_lastname: Ðтрибут презимена + field_attr_mail: Ðтрибут е-адреÑе + field_onthefly: Креирање кориÑника у току рада + field_start_date: Почетак + field_done_ratio: "% урађено" + field_auth_source: Режим потврде идентитета + field_hide_mail: Сакриј моју е-адреÑу + field_comments: Коментар + field_url: URL + field_start_page: Почетна Ñтраница + field_subproject: Потпројекат + field_hours: Ñати + field_activity: ÐктивноÑÑ‚ + field_spent_on: Датум + field_identifier: Идентификатор + field_is_filter: Употреби као филтер + field_issue_to: Сродни проблеми + field_delay: Кашњење + field_assignable: Проблем може бити додељен овој улози + field_redirect_existing_links: ПреуÑмери поÑтојеће везе + field_estimated_hours: Протекло време + field_column_names: Колоне + field_time_zone: ВременÑка зона + field_searchable: Може да Ñе претражује + field_default_value: Подразумевана вредноÑÑ‚ + field_comments_sorting: Прикажи коментаре + field_parent_title: Матична Ñтраница + field_editable: Изменљиво + field_watcher: ПоÑматрач + field_identity_url: OpenID URL + field_content: Садржај + field_group_by: ГрупиÑање резултата по + field_sharing: Дељење + field_parent_issue: Матични задатак + + setting_app_title: ÐаÑлов апликације + setting_app_subtitle: ПоднаÑлов апликације + setting_welcome_text: ТекÑÑ‚ добродошлице + setting_default_language: Подразумевани језик + setting_login_required: Обавезна потврда идентитета + setting_self_registration: СаморегиÑтрација + setting_attachment_max_size: МакÑ. величина приложене датотеке + setting_issues_export_limit: Ограничење извоза „проблема“ + setting_mail_from: Е-адреÑа пошиљаоца + setting_bcc_recipients: Примаоци „Bcc“ копије + setting_plain_text_mail: Порука Ñа чиÑтим текÑтом (без HTML-а) + setting_host_name: Путања и назив главног рачунара + setting_text_formatting: Обликовање текÑта + setting_wiki_compression: КомпреÑија Wiki иÑторије + setting_feeds_limit: Ограничење Ñадржаја извора веÑти + setting_default_projects_public: Подразумева Ñе јавно приказивање нових пројеката + setting_autofetch_changesets: Извршавање аутоматÑког преузимања + setting_sys_api_enabled: Омогућавање WS за управљање Ñпремиштем + setting_commit_ref_keywords: Референцирање кључних речи + setting_commit_fix_keywords: Поправљање кључних речи + setting_autologin: ÐутоматÑка пријава + setting_date_format: Формат датума + setting_time_format: Формат времена + setting_cross_project_issue_relations: Дозволи повезивање проблема из унакрÑних пројеката + setting_issue_list_default_columns: Подразумеване колоне приказане на ÑпиÑку проблема + setting_emails_footer: Подножје Ñтранице е-поруке + setting_protocol: Протокол + setting_per_page_options: Опције приказа објеката по Ñтраници + setting_user_format: Формат приказа кориÑника + setting_activity_days_default: Број дана приказаних на пројектној активноÑти + setting_display_subprojects_issues: Приказуј проблеме из потпројеката на главном пројекту, уколико није другачије наведено + setting_enabled_scm: Омогућавање SCM + setting_mail_handler_body_delimiters: "Скраћивање е-поруке након једне од ових линија" + setting_mail_handler_api_enabled: Омогућавање WS долазне е-поруке + setting_mail_handler_api_key: API кључ + setting_sequential_project_identifiers: ГенериÑање Ñеквенцијалног имена пројекта + setting_gravatar_enabled: КориÑти Gravatar кориÑничке иконе + setting_gravatar_default: Подразумевана Gravatar Ñлика + setting_diff_max_lines_displayed: МакÑ. број приказаних различитих линија + setting_file_max_size_displayed: МакÑ. величина текÑÑ‚. датотека приказаних уметнуто + setting_repository_log_display_limit: МакÑ. број ревизија приказаних у датотеци за евиденцију + setting_openid: Дозволи OpenID пријаву и региÑтрацију + setting_password_min_length: Минимална дужина лозинке + setting_new_project_user_role_id: Креатору пројекта (који није админиÑтратор) додељује је улога + setting_default_projects_modules: Подразумевано омогућени модули за нове пројекте + setting_issue_done_ratio: Израчунај Ð¾Ð´Ð½Ð¾Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ… проблема + setting_issue_done_ratio_issue_field: кориÑтећи поље проблема + setting_issue_done_ratio_issue_status: кориÑтећи ÑÑ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° + setting_start_of_week: Први дан у Ñедмици + setting_rest_api_enabled: Омогући REST web уÑлуге + setting_cache_formatted_text: Кеширање обрађеног текÑта + + permission_add_project: Креирање пројекта + permission_add_subprojects: Креирање потпојекта + permission_edit_project: Измена пројеката + permission_select_project_modules: Одабирање модула пројекта + permission_manage_members: Управљање члановима + permission_manage_project_activities: Управљање пројектним активноÑтима + permission_manage_versions: Управљање верзијама + permission_manage_categories: Управљање категоријама проблема + permission_view_issues: Преглед проблема + permission_add_issues: Додавање проблема + permission_edit_issues: Измена проблема + permission_manage_issue_relations: Управљање везама између проблема + permission_add_issue_notes: Додавање белешки + permission_edit_issue_notes: Измена белешки + permission_edit_own_issue_notes: Измена ÑопÑтвених белешки + permission_move_issues: Померање проблема + permission_delete_issues: БриÑање проблема + permission_manage_public_queries: Управљање јавним упитима + permission_save_queries: Снимање упита + permission_view_gantt: Прегледање Гантовог дијаграма + permission_view_calendar: Прегледање календара + permission_view_issue_watchers: Прегледање ÑпиÑка поÑматрача + permission_add_issue_watchers: Додавање поÑматрача + permission_delete_issue_watchers: БриÑање поÑматрача + permission_log_time: Бележење утрошеног времена + permission_view_time_entries: Прегледање утрошеног времена + permission_edit_time_entries: Измена утрошеног времена + permission_edit_own_time_entries: Измена ÑопÑтвеног утрошеног времена + permission_manage_news: Управљање веÑтима + permission_comment_news: КоментариÑање веÑти + permission_view_documents: Прегледање докумената + permission_manage_files: Управљање датотекама + permission_view_files: Прегледање датотека + permission_manage_wiki: Управљање wiki Ñтраницама + permission_rename_wiki_pages: Промена имена wiki Ñтраницама + permission_delete_wiki_pages: БриÑање wiki Ñтраница + permission_view_wiki_pages: Прегледање wiki Ñтраница + permission_view_wiki_edits: Прегледање wiki иÑторије + permission_edit_wiki_pages: Измена wiki Ñтраница + permission_delete_wiki_pages_attachments: БриÑање приложених датотека + permission_protect_wiki_pages: Заштита wiki Ñтраница + permission_manage_repository: Управљање Ñпремиштем + permission_browse_repository: Прегледање Ñпремишта + permission_view_changesets: Прегледање Ñкупа промена + permission_commit_access: Потврда приÑтупа + permission_manage_boards: Управљање форумима + permission_view_messages: Прегледање порука + permission_add_messages: Слање порука + permission_edit_messages: Измена порука + permission_edit_own_messages: Измена ÑопÑтвених порука + permission_delete_messages: БриÑање порука + permission_delete_own_messages: БриÑање ÑопÑтвених порука + permission_export_wiki_pages: Извоз wiki Ñтраница + permission_manage_subtasks: Управљање подзадацима + + project_module_issue_tracking: Праћење проблема + project_module_time_tracking: Праћење времена + project_module_news: ВеÑти + project_module_documents: Документи + project_module_files: Датотеке + project_module_wiki: Wiki + project_module_repository: Спремиште + project_module_boards: Форуми + + label_user: КориÑник + label_user_plural: КориÑници + label_user_new: Ðови кориÑник + label_user_anonymous: Ðнониман + label_project: Пројекат + label_project_new: Ðови пројекат + label_project_plural: Пројекти + label_x_projects: + zero: нема пројеката + one: један пројекат + other: "%{count} пројеката" + label_project_all: Сви пројекти + label_project_latest: ПоÑледњи пројекти + label_issue: Проблем + label_issue_new: Ðови проблем + label_issue_plural: Проблеми + label_issue_view_all: Приказ Ñвих проблема + label_issues_by: "Проблеми (%{value})" + label_issue_added: Проблем је додат + label_issue_updated: Проблем је ажуриран + label_document: Документ + label_document_new: Ðови документ + label_document_plural: Документи + label_document_added: Документ је додат + label_role: Улога + label_role_plural: Улоге + label_role_new: Ðова улога + label_role_and_permissions: Улоге и дозволе + label_member: Члан + label_member_new: Ðови члан + label_member_plural: Чланови + label_tracker: Праћење + label_tracker_plural: Праћења + label_tracker_new: Ðово праћење + label_workflow: Ток поÑла + label_issue_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° + label_issue_status_plural: СтатуÑи проблема + label_issue_status_new: Ðови ÑÑ‚Ð°Ñ‚ÑƒÑ + label_issue_category: Категорија проблема + label_issue_category_plural: Категорије проблема + label_issue_category_new: Ðова категорија + label_custom_field: Прилагођено поље + label_custom_field_plural: Прилагођена поља + label_custom_field_new: Ðово прилагођено поље + label_enumerations: Ðабројива лиÑта + label_enumeration_new: Ðова вредноÑÑ‚ + label_information: Информација + label_information_plural: Информације + label_please_login: Молимо, пријавите Ñе + label_register: РегиÑтрација + label_login_with_open_id_option: или пријава Ñа OpenID + label_password_lost: Изгубљена лозинка + label_home: Почетак + label_my_page: Моја Ñтраница + label_my_account: Мој налог + label_my_projects: Моји пројекти + label_my_page_block: My page block + label_administration: ÐдминиÑтрација + label_login: Пријава + label_logout: Одјава + label_help: Помоћ + label_reported_issues: Пријављени проблеми + label_assigned_to_me_issues: Проблеми додељени мени + label_last_login: ПоÑледње повезивање + label_registered_on: РегиÑтрован + label_activity: ÐктивноÑÑ‚ + label_overall_activity: Целокупна активноÑÑ‚ + label_user_activity: "ÐктивноÑÑ‚ кориÑника %{value}" + label_new: Ðово + label_logged_as: Пријављени Ñте као + label_environment: Окружење + label_authentication: Потврда идентитета + label_auth_source: Режим потврде идентитета + label_auth_source_new: Ðови режим потврде идентитета + label_auth_source_plural: Режими потврде идентитета + label_subproject_plural: Потпројекти + label_subproject_new: Ðови потпројекат + label_and_its_subprojects: "%{value} и његови потпројекти" + label_min_max_length: Мин. - МакÑ. дужина + label_list: СпиÑак + label_date: Датум + label_integer: Цео број + label_float: Са покретним зарезом + label_boolean: Логички оператор + label_string: ТекÑÑ‚ + label_text: Дуги текÑÑ‚ + label_attribute: ОÑобина + label_attribute_plural: ОÑобине + label_no_data: Ðема података за приказивање + label_change_status: Промена ÑтатуÑа + label_history: ИÑторија + label_attachment: Датотека + label_attachment_new: Ðова датотека + label_attachment_delete: БриÑање датотеке + label_attachment_plural: Датотеке + label_file_added: Датотека је додата + label_report: Извештај + label_report_plural: Извештаји + label_news: ВеÑти + label_news_new: Додавање веÑти + label_news_plural: ВеÑти + label_news_latest: ПоÑледње веÑти + label_news_view_all: Приказ Ñвих веÑти + label_news_added: ВеÑти Ñу додате + label_settings: Подешавања + label_overview: Преглед + label_version: Верзија + label_version_new: Ðова верзија + label_version_plural: Верзије + label_close_versions: Затвори завршене верзије + label_confirmation: Потврда + label_export_to: 'Такође доÑтупно и у варијанти:' + label_read: Читање... + label_public_projects: Јавни пројекти + label_open_issues: отворен + label_open_issues_plural: отворених + label_closed_issues: затворен + label_closed_issues_plural: затворених + label_x_open_issues_abbr_on_total: + zero: 0 отворених / %{total} + one: 1 отворен / %{total} + other: "%{count} отворених / %{total}" + label_x_open_issues_abbr: + zero: 0 отворених + one: 1 отворен + other: "%{count} отворених" + label_x_closed_issues_abbr: + zero: 0 затворених + one: 1 затворен + other: "%{count} затворених" + label_total: Укупно + label_permissions: Дозволе + label_current_status: Тренутни ÑÑ‚Ð°Ñ‚ÑƒÑ + label_new_statuses_allowed: Ðови ÑтатуÑи дозвољени + label_all: Ñви + label_none: ниједан + label_nobody: никоме + label_next: Следеће + label_previous: Претходно + label_used_by: КориÑтио + label_details: Детаљи + label_add_note: Додај белешку + label_per_page: По Ñтрани + label_calendar: Календар + label_months_from: меÑеци од + label_gantt: Гантов дијаграм + label_internal: Унутрашњи + label_last_changes: "поÑледњих %{count} промена" + label_change_view_all: Прикажи Ñве промене + label_personalize_page: ПерÑонализуј ову Ñтрану + label_comment: Коментар + label_comment_plural: Коментари + label_x_comments: + zero: без коментара + one: један коментар + other: "%{count} коментара" + label_comment_add: Додај коментар + label_comment_added: Коментар додат + label_comment_delete: Обриши коментаре + label_query: Прилагођен упит + label_query_plural: Прилагођени упити + label_query_new: Ðови упит + label_filter_add: Додавање филтера + label_filter_plural: Филтери + label_equals: је + label_not_equals: није + label_in_less_than: мање од + label_in_more_than: више од + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: у + label_today: Ð´Ð°Ð½Ð°Ñ + label_all_time: Ñве време + label_yesterday: јуче + label_this_week: ове Ñедмице + label_last_week: поÑледње Ñедмице + label_last_n_days: "поÑледњих %{count} дана" + label_this_month: овог меÑеца + label_last_month: поÑледњег меÑеца + label_this_year: ове године + label_date_range: ВременÑки период + label_less_than_ago: пре мање од неколико дана + label_more_than_ago: пре више од неколико дана + label_ago: пре неколико дана + label_contains: Ñадржи + label_not_contains: не Ñадржи + label_day_plural: дана + label_repository: Спремиште + label_repository_plural: Спремишта + label_browse: Прегледање + label_branch: Грана + label_tag: Ознака + label_revision: Ревизија + label_revision_plural: Ревизије + label_revision_id: "Ревизија %{value}" + label_associated_revisions: Придружене ревизије + label_added: додато + label_modified: промењено + label_copied: копирано + label_renamed: преименовано + label_deleted: избриÑано + label_latest_revision: ПоÑледња ревизија + label_latest_revision_plural: ПоÑледње ревизије + label_view_revisions: Преглед ревизија + label_view_all_revisions: Преглед Ñвих ревизија + label_max_size: МакÑимална величина + label_sort_highest: Премештање на врх + label_sort_higher: Премештање на горе + label_sort_lower: Премештање на доле + label_sort_lowest: Премештање на дно + label_roadmap: План рада + label_roadmap_due_in: "ДоÑпева %{value}" + label_roadmap_overdue: "%{value} најкаÑније" + label_roadmap_no_issues: Ðема проблема за ову верзију + label_search: Претрага + label_result_plural: Резултати + label_all_words: Све речи + label_wiki: Wiki + label_wiki_edit: Wiki измена + label_wiki_edit_plural: Wiki измене + label_wiki_page: Wiki Ñтраница + label_wiki_page_plural: Wiki Ñтранице + label_index_by_title: ИндекÑирање по наÑлову + label_index_by_date: ИндекÑирање по датуму + label_current_version: Тренутна верзија + label_preview: Преглед + label_feed_plural: Извори веÑти + label_changes_details: Детаљи Ñвих промена + label_issue_tracking: Праћење проблема + label_spent_time: Утрошено време + label_overall_spent_time: Целокупно утрошено време + label_f_hour: "%{value} Ñат" + label_f_hour_plural: "%{value} Ñати" + label_time_tracking: Праћење времена + label_change_plural: Промене + label_statistics: СтатиÑтика + label_commits_per_month: Извршења меÑечно + label_commits_per_author: Извршења по аутору + label_view_diff: Погледај разлике + label_diff_inline: унутра + label_diff_side_by_side: упоредо + label_options: Опције + label_copy_workflow_from: Копирање тока поÑла од + label_permissions_report: Извештај о дозволама + label_watched_issues: ПоÑматрани проблеми + label_related_issues: Сродни проблеми + label_applied_status: Примењени ÑтатуÑи + label_loading: Учитавање... + label_relation_new: Ðова релација + label_relation_delete: БриÑање релације + label_relates_to: Ñродних Ñа + label_duplicates: дуплираних + label_duplicated_by: дуплираних од + label_blocks: одбијених + label_blocked_by: одбијених од + label_precedes: претходи + label_follows: праћених + label_end_to_start: од краја до почетка + label_end_to_end: од краја до краја + label_start_to_start: од почетка до почетка + label_start_to_end: од почетка до краја + label_stay_logged_in: ОÑтаните пријављени + label_disabled: онемогућено + label_show_completed_versions: Приказивање завршене верзије + label_me: мени + label_board: Форум + label_board_new: Ðови форум + label_board_plural: Форуми + label_board_locked: Закључана + label_board_sticky: Лепљива + label_topic_plural: Теме + label_message_plural: Поруке + label_message_last: ПоÑледња порука + label_message_new: Ðова порука + label_message_posted: Порука је додата + label_reply_plural: Одговори + label_send_information: Пошаљи кориÑнику детаље налога + label_year: Година + label_month: МеÑец + label_week: Седмица + label_date_from: Шаље + label_date_to: Прима + label_language_based: Базирано на језику кориÑника + label_sort_by: "Сортирано по %{value}" + label_send_test_email: Слање пробне е-поруке + label_feeds_access_key: Atom приÑтупни кључ + label_missing_feeds_access_key: Atom приÑтупни кључ недоÑтаје + label_feeds_access_key_created_on: "Atom приÑтупни кључ је направљен пре %{value}" + label_module_plural: Модули + label_added_time_by: "Додао %{author} пре %{age}" + label_updated_time_by: "Ðжурирао %{author} пре %{age}" + label_updated_time: "Ðжурирано пре %{value}" + label_jump_to_a_project: Скок на пројекат... + label_file_plural: Датотеке + label_changeset_plural: Скупови промена + label_default_columns: Подразумеване колоне + label_no_change_option: (Без промена) + label_bulk_edit_selected_issues: Групна измена одабраних проблема + label_theme: Тема + label_default: Подразумевано + label_search_titles_only: Претражуј Ñамо наÑлове + label_user_mail_option_all: "За било који догађај на Ñвим мојим пројектима" + label_user_mail_option_selected: "За било који догађај на Ñамо одабраним пројектима..." + label_user_mail_no_self_notified: "Ðе желим бити обавештаван за промене које Ñам правим" + label_registration_activation_by_email: активација налога путем е-поруке + label_registration_manual_activation: ручна активација налога + label_registration_automatic_activation: аутоматÑка активација налога + label_display_per_page: "Број Ñтавки по Ñтраници: %{value}" + label_age: СтароÑÑ‚ + label_change_properties: Промени ÑвојÑтва + label_general: Општи + label_more: Више + label_scm: SCM + label_plugins: Додатне компоненте + label_ldap_authentication: LDAP потврда идентитета + label_downloads_abbr: D/L + label_optional_description: Опционо Ð¾Ð¿Ð¸Ñ + label_add_another_file: Додај још једну датотеку + label_preferences: Подешавања + label_chronological_order: по хронолошком редоÑледу + label_reverse_chronological_order: по обрнутом хронолошком редоÑледу + label_planning: Планирање + label_incoming_emails: Долазне е-поруке + label_generate_key: ГенериÑање кључа + label_issue_watchers: ПоÑматрачи + label_example: Пример + label_display: Приказ + label_sort: Сортирање + label_ascending: РаÑтући низ + label_descending: Опадајући низ + label_date_from_to: Од %{start} до %{end} + label_wiki_content_added: Wiki Ñтраница је додата + label_wiki_content_updated: Wiki Ñтраница је ажурирана + label_group: Група + label_group_plural: Групе + label_group_new: Ðова група + label_time_entry_plural: Утрошено време + label_version_sharing_none: Ðије дељено + label_version_sharing_descendants: Са потпројектима + label_version_sharing_hierarchy: Са хијерархијом пројекта + label_version_sharing_tree: Са Ñтаблом пројекта + label_version_sharing_system: Са Ñвим пројектима + label_update_issue_done_ratios: Ðжурирај Ð¾Ð´Ð½Ð¾Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ… проблема + label_copy_source: Извор + label_copy_target: Одредиште + label_copy_same_as_target: ИÑто као одредиште + label_display_used_statuses_only: Приказуј ÑтатуÑе коришћене Ñамо од Ñтране овог праћења + label_api_access_key: API приÑтупни кључ + label_missing_api_access_key: ÐедоÑтаје API приÑтупни кључ + label_api_access_key_created_on: "API приÑтупни кључ је креиран пре %{value}" + label_profile: Профил + label_subtask_plural: Подзадатак + label_project_copy_notifications: Пошаљи е-поруку Ñа обавештењем приликом копирања пројекта + + button_login: Пријава + button_submit: Пошаљи + button_save: Сними + button_check_all: Укључи Ñве + button_uncheck_all: ИÑкључи Ñве + button_delete: Избриши + button_create: Креирај + button_create_and_continue: Креирај и наÑтави + button_test: ТеÑÑ‚ + button_edit: Измени + button_add: Додај + button_change: Промени + button_apply: Примени + button_clear: Обриши + button_lock: Закључај + button_unlock: Откључај + button_download: Преузми + button_list: СпиÑак + button_view: Прикажи + button_move: Помери + button_move_and_follow: Помери и прати + button_back: Ðазад + button_cancel: Поништи + button_activate: Ðктивирај + button_sort: Сортирај + button_log_time: Евидентирај време + button_rollback: Повратак на ову верзију + button_watch: Прати + button_unwatch: Ðе прати више + button_reply: Одговори + button_archive: Ðрхивирај + button_unarchive: Врати из архиве + button_reset: Поништи + button_rename: Преименуј + button_change_password: Промени лозинку + button_copy: Копирај + button_copy_and_follow: Копирај и прати + button_annotate: Прибележи + button_update: Ðжурирај + button_configure: ПодеÑи + button_quote: Под наводницима + button_duplicate: Дуплирај + button_show: Прикажи + + status_active: активни + status_registered: региÑтровани + status_locked: закључани + + version_status_open: отворен + version_status_locked: закључан + version_status_closed: затворен + + field_active: Ðктиван + + text_select_mail_notifications: Одабери акције за које ће обавештење бити поÑлато путем е-поште. + text_regexp_info: нпр. ^[A-Z0-9]+$ + text_min_max_length_info: 0 значи без ограничења + text_project_destroy_confirmation: ЈеÑте ли Ñигурни да желите да избришете овај пројекат и Ñве припадајуће податке? + text_subprojects_destroy_warning: "Потпројекти: %{value} ће такође бити избриÑан." + text_workflow_edit: Одаберите улогу и праћење за измену тока поÑла + text_are_you_sure: ЈеÑте ли Ñигурни? + text_journal_changed: "%{label} промењен од %{old} у %{new}" + text_journal_set_to: "%{label} поÑтављен у %{value}" + text_journal_deleted: "%{label} избриÑано (%{old})" + text_journal_added: "%{label} %{value} додато" + text_tip_issue_begin_day: задатак почиње овог дана + text_tip_issue_end_day: задатак Ñе завршава овог дана + text_tip_issue_begin_end_day: задатак почиње и завршава овог дана + text_caracters_maximum: "Ðајвише %{count} знак(ова)." + text_caracters_minimum: "Број знакова мора бити најмање %{count}." + text_length_between: "Број знакова мора бити између %{min} и %{max}." + text_tracker_no_workflow: Ово праћење нема дефиниÑан ток поÑла + text_unallowed_characters: Ðедозвољени знакови + text_comma_separated: Дозвољене Ñу вишеÑтруке вредноÑти (одвојене зарезом). + text_line_separated: Дозвољене Ñу вишеÑтруке вредноÑти (један ред за Ñваку вредноÑÑ‚). + text_issues_ref_in_commit_messages: Референцирање и поправљање проблема у извршним порукама + text_issue_added: "%{author} је пријавио проблем %{id}." + text_issue_updated: "%{author} је ажурирао проблем %{id}." + text_wiki_destroy_confirmation: ЈеÑте ли Ñигурни да желите да обришете wiki и Ñав Ñадржај? + text_issue_category_destroy_question: "Ðеколико проблема (%{count}) је додељено овој категорији. Шта желите да урадите?" + text_issue_category_destroy_assignments: Уклони додељене категорије + text_issue_category_reassign_to: Додели поново проблеме овој категорији + text_user_mail_option: "За неизабране пројекте, добићете Ñамо обавештење о Ñтварима које пратите или Ñте укључени (нпр. проблеми чији Ñте ви аутор или заÑтупник)." + text_no_configuration_data: "Улоге, праћења, ÑтатуÑи проблема и тока поÑла још увек ниÑу подешени.\nПрепоручљиво је да учитате подразумевано конфигуриÑање. Измена је могућа након првог учитавања." + text_load_default_configuration: Учитај подразумевано конфигуриÑање + text_status_changed_by_changeset: "Примењено у Ñкупу Ñа променама %{value}." + text_issues_destroy_confirmation: 'ЈеÑте ли Ñигурни да желите да избришете одабране проблеме?' + text_select_project_modules: 'Одаберите модуле које желите омогућити за овај пројекат:' + text_default_administrator_account_changed: Подразумевани админиÑтраторÑки налог је промењен + text_file_repository_writable: ФаÑцикла приложених датотека је упиÑива + text_plugin_assets_writable: ФаÑцикла елемената додатних компоненти је упиÑива + text_rmagick_available: RMagick је доÑтупан (опционо) + text_destroy_time_entries_question: "%{hours} Ñати је пријављено за овај проблем који желите избриÑати. Шта желите да урадите?" + text_destroy_time_entries: Избриши пријављене Ñате + text_assign_time_entries_to_project: Додели пријављене Ñате пројекту + text_reassign_time_entries: 'Додели поново пријављене Ñате овом проблему:' + text_user_wrote: "%{value} је напиÑао:" + text_enumeration_destroy_question: "%{count} објекат(а) је додељено овој вредноÑти." + text_enumeration_category_reassign_to: 'Додели их поново овој вредноÑти:' + text_email_delivery_not_configured: "ИÑпорука е-порука није конфигуриÑана и обавештења Ñу онемогућена.\nПодеÑите ваш SMTP Ñервер у config/configuration.yml и покрените поново апликацију за њихово омогућавање." + text_repository_usernames_mapping: "Одаберите или ажурирајте Redmine кориÑнике мапирањем Ñваког кориÑничког имена пронађеног у евиденцији Ñпремишта.\nКориÑници Ñа иÑтим Redmine именом и именом Ñпремишта или е-адреÑом Ñу аутоматÑки мапирани." + text_diff_truncated: '... Ова разлика је иÑечена јер је доÑтигнута макÑимална величина приказа.' + text_custom_field_possible_values_info: 'Један ред за Ñваку вредноÑÑ‚' + text_wiki_page_destroy_question: "Ова Ñтраница има %{descendants} подређених Ñтраница и подÑтраница. Шта желите да урадите?" + text_wiki_page_nullify_children: "Задржи подређене Ñтранице као корене Ñтранице" + text_wiki_page_destroy_children: "Избриши подређене Ñтранице и Ñве њихове подÑтранице" + text_wiki_page_reassign_children: "Додели поново подређене Ñтранице овој матичној Ñтраници" + text_own_membership_delete_confirmation: "Ðакон уклањања појединих или Ñвих ваших дозвола нећете више моћи да уређујете овај пројекат.\nЖелите ли да наÑтавите?" + text_zoom_in: Увећај + text_zoom_out: Умањи + + default_role_manager: Менаџер + default_role_developer: Програмер + default_role_reporter: Извештач + default_tracker_bug: Грешка + default_tracker_feature: ФункционалноÑÑ‚ + default_tracker_support: Подршка + default_issue_status_new: Ðово + default_issue_status_in_progress: У току + default_issue_status_resolved: Решено + default_issue_status_feedback: Повратна информација + default_issue_status_closed: Затворено + default_issue_status_rejected: Одбијено + default_doc_category_user: КориÑничка документација + default_doc_category_tech: Техничка документација + default_priority_low: Ðизак + default_priority_normal: Ðормалан + default_priority_high: ВиÑок + default_priority_urgent: Хитно + default_priority_immediate: ÐепоÑредно + default_activity_design: Дизајн + default_activity_development: Развој + + enumeration_issue_priorities: Приоритети проблема + enumeration_doc_categories: Категорије документа + enumeration_activities: ÐктивноÑти (праћење времена) + enumeration_system_activity: СиÑтемÑка активноÑÑ‚ + + field_time_entries: Време евиденције + project_module_gantt: Гантов дијаграм + project_module_calendar: Календар + + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Кодирање извршних порука + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 Проблем + one: 1 Проблем + other: "%{count} Проблеми" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: Ñви + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Са потпројектима + label_cross_project_tree: Са Ñтаблом пројекта + label_cross_project_hierarchy: Са хијерархијом пројекта + label_cross_project_system: Са Ñвим пројектима + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Укупно + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1d/1d734add023a98369ca3ca65fe3091fb8e56d827.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1d/1d734add023a98369ca3ca65fe3091fb8e56d827.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,193 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class Journal < ActiveRecord::Base + belongs_to :journalized, :polymorphic => true + # added as a quick fix to allow eager loading of the polymorphic association + # since always associated to an issue, for now + belongs_to :issue, :foreign_key => :journalized_id + + belongs_to :user + has_many :details, :class_name => "JournalDetail", :dependent => :delete_all + attr_accessor :indice + + acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" }, + :description => :notes, + :author => :user, + :group => :issue, + :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' }, + :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} + + acts_as_activity_provider :type => 'issues', + :author_key => :user_id, + :find_options => {:include => [{:issue => :project}, :details, :user], + :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + + " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} + + before_create :split_private_notes + after_create :send_notification + + scope :visible, lambda {|*args| + user = args.shift || User.current + + includes(:issue => :project). + where(Issue.visible_condition(user, *args)). + where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false) + } + + def save(*args) + # Do not save an empty journal + (details.empty? && notes.blank?) ? false : super + end + + # Returns journal details that are visible to user + def visible_details(user=User.current) + details.select do |detail| + if detail.property == 'cf' + detail.custom_field && detail.custom_field.visible_by?(project, user) + elsif detail.property == 'relation' + Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user) + else + true + end + end + end + + def each_notification(users, &block) + if users.any? + users_by_details_visibility = users.group_by do |user| + visible_details(user) + end + users_by_details_visibility.each do |visible_details, users| + if notes? || visible_details.any? + yield(users) + end + end + end + end + + # Returns the new status if the journal contains a status change, otherwise nil + def new_status + c = details.detect {|detail| detail.prop_key == 'status_id'} + (c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil + end + + def new_value_for(prop) + c = details.detect {|detail| detail.prop_key == prop} + c ? c.value : nil + end + + def editable_by?(usr) + usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project))) + end + + def project + journalized.respond_to?(:project) ? journalized.project : nil + end + + def attachments + journalized.respond_to?(:attachments) ? journalized.attachments : nil + end + + # Returns a string of css classes + def css_classes + s = 'journal' + s << ' has-notes' unless notes.blank? + s << ' has-details' unless details.blank? + s << ' private-notes' if private_notes? + s + end + + def notify? + @notify != false + end + + def notify=(arg) + @notify = arg + end + + def notified_users + notified = journalized.notified_users + if private_notes? + notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)} + end + notified + end + + def recipients + notified_users.map(&:mail) + end + + def notified_watchers + notified = journalized.notified_watchers + if private_notes? + notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)} + end + notified + end + + def watcher_recipients + notified_watchers.map(&:mail) + end + + # Sets @custom_field instance variable on journals details using a single query + def self.preload_journals_details_custom_fields(journals) + field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq + if field_ids.any? + fields_by_id = CustomField.find_all_by_id(field_ids).inject({}) {|h, f| h[f.id] = f; h} + journals.each do |journal| + journal.details.each do |detail| + if detail.property == 'cf' + detail.instance_variable_set "@custom_field", fields_by_id[detail.prop_key.to_i] + end + end + end + end + journals + end + + private + + def split_private_notes + if private_notes? + if notes.present? + if details.any? + # Split the journal (notes/changes) so we don't have half-private journals + journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false) + journal.details = details + journal.save + self.details = [] + self.created_on = journal.created_on + end + else + # Blank notes should not be private + self.private_notes = false + end + end + true + end + + def send_notification + if notify? && (Setting.notified_events.include?('issue_updated') || + (Setting.notified_events.include?('issue_note_added') && notes.present?) || + (Setting.notified_events.include?('issue_status_updated') && new_status.present?) || + (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?) + ) + Mailer.deliver_issue_edit(self) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1d/1da882d06ce8605d4176f84b3f42ce44acc55377.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1d/1da882d06ce8605d4176f84b3f42ce44acc55377.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1212 @@ +# Russian localization for Ruby on Rails 2.2+ +# by Yaroslav Markin +# +# Be sure to check out "russian" gem (http://github.com/yaroslav/russian) for +# full Russian language support in Rails (month names, pluralization, etc). +# The following is an excerpt from that gem. +# +# Ð”Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ñ†ÐµÐ½Ð½Ð¾Ð¹ поддержки руÑÑкого Ñзыка (варианты названий меÑÑцев, +# Ð¿Ð»ÑŽÑ€Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¸ так далее) в Rails 2.2 нужно иÑпользовать gem "russian" +# (http://github.com/yaroslav/russian). Следующие данные -- выдержка их него, чтобы +# была возможноÑть минимальной локализации Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° руÑÑкий Ñзык. + +ru: + direction: ltr + date: + formats: + default: "%d.%m.%Y" + short: "%d %b" + long: "%d %B %Y" + + day_names: [воÑкреÑенье, понедельник, вторник, Ñреда, четверг, пÑтница, Ñуббота] + standalone_day_names: [ВоÑкреÑенье, Понедельник, Вторник, Среда, Четверг, ПÑтница, Суббота] + abbr_day_names: [Ð’Ñ, Пн, Ð’Ñ‚, Ср, Чт, Пт, Сб] + + month_names: [~, ÑнварÑ, февралÑ, марта, апрелÑ, маÑ, июнÑ, июлÑ, авгуÑта, ÑентÑбрÑ, октÑбрÑ, ноÑбрÑ, декабрÑ] + # see russian gem for info on "standalone" day names + standalone_month_names: [~, Январь, Февраль, Март, Ðпрель, Май, Июнь, Июль, ÐвгуÑÑ‚, СентÑбрь, ОктÑбрь, ÐоÑбрь, Декабрь] + abbr_month_names: [~, Ñнв., февр., марта, апр., маÑ, июнÑ, июлÑ, авг., Ñент., окт., ноÑб., дек.] + standalone_abbr_month_names: [~, Ñнв., февр., март, апр., май, июнь, июль, авг., Ñент., окт., ноÑб., дек.] + + order: + - :day + - :month + - :year + + time: + formats: + default: "%a, %d %b %Y, %H:%M:%S %z" + time: "%H:%M" + short: "%d %b, %H:%M" + long: "%d %B %Y, %H:%M" + + am: "утра" + pm: "вечера" + + number: + format: + separator: "," + delimiter: " " + precision: 3 + + currency: + format: + format: "%n %u" + unit: "руб." + separator: "." + delimiter: " " + precision: 2 + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + # Rails 2.2 + # storage_units: [байт, КБ, МБ, ГБ, ТБ] + + # Rails 2.3 + storage_units: + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u" + units: + byte: + one: "байт" + few: "байта" + many: "байт" + other: "байта" + kb: "КБ" + mb: "МБ" + gb: "ГБ" + tb: "ТБ" + + datetime: + distance_in_words: + half_a_minute: "меньше минуты" + less_than_x_seconds: + one: "меньше %{count} Ñекунды" + few: "меньше %{count} Ñекунд" + many: "меньше %{count} Ñекунд" + other: "меньше %{count} Ñекунды" + x_seconds: + one: "%{count} Ñекунда" + few: "%{count} Ñекунды" + many: "%{count} Ñекунд" + other: "%{count} Ñекунды" + less_than_x_minutes: + one: "меньше %{count} минуты" + few: "меньше %{count} минут" + many: "меньше %{count} минут" + other: "меньше %{count} минуты" + x_minutes: + one: "%{count} минуту" + few: "%{count} минуты" + many: "%{count} минут" + other: "%{count} минуты" + about_x_hours: + one: "около %{count} чаÑа" + few: "около %{count} чаÑов" + many: "около %{count} чаÑов" + other: "около %{count} чаÑа" + x_hours: + one: "%{count} чаÑ" + few: "%{count} чаÑа" + many: "%{count} чаÑов" + other: "%{count} чаÑа" + x_days: + one: "%{count} день" + few: "%{count} днÑ" + many: "%{count} дней" + other: "%{count} днÑ" + about_x_months: + one: "около %{count} меÑÑца" + few: "около %{count} меÑÑцев" + many: "около %{count} меÑÑцев" + other: "около %{count} меÑÑца" + x_months: + one: "%{count} меÑÑц" + few: "%{count} меÑÑца" + many: "%{count} меÑÑцев" + other: "%{count} меÑÑца" + about_x_years: + one: "около %{count} года" + few: "около %{count} лет" + many: "около %{count} лет" + other: "около %{count} лет" + over_x_years: + one: "больше %{count} года" + few: "больше %{count} лет" + many: "больше %{count} лет" + other: "больше %{count} лет" + almost_x_years: + one: "почти %{count} год" + few: "почти %{count} года" + many: "почти %{count} лет" + other: "почти %{count} года" + prompts: + year: "Год" + month: "МеÑÑц" + day: "День" + hour: "ЧаÑов" + minute: "Минут" + second: "Секунд" + + activerecord: + errors: + template: + header: + one: "%{model}: Ñохранение не удалоÑÑŒ из-за %{count} ошибки" + few: "%{model}: Ñохранение не удалоÑÑŒ из-за %{count} ошибок" + many: "%{model}: Ñохранение не удалоÑÑŒ из-за %{count} ошибок" + other: "%{model}: Ñохранение не удалоÑÑŒ из-за %{count} ошибки" + + body: "Проблемы возникли Ñо Ñледующими полÑми:" + + messages: + inclusion: "имеет непредуÑмотренное значение" + exclusion: "имеет зарезервированное значение" + invalid: "имеет неверное значение" + confirmation: "не Ñовпадает Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸ÐµÐ¼" + accepted: "нужно подтвердить" + empty: "не может быть пуÑтым" + blank: "не может быть пуÑтым" + too_long: + one: "Ñлишком большой длины (не может быть больше чем %{count} Ñимвол)" + few: "Ñлишком большой длины (не может быть больше чем %{count} Ñимвола)" + many: "Ñлишком большой длины (не может быть больше чем %{count} Ñимволов)" + other: "Ñлишком большой длины (не может быть больше чем %{count} Ñимвола)" + too_short: + one: "недоÑтаточной длины (не может быть меньше %{count} Ñимвола)" + few: "недоÑтаточной длины (не может быть меньше %{count} Ñимволов)" + many: "недоÑтаточной длины (не может быть меньше %{count} Ñимволов)" + other: "недоÑтаточной длины (не может быть меньше %{count} Ñимвола)" + wrong_length: + one: "неверной длины (может быть длиной ровно %{count} Ñимвол)" + few: "неверной длины (может быть длиной ровно %{count} Ñимвола)" + many: "неверной длины (может быть длиной ровно %{count} Ñимволов)" + other: "неверной длины (может быть длиной ровно %{count} Ñимвола)" + taken: "уже ÑущеÑтвует" + not_a_number: "не ÑвлÑетÑÑ Ñ‡Ð¸Ñлом" + greater_than: "может иметь значение большее %{count}" + greater_than_or_equal_to: "может иметь значение большее или равное %{count}" + equal_to: "может иметь лишь значение, равное %{count}" + less_than: "может иметь значение меньшее чем %{count}" + less_than_or_equal_to: "может иметь значение меньшее или равное %{count}" + odd: "может иметь лишь нечетное значение" + even: "может иметь лишь четное значение" + greater_than_start_date: "должна быть позднее даты начала" + not_same_project: "не отноÑитÑÑ Ðº одному проекту" + circular_dependency: "Ð¢Ð°ÐºÐ°Ñ ÑвÑзь приведет к цикличеÑкой завиÑимоÑти" + cant_link_an_issue_with_a_descendant: "Задача не может быть ÑвÑзана Ñо Ñвоей подзадачей" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + support: + array: + # Rails 2.2 + sentence_connector: "и" + skip_last_comma: true + + # Rails 2.3 + words_connector: ", " + two_words_connector: " и " + last_word_connector: " и " + + actionview_instancetag_blank_option: Выберите + + button_activate: Ðктивировать + button_add: Добавить + button_annotate: ÐвторÑтво + button_apply: Применить + button_archive: Ðрхивировать + button_back: Ðазад + button_cancel: Отмена + button_change_password: Изменить пароль + button_change: Изменить + button_check_all: Отметить вÑе + button_clear: ОчиÑтить + button_configure: Параметры + button_copy: Копировать + button_create: Создать + button_create_and_continue: Создать и продолжить + button_delete: Удалить + button_download: Загрузить + button_edit: Редактировать + button_edit_associated_wikipage: "Редактировать ÑвÑзанную wiki-Ñтраницу: %{page_title}" + button_list: СпиÑок + button_lock: Заблокировать + button_login: Вход + button_log_time: Затраченное Ð²Ñ€ÐµÐ¼Ñ + button_move: ПеремеÑтить + button_quote: Цитировать + button_rename: Переименовать + button_reply: Ответить + button_reset: СброÑить + button_rollback: ВернутьÑÑ Ðº данной верÑии + button_save: Сохранить + button_sort: Сортировать + button_submit: ПринÑть + button_test: Проверить + button_unarchive: Разархивировать + button_uncheck_all: ОчиÑтить + button_unlock: Разблокировать + button_unwatch: Ðе Ñледить + button_update: Обновить + button_view: ПроÑмотреть + button_watch: Следить + + default_activity_design: Проектирование + default_activity_development: Разработка + default_doc_category_tech: ТехничеÑÐºÐ°Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ + default_doc_category_user: ПользовательÑÐºÐ°Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ + default_issue_status_in_progress: Ð’ работе + default_issue_status_closed: Закрыта + default_issue_status_feedback: ÐžÐ±Ñ€Ð°Ñ‚Ð½Ð°Ñ ÑвÑзь + default_issue_status_new: ÐÐ¾Ð²Ð°Ñ + default_issue_status_rejected: Отклонена + default_issue_status_resolved: Решена + default_priority_high: Ð’Ñ‹Ñокий + default_priority_immediate: Ðемедленный + default_priority_low: Ðизкий + default_priority_normal: Ðормальный + default_priority_urgent: Срочный + default_role_developer: Разработчик + default_role_manager: Менеджер + default_role_reporter: Репортёр + default_tracker_bug: Ошибка + default_tracker_feature: Улучшение + default_tracker_support: Поддержка + + enumeration_activities: ДейÑÑ‚Ð²Ð¸Ñ (учёт времени) + enumeration_doc_categories: Категории документов + enumeration_issue_priorities: Приоритеты задач + + error_can_not_remove_role: Эта роль иÑпользуетÑÑ Ð¸ не может быть удалена. + error_can_not_delete_custom_field: Ðевозможно удалить наÑтраиваемое поле + error_can_not_delete_tracker: Этот трекер Ñодержит задачи и не может быть удален. + error_can_t_load_default_data: "ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¿Ð¾ умолчанию не была загружена: %{value}" + error_issue_not_found_in_project: Задача не была найдена или не прикреплена к Ñтому проекту + error_scm_annotate: "Данные отÑутÑтвуют или не могут быть подпиÑаны." + error_scm_command_failed: "Ошибка доÑтупа к хранилищу: %{value}" + error_scm_not_found: Хранилище не Ñодержит запиÑи и/или иÑправлениÑ. + error_unable_to_connect: Ðевозможно подключитьÑÑ (%{value}) + error_unable_delete_issue_status: Ðевозможно удалить ÑÑ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸ + + field_account: Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ + field_activity: ДеÑтельноÑть + field_admin: ÐдминиÑтратор + field_assignable: Задача может быть назначена Ñтой роли + field_assigned_to: Ðазначена + field_attr_firstname: Ð˜Ð¼Ñ + field_attr_lastname: Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ + field_attr_login: Ðтрибут Login + field_attr_mail: email + field_author: Ðвтор + field_auth_source: Режим аутентификации + field_base_dn: BaseDN + field_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ + field_column_names: Столбцы + field_comments: Комментарий + field_comments_sorting: Отображение комментариев + field_content: Content + field_created_on: Создано + field_default_value: Значение по умолчанию + field_delay: Отложить + field_description: ОпиÑание + field_done_ratio: ГотовноÑть + field_downloads: Загрузки + field_due_date: Дата Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ + field_editable: Редактируемое + field_effective_date: Дата + field_estimated_hours: Оценка времени + field_field_format: Формат + field_filename: Файл + field_filesize: Размер + field_firstname: Ð˜Ð¼Ñ + field_fixed_version: ВерÑÐ¸Ñ + field_hide_mail: Скрывать мой email + field_homepage: Ð¡Ñ‚Ð°Ñ€Ñ‚Ð¾Ð²Ð°Ñ Ñтраница + field_host: Компьютер + field_hours: чаÑ(а,ов) + field_identifier: Уникальный идентификатор + field_identity_url: OpenID URL + field_is_closed: Задача закрыта + field_is_default: Значение по умолчанию + field_is_filter: ИÑпользуетÑÑ Ð² качеÑтве фильтра + field_is_for_all: Ð”Ð»Ñ Ð²Ñех проектов + field_is_in_roadmap: Задачи, отображаемые в оперативном плане + field_is_public: ОбщедоÑтупный + field_is_required: ОбÑзательное + field_issue_to: СвÑзанные задачи + field_issue: Задача + field_language: Язык + field_last_login_on: ПоÑледнее подключение + field_lastname: Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ + field_login: Пользователь + field_mail: Email + field_mail_notification: Ð£Ð²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ email + field_max_length: МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° + field_min_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° + field_name: Ð˜Ð¼Ñ + field_new_password: Ðовый пароль + field_notes: ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ + field_onthefly: Создание Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ð° лету + field_parent_title: РодительÑÐºÐ°Ñ Ñтраница + field_parent: РодительÑкий проект + field_parent_issue: РодительÑÐºÐ°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° + field_password_confirmation: Подтверждение + field_password: Пароль + field_port: Порт + field_possible_values: Возможные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ + field_priority: Приоритет + field_project: Проект + field_redirect_existing_links: Перенаправить ÑущеÑтвующие ÑÑылки + field_regexp: РегулÑрное выражение + field_role: Роль + field_searchable: ДоÑтупно Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка + field_spent_on: Дата + field_start_date: Ðачата + field_start_page: Ð¡Ñ‚Ð°Ñ€Ñ‚Ð¾Ð²Ð°Ñ Ñтраница + field_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ + field_subject: Тема + field_subproject: Подпроект + field_summary: Краткое опиÑание + field_text: ТекÑтовое поле + field_time_entries: Затраченное Ð²Ñ€ÐµÐ¼Ñ + field_time_zone: ЧаÑовой поÑÑ + field_title: Заголовок + field_tracker: Трекер + field_type: Тип + field_updated_on: Обновлено + field_url: URL + field_user: Пользователь + field_value: Значение + field_version: ВерÑÐ¸Ñ + field_watcher: Ðаблюдатель + + general_csv_decimal_separator: ',' + general_csv_encoding: UTF-8 + general_csv_separator: ';' + general_first_day_of_week: '1' + general_lang_name: 'Russian (РуÑÑкий)' + general_pdf_encoding: UTF-8 + general_text_no: 'нет' + general_text_No: 'Ðет' + general_text_yes: 'да' + general_text_Yes: 'Да' + + label_activity: ДейÑÑ‚Ð²Ð¸Ñ + label_add_another_file: Добавить ещё один файл + label_added_time_by: "Добавил(а) %{author} %{age} назад" + label_added: добавлено + label_add_note: Добавить замечание + label_administration: ÐдминиÑтрирование + label_age: ВозраÑÑ‚ + label_ago: дней(Ñ) назад + label_all_time: вÑÑ‘ Ð²Ñ€ÐµÐ¼Ñ + label_all_words: Ð’Ñе Ñлова + label_all: вÑе + label_and_its_subprojects: "%{value} и вÑе подпроекты" + label_applied_status: Применимый ÑÑ‚Ð°Ñ‚ÑƒÑ + label_ascending: По возраÑтанию + label_assigned_to_me_issues: Мои задачи + label_associated_revisions: СвÑзанные редакции + label_attachment: Файл + label_attachment_delete: Удалить файл + label_attachment_new: Ðовый файл + label_attachment_plural: Файлы + label_attribute: Ðтрибут + label_attribute_plural: Ðтрибуты + label_authentication: ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ + label_auth_source: Режим аутентификации + label_auth_source_new: Ðовый режим аутентификации + label_auth_source_plural: Режимы аутентификации + label_blocked_by: блокируетÑÑ + label_blocks: блокирует + label_board: Форум + label_board_new: Ðовый форум + label_board_plural: Форумы + label_boolean: ЛогичеÑкий + label_browse: Обзор + label_bulk_edit_selected_issues: Редактировать вÑе выбранные задачи + label_calendar: Календарь + label_calendar_filter: Ð’ÐºÐ»ÑŽÑ‡Ð°Ñ + label_calendar_no_assigned: не мои + label_change_plural: Правки + label_change_properties: Изменить ÑвойÑтва + label_change_status: Изменить ÑÑ‚Ð°Ñ‚ÑƒÑ + label_change_view_all: ПроÑмотреть вÑе Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ + label_changes_details: ПодробноÑти по вÑем изменениÑм + label_changeset_plural: Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ + label_chronological_order: Ð’ хронологичеÑком порÑдке + label_closed_issues: закрыто + label_closed_issues_plural: закрыто + label_closed_issues_plural2: закрыто + label_closed_issues_plural5: закрыто + label_comment: комментарий + label_comment_add: ОÑтавить комментарий + label_comment_added: Добавленный комментарий + label_comment_delete: Удалить комментарии + label_comment_plural: Комментарии + label_comment_plural2: ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸Ñ + label_comment_plural5: комментариев + label_commits_per_author: Изменений на Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ + label_commits_per_month: Изменений в меÑÑц + label_confirmation: Подтверждение + label_contains: Ñодержит + label_copied: Ñкопировано + label_copy_workflow_from: Скопировать поÑледовательноÑть дейÑтвий из + label_current_status: Текущий ÑÑ‚Ð°Ñ‚ÑƒÑ + label_current_version: Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ + label_custom_field: ÐаÑтраиваемое поле + label_custom_field_new: Ðовое наÑтраиваемое поле + label_custom_field_plural: ÐаÑтраиваемые Ð¿Ð¾Ð»Ñ + label_date_from: С + label_date_from_to: С %{start} по %{end} + label_date_range: временной интервал + label_date_to: по + label_date: Дата + label_day_plural: дней(Ñ) + label_default: По умолчанию + label_default_columns: Столбцы по умолчанию + label_deleted: удалено + label_descending: По убыванию + label_details: ПодробноÑти + label_diff_inline: в текÑте + label_diff_side_by_side: Ñ€Ñдом + label_disabled: отключено + label_display: Отображение + label_display_per_page: "Ðа Ñтраницу: %{value}" + label_document: Документ + label_document_added: Добавлен документ + label_document_new: Ðовый документ + label_document_plural: Документы + label_downloads_abbr: Скачиваний + label_duplicated_by: дублируетÑÑ + label_duplicates: дублирует + label_end_to_end: Ñ ÐºÐ¾Ð½Ñ†Ð° к концу + label_end_to_start: Ñ ÐºÐ¾Ð½Ñ†Ð° к началу + label_enumeration_new: Ðовое значение + label_enumerations: СпиÑки значений + label_environment: Окружение + label_equals: ÑоответÑтвует + label_example: Пример + label_export_to: ЭкÑпортировать в + label_feed_plural: Atom + label_feeds_access_key_created_on: "Ключ доÑтупа Atom Ñоздан %{value} назад" + label_f_hour: "%{value} чаÑ" + label_f_hour_plural: "%{value} чаÑов" + label_file_added: Добавлен файл + label_file_plural: Файлы + label_filter_add: Добавить фильтр + label_filter_plural: Фильтры + label_float: С плавающей точкой + label_follows: Ð¿Ñ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ + label_gantt: Диаграмма Ганта + label_general: Общее + label_generate_key: Сгенерировать ключ + label_greater_or_equal: ">=" + label_help: Помощь + label_history: ИÑÑ‚Ð¾Ñ€Ð¸Ñ + label_home: ДомашнÑÑ Ñтраница + label_incoming_emails: Приём Ñообщений + label_index_by_date: ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ñтраниц + label_index_by_title: Оглавление + label_information_plural: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ + label_information: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ + label_in_less_than: менее чем + label_in_more_than: более чем + label_integer: Целый + label_internal: Внутренний + label_in: в + label_issue: Задача + label_issue_added: Добавлена задача + label_issue_category_new: ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ + label_issue_category_plural: Категории задачи + label_issue_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸ + label_issue_new: ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° + label_issue_plural: Задачи + label_issues_by: "Сортировать по %{value}" + label_issue_status_new: Ðовый ÑÑ‚Ð°Ñ‚ÑƒÑ + label_issue_status_plural: СтатуÑÑ‹ задач + label_issue_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸ + label_issue_tracking: Задачи + label_issue_updated: Обновлена задача + label_issue_view_all: ПроÑмотреть вÑе задачи + label_issue_watchers: Ðаблюдатели + label_jump_to_a_project: Перейти к проекту... + label_language_based: Ðа оÑнове Ñзыка + label_last_changes: "менее %{count} изменений" + label_last_login: ПоÑледнее подключение + label_last_month: поÑледний меÑÑц + label_last_n_days: "поÑледние %{count} дней" + label_last_week: поÑледнÑÑ Ð½ÐµÐ´ÐµÐ»Ñ + label_latest_revision: ПоÑледнÑÑ Ñ€ÐµÐ´Ð°ÐºÑ†Ð¸Ñ + label_latest_revision_plural: ПоÑледние редакции + label_ldap_authentication: ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ LDAP + label_less_or_equal: <= + label_less_than_ago: менее, чем дней(Ñ) назад + label_list: СпиÑок + label_loading: Загрузка... + label_logged_as: Вошли как + label_login: Войти + label_login_with_open_id_option: или войти Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ OpenID + label_logout: Выйти + label_max_size: МакÑимальный размер + label_member_new: Ðовый учаÑтник + label_member: УчаÑтник + label_member_plural: УчаÑтники + label_message_last: ПоÑледнее Ñообщение + label_message_new: Ðовое Ñообщение + label_message_plural: Ð¡Ð¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + label_message_posted: Добавлено Ñообщение + label_me: мне + label_min_max_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ - макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° + label_modified: изменено + label_module_plural: Модули + label_months_from: меÑÑцев(ца) Ñ + label_month: МеÑÑц + label_more_than_ago: более, чем дней(Ñ) назад + label_more: Больше + label_my_account: ÐœÐ¾Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ + label_my_page: ÐœÐ¾Ñ Ñтраница + label_my_page_block: Блок моей Ñтраницы + label_my_projects: Мои проекты + label_new: Ðовый + label_new_statuses_allowed: Разрешенные новые ÑтатуÑÑ‹ + label_news_added: Добавлена новоÑть + label_news_latest: ПоÑледние новоÑти + label_news_new: Добавить новоÑть + label_news_plural: ÐовоÑти + label_news_view_all: ПоÑмотреть вÑе новоÑти + label_news: ÐовоÑти + label_next: Следующее + label_nobody: никто + label_no_change_option: (Ðет изменений) + label_no_data: Ðет данных Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + label_none: отÑутÑтвует + label_not_contains: не Ñодержит + label_not_equals: не ÑоответÑтвует + label_open_issues: открыто + label_open_issues_plural: открыто + label_open_issues_plural2: открыто + label_open_issues_plural5: открыто + label_optional_description: ОпиÑание (необÑзательно) + label_options: Опции + label_overall_activity: Сводный отчёт дейÑтвий + label_overview: Обзор + label_password_lost: ВоÑÑтановление Ð¿Ð°Ñ€Ð¾Ð»Ñ + label_permissions_report: Отчёт по правам доÑтупа + label_permissions: Права доÑтупа + label_per_page: Ðа Ñтраницу + label_personalize_page: ПерÑонализировать данную Ñтраницу + label_planning: Планирование + label_please_login: ПожалуйÑта, войдите. + label_plugins: Модули + label_precedes: ÑÐ»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ + label_preferences: ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ñ‚ÐµÐ½Ð¸Ñ + label_preview: ПредпроÑмотр + label_previous: Предыдущее + label_profile: Профиль + label_project: Проект + label_project_all: Ð’Ñе проекты + label_project_copy_notifications: ОтправлÑть ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ Ñлектронной почте при копировании проекта + label_project_latest: ПоÑледние проекты + label_project_new: Ðовый проект + label_project_plural: Проекты + label_project_plural2: проекта + label_project_plural5: проектов + label_public_projects: Общие проекты + label_query: Сохранённый Ð·Ð°Ð¿Ñ€Ð¾Ñ + label_query_new: Ðовый Ð·Ð°Ð¿Ñ€Ð¾Ñ + label_query_plural: Сохранённые запроÑÑ‹ + label_read: Чтение... + label_register: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ + label_registered_on: ЗарегиÑтрирован(а) + label_registration_activation_by_email: Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ñ‹Ñ… запиÑей по email + label_registration_automatic_activation: автоматичеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ñ‹Ñ… запиÑей + label_registration_manual_activation: активировать учётные запиÑи вручную + label_related_issues: СвÑзанные задачи + label_relates_to: ÑвÑзана Ñ + label_relation_delete: Удалить ÑвÑзь + label_relation_new: Ðовое отношение + label_renamed: переименовано + label_reply_plural: Ответы + label_report: Отчёт + label_report_plural: Отчёты + label_reported_issues: Созданные задачи + label_repository: Хранилище + label_repository_plural: Хранилища + label_result_plural: Результаты + label_reverse_chronological_order: Ð’ обратном порÑдке + label_revision: Ð ÐµÐ´Ð°ÐºÑ†Ð¸Ñ + label_revision_plural: Редакции + label_roadmap: Оперативный план + label_roadmap_due_in: "Ð’ Ñрок %{value}" + label_roadmap_no_issues: Ðет задач Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ верÑии + label_roadmap_overdue: "опоздание %{value}" + label_role: Роль + label_role_and_permissions: Роли и права доÑтупа + label_role_new: ÐÐ¾Ð²Ð°Ñ Ñ€Ð¾Ð»ÑŒ + label_role_plural: Роли + label_scm: Тип хранилища + label_search: ПоиÑк + label_search_titles_only: ИÑкать только в названиÑÑ… + label_send_information: Отправить пользователю информацию по учётной запиÑи + label_send_test_email: ПоÑлать email Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ + label_settings: ÐаÑтройки + label_show_completed_versions: Показывать завершённые верÑии + label_sort: Сортировать + label_sort_by: "Сортировать по %{value}" + label_sort_higher: Вверх + label_sort_highest: Ð’ начало + label_sort_lower: Вниз + label_sort_lowest: Ð’ конец + label_spent_time: Затраченное Ð²Ñ€ÐµÐ¼Ñ + label_start_to_end: Ñ Ð½Ð°Ñ‡Ð°Ð»Ð° к концу + label_start_to_start: Ñ Ð½Ð°Ñ‡Ð°Ð»Ð° к началу + label_statistics: СтатиÑтика + label_stay_logged_in: ОÑтаватьÑÑ Ð² ÑиÑтеме + label_string: ТекÑÑ‚ + label_subproject_plural: Подпроекты + label_subtask_plural: Подзадачи + label_text: Длинный текÑÑ‚ + label_theme: Тема + label_this_month: Ñтот меÑÑц + label_this_week: на Ñтой неделе + label_this_year: Ñтот год + label_time_tracking: Учёт времени + label_timelog_today: РаÑход времени на ÑÐµÐ³Ð¾Ð´Ð½Ñ + label_today: ÑÐµÐ³Ð¾Ð´Ð½Ñ + label_topic_plural: Темы + label_total: Ð’Ñего + label_tracker: Трекер + label_tracker_new: Ðовый трекер + label_tracker_plural: Трекеры + label_updated_time: "Обновлено %{value} назад" + label_updated_time_by: "Обновлено %{author} %{age} назад" + label_used_by: ИÑпользуетÑÑ + label_user: Пользователь + label_user_activity: "ДейÑÑ‚Ð²Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %{value}" + label_user_mail_no_self_notified: "Ðе извещать об изменениÑÑ…, которые Ñ Ñделал Ñам" + label_user_mail_option_all: "О вÑех ÑобытиÑÑ… во вÑех моих проектах" + label_user_mail_option_selected: "О вÑех ÑобытиÑÑ… только в выбранном проекте..." + label_user_mail_option_only_owner: Только Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð², Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… Ñ ÑвлÑÑŽÑÑŒ владельцем + label_user_mail_option_only_my_events: Только Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð², которые Ñ Ð¾Ñ‚Ñлеживаю или в которых учаÑтвую + label_user_mail_option_only_assigned: Только Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð², которые назначены мне + label_user_new: Ðовый пользователь + label_user_plural: Пользователи + label_version: ВерÑÐ¸Ñ + label_version_new: ÐÐ¾Ð²Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ + label_version_plural: ВерÑии + label_view_diff: ПроÑмотреть Ð¾Ñ‚Ð»Ð¸Ñ‡Ð¸Ñ + label_view_revisions: ПроÑмотреть редакции + label_watched_issues: ОтÑлеживаемые задачи + label_week: ÐÐµÐ´ÐµÐ»Ñ + label_wiki: Wiki + label_wiki_edit: Редактирование Wiki + label_wiki_edit_plural: Wiki + label_wiki_page: Страница Wiki + label_wiki_page_plural: Страницы Wiki + label_workflow: ПоÑледовательноÑть дейÑтвий + label_x_closed_issues_abbr: + zero: "0 закрыто" + one: "%{count} закрыта" + few: "%{count} закрыто" + many: "%{count} закрыто" + other: "%{count} закрыто" + label_x_comments: + zero: "нет комментариев" + one: "%{count} комментарий" + few: "%{count} комментариÑ" + many: "%{count} комментариев" + other: "%{count} комментариев" + label_x_open_issues_abbr: + zero: "0 открыто" + one: "%{count} открыта" + few: "%{count} открыто" + many: "%{count} открыто" + other: "%{count} открыто" + label_x_open_issues_abbr_on_total: + zero: "0 открыто / %{total}" + one: "%{count} открыта / %{total}" + few: "%{count} открыто / %{total}" + many: "%{count} открыто / %{total}" + other: "%{count} открыто / %{total}" + label_x_projects: + zero: "нет проектов" + one: "%{count} проект" + few: "%{count} проекта" + many: "%{count} проектов" + other: "%{count} проектов" + label_year: Год + label_yesterday: вчера + + mail_body_account_activation_request: "ЗарегиÑтрирован новый пользователь (%{value}). Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ ожидает Вашего утверждениÑ:" + mail_body_account_information: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Вашей учётной запиÑи + mail_body_account_information_external: "Ð’Ñ‹ можете иÑпользовать Вашу %{value} учётную запиÑÑŒ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð°." + mail_body_lost_password: 'Ð”Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð¿Ñ€Ð¾Ð¹Ð´Ð¸Ñ‚Ðµ по Ñледующей ÑÑылке:' + mail_body_register: 'Ð”Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ð¸ учётной запиÑи пройдите по Ñледующей ÑÑылке:' + mail_body_reminder: "%{count} назначенных на Ð’Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡ на Ñледующие %{days} дней:" + mail_subject_account_activation_request: "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° активацию Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² ÑиÑтеме %{value}" + mail_subject_lost_password: "Ваш %{value} пароль" + mail_subject_register: "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ð¾Ð¹ запиÑи %{value}" + mail_subject_reminder: "%{count} назначенных на Ð’Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡ в ближайшие %{days} дней" + + notice_account_activated: Ваша ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ активирована. Ð’Ñ‹ можете войти. + notice_account_invalid_creditentials: Ðеправильное Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ пароль + notice_account_lost_email_sent: Вам отправлено пиÑьмо Ñ Ð¸Ð½ÑтрукциÑми по выбору нового паролÑ. + notice_account_password_updated: Пароль уÑпешно обновлён. + notice_account_pending: "Ваша ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ Ñоздана и ожидает Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратора." + notice_account_register_done: Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ уÑпешно Ñоздана. Ð”Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ð¸ Вашей учётной запиÑи пройдите по ÑÑылке, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð²Ñ‹Ñлана Вам по Ñлектронной почте. + notice_account_unknown_email: ÐеизвеÑтный пользователь. + notice_account_updated: Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ уÑпешно обновлена. + notice_account_wrong_password: Ðеверный пароль + notice_can_t_change_password: Ð”Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ учётной запиÑи иÑпользуетÑÑ Ð¸Ñточник внешней аутентификации. Ðевозможно изменить пароль. + notice_default_data_loaded: Была загружена ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¿Ð¾ умолчанию. + notice_email_error: "Во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸ пиÑьма произошла ошибка (%{value})" + notice_email_sent: "Отправлено пиÑьмо %{value}" + notice_failed_to_save_issues: "Ðе удалоÑÑŒ Ñохранить %{count} пункт(ов) из %{total} выбранных: %{ids}." + notice_failed_to_save_members: "Ðе удалоÑÑŒ Ñохранить учаÑтника(ов): %{errors}." + notice_feeds_access_key_reseted: Ваш ключ доÑтупа Atom был Ñброшен. + notice_file_not_found: Страница, на которую Ð’Ñ‹ пытаетеÑÑŒ зайти, не ÑущеÑтвует или удалена. + notice_locking_conflict: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð° другим пользователем. + notice_no_issue_selected: "Ðе выбрано ни одной задачи! ПожалуйÑта, отметьте задачи, которые Ð’Ñ‹ хотите отредактировать." + notice_not_authorized: У Ð’Ð°Ñ Ð½ÐµÑ‚ прав Ð´Ð»Ñ Ð¿Ð¾ÑÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ Ñтраницы. + notice_successful_connection: Подключение уÑпешно уÑтановлено. + notice_successful_create: Создание уÑпешно. + notice_successful_delete: Удаление уÑпешно. + notice_successful_update: Обновление уÑпешно. + notice_unable_delete_version: Ðевозможно удалить верÑию. + + permission_add_issues: Добавление задач + permission_add_issue_notes: Добавление примечаний + permission_add_issue_watchers: Добавление наблюдателей + permission_add_messages: Отправка Ñообщений + permission_browse_repository: ПроÑмотр хранилища + permission_comment_news: Комментирование новоÑтей + permission_commit_access: Изменение файлов в хранилище + permission_delete_issues: Удаление задач + permission_delete_messages: Удаление Ñообщений + permission_delete_own_messages: Удаление ÑобÑтвенных Ñообщений + permission_delete_wiki_pages: Удаление wiki-Ñтраниц + permission_delete_wiki_pages_attachments: Удаление прикреплённых файлов + permission_edit_issue_notes: Редактирование примечаний + permission_edit_issues: Редактирование задач + permission_edit_messages: Редактирование Ñообщений + permission_edit_own_issue_notes: Редактирование ÑобÑтвенных примечаний + permission_edit_own_messages: Редактирование ÑобÑтвенных Ñообщений + permission_edit_own_time_entries: Редактирование ÑобÑтвенного учёта времени + permission_edit_project: Редактирование проектов + permission_edit_time_entries: Редактирование учёта времени + permission_edit_wiki_pages: Редактирование wiki-Ñтраниц + permission_export_wiki_pages: ЭкÑпорт wiki-Ñтраниц + permission_log_time: Учёт затраченного времени + permission_view_changesets: ПроÑмотр изменений хранилища + permission_view_time_entries: ПроÑмотр затраченного времени + permission_manage_project_activities: Управление типами дейÑтвий Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° + permission_manage_boards: Управление форумами + permission_manage_categories: Управление категориÑми задач + permission_manage_files: Управление файлами + permission_manage_issue_relations: Управление ÑвÑзыванием задач + permission_manage_members: Управление учаÑтниками + permission_manage_news: Управление новоÑÑ‚Ñми + permission_manage_public_queries: Управление общими запроÑами + permission_manage_repository: Управление хранилищем + permission_manage_subtasks: Управление подзадачами + permission_manage_versions: Управление верÑиÑми + permission_manage_wiki: Управление Wiki + permission_move_issues: ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð·Ð°Ð´Ð°Ñ‡ + permission_protect_wiki_pages: Блокирование wiki-Ñтраниц + permission_rename_wiki_pages: Переименование wiki-Ñтраниц + permission_save_queries: Сохранение запроÑов + permission_select_project_modules: Выбор модулей проекта + permission_view_calendar: ПроÑмотр ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ + permission_view_documents: ПроÑмотр документов + permission_view_files: ПроÑмотр файлов + permission_view_gantt: ПроÑмотр диаграммы Ганта + permission_view_issue_watchers: ПроÑмотр ÑпиÑка наблюдателей + permission_view_messages: ПроÑмотр Ñообщений + permission_view_wiki_edits: ПроÑмотр иÑтории Wiki + permission_view_wiki_pages: ПроÑмотр Wiki + + project_module_boards: Форумы + project_module_documents: Документы + project_module_files: Файлы + project_module_issue_tracking: Задачи + project_module_news: ÐовоÑти + project_module_repository: Хранилище + project_module_time_tracking: Учёт времени + project_module_wiki: Wiki + project_module_gantt: Диаграмма Ганта + project_module_calendar: Календарь + + setting_activity_days_default: КоличеÑтво дней, отображаемых в ДейÑтвиÑÑ… + setting_app_subtitle: Подзаголовок Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ + setting_app_title: Ðазвание Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ + setting_attachment_max_size: МакÑимальный размер Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ + setting_autofetch_changesets: ÐвтоматичеÑки Ñледить за изменениÑми хранилища + setting_autologin: ÐвтоматичеÑкий вход + setting_bcc_recipients: ИÑпользовать Ñкрытые копии (BCC) + setting_cache_formatted_text: Кешировать форматированный текÑÑ‚ + setting_commit_fix_keywords: Ðазначение ключевых Ñлов + setting_commit_ref_keywords: Ключевые Ñлова Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка + setting_cross_project_issue_relations: Разрешить переÑечение задач по проектам + setting_date_format: Формат даты + setting_default_language: Язык по умолчанию + setting_default_notification_option: СпоÑоб Ð¾Ð¿Ð¾Ð²ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾ умолчанию + setting_default_projects_public: Ðовые проекты ÑвлÑÑŽÑ‚ÑÑ Ð¾Ð±Ñ‰ÐµÐ´Ð¾Ñтупными + setting_diff_max_lines_displayed: МакÑимальное чиÑло Ñтрок Ð´Ð»Ñ diff + setting_display_subprojects_issues: Отображение подпроектов по умолчанию + setting_emails_footer: ПодÑтрочные Ð¿Ñ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð¿Ð¸Ñьма + setting_enabled_scm: Включённые SCM + setting_feeds_limit: Ограничение количеÑтва заголовков Ð´Ð»Ñ Atom потока + setting_file_max_size_displayed: МакÑимальный размер текÑтового файла Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + setting_gravatar_enabled: ИÑпользовать аватар Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· Gravatar + setting_host_name: Ð˜Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð° + setting_issue_list_default_columns: Столбцы, отображаемые в ÑпиÑке задач по умолчанию + setting_issues_export_limit: Ограничение по ÑкÑпортируемым задачам + setting_login_required: Ðеобходима Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ + setting_mail_from: ИÑходÑщий email Ð°Ð´Ñ€ÐµÑ + setting_mail_handler_api_enabled: Включить веб-ÑÐµÑ€Ð²Ð¸Ñ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñщих Ñообщений + setting_mail_handler_api_key: API ключ + setting_openid: Разрешить OpenID Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð° и региÑтрации + setting_per_page_options: КоличеÑтво запиÑей на Ñтраницу + setting_plain_text_mail: Только проÑтой текÑÑ‚ (без HTML) + setting_protocol: Протокол + setting_repository_log_display_limit: МакÑимальное количеÑтво редакций, отображаемых в журнале изменений + setting_self_registration: СаморегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ + setting_sequential_project_identifiers: Генерировать поÑледовательные идентификаторы проектов + setting_sys_api_enabled: Включить веб-ÑÐµÑ€Ð²Ð¸Ñ Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰ÐµÐ¼ + setting_text_formatting: Форматирование текÑта + setting_time_format: Формат времени + setting_user_format: Формат Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¸Ð¼ÐµÐ½Ð¸ + setting_welcome_text: ТекÑÑ‚ приветÑÑ‚Ð²Ð¸Ñ + setting_wiki_compression: Сжатие иÑтории Wiki + + status_active: активен + status_locked: заблокирован + status_registered: зарегиÑтрирован + + text_are_you_sure: Ð’Ñ‹ уверены? + text_assign_time_entries_to_project: Прикрепить зарегиÑтрированное Ð²Ñ€ÐµÐ¼Ñ Ðº проекту + text_caracters_maximum: "МакÑимум %{count} Ñимволов(а)." + text_caracters_minimum: "Должно быть не менее %{count} Ñимволов." + text_comma_separated: ДопуÑтимы неÑколько значений (через запÑтую). + text_custom_field_possible_values_info: 'По одному значению в каждой Ñтроке' + text_default_administrator_account_changed: Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ админиÑтратора по умолчанию изменена + text_destroy_time_entries_question: "Ðа Ñту задачу зарегиÑтрировано %{hours} чаÑа(ов) затраченного времени. Что Ð’Ñ‹ хотите предпринÑть?" + text_destroy_time_entries: Удалить зарегиÑтрированное Ð²Ñ€ÐµÐ¼Ñ + text_diff_truncated: '... Этот diff ограничен, так как превышает макÑимальный отображаемый размер.' + text_email_delivery_not_configured: "Параметры работы Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ñ‹Ð¼ Ñервером не наÑтроены и Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ email не активна.\nÐаÑтроить параметры Ð´Ð»Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ SMTP-Ñервера Ð’Ñ‹ можете в файле config/configuration.yml. Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ перезапуÑтите приложение." + text_enumeration_category_reassign_to: 'Ðазначить им Ñледующее значение:' + text_enumeration_destroy_question: "%{count} объект(а,ов) ÑвÑзаны Ñ Ñтим значением." + text_file_repository_writable: Хранилище файлов доÑтупно Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи + text_issue_added: "Создана Ð½Ð¾Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° %{id} (%{author})." + text_issue_category_destroy_assignments: Удалить Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ð¸ + text_issue_category_destroy_question: "ÐеÑколько задач (%{count}) назначено в данную категорию. Что Ð’Ñ‹ хотите предпринÑть?" + text_issue_category_reassign_to: Переназначить задачи Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ категории + text_issues_destroy_confirmation: 'Ð’Ñ‹ уверены, что хотите удалить выбранные задачи?' + text_issues_ref_in_commit_messages: СопоÑтавление и изменение ÑтатуÑа задач иÑÑ…Ð¾Ð´Ñ Ð¸Ð· текÑта Ñообщений + text_issue_updated: "Задача %{id} была обновлена (%{author})." + text_journal_changed: "Параметр %{label} изменилÑÑ Ñ %{old} на %{new}" + text_journal_deleted: "Значение %{old} параметра %{label} удалено" + text_journal_set_to: "Параметр %{label} изменилÑÑ Ð½Ð° %{value}" + text_length_between: "Длина между %{min} и %{max} Ñимволов." + text_load_default_configuration: Загрузить конфигурацию по умолчанию + text_min_max_length_info: 0 означает отÑутÑтвие ограничений + text_no_configuration_data: "Роли, трекеры, ÑтатуÑÑ‹ задач и оперативный план не были Ñконфигурированы.\nÐаÑтоÑтельно рекомендуетÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ конфигурацию по-умолчанию. Ð’Ñ‹ Ñможете её изменить потом." + text_plugin_assets_writable: Каталог реÑурÑов модулей доÑтупен Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи + text_project_destroy_confirmation: Ð’Ñ‹ наÑтаиваете на удалении данного проекта и вÑей отноÑÑщейÑÑ Ðº нему информации? + text_reassign_time_entries: 'ПеренеÑти зарегиÑтрированное Ð²Ñ€ÐµÐ¼Ñ Ð½Ð° Ñледующую задачу:' + text_regexp_info: "например: ^[A-Z0-9]+$" + text_repository_usernames_mapping: "Выберите или обновите Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Redmine, ÑвÑзанного Ñ Ð½Ð°Ð¹Ð´ÐµÐ½Ð½Ñ‹Ð¼Ð¸ именами в журнале хранилища.\nПользователи Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ñ‹Ð¼Ð¸ именами или email в Redmine и хранилище ÑвÑзываютÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки." + text_rmagick_available: ДоÑтупно иÑпользование RMagick (опционально) + text_select_mail_notifications: Выберите дейÑтвиÑ, при которых будет отÑылатьÑÑ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ðµ на Ñлектронную почту. + text_select_project_modules: 'Выберите модули, которые будут иÑпользованы в проекте:' + text_status_changed_by_changeset: "Реализовано в %{value} редакции." + text_subprojects_destroy_warning: "Подпроекты: %{value} также будут удалены." + text_tip_issue_begin_day: дата начала задачи + text_tip_issue_begin_end_day: начало задачи и окончание её в Ñтот же день + text_tip_issue_end_day: дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸ + text_tracker_no_workflow: Ð”Ð»Ñ Ñтого трекера поÑледовательноÑть дейÑтвий не определена + text_unallowed_characters: Запрещенные Ñимволы + text_user_mail_option: "Ð”Ð»Ñ Ð½ÐµÐ²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ñ‹Ñ… проектов, Ð’Ñ‹ будете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ о том, что проÑматриваете или в чем учаÑтвуете (например, задачи, автором которых Ð’Ñ‹ ÑвлÑетеÑÑŒ, или которые Вам назначены)." + text_user_wrote: "%{value} пиÑал(а):" + text_wiki_destroy_confirmation: Ð’Ñ‹ уверены, что хотите удалить данную Wiki и вÑе её Ñодержимое? + text_workflow_edit: Выберите роль и трекер Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ÑледовательноÑти ÑоÑтоÑний + + warning_attachments_not_saved: "%{count} файл(ов) невозможно Ñохранить." + text_wiki_page_destroy_question: Эта Ñтраница имеет %{descendants} дочерних Ñтраниц и их потомков. Что вы хотите предпринÑть? + text_wiki_page_reassign_children: Переопределить дочерние Ñтраницы на текущую Ñтраницу + text_wiki_page_nullify_children: Сделать дочерние Ñтраницы главными Ñтраницами + text_wiki_page_destroy_children: Удалить дочерние Ñтраницы и вÑех их потомков + setting_password_min_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° Ð¿Ð°Ñ€Ð¾Ð»Ñ + field_group_by: Группировать результаты по + mail_subject_wiki_content_updated: "Wiki-Ñтраница '%{id}' была обновлена" + label_wiki_content_added: Добавлена wiki-Ñтраница + mail_subject_wiki_content_added: "Wiki-Ñтраница '%{id}' была добавлена" + mail_body_wiki_content_added: "%{author} добавил(а) wiki-Ñтраницу '%{id}'." + label_wiki_content_updated: Обновлена wiki-Ñтраница + mail_body_wiki_content_updated: "%{author} обновил(а) wiki-Ñтраницу '%{id}'." + permission_add_project: Создание проекта + setting_new_project_user_role_id: Роль, Ð½Ð°Ð·Ð½Ð°Ñ‡Ð°ÐµÐ¼Ð°Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŽ, Ñоздавшему проект + label_view_all_revisions: Показать вÑе ревизии + label_tag: Метка + label_branch: Ветвь + error_no_tracker_in_project: С Ñтим проектом не аÑÑоциирован ни один трекер. Проверьте наÑтройки проекта. + error_no_default_issue_status: Ðе определен ÑÑ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð´Ð°Ñ‡ по умолчанию. Проверьте наÑтройки (Ñм. "ÐдминиÑтрирование -> СтатуÑÑ‹ задач"). + label_group_plural: Группы + label_group: Группа + label_group_new: ÐÐ¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° + label_time_entry_plural: Затраченное Ð²Ñ€ÐµÐ¼Ñ + text_journal_added: "%{label} %{value} добавлен" + field_active: Ðктивно + enumeration_system_activity: СиÑтемное + permission_delete_issue_watchers: Удаление наблюдателей + version_status_closed: закрыт + version_status_locked: заблокирован + version_status_open: открыт + error_can_not_reopen_issue_on_closed_version: Задача, Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ð°Ñ Ðº закрытой верÑии, не Ñможет быть открыта Ñнова + label_user_anonymous: Ðноним + button_move_and_follow: ПеремеÑтить и перейти + setting_default_projects_modules: Включенные по умолчанию модули Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… проектов + setting_gravatar_default: Изображение Gravatar по умолчанию + field_sharing: СовмеÑтное иÑпользование + label_version_sharing_hierarchy: С иерархией проектов + label_version_sharing_system: Со вÑеми проектами + label_version_sharing_descendants: С подпроектами + label_version_sharing_tree: С деревом проектов + label_version_sharing_none: Без ÑовмеÑтного иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ + error_can_not_archive_project: Этот проект не может быть заархивирован + button_duplicate: Дублировать + button_copy_and_follow: Копировать и продолжить + label_copy_source: ИÑточник + setting_issue_done_ratio: РаÑÑчитывать готовноÑть задачи Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Ð¿Ð¾Ð»Ñ + setting_issue_done_ratio_issue_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸ + error_issue_done_ratios_not_updated: Параметр готовноÑть задач не обновлён + error_workflow_copy_target: Выберите целевые трекеры и роли + setting_issue_done_ratio_issue_field: ГотовноÑть задачи + label_copy_same_as_target: То же, что и у цели + label_copy_target: Цель + notice_issue_done_ratios_updated: Параметр «Ð³Ð¾Ñ‚овноÑть» обновлён. + error_workflow_copy_source: Выберите иÑходный трекер или роль + label_update_issue_done_ratios: Обновить готовноÑть задач + setting_start_of_week: День начала недели + label_api_access_key: Ключ доÑтупа к API + text_line_separated: Разрешено неÑколько значений (по одному значению в Ñтроку). + label_revision_id: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ %{value} + permission_view_issues: ПроÑмотр задач + label_display_used_statuses_only: Отображать только те ÑтатуÑÑ‹, которые иÑпользуютÑÑ Ð² Ñтом трекере + label_api_access_key_created_on: Ключ доÑтуп к API был Ñоздан %{value} назад + label_feeds_access_key: Ключ доÑтупа к Atom + notice_api_access_key_reseted: Ваш ключ доÑтупа к API был Ñброшен. + setting_rest_api_enabled: Включить веб-ÑÐµÑ€Ð²Ð¸Ñ REST + button_show: Показать + label_missing_api_access_key: ОтÑутÑтвует ключ доÑтупа к API + label_missing_feeds_access_key: ОтÑутÑтвует ключ доÑтупа к Atom + setting_mail_handler_body_delimiters: Урезать пиÑьмо поÑле одной из Ñтих Ñтрок + permission_add_subprojects: Создание подпроектов + label_subproject_new: Ðовый подпроект + text_own_membership_delete_confirmation: |- + Ð’Ñ‹ ÑобираетеÑÑŒ удалить некоторые или вÑе права, из-за чего могут пропаÑть права на редактирование Ñтого проекта. + Продолжить? + label_close_versions: Закрыть завершённые верÑии + label_board_sticky: Прикреплена + label_board_locked: Заблокирована + field_principal: Ð˜Ð¼Ñ + text_zoom_out: Отдалить + text_zoom_in: Приблизить + notice_unable_delete_time_entry: Ðевозможно удалить запиÑÑŒ журнала. + label_overall_spent_time: Ð’Ñего затрачено времени + label_user_mail_option_none: Ðет Ñобытий + field_member_of_group: Группа назначенного + field_assigned_to_role: Роль назначенного + notice_not_authorized_archived_project: Запрашиваемый проект был архивирован. + label_principal_search: "Ðайти Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ группу:" + label_user_search: "Ðайти пользователÑ:" + field_visible: Видимое + setting_emails_header: Заголовок пиÑьма + + setting_commit_logtime_activity_id: ДейÑтвие Ð´Ð»Ñ ÑƒÑ‡Ñ‘Ñ‚Ð° времени + text_time_logged_by_changeset: Учтено в редакции %{value}. + setting_commit_logtime_enabled: Включить учёт времени + notice_gantt_chart_truncated: Диаграмма будет уÑечена, поÑкольку превышено макÑимальное кол-во Ñлементов, которые могут отображатьÑÑ (%{max}) + setting_gantt_items_limit: МакÑимальное кол-во Ñлементов отображаемых на диаграмме Ганта + field_warn_on_leaving_unsaved: Предупреждать при закрытии Ñтраницы Ñ Ð½ÐµÑохранённым текÑтом + text_warn_on_leaving_unsaved: Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ñтраница Ñодержит неÑохранённый текÑÑ‚, который будет потерÑн, еÑли вы покинете Ñту Ñтраницу. + label_my_queries: Мои Ñохранённые запроÑÑ‹ + text_journal_changed_no_detail: "%{label} обновлено" + label_news_comment_added: Добавлен комментарий к новоÑти + button_expand_all: Развернуть вÑе + button_collapse_all: Свернуть вÑе + label_additional_workflow_transitions_for_assignee: Дополнительные переходы, когда пользователь ÑвлÑетÑÑ Ð¸Ñполнителем + label_additional_workflow_transitions_for_author: Дополнительные переходы, когда пользователь ÑвлÑетÑÑ Ð°Ð²Ñ‚Ð¾Ñ€Ð¾Ð¼ + label_bulk_edit_selected_time_entries: МаÑÑовое изменение выбранных запиÑей затраченного времени + text_time_entries_destroy_confirmation: Ð’Ñ‹ уверены что хотите удалить выбранные запиÑи затраченного времени? + label_role_anonymous: Ðноним + label_role_non_member: Ðе учаÑтник + label_issue_note_added: Примечание добавлено + label_issue_status_updated: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¾Ð±Ð½Ð¾Ð²Ð»Ñ‘Ð½ + label_issue_priority_updated: Приоритет обновлён + label_issues_visibility_own: Задачи Ñозданные или назначенные пользователю + field_issues_visibility: ВидимоÑть задач + label_issues_visibility_all: Ð’Ñе задачи + permission_set_own_issues_private: УÑтановление видимоÑти (общаÑ/чаÑтнаÑ) Ð´Ð»Ñ ÑобÑтвенных задач + field_is_private: ЧаÑÑ‚Ð½Ð°Ñ + permission_set_issues_private: УÑтановление видимоÑти (общаÑ/чаÑтнаÑ) Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ + label_issues_visibility_public: Только общие задачи + text_issues_destroy_descendants_confirmation: Так же будет удалено %{count} задач(и). + field_commit_logs_encoding: Кодировка комментариев в хранилище + field_scm_path_encoding: Кодировка пути + text_scm_path_encoding_note: "По умолчанию: UTF-8" + field_path_to_repository: Путь к хранилищу + field_root_directory: ÐšÐ¾Ñ€Ð½ÐµÐ²Ð°Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ + field_cvs_module: Модуль + field_cvsroot: CVSROOT + text_mercurial_repository_note: Локальное хранилище (например, /hgrepo, c:\hgrepo) + text_scm_command: Команда + text_scm_command_version: ВерÑÐ¸Ñ + label_git_report_last_commit: Указывать поÑледнее Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² и директорий + text_scm_config: Ð’Ñ‹ можете наÑтроить команды SCM в файле config/configuration.yml. ПожалуйÑта, перезапуÑтите приложение поÑле Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñтого файла. + text_scm_command_not_available: Команда ÑиÑтемы ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»Ñ Ð²ÐµÑ€Ñий недоÑтупна. ПожалуйÑта, проверьте наÑтройки в админиÑтративной панели. + notice_issue_successful_create: Задача %{id} Ñоздана. + label_between: между + setting_issue_group_assignment: Разрешить назначение задач группам пользователей + label_diff: Разница(diff) + text_git_repository_note: Хранилище пуÑтое и локальное (Ñ‚.е. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: ПорÑдок Ñортировки + description_project_scope: ОблаÑть поиÑка + description_filter: Фильтр + description_user_mail_notification: ÐаÑтройки почтовых оповещений + description_date_from: Введите дату начала + description_message_content: Содержание ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + description_available_columns: ДоÑтупные Ñтолбцы + description_date_range_interval: Выберите диапазон, уÑтановив дату начала и дату Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ + description_issue_category_reassign: Выберите категорию задачи + description_search: Поле поиÑка + description_notes: ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ + description_date_range_list: Выберите диапазон из ÑпиÑка + description_choose_project: Проекты + description_date_to: Введите дату Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ + description_query_sort_criteria_attribute: Критерий Ñортировки + description_wiki_subpages_reassign: Выбрать новую родительÑкую Ñтраницу + description_selected_columns: Выбранные Ñтолбцы + label_parent_revision: РодительÑкий + label_child_revision: Дочерний + error_scm_annotate_big_text_file: Комментарий невозможен из-за Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸Ñ Ð¼Ð°ÐºÑимального размера текÑтового файла. + setting_default_issue_start_date_to_creation_date: ИÑпользовать текущую дату в качеÑтве даты начала Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… задач + button_edit_section: Редактировать Ñту Ñекцию + setting_repositories_encodings: Кодировка вложений и хранилищ + description_all_columns: Ð’Ñе Ñтолбцы + button_export: ЭкÑпорт + label_export_options: "%{export_format} параметры ÑкÑпорта" + error_attachment_too_big: Этот файл Ð½ÐµÐ»ÑŒÐ·Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ из-за Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸Ñ Ð¼Ð°ÐºÑимального размера файла (%{max_size}) + notice_failed_to_save_time_entries: "Ðевозможно Ñохранить %{count} затраченное Ð²Ñ€ÐµÐ¼Ñ Ð´Ð»Ñ %{total} выбранных: %{ids}." + label_x_issues: + one: "%{count} задача" + few: "%{count} задачи" + many: "%{count} задач" + other: "%{count} Задачи" + label_repository_new: Ðовое хранилище + field_repository_is_default: Хранилище по умолчанию + label_copy_attachments: Копировать Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ + label_item_position: "%{position}/%{count}" + label_completed_versions: Завершенные верÑии + text_project_identifier_info: ДопуÑкаютÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ Ñтрочные латинÑкие буквы (a-z), цифры, тире и подчеркиваниÑ.
    ПоÑле ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¸Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ‚Ð¾Ñ€ изменить нельзÑ. + field_multiple: МножеÑтвенные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ + setting_commit_cross_project_ref: Разрешить ÑÑылатьÑÑ Ð¸ иÑправлÑть задачи во вÑех оÑтальных проектах + text_issue_conflict_resolution_add_notes: Добавить мои Ð¿Ñ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð¸ отказатьÑÑ Ð¾Ñ‚ моих изменений + text_issue_conflict_resolution_overwrite: Применить мои Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ (вÑе предыдущие Ð·Ð°Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ Ñохранены, но некоторые Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ быть перезапиÑаны) + notice_issue_update_conflict: Кто-то изменил задачу, пока вы ее редактировали. + text_issue_conflict_resolution_cancel: Отменить мои Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸ показать задачу заново %{link} + permission_manage_related_issues: Управление ÑвÑзанными задачами + field_auth_source_ldap_filter: Фильтр LDAP + label_search_for_watchers: Ðайти наблюдателей + notice_account_deleted: "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ полноÑтью удалена" + setting_unsubscribe: "Разрешить пользователÑм удалÑть Ñвои учетные запиÑи" + button_delete_my_account: "Удалить мою учетную запиÑÑŒ" + text_account_destroy_confirmation: "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ будет полноÑтью удалена без возможноÑти воÑÑтановлениÑ.\nÐ’Ñ‹ уверены, что хотите продолжить?" + error_session_expired: Срок вашей ÑеÑÑии иÑтек. ПожалуйÑта войдите еще раз + text_session_expiration_settings: "Внимание! Изменение Ñтих наÑтроек может привеÑти к завершению текущих ÑеÑÑий, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð²Ð°ÑˆÑƒ." + setting_session_lifetime: МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ñть ÑеÑÑии + setting_session_timeout: Таймаут ÑеÑÑии + label_session_expiration: Срок иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ ÑеÑÑии + permission_close_project: Закрывать / открывать проекты + label_show_closed_projects: ПроÑматривать закрытые проекты + button_close: Сделать закрытым + button_reopen: Сделать открытым + project_status_active: открытые + project_status_closed: закрытые + project_status_archived: архивированные + text_project_closed: Проект закрыт и находитÑÑ Ð² режиме только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ. + notice_user_successful_create: Пользователь %{id} Ñоздан. + field_core_fields: Стандартные Ð¿Ð¾Ð»Ñ + field_timeout: Таймаут (в Ñекундах) + setting_thumbnails_enabled: Отображать превью Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ð¹ + setting_thumbnails_size: Размер первью (в пикÑелÑÑ…) + label_status_transitions: СтатуÑ-переходы + label_fields_permissions: Права на Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÐµÐ¹ + label_readonly: Ðе изменÑетÑÑ + label_required: ОбÑзательное + text_repository_identifier_info: ДопуÑкаютÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ Ñтрочные латинÑкие буквы (a-z), цифры, тире и подчеркиваниÑ.
    ПоÑле ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¸Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ‚Ð¾Ñ€ изменить нельзÑ. + field_board_parent: РодительÑкий форум + label_attribute_of_project: Проект %{name} + label_attribute_of_author: Ð˜Ð¼Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð° %{name} + label_attribute_of_assigned_to: Ðазначена %{name} + label_attribute_of_fixed_version: ВерÑÐ¸Ñ %{name} + label_copy_subtasks: Копировать подзадачи + label_copied_to: Ñкопирована в + label_copied_from: Ñкопирована Ñ + label_any_issues_in_project: любые задачи в проекте + label_any_issues_not_in_project: любые задачи не в проекте + field_private_notes: Приватный комментарий + permission_view_private_notes: ПроÑмотр приватных комментариев + permission_set_notes_private: Размещение приватных комментариев + label_no_issues_in_project: нет задач в проекте + label_any: вÑе + label_last_n_weeks: + one: "поÑледнÑÑ %{count} неделÑ" + few: "поÑледние %{count} недели" + many: "поÑледние %{count} недель" + other: "поÑледние %{count} недели" + setting_cross_project_subtasks: Разрешить подзадачи в между проектами + label_cross_project_descendants: С подпроектами + label_cross_project_tree: С деревом проектов + label_cross_project_hierarchy: С иерархией проектов + label_cross_project_system: Со вÑеми проектами + button_hide: Скрыть + setting_non_working_week_days: Ðерабочие дни + label_in_the_next_days: в Ñредующие дни + label_in_the_past_days: в прошлые дни + label_attribute_of_user: Пользователь %{name} + text_turning_multiple_off: ЕÑли отключить множеÑтвенные значениÑ, лишние Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¸Ð· ÑпиÑка будут удалены, чтобы оÑталоÑÑŒ только по одному значению. + label_attribute_of_issue: Задача %{name} + permission_add_documents: Добавить документы + permission_edit_documents: Редактировать документы + permission_delete_documents: Удалить документы + label_gantt_progress_line: Ð›Ð¸Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€ÐµÑÑа + setting_jsonp_enabled: Поддержка JSONP + field_inherit_members: ÐаÑледовать учаÑтников + field_closed_on: Закрыта + field_generate_password: Создание Ð¿Ð°Ñ€Ð¾Ð»Ñ + setting_default_projects_tracker_ids: Трекеры по умолчанию Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… проектов + label_total_time: Общее Ð²Ñ€ÐµÐ¼Ñ + notice_account_not_activated_yet: Ð’Ñ‹ пока не имеете активированных учетных запиÑей. + Чтобы получить пиÑьмо Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸ÐµÐ¹, перейдите по ÑÑылке. + notice_account_locked: Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ заблокирована. + label_hidden: Скрытый + label_visibility_private: только мне + label_visibility_roles: только Ñтим ролÑм + label_visibility_public: вÑем пользователÑм + field_must_change_passwd: Изменить пароль при Ñледующем входе + notice_new_password_must_be_different: Ðовый пароль должен отличатьÑÑ Ð¾Ñ‚ текущего + setting_mail_handler_excluded_filenames: ИÑключать Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾ имени + text_convert_available: ДоÑтупно иÑпользование ImageMagick (необÑзательно) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1e/1e34af29d75d4695760428516010f4b5a35d1aad.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1e/1e34af29d75d4695760428516010f4b5a35d1aad.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,85 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Helpers + + # Simple class to compute the start and end dates of a calendar + class Calendar + include Redmine::I18n + attr_reader :startdt, :enddt + + def initialize(date, lang = current_language, period = :month) + @date = date + @events = [] + @ending_events_by_days = {} + @starting_events_by_days = {} + set_language_if_valid lang + case period + when :month + @startdt = Date.civil(date.year, date.month, 1) + @enddt = (@startdt >> 1)-1 + # starts from the first day of the week + @startdt = @startdt - (@startdt.cwday - first_wday)%7 + # ends on the last day of the week + @enddt = @enddt + (last_wday - @enddt.cwday)%7 + when :week + @startdt = date - (date.cwday - first_wday)%7 + @enddt = date + (last_wday - date.cwday)%7 + else + raise 'Invalid period' + end + end + + # Sets calendar events + def events=(events) + @events = events + @ending_events_by_days = @events.group_by {|event| event.due_date} + @starting_events_by_days = @events.group_by {|event| event.start_date} + end + + # Returns events for the given day + def events_on(day) + ((@ending_events_by_days[day] || []) + (@starting_events_by_days[day] || [])).uniq + end + + # Calendar current month + def month + @date.month + end + + # Return the first day of week + # 1 = Monday ... 7 = Sunday + def first_wday + case Setting.start_of_week.to_i + when 1 + @first_dow ||= (1 - 1)%7 + 1 + when 6 + @first_dow ||= (6 - 1)%7 + 1 + when 7 + @first_dow ||= (7 - 1)%7 + 1 + else + @first_dow ||= (l(:general_first_day_of_week).to_i - 1)%7 + 1 + end + end + + def last_wday + @last_dow ||= (first_wday + 5)%7 + 1 + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1e/1e36d3b0b9a7b1dcdb385f344cf25506fbe7bce9.svn-base --- a/.svn/pristine/1e/1e36d3b0b9a7b1dcdb385f344cf25506fbe7bce9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

    <%=l(:label_auth_source)%> (<%= h(@auth_source.auth_method_name) %>)

    - -<%= form_tag({:action => 'update', :id => @auth_source}, :method => :put, :class => "tabular") do %> - <%= render :partial => auth_source_partial_name(@auth_source) %> - <%= submit_tag l(:button_save) %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1e/1e8f4706c992f648980e1bbef5b267351fd0e7ee.svn-base --- a/.svn/pristine/1e/1e8f4706c992f648980e1bbef5b267351fd0e7ee.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ - - - - - - <% if tab[:name] == 'IssueCustomField' %> - - - <% end %> - - - - - <% (@custom_fields_by_type[tab[:name]] || []).sort.each do |custom_field| -%> - "> - - - - <% if tab[:name] == 'IssueCustomField' %> - - - <% end %> - - - - <% end; reset_cycle %> - -
    <%=l(:field_name)%><%=l(:field_field_format)%><%=l(:field_is_required)%><%=l(:field_is_for_all)%><%=l(:label_used_by)%><%=l(:button_sort)%>
    <%= link_to h(custom_field.name), edit_custom_field_path(custom_field) %><%= l(Redmine::CustomFieldFormat.label_for(custom_field.field_format)) %><%= checked_image custom_field.is_required? %><%= checked_image custom_field.is_for_all? %><%= l(:label_x_projects, :count => custom_field.projects.count) if custom_field.is_a? IssueCustomField and !custom_field.is_for_all? %><%= reorder_links('custom_field', {:action => 'update', :id => custom_field}, :put) %> - <%= delete_link custom_field_path(custom_field) %> -
    - -

    <%= link_to l(:label_custom_field_new), new_custom_field_path(:type => tab[:name]), :class => 'icon icon-add' %>

    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1e/1ea62f0251b7b445e6492159a303f580aeb22b77.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1e/1ea62f0251b7b445e6492159a303f580aeb22b77.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,291 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'redmine/scm/adapters/abstract_adapter' +require 'uri' + +module Redmine + module Scm + module Adapters + class SubversionAdapter < AbstractAdapter + + # SVN executable name + SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn" + + class << self + def client_command + @@bin ||= SVN_BIN + end + + def sq_bin + @@sq_bin ||= shell_quote_command + end + + def client_version + @@client_version ||= (svn_binary_version || []) + end + + def client_available + # --xml options are introduced in 1.3. + # http://subversion.apache.org/docs/release-notes/1.3.html + client_version_above?([1, 3]) + end + + def svn_binary_version + scm_version = scm_version_from_command_line.dup + if scm_version.respond_to?(:force_encoding) + scm_version.force_encoding('ASCII-8BIT') + end + if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def scm_version_from_command_line + shellout("#{sq_bin} --version") { |io| io.read }.to_s + end + end + + # Get info about the svn repository + def info + cmd = "#{self.class.sq_bin} info --xml #{target}" + cmd << credentials_string + info = nil + shellout(cmd) do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + doc = parse_xml(output) + # root_url = doc.elements["info/entry/repository/root"].text + info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'], + :lastrev => Revision.new({ + :identifier => doc['info']['entry']['commit']['revision'], + :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime, + :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "") + }) + }) + rescue + end + end + return nil if $? && $?.exitstatus != 0 + info + rescue CommandFailed + return nil + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + def entries(path=nil, identifier=nil, options={}) + path ||= '' + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + entries = Entries.new + cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}" + cmd << credentials_string + shellout(cmd) do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + doc = parse_xml(output) + each_xml_element(doc['lists']['list'], 'entry') do |entry| + commit = entry['commit'] + commit_date = commit['date'] + # Skip directory if there is no commit date (usually that + # means that we don't have read access to it) + next if entry['kind'] == 'dir' && commit_date.nil? + name = entry['name']['__content__'] + entries << Entry.new({:name => URI.unescape(name), + :path => ((path.empty? ? "" : "#{path}/") + name), + :kind => entry['kind'], + :size => ((s = entry['size']) ? s['__content__'].to_i : nil), + :lastrev => Revision.new({ + :identifier => commit['revision'], + :time => Time.parse(commit_date['__content__'].to_s).localtime, + :author => ((a = commit['author']) ? a['__content__'] : nil) + }) + }) + end + rescue Exception => e + logger.error("Error parsing svn output: #{e.message}") + logger.error("Output was:\n #{output}") + end + end + return nil if $? && $?.exitstatus != 0 + logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug? + entries.sort_by_name + end + + def properties(path, identifier=nil) + # proplist xml output supported in svn 1.5.0 and higher + return nil unless self.class.client_version_above?([1, 5, 0]) + + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}" + cmd << credentials_string + properties = {} + shellout(cmd) do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + doc = parse_xml(output) + each_xml_element(doc['properties']['target'], 'property') do |property| + properties[ property['name'] ] = property['__content__'].to_s + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + properties + end + + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + path ||= '' + identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD" + identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1 + revisions = Revisions.new + cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}" + cmd << credentials_string + cmd << " --verbose " if options[:with_paths] + cmd << " --limit #{options[:limit].to_i}" if options[:limit] + cmd << ' ' + target(path) + shellout(cmd) do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + doc = parse_xml(output) + each_xml_element(doc['log'], 'logentry') do |logentry| + paths = [] + each_xml_element(logentry['paths'], 'path') do |path| + paths << {:action => path['action'], + :path => path['__content__'], + :from_path => path['copyfrom-path'], + :from_revision => path['copyfrom-rev'] + } + end if logentry['paths'] && logentry['paths']['path'] + paths.sort! { |x,y| x[:path] <=> y[:path] } + + revisions << Revision.new({:identifier => logentry['revision'], + :author => (logentry['author'] ? logentry['author']['__content__'] : ""), + :time => Time.parse(logentry['date']['__content__'].to_s).localtime, + :message => logentry['msg']['__content__'], + :paths => paths + }) + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + revisions + end + + def diff(path, identifier_from, identifier_to=nil) + path ||= '' + identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : '' + + identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1) + + cmd = "#{self.class.sq_bin} diff -r " + cmd << "#{identifier_to}:" + cmd << "#{identifier_from}" + cmd << " #{target(path)}@#{identifier_from}" + cmd << credentials_string + diff = [] + shellout(cmd) do |io| + io.each_line do |line| + diff << line + end + end + return nil if $? && $?.exitstatus != 0 + diff + end + + def cat(path, identifier=nil) + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}" + cmd << credentials_string + cat = nil + shellout(cmd) do |io| + io.binmode + cat = io.read + end + return nil if $? && $?.exitstatus != 0 + cat + end + + def annotate(path, identifier=nil) + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}" + cmd << credentials_string + blame = Annotate.new + shellout(cmd) do |io| + io.each_line do |line| + next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$} + rev = $1 + blame.add_line($3.rstrip, + Revision.new( + :identifier => rev, + :revision => rev, + :author => $2.strip + )) + end + end + return nil if $? && $?.exitstatus != 0 + blame + end + + private + + def credentials_string + str = '' + str << " --username #{shell_quote(@login)}" unless @login.blank? + str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank? + str << " --no-auth-cache --non-interactive" + str + end + + # Helper that iterates over the child elements of a xml node + # MiniXml returns a hash when a single child is found + # or an array of hashes for multiple children + def each_xml_element(node, name) + if node && node[name] + if node[name].is_a?(Hash) + yield node[name] + else + node[name].each do |element| + yield element + end + end + end + end + + def target(path = '') + base = path.match(/^\//) ? root_url : url + uri = "#{base}/#{path}" + uri = URI.escape(URI.escape(uri), '[]') + shell_quote(uri.gsub(/[?<>\*]/, '')) + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1e/1ec171c5d371f7815766568fcf1c2d5feb1a3958.svn-base --- a/.svn/pristine/1e/1ec171c5d371f7815766568fcf1c2d5feb1a3958.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -<%= "#{issue.tracker.name} ##{issue.id}: #{issue.subject}" %> -<%= issue_url %> - -* <%=l(:field_author)%>: <%= issue.author %> -* <%=l(:field_status)%>: <%= issue.status %> -* <%=l(:field_priority)%>: <%= issue.priority %> -* <%=l(:field_assigned_to)%>: <%= issue.assigned_to %> -* <%=l(:field_category)%>: <%= issue.category %> -* <%=l(:field_fixed_version)%>: <%= issue.fixed_version %> -<% issue.custom_field_values.each do |c| %>* <%= c.custom_field.name %>: <%= show_value(c) %> -<% end -%> ----------------------------------------- -<%= issue.description %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1f/1f2100d78a901e27c2b48a6bdb60bbd56653d6f4.svn-base --- a/.svn/pristine/1f/1f2100d78a901e27c2b48a6bdb60bbd56653d6f4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class AuthSourcesController < ApplicationController - layout 'admin' - menu_item :ldap_authentication - - before_filter :require_admin - - def index - @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10 - end - - def new - klass_name = params[:type] || 'AuthSourceLdap' - @auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) - end - - def create - @auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) - if @auth_source.save - flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' - else - render :action => 'new' - end - end - - def edit - @auth_source = AuthSource.find(params[:id]) - end - - def update - @auth_source = AuthSource.find(params[:id]) - if @auth_source.update_attributes(params[:auth_source]) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' - else - render :action => 'edit' - end - end - - def test_connection - @auth_source = AuthSource.find(params[:id]) - begin - @auth_source.test_connection - flash[:notice] = l(:notice_successful_connection) - rescue Exception => e - flash[:error] = l(:error_unable_to_connect, e.message) - end - redirect_to :action => 'index' - end - - def destroy - @auth_source = AuthSource.find(params[:id]) - unless @auth_source.users.find(:first) - @auth_source.destroy - flash[:notice] = l(:notice_successful_delete) - end - redirect_to :action => 'index' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1f/1f7f86dc321cbe0fcb94878fe50d5b529000cd57.svn-base --- a/.svn/pristine/1f/1f7f86dc321cbe0fcb94878fe50d5b529000cd57.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,152 +0,0 @@ -* Exported the changelog of Pagination code for historical reference. - -* Imported some patches from Rails Trac (others closed as "wontfix"): - #8176, #7325, #7028, #4113. Documentation is much cleaner now and there - are some new unobtrusive features! - -* Extracted Pagination from Rails trunk (r6795) - -# -# ChangeLog for /trunk/actionpack/lib/action_controller/pagination.rb -# -# Generated by Trac 0.10.3 -# 05/20/07 23:48:02 -# - -09/03/06 23:28:54 david [4953] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Docs and deprecation - -08/07/06 12:40:14 bitsweat [4715] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Deprecate direct usage of @params. Update ActionView::Base for - instance var deprecation. - -06/21/06 02:16:11 rick [4476] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Fix indent in pagination documentation. Closes #4990. [Kevin Clark] - -04/25/06 17:42:48 marcel [4268] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Remove all remaining references to @params in the documentation. - -03/16/06 06:38:08 rick [3899] - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - trivial documentation patch for #pagination_links [Francois - Beausoleil] closes #4258 - -02/20/06 03:15:22 david [3620] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/test/activerecord/pagination_test.rb (modified) - * trunk/activerecord/CHANGELOG (modified) - * trunk/activerecord/lib/active_record/base.rb (modified) - * trunk/activerecord/test/base_test.rb (modified) - Added :count option to pagination that'll make it possible for the - ActiveRecord::Base.count call to using something else than * for the - count. Especially important for count queries using DISTINCT #3839 - [skaes]. Added :select option to Base.count that'll allow you to - select something else than * to be counted on. Especially important - for count queries using DISTINCT (closes #3839) [skaes]. - -02/09/06 09:17:40 nzkoz [3553] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/test/active_record_unit.rb (added) - * trunk/actionpack/test/activerecord (added) - * trunk/actionpack/test/activerecord/active_record_assertions_test.rb (added) - * trunk/actionpack/test/activerecord/pagination_test.rb (added) - * trunk/actionpack/test/controller/active_record_assertions_test.rb (deleted) - * trunk/actionpack/test/fixtures/companies.yml (added) - * trunk/actionpack/test/fixtures/company.rb (added) - * trunk/actionpack/test/fixtures/db_definitions (added) - * trunk/actionpack/test/fixtures/db_definitions/sqlite.sql (added) - * trunk/actionpack/test/fixtures/developer.rb (added) - * trunk/actionpack/test/fixtures/developers_projects.yml (added) - * trunk/actionpack/test/fixtures/developers.yml (added) - * trunk/actionpack/test/fixtures/project.rb (added) - * trunk/actionpack/test/fixtures/projects.yml (added) - * trunk/actionpack/test/fixtures/replies.yml (added) - * trunk/actionpack/test/fixtures/reply.rb (added) - * trunk/actionpack/test/fixtures/topic.rb (added) - * trunk/actionpack/test/fixtures/topics.yml (added) - * Fix pagination problems when using include - * Introduce Unit Tests for pagination - * Allow count to work with :include by using count distinct. - - [Kevin Clark & Jeremy Hopple] - -11/05/05 02:10:29 bitsweat [2878] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Update paginator docs. Closes #2744. - -10/16/05 15:42:03 minam [2649] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Update/clean up AP documentation (rdoc) - -08/31/05 00:13:10 ulysses [2078] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Add option to specify the singular name used by pagination. Closes - #1960 - -08/23/05 14:24:15 minam [2041] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Add support for :include with pagination (subject to existing - constraints for :include with :limit and :offset) #1478 - [michael@schubert.cx] - -07/15/05 20:27:38 david [1839] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - More pagination speed #1334 [Stefan Kaes] - -07/14/05 08:02:01 david [1832] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - * trunk/actionpack/test/controller/addresses_render_test.rb (modified) - Made pagination faster #1334 [Stefan Kaes] - -04/13/05 05:40:22 david [1159] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/activerecord/lib/active_record/base.rb (modified) - Fixed pagination to work with joins #1034 [scott@sigkill.org] - -04/02/05 09:11:17 david [1067] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_controller/scaffolding.rb (modified) - * trunk/actionpack/lib/action_controller/templates/scaffolds/list.rhtml (modified) - * trunk/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb (modified) - * trunk/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml (modified) - Added pagination for scaffolding (10 items per page) #964 - [mortonda@dgrmm.net] - -03/31/05 14:46:11 david [1048] - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Improved the message display on the exception handler pages #963 - [Johan Sorensen] - -03/27/05 00:04:07 david [1017] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Fixed that pagination_helper would ignore :params #947 [Sebastian - Kanthak] - -03/22/05 13:09:44 david [976] - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Fixed documentation and prepared for 0.11.0 release - -03/21/05 14:35:36 david [967] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Tweaked the documentation - -03/20/05 23:12:05 david [949] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller.rb (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (added) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (added) - * trunk/activesupport/lib/active_support/core_ext/kernel.rb (added) - Added pagination support through both a controller and helper add-on - #817 [Sam Stephenson] diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1f/1fbae96ef6943c3bcd6afe5e00988dd05b87165c.svn-base --- a/.svn/pristine/1f/1fbae96ef6943c3bcd6afe5e00988dd05b87165c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class IssueStatusesController < ApplicationController - layout 'admin' - - before_filter :require_admin, :except => :index - before_filter :require_admin_or_api_request, :only => :index - accept_api_auth :index - - def index - respond_to do |format| - format.html { - @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position" - render :action => "index", :layout => false if request.xhr? - } - format.api { - @issue_statuses = IssueStatus.all(:order => 'position') - } - end - end - - def new - @issue_status = IssueStatus.new - end - - def create - @issue_status = IssueStatus.new(params[:issue_status]) - if request.post? && @issue_status.save - flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' - else - render :action => 'new' - end - end - - def edit - @issue_status = IssueStatus.find(params[:id]) - end - - def update - @issue_status = IssueStatus.find(params[:id]) - if request.put? && @issue_status.update_attributes(params[:issue_status]) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' - else - render :action => 'edit' - end - end - - def destroy - IssueStatus.find(params[:id]).destroy - redirect_to :action => 'index' - rescue - flash[:error] = l(:error_unable_delete_issue_status) - redirect_to :action => 'index' - end - - def update_issue_done_ratio - if request.post? && IssueStatus.update_issue_done_ratios - flash[:notice] = l(:notice_issue_done_ratios_updated) - else - flash[:error] = l(:error_issue_done_ratios_not_updated) - end - redirect_to :action => 'index' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1f/1fc4113fdbf747b80464d17633b16c0ed0796efd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1f/1fc4113fdbf747b80464d17633b16c0ed0796efd.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +

    <%=l(:label_report_plural)%>

    + +

    <%=@report_title%>

    +<%= render :partial => 'details', :locals => { :data => @data, :field_name => @field, :rows => @rows } %> +
    +<%= link_to l(:button_back), project_issues_report_path(@project) %> + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1f/1fd3808f19058a6dd60b8c79e61039841f985687.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1f/1fd3808f19058a6dd60b8c79e61039841f985687.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,211 @@ +# = Redmine configuration file +# +# Each environment has it's own configuration options. If you are only +# running in production, only the production block needs to be configured. +# Environment specific configuration options override the default ones. +# +# Note that this file needs to be a valid YAML file. +# DO NOT USE TABS! Use 2 spaces instead of tabs for identation. +# +# == Outgoing email settings (email_delivery setting) +# +# === Common configurations +# +# ==== Sendmail command +# +# production: +# email_delivery: +# delivery_method: :sendmail +# +# ==== Simple SMTP server at localhost +# +# production: +# email_delivery: +# delivery_method: :smtp +# smtp_settings: +# address: "localhost" +# port: 25 +# +# ==== SMTP server at example.com using LOGIN authentication and checking HELO for foo.com +# +# production: +# email_delivery: +# delivery_method: :smtp +# smtp_settings: +# address: "example.com" +# port: 25 +# authentication: :login +# domain: 'foo.com' +# user_name: 'myaccount' +# password: 'password' +# +# ==== SMTP server at example.com using PLAIN authentication +# +# production: +# email_delivery: +# delivery_method: :smtp +# smtp_settings: +# address: "example.com" +# port: 25 +# authentication: :plain +# domain: 'example.com' +# user_name: 'myaccount' +# password: 'password' +# +# ==== SMTP server at using TLS (GMail) +# +# This might require some additional configuration. See the guides at: +# http://www.redmine.org/projects/redmine/wiki/EmailConfiguration +# +# production: +# email_delivery: +# delivery_method: :smtp +# smtp_settings: +# enable_starttls_auto: true +# address: "smtp.gmail.com" +# port: 587 +# domain: "smtp.gmail.com" # 'your.domain.com' for GoogleApps +# authentication: :plain +# user_name: "your_email@gmail.com" +# password: "your_password" +# +# +# === More configuration options +# +# See the "Configuration options" at the following website for a list of the +# full options allowed: +# +# http://wiki.rubyonrails.org/rails/pages/HowToSendEmailsWithActionMailer + + +# default configuration options for all environments +default: + # Outgoing emails configuration (see examples above) + email_delivery: + delivery_method: :smtp + smtp_settings: + address: smtp.example.net + port: 25 + domain: example.net + authentication: :login + user_name: "redmine@example.net" + password: "redmine" + + # Absolute path to the directory where attachments are stored. + # The default is the 'files' directory in your Redmine instance. + # Your Redmine instance needs to have write permission on this + # directory. + # Examples: + # attachments_storage_path: /var/redmine/files + # attachments_storage_path: D:/redmine/files + attachments_storage_path: + + # Configuration of the autologin cookie. + # autologin_cookie_name: the name of the cookie (default: autologin) + # autologin_cookie_path: the cookie path (default: /) + # autologin_cookie_secure: true sets the cookie secure flag (default: false) + autologin_cookie_name: + autologin_cookie_path: + autologin_cookie_secure: + + # Configuration of SCM executable command. + # + # Absolute path (e.g. /usr/local/bin/hg) or command name (e.g. hg.exe, bzr.exe) + # On Windows + CRuby, *.cmd, *.bat (e.g. hg.cmd, bzr.bat) does not work. + # + # On Windows + JRuby 1.6.2, path which contains spaces does not work. + # For example, "C:\Program Files\TortoiseHg\hg.exe". + # If you want to this feature, you need to install to the path which does not contains spaces. + # For example, "C:\TortoiseHg\hg.exe". + # + # Examples: + # scm_subversion_command: svn # (default: svn) + # scm_mercurial_command: C:\Program Files\TortoiseHg\hg.exe # (default: hg) + # scm_git_command: /usr/local/bin/git # (default: git) + # scm_cvs_command: cvs # (default: cvs) + # scm_bazaar_command: bzr.exe # (default: bzr) + # scm_darcs_command: darcs-1.0.9-i386-linux # (default: darcs) + # + scm_subversion_command: + scm_mercurial_command: + scm_git_command: + scm_cvs_command: + scm_bazaar_command: + scm_darcs_command: + + # Absolute path to the SCM commands errors (stderr) log file. + # The default is to log in the 'log' directory of your Redmine instance. + # Example: + # scm_stderr_log_file: /var/log/redmine_scm_stderr.log + scm_stderr_log_file: + + # Key used to encrypt sensitive data in the database (SCM and LDAP passwords). + # If you don't want to enable data encryption, just leave it blank. + # WARNING: losing/changing this key will make encrypted data unreadable. + # + # If you want to encrypt existing passwords in your database: + # * set the cipher key here in your configuration file + # * encrypt data using 'rake db:encrypt RAILS_ENV=production' + # + # If you have encrypted data and want to change this key, you have to: + # * decrypt data using 'rake db:decrypt RAILS_ENV=production' first + # * change the cipher key here in your configuration file + # * encrypt data using 'rake db:encrypt RAILS_ENV=production' + database_cipher_key: + + # Set this to false to disable plugins' assets mirroring on startup. + # You can use `rake redmine:plugins:assets` to manually mirror assets + # to public/plugin_assets when you install/upgrade a Redmine plugin. + # + #mirror_plugins_assets_on_startup: false + + # Your secret key for verifying cookie session data integrity. If you + # change this key, all old sessions will become invalid! Make sure the + # secret is at least 30 characters and all random, no regular words or + # you'll be exposed to dictionary attacks. + # + # If you have a load-balancing Redmine cluster, you have to use the + # same secret token on each machine. + #secret_token: 'change it to a long random string' + + # Absolute path (e.g. /usr/bin/convert, c:/im/convert.exe) to + # the ImageMagick's `convert` binary. Used to generate attachment thumbnails. + #imagemagick_convert_command: + + # Configuration of RMagcik font. + # + # Redmine uses RMagcik in order to export gantt png. + # You don't need this setting if you don't install RMagcik. + # + # In CJK (Chinese, Japanese and Korean), + # in order to show CJK characters correctly, + # you need to set this configuration. + # + # Because there is no standard font across platforms in CJK, + # you need to set a font installed in your server. + # + # This setting is not necessary in non CJK. + # + # Examples for Japanese: + # Windows: + # rmagick_font_path: C:\windows\fonts\msgothic.ttc + # Linux: + # rmagick_font_path: /usr/share/fonts/ipa-mincho/ipam.ttf + # + rmagick_font_path: + + # Maximum number of simultaneous AJAX uploads + #max_concurrent_ajax_uploads: 2 + + # Configure OpenIdAuthentication.store + # + # allowed values: :memory, :file, :memcache + #openid_authentication_store: :memory + +# specific configuration options for production environment +# that overrides the default ones +production: + +# specific configuration options for development environment +# that overrides the default ones +development: diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1f/1fe3164d40b2f175bf0aa2fdbd0804dbea4969a1.svn-base --- a/.svn/pristine/1f/1fe3164d40b2f175bf0aa2fdbd0804dbea4969a1.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,578 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'projects_controller' - -# Re-raise errors caught by the controller. -class ProjectsController; def rescue_action(e) raise e end; end - -class ProjectsControllerTest < ActionController::TestCase - fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, - :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, - :attachments, :custom_fields, :custom_values, :time_entries - - def setup - @controller = ProjectsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @request.session[:user_id] = nil - Setting.default_language = 'en' - end - - def test_index - get :index - assert_response :success - assert_template 'index' - assert_not_nil assigns(:projects) - - assert_tag :ul, :child => {:tag => 'li', - :descendant => {:tag => 'a', :content => 'eCookbook'}, - :child => { :tag => 'ul', - :descendant => { :tag => 'a', - :content => 'Child of private child' - } - } - } - - assert_no_tag :a, :content => /Private child of eCookbook/ - end - - def test_index_atom - get :index, :format => 'atom' - assert_response :success - assert_template 'common/feed' - assert_select 'feed>title', :text => 'Redmine: Latest projects' - assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_condition(User.current)) - end - - context "#index" do - context "by non-admin user with view_time_entries permission" do - setup do - @request.session[:user_id] = 3 - end - should "show overall spent time link" do - get :index - assert_template 'index' - assert_tag :a, :attributes => {:href => '/time_entries'} - end - end - - context "by non-admin user without view_time_entries permission" do - setup do - Role.find(2).remove_permission! :view_time_entries - Role.non_member.remove_permission! :view_time_entries - Role.anonymous.remove_permission! :view_time_entries - @request.session[:user_id] = 3 - end - should "not show overall spent time link" do - get :index - assert_template 'index' - assert_no_tag :a, :attributes => {:href => '/time_entries'} - end - end - end - - context "#new" do - context "by admin user" do - setup do - @request.session[:user_id] = 1 - end - - should "accept get" do - get :new - assert_response :success - assert_template 'new' - end - - end - - context "by non-admin user with add_project permission" do - setup do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - end - - should "accept get" do - get :new - assert_response :success - assert_template 'new' - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'} - end - end - - context "by non-admin user with add_subprojects permission" do - setup do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - end - - should "accept get" do - get :new, :parent_id => 'ecookbook' - assert_response :success - assert_template 'new' - # parent project selected - assert_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}} - # no empty value - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => ''}} - end - end - - end - - context "POST :create" do - context "by admin user" do - setup do - @request.session[:user_id] = 1 - end - - should "create a new project" do - post :create, - :project => { - :name => "blog", - :description => "weblog", - :homepage => 'http://weblog', - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - # an issue custom field that is not for all project - :issue_custom_field_ids => ['9'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert project.active? - assert_equal 'weblog', project.description - assert_equal 'http://weblog', project.homepage - assert_equal true, project.is_public? - assert_nil project.parent - assert_equal 'Beta', project.custom_value_for(3).value - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - assert project.issue_custom_fields.include?(IssueCustomField.find(9)) - end - - should "create a new subproject" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal Project.find(1), project.parent - end - - should "continue" do - assert_difference 'Project.count' do - post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue' - end - assert_redirected_to '/projects/new?' - end - end - - context "by non-admin user with add_project permission" do - setup do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - end - - should "accept create a Project" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal 'weblog', project.description - assert_equal true, project.is_public? - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - - # User should be added as a project member - assert User.find(9).member_of?(project) - assert_equal 1, project.members.size - end - - should "fail with parent_id" do - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - end - - context "by non-admin user with add_subprojects permission" do - setup do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - end - - should "create a project with a parent_id" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - project = Project.find_by_name('blog') - end - - should "fail without parent_id" do - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' } - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - - should "fail with unauthorized parent_id" do - assert !User.find(2).member_of?(Project.find(6)) - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 6 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - end - end - - def test_create_should_preserve_modules_on_validation_failure - with_settings :default_projects_modules => ['issue_tracking', 'repository'] do - @request.session[:user_id] = 1 - assert_no_difference 'Project.count' do - post :create, :project => { - :name => "blog", - :identifier => "", - :enabled_module_names => %w(issue_tracking news) - } - end - assert_response :success - project = assigns(:project) - assert_equal %w(issue_tracking news), project.enabled_module_names.sort - end - end - - def test_show_by_id - get :show, :id => 1 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - end - - def test_show_by_identifier - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) - - assert_tag 'li', :content => /Development status/ - end - - def test_show_should_not_display_hidden_custom_fields - ProjectCustomField.find_by_name('Development status').update_attribute :visible, false - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - - assert_no_tag 'li', :content => /Development status/ - end - - def test_show_should_not_fail_when_custom_values_are_nil - project = Project.find_by_identifier('ecookbook') - project.custom_values.first.update_attribute(:value, nil) - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) - end - - def show_archived_project_should_be_denied - project = Project.find_by_identifier('ecookbook') - project.archive! - - get :show, :id => 'ecookbook' - assert_response 403 - assert_nil assigns(:project) - assert_tag :tag => 'p', :content => /archived/ - end - - def test_private_subprojects_hidden - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_no_tag :tag => 'a', :content => /Private child/ - end - - def test_private_subprojects_visible - @request.session[:user_id] = 2 # manager who is a member of the private subproject - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_tag :tag => 'a', :content => /Private child/ - end - - def test_settings - @request.session[:user_id] = 2 # manager - get :settings, :id => 1 - assert_response :success - assert_template 'settings' - end - - def test_settings_should_be_denied_for_member_on_closed_project - Project.find(1).close - @request.session[:user_id] = 2 # manager - - get :settings, :id => 1 - assert_response 403 - end - - def test_settings_should_be_denied_for_anonymous_on_closed_project - Project.find(1).close - - get :settings, :id => 1 - assert_response 302 - end - - def test_update - @request.session[:user_id] = 2 # manager - post :update, :id => 1, :project => {:name => 'Test changed name', - :issue_custom_field_ids => ['']} - assert_redirected_to '/projects/ecookbook/settings' - project = Project.find(1) - assert_equal 'Test changed name', project.name - end - - def test_update_with_failure - @request.session[:user_id] = 2 # manager - post :update, :id => 1, :project => {:name => ''} - assert_response :success - assert_template 'settings' - assert_error_tag :content => /name can't be blank/i - end - - def test_update_should_be_denied_for_member_on_closed_project - Project.find(1).close - @request.session[:user_id] = 2 # manager - - post :update, :id => 1, :project => {:name => 'Closed'} - assert_response 403 - assert_equal 'eCookbook', Project.find(1).name - end - - def test_update_should_be_denied_for_anonymous_on_closed_project - Project.find(1).close - - post :update, :id => 1, :project => {:name => 'Closed'} - assert_response 302 - assert_equal 'eCookbook', Project.find(1).name - end - - def test_modules - @request.session[:user_id] = 2 - Project.find(1).enabled_module_names = ['issue_tracking', 'news'] - - post :modules, :id => 1, :enabled_module_names => ['issue_tracking', 'repository', 'documents'] - assert_redirected_to '/projects/ecookbook/settings/modules' - assert_equal ['documents', 'issue_tracking', 'repository'], Project.find(1).enabled_module_names.sort - end - - def test_destroy_without_confirmation - @request.session[:user_id] = 1 # admin - delete :destroy, :id => 1 - assert_response :success - assert_template 'destroy' - assert_not_nil Project.find_by_id(1) - assert_tag :tag => 'strong', - :content => ['Private child of eCookbook', - 'Child of private child, eCookbook Subproject 1', - 'eCookbook Subproject 2'].join(', ') - end - - def test_destroy - @request.session[:user_id] = 1 # admin - delete :destroy, :id => 1, :confirm => 1 - assert_redirected_to '/admin/projects' - assert_nil Project.find_by_id(1) - end - - def test_archive - @request.session[:user_id] = 1 # admin - post :archive, :id => 1 - assert_redirected_to '/admin/projects' - assert !Project.find(1).active? - end - - def test_archive_with_failure - @request.session[:user_id] = 1 - Project.any_instance.stubs(:archive).returns(false) - post :archive, :id => 1 - assert_redirected_to '/admin/projects' - assert_match /project cannot be archived/i, flash[:error] - end - - def test_unarchive - @request.session[:user_id] = 1 # admin - Project.find(1).archive - post :unarchive, :id => 1 - assert_redirected_to '/admin/projects' - assert Project.find(1).active? - end - - def test_close - @request.session[:user_id] = 2 - post :close, :id => 1 - assert_redirected_to '/projects/ecookbook' - assert_equal Project::STATUS_CLOSED, Project.find(1).status - end - - def test_reopen - Project.find(1).close - @request.session[:user_id] = 2 - post :reopen, :id => 1 - assert_redirected_to '/projects/ecookbook' - assert Project.find(1).active? - end - - def test_project_breadcrumbs_should_be_limited_to_3_ancestors - CustomField.delete_all - parent = nil - 6.times do |i| - p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}") - p.set_parent!(parent) - get :show, :id => p - assert_tag :h1, :parent => { :attributes => {:id => 'header'}}, - :children => { :count => [i, 3].min, - :only => { :tag => 'a' } } - - parent = p - end - end - - def test_get_copy - @request.session[:user_id] = 1 # admin - get :copy, :id => 1 - assert_response :success - assert_template 'copy' - assert assigns(:project) - assert_equal Project.find(1).description, assigns(:project).description - assert_nil assigns(:project).id - - assert_tag :tag => 'input', - :attributes => {:name => 'project[enabled_module_names][]', :value => 'issue_tracking'} - end - - def test_get_copy_with_invalid_source_should_respond_with_404 - @request.session[:user_id] = 1 - get :copy, :id => 99 - assert_response 404 - end - - def test_post_copy_should_copy_requested_items - @request.session[:user_id] = 1 # admin - CustomField.delete_all - - assert_difference 'Project.count' do - post :copy, :id => 1, - :project => { - :name => 'Copy', - :identifier => 'unique-copy', - :tracker_ids => ['1', '2', '3', ''], - :enabled_module_names => %w(issue_tracking time_tracking) - }, - :only => %w(issues versions) - end - project = Project.find('unique-copy') - source = Project.find(1) - assert_equal %w(issue_tracking time_tracking), project.enabled_module_names.sort - - assert_equal source.versions.count, project.versions.count, "All versions were not copied" - assert_equal source.issues.count, project.issues.count, "All issues were not copied" - assert_equal 0, project.members.count - end - - def test_post_copy_should_redirect_to_settings_when_successful - @request.session[:user_id] = 1 # admin - post :copy, :id => 1, :project => {:name => 'Copy', :identifier => 'unique-copy'} - assert_response :redirect - assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy' - end - - def test_jump_should_redirect_to_active_tab - get :show, :id => 1, :jump => 'issues' - assert_redirected_to '/projects/ecookbook/issues' - end - - def test_jump_should_not_redirect_to_inactive_tab - get :show, :id => 3, :jump => 'documents' - assert_response :success - assert_template 'show' - end - - def test_jump_should_not_redirect_to_unknown_tab - get :show, :id => 3, :jump => 'foobar' - assert_response :success - assert_template 'show' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/1f/1feeecc995893880348eca32af1a4a9f7f1dfb97.svn-base --- a/.svn/pristine/1f/1feeecc995893880348eca32af1a4a9f7f1dfb97.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -require 'rake' -require 'rake/testtask' -require 'rake/rdoctask' - -desc 'Default: run unit tests.' -task :default => :test - -desc 'Test the classic_pagination plugin.' -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.pattern = 'test/**/*_test.rb' - t.verbose = true -end - -desc 'Generate documentation for the classic_pagination plugin.' -Rake::RDocTask.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'Pagination' - rdoc.options << '--line-numbers' << '--inline-source' - rdoc.rdoc_files.include('README') - rdoc.rdoc_files.include('lib/**/*.rb') -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/2006d03f57217f185b674373ebf261dbb0c3c839.svn-base --- a/.svn/pristine/20/2006d03f57217f185b674373ebf261dbb0c3c839.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,200 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::MembershipsTest < ActionController::IntegrationTest - fixtures :projects, :users, :roles, :members, :member_roles - - def setup - Setting.rest_api_enabled = '1' - end - - context "/projects/:project_id/memberships" do - context "GET" do - context "xml" do - should "return memberships" do - get '/projects/1/memberships.xml', {}, credentials('jsmith') - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'memberships', - :attributes => {:type => 'array'}, - :child => { - :tag => 'membership', - :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'user', - :attributes => {:id => '3', :name => 'Dave Lopper'}, - :sibling => { - :tag => 'roles', - :child => { - :tag => 'role', - :attributes => {:id => '2', :name => 'Developer'} - } - } - } - } - } - end - end - - context "json" do - should "return memberships" do - get '/projects/1/memberships.json', {}, credentials('jsmith') - - assert_response :success - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_equal({ - "memberships" => - [{"id"=>1, - "project" => {"name"=>"eCookbook", "id"=>1}, - "roles" => [{"name"=>"Manager", "id"=>1}], - "user" => {"name"=>"John Smith", "id"=>2}}, - {"id"=>2, - "project" => {"name"=>"eCookbook", "id"=>1}, - "roles" => [{"name"=>"Developer", "id"=>2}], - "user" => {"name"=>"Dave Lopper", "id"=>3}}], - "limit" => 25, - "total_count" => 2, - "offset" => 0}, - json) - end - end - end - - context "POST" do - context "xml" do - should "create membership" do - assert_difference 'Member.count' do - post '/projects/1/memberships.xml', {:membership => {:user_id => 7, :role_ids => [2,3]}}, credentials('jsmith') - - assert_response :created - end - end - - should "return errors on failure" do - assert_no_difference 'Member.count' do - post '/projects/1/memberships.xml', {:membership => {:role_ids => [2,3]}}, credentials('jsmith') - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Principal can't be blank"} - end - end - end - end - end - - context "/memberships/:id" do - context "GET" do - context "xml" do - should "return the membership" do - get '/memberships/2.xml', {}, credentials('jsmith') - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'membership', - :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'user', - :attributes => {:id => '3', :name => 'Dave Lopper'}, - :sibling => { - :tag => 'roles', - :child => { - :tag => 'role', - :attributes => {:id => '2', :name => 'Developer'} - } - } - } - } - end - end - - context "json" do - should "return the membership" do - get '/memberships/2.json', {}, credentials('jsmith') - - assert_response :success - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_equal( - {"membership" => { - "id" => 2, - "project" => {"name"=>"eCookbook", "id"=>1}, - "roles" => [{"name"=>"Developer", "id"=>2}], - "user" => {"name"=>"Dave Lopper", "id"=>3}} - }, - json) - end - end - end - - context "PUT" do - context "xml" do - should "update membership" do - assert_not_equal [1,2], Member.find(2).role_ids.sort - assert_no_difference 'Member.count' do - put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [1,2]}}, credentials('jsmith') - - assert_response :ok - assert_equal '', @response.body - end - member = Member.find(2) - assert_equal [1,2], member.role_ids.sort - end - - should "return errors on failure" do - put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [99]}}, credentials('jsmith') - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => /member_roles is invalid/} - end - end - end - - context "DELETE" do - context "xml" do - should "destroy membership" do - assert_difference 'Member.count', -1 do - delete '/memberships/2.xml', {}, credentials('jsmith') - - assert_response :ok - assert_equal '', @response.body - end - assert_nil Member.find_by_id(2) - end - - should "respond with 422 on failure" do - assert_no_difference 'Member.count' do - # A membership with an inherited role can't be deleted - Member.find(2).member_roles.first.update_attribute :inherited_from, 99 - delete '/memberships/2.xml', {}, credentials('jsmith') - - assert_response :unprocessable_entity - end - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/201ba8bb33fe7d932e25d4d032482548144aca3a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/20/201ba8bb33fe7d932e25d4d032482548144aca3a.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,412 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'redmine/scm/adapters/abstract_adapter' + +module Redmine + module Scm + module Adapters + class GitAdapter < AbstractAdapter + + # Git executable name + GIT_BIN = Redmine::Configuration['scm_git_command'] || "git" + + class GitBranch < Branch + attr_accessor :is_default + end + + class << self + def client_command + @@bin ||= GIT_BIN + end + + def sq_bin + @@sq_bin ||= shell_quote_command + end + + def client_version + @@client_version ||= (scm_command_version || []) + end + + def client_available + !client_version.empty? + end + + def scm_command_version + scm_version = scm_version_from_command_line.dup + if scm_version.respond_to?(:force_encoding) + scm_version.force_encoding('ASCII-8BIT') + end + if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def scm_version_from_command_line + shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s + end + end + + def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) + super + @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding + end + + def path_encoding + @path_encoding + end + + def info + begin + Info.new(:root_url => url, :lastrev => lastrev('',nil)) + rescue + nil + end + end + + def branches + return @branches if @branches + @branches = [] + cmd_args = %w|branch --no-color --verbose --no-abbrev| + git_cmd(cmd_args) do |io| + io.each_line do |line| + branch_rev = line.match('\s*(\*?)\s*(.*?)\s*([0-9a-f]{40}).*$') + bran = GitBranch.new(branch_rev[2]) + bran.revision = branch_rev[3] + bran.scmid = branch_rev[3] + bran.is_default = ( branch_rev[1] == '*' ) + @branches << bran + end + end + @branches.sort! + rescue ScmCommandAborted + nil + end + + def tags + return @tags if @tags + cmd_args = %w|tag| + git_cmd(cmd_args) do |io| + @tags = io.readlines.sort!.map{|t| t.strip} + end + rescue ScmCommandAborted + nil + end + + def default_branch + bras = self.branches + return nil if bras.nil? + default_bras = bras.select{|x| x.is_default == true} + return default_bras.first.to_s if ! default_bras.empty? + master_bras = bras.select{|x| x.to_s == 'master'} + master_bras.empty? ? bras.first.to_s : 'master' + end + + def entry(path=nil, identifier=nil) + parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?} + search_path = parts[0..-2].join('/') + search_name = parts[-1] + if search_path.blank? && search_name.blank? + # Root entry + Entry.new(:path => '', :kind => 'dir') + else + # Search for the entry in the parent directory + es = entries(search_path, identifier, + options = {:report_last_commit => false}) + es ? es.detect {|e| e.name == search_name} : nil + end + end + + def entries(path=nil, identifier=nil, options={}) + path ||= '' + p = scm_iconv(@path_encoding, 'UTF-8', path) + entries = Entries.new + cmd_args = %w|ls-tree -l| + cmd_args << "HEAD:#{p}" if identifier.nil? + cmd_args << "#{identifier}:#{p}" if identifier + git_cmd(cmd_args) do |io| + io.each_line do |line| + e = line.chomp.to_s + if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/ + type = $1 + sha = $2 + size = $3 + name = $4 + if name.respond_to?(:force_encoding) + name.force_encoding(@path_encoding) + end + full_path = p.empty? ? name : "#{p}/#{name}" + n = scm_iconv('UTF-8', @path_encoding, name) + full_p = scm_iconv('UTF-8', @path_encoding, full_path) + entries << Entry.new({:name => n, + :path => full_p, + :kind => (type == "tree") ? 'dir' : 'file', + :size => (type == "tree") ? nil : size, + :lastrev => options[:report_last_commit] ? + lastrev(full_path, identifier) : Revision.new + }) unless entries.detect{|entry| entry.name == name} + end + end + end + entries.sort_by_name + rescue ScmCommandAborted + nil + end + + def lastrev(path, rev) + return nil if path.nil? + cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1| + cmd_args << rev if rev + cmd_args << "--" << path unless path.empty? + lines = [] + git_cmd(cmd_args) { |io| lines = io.readlines } + begin + id = lines[0].split[1] + author = lines[1].match('Author:\s+(.*)$')[1] + time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1]) + + Revision.new({ + :identifier => id, + :scmid => id, + :author => author, + :time => time, + :message => nil, + :paths => nil + }) + rescue NoMethodError => e + logger.error("The revision '#{path}' has a wrong format") + return nil + end + rescue ScmCommandAborted + nil + end + + def revisions(path, identifier_from, identifier_to, options={}) + revs = Revisions.new + cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller --parents --stdin| + cmd_args << "--reverse" if options[:reverse] + cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit] + cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty? + revisions = [] + if identifier_from || identifier_to + revisions << "" + revisions[0] << "#{identifier_from}.." if identifier_from + revisions[0] << "#{identifier_to}" if identifier_to + else + unless options[:includes].blank? + revisions += options[:includes] + end + unless options[:excludes].blank? + revisions += options[:excludes].map{|r| "^#{r}"} + end + end + + git_cmd(cmd_args, {:write_stdin => true}) do |io| + io.binmode + io.puts(revisions.join("\n")) + io.close_write + files=[] + changeset = {} + parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files + + io.each_line do |line| + if line =~ /^commit ([0-9a-f]{40})(( [0-9a-f]{40})*)$/ + key = "commit" + value = $1 + parents_str = $2 + if (parsing_descr == 1 || parsing_descr == 2) + parsing_descr = 0 + revision = Revision.new({ + :identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files, + :parents => changeset[:parents] + }) + if block_given? + yield revision + else + revs << revision + end + changeset = {} + files = [] + end + changeset[:commit] = $1 + unless parents_str.nil? or parents_str == "" + changeset[:parents] = parents_str.strip.split(' ') + end + 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)\t(.+)$/ + parsing_descr = 2 + fileaction = $1 + filepath = $2 + p = scm_iconv('UTF-8', @path_encoding, filepath) + files << {:action => fileaction, :path => p} + elsif (parsing_descr == 1 || parsing_descr == 2) \ + && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/ + parsing_descr = 2 + fileaction = $1 + filepath = $3 + p = scm_iconv('UTF-8', @path_encoding, filepath) + files << {:action => fileaction, :path => p} + 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 = Revision.new({ + :identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files, + :parents => changeset[:parents] + }) + if block_given? + yield revision + else + revs << revision + end + end + end + revs + rescue ScmCommandAborted => e + err_msg = "git log error: #{e.message}" + logger.error(err_msg) + if block_given? + raise CommandFailed, err_msg + else + revs + end + end + + def diff(path, identifier_from, identifier_to=nil) + path ||= '' + cmd_args = [] + if identifier_to + cmd_args << "diff" << "--no-color" << identifier_to << identifier_from + else + cmd_args << "show" << "--no-color" << identifier_from + end + cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty? + diff = [] + git_cmd(cmd_args) do |io| + io.each_line do |line| + diff << line + end + end + diff + rescue ScmCommandAborted + nil + end + + def annotate(path, identifier=nil) + identifier = 'HEAD' if identifier.blank? + cmd_args = %w|blame --encoding=UTF-8| + cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path) + blame = Annotate.new + content = nil + git_cmd(cmd_args) { |io| io.binmode; content = io.read } + # git annotates binary files + return nil if content.is_binary_data? + identifier = '' + # git shows commit author on the first occurrence only + authors_by_commit = {} + content.split("\n").each do |line| + if line =~ /^([0-9a-f]{39,40})\s.*/ + identifier = $1 + elsif line =~ /^author (.+)/ + authors_by_commit[identifier] = $1.strip + elsif line =~ /^\t(.*)/ + blame.add_line($1, Revision.new( + :identifier => identifier, + :revision => identifier, + :scmid => identifier, + :author => authors_by_commit[identifier] + )) + identifier = '' + author = '' + end + end + blame + rescue ScmCommandAborted + nil + end + + def cat(path, identifier=nil) + if identifier.nil? + identifier = 'HEAD' + end + cmd_args = %w|show --no-color| + cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}" + cat = nil + git_cmd(cmd_args) do |io| + io.binmode + cat = io.read + end + cat + rescue ScmCommandAborted + nil + end + + class Revision < Redmine::Scm::Adapters::Revision + # Returns the readable identifier + def format_identifier + identifier[0,8] + end + end + + def git_cmd(args, options = {}, &block) + repo_path = root_url || url + full_args = ['--git-dir', repo_path] + if self.class.client_version_above?([1, 7, 2]) + full_args << '-c' << 'core.quotepath=false' + full_args << '-c' << 'log.decorate=no' + end + full_args += args + ret = shellout( + self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '), + options, + &block + ) + if $? && $?.exitstatus != 0 + raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}" + end + ret + end + private :git_cmd + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/2048f249df3ed3cda47a1ab8ba2f127935c58466.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/20/2048f249df3ed3cda47a1ab8ba2f127935c58466.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,248 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class MyControllerTest < ActionController::TestCase + fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, + :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, :auth_sources + + def setup + @request.session[:user_id] = 2 + end + + def test_index + get :index + assert_response :success + assert_template 'page' + end + + def test_page + get :page + assert_response :success + assert_template 'page' + end + + def test_page_with_timelog_block + preferences = User.find(2).pref + preferences[:my_page_layout] = {'top' => ['timelog']} + preferences.save! + TimeEntry.create!(:user => User.find(2), :spent_on => Date.yesterday, :issue_id => 1, :hours => 2.5, :activity_id => 10) + + get :page + assert_response :success + assert_select 'tr.time-entry' do + assert_select 'td.subject a[href=/issues/1]' + assert_select 'td.hours', :text => '2.50' + end + end + + def test_page_with_all_blocks + blocks = MyController::BLOCKS.keys + preferences = User.find(2).pref + preferences[:my_page_layout] = {'top' => blocks} + preferences.save! + + get :page + assert_response :success + assert_select 'div.mypage-box', blocks.size + end + + def test_my_account_should_show_editable_custom_fields + get :account + assert_response :success + assert_template 'account' + assert_equal User.find(2), assigns(:user) + + assert_tag :input, :attributes => { :name => 'user[custom_field_values][4]'} + end + + def test_my_account_should_not_show_non_editable_custom_fields + UserCustomField.find(4).update_attribute :editable, false + + get :account + assert_response :success + assert_template 'account' + assert_equal User.find(2), assigns(:user) + + assert_no_tag :input, :attributes => { :name => 'user[custom_field_values][4]'} + end + + def test_update_account + post :account, + :user => { + :firstname => "Joe", + :login => "root", + :admin => 1, + :group_ids => ['10'], + :custom_field_values => {"4" => "0100562500"} + } + + assert_redirected_to '/my/account' + user = User.find(2) + assert_equal user, assigns(:user) + assert_equal "Joe", user.firstname + assert_equal "jsmith", user.login + assert_equal "0100562500", user.custom_value_for(4).value + # ignored + assert !user.admin? + assert user.groups.empty? + end + + def test_my_account_should_show_destroy_link + get :account + assert_select 'a[href=/my/account/destroy]' + end + + def test_get_destroy_should_display_the_destroy_confirmation + get :destroy + assert_response :success + assert_template 'destroy' + assert_select 'form[action=/my/account/destroy]' do + assert_select 'input[name=confirm]' + end + end + + def test_post_destroy_without_confirmation_should_not_destroy_account + assert_no_difference 'User.count' do + post :destroy + end + assert_response :success + assert_template 'destroy' + end + + def test_post_destroy_without_confirmation_should_destroy_account + assert_difference 'User.count', -1 do + post :destroy, :confirm => '1' + end + assert_redirected_to '/' + assert_match /deleted/i, flash[:notice] + end + + def test_post_destroy_with_unsubscribe_not_allowed_should_not_destroy_account + User.any_instance.stubs(:own_account_deletable?).returns(false) + + assert_no_difference 'User.count' do + post :destroy, :confirm => '1' + end + assert_redirected_to '/my/account' + end + + def test_change_password + get :password + assert_response :success + assert_template 'password' + + # non matching password confirmation + post :password, :password => 'jsmith', + :new_password => 'secret123', + :new_password_confirmation => 'secret1234' + assert_response :success + assert_template 'password' + assert_error_tag :content => /Password doesn't match confirmation/ + + # wrong password + post :password, :password => 'wrongpassword', + :new_password => 'secret123', + :new_password_confirmation => 'secret123' + assert_response :success + assert_template 'password' + assert_equal 'Wrong password', flash[:error] + + # good password + post :password, :password => 'jsmith', + :new_password => 'secret123', + :new_password_confirmation => 'secret123' + assert_redirected_to '/my/account' + assert User.try_to_login('jsmith', 'secret123') + end + + def test_change_password_should_redirect_if_user_cannot_change_its_password + User.find(2).update_attribute(:auth_source_id, 1) + + get :password + assert_not_nil flash[:error] + assert_redirected_to '/my/account' + end + + def test_page_layout + get :page_layout + assert_response :success + assert_template 'page_layout' + end + + def test_add_block + post :add_block, :block => 'issuesreportedbyme' + assert_redirected_to '/my/page_layout' + assert User.find(2).pref[:my_page_layout]['top'].include?('issuesreportedbyme') + end + + def test_add_invalid_block_should_redirect + post :add_block, :block => 'invalid' + assert_redirected_to '/my/page_layout' + end + + def test_remove_block + post :remove_block, :block => 'issuesassignedtome' + assert_redirected_to '/my/page_layout' + assert !User.find(2).pref[:my_page_layout].values.flatten.include?('issuesassignedtome') + end + + def test_order_blocks + xhr :post, :order_blocks, :group => 'left', 'blocks' => ['documents', 'calendar', 'latestnews'] + assert_response :success + assert_equal ['documents', 'calendar', 'latestnews'], User.find(2).pref[:my_page_layout]['left'] + end + + def test_reset_rss_key_with_existing_key + @previous_token_value = User.find(2).rss_key # Will generate one if it's missing + post :reset_rss_key + + assert_not_equal @previous_token_value, User.find(2).rss_key + assert User.find(2).rss_token + assert_match /reset/, flash[:notice] + assert_redirected_to '/my/account' + end + + def test_reset_rss_key_without_existing_key + assert_nil User.find(2).rss_token + post :reset_rss_key + + assert User.find(2).rss_token + assert_match /reset/, flash[:notice] + assert_redirected_to '/my/account' + end + + def test_reset_api_key_with_existing_key + @previous_token_value = User.find(2).api_key # Will generate one if it's missing + post :reset_api_key + + assert_not_equal @previous_token_value, User.find(2).api_key + assert User.find(2).api_token + assert_match /reset/, flash[:notice] + assert_redirected_to '/my/account' + end + + def test_reset_api_key_without_existing_key + assert_nil User.find(2).api_token + post :reset_api_key + + assert User.find(2).api_token + assert_match /reset/, flash[:notice] + assert_redirected_to '/my/account' + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/205de64e23e7f44f01ee402d6427c247c8347982.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/20/205de64e23e7f44f01ee402d6427c247c8347982.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,6 @@ +<%= title [l(@enumeration.option_name), enumerations_path], @enumeration.name %> + +<%= labelled_form_for :enumeration, @enumeration, :url => enumeration_path(@enumeration), :html => {:method => :put} do |f| %> + <%= render :partial => 'form', :locals => {:f => f} %> + <%= submit_tag l(:button_save) %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/205eb718e22b02ea59cca1358f48bb87e5bd7f8d.svn-base --- a/.svn/pristine/20/205eb718e22b02ea59cca1358f48bb87e5bd7f8d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,280 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'iconv' - -class Changeset < ActiveRecord::Base - belongs_to :repository - belongs_to :user - has_many :filechanges, :class_name => 'Change', :dependent => :delete_all - has_and_belongs_to_many :issues - has_and_belongs_to_many :parents, - :class_name => "Changeset", - :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}", - :association_foreign_key => 'parent_id', :foreign_key => 'changeset_id' - has_and_belongs_to_many :children, - :class_name => "Changeset", - :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}", - :association_foreign_key => 'changeset_id', :foreign_key => 'parent_id' - - acts_as_event :title => Proc.new {|o| o.title}, - :description => :long_comments, - :datetime => :committed_on, - :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}} - - acts_as_searchable :columns => 'comments', - :include => {:repository => :project}, - :project_key => "#{Repository.table_name}.project_id", - :date_column => 'committed_on' - - acts_as_activity_provider :timestamp => "#{table_name}.committed_on", - :author_key => :user_id, - :find_options => {:include => [:user, {:repository => :project}]} - - validates_presence_of :repository_id, :revision, :committed_on, :commit_date - validates_uniqueness_of :revision, :scope => :repository_id - validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true - - scope :visible, - lambda {|*args| { :include => {:repository => :project}, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } } - - after_create :scan_for_issues - before_create :before_create_cs - - def revision=(r) - write_attribute :revision, (r.nil? ? nil : r.to_s) - end - - # Returns the identifier of this changeset; depending on repository backends - def identifier - if repository.class.respond_to? :changeset_identifier - repository.class.changeset_identifier self - else - revision.to_s - end - end - - def committed_on=(date) - self.commit_date = date - super - end - - # Returns the readable identifier - def format_identifier - if repository.class.respond_to? :format_changeset_identifier - repository.class.format_changeset_identifier self - else - identifier - end - end - - def project - repository.project - end - - def author - user || committer.to_s.split('<').first - end - - def before_create_cs - self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding) - self.comments = self.class.normalize_comments( - self.comments, repository.repo_log_encoding) - self.user = repository.find_committer_user(self.committer) - end - - def scan_for_issues - scan_comment_for_issue_ids - end - - TIMELOG_RE = / - ( - ((\d+)(h|hours?))((\d+)(m|min)?)? - | - ((\d+)(h|hours?|m|min)) - | - (\d+):(\d+) - | - (\d+([\.,]\d+)?)h? - ) - /x - - def scan_comment_for_issue_ids - return if comments.blank? - # keywords used to reference issues - ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip) - ref_keywords_any = ref_keywords.delete('*') - # keywords used to fix issues - fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip) - - kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|") - - referenced_issues = [] - - comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match| - action, refs = match[2], match[3] - next unless action.present? || ref_keywords_any - - refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m| - issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2] - if issue - referenced_issues << issue - fix_issue(issue) if fix_keywords.include?(action.to_s.downcase) - log_time(issue, hours) if hours && Setting.commit_logtime_enabled? - end - end - end - - referenced_issues.uniq! - self.issues = referenced_issues unless referenced_issues.empty? - end - - def short_comments - @short_comments || split_comments.first - end - - def long_comments - @long_comments || split_comments.last - end - - def text_tag(ref_project=nil) - tag = if scmid? - "commit:#{scmid}" - else - "r#{revision}" - end - if repository && repository.identifier.present? - tag = "#{repository.identifier}|#{tag}" - end - if ref_project && project && ref_project != project - tag = "#{project.identifier}:#{tag}" - end - tag - end - - # Returns the title used for the changeset in the activity/search results - def title - repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : '' - comm = short_comments.blank? ? '' : (': ' + short_comments) - "#{l(:label_revision)} #{format_identifier}#{repo}#{comm}" - end - - # Returns the previous changeset - def previous - @previous ||= Changeset.where(["id < ? AND repository_id = ?", id, repository_id]).order('id DESC').first - end - - # Returns the next changeset - def next - @next ||= Changeset.where(["id > ? AND repository_id = ?", id, repository_id]).order('id ASC').first - end - - # Creates a new Change from it's common parameters - def create_change(change) - Change.create(:changeset => self, - :action => change[:action], - :path => change[:path], - :from_path => change[:from_path], - :from_revision => change[:from_revision]) - end - - # Finds an issue that can be referenced by the commit message - def find_referenced_issue_by_id(id) - return nil if id.blank? - issue = Issue.find_by_id(id.to_i, :include => :project) - if Setting.commit_cross_project_ref? - # all issues can be referenced/fixed - elsif issue - # issue that belong to the repository project, a subproject or a parent project only - unless issue.project && - (project == issue.project || project.is_ancestor_of?(issue.project) || - project.is_descendant_of?(issue.project)) - issue = nil - end - end - issue - end - - private - - def fix_issue(issue) - status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i) - if status.nil? - logger.warn("No status matches commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger - return issue - end - - # the issue may have been updated by the closure of another one (eg. duplicate) - issue.reload - # don't change the status is the issue is closed - return if issue.status && issue.status.is_closed? - - journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project))) - issue.status = status - unless Setting.commit_fix_done_ratio.blank? - issue.done_ratio = Setting.commit_fix_done_ratio.to_i - end - Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update, - { :changeset => self, :issue => issue }) - unless issue.save - logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger - end - issue - end - - def log_time(issue, hours) - time_entry = TimeEntry.new( - :user => user, - :hours => hours, - :issue => issue, - :spent_on => commit_date, - :comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project), - :locale => Setting.default_language) - ) - time_entry.activity = log_time_activity unless log_time_activity.nil? - - unless time_entry.save - logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger - end - time_entry - end - - def log_time_activity - if Setting.commit_logtime_activity_id.to_i > 0 - TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i) - end - end - - def split_comments - comments =~ /\A(.+?)\r?\n(.*)$/m - @short_comments = $1 || comments - @long_comments = $2.to_s.strip - return @short_comments, @long_comments - end - - public - - # Strips and reencodes a commit log before insertion into the database - def self.normalize_comments(str, encoding) - Changeset.to_utf8(str.to_s.strip, encoding) - end - - def self.to_utf8(str, encoding) - Redmine::CodesetUtil.to_utf8(str, encoding) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/2069c7a6894bd57d0791bf6d175776952425d0ca.svn-base --- a/.svn/pristine/20/2069c7a6894bd57d0791bf6d175776952425d0ca.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,354 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'diff' - -# The WikiController follows the Rails REST controller pattern but with -# a few differences -# -# * index - shows a list of WikiPages grouped by page or date -# * new - not used -# * create - not used -# * show - will also show the form for creating a new wiki page -# * edit - used to edit an existing or new page -# * update - used to save a wiki page update to the database, including new pages -# * destroy - normal -# -# Other member and collection methods are also used -# -# TODO: still being worked on -class WikiController < ApplicationController - default_search_scope :wiki_pages - before_filter :find_wiki, :authorize - before_filter :find_existing_or_new_page, :only => [:show, :edit, :update] - before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] - accept_api_auth :index, :show, :update, :destroy - - helper :attachments - include AttachmentsHelper - helper :watchers - include Redmine::Export::PDF - - # List of pages, sorted alphabetically and by parent (hierarchy) - def index - load_pages_for_index - - respond_to do |format| - format.html { - @pages_by_parent_id = @pages.group_by(&:parent_id) - } - format.api - end - end - - # List of page, by last update - def date_index - load_pages_for_index - @pages_by_date = @pages.group_by {|p| p.updated_on.to_date} - end - - # display a page (in editing mode if it doesn't exist) - def show - if @page.new_record? - if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request? - edit - render :action => 'edit' - else - render_404 - end - return - end - if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project) - deny_access - return - end - @content = @page.content_for_version(params[:version]) - if User.current.allowed_to?(:export_wiki_pages, @project) - if params[:format] == 'pdf' - send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf") - return - elsif params[:format] == 'html' - export = render_to_string :action => 'export', :layout => false - send_data(export, :type => 'text/html', :filename => "#{@page.title}.html") - return - elsif params[:format] == 'txt' - send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt") - return - end - end - @editable = editable? - @sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) && - @content.current_version? && - Redmine::WikiFormatting.supports_section_edit? - - respond_to do |format| - format.html - format.api - end - end - - # edit an existing page or a new one - def edit - return render_403 unless editable? - if @page.new_record? - @page.content = WikiContent.new(:page => @page) - if params[:parent].present? - @page.parent = @page.wiki.find_page(params[:parent].to_s) - end - end - - @content = @page.content_for_version(params[:version]) - @content.text = initial_page_content(@page) if @content.text.blank? - # don't keep previous comment - @content.comments = nil - - # To prevent StaleObjectError exception when reverting to a previous version - @content.version = @page.content.version - - @text = @content.text - if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? - @section = params[:section].to_i - @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section) - render_404 if @text.blank? - end - end - - # Creates a new page or updates an existing one - def update - return render_403 unless editable? - was_new_page = @page.new_record? - @page.content = WikiContent.new(:page => @page) if @page.new_record? - @page.safe_attributes = params[:wiki_page] - - @content = @page.content - content_params = params[:content] - if content_params.nil? && params[:wiki_page].is_a?(Hash) - content_params = params[:wiki_page].slice(:text, :comments, :version) - end - content_params ||= {} - - @content.comments = content_params[:comments] - @text = content_params[:text] - if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? - @section = params[:section].to_i - @section_hash = params[:section_hash] - @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash) - else - @content.version = content_params[:version] if content_params[:version] - @content.text = @text - end - @content.author = User.current - - if @page.save_with_content - attachments = Attachment.attach_files(@page, params[:attachments]) - render_attachment_warning_if_needed(@page) - call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) - - respond_to do |format| - format.html { redirect_to :action => 'show', :project_id => @project, :id => @page.title } - format.api { - if was_new_page - render :action => 'show', :status => :created, :location => url_for(:controller => 'wiki', :action => 'show', :project_id => @project, :id => @page.title) - else - render_api_ok - end - } - end - else - respond_to do |format| - format.html { render :action => 'edit' } - format.api { render_validation_errors(@content) } - end - end - - rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError - # Optimistic locking exception - respond_to do |format| - format.html { - flash.now[:error] = l(:notice_locking_conflict) - render :action => 'edit' - } - format.api { render_api_head :conflict } - end - rescue ActiveRecord::RecordNotSaved - respond_to do |format| - format.html { render :action => 'edit' } - format.api { render_validation_errors(@content) } - end - end - - # rename a page - def rename - return render_403 unless editable? - @page.redirect_existing_links = true - # used to display the *original* title if some AR validation errors occur - @original_title = @page.pretty_title - if request.post? && @page.update_attributes(params[:wiki_page]) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'show', :project_id => @project, :id => @page.title - end - end - - def protect - @page.update_attribute :protected, params[:protected] - redirect_to :action => 'show', :project_id => @project, :id => @page.title - end - - # show page history - def history - @version_count = @page.content.versions.count - @version_pages = Paginator.new self, @version_count, per_page_option, params['page'] - # don't load text - @versions = @page.content.versions.find :all, - :select => "id, author_id, comments, updated_on, version", - :order => 'version DESC', - :limit => @version_pages.items_per_page + 1, - :offset => @version_pages.current.offset - - render :layout => false if request.xhr? - end - - def diff - @diff = @page.diff(params[:version], params[:version_from]) - render_404 unless @diff - end - - def annotate - @annotate = @page.annotate(params[:version]) - render_404 unless @annotate - end - - # Removes a wiki page and its history - # Children can be either set as root pages, removed or reassigned to another parent page - def destroy - return render_403 unless editable? - - @descendants_count = @page.descendants.size - if @descendants_count > 0 - case params[:todo] - when 'nullify' - # Nothing to do - when 'destroy' - # Removes all its descendants - @page.descendants.each(&:destroy) - when 'reassign' - # Reassign children to another parent page - reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i) - return unless reassign_to - @page.children.each do |child| - child.update_attribute(:parent, reassign_to) - end - else - @reassignable_to = @wiki.pages - @page.self_and_descendants - # display the destroy form if it's a user request - return unless api_request? - end - end - @page.destroy - respond_to do |format| - format.html { redirect_to :action => 'index', :project_id => @project } - format.api { render_api_ok } - end - end - - def destroy_version - return render_403 unless editable? - - @content = @page.content_for_version(params[:version]) - @content.destroy - redirect_to_referer_or :action => 'history', :id => @page.title, :project_id => @project - end - - # Export wiki to a single pdf or html file - def export - @pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}]) - respond_to do |format| - format.html { - export = render_to_string :action => 'export_multiple', :layout => false - send_data(export, :type => 'text/html', :filename => "wiki.html") - } - format.pdf { - send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf") - } - end - end - - def preview - page = @wiki.find_page(params[:id]) - # page is nil when previewing a new page - return render_403 unless page.nil? || editable?(page) - if page - @attachements = page.attachments - @previewed = page.content - end - @text = params[:content][:text] - render :partial => 'common/preview' - end - - def add_attachment - return render_403 unless editable? - attachments = Attachment.attach_files(@page, params[:attachments]) - render_attachment_warning_if_needed(@page) - redirect_to :action => 'show', :id => @page.title, :project_id => @project - end - -private - - def find_wiki - @project = Project.find(params[:project_id]) - @wiki = @project.wiki - render_404 unless @wiki - rescue ActiveRecord::RecordNotFound - render_404 - end - - # Finds the requested page or a new page if it doesn't exist - def find_existing_or_new_page - @page = @wiki.find_or_new_page(params[:id]) - if @wiki.page_found_with_redirect? - redirect_to params.update(:id => @page.title) - end - end - - # Finds the requested page and returns a 404 error if it doesn't exist - def find_existing_page - @page = @wiki.find_page(params[:id]) - if @page.nil? - render_404 - return - end - if @wiki.page_found_with_redirect? - redirect_to params.update(:id => @page.title) - end - end - - # Returns true if the current user is allowed to edit the page, otherwise false - def editable?(page = @page) - page.editable_by?(User.current) - end - - # Returns the default content of a new wiki page - def initial_page_content(page) - helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) - extend helper unless self.instance_of?(helper) - helper.instance_method(:initial_page_content).bind(self).call(page) - end - - def load_pages_for_index - @pages = @wiki.pages.with_updated_on.order("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/209f9ebc3aa58812be26dbeb5ba5ab0a2d6a75b3.svn-base --- a/.svn/pristine/20/209f9ebc3aa58812be26dbeb5ba5ab0a2d6a75b3.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,242 +0,0 @@ -# encoding: utf-8 -# -# Helpers to sort tables using clickable column headers. -# -# Author: Stuart Rackham , March 2005. -# Jean-Philippe Lang, 2009 -# License: This source code is released under the MIT license. -# -# - Consecutive clicks toggle the column's sort order. -# - Sort state is maintained by a session hash entry. -# - CSS classes identify sort column and state. -# - Typically used in conjunction with the Pagination module. -# -# Example code snippets: -# -# Controller: -# -# helper :sort -# include SortHelper -# -# def list -# sort_init 'last_name' -# sort_update %w(first_name last_name) -# @items = Contact.find_all nil, sort_clause -# end -# -# Controller (using Pagination module): -# -# helper :sort -# include SortHelper -# -# def list -# sort_init 'last_name' -# sort_update %w(first_name last_name) -# @contact_pages, @items = paginate :contacts, -# :order_by => sort_clause, -# :per_page => 10 -# end -# -# View (table header in list.rhtml): -# -# -# -# <%= sort_header_tag('id', :title => 'Sort by contact ID') %> -# <%= sort_header_tag('last_name', :caption => 'Name') %> -# <%= sort_header_tag('phone') %> -# <%= sort_header_tag('address', :width => 200) %> -# -# -# -# - Introduces instance variables: @sort_default, @sort_criteria -# - Introduces param :sort -# - -module SortHelper - class SortCriteria - - def initialize - @criteria = [] - end - - def available_criteria=(criteria) - unless criteria.is_a?(Hash) - criteria = criteria.inject({}) {|h,k| h[k] = k; h} - end - @available_criteria = criteria - end - - def from_param(param) - @criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]} - normalize! - end - - def criteria=(arg) - @criteria = arg - normalize! - end - - def to_param - @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') - end - - def to_sql - sql = @criteria.collect do |k,o| - if s = @available_criteria[k] - (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}).join(', ') - end - end.compact.join(', ') - sql.blank? ? nil : sql - end - - def to_a - @criteria.dup - end - - def add!(key, asc) - @criteria.delete_if {|k,o| k == key} - @criteria = [[key, asc]] + @criteria - normalize! - end - - def add(*args) - r = self.class.new.from_param(to_param) - r.add!(*args) - r - end - - def first_key - @criteria.first && @criteria.first.first - end - - def first_asc? - @criteria.first && @criteria.first.last - end - - def empty? - @criteria.empty? - end - - private - - def normalize! - @criteria ||= [] - @criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]} - @criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria - @criteria.slice!(3) - self - end - - # Appends DESC to the sort criterion unless it has a fixed order - def append_desc(criterion) - if criterion =~ / (asc|desc)$/i - criterion - else - "#{criterion} DESC" - end - end - end - - def sort_name - controller_name + '_' + action_name + '_sort' - end - - # Initializes the default sort. - # Examples: - # - # sort_init 'name' - # sort_init 'id', 'desc' - # sort_init ['name', ['id', 'desc']] - # sort_init [['name', 'desc'], ['id', 'desc']] - # - def sort_init(*args) - case args.size - when 1 - @sort_default = args.first.is_a?(Array) ? args.first : [[args.first]] - when 2 - @sort_default = [[args.first, args.last]] - else - raise ArgumentError - end - end - - # Updates the sort state. Call this in the controller prior to calling - # sort_clause. - # - criteria can be either an array or a hash of allowed keys - # - def sort_update(criteria, sort_name=nil) - sort_name ||= self.sort_name - @sort_criteria = SortCriteria.new - @sort_criteria.available_criteria = criteria - @sort_criteria.from_param(params[:sort] || session[sort_name]) - @sort_criteria.criteria = @sort_default if @sort_criteria.empty? - session[sort_name] = @sort_criteria.to_param - end - - # Clears the sort criteria session data - # - def sort_clear - session[sort_name] = nil - end - - # Returns an SQL sort clause corresponding to the current sort state. - # Use this to sort the controller's table items collection. - # - def sort_clause() - @sort_criteria.to_sql - end - - def sort_criteria - @sort_criteria - end - - # Returns a link which sorts by the named column. - # - # - column is the name of an attribute in the sorted record collection. - # - the optional caption explicitly specifies the displayed link text. - # - 2 CSS classes reflect the state of the link: sort and asc or desc - # - def sort_link(column, caption, default_order) - css, order = nil, default_order - - if column.to_s == @sort_criteria.first_key - if @sort_criteria.first_asc? - css = 'sort asc' - order = 'desc' - else - css = 'sort desc' - order = 'asc' - end - end - caption = column.to_s.humanize unless caption - - sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param } - url_options = params.merge(sort_options) - - # Add project_id to url_options - url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id) - - link_to_content_update(h(caption), url_options, :class => css) - end - - # Returns a table header tag with a sort link for the named column - # attribute. - # - # Options: - # :caption The displayed link name (defaults to titleized column name). - # :title The tag's 'title' attribute (defaults to 'Sort by :caption'). - # - # Other options hash entries generate additional table header tag attributes. - # - # Example: - # - # <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %> - # - def sort_header_tag(column, options = {}) - caption = options.delete(:caption) || column.to_s.humanize - default_order = options.delete(:default_order) || 'asc' - options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title] - content_tag('th', sort_link(column, caption, default_order), options) - end -end - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/20cf4d4753525274da375856e4def6acfb536bf9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/20/20cf4d4753525274da375856e4def6acfb536bf9.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,372 @@ +# -*- coding: utf-8 -*- +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class TimeEntryReportsControllerTest < ActionController::TestCase + tests TimelogController + + fixtures :projects, :enabled_modules, :roles, :members, :member_roles, + :issues, :time_entries, :users, :trackers, :enumerations, + :issue_statuses, :custom_fields, :custom_values + + include Redmine::I18n + + def setup + Setting.default_language = "en" + end + + def test_report_at_project_level + get :report, :project_id => 'ecookbook' + assert_response :success + assert_template 'report' + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries/report", :id => 'query_form'} + end + + def test_report_all_projects + get :report + assert_response :success + assert_template 'report' + assert_tag :form, + :attributes => {:action => "/time_entries/report", :id => 'query_form'} + end + + def test_report_all_projects_denied + r = Role.anonymous + r.permissions.delete(:view_time_entries) + r.permissions_will_change! + r.save + get :report + assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftime_entries%2Freport' + end + + def test_report_all_projects_one_criteria + get :report, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criteria => ['project'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:report) + assert_equal "8.65", "%.2f" % assigns(:report).total_hours + end + + def test_report_all_time + get :report, :project_id => 1, :criteria => ['project', 'issue'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:report) + assert_equal "162.90", "%.2f" % assigns(:report).total_hours + end + + def test_report_all_time_by_day + get :report, :project_id => 1, :criteria => ['project', 'issue'], :columns => 'day' + assert_response :success + assert_template 'report' + assert_not_nil assigns(:report) + assert_equal "162.90", "%.2f" % assigns(:report).total_hours + assert_tag :tag => 'th', :content => '2007-03-12' + end + + def test_report_one_criteria + get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criteria => ['project'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:report) + assert_equal "8.65", "%.2f" % assigns(:report).total_hours + end + + def test_report_two_criteria + get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["user", "activity"] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:report) + assert_equal "162.90", "%.2f" % assigns(:report).total_hours + end + + def test_report_custom_field_criteria_with_multiple_values + field = TimeEntryCustomField.create!(:name => 'multi', :field_format => 'list', :possible_values => ['value1', 'value2']) + entry = TimeEntry.create!(:project => Project.find(1), :hours => 1, :activity_id => 10, :user => User.find(2), :spent_on => Date.today) + CustomValue.create!(:customized => entry, :custom_field => field, :value => 'value1') + CustomValue.create!(:customized => entry, :custom_field => field, :value => 'value2') + + get :report, :project_id => 1, :columns => 'day', :criteria => ["cf_#{field.id}"] + assert_response :success + end + + def test_report_one_day + get :report, :project_id => 1, :columns => 'day', :from => "2007-03-23", :to => "2007-03-23", :criteria => ["user", "activity"] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:report) + assert_equal "4.25", "%.2f" % assigns(:report).total_hours + end + + def test_report_at_issue_level + get :report, :project_id => 1, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["user", "activity"] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:report) + assert_equal "154.25", "%.2f" % assigns(:report).total_hours + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/issues/1/time_entries/report", :id => 'query_form'} + end + + def test_report_by_week_should_use_commercial_year + TimeEntry.delete_all + TimeEntry.generate!(:hours => '2', :spent_on => '2009-12-25') # 2009-52 + TimeEntry.generate!(:hours => '4', :spent_on => '2009-12-31') # 2009-53 + TimeEntry.generate!(:hours => '8', :spent_on => '2010-01-01') # 2009-53 + TimeEntry.generate!(:hours => '16', :spent_on => '2010-01-05') # 2010-1 + + get :report, :columns => 'week', :from => "2009-12-25", :to => "2010-01-05", :criteria => ["project"] + assert_response :success + + assert_select '#time-report thead tr' do + assert_select 'th:nth-child(1)', :text => 'Project' + assert_select 'th:nth-child(2)', :text => '2009-52' + assert_select 'th:nth-child(3)', :text => '2009-53' + assert_select 'th:nth-child(4)', :text => '2010-1' + assert_select 'th:nth-child(5)', :text => 'Total time' + end + assert_select '#time-report tbody tr' do + assert_select 'td:nth-child(1)', :text => 'eCookbook' + assert_select 'td:nth-child(2)', :text => '2.00' + assert_select 'td:nth-child(3)', :text => '12.00' + assert_select 'td:nth-child(4)', :text => '16.00' + assert_select 'td:nth-child(5)', :text => '30.00' # Total + end + end + + def test_report_should_propose_association_custom_fields + get :report + assert_response :success + assert_template 'report' + + assert_select 'select[name=?]', 'criteria[]' do + assert_select 'option[value=cf_1]', {:text => 'Database'}, 'Issue custom field not found' + assert_select 'option[value=cf_3]', {:text => 'Development status'}, 'Project custom field not found' + assert_select 'option[value=cf_7]', {:text => 'Billable'}, 'TimeEntryActivity custom field not found' + end + end + + def test_report_with_association_custom_fields + get :report, :criteria => ['cf_1', 'cf_3', 'cf_7'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:report) + assert_equal 3, assigns(:report).criteria.size + assert_equal "162.90", "%.2f" % assigns(:report).total_hours + + # Custom fields columns + assert_select 'th', :text => 'Database' + assert_select 'th', :text => 'Development status' + assert_select 'th', :text => 'Billable' + + # Custom field row + assert_select 'tr' do + assert_select 'td', :text => 'MySQL' + assert_select 'td.hours', :text => '1.00' + end + end + + def test_report_one_criteria_no_result + get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criteria => ['project'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:report) + assert_equal "0.00", "%.2f" % assigns(:report).total_hours + end + + def test_report_status_criterion + get :report, :project_id => 1, :criteria => ['status'] + assert_response :success + assert_template 'report' + assert_tag :tag => 'th', :content => 'Status' + assert_tag :tag => 'td', :content => 'New' + end + + def test_report_all_projects_csv_export + get :report, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", + :criteria => ["project", "user", "activity"], :format => "csv" + assert_response :success + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + # Headers + assert_equal 'Project,User,Activity,2007-3,2007-4,Total time', lines.first + # Total row + assert_equal 'Total time,"","",154.25,8.65,162.90', lines.last + end + + def test_report_csv_export + get :report, :project_id => 1, :columns => 'month', + :from => "2007-01-01", :to => "2007-06-30", + :criteria => ["project", "user", "activity"], :format => "csv" + assert_response :success + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + # Headers + assert_equal 'Project,User,Activity,2007-3,2007-4,Total time', lines.first + # Total row + assert_equal 'Total time,"","",154.25,8.65,162.90', lines.last + end + + def test_csv_big_5 + Setting.default_language = "zh-TW" + str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88" + str_big5 = "\xa4@\xa4\xeb" + if str_utf8.respond_to?(:force_encoding) + str_utf8.force_encoding('UTF-8') + str_big5.force_encoding('Big5') + end + user = User.find_by_id(3) + user.firstname = str_utf8 + user.lastname = "test-lastname" + assert user.save + comments = "test_csv_big_5" + te1 = TimeEntry.create(:spent_on => '2011-11-11', + :hours => 7.3, + :project => Project.find(1), + :user => user, + :activity => TimeEntryActivity.find_by_name('Design'), + :comments => comments) + + te2 = TimeEntry.find_by_comments(comments) + assert_not_nil te2 + assert_equal 7.3, te2.hours + assert_equal 3, te2.user_id + + get :report, :project_id => 1, :columns => 'day', + :from => "2011-11-11", :to => "2011-11-11", + :criteria => ["user"], :format => "csv" + assert_response :success + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + # Headers + s1 = "\xa5\xce\xa4\xe1,2011-11-11,\xa4u\xae\xc9\xc1`\xadp" + s2 = "\xa4u\xae\xc9\xc1`\xadp" + if s1.respond_to?(:force_encoding) + s1.force_encoding('Big5') + s2.force_encoding('Big5') + end + assert_equal s1, lines.first + # Total row + assert_equal "#{str_big5} #{user.lastname},7.30,7.30", lines[1] + assert_equal "#{s2},7.30,7.30", lines[2] + + str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)" + if str_tw.respond_to?(:force_encoding) + str_tw.force_encoding('UTF-8') + end + assert_equal str_tw, l(:general_lang_name) + assert_equal 'Big5', l(:general_csv_encoding) + assert_equal ',', l(:general_csv_separator) + assert_equal '.', l(:general_csv_decimal_separator) + end + + def test_csv_cannot_convert_should_be_replaced_big_5 + Setting.default_language = "zh-TW" + str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85" + if str_utf8.respond_to?(:force_encoding) + str_utf8.force_encoding('UTF-8') + end + user = User.find_by_id(3) + user.firstname = str_utf8 + user.lastname = "test-lastname" + assert user.save + comments = "test_replaced" + te1 = TimeEntry.create(:spent_on => '2011-11-11', + :hours => 7.3, + :project => Project.find(1), + :user => user, + :activity => TimeEntryActivity.find_by_name('Design'), + :comments => comments) + + te2 = TimeEntry.find_by_comments(comments) + assert_not_nil te2 + assert_equal 7.3, te2.hours + assert_equal 3, te2.user_id + + get :report, :project_id => 1, :columns => 'day', + :from => "2011-11-11", :to => "2011-11-11", + :criteria => ["user"], :format => "csv" + assert_response :success + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + # Headers + s1 = "\xa5\xce\xa4\xe1,2011-11-11,\xa4u\xae\xc9\xc1`\xadp" + if s1.respond_to?(:force_encoding) + s1.force_encoding('Big5') + end + assert_equal s1, lines.first + # Total row + s2 = "" + if s2.respond_to?(:force_encoding) + s2 = "\xa5H?" + s2.force_encoding('Big5') + elsif RUBY_PLATFORM == 'java' + s2 = "??" + else + s2 = "\xa5H???" + end + assert_equal "#{s2} #{user.lastname},7.30,7.30", lines[1] + end + + def test_csv_fr + with_settings :default_language => "fr" do + str1 = "test_csv_fr" + user = User.find_by_id(3) + te1 = TimeEntry.create(:spent_on => '2011-11-11', + :hours => 7.3, + :project => Project.find(1), + :user => user, + :activity => TimeEntryActivity.find_by_name('Design'), + :comments => str1) + + te2 = TimeEntry.find_by_comments(str1) + assert_not_nil te2 + assert_equal 7.3, te2.hours + assert_equal 3, te2.user_id + + get :report, :project_id => 1, :columns => 'day', + :from => "2011-11-11", :to => "2011-11-11", + :criteria => ["user"], :format => "csv" + assert_response :success + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + # Headers + s1 = "Utilisateur;2011-11-11;Temps total" + s2 = "Temps total" + if s1.respond_to?(:force_encoding) + s1.force_encoding('ISO-8859-1') + s2.force_encoding('ISO-8859-1') + end + assert_equal s1, lines.first + # Total row + assert_equal "#{user.firstname} #{user.lastname};7,30;7,30", lines[1] + assert_equal "#{s2};7,30;7,30", lines[2] + + str_fr = "Fran\xc3\xa7ais" + if str_fr.respond_to?(:force_encoding) + str_fr.force_encoding('UTF-8') + end + assert_equal str_fr, l(:general_lang_name) + assert_equal 'ISO-8859-1', l(:general_csv_encoding) + assert_equal ';', l(:general_csv_separator) + assert_equal ',', l(:general_csv_decimal_separator) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/20cfc1def6cf64da0d22e9f4697140fef8698ede.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/20/20cfc1def6cf64da0d22e9f4697140fef8698ede.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,24 @@ +class AddUniqueIndexOnCustomFieldsTrackers < ActiveRecord::Migration + def up + table_name = "#{CustomField.table_name_prefix}custom_fields_trackers#{CustomField.table_name_suffix}" + duplicates = CustomField.connection.select_rows("SELECT custom_field_id, tracker_id FROM #{table_name} GROUP BY custom_field_id, tracker_id HAVING COUNT(*) > 1") + duplicates.each do |custom_field_id, tracker_id| + # Removes duplicate rows + CustomField.connection.execute("DELETE FROM #{table_name} WHERE custom_field_id=#{custom_field_id} AND tracker_id=#{tracker_id}") + # And insert one + CustomField.connection.execute("INSERT INTO #{table_name} (custom_field_id, tracker_id) VALUES (#{custom_field_id}, #{tracker_id})") + end + + if index_exists? :custom_fields_trackers, [:custom_field_id, :tracker_id] + remove_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end + add_index :custom_fields_trackers, [:custom_field_id, :tracker_id], :unique => true + end + + def down + if index_exists? :custom_fields_trackers, [:custom_field_id, :tracker_id] + remove_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end + add_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/20/20f8dff24ed27f7604eb718009f2a8bf899ab23c.svn-base --- a/.svn/pristine/20/20f8dff24ed27f7604eb718009f2a8bf899ab23c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) -require 'pp' -class ApiTest::NewsTest < ActionController::IntegrationTest - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :news - - def setup - Setting.rest_api_enabled = '1' - end - - context "GET /news" do - context ".xml" do - should "return news" do - get '/news.xml' - - assert_tag :tag => 'news', - :attributes => {:type => 'array'}, - :child => { - :tag => 'news', - :child => { - :tag => 'id', - :content => '2' - } - } - end - end - - context ".json" do - should "return news" do - get '/news.json' - - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Array, json['news'] - assert_kind_of Hash, json['news'].first - assert_equal 2, json['news'].first['id'] - end - end - end - - context "GET /projects/:project_id/news" do - context ".xml" do - should_allow_api_authentication(:get, "/projects/onlinestore/news.xml") - - should "return news" do - get '/projects/ecookbook/news.xml' - - assert_tag :tag => 'news', - :attributes => {:type => 'array'}, - :child => { - :tag => 'news', - :child => { - :tag => 'id', - :content => '2' - } - } - end - end - - context ".json" do - should_allow_api_authentication(:get, "/projects/onlinestore/news.json") - - should "return news" do - get '/projects/ecookbook/news.json' - - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Array, json['news'] - assert_kind_of Hash, json['news'].first - assert_equal 2, json['news'].first['id'] - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/21/212b78099cd591bc9596c563f09ce2ddb8eb84ad.svn-base --- a/.svn/pristine/21/212b78099cd591bc9596c563f09ce2ddb8eb84ad.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingMembersTest < ActionController::IntegrationTest - def test_members - assert_routing( - { :method => 'get', :path => "/projects/5234/memberships.xml" }, - { :controller => 'members', :action => 'index', :project_id => '5234', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/memberships/5234.xml" }, - { :controller => 'members', :action => 'show', :id => '5234', :format => 'xml' } - ) - assert_routing( - { :method => 'post', :path => "/projects/5234/memberships" }, - { :controller => 'members', :action => 'create', :project_id => '5234' } - ) - assert_routing( - { :method => 'post', :path => "/projects/5234/memberships.xml" }, - { :controller => 'members', :action => 'create', :project_id => '5234', :format => 'xml' } - ) - assert_routing( - { :method => 'put', :path => "/memberships/5234" }, - { :controller => 'members', :action => 'update', :id => '5234' } - ) - assert_routing( - { :method => 'put', :path => "/memberships/5234.xml" }, - { :controller => 'members', :action => 'update', :id => '5234', :format => 'xml' } - ) - assert_routing( - { :method => 'delete', :path => "/memberships/5234" }, - { :controller => 'members', :action => 'destroy', :id => '5234' } - ) - assert_routing( - { :method => 'delete', :path => "/memberships/5234.xml" }, - { :controller => 'members', :action => 'destroy', :id => '5234', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/projects/5234/memberships/autocomplete" }, - { :controller => 'members', :action => 'autocomplete', :project_id => '5234' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/21/2133044f3bd39e97fd612d3eaa27ffd002d52b29.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/21/2133044f3bd39e97fd612d3eaa27ffd002d52b29.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,46 @@ +<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> + +
    + <%= render :partial => 'navigation' %> +
    + +

    <%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %>

    + +<%= render :partial => 'link_to_functions' %> + +<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %> + +
    + + + <% line_num = 1; previous_revision = nil %> + <% syntax_highlight_lines(@path, Redmine::CodesetUtil.to_utf8_by_setting(@annotate.content)).each do |line| %> + <% revision = @annotate.revisions[line_num - 1] %> + + + + + + + <% line_num += 1; previous_revision = revision %> + <% end %> + +
    <%= line_num %> + <% if revision && revision != previous_revision %> + <%= revision.identifier ? + link_to_revision(revision, @repository) : format_revision(revision) %> + <% end %> + + <% if revision && revision != previous_revision %> + <% author = Redmine::CodesetUtil.to_utf8(revision.author.to_s, + @repository.repo_log_encoding) %> + <%= author.split('<').first %> + <% end %> +
    <%= line.html_safe %>
    +
    + +<% html_title(l(:button_annotate)) -%> + +<% content_for :header_tags do %> +<%= stylesheet_link_tag 'scm' %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/21/213cd47b5ee47b691425e1fcb6dffb207c707d8c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/21/213cd47b5ee47b691425e1fcb6dffb207c707d8c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,5 @@ +/*! jQuery UI - v1.9.2 - 2012-12-26 +* http://jqueryui.com +* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2C%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=759fcf&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=628db6&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=628db6&iconColorDefault=759fcf&bgColorHover=eef5fd&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=628db6&fcHover=628db6&iconColorHover=759fcf&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=628db6&fcActive=628db6&iconColorActive=759fcf&bgColorHighlight=759fcf&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=628db6&fcHighlight=363636&iconColorHighlight=759fcf&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px +* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{zoom:1}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;zoom:1}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto;zoom:1}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}* html .ui-autocomplete{width:1px}.ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0em}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:right}.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-cover{position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .1em 0}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;width:100%}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;zoom:1;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em;zoom:1}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-tabs-loading a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}* html .ui-tooltip{background-image:none}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #628db6;background:#759fcf url(images/ui-bg_gloss-wave_35_759fcf_500x100.png) 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#628db6;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #628db6;background:#eef5fd url(images/ui-bg_glass_100_eef5fd_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited{color:#628db6;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #628db6;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#628db6;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #628db6;background:#759fcf url(images/ui-bg_highlight-soft_75_759fcf_1x100.png) 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px;background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_ffffff_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_ffd27a_256x240.png)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;-khtml-border-top-left-radius:4px;border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;-khtml-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;-khtml-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;-khtml-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);-moz-border-radius:5px;-khtml-border-radius:5px;-webkit-border-radius:5px;border-radius:5px} diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/22/22629c3ce001e5a68327c24a4b23675476508e16.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/22/22629c3ce001e5a68327c24a4b23675476508e16.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,32 @@ +<%= error_messages_for 'time_entry' %> +<%= back_url_hidden_field_tag %> + +
    + <% if @time_entry.new_record? %> + <% if params[:project_id] || @time_entry.issue %> + <%= f.hidden_field :project_id %> + <% else %> +

    <%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).all, :selected => @time_entry.project), :required => true %>

    + <% end %> + <% end %> +

    + <%= f.text_field :issue_id, :size => 6 %> + <%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %> +

    +

    <%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %>

    +

    <%= f.text_field :hours, :size => 6, :required => true %>

    +

    <%= f.text_field :comments, :size => 100, :maxlength => 255 %>

    +

    <%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %>

    + <% @time_entry.custom_field_values.each do |value| %> +

    <%= custom_field_tag_with_label :time_entry, value %>

    + <% end %> + <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %> +
    + +<%= javascript_tag do %> + observeAutocompleteField('time_entry_issue_id', '<%= escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (@project ? nil : 'all'))%>', { + select: function(event, ui) { + $('#time_entry_issue').text(ui.item.label); + } + }); +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/22/226ca6df54d2f722db0a366c7bc4bc8b7ec109c6.svn-base --- a/.svn/pristine/22/226ca6df54d2f722db0a366c7bc4bc8b7ec109c6.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module SubclassFactory - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - def get_subclass(class_name) - klass = nil - begin - klass = class_name.to_s.classify.constantize - rescue - # invalid class name - end - unless subclasses.include? klass - klass = nil - end - klass - end - - # Returns an instance of the given subclass name - def new_subclass_instance(class_name, *args) - klass = get_subclass(class_name) - if klass - klass.new(*args) - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/22/228763af0fab9d92d601d760e23db085d02a829d.svn-base --- a/.svn/pristine/22/228763af0fab9d92d601d760e23db085d02a829d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -
    - « - <% unless @changeset.previous.nil? -%> - <%= link_to_revision(@changeset.previous, @repository, :text => l(:label_previous)) %> - <% else -%> - <%= l(:label_previous) %> - <% end -%> -| - <% unless @changeset.next.nil? -%> - <%= link_to_revision(@changeset.next, @repository, :text => l(:label_next)) %> - <% else -%> - <%= l(:label_next) %> - <% end -%> - »  - - <%= form_tag({:controller => 'repositories', - :action => 'revision', - :id => @project, - :repository_id => @repository.identifier_param, - :rev => nil}, - :method => :get) do %> - <%= text_field_tag 'rev', @rev, :size => 8 %> - <%= submit_tag 'OK', :name => nil %> - <% end %> -
    - -

    <%= avatar(@changeset.user, :size => "24") %><%= l(:label_revision) %> <%= format_revision(@changeset) %>

    - -<% if @changeset.scmid.present? || @changeset.parents.present? || @changeset.children.present? %> - - <% if @changeset.scmid.present? %> - - - - <% end %> - <% if @changeset.parents.present? %> - - - - - <% end %> - <% if @changeset.children.present? %> - - - - - <% end %> -
    ID<%= h(@changeset.scmid) %>
    <%= l(:label_parent_revision) %> - <%= @changeset.parents.collect{ - |p| link_to_revision(p, @repository, :text => format_revision(p)) - }.join(", ").html_safe %> -
    <%= l(:label_child_revision) %> - <%= @changeset.children.collect{ - |p| link_to_revision(p, @repository, :text => format_revision(p)) - }.join(", ").html_safe %> -
    -<% end %> - -

    - -<%= authoring(@changeset.committed_on, @changeset.author) %> - -

    - -<%= textilizable @changeset.comments %> - -<% if @changeset.issues.visible.any? || User.current.allowed_to?(:manage_related_issues, @repository.project) %> - <%= render :partial => 'related_issues' %> -<% end %> - -<% if User.current.allowed_to?(:browse_repository, @project) %> -

    <%= l(:label_attachment_plural) %>

    -
      -
    • <%= l(:label_added) %>
    • -
    • <%= l(:label_modified) %>
    • -
    • <%= l(:label_copied) %>
    • -
    • <%= l(:label_renamed) %>
    • -
    • <%= l(:label_deleted) %>
    • -
    - -

    <%= link_to(l(:label_view_diff), - :action => 'diff', - :id => @project, - :repository_id => @repository.identifier_param, - :path => "", - :rev => @changeset.identifier) if @changeset.filechanges.any? %>

    - -
    -<%= render_changeset_changes %> -
    -<% end %> - -<% content_for :header_tags do %> -<%= stylesheet_link_tag "scm" %> -<% end %> - -<% html_title("#{l(:label_revision)} #{format_revision(@changeset)}") -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/22/228ab5b14477141cc691a1ebb6e20efbefbf1ba7.svn-base --- a/.svn/pristine/22/228ab5b14477141cc691a1ebb6e20efbefbf1ba7.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ - - - - - - - - - <% for new_status in @statuses %> - - <% end %> - - - - <% for old_status in @statuses %> - "> - - <% for new_status in @statuses -%> - <% checked = workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id} %> - - <% end -%> - - <% end %> - -
    - <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> - <%=l(:label_current_status)%> - <%=l(:label_new_statuses_allowed)%>
    - <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.new-status-#{new_status.id}')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> - <%=h new_status.name %> -
    - <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.old-status-#{old_status.id}')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> - - <%=h old_status.name %> - - <%= check_box_tag "issue_status[#{ old_status.id }][#{new_status.id}][]", name, checked, - :class => "old-status-#{old_status.id} new-status-#{new_status.id}" %> -
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/22/229053adf4d0f0cdc30b283c6478a18c47c6fe1d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/22/229053adf4d0f0cdc30b283c6478a18c47c6fe1d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,161 @@ +if RUBY_VERSION < '1.9' + require 'iconv' +end + +module Redmine + module CodesetUtil + + def self.replace_invalid_utf8(str) + return str if str.nil? + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + if ! str.valid_encoding? + str = str.encode("US-ASCII", :invalid => :replace, + :undef => :replace, :replace => '?').encode("UTF-8") + end + elsif RUBY_PLATFORM == 'java' + begin + ic = Iconv.new('UTF-8', 'UTF-8') + str = ic.iconv(str) + rescue + str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') + end + else + ic = Iconv.new('UTF-8', 'UTF-8') + txtar = "" + begin + txtar += ic.iconv(str) + rescue Iconv::IllegalSequence + txtar += $!.success + str = '?' + $!.failed[1,$!.failed.length] + retry + rescue + txtar += $!.success + end + str = txtar + end + str + end + + def self.to_utf8(str, encoding) + return str if str.nil? + str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding) + if str.empty? + str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) + return str + end + enc = encoding.blank? ? "UTF-8" : encoding + if str.respond_to?(:force_encoding) + if enc.upcase != "UTF-8" + str.force_encoding(enc) + str = str.encode("UTF-8", :invalid => :replace, + :undef => :replace, :replace => '?') + else + str.force_encoding("UTF-8") + if ! str.valid_encoding? + str = str.encode("US-ASCII", :invalid => :replace, + :undef => :replace, :replace => '?').encode("UTF-8") + end + end + elsif RUBY_PLATFORM == 'java' + begin + ic = Iconv.new('UTF-8', enc) + str = ic.iconv(str) + rescue + str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') + end + else + ic = Iconv.new('UTF-8', enc) + txtar = "" + begin + txtar += ic.iconv(str) + rescue Iconv::IllegalSequence + txtar += $!.success + str = '?' + $!.failed[1,$!.failed.length] + retry + rescue + txtar += $!.success + end + str = txtar + end + str + end + + def self.to_utf8_by_setting(str) + return str if str.nil? + str = self.to_utf8_by_setting_internal(str) + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + end + str + end + + def self.to_utf8_by_setting_internal(str) + return str if str.nil? + if str.respond_to?(:force_encoding) + str.force_encoding('ASCII-8BIT') + end + return str if str.empty? + return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + end + encodings = Setting.repositories_encodings.split(',').collect(&:strip) + encodings.each do |encoding| + if str.respond_to?(:force_encoding) + begin + str.force_encoding(encoding) + utf8 = str.encode('UTF-8') + return utf8 if utf8.valid_encoding? + rescue + # do nothing here and try the next encoding + end + else + begin + return Iconv.conv('UTF-8', encoding, str) + rescue Iconv::Failure + # do nothing here and try the next encoding + end + end + end + str = self.replace_invalid_utf8(str) + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + end + str + end + + def self.from_utf8(str, encoding) + str ||= '' + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + if encoding.upcase != 'UTF-8' + str = str.encode(encoding, :invalid => :replace, + :undef => :replace, :replace => '?') + else + str = self.replace_invalid_utf8(str) + end + elsif RUBY_PLATFORM == 'java' + begin + ic = Iconv.new(encoding, 'UTF-8') + str = ic.iconv(str) + rescue + str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') + end + else + ic = Iconv.new(encoding, 'UTF-8') + txtar = "" + begin + txtar += ic.iconv(str) + rescue Iconv::IllegalSequence + txtar += $!.success + str = '?' + $!.failed[1, $!.failed.length] + retry + rescue + txtar += $!.success + end + str = txtar + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/22/22d0c7f9d45361c16b2b7eccd0c488f009ccf152.svn-base --- a/.svn/pristine/22/22d0c7f9d45361c16b2b7eccd0c488f009ccf152.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class TimeEntryActivityTest < ActiveSupport::TestCase - fixtures :enumerations, :time_entries, :custom_fields - - include Redmine::I18n - - def test_should_be_an_enumeration - assert TimeEntryActivity.ancestors.include?(Enumeration) - end - - def test_objects_count - assert_equal 3, TimeEntryActivity.find_by_name("Design").objects_count - assert_equal 2, TimeEntryActivity.find_by_name("Development").objects_count - end - - def test_option_name - assert_equal :enumeration_activities, TimeEntryActivity.new.option_name - end - - def test_create_with_custom_field - field = TimeEntryActivityCustomField.find_by_name('Billable') - e = TimeEntryActivity.new(:name => 'Custom Data') - e.custom_field_values = {field.id => "1"} - assert e.save - - e.reload - assert_equal "1", e.custom_value_for(field).value - end - - def test_create_without_required_custom_field_should_fail - set_language_if_valid 'en' - field = TimeEntryActivityCustomField.find_by_name('Billable') - field.update_attribute(:is_required, true) - - e = TimeEntryActivity.new(:name => 'Custom Data') - assert !e.save - assert_equal ["Billable can't be blank"], e.errors.full_messages - end - - def test_create_with_required_custom_field_should_succeed - field = TimeEntryActivityCustomField.find_by_name('Billable') - field.update_attribute(:is_required, true) - - e = TimeEntryActivity.new(:name => 'Custom Data') - e.custom_field_values = {field.id => "1"} - assert e.save - end - - def test_update_with_required_custom_field_change - set_language_if_valid 'en' - field = TimeEntryActivityCustomField.find_by_name('Billable') - field.update_attribute(:is_required, true) - - e = TimeEntryActivity.find(10) - assert e.available_custom_fields.include?(field) - # No change to custom field, record can be saved - assert e.save - # Blanking custom field, save should fail - e.custom_field_values = {field.id => ""} - assert !e.save - assert_equal ["Billable can't be blank"], e.errors.full_messages - - # Update custom field to valid value, save should succeed - e.custom_field_values = {field.id => "0"} - assert e.save - e.reload - assert_equal "0", e.custom_value_for(field).value - end -end - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/22/22ddddc8c878fc3e93d3136a28528f9e4f69f7e0.svn-base --- a/.svn/pristine/22/22ddddc8c878fc3e93d3136a28528f9e4f69f7e0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class Redmine::SafeAttributesTest < ActiveSupport::TestCase - fixtures :users - - class Base - def attributes=(attrs) - attrs.each do |key, value| - send("#{key}=", value) - end - end - end - - class Person < Base - attr_accessor :firstname, :lastname, :login - include Redmine::SafeAttributes - safe_attributes :firstname, :lastname - safe_attributes :login, :if => lambda {|person, user| user.admin?} - end - - class Book < Base - attr_accessor :title - include Redmine::SafeAttributes - safe_attributes :title - end - - def test_safe_attribute_names - p = Person.new - user = User.anonymous - assert_equal ['firstname', 'lastname'], p.safe_attribute_names(user) - assert p.safe_attribute?('firstname', user) - assert !p.safe_attribute?('login', user) - - p = Person.new - user = User.find(1) - assert_equal ['firstname', 'lastname', 'login'], p.safe_attribute_names(user) - assert p.safe_attribute?('firstname', user) - assert p.safe_attribute?('login', user) - end - - def test_safe_attribute_names_without_user - p = Person.new - User.current = nil - assert_equal ['firstname', 'lastname'], p.safe_attribute_names - assert p.safe_attribute?('firstname') - assert !p.safe_attribute?('login') - - p = Person.new - User.current = User.find(1) - assert_equal ['firstname', 'lastname', 'login'], p.safe_attribute_names - assert p.safe_attribute?('firstname') - assert p.safe_attribute?('login') - end - - def test_set_safe_attributes - p = Person.new - p.send('safe_attributes=', {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'}, User.anonymous) - assert_equal 'John', p.firstname - assert_equal 'Smith', p.lastname - assert_nil p.login - - p = Person.new - User.current = User.find(1) - p.send('safe_attributes=', {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'}, User.find(1)) - assert_equal 'John', p.firstname - assert_equal 'Smith', p.lastname - assert_equal 'jsmith', p.login - end - - def test_set_safe_attributes_without_user - p = Person.new - User.current = nil - p.safe_attributes = {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'} - assert_equal 'John', p.firstname - assert_equal 'Smith', p.lastname - assert_nil p.login - - p = Person.new - User.current = User.find(1) - p.safe_attributes = {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'} - assert_equal 'John', p.firstname - assert_equal 'Smith', p.lastname - assert_equal 'jsmith', p.login - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/22/22e0815b30d651aac0c03e283ee06938ada8af0c.svn-base --- a/.svn/pristine/22/22e0815b30d651aac0c03e283ee06938ada8af0c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'net/imap' - -module Redmine - module IMAP - class << self - def check(imap_options={}, options={}) - host = imap_options[:host] || '127.0.0.1' - port = imap_options[:port] || '143' - ssl = !imap_options[:ssl].nil? - folder = imap_options[:folder] || 'INBOX' - - imap = Net::IMAP.new(host, port, ssl) - imap.login(imap_options[:username], imap_options[:password]) unless imap_options[:username].nil? - imap.select(folder) - imap.search(['NOT', 'SEEN']).each do |message_id| - msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822'] - logger.debug "Receiving message #{message_id}" if logger && logger.debug? - if MailHandler.receive(msg, options) - logger.debug "Message #{message_id} successfully received" if logger && logger.debug? - if imap_options[:move_on_success] - imap.copy(message_id, imap_options[:move_on_success]) - end - imap.store(message_id, "+FLAGS", [:Seen, :Deleted]) - else - logger.debug "Message #{message_id} can not be processed" if logger && logger.debug? - imap.store(message_id, "+FLAGS", [:Seen]) - if imap_options[:move_on_failure] - imap.copy(message_id, imap_options[:move_on_failure]) - imap.store(message_id, "+FLAGS", [:Deleted]) - end - end - end - imap.expunge - end - - private - - def logger - ::Rails.logger - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/23/23161378fd0429fed8a9ad8cd7fdacb71cc2db68.svn-base --- a/.svn/pristine/23/23161378fd0429fed8a9ad8cd7fdacb71cc2db68.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -<%= error_messages_for @group %> - -
    -

    <%= f.text_field :name %>

    - <% @group.custom_field_values.each do |value| %> -

    <%= custom_field_tag_with_label :group, value %>

    - <% end %> -
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/23/231b70e58d27161a5f2deb16ab2e057f571aa2ab.svn-base --- a/.svn/pristine/23/231b70e58d27161a5f2deb16ab2e057f571aa2ab.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'blankslate' - -module Redmine - module Views - module Builders - class Json < Structure - attr_accessor :jsonp - - def initialize(request, response) - super - self.jsonp = (request.params[:callback] || request.params[:jsonp]).to_s.gsub(/[^a-zA-Z0-9_]/, '') - end - - def output - json = @struct.first.to_json - if jsonp.present? - json = "#{jsonp}(#{json})" - response.content_type = 'application/javascript' - end - json - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/23/2334e365cbae3c0f02226259643ce7e569ce00f6.svn-base --- a/.svn/pristine/23/2334e365cbae3c0f02226259643ce7e569ce00f6.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class CommentsControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news, :comments - - def setup - User.current = nil - end - - def test_add_comment - @request.session[:user_id] = 2 - post :create, :id => 1, :comment => { :comments => 'This is a test comment' } - assert_redirected_to '/news/1' - - comment = News.find(1).comments.last - assert_not_nil comment - assert_equal 'This is a test comment', comment.comments - assert_equal User.find(2), comment.author - end - - def test_empty_comment_should_not_be_added - @request.session[:user_id] = 2 - assert_no_difference 'Comment.count' do - post :create, :id => 1, :comment => { :comments => '' } - assert_response :redirect - assert_redirected_to '/news/1' - end - end - - def test_create_should_be_denied_if_news_is_not_commentable - News.any_instance.stubs(:commentable?).returns(false) - @request.session[:user_id] = 2 - assert_no_difference 'Comment.count' do - post :create, :id => 1, :comment => { :comments => 'This is a test comment' } - assert_response 403 - end - end - - def test_destroy_comment - comments_count = News.find(1).comments.size - @request.session[:user_id] = 2 - delete :destroy, :id => 1, :comment_id => 2 - assert_redirected_to '/news/1' - assert_nil Comment.find_by_id(2) - assert_equal comments_count - 1, News.find(1).comments.size - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/23/23389be83ae1e1f9aef946f48e6c0e1c8c6bbfb1.svn-base --- a/.svn/pristine/23/23389be83ae1e1f9aef946f48e6c0e1c8c6bbfb1.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,263 +0,0 @@ -<% @gantt.view = self %> -

    <%= @query.new_record? ? l(:label_gantt) : h(@query.name) %>

    - -<%= form_tag({:controller => 'gantts', :action => 'show', - :project_id => @project, :month => params[:month], - :year => params[:year], :months => params[:months]}, - :method => :get, :id => 'query_form') do %> -<%= hidden_field_tag 'set_filter', '1' %> -
    "> - <%= l(:label_filter_plural) %> -
    "> - <%= render :partial => 'queries/filters', :locals => {:query => @query} %> -
    -
    - -

    - <%= gantt_zoom_link(@gantt, :in) %> - <%= gantt_zoom_link(@gantt, :out) %> -

    - -

    -<%= text_field_tag 'months', @gantt.months, :size => 2 %> -<%= l(:label_months_from) %> -<%= select_month(@gantt.month_from, :prefix => "month", :discard_type => true) %> -<%= select_year(@gantt.year_from, :prefix => "year", :discard_type => true) %> -<%= hidden_field_tag 'zoom', @gantt.zoom %> - -<%= link_to_function l(:button_apply), '$("#query_form").submit()', - :class => 'icon icon-checked' %> -<%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, - :class => 'icon icon-reload' %> -

    -<% end %> - -<%= error_messages_for 'query' %> -<% if @query.valid? %> -<% - zoom = 1 - @gantt.zoom.times { zoom = zoom * 2 } - - subject_width = 330 - header_heigth = 18 - - headers_height = header_heigth - show_weeks = false - show_days = false - - if @gantt.zoom > 1 - show_weeks = true - headers_height = 2 * header_heigth - if @gantt.zoom > 2 - show_days = true - headers_height = 3 * header_heigth - end - end - - # Width of the entire chart - g_width = ((@gantt.date_to - @gantt.date_from + 1) * zoom).to_i - @gantt.render(:top => headers_height + 8, - :zoom => zoom, - :g_width => g_width, - :subject_width => subject_width) - g_height = [(20 * (@gantt.number_of_rows + 6)) + 150, 206].max - t_height = g_height + headers_height -%> - -<% if @gantt.truncated %> -

    <%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %>

    -<% end %> - - - - - - - -
    - <% - style = "" - style += "position:relative;" - style += "height: #{t_height + 24}px;" - style += "width: #{subject_width + 1}px;" - %> - <%= content_tag(:div, :style => style) do %> - <% - style = "" - style += "right:-2px;" - style += "width: #{subject_width}px;" - style += "height: #{headers_height}px;" - style += 'background: #eee;' - %> - <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %> - <% - style = "" - style += "right:-2px;" - style += "width: #{subject_width}px;" - style += "height: #{t_height}px;" - style += 'border-left: 1px solid #c0c0c0;' - style += 'overflow: hidden;' - %> - <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %> - <%= content_tag(:div, :class => "gantt_subjects") do %> - <%= @gantt.subjects.html_safe %> - <% end %> - <% end %> - -
    -<% - style = "" - style += "width: #{g_width - 1}px;" - style += "height: #{headers_height}px;" - style += 'background: #eee;' -%> -<%= content_tag(:div, ' '.html_safe, :style => style, :class => "gantt_hdr") %> - -<% ###### Months headers ###### %> -<% - month_f = @gantt.date_from - left = 0 - height = (show_weeks ? header_heigth : header_heigth + g_height) -%> -<% @gantt.months.times do %> - <% - width = (((month_f >> 1) - month_f) * zoom - 1).to_i - style = "" - style += "left: #{left}px;" - style += "width: #{width}px;" - style += "height: #{height}px;" - %> - <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %> - <%= link_to h("#{month_f.year}-#{month_f.month}"), - @gantt.params.merge(:year => month_f.year, :month => month_f.month), - :title => "#{month_name(month_f.month)} #{month_f.year}" %> - <% end %> - <% - left = left + width + 1 - month_f = month_f >> 1 - %> -<% end %> - -<% ###### Weeks headers ###### %> -<% if show_weeks %> - <% - left = 0 - height = (show_days ? header_heigth - 1 : header_heigth - 1 + g_height) - %> - <% if @gantt.date_from.cwday == 1 %> - <% - # @date_from is monday - week_f = @gantt.date_from - %> - <% else %> - <% - # find next monday after @date_from - week_f = @gantt.date_from + (7 - @gantt.date_from.cwday + 1) - width = (7 - @gantt.date_from.cwday + 1) * zoom - 1 - style = "" - style += "left: #{left}px;" - style += "top: 19px;" - style += "width: #{width}px;" - style += "height: #{height}px;" - %> - <%= content_tag(:div, ' '.html_safe, - :style => style, :class => "gantt_hdr") %> - <% left = left + width + 1 %> - <% end %> - <% while week_f <= @gantt.date_to %> - <% - width = ((week_f + 6 <= @gantt.date_to) ? - 7 * zoom - 1 : - (@gantt.date_to - week_f + 1) * zoom - 1).to_i - style = "" - style += "left: #{left}px;" - style += "top: 19px;" - style += "width: #{width}px;" - style += "height: #{height}px;" - %> - <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %> - <%= content_tag(:small) do %> - <%= week_f.cweek if width >= 16 %> - <% end %> - <% end %> - <% - left = left + width + 1 - week_f = week_f + 7 - %> - <% end %> -<% end %> - -<% ###### Days headers ####### %> -<% if show_days %> - <% - left = 0 - height = g_height + header_heigth - 1 - wday = @gantt.date_from.cwday - %> - <% (@gantt.date_to - @gantt.date_from + 1).to_i.times do %> - <% - width = zoom - 1 - style = "" - style += "left: #{left}px;" - style += "top:37px;" - style += "width: #{width}px;" - style += "height: #{height}px;" - style += "font-size:0.7em;" - clss = "gantt_hdr" - clss << " nwday" if @gantt.non_working_week_days.include?(wday) - %> - <%= content_tag(:div, :style => style, :class => clss) do %> - <%= day_letter(wday) %> - <% end %> - <% - left = left + width + 1 - wday = wday + 1 - wday = 1 if wday > 7 - %> - <% end %> -<% end %> - -<%= @gantt.lines.html_safe %> - -<% ###### Today red line (excluded from cache) ###### %> -<% if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %> - <% - today_left = (((Date.today - @gantt.date_from + 1) * zoom).floor() - 1).to_i - style = "" - style += "position: absolute;" - style += "height: #{g_height}px;" - style += "top: #{headers_height + 1}px;" - style += "left: #{today_left}px;" - style += "width:10px;" - style += "border-left: 1px dashed red;" - %> - <%= content_tag(:div, ' '.html_safe, :style => style) %> -<% end %> - -
    -
    - - - - - - -
    - <%= link_to_content_update("\xc2\xab " + l(:label_previous), - params.merge(@gantt.params_previous)) %> - - <%= link_to_content_update(l(:label_next) + " \xc2\xbb", - params.merge(@gantt.params_next)) %> -
    - -<% other_formats_links do |f| %> - <%= f.link_to 'PDF', :url => params.merge(@gantt.params) %> - <%= f.link_to('PNG', :url => params.merge(@gantt.params)) if @gantt.respond_to?('to_image') %> -<% end %> -<% end # query.valid? %> - -<% content_for :sidebar do %> - <%= render :partial => 'issues/sidebar' %> -<% end %> - -<% html_title(l(:label_gantt)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/24/241cbe0d1433ee9e75de970381ea7ac06108d9a9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/24/241cbe0d1433ee9e75de970381ea7ac06108d9a9.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,68 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class WikiContentVersionTest < ActiveSupport::TestCase + fixtures :projects, :users, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + + def setup + end + + def test_destroy + v = WikiContent::Version.find(2) + + assert_difference 'WikiContent::Version.count', -1 do + v.destroy + end + end + + def test_destroy_last_version_should_revert_content + v = WikiContent::Version.find(3) + + assert_no_difference 'WikiPage.count' do + assert_no_difference 'WikiContent.count' do + assert_difference 'WikiContent::Version.count', -1 do + assert v.destroy + end + end + end + c = WikiContent.find(1) + v = c.versions.last + assert_equal 2, c.version + assert_equal v.version, c.version + assert_equal v.comments, c.comments + assert_equal v.text, c.text + assert_equal v.author, c.author + assert_equal v.updated_on, c.updated_on + end + + def test_destroy_all_versions_should_delete_page + WikiContent::Version.find(1).destroy + WikiContent::Version.find(2).destroy + v = WikiContent::Version.find(3) + + assert_difference 'WikiPage.count', -1 do + assert_difference 'WikiContent.count', -1 do + assert_difference 'WikiContent::Version.count', -1 do + assert v.destroy + end + end + end + assert_nil WikiPage.find_by_id(1) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/24/24ef36c7147955d3fac8e868fb99fab632f24089.svn-base --- a/.svn/pristine/24/24ef36c7147955d3fac8e868fb99fab632f24089.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class LayoutTest < ActionController::IntegrationTest - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows - - test "browsing to a missing page should render the base layout" do - get "/users/100000000" - - assert_response :not_found - - # UsersController uses the admin layout by default - assert_select "#admin-menu", :count => 0 - end - - test "browsing to an unauthorized page should render the base layout" do - change_user_password('miscuser9', 'test1234') - - log_user('miscuser9','test1234') - - get "/admin" - assert_response :forbidden - assert_select "#admin-menu", :count => 0 - end - - def test_top_menu_and_search_not_visible_when_login_required - with_settings :login_required => '1' do - get '/' - assert_select "#top-menu > ul", 0 - assert_select "#quick-search", 0 - end - end - - def test_top_menu_and_search_visible_when_login_not_required - with_settings :login_required => '0' do - get '/' - assert_select "#top-menu > ul" - assert_select "#quick-search" - end - end - - def test_wiki_formatter_header_tags - Role.anonymous.add_permission! :add_issues - - get '/projects/ecookbook/issues/new' - assert_tag :script, - :attributes => {:src => %r{^/javascripts/jstoolbar/jstoolbar-textile.min.js}}, - :parent => {:tag => 'head'} - end - - def test_calendar_header_tags - with_settings :default_language => 'fr' do - get '/issues' - assert_include "/javascripts/i18n/jquery.ui.datepicker-fr.js", response.body - end - - with_settings :default_language => 'en-GB' do - get '/issues' - assert_include "/javascripts/i18n/jquery.ui.datepicker-en-GB.js", response.body - end - - with_settings :default_language => 'en' do - get '/issues' - assert_not_include "/javascripts/i18n/jquery.ui.datepicker", response.body - end - end - - def test_search_field_outside_project_should_link_to_global_search - get '/' - assert_select 'div#quick-search form[action=/search]' - end - - def test_search_field_inside_project_should_link_to_project_search - get '/projects/ecookbook' - assert_select 'div#quick-search form[action=/projects/ecookbook/search]' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/252eb4f2f106201746352d88a816534e53e8ac4b.svn-base --- a/.svn/pristine/25/252eb4f2f106201746352d88a816534e53e8ac4b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine #:nodoc: - module CoreExtensions #:nodoc: - module String #:nodoc: - # Custom string conversions - module Conversions - # Parses hours format and returns a float - def to_hours - s = self.dup - s.strip! - if s =~ %r{^(\d+([.,]\d+)?)h?$} - s = $1 - else - # 2:30 => 2.5 - s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 } - # 2h30, 2h, 30m => 2.5, 2, 0.5 - s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}i) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] } - end - # 2,5 => 2.5 - s.gsub!(',', '.') - begin; Kernel.Float(s); rescue; nil; end - end - - # Object#to_a removed in ruby1.9 - if RUBY_VERSION > '1.9' - def to_a - [self.dup] - end - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/25444d6bb6def1b64d15ccc601db528a2e5aca60.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/25444d6bb6def1b64d15ccc601db528a2e5aca60.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1406 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class QueryTest < ActiveSupport::TestCase + include Redmine::I18n + + fixtures :projects, :enabled_modules, :users, :members, + :member_roles, :roles, :trackers, :issue_statuses, + :issue_categories, :enumerations, :issues, + :watchers, :custom_fields, :custom_values, :versions, + :queries, + :projects_trackers, + :custom_fields_trackers + + def test_query_with_roles_visibility_should_validate_roles + set_language_if_valid 'en' + query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES) + assert !query.save + assert_include "Roles can't be blank", query.errors.full_messages + query.role_ids = [1, 2] + assert query.save + end + + def test_changing_roles_visibility_should_clear_roles + query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2]) + assert_equal 2, query.roles.count + + query.visibility = IssueQuery::VISIBILITY_PUBLIC + query.save! + assert_equal 0, query.roles.count + end + + def test_available_filters_should_be_ordered + set_language_if_valid 'en' + query = IssueQuery.new + assert_equal 0, query.available_filters.keys.index('status_id') + expected_order = [ + "Status", + "Project", + "Tracker", + "Priority" + ] + assert_equal expected_order, + (query.available_filters.values.map{|v| v[:name]} & expected_order) + end + + def test_available_filters_with_custom_fields_should_be_ordered + set_language_if_valid 'en' + UserCustomField.create!( + :name => 'order test', :field_format => 'string', + :is_for_all => true, :is_filter => true + ) + query = IssueQuery.new + expected_order = [ + "Searchable field", + "Database", + "Project's Development status", + "Author's order test", + "Assignee's order test" + ] + assert_equal expected_order, + (query.available_filters.values.map{|v| v[:name]} & expected_order) + end + + def test_custom_fields_for_all_projects_should_be_available_in_global_queries + query = IssueQuery.new(:project => nil, :name => '_') + assert query.available_filters.has_key?('cf_1') + assert !query.available_filters.has_key?('cf_3') + end + + def test_system_shared_versions_should_be_available_in_global_queries + Version.find(2).update_attribute :sharing, 'system' + query = IssueQuery.new(:project => nil, :name => '_') + assert query.available_filters.has_key?('fixed_version_id') + assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'} + end + + def test_project_filter_in_global_queries + query = IssueQuery.new(:project => nil, :name => '_') + project_filter = query.available_filters["project_id"] + assert_not_nil project_filter + project_ids = project_filter[:values].map{|p| p[1]} + assert project_ids.include?("1") #public project + assert !project_ids.include?("2") #private project user cannot see + end + + def find_issues_with_query(query) + Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( + query.statement + ).all + end + + def assert_find_issues_with_query_is_successful(query) + assert_nothing_raised do + find_issues_with_query(query) + end + end + + def assert_query_statement_includes(query, condition) + assert_include condition, query.statement + end + + def assert_query_result(expected, query) + assert_nothing_raised do + assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort + assert_equal expected.size, query.issue_count + end + end + + def test_query_should_allow_shared_versions_for_a_project_query + subproject_version = Version.find(4) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s]) + + assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')") + end + + def test_query_with_multiple_custom_fields + query = IssueQuery.find(1) + assert query.valid? + assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')") + issues = find_issues_with_query(query) + assert_equal 1, issues.length + assert_equal Issue.find(3), issues.first + end + + def test_operator_none + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('fixed_version_id', '!*', ['']) + query.add_filter('cf_1', '!*', ['']) + assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL") + assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''") + find_issues_with_query(query) + end + + def test_operator_none_for_integer + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('estimated_hours', '!*', ['']) + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| !i.estimated_hours} + end + + def test_operator_none_for_date + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('start_date', '!*', ['']) + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| i.start_date.nil?} + end + + def test_operator_none_for_string_custom_field + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('cf_2', '!*', ['']) + assert query.has_filter?('cf_2') + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| i.custom_field_value(2).blank?} + end + + def test_operator_all + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('fixed_version_id', '*', ['']) + query.add_filter('cf_1', '*', ['']) + assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL") + assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''") + find_issues_with_query(query) + end + + def test_operator_all_for_date + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('start_date', '*', ['']) + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| i.start_date.present?} + end + + def test_operator_all_for_string_custom_field + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('cf_2', '*', ['']) + assert query.has_filter?('cf_2') + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| i.custom_field_value(2).present?} + end + + def test_numeric_filter_should_not_accept_non_numeric_values + query = IssueQuery.new(:name => '_') + query.add_filter('estimated_hours', '=', ['a']) + + assert query.has_filter?('estimated_hours') + assert !query.valid? + end + + def test_operator_is_on_float + Issue.update_all("estimated_hours = 171.2", "id=2") + + query = IssueQuery.new(:name => '_') + query.add_filter('estimated_hours', '=', ['171.20']) + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_integer_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['12']) + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_integer_custom_field_should_accept_negative_value + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['-12']) + assert query.valid? + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_float_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['12.7']) + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_float_custom_field_should_accept_negative_value + f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['-12.7']) + assert query.valid? + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_multi_list_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, + :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['value1']) + issues = find_issues_with_query(query) + assert_equal [1, 3], issues.map(&:id).sort + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['value2']) + issues = find_issues_with_query(query) + assert_equal [1], issues.map(&:id).sort + end + + def test_operator_is_not_on_multi_list_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, + :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '!', ['value1']) + issues = find_issues_with_query(query) + assert !issues.map(&:id).include?(1) + assert !issues.map(&:id).include?(3) + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '!', ['value2']) + issues = find_issues_with_query(query) + assert !issues.map(&:id).include?(1) + assert issues.map(&:id).include?(3) + end + + def test_operator_is_on_is_private_field + # is_private filter only available for those who can set issues private + User.current = User.find(2) + + query = IssueQuery.new(:name => '_') + assert query.available_filters.key?('is_private') + + query.add_filter("is_private", '=', ['1']) + issues = find_issues_with_query(query) + assert issues.any? + assert_nil issues.detect {|issue| !issue.is_private?} + ensure + User.current = nil + end + + def test_operator_is_not_on_is_private_field + # is_private filter only available for those who can set issues private + User.current = User.find(2) + + query = IssueQuery.new(:name => '_') + assert query.available_filters.key?('is_private') + + query.add_filter("is_private", '!', ['1']) + issues = find_issues_with_query(query) + assert issues.any? + assert_nil issues.detect {|issue| issue.is_private?} + ensure + User.current = nil + end + + def test_operator_greater_than + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('done_ratio', '>=', ['40']) + assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0") + find_issues_with_query(query) + end + + def test_operator_greater_than_a_float + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('estimated_hours', '>=', ['40.5']) + assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5") + find_issues_with_query(query) + end + + def test_operator_greater_than_on_int_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true, :trackers => Tracker.all) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter("cf_#{f.id}", '>=', ['8']) + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_lesser_than + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('done_ratio', '<=', ['30']) + assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0") + find_issues_with_query(query) + end + + def test_operator_lesser_than_on_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter("cf_#{f.id}", '<=', ['30']) + assert_match /CAST.+ <= 30\.0/, query.statement + find_issues_with_query(query) + end + + def test_operator_lesser_than_on_date_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true, :trackers => Tracker.all) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter("cf_#{f.id}", '<=', ['2013-05-01']) + issue_ids = find_issues_with_query(query).map(&:id) + assert_include 1, issue_ids + assert_not_include 2, issue_ids + assert_not_include 3, issue_ids + end + + def test_operator_between + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('done_ratio', '><', ['30', '40']) + assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement + find_issues_with_query(query) + end + + def test_operator_between_on_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter("cf_#{f.id}", '><', ['30', '40']) + assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement + find_issues_with_query(query) + end + + def test_date_filter_should_not_accept_non_date_values + query = IssueQuery.new(:name => '_') + query.add_filter('created_on', '=', ['a']) + + assert query.has_filter?('created_on') + assert !query.valid? + end + + def test_date_filter_should_not_accept_invalid_date_values + query = IssueQuery.new(:name => '_') + query.add_filter('created_on', '=', ['2011-01-34']) + + assert query.has_filter?('created_on') + assert !query.valid? + end + + def test_relative_date_filter_should_not_accept_non_integer_values + query = IssueQuery.new(:name => '_') + query.add_filter('created_on', '>t-', ['a']) + + assert query.has_filter?('created_on') + assert !query.valid? + end + + def test_operator_date_equals + query = IssueQuery.new(:name => '_') + query.add_filter('due_date', '=', ['2011-07-10']) + assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement + find_issues_with_query(query) + end + + def test_operator_date_lesser_than + query = IssueQuery.new(:name => '_') + query.add_filter('due_date', '<=', ['2011-07-10']) + assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement + find_issues_with_query(query) + end + + def test_operator_date_greater_than + query = IssueQuery.new(:name => '_') + query.add_filter('due_date', '>=', ['2011-07-10']) + assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement + find_issues_with_query(query) + end + + def test_operator_date_between + query = IssueQuery.new(:name => '_') + query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10']) + assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?'/, query.statement + find_issues_with_query(query) + end + + def test_operator_in_more_than + Issue.find(7).update_attribute(:due_date, (Date.today + 15)) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', '>t+', ['15']) + issues = find_issues_with_query(query) + assert !issues.empty? + issues.each {|issue| assert(issue.due_date >= (Date.today + 15))} + end + + def test_operator_in_less_than + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', ' Project.find(1), :name => '_') + query.add_filter('due_date', '>= Date.today && issue.due_date <= (Date.today + 15))} + end + + def test_operator_less_than_ago + Issue.find(7).update_attribute(:due_date, (Date.today - 3)) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', '>t-', ['3']) + issues = find_issues_with_query(query) + assert !issues.empty? + issues.each {|issue| assert(issue.due_date >= (Date.today - 3))} + end + + def test_operator_in_the_past_days + Issue.find(7).update_attribute(:due_date, (Date.today - 3)) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', '>= (Date.today - 3) && issue.due_date <= Date.today)} + end + + def test_operator_more_than_ago + Issue.find(7).update_attribute(:due_date, (Date.today - 10)) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', ' Project.find(1), :name => '_') + query.add_filter('due_date', 'w', ['']) + assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}" + I18n.locale = :en + end + + def test_range_for_this_week_with_week_starting_on_sunday + I18n.locale = :en + assert_equal '7', I18n.t(:general_first_day_of_week) + + Date.stubs(:today).returns(Date.parse('2011-04-29')) + + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', 'w', ['']) + assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}" + end + + def test_operator_does_not_contains + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('subject', '!~', ['uNable']) + assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'") + find_issues_with_query(query) + end + + def test_filter_assigned_to_me + user = User.find(2) + group = Group.find(10) + User.current = user + i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user) + i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group) + i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11)) + group.users << user + + query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}}) + result = query.issues + assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id) + + assert result.include?(i1) + assert result.include?(i2) + assert !result.include?(i3) + end + + def test_user_custom_field_filtered_on_me + User.current = User.find(2) + cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) + issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1) + issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'}) + + query = IssueQuery.new(:name => '_', :project => Project.find(1)) + filter = query.available_filters["cf_#{cf.id}"] + assert_not_nil filter + assert_include 'me', filter[:values].map{|v| v[1]} + + query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}} + result = query.issues + assert_equal 1, result.size + assert_equal issue1, result.first + end + + def test_filter_my_projects + User.current = User.find(2) + query = IssueQuery.new(:name => '_') + filter = query.available_filters['project_id'] + assert_not_nil filter + assert_include 'mine', filter[:values].map{|v| v[1]} + + query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}} + result = query.issues + assert_nil result.detect {|issue| !User.current.member_of?(issue.project)} + end + + def test_filter_watched_issues + User.current = User.find(1) + query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}}) + result = find_issues_with_query(query) + assert_not_nil result + assert !result.empty? + assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id) + User.current = nil + end + + def test_filter_unwatched_issues + User.current = User.find(1) + query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}}) + result = find_issues_with_query(query) + assert_not_nil result + assert !result.empty? + assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size) + User.current = nil + end + + def test_filter_on_custom_field_should_ignore_projects_with_field_disabled + field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_filter => true) + Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'}) + Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'}) + + query = IssueQuery.new(:name => '_', :project => Project.find(1)) + query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}} + assert_equal 2, find_issues_with_query(query).size + + field.project_ids = [1, 3] # Disable the field for project 4 + field.save! + assert_equal 1, find_issues_with_query(query).size + end + + def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled + field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true) + Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'}) + Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'}) + + query = IssueQuery.new(:name => '_', :project => Project.find(1)) + query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}} + assert_equal 2, find_issues_with_query(query).size + + field.tracker_ids = [1] # Disable the field for tracker 2 + field.save! + assert_equal 1, find_issues_with_query(query).size + end + + def test_filter_on_project_custom_field + field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo') + CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo') + + query = IssueQuery.new(:name => '_') + filter_name = "project.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort + end + + def test_filter_on_author_custom_field + field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') + + query = IssueQuery.new(:name => '_') + filter_name = "author.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort + end + + def test_filter_on_assigned_to_custom_field + field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') + + query = IssueQuery.new(:name => '_') + filter_name = "assigned_to.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort + end + + def test_filter_on_fixed_version_custom_field + field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo') + + query = IssueQuery.new(:name => '_') + filter_name = "fixed_version.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort + end + + def test_filter_on_relations_with_a_specific_issue + IssueRelation.delete_all + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=', :values => ['1']}} + assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=', :values => ['2']}} + assert_equal [1], find_issues_with_query(query).map(&:id).sort + end + + def test_filter_on_relations_with_any_issues_in_a_project + IssueRelation.delete_all + with_settings :cross_project_issue_relations => '1' do + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) + end + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=p', :values => ['2']}} + assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=p', :values => ['3']}} + assert_equal [1], find_issues_with_query(query).map(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=p', :values => ['4']}} + assert_equal [], find_issues_with_query(query).map(&:id).sort + end + + def test_filter_on_relations_with_any_issues_not_in_a_project + IssueRelation.delete_all + with_settings :cross_project_issue_relations => '1' do + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) + #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) + end + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=!p', :values => ['1']}} + assert_equal [1], find_issues_with_query(query).map(&:id).sort + end + + def test_filter_on_relations_with_no_issues_in_a_project + IssueRelation.delete_all + with_settings :cross_project_issue_relations => '1' do + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3)) + end + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '!p', :values => ['2']}} + ids = find_issues_with_query(query).map(&:id).sort + assert_include 2, ids + assert_not_include 1, ids + assert_not_include 3, ids + end + + def test_filter_on_relations_with_no_issues + IssueRelation.delete_all + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '!*', :values => ['']}} + ids = find_issues_with_query(query).map(&:id) + assert_equal [], ids & [1, 2, 3] + assert_include 4, ids + end + + def test_filter_on_relations_with_any_issues + IssueRelation.delete_all + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '*', :values => ['']}} + assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort + end + + def test_filter_on_relations_should_not_ignore_other_filter + issue = Issue.generate! + issue1 = Issue.generate!(:status_id => 1) + issue2 = Issue.generate!(:status_id => 2) + IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1) + IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2) + + query = IssueQuery.new(:name => '_') + query.filters = { + "status_id" => {:operator => '=', :values => ['1']}, + "relates" => {:operator => '=', :values => [issue.id.to_s]} + } + assert_equal [issue1], find_issues_with_query(query) + end + + def test_statement_should_be_nil_with_no_filters + q = IssueQuery.new(:name => '_') + q.filters = {} + + assert q.valid? + assert_nil q.statement + end + + def test_default_columns + q = IssueQuery.new + assert q.columns.any? + assert q.inline_columns.any? + assert q.block_columns.empty? + end + + def test_set_column_names + q = IssueQuery.new + q.column_names = ['tracker', :subject, '', 'unknonw_column'] + assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name} + end + + def test_has_column_should_accept_a_column_name + q = IssueQuery.new + q.column_names = ['tracker', :subject] + assert q.has_column?(:tracker) + assert !q.has_column?(:category) + end + + def test_has_column_should_accept_a_column + q = IssueQuery.new + q.column_names = ['tracker', :subject] + + tracker_column = q.available_columns.detect {|c| c.name==:tracker} + assert_kind_of QueryColumn, tracker_column + category_column = q.available_columns.detect {|c| c.name==:category} + assert_kind_of QueryColumn, category_column + + assert q.has_column?(tracker_column) + assert !q.has_column?(category_column) + end + + def test_inline_and_block_columns + q = IssueQuery.new + q.column_names = ['subject', 'description', 'tracker'] + + assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name) + assert_equal [:description], q.block_columns.map(&:name) + end + + def test_custom_field_columns_should_be_inline + q = IssueQuery.new + columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn} + assert columns.any? + assert_nil columns.detect {|column| !column.inline?} + end + + def test_query_should_preload_spent_hours + q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours]) + assert q.has_column?(:spent_hours) + issues = q.issues + assert_not_nil issues.first.instance_variable_get("@spent_hours") + end + + def test_groupable_columns_should_include_custom_fields + q = IssueQuery.new + column = q.groupable_columns.detect {|c| c.name == :cf_1} + assert_not_nil column + assert_kind_of QueryCustomFieldColumn, column + end + + def test_groupable_columns_should_not_include_multi_custom_fields + field = CustomField.find(1) + field.update_attribute :multiple, true + + q = IssueQuery.new + column = q.groupable_columns.detect {|c| c.name == :cf_1} + assert_nil column + end + + def test_groupable_columns_should_include_user_custom_fields + cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user') + + q = IssueQuery.new + assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} + end + + def test_groupable_columns_should_include_version_custom_fields + cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version') + + q = IssueQuery.new + assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} + end + + def test_grouped_with_valid_column + q = IssueQuery.new(:group_by => 'status') + assert q.grouped? + assert_not_nil q.group_by_column + assert_equal :status, q.group_by_column.name + assert_not_nil q.group_by_statement + assert_equal 'status', q.group_by_statement + end + + def test_grouped_with_invalid_column + q = IssueQuery.new(:group_by => 'foo') + assert !q.grouped? + assert_nil q.group_by_column + assert_nil q.group_by_statement + end + + def test_sortable_columns_should_sort_assignees_according_to_user_format_setting + with_settings :user_format => 'lastname_coma_firstname' do + q = IssueQuery.new + assert q.sortable_columns.has_key?('assigned_to') + assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to'] + end + end + + def test_sortable_columns_should_sort_authors_according_to_user_format_setting + with_settings :user_format => 'lastname_coma_firstname' do + q = IssueQuery.new + assert q.sortable_columns.has_key?('author') + assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author'] + end + end + + def test_sortable_columns_should_include_custom_field + q = IssueQuery.new + assert q.sortable_columns['cf_1'] + end + + def test_sortable_columns_should_not_include_multi_custom_field + field = CustomField.find(1) + field.update_attribute :multiple, true + + q = IssueQuery.new + assert !q.sortable_columns['cf_1'] + end + + def test_default_sort + q = IssueQuery.new + assert_equal [], q.sort_criteria + end + + def test_set_sort_criteria_with_hash + q = IssueQuery.new + q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']} + assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria + end + + def test_set_sort_criteria_with_array + q = IssueQuery.new + q.sort_criteria = [['priority', 'desc'], 'tracker'] + assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria + end + + def test_create_query_with_sort + q = IssueQuery.new(:name => 'Sorted') + q.sort_criteria = [['priority', 'desc'], 'tracker'] + assert q.save + q.reload + assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria + end + + def test_sort_by_string_custom_field_asc + q = IssueQuery.new + c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } + assert c + assert c.sortable + issues = q.issues(:order => "#{c.sortable} ASC") + values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} + assert !values.empty? + assert_equal values.sort, values + end + + def test_sort_by_string_custom_field_desc + q = IssueQuery.new + c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } + assert c + assert c.sortable + issues = q.issues(:order => "#{c.sortable} DESC") + values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} + assert !values.empty? + assert_equal values.sort.reverse, values + end + + def test_sort_by_float_custom_field_asc + q = IssueQuery.new + c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' } + assert c + assert c.sortable + issues = q.issues(:order => "#{c.sortable} ASC") + values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact + assert !values.empty? + assert_equal values.sort, values + end + + def test_invalid_query_should_raise_query_statement_invalid_error + q = IssueQuery.new + assert_raise Query::StatementInvalid do + q.issues(:conditions => "foo = 1") + end + end + + def test_issue_count + q = IssueQuery.new(:name => '_') + issue_count = q.issue_count + assert_equal q.issues.size, issue_count + end + + def test_issue_count_with_archived_issues + p = Project.generate! do |project| + project.status = Project::STATUS_ARCHIVED + end + i = Issue.generate!( :project => p, :tracker => p.trackers.first ) + assert !i.visible? + + test_issue_count + end + + def test_issue_count_by_association_group + q = IssueQuery.new(:name => '_', :group_by => 'assigned_to') + count_by_group = q.issue_count_by_group + assert_kind_of Hash, count_by_group + assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort + assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq + assert count_by_group.has_key?(User.find(3)) + end + + def test_issue_count_by_list_custom_field_group + q = IssueQuery.new(:name => '_', :group_by => 'cf_1') + count_by_group = q.issue_count_by_group + assert_kind_of Hash, count_by_group + assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort + assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq + assert count_by_group.has_key?('MySQL') + end + + def test_issue_count_by_date_custom_field_group + q = IssueQuery.new(:name => '_', :group_by => 'cf_8') + count_by_group = q.issue_count_by_group + assert_kind_of Hash, count_by_group + assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort + assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq + end + + def test_issue_count_with_nil_group_only + Issue.update_all("assigned_to_id = NULL") + + q = IssueQuery.new(:name => '_', :group_by => 'assigned_to') + count_by_group = q.issue_count_by_group + assert_kind_of Hash, count_by_group + assert_equal 1, count_by_group.keys.size + assert_nil count_by_group.keys.first + end + + def test_issue_ids + q = IssueQuery.new(:name => '_') + order = "issues.subject, issues.id" + issues = q.issues(:order => order) + assert_equal issues.map(&:id), q.issue_ids(:order => order) + end + + def test_label_for + set_language_if_valid 'en' + q = IssueQuery.new + assert_equal 'Assignee', q.label_for('assigned_to_id') + end + + def test_label_for_fr + set_language_if_valid 'fr' + q = IssueQuery.new + s = "Assign\xc3\xa9 \xc3\xa0" + s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) + assert_equal s, q.label_for('assigned_to_id') + end + + def test_editable_by + admin = User.find(1) + manager = User.find(2) + developer = User.find(3) + + # Public query on project 1 + q = IssueQuery.find(1) + assert q.editable_by?(admin) + assert q.editable_by?(manager) + assert !q.editable_by?(developer) + + # Private query on project 1 + q = IssueQuery.find(2) + assert q.editable_by?(admin) + assert !q.editable_by?(manager) + assert q.editable_by?(developer) + + # Private query for all projects + q = IssueQuery.find(3) + assert q.editable_by?(admin) + assert !q.editable_by?(manager) + assert q.editable_by?(developer) + + # Public query for all projects + q = IssueQuery.find(4) + assert q.editable_by?(admin) + assert !q.editable_by?(manager) + assert !q.editable_by?(developer) + end + + def test_visible_scope + query_ids = IssueQuery.visible(User.anonymous).map(&:id) + + assert query_ids.include?(1), 'public query on public project was not visible' + assert query_ids.include?(4), 'public query for all projects was not visible' + assert !query_ids.include?(2), 'private query on public project was visible' + assert !query_ids.include?(3), 'private query for all projects was visible' + assert !query_ids.include?(7), 'public query on private project was visible' + end + + def test_query_with_public_visibility_should_be_visible_to_anyone + q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PUBLIC) + + assert q.visible?(User.anonymous) + assert IssueQuery.visible(User.anonymous).find_by_id(q.id) + + assert q.visible?(User.find(7)) + assert IssueQuery.visible(User.find(7)).find_by_id(q.id) + + assert q.visible?(User.find(2)) + assert IssueQuery.visible(User.find(2)).find_by_id(q.id) + + assert q.visible?(User.find(1)) + assert IssueQuery.visible(User.find(1)).find_by_id(q.id) + end + + def test_query_with_roles_visibility_should_be_visible_to_user_with_role + q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1,2]) + + assert !q.visible?(User.anonymous) + assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id) + + assert !q.visible?(User.find(7)) + assert_nil IssueQuery.visible(User.find(7)).find_by_id(q.id) + + assert q.visible?(User.find(2)) + assert IssueQuery.visible(User.find(2)).find_by_id(q.id) + + assert q.visible?(User.find(1)) + assert IssueQuery.visible(User.find(1)).find_by_id(q.id) + end + + def test_query_with_private_visibility_should_be_visible_to_owner + q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PRIVATE, :user => User.find(7)) + + assert !q.visible?(User.anonymous) + assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id) + + assert q.visible?(User.find(7)) + assert IssueQuery.visible(User.find(7)).find_by_id(q.id) + + assert !q.visible?(User.find(2)) + assert_nil IssueQuery.visible(User.find(2)).find_by_id(q.id) + + assert q.visible?(User.find(1)) + assert_nil IssueQuery.visible(User.find(1)).find_by_id(q.id) + end + + test "#available_filters should include users of visible projects in cross-project view" do + users = IssueQuery.new.available_filters["assigned_to_id"] + assert_not_nil users + assert users[:values].map{|u|u[1]}.include?("3") + end + + test "#available_filters should include users of subprojects" do + user1 = User.generate! + user2 = User.generate! + project = Project.find(1) + Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1]) + + users = IssueQuery.new(:project => project).available_filters["assigned_to_id"] + assert_not_nil users + assert users[:values].map{|u|u[1]}.include?(user1.id.to_s) + assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s) + end + + test "#available_filters should include visible projects in cross-project view" do + projects = IssueQuery.new.available_filters["project_id"] + assert_not_nil projects + assert projects[:values].map{|u|u[1]}.include?("1") + end + + test "#available_filters should include 'member_of_group' filter" do + query = IssueQuery.new + assert query.available_filters.keys.include?("member_of_group") + assert_equal :list_optional, query.available_filters["member_of_group"][:type] + assert query.available_filters["member_of_group"][:values].present? + assert_equal Group.all.sort.map {|g| [g.name, g.id.to_s]}, + query.available_filters["member_of_group"][:values].sort + end + + test "#available_filters should include 'assigned_to_role' filter" do + query = IssueQuery.new + assert query.available_filters.keys.include?("assigned_to_role") + assert_equal :list_optional, query.available_filters["assigned_to_role"][:type] + + assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1']) + assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2']) + assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3']) + + assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4']) + assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5']) + end + + def test_available_filters_should_include_custom_field_according_to_user_visibility + visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true) + hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1]) + + with_current_user User.find(3) do + query = IssueQuery.new + assert_include "cf_#{visible_field.id}", query.available_filters.keys + assert_not_include "cf_#{hidden_field.id}", query.available_filters.keys + end + end + + def test_available_columns_should_include_custom_field_according_to_user_visibility + visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true) + hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1]) + + with_current_user User.find(3) do + query = IssueQuery.new + assert_include :"cf_#{visible_field.id}", query.available_columns.map(&:name) + assert_not_include :"cf_#{hidden_field.id}", query.available_columns.map(&:name) + end + end + + context "#statement" do + context "with 'member_of_group' filter" do + setup do + Group.destroy_all # No fixtures + @user_in_group = User.generate! + @second_user_in_group = User.generate! + @user_in_group2 = User.generate! + @user_not_in_group = User.generate! + + @group = Group.generate!.reload + @group.users << @user_in_group + @group.users << @second_user_in_group + + @group2 = Group.generate!.reload + @group2.users << @user_in_group2 + + end + + should "search assigned to for users in the group" do + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '=', [@group.id.to_s]) + + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@group.id}')" + assert_find_issues_with_query_is_successful @query + end + + should "search not assigned to any group member (none)" do + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '!*', ['']) + + # Users not in a group + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')" + assert_find_issues_with_query_is_successful @query + end + + should "search assigned to any group member (all)" do + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '*', ['']) + + # Only users in a group + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')" + assert_find_issues_with_query_is_successful @query + end + + should "return an empty set with = empty group" do + @empty_group = Group.generate! + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '=', [@empty_group.id.to_s]) + + assert_equal [], find_issues_with_query(@query) + end + + should "return issues with ! empty group" do + @empty_group = Group.generate! + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '!', [@empty_group.id.to_s]) + + assert_find_issues_with_query_is_successful @query + end + end + + context "with 'assigned_to_role' filter" do + setup do + @manager_role = Role.find_by_name('Manager') + @developer_role = Role.find_by_name('Developer') + + @project = Project.generate! + @manager = User.generate! + @developer = User.generate! + @boss = User.generate! + @guest = User.generate! + User.add_to_project(@manager, @project, @manager_role) + User.add_to_project(@developer, @project, @developer_role) + User.add_to_project(@boss, @project, [@manager_role, @developer_role]) + + @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id) + @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id) + @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id) + @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id) + @issue5 = Issue.generate!(:project => @project) + end + + should "search assigned to for users with the Role" do + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) + + assert_query_result [@issue1, @issue3], @query + end + + should "search assigned to for users with the Role on the issue project" do + other_project = Project.generate! + User.add_to_project(@developer, other_project, @manager_role) + + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) + + assert_query_result [@issue1, @issue3], @query + end + + should "return an empty set with empty role" do + @empty_role = Role.generate! + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s]) + + assert_query_result [], @query + end + + should "search assigned to for users without the Role" do + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s]) + + assert_query_result [@issue2, @issue4, @issue5], @query + end + + should "search assigned to for users not assigned to any Role (none)" do + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '!*', ['']) + + assert_query_result [@issue4, @issue5], @query + end + + should "search assigned to for users assigned to any Role (all)" do + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '*', ['']) + + assert_query_result [@issue1, @issue2, @issue3], @query + end + + should "return issues with ! empty role" do + @empty_role = Role.generate! + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s]) + + assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/25955007822b3924f8cfe4c9f332b3be0a33dd6f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/25955007822b3924f8cfe4c9f332b3be0a33dd6f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,61 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::ConfigurationTest < ActiveSupport::TestCase + def setup + @conf = Redmine::Configuration + end + + def test_empty + assert_kind_of Hash, load_conf('empty.yml', 'test') + end + + def test_default + assert_kind_of Hash, load_conf('default.yml', 'test') + assert_equal 'foo', @conf['somesetting'] + end + + def test_no_default + assert_kind_of Hash, load_conf('no_default.yml', 'test') + assert_equal 'foo', @conf['somesetting'] + end + + def test_overrides + assert_kind_of Hash, load_conf('overrides.yml', 'test') + assert_equal 'bar', @conf['somesetting'] + end + + def test_with + load_conf('default.yml', 'test') + assert_equal 'foo', @conf['somesetting'] + @conf.with 'somesetting' => 'bar' do + assert_equal 'bar', @conf['somesetting'] + end + assert_equal 'foo', @conf['somesetting'] + end + + private + + def load_conf(file, env) + @conf.load( + :file => File.join(Rails.root, 'test', 'fixtures', 'configuration', file), + :env => env + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/25969a217fc5385c0e7df92d761b2fee83db869b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/25969a217fc5385c0e7df92d761b2fee83db869b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,42 @@ +/.project +/.loadpath +/.powrc +/.rvmrc +/config/additional_environment.rb +/config/configuration.yml +/config/database.yml +/config/email.yml +/config/initializers/session_store.rb +/config/initializers/secret_token.rb +/coverage +/db/*.db +/db/*.sqlite3 +/db/schema.rb +/files/* +/lib/redmine/scm/adapters/mercurial/redminehelper.pyc +/lib/redmine/scm/adapters/mercurial/redminehelper.pyo +/log/*.log* +/log/mongrel_debug +/plugins/* +!/plugins/README +/public/dispatch.* +/public/plugin_assets +/public/themes/* +!/public/themes/alternate +!/public/themes/classic +!/public/themes/README +/tmp/* +/tmp/cache/* +/tmp/pdf/* +/tmp/sessions/* +/tmp/sockets/* +/tmp/test/* +/tmp/thumbnails/* +/vendor/cache +/vendor/rails +*.rbc + +/.bundle +/Gemfile.lock +/Gemfile.local + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/25a75439387b5e7c9157d74c8baba2d41c8319c0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/25a75439387b5e7c9157d74c8baba2d41c8319c0.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,48 @@ +
    +<%= link_to l(:button_log_time), + {:controller => 'timelog', :action => 'new', :project_id => @project, :issue_id => @issue}, + :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project, :global => true) %> +
    + +<%= render_timelog_breadcrumb %> + +

    <%= l(:label_spent_time) %>

    + +<%= form_tag({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}, :method => :get, :id => 'query_form') do %> +<%= render :partial => 'date_range' %> +<% end %> + +
    +

    <%= l(:label_total_time) %>: <%= html_hours(l_hours(@total_hours)) %>

    +
    + +<% unless @entries.empty? %> +<%= render :partial => 'list', :locals => { :entries => @entries }%> +

    <%= pagination_links_full @entry_pages, @entry_count %>

    + +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => params.merge({:issue_id => @issue, :key => User.current.rss_key}) %> + <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '330px'); return false;" %> +<% end %> + + +<% end %> + +<% html_title l(:label_spent_time), l(:label_details) %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {:issue_id => @issue, :format => 'atom', :key => User.current.rss_key}, :title => l(:label_spent_time)) %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/25a7a4fdea85fbb1b14478fb690111974ca9c5c6.svn-base --- a/.svn/pristine/25/25a7a4fdea85fbb1b14478fb690111974ca9c5c6.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class DocumentCategory < Enumeration - has_many :documents, :foreign_key => 'category_id' - - OptionName = :enumeration_doc_categories - - def option_name - OptionName - end - - def objects_count - documents.count - end - - def transfer_relations(to) - documents.update_all("category_id = #{to.id}") - end - - def self.default - d = super - d = first if d.nil? - d - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/25cdcad944ee39e6a9c2a56e5a525014ecebc7bc.svn-base --- a/.svn/pristine/25/25cdcad944ee39e6a9c2a56e5a525014ecebc7bc.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,525 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class RepositoriesMercurialControllerTest < ActionController::TestCase - tests RepositoriesController - - fixtures :projects, :users, :roles, :members, :member_roles, - :repositories, :enabled_modules - - REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s - CHAR_1_HEX = "\xc3\x9c" - PRJ_ID = 3 - NUM_REV = 32 - - ruby19_non_utf8_pass = - (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8') - - def setup - User.current = nil - @project = Project.find(PRJ_ID) - @repository = Repository::Mercurial.create( - :project => @project, - :url => REPOSITORY_PATH, - :path_encoding => 'ISO-8859-1' - ) - assert @repository - @diff_c_support = true - @char_1 = CHAR_1_HEX.dup - @tag_char_1 = "tag-#{CHAR_1_HEX}-00" - @branch_char_0 = "branch-#{CHAR_1_HEX}-00" - @branch_char_1 = "branch-#{CHAR_1_HEX}-01" - if @char_1.respond_to?(:force_encoding) - @char_1.force_encoding('UTF-8') - @tag_char_1.force_encoding('UTF-8') - @branch_char_0.force_encoding('UTF-8') - @branch_char_1.force_encoding('UTF-8') - end - end - - if ruby19_non_utf8_pass - puts "TODO: Mercurial functional test fails in Ruby 1.9 " + - "and Encoding.default_external is not UTF-8. " + - "Current value is '#{Encoding.default_external.to_s}'" - def test_fake; assert true end - elsif File.directory?(REPOSITORY_PATH) - - def test_get_new - @request.session[:user_id] = 1 - @project.repository.destroy - get :new, :project_id => 'subproject1', :repository_scm => 'Mercurial' - assert_response :success - assert_template 'new' - assert_kind_of Repository::Mercurial, assigns(:repository) - assert assigns(:repository).new_record? - end - - def test_show_root - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal 4, assigns(:entries).size - assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - - def test_show_directory - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) - entry = assigns(:entries).detect {|e| e.name == 'edit.png'} - assert_not_nil entry - assert_equal 'file', entry.kind - assert_equal 'images/edit.png', entry.path - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - - def test_show_at_given_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - [0, '0', '0885933ad4f6'].each do |r1| - get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param], - :rev => r1 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['delete.png'], assigns(:entries).collect(&:name) - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - end - - def test_show_directory_sql_escape_percent - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - [13, '13', '3a330eb32958'].each do |r1| - get :show, :id => PRJ_ID, - :path => repository_path_hash(['sql_escape', 'percent%dir'])[:param], - :rev => r1 - assert_response :success - assert_template 'show' - - assert_not_nil assigns(:entries) - assert_equal ['percent%file1.txt', 'percentfile1.txt'], - assigns(:entries).collect(&:name) - changesets = assigns(:changesets) - assert_not_nil changesets - assert assigns(:changesets).size > 0 - assert_equal %w(13 11 10 9), changesets.collect(&:revision) - end - end - - def test_show_directory_latin_1_path - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - [21, '21', 'adf805632193'].each do |r1| - get :show, :id => PRJ_ID, - :path => repository_path_hash(['latin-1-dir'])[:param], - :rev => r1 - assert_response :success - assert_template 'show' - - assert_not_nil assigns(:entries) - assert_equal ["make-latin-1-file.rb", - "test-#{@char_1}-1.txt", - "test-#{@char_1}-2.txt", - "test-#{@char_1}.txt"], assigns(:entries).collect(&:name) - changesets = assigns(:changesets) - assert_not_nil changesets - assert_equal %w(21 20 19 18 17), changesets.collect(&:revision) - end - end - - def show_should_show_branch_selection_form - @repository.fetch_changesets - @project.reload - get :show, :id => PRJ_ID - assert_tag 'form', :attributes => {:id => 'revision_selector', :action => '/projects/subproject1/repository/show'} - assert_tag 'select', :attributes => {:name => 'branch'}, - :child => {:tag => 'option', :attributes => {:value => 'test-branch-01'}}, - :parent => {:tag => 'form', :attributes => {:id => 'revision_selector'}} - end - - def test_show_branch - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - [ - 'default', - @branch_char_1, - 'branch (1)[2]&,%.-3_4', - @branch_char_0, - 'test_branch.latin-1', - 'test-branch-00', - ].each do |bra| - get :show, :id => PRJ_ID, :rev => bra - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert assigns(:entries).size > 0 - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - end - - def test_show_tag - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - [ - @tag_char_1, - 'tag_test.00', - 'tag-init-revision' - ].each do |tag| - get :show, :id => PRJ_ID, :rev => tag - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert assigns(:entries).size > 0 - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - end - - def test_changes - get :changes, :id => PRJ_ID, - :path => repository_path_hash(['images', 'edit.png'])[:param] - assert_response :success - assert_template 'changes' - assert_tag :tag => 'h2', :content => 'edit.png' - end - - def test_entry_show - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] - assert_response :success - assert_template 'entry' - # Line 10 - assert_tag :tag => 'th', - :content => '10', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ } - end - - def test_entry_show_latin_1_path - [21, '21', 'adf805632193'].each do |r1| - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}-2.txt"])[:param], - :rev => r1 - assert_response :success - assert_template 'entry' - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', - :content => /Mercurial is a distributed version control system/ } - end - end - - def test_entry_show_latin_1_contents - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - [27, '27', '7bbf4c738e71'].each do |r1| - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param], - :rev => r1 - assert_response :success - assert_template 'entry' - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', - :content => /test-#{@char_1}.txt/ } - end - end - end - - def test_entry_download - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], - :format => 'raw' - assert_response :success - # File content - assert @response.body.include?('WITHOUT ANY WARRANTY') - end - - def test_entry_binary_force_download - get :entry, :id => PRJ_ID, :rev => 1, - :path => repository_path_hash(['images', 'edit.png'])[:param] - assert_response :success - assert_equal 'image/png', @response.content_type - end - - def test_directory_entry - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['sources'])[:param] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entry) - assert_equal 'sources', assigns(:entry).name - end - - def test_diff - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - [4, '4', 'def6d2f1254a'].each do |r1| - # Full diff of changeset 4 - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => r1, :type => dt - assert_response :success - assert_template 'diff' - if @diff_c_support - # Line 22 removed - assert_tag :tag => 'th', - :content => '22', - :sibling => { :tag => 'td', - :attributes => { :class => /diff_out/ }, - :content => /def remove/ } - assert_tag :tag => 'h2', :content => /4:def6d2f1254a/ - end - end - end - end - - def test_diff_two_revs - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - [2, '400bb8672109', '400', 400].each do |r1| - [4, 'def6d2f1254a'].each do |r2| - ['inline', 'sbs'].each do |dt| - get :diff, - :id => PRJ_ID, - :rev => r1, - :rev_to => r2, - :type => dt - assert_response :success - assert_template 'diff' - diff = assigns(:diff) - assert_not_nil diff - assert_tag :tag => 'h2', - :content => /4:def6d2f1254a 2:400bb8672109/ - end - end - end - end - - def test_diff_latin_1_path - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - [21, 'adf805632193'].each do |r1| - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => r1, :type => dt - assert_response :success - assert_template 'diff' - assert_tag :tag => 'thead', - :descendant => { - :tag => 'th', - :attributes => { :class => 'filename' } , - :content => /latin-1-dir\/test-#{@char_1}-2.txt/ , - }, - :sibling => { - :tag => 'tbody', - :descendant => { - :tag => 'td', - :attributes => { :class => /diff_in/ }, - :content => /It is written in Python/ - } - } - end - end - end - end - - def test_diff_should_show_modified_filenames - get :diff, :id => PRJ_ID, :rev => '400bb8672109', :type => 'inline' - assert_response :success - assert_template 'diff' - assert_select 'th.filename', :text => 'sources/watchers_controller.rb' - end - - def test_diff_should_show_deleted_filenames - get :diff, :id => PRJ_ID, :rev => 'b3a615152df8', :type => 'inline' - assert_response :success - assert_template 'diff' - assert_select 'th.filename', :text => 'sources/welcome_controller.rb' - end - - def test_annotate - get :annotate, :id => PRJ_ID, - :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] - assert_response :success - assert_template 'annotate' - - # Line 22, revision 4:def6d2f1254a - assert_select 'tr' do - assert_select 'th.line-num', :text => '22' - assert_select 'td.revision', :text => '4:def6d2f1254a' - assert_select 'td.author', :text => 'jsmith' - assert_select 'td', :text => /remove_watcher/ - end - end - - def test_annotate_not_in_tip - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :annotate, :id => PRJ_ID, - :path => repository_path_hash(['sources', 'welcome_controller.rb'])[:param] - assert_response 404 - assert_error_tag :content => /was not found/ - end - - def test_annotate_at_given_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - [2, '400bb8672109', '400', 400].each do |r1| - get :annotate, :id => PRJ_ID, :rev => r1, - :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] - assert_response :success - assert_template 'annotate' - assert_tag :tag => 'h2', :content => /@ 2:400bb8672109/ - end - end - - def test_annotate_latin_1_path - [21, '21', 'adf805632193'].each do |r1| - get :annotate, :id => PRJ_ID, - :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}-2.txt"])[:param], - :rev => r1 - assert_response :success - assert_template 'annotate' - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => - { - :tag => 'td', - :attributes => { :class => 'revision' }, - :child => { :tag => 'a', :content => '20:709858aafd1b' } - } - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => - { - :tag => 'td' , - :content => 'jsmith' , - :attributes => { :class => 'author' }, - } - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', - :content => /Mercurial is a distributed version control system/ } - - end - end - - def test_annotate_latin_1_contents - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - [27, '7bbf4c738e71'].each do |r1| - get :annotate, :id => PRJ_ID, - :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param], - :rev => r1 - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', - :content => /test-#{@char_1}.txt/ } - end - end - end - - def test_empty_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['', ' ', nil].each do |r| - get :revision, :id => PRJ_ID, :rev => r - assert_response 404 - assert_error_tag :content => /was not found/ - end - end - - def test_destroy_valid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - assert_equal NUM_REV, @repository.changesets.count - - assert_difference 'Repository.count', -1 do - delete :destroy, :id => @repository.id - end - assert_response 302 - @project.reload - assert_nil @project.repository - end - - def test_destroy_invalid_repository - @request.session[:user_id] = 1 # admin - @project.repository.destroy - @repository = Repository::Mercurial.create!( - :project => Project.find(PRJ_ID), - :url => "/invalid", - :path_encoding => 'ISO-8859-1' - ) - @repository.fetch_changesets - assert_equal 0, @repository.changesets.count - - assert_difference 'Repository.count', -1 do - delete :destroy, :id => @repository.id - end - assert_response 302 - @project.reload - assert_nil @project.repository - end - else - puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!" - def test_fake; assert true end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/25da0d3c14f76d4d61d0a700c479d9e89afb5581.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/25da0d3c14f76d4d61d0a700c479d9e89afb5581.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,42 @@ +<%= form_tag(project_enumerations_path(@project), :method => :put, :class => "tabular") do %> + + + + + + <% TimeEntryActivity.new.available_custom_fields.each do |value| %> + + <% end %> + + + + <% @project.activities(true).each do |enumeration| %> + <%= fields_for "enumerations[#{enumeration.id}]", enumeration do |ff| %> + + + + <% enumeration.custom_field_values.each do |value| %> + + <% end %> + + + <% end %> + <% end %> +
    <%= l(:field_name) %><%= l(:enumeration_system_activity) %><%= h value.name %><%= l(:field_active) %>
    + <%= ff.hidden_field :parent_id, :value => enumeration.id unless enumeration.project %> + <%= h(enumeration) %> + <%= checked_image !enumeration.project %> + <%= custom_field_tag "enumerations[#{enumeration.id}]", value %> + + <%= ff.check_box :active %> +
    + +
    +<%= link_to(l(:button_reset), project_enumerations_path(@project), + :method => :delete, + :data => {:confirm => l(:text_are_you_sure)}, + :class => 'icon icon-del') %> +
    + +<%= submit_tag l(:button_save) %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/25dd827654fa8266aba700a8953d3bb722e81865.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/25dd827654fa8266aba700a8953d3bb722e81865.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,247 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module WikiFormatting + module Macros + module Definitions + # Returns true if +name+ is the name of an existing macro + def macro_exists?(name) + Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym) + end + + def exec_macro(name, obj, args, text) + macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym] + return unless macro_options + + method_name = "macro_#{name}" + unless macro_options[:parse_args] == false + args = args.split(',').map(&:strip) + end + + begin + if self.class.instance_method(method_name).arity == 3 + send(method_name, obj, args, text) + elsif text + raise "This macro does not accept a block of text" + else + send(method_name, obj, args) + end + rescue => e + "
    Error executing the #{h name} macro (#{h e.to_s})
    ".html_safe + end + end + + def extract_macro_options(args, *keys) + options = {} + while args.last.to_s.strip =~ %r{^(.+?)\=(.+)$} && keys.include?($1.downcase.to_sym) + options[$1.downcase.to_sym] = $2 + args.pop + end + return [args, options] + end + end + + @@available_macros = {} + mattr_accessor :available_macros + + class << self + # Plugins can use this method to define new macros: + # + # Redmine::WikiFormatting::Macros.register do + # desc "This is my macro" + # macro :my_macro do |obj, args| + # "My macro output" + # end + # + # desc "This is my macro that accepts a block of text" + # macro :my_macro do |obj, args, text| + # "My macro output" + # end + # end + def register(&block) + class_eval(&block) if block_given? + end + + # Defines a new macro with the given name, options and block. + # + # Options: + # * :desc - A description of the macro + # * :parse_args => false - Disables arguments parsing (the whole arguments + # string is passed to the macro) + # + # Macro blocks accept 2 or 3 arguments: + # * obj: the object that is rendered (eg. an Issue, a WikiContent...) + # * args: macro arguments + # * text: the block of text given to the macro (should be present only if the + # macro accepts a block of text). text is a String or nil if the macro is + # invoked without a block of text. + # + # Examples: + # By default, when the macro is invoked, the coma separated list of arguments + # is split and passed to the macro block as an array. If no argument is given + # the macro will be invoked with an empty array: + # + # macro :my_macro do |obj, args| + # # args is an array + # # and this macro do not accept a block of text + # end + # + # You can disable arguments spliting with the :parse_args => false option. In + # this case, the full string of arguments is passed to the macro: + # + # macro :my_macro, :parse_args => false do |obj, args| + # # args is a string + # end + # + # Macro can optionally accept a block of text: + # + # macro :my_macro do |obj, args, text| + # # this macro accepts a block of text + # end + # + # Macros are invoked in formatted text using double curly brackets. Arguments + # must be enclosed in parenthesis if any. A new line after the macro name or the + # arguments starts the block of text that will be passe to the macro (invoking + # a macro that do not accept a block of text with some text will fail). + # Examples: + # + # No arguments: + # {{my_macro}} + # + # With arguments: + # {{my_macro(arg1, arg2)}} + # + # With a block of text: + # {{my_macro + # multiple lines + # of text + # }} + # + # With arguments and a block of text + # {{my_macro(arg1, arg2) + # multiple lines + # of text + # }} + # + # If a block of text is given, the closing tag }} must be at the start of a new line. + def macro(name, options={}, &block) + options.assert_valid_keys(:desc, :parse_args) + unless name.to_s.match(/\A\w+\z/) + raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)" + end + unless block_given? + raise "Can not create a macro without a block!" + end + name = name.to_s.downcase.to_sym + available_macros[name] = {:desc => @@desc || ''}.merge(options) + @@desc = nil + Definitions.send :define_method, "macro_#{name}", &block + end + + # Sets description for the next macro to be defined + def desc(txt) + @@desc = txt + end + end + + # Builtin macros + desc "Sample macro." + macro :hello_world do |obj, args, text| + h("Hello world! Object: #{obj.class.name}, " + + (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") + + " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.") + ) + end + + desc "Displays a list of all available macros, including description if available." + macro :macro_list do |obj, args| + out = ''.html_safe + @@available_macros.each do |macro, options| + out << content_tag('dt', content_tag('code', macro.to_s)) + out << content_tag('dd', textilizable(options[:desc])) + end + content_tag('dl', out) + end + + desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" + + " !{{child_pages}} -- can be used from a wiki page only\n" + + " !{{child_pages(depth=2)}} -- display 2 levels nesting only\n" + " !{{child_pages(Foo)}} -- lists all children of page Foo\n" + + " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo" + macro :child_pages do |obj, args| + args, options = extract_macro_options(args, :parent, :depth) + options[:depth] = options[:depth].to_i if options[:depth].present? + + page = nil + if args.size > 0 + page = Wiki.find_page(args.first.to_s, :project => @project) + elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version) + page = obj.page + else + raise 'With no argument, this macro can be called from wiki pages only.' + end + raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) + pages = page.self_and_descendants(options[:depth]).group_by(&:parent_id) + render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id) + end + + desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}" + macro :include do |obj, args| + page = Wiki.find_page(args.first.to_s, :project => @project) + raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) + @included_wiki_pages ||= [] + raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title) + @included_wiki_pages << page.title + out = textilizable(page.content, :text, :attachments => page.attachments, :headings => false) + @included_wiki_pages.pop + out + end + + desc "Inserts of collapsed block of text. Example:\n\n {{collapse(View details...)\nThis is a block of text that is collapsed by default.\nIt can be expanded by clicking a link.\n}}" + macro :collapse do |obj, args, text| + html_id = "collapse-#{Redmine::Utils.random_hex(4)}" + show_label = args[0] || l(:button_show) + hide_label = args[1] || args[0] || l(:button_hide) + js = "$('##{html_id}-show, ##{html_id}-hide').toggle(); $('##{html_id}').fadeToggle(150);" + out = ''.html_safe + out << link_to_function(show_label, js, :id => "#{html_id}-show", :class => 'collapsible collapsed') + out << link_to_function(hide_label, js, :id => "#{html_id}-hide", :class => 'collapsible', :style => 'display:none;') + out << content_tag('div', textilizable(text, :object => obj), :id => html_id, :class => 'collapsed-text', :style => 'display:none;') + out + end + + desc "Displays a clickable thumbnail of an attached image. Examples:\n\n
    {{thumbnail(image.png)}}\n{{thumbnail(image.png, size=300, title=Thumbnail)}}
    " + macro :thumbnail do |obj, args| + args, options = extract_macro_options(args, :size, :title) + filename = args.first + raise 'Filename required' unless filename.present? + size = options[:size] + raise 'Invalid size parameter' unless size.nil? || size.match(/^\d+$/) + size = size.to_i + size = nil unless size > 0 + if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename) + title = options[:title] || attachment.title + img = image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size), :alt => attachment.filename) + link_to(img, url_for(:controller => 'attachments', :action => 'show', :id => attachment), :class => 'thumbnail', :title => title) + else + raise "Attachment #{filename} not found" + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/25/25e1449caab60bc909f79da5e84ca8f30d8566d5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/25e1449caab60bc909f79da5e84ca8f30d8566d5.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,47 @@ +<%= labelled_fields_for :issue, @issue do |f| %> +<%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %> + +<% if @issue.safe_attribute? 'is_private' %> +

    + <%= f.check_box :is_private, :no_label => true %> +

    +<% end %> + +<% if @issue.safe_attribute? 'project_id' %> +

    <%= f.select :project_id, project_tree_options_for_select(@issue.allowed_target_projects, :selected => @issue.project), {:required => true}, + :onchange => "updateIssueFrom('#{escape_javascript project_issue_form_path(@project, :id => @issue, :format => 'js')}')" %>

    +<% end %> + +<% if @issue.safe_attribute? 'tracker_id' %> +

    <%= f.select :tracker_id, @issue.project.trackers.collect {|t| [t.name, t.id]}, {:required => true}, + :onchange => "updateIssueFrom('#{escape_javascript project_issue_form_path(@project, :id => @issue, :format => 'js')}')" %>

    +<% end %> + +<% if @issue.safe_attribute? 'subject' %> +

    <%= f.text_field :subject, :size => 80, :maxlength => 255, :required => true %>

    +<% end %> + +<% if @issue.safe_attribute? 'description' %> +

    + <%= f.label_for_field :description, :required => @issue.required_attribute?('description') %> + <%= link_to_function image_tag('edit.png'), '$(this).hide(); $("#issue_description_and_toolbar").show()' unless @issue.new_record? %> + <%= content_tag 'span', :id => "issue_description_and_toolbar", :style => (@issue.new_record? ? nil : 'display:none') do %> + <%= f.text_area :description, + :cols => 60, + :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min), + :accesskey => accesskey(:edit), + :class => 'wiki-edit', + :no_label => true %> + <% end %> +

    +<%= wikitoolbar_for 'issue_description' %> +<% end %> + +
    + <%= render :partial => 'issues/attributes' %> +
    + +<%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %> +<% end %> + +<% heads_for_wiki_formatter %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/26/2610f5493d7250662d4298685db33ab62ac8d21f.svn-base --- a/.svn/pristine/26/2610f5493d7250662d4298685db33ab62ac8d21f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::EnumerationsTest < ActionController::IntegrationTest - fixtures :enumerations - - def setup - Setting.rest_api_enabled = '1' - end - - context "/enumerations/issue_priorities" do - context "GET" do - - should "return priorities" do - get '/enumerations/issue_priorities.xml' - - assert_response :success - assert_equal 'application/xml', response.content_type - assert_select 'issue_priorities[type=array]' do - assert_select 'issue_priority' do - assert_select 'id', :text => '6' - assert_select 'name', :text => 'High' - end - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/26/261610bb580a68c8ae61110e8a808bb695edda19.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/26/261610bb580a68c8ae61110e8a808bb695edda19.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,475 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine #:nodoc: + + class PluginNotFound < StandardError; end + class PluginRequirementError < StandardError; end + + # Base class for Redmine plugins. + # Plugins are registered using the register class method that acts as the public constructor. + # + # Redmine::Plugin.register :example do + # name 'Example plugin' + # author 'John Smith' + # description 'This is an example plugin for Redmine' + # version '0.0.1' + # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings' + # end + # + # === Plugin attributes + # + # +settings+ is an optional attribute that let the plugin be configurable. + # It must be a hash with the following keys: + # * :default: default value for the plugin settings + # * :partial: path of the configuration partial view, relative to the plugin app/views directory + # Example: + # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings' + # In this example, the settings partial will be found here in the plugin directory: app/views/settings/_settings.rhtml. + # + # When rendered, the plugin settings value is available as the local variable +settings+ + class Plugin + cattr_accessor :directory + self.directory = File.join(Rails.root, 'plugins') + + cattr_accessor :public_directory + self.public_directory = File.join(Rails.root, 'public', 'plugin_assets') + + @registered_plugins = {} + class << self + attr_reader :registered_plugins + private :new + + def def_field(*names) + class_eval do + names.each do |name| + define_method(name) do |*args| + args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args) + end + end + end + end + end + def_field :name, :description, :url, :author, :author_url, :version, :settings, :directory + attr_reader :id + + # Plugin constructor + def self.register(id, &block) + p = new(id) + p.instance_eval(&block) + + # Set a default name if it was not provided during registration + p.name(id.to_s.humanize) if p.name.nil? + # Set a default directory if it was not provided during registration + p.directory(File.join(self.directory, id.to_s)) if p.directory.nil? + + # Adds plugin locales if any + # YAML translation files should be found under /config/locales/ + ::I18n.load_path += Dir.glob(File.join(p.directory, 'config', 'locales', '*.yml')) + + # Prepends the app/views directory of the plugin to the view path + view_path = File.join(p.directory, 'app', 'views') + if File.directory?(view_path) + ActionController::Base.prepend_view_path(view_path) + ActionMailer::Base.prepend_view_path(view_path) + end + + # Adds the app/{controllers,helpers,models} directories of the plugin to the autoload path + Dir.glob File.expand_path(File.join(p.directory, 'app', '{controllers,helpers,models}')) do |dir| + ActiveSupport::Dependencies.autoload_paths += [dir] + end + + registered_plugins[id] = p + end + + # Returns an array of all registered plugins + def self.all + registered_plugins.values.sort + end + + # Finds a plugin by its id + # Returns a PluginNotFound exception if the plugin doesn't exist + def self.find(id) + registered_plugins[id.to_sym] || raise(PluginNotFound) + end + + # Clears the registered plugins hash + # It doesn't unload installed plugins + def self.clear + @registered_plugins = {} + end + + # Checks if a plugin is installed + # + # @param [String] id name of the plugin + def self.installed?(id) + registered_plugins[id.to_sym].present? + end + + def self.load + Dir.glob(File.join(self.directory, '*')).sort.each do |directory| + if File.directory?(directory) + lib = File.join(directory, "lib") + if File.directory?(lib) + $:.unshift lib + ActiveSupport::Dependencies.autoload_paths += [lib] + end + initializer = File.join(directory, "init.rb") + if File.file?(initializer) + require initializer + end + end + end + end + + def initialize(id) + @id = id.to_sym + end + + def public_directory + File.join(self.class.public_directory, id.to_s) + end + + def to_param + id + end + + def assets_directory + File.join(directory, 'assets') + end + + def <=>(plugin) + self.id.to_s <=> plugin.id.to_s + end + + # Sets a requirement on Redmine version + # Raises a PluginRequirementError exception if the requirement is not met + # + # Examples + # # Requires Redmine 0.7.3 or higher + # requires_redmine :version_or_higher => '0.7.3' + # requires_redmine '0.7.3' + # + # # Requires Redmine 0.7.x or higher + # requires_redmine '0.7' + # + # # Requires a specific Redmine version + # requires_redmine :version => '0.7.3' # 0.7.3 only + # requires_redmine :version => '0.7' # 0.7.x + # requires_redmine :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0 + # + # # Requires a Redmine version within a range + # requires_redmine :version => '0.7.3'..'0.9.1' # >= 0.7.3 and <= 0.9.1 + # requires_redmine :version => '0.7'..'0.9' # >= 0.7.x and <= 0.9.x + def requires_redmine(arg) + arg = { :version_or_higher => arg } unless arg.is_a?(Hash) + arg.assert_valid_keys(:version, :version_or_higher) + + current = Redmine::VERSION.to_a + arg.each do |k, req| + case k + when :version_or_higher + raise ArgumentError.new(":version_or_higher accepts a version string only") unless req.is_a?(String) + unless compare_versions(req, current) <= 0 + raise PluginRequirementError.new("#{id} plugin requires Redmine #{req} or higher but current is #{current.join('.')}") + end + when :version + req = [req] if req.is_a?(String) + if req.is_a?(Array) + unless req.detect {|ver| compare_versions(ver, current) == 0} + raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{req.join(', ')} but current is #{current.join('.')}") + end + elsif req.is_a?(Range) + unless compare_versions(req.first, current) <= 0 && compare_versions(req.last, current) >= 0 + raise PluginRequirementError.new("#{id} plugin requires a Redmine version between #{req.first} and #{req.last} but current is #{current.join('.')}") + end + else + raise ArgumentError.new(":version option accepts a version string, an array or a range of versions") + end + end + end + true + end + + def compare_versions(requirement, current) + requirement = requirement.split('.').collect(&:to_i) + requirement <=> current.slice(0, requirement.size) + end + private :compare_versions + + # Sets a requirement on a Redmine plugin version + # Raises a PluginRequirementError exception if the requirement is not met + # + # Examples + # # Requires a plugin named :foo version 0.7.3 or higher + # requires_redmine_plugin :foo, :version_or_higher => '0.7.3' + # requires_redmine_plugin :foo, '0.7.3' + # + # # Requires a specific version of a Redmine plugin + # requires_redmine_plugin :foo, :version => '0.7.3' # 0.7.3 only + # requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0 + def requires_redmine_plugin(plugin_name, arg) + arg = { :version_or_higher => arg } unless arg.is_a?(Hash) + arg.assert_valid_keys(:version, :version_or_higher) + + plugin = Plugin.find(plugin_name) + current = plugin.version.split('.').collect(&:to_i) + + arg.each do |k, v| + v = [] << v unless v.is_a?(Array) + versions = v.collect {|s| s.split('.').collect(&:to_i)} + case k + when :version_or_higher + raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1 + unless (current <=> versions.first) >= 0 + raise PluginRequirementError.new("#{id} plugin requires the #{plugin_name} plugin #{v} or higher but current is #{current.join('.')}") + end + when :version + unless versions.include?(current.slice(0,3)) + raise PluginRequirementError.new("#{id} plugin requires one the following versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}") + end + end + end + true + end + + # Adds an item to the given +menu+. + # The +id+ parameter (equals to the project id) is automatically added to the url. + # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample' + # + # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu + # + def menu(menu, item, url, options={}) + Redmine::MenuManager.map(menu).push(item, url, options) + end + alias :add_menu_item :menu + + # Removes +item+ from the given +menu+. + def delete_menu_item(menu, item) + Redmine::MenuManager.map(menu).delete(item) + end + + # Defines a permission called +name+ for the given +actions+. + # + # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array): + # permission :destroy_contacts, { :contacts => :destroy } + # permission :view_contacts, { :contacts => [:index, :show] } + # + # The +options+ argument is a hash that accept the following keys: + # * :public => the permission is public if set to true (implicitly given to any user) + # * :require => can be set to one of the following values to restrict users the permission can be given to: :loggedin, :member + # * :read => set it to true so that the permission is still granted on closed projects + # + # Examples + # # A permission that is implicitly given to any user + # # This permission won't appear on the Roles & Permissions setup screen + # permission :say_hello, { :example => :say_hello }, :public => true, :read => true + # + # # A permission that can be given to any user + # permission :say_hello, { :example => :say_hello } + # + # # A permission that can be given to registered users only + # permission :say_hello, { :example => :say_hello }, :require => :loggedin + # + # # A permission that can be given to project members only + # permission :say_hello, { :example => :say_hello }, :require => :member + def permission(name, actions, options = {}) + if @project_module + Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}} + else + Redmine::AccessControl.map {|map| map.permission(name, actions, options)} + end + end + + # Defines a project module, that can be enabled/disabled for each project. + # Permissions defined inside +block+ will be bind to the module. + # + # project_module :things do + # permission :view_contacts, { :contacts => [:list, :show] }, :public => true + # permission :destroy_contacts, { :contacts => :destroy } + # end + def project_module(name, &block) + @project_module = name + self.instance_eval(&block) + @project_module = nil + end + + # Registers an activity provider. + # + # Options: + # * :class_name - one or more model(s) that provide these events (inferred from event_type by default) + # * :default - setting this option to false will make the events not displayed by default + # + # A model can provide several activity event types. + # + # Examples: + # register :news + # register :scrums, :class_name => 'Meeting' + # register :issues, :class_name => ['Issue', 'Journal'] + # + # Retrieving events: + # Associated model(s) must implement the find_events class method. + # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method. + # + # The following call should return all the scrum events visible by current user that occured in the 5 last days: + # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today) + # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only + # + # Note that :view_scrums permission is required to view these events in the activity view. + def activity_provider(*args) + Redmine::Activity.register(*args) + end + + # Registers a wiki formatter. + # + # Parameters: + # * +name+ - human-readable name + # * +formatter+ - formatter class, which should have an instance method +to_html+ + # * +helper+ - helper module, which will be included by wiki pages + def wiki_format_provider(name, formatter, helper) + Redmine::WikiFormatting.register(name, formatter, helper) + end + + # Returns +true+ if the plugin can be configured. + def configurable? + settings && settings.is_a?(Hash) && !settings[:partial].blank? + end + + def mirror_assets + source = assets_directory + destination = public_directory + return unless File.directory?(source) + + source_files = Dir[source + "/**/*"] + source_dirs = source_files.select { |d| File.directory?(d) } + source_files -= source_dirs + + unless source_files.empty? + base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, '')) + begin + FileUtils.mkdir_p(base_target_dir) + rescue Exception => e + raise "Could not create directory #{base_target_dir}: " + e.message + end + end + + source_dirs.each do |dir| + # strip down these paths so we have simple, relative paths we can + # add to the destination + target_dir = File.join(destination, dir.gsub(source, '')) + begin + FileUtils.mkdir_p(target_dir) + rescue Exception => e + raise "Could not create directory #{target_dir}: " + e.message + end + end + + source_files.each do |file| + begin + target = File.join(destination, file.gsub(source, '')) + unless File.exist?(target) && FileUtils.identical?(file, target) + FileUtils.cp(file, target) + end + rescue Exception => e + raise "Could not copy #{file} to #{target}: " + e.message + end + end + end + + # Mirrors assets from one or all plugins to public/plugin_assets + def self.mirror_assets(name=nil) + if name.present? + find(name).mirror_assets + else + all.each do |plugin| + plugin.mirror_assets + end + end + end + + # The directory containing this plugin's migrations (plugin/db/migrate) + def migration_directory + File.join(Rails.root, 'plugins', id.to_s, 'db', 'migrate') + end + + # Returns the version number of the latest migration for this plugin. Returns + # nil if this plugin has no migrations. + def latest_migration + migrations.last + end + + # Returns the version numbers of all migrations for this plugin. + def migrations + migrations = Dir[migration_directory+"/*.rb"] + migrations.map { |p| File.basename(p).match(/0*(\d+)\_/)[1].to_i }.sort + end + + # Migrate this plugin to the given version + def migrate(version = nil) + puts "Migrating #{id} (#{name})..." + Redmine::Plugin::Migrator.migrate_plugin(self, version) + end + + # Migrates all plugins or a single plugin to a given version + # Exemples: + # Plugin.migrate + # Plugin.migrate('sample_plugin') + # Plugin.migrate('sample_plugin', 1) + # + def self.migrate(name=nil, version=nil) + if name.present? + find(name).migrate(version) + else + all.each do |plugin| + plugin.migrate + end + end + end + + class Migrator < ActiveRecord::Migrator + # We need to be able to set the 'current' plugin being migrated. + cattr_accessor :current_plugin + + class << self + # Runs the migrations from a plugin, up (or down) to the version given + def migrate_plugin(plugin, version) + self.current_plugin = plugin + return if current_version(plugin) == version + migrate(plugin.migration_directory, version) + end + + def current_version(plugin=current_plugin) + # Delete migrations that don't match .. to_i will work because the number comes first + ::ActiveRecord::Base.connection.select_values( + "SELECT version FROM #{schema_migrations_table_name}" + ).delete_if{ |v| v.match(/-#{plugin.id}/) == nil }.map(&:to_i).max || 0 + end + end + + def migrated + sm_table = self.class.schema_migrations_table_name + ::ActiveRecord::Base.connection.select_values( + "SELECT version FROM #{sm_table}" + ).delete_if{ |v| v.match(/-#{current_plugin.id}/) == nil }.map(&:to_i).sort + end + + def record_version_state_after_migrating(version) + super(version.to_s + "-" + current_plugin.id.to_s) + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/26/2647128cbfe83c34c563d9a0e3659f5a24700cec.svn-base --- a/.svn/pristine/26/2647128cbfe83c34c563d9a0e3659f5a24700cec.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module IssueStatusesHelper -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/26/266dfa6887b7544baedd932a1d45fffff0382a86.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/26/266dfa6887b7544baedd932a1d45fffff0382a86.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,388 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class CustomField < ActiveRecord::Base + include Redmine::SubclassFactory + + has_many :custom_values, :dependent => :delete_all + has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id" + acts_as_list :scope => 'type = \'#{self.class}\'' + serialize :possible_values + + validates_presence_of :name, :field_format + validates_uniqueness_of :name, :scope => :type + validates_length_of :name, :maximum => 30 + validates_inclusion_of :field_format, :in => Proc.new { Redmine::CustomFieldFormat.available_formats } + validate :validate_custom_field + + before_validation :set_searchable + after_save :handle_multiplicity_change + after_save do |field| + if field.visible_changed? && field.visible + field.roles.clear + end + end + + scope :sorted, lambda { order("#{table_name}.position ASC") } + scope :visible, lambda {|*args| + user = args.shift || User.current + if user.admin? + # nop + elsif user.memberships.any? + where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" + + " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" + + " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" + + " WHERE m.user_id = ?)", + true, user.id) + else + where(:visible => true) + end + } + + def visible_by?(project, user=User.current) + visible? || user.admin? + end + + def field_format=(arg) + # cannot change format of a saved custom field + super if new_record? + end + + def set_searchable + # make sure these fields are not searchable + self.searchable = false if %w(int float date bool).include?(field_format) + # make sure only these fields can have multiple values + self.multiple = false unless %w(list user version).include?(field_format) + true + end + + def validate_custom_field + if self.field_format == "list" + errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty? + errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array + end + + if regexp.present? + begin + Regexp.new(regexp) + rescue + errors.add(:regexp, :invalid) + end + end + + if default_value.present? && !valid_field_value?(default_value) + errors.add(:default_value, :invalid) + end + end + + def possible_values_options(obj=nil) + case field_format + when 'user', 'version' + if obj.respond_to?(:project) && obj.project + case field_format + when 'user' + obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]} + when 'version' + obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]} + end + elsif obj.is_a?(Array) + obj.collect {|o| possible_values_options(o)}.reduce(:&) + else + [] + end + when 'bool' + [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']] + else + possible_values || [] + end + end + + def possible_values(obj=nil) + case field_format + when 'user', 'version' + possible_values_options(obj).collect(&:last) + when 'bool' + ['1', '0'] + else + values = super() + if values.is_a?(Array) + values.each do |value| + value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) + end + values + else + [] + end + end + end + + # Makes possible_values accept a multiline string + def possible_values=(arg) + if arg.is_a?(Array) + super(arg.compact.collect(&:strip).select {|v| !v.blank?}) + else + self.possible_values = arg.to_s.split(/[\n\r]+/) + end + end + + def cast_value(value) + casted = nil + unless value.blank? + case field_format + when 'string', 'text', 'list' + casted = value + when 'date' + casted = begin; value.to_date; rescue; nil end + when 'bool' + casted = (value == '1' ? true : false) + when 'int' + casted = value.to_i + when 'float' + casted = value.to_f + when 'user', 'version' + casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i)) + end + end + casted + end + + def value_from_keyword(keyword, customized) + possible_values_options = possible_values_options(customized) + if possible_values_options.present? + keyword = keyword.to_s.downcase + if v = possible_values_options.detect {|text, id| text.downcase == keyword} + if v.is_a?(Array) + v.last + else + v + end + end + else + keyword + end + end + + # Returns a ORDER BY clause that can used to sort customized + # objects by their value of the custom field. + # Returns nil if the custom field can not be used for sorting. + def order_statement + return nil if multiple? + case field_format + when 'string', 'text', 'list', 'date', 'bool' + # COALESCE is here to make sure that blank and NULL values are sorted equally + "COALESCE(#{join_alias}.value, '')" + when 'int', 'float' + # Make the database cast values into numeric + # Postgresql will raise an error if a value can not be casted! + # CustomValue validations should ensure that it doesn't occur + "CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))" + when 'user', 'version' + value_class.fields_for_order_statement(value_join_alias) + else + nil + end + end + + # Returns a GROUP BY clause that can used to group by custom value + # Returns nil if the custom field can not be used for grouping. + def group_statement + return nil if multiple? + case field_format + when 'list', 'date', 'bool', 'int' + order_statement + when 'user', 'version' + "COALESCE(#{join_alias}.value, '')" + else + nil + end + end + + def join_for_order_statement + case field_format + when 'user', 'version' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND (#{visibility_by_project_condition})" + + " AND #{join_alias}.value <> ''" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + + " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" + + " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id" + when 'int', 'float' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND (#{visibility_by_project_condition})" + + " AND #{join_alias}.value <> ''" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + when 'string', 'text', 'list', 'date', 'bool' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND (#{visibility_by_project_condition})" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + else + nil + end + end + + def join_alias + "cf_#{id}" + end + + def value_join_alias + join_alias + "_" + field_format + end + + def visibility_by_project_condition(project_key=nil, user=User.current) + if visible? || user.admin? + "1=1" + elsif user.anonymous? + "1=0" + else + project_key ||= "#{self.class.customized_class.table_name}.project_id" + "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" + + " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" + + " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" + + " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})" + end + end + + def self.visibility_condition + if user.admin? + "1=1" + elsif user.anonymous? + "#{table_name}.visible" + else + "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" + + " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" + + " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" + + " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})" + end + end + + def <=>(field) + position <=> field.position + end + + # Returns the class that values represent + def value_class + case field_format + when 'user', 'version' + field_format.classify.constantize + else + nil + end + end + + def self.customized_class + self.name =~ /^(.+)CustomField$/ + $1.constantize rescue nil + end + + # to move in project_custom_field + def self.for_all + where(:is_for_all => true).order('position').all + end + + def type_name + nil + end + + # Returns the error messages for the given value + # or an empty array if value is a valid value for the custom field + def validate_field_value(value) + errs = [] + if value.is_a?(Array) + if !multiple? + errs << ::I18n.t('activerecord.errors.messages.invalid') + end + if is_required? && value.detect(&:present?).nil? + errs << ::I18n.t('activerecord.errors.messages.blank') + end + value.each {|v| errs += validate_field_value_format(v)} + else + if is_required? && value.blank? + errs << ::I18n.t('activerecord.errors.messages.blank') + end + errs += validate_field_value_format(value) + end + errs + end + + # Returns true if value is a valid value for the custom field + def valid_field_value?(value) + validate_field_value(value).empty? + end + + def format_in?(*args) + args.include?(field_format) + end + + protected + + # Returns the error message for the given value regarding its format + def validate_field_value_format(value) + errs = [] + unless value.to_s == '' + errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp) + errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length && min_length > 0 && value.length < min_length + errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length && max_length > 0 && value.length > max_length + + # Format specific validations + case field_format + when 'int' + errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/ + when 'float' + begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end + when 'date' + errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end + when 'list' + errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value) + end + end + errs + end + + # Removes multiple values for the custom field after setting the multiple attribute to false + # We kepp the value with the highest id for each customized object + def handle_multiplicity_change + if !new_record? && multiple_was && !multiple + ids = custom_values. + where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" + + " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" + + " AND cve.id > #{CustomValue.table_name}.id)"). + pluck(:id) + + if ids.any? + custom_values.where(:id => ids).delete_all + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/26/26d1f0e306c560c0d8ab8e97cc50b4f5ea6a381f.svn-base --- a/.svn/pristine/26/26d1f0e306c560c0d8ab8e97cc50b4f5ea6a381f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class UserCustomField < CustomField - def type_name - :label_user_plural - end -end - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/26/26d549e31ef6d77ef22f8be4d975f3ff8dfc16bc.svn-base --- a/.svn/pristine/26/26d549e31ef6d77ef22f8be4d975f3ff8dfc16bc.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1085 +0,0 @@ -# Redmine catalan translation: -# by Joan Duran - -ca: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%d-%m-%Y" - short: "%e de %b" - long: "%a, %e de %b de %Y" - - day_names: [Diumenge, Dilluns, Dimarts, Dimecres, Dijous, Divendres, Dissabte] - abbr_day_names: [dg, dl, dt, dc, dj, dv, ds] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Gener, Febrer, Març, Abril, Maig, Juny, Juliol, Agost, Setembre, Octubre, Novembre, Desembre] - abbr_month_names: [~, Gen, Feb, Mar, Abr, Mai, Jun, Jul, Ago, Set, Oct, Nov, Des] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%d-%m-%Y %H:%M" - time: "%H:%M" - short: "%e de %b, %H:%M" - long: "%a, %e de %b de %Y, %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "mig minut" - less_than_x_seconds: - one: "menys d'un segon" - other: "menys de %{count} segons" - x_seconds: - one: "1 segons" - other: "%{count} segons" - less_than_x_minutes: - one: "menys d'un minut" - other: "menys de %{count} minuts" - x_minutes: - one: "1 minut" - other: "%{count} minuts" - about_x_hours: - one: "aproximadament 1 hora" - other: "aproximadament %{count} hores" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 dia" - other: "%{count} dies" - about_x_months: - one: "aproximadament 1 mes" - other: "aproximadament %{count} mesos" - x_months: - one: "1 mes" - other: "%{count} mesos" - about_x_years: - one: "aproximadament 1 any" - other: "aproximadament %{count} anys" - over_x_years: - one: "més d'un any" - other: "més de %{count} anys" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - # Default format for numbers - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "i" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "no està inclòs a la llista" - exclusion: "està reservat" - invalid: "no és vàlid" - confirmation: "la confirmació no coincideix" - accepted: "s'ha d'acceptar" - empty: "no pot estar buit" - blank: "no pot estar en blanc" - too_long: "és massa llarg" - too_short: "és massa curt" - wrong_length: "la longitud és incorrecta" - taken: "ja s'està utilitzant" - not_a_number: "no és un número" - not_a_date: "no és una data vàlida" - greater_than: "ha de ser més gran que %{count}" - greater_than_or_equal_to: "ha de ser més gran o igual a %{count}" - equal_to: "ha de ser igual a %{count}" - less_than: "ha de ser menys que %{count}" - less_than_or_equal_to: "ha de ser menys o igual a %{count}" - odd: "ha de ser senar" - even: "ha de ser parell" - greater_than_start_date: "ha de ser superior que la data inicial" - not_same_project: "no pertany al mateix projecte" - circular_dependency: "Aquesta relació crearia una dependència circular" - cant_link_an_issue_with_a_descendant: "Un assumpte no es pot enllaçar a una de les seves subtasques" - - actionview_instancetag_blank_option: Seleccioneu - - general_text_No: 'No' - general_text_Yes: 'Si' - general_text_no: 'no' - general_text_yes: 'si' - general_lang_name: 'Català' - general_csv_separator: ';' - general_csv_decimal_separator: ',' - general_csv_encoding: ISO-8859-15 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: "El compte s'ha actualitzat correctament." - notice_account_invalid_creditentials: Usuari o contrasenya invàlid - notice_account_password_updated: "La contrasenya s'ha modificat correctament." - notice_account_wrong_password: Contrasenya incorrecta - notice_account_register_done: "El compte s'ha creat correctament. Per a activar el compte, feu clic en l'enllaç que us han enviat per correu electrònic." - notice_account_unknown_email: Usuari desconegut. - notice_can_t_change_password: "Aquest compte utilitza una font d'autenticació externa. No és possible canviar la contrasenya." - notice_account_lost_email_sent: "S'ha enviat un correu electrònic amb instruccions per a seleccionar una contrasenya nova." - notice_account_activated: "El compte s'ha activat. Ara podeu entrar." - notice_successful_create: "S'ha creat correctament." - notice_successful_update: "S'ha modificat correctament." - notice_successful_delete: "S'ha suprimit correctament." - notice_successful_connection: "S'ha connectat correctament." - notice_file_not_found: "La pàgina a la que intenteu accedir no existeix o s'ha suprimit." - notice_locking_conflict: Un altre usuari ha actualitzat les dades. - notice_not_authorized: No teniu permís per a accedir a aquesta pàgina. - notice_email_sent: "S'ha enviat un correu electrònic a %{value}" - notice_email_error: "S'ha produït un error en enviar el correu (%{value})" - notice_feeds_access_key_reseted: "S'ha reiniciat la clau d'accés del RSS." - notice_api_access_key_reseted: "S'ha reiniciat la clau d'accés a l'API." - notice_failed_to_save_issues: "No s'han pogut desar %{count} assumptes de %{total} seleccionats: %{ids}." - notice_failed_to_save_members: "No s'han pogut desar els membres: %{errors}." - notice_no_issue_selected: "No s'ha seleccionat cap assumpte. Activeu els assumptes que voleu editar." - notice_account_pending: "S'ha creat el compte i ara està pendent de l'aprovació de l'administrador." - notice_default_data_loaded: "S'ha carregat correctament la configuració predeterminada." - notice_unable_delete_version: "No s'ha pogut suprimir la versió." - notice_unable_delete_time_entry: "No s'ha pogut suprimir l'entrada del registre de temps." - notice_issue_done_ratios_updated: "S'ha actualitzat el tant per cent dels assumptes." - - error_can_t_load_default_data: "No s'ha pogut carregar la configuració predeterminada: %{value} " - error_scm_not_found: "No s'ha trobat l'entrada o la revisió en el dipòsit." - error_scm_command_failed: "S'ha produït un error en intentar accedir al dipòsit: %{value}" - error_scm_annotate: "L'entrada no existeix o no s'ha pogut anotar." - error_issue_not_found_in_project: "No s'ha trobat l'assumpte o no pertany a aquest projecte" - error_no_tracker_in_project: "Aquest projecte no té seguidor associat. Comproveu els paràmetres del projecte." - error_no_default_issue_status: "No s'ha definit cap estat d'assumpte predeterminat. Comproveu la configuració (aneu a «Administració -> Estats de l'assumpte»)." - error_can_not_delete_custom_field: "No s'ha pogut suprimir el camp personalitat" - error_can_not_delete_tracker: "Aquest seguidor conté assumptes i no es pot suprimir." - error_can_not_remove_role: "Aquest rol s'està utilitzant i no es pot suprimir." - error_can_not_reopen_issue_on_closed_version: "Un assumpte assignat a una versió tancada no es pot tornar a obrir" - error_can_not_archive_project: "Aquest projecte no es pot arxivar" - error_issue_done_ratios_not_updated: "No s'ha actualitza el tant per cent dels assumptes." - error_workflow_copy_source: "Seleccioneu un seguidor o rol font" - error_workflow_copy_target: "Seleccioneu seguidors i rols objectiu" - error_unable_delete_issue_status: "No s'ha pogut suprimir l'estat de l'assumpte" - error_unable_to_connect: "No s'ha pogut connectar (%{value})" - warning_attachments_not_saved: "No s'han pogut desar %{count} fitxers." - - mail_subject_lost_password: "Contrasenya de %{value}" - mail_body_lost_password: "Per a canviar la contrasenya, feu clic en l'enllaç següent:" - mail_subject_register: "Activació del compte de %{value}" - mail_body_register: "Per a activar el compte, feu clic en l'enllaç següent:" - mail_body_account_information_external: "Podeu utilitzar el compte «%{value}» per a entrar." - mail_body_account_information: Informació del compte - mail_subject_account_activation_request: "Sol·licitud d'activació del compte de %{value}" - mail_body_account_activation_request: "S'ha registrat un usuari nou (%{value}). El seu compte està pendent d'aprovació:" - mail_subject_reminder: "%{count} assumptes venceran els següents %{days} dies" - mail_body_reminder: "%{count} assumptes que teniu assignades venceran els següents %{days} dies:" - mail_subject_wiki_content_added: "S'ha afegit la pàgina wiki «%{id}»" - mail_body_wiki_content_added: "En %{author} ha afegit la pàgina wiki «%{id}»." - mail_subject_wiki_content_updated: "S'ha actualitzat la pàgina wiki «%{id}»" - mail_body_wiki_content_updated: "En %{author} ha actualitzat la pàgina wiki «%{id}»." - - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errors" - - field_name: Nom - field_description: Descripció - field_summary: Resum - field_is_required: Necessari - field_firstname: Nom - field_lastname: Cognom - field_mail: Correu electrònic - field_filename: Fitxer - field_filesize: Mida - field_downloads: Baixades - field_author: Autor - field_created_on: Creat - field_updated_on: Actualitzat - field_field_format: Format - field_is_for_all: Per a tots els projectes - field_possible_values: Valores possibles - field_regexp: Expressió regular - field_min_length: Longitud mínima - field_max_length: Longitud màxima - field_value: Valor - field_category: Categoria - field_title: Títol - field_project: Projecte - field_issue: Assumpte - field_status: Estat - field_notes: Notes - field_is_closed: Assumpte tancat - field_is_default: Estat predeterminat - field_tracker: Seguidor - field_subject: Tema - field_due_date: Data de venciment - field_assigned_to: Assignat a - field_priority: Prioritat - field_fixed_version: Versió objectiu - field_user: Usuari - field_principal: Principal - field_role: Rol - field_homepage: Pàgina web - field_is_public: Públic - field_parent: Subprojecte de - field_is_in_roadmap: Assumptes mostrats en la planificació - field_login: Entrada - field_mail_notification: Notificacions per correu electrònic - field_admin: Administrador - field_last_login_on: Última connexió - field_language: Idioma - field_effective_date: Data - field_password: Contrasenya - field_new_password: Contrasenya nova - field_password_confirmation: Confirmació - field_version: Versió - field_type: Tipus - field_host: Ordinador - field_port: Port - field_account: Compte - field_base_dn: Base DN - field_attr_login: "Atribut d'entrada" - field_attr_firstname: Atribut del nom - field_attr_lastname: Atribut del cognom - field_attr_mail: Atribut del correu electrònic - field_onthefly: "Creació de l'usuari «al vol»" - field_start_date: Inici - field_done_ratio: "% realitzat" - field_auth_source: "Mode d'autenticació" - field_hide_mail: "Oculta l'adreça de correu electrònic" - field_comments: Comentari - field_url: URL - field_start_page: Pàgina inicial - field_subproject: Subprojecte - field_hours: Hores - field_activity: Activitat - field_spent_on: Data - field_identifier: Identificador - field_is_filter: "S'ha utilitzat com a filtre" - field_issue_to: Assumpte relacionat - field_delay: Retard - field_assignable: Es poden assignar assumptes a aquest rol - field_redirect_existing_links: Redirigeix els enllaços existents - field_estimated_hours: Temps previst - field_column_names: Columnes - field_time_entries: "Registre de temps" - field_time_zone: Zona horària - field_searchable: Es pot cercar - field_default_value: Valor predeterminat - field_comments_sorting: Mostra els comentaris - field_parent_title: Pàgina pare - field_editable: Es pot editar - field_watcher: Vigilància - field_identity_url: URL OpenID - field_content: Contingut - field_group_by: "Agrupa els resultats per" - field_sharing: Compartició - field_parent_issue: "Tasca pare" - - setting_app_title: "Títol de l'aplicació" - setting_app_subtitle: "Subtítol de l'aplicació" - setting_welcome_text: Text de benvinguda - setting_default_language: Idioma predeterminat - setting_login_required: Es necessita autenticació - setting_self_registration: Registre automàtic - setting_attachment_max_size: Mida màxima dels adjunts - setting_issues_export_limit: "Límit d'exportació d'assumptes" - setting_mail_from: "Adreça de correu electrònic d'emissió" - setting_bcc_recipients: Vincula els destinataris de les còpies amb carbó (bcc) - setting_plain_text_mail: només text pla (no HTML) - setting_host_name: "Nom de l'ordinador" - setting_text_formatting: Format del text - setting_wiki_compression: "Comprimeix l'historial del wiki" - setting_feeds_limit: Límit de contingut del canal - setting_default_projects_public: Els projectes nous són públics per defecte - setting_autofetch_changesets: Omple automàticament les publicacions - setting_sys_api_enabled: Habilita el WS per a la gestió del dipòsit - setting_commit_ref_keywords: Paraules claus per a la referència - setting_commit_fix_keywords: Paraules claus per a la correcció - setting_autologin: Entrada automàtica - setting_date_format: Format de la data - setting_time_format: Format de hora - setting_cross_project_issue_relations: "Permet les relacions d'assumptes entre projectes" - setting_issue_list_default_columns: "Columnes mostrades per defecte en la llista d'assumptes" - setting_emails_footer: Peu dels correus electrònics - setting_protocol: Protocol - setting_per_page_options: Opcions dels objectes per pàgina - setting_user_format: "Format de com mostrar l'usuari" - setting_activity_days_default: "Dies a mostrar l'activitat del projecte" - setting_display_subprojects_issues: "Mostra els assumptes d'un subprojecte en el projecte pare per defecte" - setting_enabled_scm: "Habilita l'SCM" - setting_mail_handler_body_delimiters: "Trunca els correus electrònics després d'una d'aquestes línies" - setting_mail_handler_api_enabled: "Habilita el WS per correus electrònics d'entrada" - setting_mail_handler_api_key: Clau API - setting_sequential_project_identifiers: Genera identificadors de projecte seqüencials - setting_gravatar_enabled: "Utilitza les icones d'usuari Gravatar" - setting_gravatar_default: "Imatge Gravatar predeterminada" - setting_diff_max_lines_displayed: Número màxim de línies amb diferències mostrades - setting_file_max_size_displayed: Mida màxima dels fitxers de text mostrats en línia - setting_repository_log_display_limit: Número màxim de revisions que es mostren al registre de fitxers - setting_openid: "Permet entrar i registrar-se amb l'OpenID" - setting_password_min_length: "Longitud mínima de la contrasenya" - setting_new_project_user_role_id: "Aquest rol es dóna a un usuari no administrador per a crear projectes" - setting_default_projects_modules: "Mòduls activats per defecte en els projectes nous" - setting_issue_done_ratio: "Calcula tant per cent realitzat de l'assumpte amb" - setting_issue_done_ratio_issue_status: "Utilitza l'estat de l'assumpte" - setting_issue_done_ratio_issue_field: "Utilitza el camp de l'assumpte" - setting_start_of_week: "Inicia les setmanes en" - setting_rest_api_enabled: "Habilita el servei web REST" - setting_cache_formatted_text: Cache formatted text - - permission_add_project: "Crea projectes" - permission_add_subprojects: "Crea subprojectes" - permission_edit_project: Edita el projecte - permission_select_project_modules: Selecciona els mòduls del projecte - permission_manage_members: Gestiona els membres - permission_manage_project_activities: "Gestiona les activitats del projecte" - permission_manage_versions: Gestiona les versions - permission_manage_categories: Gestiona les categories dels assumptes - permission_view_issues: "Visualitza els assumptes" - permission_add_issues: Afegeix assumptes - permission_edit_issues: Edita els assumptes - permission_manage_issue_relations: Gestiona les relacions dels assumptes - permission_add_issue_notes: Afegeix notes - permission_edit_issue_notes: Edita les notes - permission_edit_own_issue_notes: Edita les notes pròpies - permission_move_issues: Mou els assumptes - permission_delete_issues: Suprimeix els assumptes - permission_manage_public_queries: Gestiona les consultes públiques - permission_save_queries: Desa les consultes - permission_view_gantt: Visualitza la gràfica de Gantt - permission_view_calendar: Visualitza el calendari - permission_view_issue_watchers: Visualitza la llista de vigilàncies - permission_add_issue_watchers: Afegeix vigilàncies - permission_delete_issue_watchers: Suprimeix els vigilants - permission_log_time: Registra el temps invertit - permission_view_time_entries: Visualitza el temps invertit - permission_edit_time_entries: Edita els registres de temps - permission_edit_own_time_entries: Edita els registres de temps propis - permission_manage_news: Gestiona les noticies - permission_comment_news: Comenta les noticies - permission_manage_documents: Gestiona els documents - permission_view_documents: Visualitza els documents - permission_manage_files: Gestiona els fitxers - permission_view_files: Visualitza els fitxers - permission_manage_wiki: Gestiona el wiki - permission_rename_wiki_pages: Canvia el nom de les pàgines wiki - permission_delete_wiki_pages: Suprimeix les pàgines wiki - permission_view_wiki_pages: Visualitza el wiki - permission_view_wiki_edits: "Visualitza l'historial del wiki" - permission_edit_wiki_pages: Edita les pàgines wiki - permission_delete_wiki_pages_attachments: Suprimeix adjunts - permission_protect_wiki_pages: Protegeix les pàgines wiki - permission_manage_repository: Gestiona el dipòsit - permission_browse_repository: Navega pel dipòsit - permission_view_changesets: Visualitza els canvis realitzats - permission_commit_access: Accés a les publicacions - permission_manage_boards: Gestiona els taulers - permission_view_messages: Visualitza els missatges - permission_add_messages: Envia missatges - permission_edit_messages: Edita els missatges - permission_edit_own_messages: Edita els missatges propis - permission_delete_messages: Suprimeix els missatges - permission_delete_own_messages: Suprimeix els missatges propis - permission_export_wiki_pages: "Exporta les pàgines wiki" - permission_manage_subtasks: "Gestiona subtasques" - - project_module_issue_tracking: "Seguidor d'assumptes" - project_module_time_tracking: Seguidor de temps - project_module_news: Noticies - project_module_documents: Documents - project_module_files: Fitxers - project_module_wiki: Wiki - project_module_repository: Dipòsit - project_module_boards: Taulers - project_module_calendar: Calendari - project_module_gantt: Gantt - - label_user: Usuari - label_user_plural: Usuaris - label_user_new: Usuari nou - label_user_anonymous: Anònim - label_project: Projecte - label_project_new: Projecte nou - label_project_plural: Projectes - label_x_projects: - zero: cap projecte - one: 1 projecte - other: "%{count} projectes" - label_project_all: Tots els projectes - label_project_latest: Els últims projectes - label_issue: Assumpte - label_issue_new: Assumpte nou - label_issue_plural: Assumptes - label_issue_view_all: Visualitza tots els assumptes - label_issues_by: "Assumptes per %{value}" - label_issue_added: Assumpte afegit - label_issue_updated: Assumpte actualitzat - label_document: Document - label_document_new: Document nou - label_document_plural: Documents - label_document_added: Document afegit - label_role: Rol - label_role_plural: Rols - label_role_new: Rol nou - label_role_and_permissions: Rols i permisos - label_member: Membre - label_member_new: Membre nou - label_member_plural: Membres - label_tracker: Seguidor - label_tracker_plural: Seguidors - label_tracker_new: Seguidor nou - label_workflow: Flux de treball - label_issue_status: "Estat de l'assumpte" - label_issue_status_plural: "Estats de l'assumpte" - label_issue_status_new: Estat nou - label_issue_category: "Categoria de l'assumpte" - label_issue_category_plural: "Categories de l'assumpte" - label_issue_category_new: Categoria nova - label_custom_field: Camp personalitzat - label_custom_field_plural: Camps personalitzats - label_custom_field_new: Camp personalitzat nou - label_enumerations: Enumeracions - label_enumeration_new: Valor nou - label_information: Informació - label_information_plural: Informació - label_please_login: Entreu - label_register: Registre - label_login_with_open_id_option: "o entra amb l'OpenID" - label_password_lost: Contrasenya perduda - label_home: Inici - label_my_page: La meva pàgina - label_my_account: El meu compte - label_my_projects: Els meus projectes - label_my_page_block: "Els meus blocs de pàgina" - label_administration: Administració - label_login: Entra - label_logout: Surt - label_help: Ajuda - label_reported_issues: Assumptes informats - label_assigned_to_me_issues: Assumptes assignats a mi - label_last_login: Última connexió - label_registered_on: Informat el - label_activity: Activitat - label_overall_activity: Activitat global - label_user_activity: "Activitat de %{value}" - label_new: Nou - label_logged_as: Heu entrat com a - label_environment: Entorn - label_authentication: Autenticació - label_auth_source: "Mode d'autenticació" - label_auth_source_new: "Mode d'autenticació nou" - label_auth_source_plural: "Modes d'autenticació" - label_subproject_plural: Subprojectes - label_subproject_new: "Subprojecte nou" - label_and_its_subprojects: "%{value} i els seus subprojectes" - label_min_max_length: Longitud mín - max - label_list: Llist - label_date: Data - label_integer: Enter - label_float: Flotant - label_boolean: Booleà - label_string: Text - label_text: Text llarg - label_attribute: Atribut - label_attribute_plural: Atributs - label_download: "%{count} baixada" - label_download_plural: "%{count} baixades" - label_no_data: Sense dades a mostrar - label_change_status: "Canvia l'estat" - label_history: Historial - label_attachment: Fitxer - label_attachment_new: Fitxer nou - label_attachment_delete: Suprimeix el fitxer - label_attachment_plural: Fitxers - label_file_added: Fitxer afegit - label_report: Informe - label_report_plural: Informes - label_news: Noticies - label_news_new: Afegeix noticies - label_news_plural: Noticies - label_news_latest: Últimes noticies - label_news_view_all: Visualitza totes les noticies - label_news_added: Noticies afegides - label_settings: Paràmetres - label_overview: Resum - label_version: Versió - label_version_new: Versió nova - label_version_plural: Versions - label_close_versions: "Tanca les versions completades" - label_confirmation: Confirmació - label_export_to: "També disponible a:" - label_read: Llegeix... - label_public_projects: Projectes públics - label_open_issues: obert - label_open_issues_plural: oberts - label_closed_issues: tancat - label_closed_issues_plural: tancats - label_x_open_issues_abbr_on_total: - zero: 0 oberts / %{total} - one: 1 obert / %{total} - other: "%{count} oberts / %{total}" - label_x_open_issues_abbr: - zero: 0 oberts - one: 1 obert - other: "%{count} oberts" - label_x_closed_issues_abbr: - zero: 0 tancats - one: 1 tancat - other: "%{count} tancats" - label_total: Total - label_permissions: Permisos - label_current_status: Estat actual - label_new_statuses_allowed: Nous estats autoritzats - label_all: tots - label_none: cap - label_nobody: ningú - label_next: Següent - label_previous: Anterior - label_used_by: Utilitzat per - label_details: Detalls - label_add_note: Afegeix una nota - label_per_page: Per pàgina - label_calendar: Calendari - label_months_from: mesos des de - label_gantt: Gantt - label_internal: Intern - label_last_changes: "últims %{count} canvis" - label_change_view_all: Visualitza tots els canvis - label_personalize_page: Personalitza aquesta pàgina - label_comment: Comentari - label_comment_plural: Comentaris - label_x_comments: - zero: sense comentaris - one: 1 comentari - other: "%{count} comentaris" - label_comment_add: Afegeix un comentari - label_comment_added: Comentari afegit - label_comment_delete: Suprimeix comentaris - label_query: Consulta personalitzada - label_query_plural: Consultes personalitzades - label_query_new: Consulta nova - label_filter_add: Afegeix un filtre - label_filter_plural: Filtres - label_equals: és - label_not_equals: no és - label_in_less_than: en menys de - label_in_more_than: en més de - label_greater_or_equal: ">=" - label_less_or_equal: <= - label_in: en - label_today: avui - label_all_time: tot el temps - label_yesterday: ahir - label_this_week: aquesta setmana - label_last_week: "l'última setmana" - label_last_n_days: "els últims %{count} dies" - label_this_month: aquest més - label_last_month: "l'últim més" - label_this_year: aquest any - label_date_range: Abast de les dates - label_less_than_ago: fa menys de - label_more_than_ago: fa més de - label_ago: fa - label_contains: conté - label_not_contains: no conté - label_day_plural: dies - label_repository: Dipòsit - label_repository_plural: Dipòsits - label_browse: Navega - label_modification: "%{count} canvi" - label_modification_plural: "%{count} canvis" - label_branch: Branca - label_tag: Etiqueta - label_revision: Revisió - label_revision_plural: Revisions - label_revision_id: "Revisió %{value}" - label_associated_revisions: Revisions associades - label_added: afegit - label_modified: modificat - label_copied: copiat - label_renamed: reanomenat - label_deleted: suprimit - label_latest_revision: Última revisió - label_latest_revision_plural: Últimes revisions - label_view_revisions: Visualitza les revisions - label_view_all_revisions: "Visualitza totes les revisions" - label_max_size: Mida màxima - label_sort_highest: Mou a la part superior - label_sort_higher: Mou cap amunt - label_sort_lower: Mou cap avall - label_sort_lowest: Mou a la part inferior - label_roadmap: Planificació - label_roadmap_due_in: "Venç en %{value}" - label_roadmap_overdue: "%{value} tard" - label_roadmap_no_issues: No hi ha assumptes per a aquesta versió - label_search: Cerca - label_result_plural: Resultats - label_all_words: Totes les paraules - label_wiki: Wiki - label_wiki_edit: Edició wiki - label_wiki_edit_plural: Edicions wiki - label_wiki_page: Pàgina wiki - label_wiki_page_plural: Pàgines wiki - label_index_by_title: Ãndex per títol - label_index_by_date: Ãndex per data - label_current_version: Versió actual - label_preview: Previsualització - label_feed_plural: Canals - label_changes_details: Detalls de tots els canvis - label_issue_tracking: "Seguiment d'assumptes" - label_spent_time: Temps invertit - label_overall_spent_time: "Temps total invertit" - label_f_hour: "%{value} hora" - label_f_hour_plural: "%{value} hores" - label_time_tracking: Temps de seguiment - label_change_plural: Canvis - label_statistics: Estadístiques - label_commits_per_month: Publicacions per mes - label_commits_per_author: Publicacions per autor - label_view_diff: Visualitza les diferències - label_diff_inline: en línia - label_diff_side_by_side: costat per costat - label_options: Opcions - label_copy_workflow_from: Copia el flux de treball des de - label_permissions_report: Informe de permisos - label_watched_issues: Assumptes vigilats - label_related_issues: Assumptes relacionats - label_applied_status: Estat aplicat - label_loading: "S'està carregant..." - label_relation_new: Relació nova - label_relation_delete: Suprimeix la relació - label_relates_to: relacionat amb - label_duplicates: duplicats - label_duplicated_by: duplicat per - label_blocks: bloqueja - label_blocked_by: bloquejats per - label_precedes: anterior a - label_follows: posterior a - label_end_to_start: final al començament - label_end_to_end: final al final - label_start_to_start: començament al començament - label_start_to_end: començament al final - label_stay_logged_in: "Manté l'entrada" - label_disabled: inhabilitat - label_show_completed_versions: Mostra les versions completes - label_me: jo mateix - label_board: Fòrum - label_board_new: Fòrum nou - label_board_plural: Fòrums - label_board_locked: Bloquejat - label_board_sticky: Sticky - label_topic_plural: Temes - label_message_plural: Missatges - label_message_last: Últim missatge - label_message_new: Missatge nou - label_message_posted: Missatge afegit - label_reply_plural: Respostes - label_send_information: "Envia la informació del compte a l'usuari" - label_year: Any - label_month: Mes - label_week: Setmana - label_date_from: Des de - label_date_to: A - label_language_based: "Basat en l'idioma de l'usuari" - label_sort_by: "Ordena per %{value}" - label_send_test_email: Envia un correu electrònic de prova - label_feeds_access_key: "Clau d'accés del RSS" - label_missing_feeds_access_key: "Falta una clau d'accés del RSS" - label_feeds_access_key_created_on: "Clau d'accés del RSS creada fa %{value}" - label_module_plural: Mòduls - label_added_time_by: "Afegit per %{author} fa %{age}" - label_updated_time_by: "Actualitzat per %{author} fa %{age}" - label_updated_time: "Actualitzat fa %{value}" - label_jump_to_a_project: Salta al projecte... - label_file_plural: Fitxers - label_changeset_plural: Conjunt de canvis - label_default_columns: Columnes predeterminades - label_no_change_option: (sense canvis) - label_bulk_edit_selected_issues: Edita en bloc els assumptes seleccionats - label_theme: Tema - label_default: Predeterminat - label_search_titles_only: Cerca només en els títols - label_user_mail_option_all: "Per qualsevol esdeveniment en tots els meus projectes" - label_user_mail_option_selected: "Per qualsevol esdeveniment en els projectes seleccionats..." - label_user_mail_no_self_notified: "No vull ser notificat pels canvis que faig jo mateix" - label_registration_activation_by_email: activació del compte per correu electrònic - label_registration_manual_activation: activació del compte manual - label_registration_automatic_activation: activació del compte automàtica - label_display_per_page: "Per pàgina: %{value}" - label_age: Edat - label_change_properties: Canvia les propietats - label_general: General - label_more: Més - label_scm: SCM - label_plugins: Connectors - label_ldap_authentication: Autenticació LDAP - label_downloads_abbr: Baixades - label_optional_description: Descripció opcional - label_add_another_file: Afegeix un altre fitxer - label_preferences: Preferències - label_chronological_order: En ordre cronològic - label_reverse_chronological_order: En ordre cronològic invers - label_planning: Planificació - label_incoming_emails: "Correu electrònics d'entrada" - label_generate_key: Genera una clau - label_issue_watchers: Vigilàncies - label_example: Exemple - label_display: Mostra - label_sort: Ordena - label_ascending: Ascendent - label_descending: Descendent - label_date_from_to: Des de %{start} a %{end} - label_wiki_content_added: "S'ha afegit la pàgina wiki" - label_wiki_content_updated: "S'ha actualitzat la pàgina wiki" - label_group: Grup - label_group_plural: Grups - label_group_new: Grup nou - label_time_entry_plural: Temps invertit - label_version_sharing_hierarchy: "Amb la jerarquia del projecte" - label_version_sharing_system: "Amb tots els projectes" - label_version_sharing_descendants: "Amb tots els subprojectes" - label_version_sharing_tree: "Amb l'arbre del projecte" - label_version_sharing_none: "Sense compartir" - label_update_issue_done_ratios: "Actualitza el tant per cent dels assumptes realitzats" - label_copy_source: Font - label_copy_target: Objectiu - label_copy_same_as_target: "El mateix que l'objectiu" - label_display_used_statuses_only: "Mostra només els estats que utilitza aquest seguidor" - label_api_access_key: "Clau d'accés a l'API" - label_missing_api_access_key: "Falta una clau d'accés de l'API" - label_api_access_key_created_on: "Clau d'accés de l'API creada fa %{value}" - label_profile: Perfil - label_subtask_plural: Subtasques - label_project_copy_notifications: "Envia notificacions de correu electrònic durant la còpia del projecte" - - button_login: Entra - button_submit: Tramet - button_save: Desa - button_check_all: Activa-ho tot - button_uncheck_all: Desactiva-ho tot - button_delete: Suprimeix - button_create: Crea - button_create_and_continue: Crea i continua - button_test: Test - button_edit: Edit - button_add: Afegeix - button_change: Canvia - button_apply: Aplica - button_clear: Neteja - button_lock: Bloca - button_unlock: Desbloca - button_download: Baixa - button_list: Llista - button_view: Visualitza - button_move: Mou - button_move_and_follow: "Mou i segueix" - button_back: Enrere - button_cancel: Cancel·la - button_activate: Activa - button_sort: Ordena - button_log_time: "Registre de temps" - button_rollback: Torna a aquesta versió - button_watch: Vigila - button_unwatch: No vigilis - button_reply: Resposta - button_archive: Arxiva - button_unarchive: Desarxiva - button_reset: Reinicia - button_rename: Reanomena - button_change_password: Canvia la contrasenya - button_copy: Copia - button_copy_and_follow: "Copia i segueix" - button_annotate: Anota - button_update: Actualitza - button_configure: Configura - button_quote: Cita - button_duplicate: Duplica - button_show: Mostra - - status_active: actiu - status_registered: informat - status_locked: bloquejat - - version_status_open: oberta - version_status_locked: bloquejada - version_status_closed: tancada - - field_active: Actiu - - text_select_mail_notifications: "Seleccioneu les accions per les quals s'hauria d'enviar una notificació per correu electrònic." - text_regexp_info: ex. ^[A-Z0-9]+$ - text_min_max_length_info: 0 significa sense restricció - text_project_destroy_confirmation: Segur que voleu suprimir aquest projecte i les dades relacionades? - text_subprojects_destroy_warning: "També seran suprimits els seus subprojectes: %{value}." - text_workflow_edit: Seleccioneu un rol i un seguidor per a editar el flux de treball - text_are_you_sure: Segur? - text_journal_changed: "%{label} ha canviat de %{old} a %{new}" - text_journal_set_to: "%{label} s'ha establert a %{value}" - text_journal_deleted: "%{label} s'ha suprimit (%{old})" - text_journal_added: "S'ha afegit %{label} %{value}" - text_tip_issue_begin_day: "tasca que s'inicia aquest dia" - text_tip_issue_end_day: tasca que finalitza aquest dia - text_tip_issue_begin_end_day: "tasca que s'inicia i finalitza aquest dia" - text_caracters_maximum: "%{count} caràcters com a màxim." - text_caracters_minimum: "Com a mínim ha de tenir %{count} caràcters." - text_length_between: "Longitud entre %{min} i %{max} caràcters." - text_tracker_no_workflow: "No s'ha definit cap flux de treball per a aquest seguidor" - text_unallowed_characters: Caràcters no permesos - text_comma_separated: Es permeten valors múltiples (separats per una coma). - text_line_separated: "Es permeten diversos valors (una línia per cada valor)." - text_issues_ref_in_commit_messages: Referència i soluciona els assumptes en els missatges publicats - text_issue_added: "L'assumpte %{id} ha sigut informat per %{author}." - text_issue_updated: "L'assumpte %{id} ha sigut actualitzat per %{author}." - text_wiki_destroy_confirmation: Segur que voleu suprimir aquest wiki i tots els seus continguts? - text_issue_category_destroy_question: "Alguns assumptes (%{count}) estan assignats a aquesta categoria. Què voleu fer?" - text_issue_category_destroy_assignments: Suprimeix les assignacions de la categoria - text_issue_category_reassign_to: Torna a assignar els assumptes a aquesta categoria - text_user_mail_option: "Per als projectes no seleccionats, només rebreu notificacions sobre les coses que vigileu o que hi esteu implicat (ex. assumptes que en sou l'autor o hi esteu assignat)." - text_no_configuration_data: "Encara no s'han configurat els rols, seguidors, estats de l'assumpte i flux de treball.\nÉs altament recomanable que carregueu la configuració predeterminada. Podreu modificar-la un cop carregada." - text_load_default_configuration: Carrega la configuració predeterminada - text_status_changed_by_changeset: "Aplicat en el conjunt de canvis %{value}." - text_issues_destroy_confirmation: "Segur que voleu suprimir els assumptes seleccionats?" - text_select_project_modules: "Seleccioneu els mòduls a habilitar per a aquest projecte:" - text_default_administrator_account_changed: "S'ha canviat el compte d'administrador predeterminat" - text_file_repository_writable: Es pot escriure en el dipòsit de fitxers - text_plugin_assets_writable: Es pot escriure als connectors actius - text_rmagick_available: RMagick disponible (opcional) - text_destroy_time_entries_question: "S'han informat %{hours} hores en els assumptes que aneu a suprimir. Què voleu fer?" - text_destroy_time_entries: Suprimeix les hores informades - text_assign_time_entries_to_project: Assigna les hores informades al projecte - text_reassign_time_entries: "Torna a assignar les hores informades a aquest assumpte:" - text_user_wrote: "%{value} va escriure:" - text_enumeration_destroy_question: "%{count} objectes estan assignats a aquest valor." - text_enumeration_category_reassign_to: "Torna a assignar-los a aquest valor:" - text_email_delivery_not_configured: "El lliurament per correu electrònic no està configurat i les notificacions estan inhabilitades.\nConfigureu el servidor SMTP a config/configuration.yml i reinicieu l'aplicació per habilitar-lo." - text_repository_usernames_mapping: "Seleccioneu l'assignació entre els usuaris del Redmine i cada nom d'usuari trobat al dipòsit.\nEls usuaris amb el mateix nom d'usuari o correu del Redmine i del dipòsit s'assignaran automàticament." - text_diff_truncated: "... Aquestes diferències s'han trucat perquè excedeixen la mida màxima que es pot mostrar." - text_custom_field_possible_values_info: "Una línia per a cada valor" - text_wiki_page_destroy_question: "Aquesta pàgina té %{descendants} pàgines fill i descendents. Què voleu fer?" - text_wiki_page_nullify_children: "Deixa les pàgines fill com a pàgines arrel" - text_wiki_page_destroy_children: "Suprimeix les pàgines fill i tots els seus descendents" - text_wiki_page_reassign_children: "Reasigna les pàgines fill a aquesta pàgina pare" - text_own_membership_delete_confirmation: "Esteu a punt de suprimir algun o tots els vostres permisos i potser no podreu editar més aquest projecte.\nSegur que voleu continuar?" - text_zoom_in: Redueix - text_zoom_out: Amplia - - default_role_manager: Gestor - default_role_developer: Desenvolupador - default_role_reporter: Informador - default_tracker_bug: Error - default_tracker_feature: Característica - default_tracker_support: Suport - default_issue_status_new: Nou - default_issue_status_in_progress: In Progress - default_issue_status_resolved: Resolt - default_issue_status_feedback: Comentaris - default_issue_status_closed: Tancat - default_issue_status_rejected: Rebutjat - default_doc_category_user: "Documentació d'usuari" - default_doc_category_tech: Documentació tècnica - default_priority_low: Baixa - default_priority_normal: Normal - default_priority_high: Alta - default_priority_urgent: Urgent - default_priority_immediate: Immediata - default_activity_design: Disseny - default_activity_development: Desenvolupament - - enumeration_issue_priorities: Prioritat dels assumptes - enumeration_doc_categories: Categories del document - enumeration_activities: Activitats (seguidor de temps) - enumeration_system_activity: Activitat del sistema - - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Codificació dels missatges publicats - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 assumpte - one: 1 assumpte - other: "%{count} assumptes" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: tots - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: "Amb tots els subprojectes" - label_cross_project_tree: "Amb l'arbre del projecte" - label_cross_project_hierarchy: "Amb la jerarquia del projecte" - label_cross_project_system: "Amb tots els projectes" - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/26/26f01879d014f7cfdce074411219f0a0e3852861.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/26/26f01879d014f7cfdce074411219f0a0e3852861.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,51 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'action_view/helpers/form_helper' + +class Redmine::Views::LabelledFormBuilder < ActionView::Helpers::FormBuilder + include Redmine::I18n + + (field_helpers.map(&:to_s) - %w(radio_button hidden_field fields_for) + + %w(date_select)).each do |selector| + src = <<-END_SRC + def #{selector}(field, options = {}) + label_for_field(field, options) + super(field, options.except(:label)).html_safe + end + END_SRC + class_eval src, __FILE__, __LINE__ + end + + def select(field, choices, options = {}, html_options = {}) + label_for_field(field, options) + super(field, choices, options, html_options.except(:label)).html_safe + end + + def time_zone_select(field, priority_zones = nil, options = {}, html_options = {}) + label_for_field(field, options) + super(field, priority_zones, options, html_options.except(:label)).html_safe + end + + # Returns a label tag for the given field + def label_for_field(field, options = {}) + return ''.html_safe if options.delete(:no_label) + text = options[:label].is_a?(Symbol) ? l(options[:label]) : options[:label] + text ||= l(("field_" + field.to_s.gsub(/\_id$/, "")).to_sym) + text += @template.content_tag("span", " *", :class => "required") if options.delete(:required) + @template.content_tag("label", text.html_safe, + :class => (@object && @object.errors[field].present? ? "error" : nil), + :for => (@object_name.to_s + "_" + field.to_s)) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/27/27ba65ff9d28d2f3bd5d9b790ec776816aaa1eb7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/27/27ba65ff9d28d2f3bd5d9b790ec776816aaa1eb7.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,609 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../../test_helper', __FILE__) +begin + require 'mocha/setup' + + class GitAdapterTest < ActiveSupport::TestCase + REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s + + FELIX_HEX = "Felix Sch\xC3\xA4fer" + CHAR_1_HEX = "\xc3\x9c" + + ## Git, Mercurial and CVS path encodings are binary. + ## Subversion supports URL encoding for path. + ## Redmine Mercurial adapter and extension use URL encoding. + ## Git accepts only binary path in command line parameter. + ## So, there is no way to use binary command line parameter in JRuby. + JRUBY_SKIP = (RUBY_PLATFORM == 'java') + JRUBY_SKIP_STR = "TODO: This test fails in JRuby" + + if File.directory?(REPOSITORY_PATH) + ## Ruby uses ANSI api to fork a process on Windows. + ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem + ## and these are incompatible with ASCII. + ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10 + ## http://code.google.com/p/msysgit/issues/detail?id=80 + ## So, Latin-1 path tests fail on Japanese Windows + WINDOWS_PASS = (Redmine::Platform.mswin? && + Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10])) + WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10" + + def setup + adapter_class = Redmine::Scm::Adapters::GitAdapter + assert adapter_class + assert adapter_class.client_command + assert_equal true, adapter_class.client_available + assert_equal true, adapter_class.client_version_above?([1]) + assert_equal true, adapter_class.client_version_above?([1, 0]) + + @adapter = Redmine::Scm::Adapters::GitAdapter.new( + REPOSITORY_PATH, + nil, + nil, + nil, + 'ISO-8859-1' + ) + assert @adapter + @char_1 = CHAR_1_HEX.dup + @str_felix_hex = FELIX_HEX.dup + if @char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + @str_felix_hex.force_encoding('ASCII-8BIT') + end + end + + def test_scm_version + to_test = { "git version 1.7.3.4\n" => [1,7,3,4], + "1.6.1\n1.7\n1.8" => [1,6,1], + "1.6.2\r\n1.8.1\r\n1.9.1" => [1,6,2]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end + end + + def test_branches + brs = [] + @adapter.branches.each do |b| + brs << b + end + assert_equal 6, brs.length + br_issue_8857 = brs[0] + assert_equal 'issue-8857', br_issue_8857.to_s + assert_equal '2a682156a3b6e77a8bf9cd4590e8db757f3c6c78', br_issue_8857.revision + assert_equal br_issue_8857.scmid, br_issue_8857.revision + assert_equal false, br_issue_8857.is_default + br_latin_1_path = brs[1] + assert_equal 'latin-1-path-encoding', br_latin_1_path.to_s + assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', br_latin_1_path.revision + assert_equal br_latin_1_path.scmid, br_latin_1_path.revision + assert_equal false, br_latin_1_path.is_default + br_master = brs[2] + assert_equal 'master', br_master.to_s + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', br_master.revision + assert_equal br_master.scmid, br_master.revision + assert_equal false, br_master.is_default + br_master_20120212 = brs[3] + assert_equal 'master-20120212', br_master_20120212.to_s + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', br_master_20120212.revision + assert_equal br_master_20120212.scmid, br_master_20120212.revision + assert_equal true, br_master_20120212.is_default + br_latin_1 = brs[-2] + assert_equal 'test-latin-1', br_latin_1.to_s + assert_equal '67e7792ce20ccae2e4bb73eed09bb397819c8834', br_latin_1.revision + assert_equal br_latin_1.scmid, br_latin_1.revision + assert_equal false, br_latin_1.is_default + br_test = brs[-1] + assert_equal 'test_branch', br_test.to_s + assert_equal 'fba357b886984ee71185ad2065e65fc0417d9b92', br_test.revision + assert_equal br_test.scmid, br_test.revision + assert_equal false, br_test.is_default + end + + def test_default_branch + assert_equal 'master-20120212', @adapter.default_branch + end + + def test_tags + assert_equal [ + "tag00.lightweight", + "tag01.annotated", + ], @adapter.tags + end + + def test_revisions_master_all + revs1 = [] + @adapter.revisions('', nil, "master",{}) do |rev| + revs1 << rev + end + assert_equal 15, revs1.length + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[ 0].identifier + assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs1[-1].identifier + + revs2 = [] + @adapter.revisions('', nil, "master", + {:reverse => true}) do |rev| + revs2 << rev + end + assert_equal 15, revs2.length + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs2[-1].identifier + assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs2[ 0].identifier + end + + def test_revisions_master_merged_rev + revs1 = [] + @adapter.revisions('', + "713f4944648826f558cf548222f813dabe7cbb04", + "master", + {:reverse => true}) do |rev| + revs1 << rev + end + assert_equal 8, revs1.length + assert_equal 'fba357b886984ee71185ad2065e65fc0417d9b92', revs1[ 0].identifier + assert_equal '7e61ac704deecde634b51e59daa8110435dcb3da', revs1[ 1].identifier + # 4a07fe31b is not a child of 713f49446 + assert_equal '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8', revs1[ 2].identifier + # Merged revision + assert_equal '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf', revs1[ 3].identifier + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[-1].identifier + + revs2 = [] + @adapter.revisions('', + "fba357b886984ee71185ad2065e65fc0417d9b92", + "master", + {:reverse => true}) do |rev| + revs2 << rev + end + assert_equal 7, revs2.length + assert_equal '7e61ac704deecde634b51e59daa8110435dcb3da', revs2[ 0].identifier + # 4a07fe31b is not a child of fba357b8869 + assert_equal '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8', revs2[ 1].identifier + # Merged revision + assert_equal '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf', revs2[ 2].identifier + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs2[-1].identifier + end + + def test_revisions_branch_latin_1_path_encoding_all + revs1 = [] + @adapter.revisions('', nil, "latin-1-path-encoding",{}) do |rev| + revs1 << rev + end + assert_equal 8, revs1.length + assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs1[ 0].identifier + assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs1[-1].identifier + + revs2 = [] + @adapter.revisions('', nil, "latin-1-path-encoding", + {:reverse => true}) do |rev| + revs2 << rev + end + assert_equal 8, revs2.length + assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs2[-1].identifier + assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs2[ 0].identifier + end + + def test_revisions_branch_latin_1_path_encoding_with_rev + revs1 = [] + @adapter.revisions('', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + "latin-1-path-encoding", + {:reverse => true}) do |rev| + revs1 << rev + end + assert_equal 7, revs1.length + assert_equal '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', revs1[ 0].identifier + assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs1[-1].identifier + + revs2 = [] + @adapter.revisions('', + '57ca437c0acbbcb749821fdf3726a1367056d364', + "latin-1-path-encoding", + {:reverse => true}) do |rev| + revs2 << rev + end + assert_equal 3, revs2.length + assert_equal '4fc55c43bf3d3dc2efb66145365ddc17639ce81e', revs2[ 0].identifier + assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs2[-1].identifier + end + + def test_revisions_invalid_rev + assert_equal [], @adapter.revisions('', '1234abcd', "master") + assert_raise Redmine::Scm::Adapters::CommandFailed do + revs1 = [] + @adapter.revisions('', + '1234abcd', + "master", + {:reverse => true}) do |rev| + revs1 << rev + end + end + end + + def test_revisions_includes_master_two_revs + revs1 = [] + @adapter.revisions('', nil, nil, + {:reverse => true, + :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c'], + :excludes => ['4f26664364207fa8b1af9f8722647ab2d4ac5d43']}) do |rev| + revs1 << rev + end + assert_equal 2, revs1.length + assert_equal 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b', revs1[ 0].identifier + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[-1].identifier + end + + def test_revisions_includes_master_two_revs_from_origin + revs1 = [] + @adapter.revisions('', nil, nil, + {:reverse => true, + :includes => ['899a15dba03a3b350b89c3f537e4bbe02a03cdc9'], + :excludes => []}) do |rev| + revs1 << rev + end + assert_equal 2, revs1.length + assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs1[ 0].identifier + assert_equal '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', revs1[ 1].identifier + end + + def test_revisions_includes_merged_revs + revs1 = [] + @adapter.revisions('', nil, nil, + {:reverse => true, + :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c'], + :excludes => ['fba357b886984ee71185ad2065e65fc0417d9b92']}) do |rev| + revs1 << rev + end + assert_equal 7, revs1.length + assert_equal '7e61ac704deecde634b51e59daa8110435dcb3da', revs1[ 0].identifier + assert_equal '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8', revs1[ 1].identifier + assert_equal '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf', revs1[ 2].identifier + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[-1].identifier + end + + def test_revisions_includes_two_heads + revs1 = [] + @adapter.revisions('', nil, nil, + {:reverse => true, + :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', + '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127'], + :excludes => ['4f26664364207fa8b1af9f8722647ab2d4ac5d43', + '4fc55c43bf3d3dc2efb66145365ddc17639ce81e']}) do |rev| + revs1 << rev + end + assert_equal 4, revs1.length + assert_equal 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b', revs1[ 0].identifier + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[ 1].identifier + assert_equal '64f1f3e89ad1cb57976ff0ad99a107012ba3481d', revs1[-2].identifier + assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs1[-1].identifier + end + + def test_revisions_disjointed_histories_revisions + revs1 = [] + @adapter.revisions('', nil, nil, + {:reverse => true, + :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', + '92397af84d22f27389c822848ecd5b463c181583'], + :excludes => ['95488a44bc25f7d1f97d775a31359539ff333a63', + '4f26664364207fa8b1af9f8722647ab2d4ac5d43'] }) do |rev| + revs1 << rev + end + assert_equal 4, revs1.length + assert_equal 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b', revs1[ 0].identifier + assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[ 1].identifier + assert_equal 'bc201c95999c4f10d018b0aa03b541cd6a2ff0ee', revs1[-2].identifier + assert_equal '92397af84d22f27389c822848ecd5b463c181583', revs1[-1].identifier + end + + def test_revisions_invalid_rev_excludes + assert_equal [], + @adapter.revisions('', nil, nil, + {:reverse => true, + :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c'], + :excludes => ['0123abcd4567']}) + assert_raise Redmine::Scm::Adapters::CommandFailed do + revs1 = [] + @adapter.revisions('', nil, nil, + {:reverse => true, + :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c'], + :excludes => ['0123abcd4567']}) do |rev| + revs1 << rev + end + end + end + + def test_getting_revisions_with_spaces_in_filename + assert_equal 1, @adapter.revisions("filemane with spaces.txt", + nil, "master").length + end + + def test_parents + revs1 = [] + @adapter.revisions('', + nil, + "master", + {:reverse => true}) do |rev| + revs1 << rev + end + assert_equal 15, revs1.length + assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", + revs1[0].identifier + assert_equal nil, revs1[0].parents + assert_equal "899a15dba03a3b350b89c3f537e4bbe02a03cdc9", + revs1[1].identifier + assert_equal 1, revs1[1].parents.length + assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", + revs1[1].parents[0] + assert_equal "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf", + revs1[10].identifier + assert_equal 2, revs1[10].parents.length + assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", + revs1[10].parents[0] + assert_equal "7e61ac704deecde634b51e59daa8110435dcb3da", + revs1[10].parents[1] + end + + def test_getting_revisions_with_leading_and_trailing_spaces_in_filename + assert_equal " filename with a leading space.txt ", + @adapter.revisions(" filename with a leading space.txt ", + nil, "master")[0].paths[0][:path] + end + + def test_getting_entries_with_leading_and_trailing_spaces_in_filename + assert_equal " filename with a leading space.txt ", + @adapter.entries('', + '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c')[3].name + end + + def test_annotate + annotate = @adapter.annotate('sources/watchers_controller.rb') + assert_kind_of Redmine::Scm::Adapters::Annotate, annotate + assert_equal 41, annotate.lines.size + assert_equal "# This program is free software; you can redistribute it and/or", + annotate.lines[4].strip + assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", + annotate.revisions[4].identifier + assert_equal "jsmith", annotate.revisions[4].author + end + + def test_annotate_moved_file + annotate = @adapter.annotate('renamed_test.txt') + assert_kind_of Redmine::Scm::Adapters::Annotate, annotate + assert_equal 2, annotate.lines.size + end + + def test_last_rev + last_rev = @adapter.lastrev("README", + "4f26664364207fa8b1af9f8722647ab2d4ac5d43") + assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.scmid + assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.identifier + assert_equal "Adam Soltys ", last_rev.author + assert_equal "2009-06-24 05:27:38".to_time, last_rev.time + end + + def test_last_rev_with_spaces_in_filename + last_rev = @adapter.lastrev("filemane with spaces.txt", + "ed5bb786bbda2dee66a2d50faf51429dbc043a7b") + last_rev_author = last_rev.author + assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.scmid + assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.identifier + assert_equal "#{@str_felix_hex} ", + last_rev.author + assert_equal "2010-09-18 19:59:46".to_time, last_rev.time + end + + def test_latin_1_path + if WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + p2 = "latin-1-dir/test-#{@char_1}-2.txt" + ['4fc55c43bf3d3dc2efb66145365ddc17639ce81e', '4fc55c43bf3'].each do |r1| + assert @adapter.diff(p2, r1) + assert @adapter.cat(p2, r1) + assert_equal 1, @adapter.annotate(p2, r1).lines.length + ['64f1f3e89ad1cb57976ff0ad99a107012ba3481d', '64f1f3e89ad1cb5797'].each do |r2| + assert @adapter.diff(p2, r1, r2) + end + end + end + end + + def test_latin_1_user_annotate + ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', '83ca5fd546063a'].each do |r1| + annotate = @adapter.annotate(" filename with a leading space.txt ", r1) + assert_kind_of Redmine::Scm::Adapters::Annotate, annotate + assert_equal 1, annotate.lines.size + assert_equal "And this is a file with a leading and trailing space...", + annotate.lines[0].strip + assert_equal "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c", + annotate.revisions[0].identifier + assert_equal @str_felix_hex, annotate.revisions[0].author + end + end + + def test_entries_tag + entries1 = @adapter.entries(nil, 'tag01.annotated', + options = {:report_last_commit => true}) + assert entries1 + assert_equal 3, entries1.size + assert_equal 'sources', entries1[1].name + assert_equal 'sources', entries1[1].path + assert_equal 'dir', entries1[1].kind + readme = entries1[2] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 27, readme.size + assert_equal '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', readme.lastrev.identifier + assert_equal Time.gm(2007, 12, 14, 9, 24, 1), readme.lastrev.time + end + + def test_entries_branch + entries1 = @adapter.entries(nil, 'test_branch', + options = {:report_last_commit => true}) + assert entries1 + assert_equal 4, entries1.size + assert_equal 'sources', entries1[1].name + assert_equal 'sources', entries1[1].path + assert_equal 'dir', entries1[1].kind + readme = entries1[2] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 159, readme.size + assert_equal '713f4944648826f558cf548222f813dabe7cbb04', readme.lastrev.identifier + assert_equal Time.gm(2009, 6, 19, 4, 37, 23), readme.lastrev.time + end + + def test_entries_wrong_path_encoding + adpt = Redmine::Scm::Adapters::GitAdapter.new( + REPOSITORY_PATH, + nil, + nil, + nil, + 'EUC-JP' + ) + entries1 = adpt.entries('latin-1-dir', '64f1f3e8') + assert entries1 + assert_equal 3, entries1.size + f1 = entries1[1] + assert_equal nil, f1.name + assert_equal nil, f1.path + assert_equal 'file', f1.kind + end + + def test_entries_latin_1_files + entries1 = @adapter.entries('latin-1-dir', '64f1f3e8') + assert entries1 + assert_equal 3, entries1.size + f1 = entries1[1] + assert_equal "test-#{@char_1}-2.txt", f1.name + assert_equal "latin-1-dir/test-#{@char_1}-2.txt", f1.path + assert_equal 'file', f1.kind + end + + def test_entries_latin_1_dir + if WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + entries1 = @adapter.entries("latin-1-dir/test-#{@char_1}-subdir", + '1ca7f5ed') + assert entries1 + assert_equal 3, entries1.size + f1 = entries1[1] + assert_equal "test-#{@char_1}-2.txt", f1.name + assert_equal "latin-1-dir/test-#{@char_1}-subdir/test-#{@char_1}-2.txt", f1.path + assert_equal 'file', f1.kind + end + end + + def test_entry + entry = @adapter.entry() + assert_equal "", entry.path + assert_equal "dir", entry.kind + entry = @adapter.entry('') + assert_equal "", entry.path + assert_equal "dir", entry.kind + assert_nil @adapter.entry('invalid') + assert_nil @adapter.entry('/invalid') + assert_nil @adapter.entry('/invalid/') + assert_nil @adapter.entry('invalid/invalid') + assert_nil @adapter.entry('invalid/invalid/') + assert_nil @adapter.entry('/invalid/invalid') + assert_nil @adapter.entry('/invalid/invalid/') + ["README", "/README"].each do |path| + entry = @adapter.entry(path, '7234cb2750b63f') + assert_equal "README", entry.path + assert_equal "file", entry.kind + end + ["sources", "/sources", "/sources/"].each do |path| + entry = @adapter.entry(path, '7234cb2750b63f') + assert_equal "sources", entry.path + assert_equal "dir", entry.kind + end + ["sources/watchers_controller.rb", "/sources/watchers_controller.rb"].each do |path| + entry = @adapter.entry(path, '7234cb2750b63f') + assert_equal "sources/watchers_controller.rb", entry.path + assert_equal "file", entry.kind + end + end + + def test_path_encoding_default_utf8 + adpt1 = Redmine::Scm::Adapters::GitAdapter.new( + REPOSITORY_PATH + ) + assert_equal "UTF-8", adpt1.path_encoding + adpt2 = Redmine::Scm::Adapters::GitAdapter.new( + REPOSITORY_PATH, + nil, + nil, + nil, + "" + ) + assert_equal "UTF-8", adpt2.path_encoding + end + + def test_cat_path_invalid + assert_nil @adapter.cat('invalid') + end + + def test_cat_revision_invalid + assert @adapter.cat('README') + assert_nil @adapter.cat('README', '1234abcd5678') + end + + def test_diff_path_invalid + assert_equal [], @adapter.diff('invalid', '713f4944648826f5') + end + + def test_diff_revision_invalid + assert_nil @adapter.diff(nil, '1234abcd5678') + assert_nil @adapter.diff(nil, '713f4944648826f5', '1234abcd5678') + assert_nil @adapter.diff(nil, '1234abcd5678', '713f4944648826f5') + end + + def test_annotate_path_invalid + assert_nil @adapter.annotate('invalid') + end + + def test_annotate_revision_invalid + assert @adapter.annotate('README') + assert_nil @adapter.annotate('README', '1234abcd5678') + end + + private + + def test_scm_version_for(scm_command_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) + assert_equal version, @adapter.class.scm_command_version + end + + else + puts "Git test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end + end + +rescue LoadError + class GitMochaFake < ActiveSupport::TestCase + def test_fake; assert(false, "Requires mocha to run those tests") end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/27/27ce56818e709e285d3d63b2d5d116ba119243bf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/27/27ce56818e709e285d3d63b2d5d116ba119243bf.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,32 @@ +class ChangeChangesetsRevisionToString < ActiveRecord::Migration + def self.up + # Some backends (eg. SQLServer 2012) do not support changing the type + # of an indexed column so the index needs to be dropped first + # BUT this index is renamed with some backends (at least SQLite3) for + # some (unknown) reasons, thus we check for the other name as well + # so we don't end up with 2 identical indexes + if index_exists? :changesets, [:repository_id, :revision], :name => :changesets_repos_rev + remove_index :changesets, :name => :changesets_repos_rev + end + if index_exists? :changesets, [:repository_id, :revision], :name => :altered_changesets_repos_rev + remove_index :changesets, :name => :altered_changesets_repos_rev + end + + change_column :changesets, :revision, :string, :null => false + + add_index :changesets, [:repository_id, :revision], :unique => true, :name => :changesets_repos_rev + end + + def self.down + if index_exists? :changesets, :changesets_repos_rev + remove_index :changesets, :name => :changesets_repos_rev + end + if index_exists? :changesets, [:repository_id, :revision], :name => :altered_changesets_repos_rev + remove_index :changesets, :name => :altered_changesets_repos_rev + end + + change_column :changesets, :revision, :integer, :null => false + + add_index :changesets, [:repository_id, :revision], :unique => true, :name => :changesets_repos_rev + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/28/28460f7863cf0e308a71ddfcfde8f112f06716f0.svn-base --- a/.svn/pristine/28/28460f7863cf0e308a71ddfcfde8f112f06716f0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -module ActiveRecord - module Acts - module Tree - def self.included(base) - base.extend(ClassMethods) - end - - # Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children - # association. This requires that you have a foreign key column, which by default is called +parent_id+. - # - # class Category < ActiveRecord::Base - # acts_as_tree :order => "name" - # end - # - # Example: - # root - # \_ child1 - # \_ subchild1 - # \_ subchild2 - # - # root = Category.create("name" => "root") - # child1 = root.children.create("name" => "child1") - # subchild1 = child1.children.create("name" => "subchild1") - # - # root.parent # => nil - # child1.parent # => root - # root.children # => [child1] - # root.children.first.children.first # => subchild1 - # - # In addition to the parent and children associations, the following instance methods are added to the class - # after calling acts_as_tree: - # * siblings - Returns all the children of the parent, excluding the current node ([subchild2] when called on subchild1) - # * self_and_siblings - Returns all the children of the parent, including the current node ([subchild1, subchild2] when called on subchild1) - # * ancestors - Returns all the ancestors of the current node ([child1, root] when called on subchild2) - # * root - Returns the root of the current node (root when called on subchild2) - module ClassMethods - # Configuration options are: - # - # * foreign_key - specifies the column name to use for tracking of the tree (default: +parent_id+) - # * order - makes it possible to sort the children according to this SQL snippet. - # * counter_cache - keeps a count in a +children_count+ column if set to +true+ (default: +false+). - def acts_as_tree(options = {}) - configuration = { :foreign_key => "parent_id", :dependent => :destroy, :order => nil, :counter_cache => nil } - configuration.update(options) if options.is_a?(Hash) - - belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache] - has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => configuration[:dependent] - - class_eval <<-EOV - include ActiveRecord::Acts::Tree::InstanceMethods - - def self.roots - find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) - end - - def self.root - find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) - end - EOV - end - end - - module InstanceMethods - # Returns list of ancestors, starting from parent until root. - # - # subchild1.ancestors # => [child1, root] - def ancestors - node, nodes = self, [] - nodes << node = node.parent while node.parent - nodes - end - - # Returns list of descendants. - # - # root.descendants # => [child1, subchild1, subchild2] - def descendants(depth=nil) - depth ||= 0 - result = children.dup - unless depth == 1 - result += children.collect {|child| child.descendants(depth-1)}.flatten - end - result - end - - # Returns list of descendants and a reference to the current node. - # - # root.self_and_descendants # => [root, child1, subchild1, subchild2] - def self_and_descendants(depth=nil) - [self] + descendants(depth) - end - - # Returns the root node of the tree. - def root - node = self - node = node.parent while node.parent - node - end - - # Returns all siblings of the current node. - # - # subchild1.siblings # => [subchild2] - def siblings - self_and_siblings - [self] - end - - # Returns all siblings and a reference to the current node. - # - # subchild1.self_and_siblings # => [subchild1, subchild2] - def self_and_siblings - parent ? parent.children : self.class.roots - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/28/284f5f782295ac4c8fe3dbacfe87f5903171e538.svn-base --- a/.svn/pristine/28/284f5f782295ac4c8fe3dbacfe87f5903171e538.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::IssueCategoriesTest < ActionController::IntegrationTest - fixtures :projects, :users, :issue_categories, :issues, - :roles, - :member_roles, - :members, - :enabled_modules - - def setup - Setting.rest_api_enabled = '1' - end - - context "GET /projects/:project_id/issue_categories.xml" do - should "return issue categories" do - get '/projects/1/issue_categories.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'issue_categories', - :child => {:tag => 'issue_category', :child => {:tag => 'id', :content => '2'}} - end - end - - context "GET /issue_categories/2.xml" do - should "return requested issue category" do - get '/issue_categories/2.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'issue_category', - :child => {:tag => 'id', :content => '2'} - end - end - - context "POST /projects/:project_id/issue_categories.xml" do - should "return create issue category" do - assert_difference 'IssueCategory.count' do - post '/projects/1/issue_categories.xml', {:issue_category => {:name => 'API'}}, credentials('jsmith') - end - assert_response :created - assert_equal 'application/xml', @response.content_type - - category = IssueCategory.first(:order => 'id DESC') - assert_equal 'API', category.name - assert_equal 1, category.project_id - end - - context "with invalid parameters" do - should "return errors" do - assert_no_difference 'IssueCategory.count' do - post '/projects/1/issue_categories.xml', {:issue_category => {:name => ''}}, credentials('jsmith') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - - assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} - end - end - end - - context "PUT /issue_categories/2.xml" do - context "with valid parameters" do - should "update issue category" do - assert_no_difference 'IssueCategory.count' do - put '/issue_categories/2.xml', {:issue_category => {:name => 'API Update'}}, credentials('jsmith') - end - assert_response :ok - assert_equal '', @response.body - assert_equal 'API Update', IssueCategory.find(2).name - end - end - - context "with invalid parameters" do - should "return errors" do - assert_no_difference 'IssueCategory.count' do - put '/issue_categories/2.xml', {:issue_category => {:name => ''}}, credentials('jsmith') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - - assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} - end - end - end - - context "DELETE /issue_categories/1.xml" do - should "destroy issue categories" do - assert_difference 'IssueCategory.count', -1 do - delete '/issue_categories/1.xml', {}, credentials('jsmith') - end - assert_response :ok - assert_equal '', @response.body - assert_nil IssueCategory.find_by_id(1) - end - - should "reassign issues with :reassign_to_id param" do - issue_count = Issue.count(:conditions => {:category_id => 1}) - assert issue_count > 0 - - assert_difference 'IssueCategory.count', -1 do - assert_difference 'Issue.count(:conditions => {:category_id => 2})', 3 do - delete '/issue_categories/1.xml', {:reassign_to_id => 2}, credentials('jsmith') - end - end - assert_response :ok - assert_equal '', @response.body - assert_nil IssueCategory.find_by_id(1) - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/28/287ad425b706a1e849a16749d3a013cf82f1d342.svn-base --- a/.svn/pristine/28/287ad425b706a1e849a16749d3a013cf82f1d342.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../../test_helper', __FILE__) - -class Redmine::Utils::DateCalculationTest < ActiveSupport::TestCase - include Redmine::Utils::DateCalculation - - def test_working_days_without_non_working_week_days - with_settings :non_working_week_days => [] do - assert_working_days 18, '2012-10-09', '2012-10-27' - assert_working_days 6, '2012-10-09', '2012-10-15' - assert_working_days 5, '2012-10-09', '2012-10-14' - assert_working_days 3, '2012-10-09', '2012-10-12' - assert_working_days 3, '2012-10-14', '2012-10-17' - assert_working_days 16, '2012-10-14', '2012-10-30' - end - end - - def test_working_days_with_non_working_week_days - with_settings :non_working_week_days => %w(6 7) do - assert_working_days 14, '2012-10-09', '2012-10-27' - assert_working_days 4, '2012-10-09', '2012-10-15' - assert_working_days 4, '2012-10-09', '2012-10-14' - assert_working_days 3, '2012-10-09', '2012-10-12' - assert_working_days 8, '2012-10-09', '2012-10-19' - assert_working_days 8, '2012-10-11', '2012-10-23' - assert_working_days 2, '2012-10-14', '2012-10-17' - assert_working_days 11, '2012-10-14', '2012-10-30' - end - end - - def test_add_working_days_without_non_working_week_days - with_settings :non_working_week_days => [] do - assert_add_working_days '2012-10-10', '2012-10-10', 0 - assert_add_working_days '2012-10-11', '2012-10-10', 1 - assert_add_working_days '2012-10-12', '2012-10-10', 2 - assert_add_working_days '2012-10-13', '2012-10-10', 3 - assert_add_working_days '2012-10-25', '2012-10-10', 15 - end - end - - def test_add_working_days_with_non_working_week_days - with_settings :non_working_week_days => %w(6 7) do - assert_add_working_days '2012-10-10', '2012-10-10', 0 - assert_add_working_days '2012-10-11', '2012-10-10', 1 - assert_add_working_days '2012-10-12', '2012-10-10', 2 - assert_add_working_days '2012-10-15', '2012-10-10', 3 - assert_add_working_days '2012-10-31', '2012-10-10', 15 - assert_add_working_days '2012-10-19', '2012-10-09', 8 - assert_add_working_days '2012-10-23', '2012-10-11', 8 - end - end - - def assert_working_days(expected_days, from, to) - assert_equal expected_days, working_days(from.to_date, to.to_date) - end - - def assert_add_working_days(expected_date, from, working_days) - assert_equal expected_date.to_date, add_working_days(from.to_date, working_days) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/28/287b739e36a269ed72c54dcd10ebdefa5f23d6ee.svn-base --- a/.svn/pristine/28/287b739e36a269ed72c54dcd10ebdefa5f23d6ee.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,173 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'iconv' -require 'net/ldap' -require 'net/ldap/dn' -require 'timeout' - -class AuthSourceLdap < AuthSource - validates_presence_of :host, :port, :attr_login - validates_length_of :name, :host, :maximum => 60, :allow_nil => true - validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => true - validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true - validates_numericality_of :port, :only_integer => true - validates_numericality_of :timeout, :only_integer => true, :allow_blank => true - validate :validate_filter - - before_validation :strip_ldap_attributes - - def initialize(attributes=nil, *args) - super - self.port = 389 if self.port == 0 - end - - def authenticate(login, password) - return nil if login.blank? || password.blank? - - with_timeout do - attrs = get_user_dn(login, password) - if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password) - logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? - return attrs.except(:dn) - end - end - rescue Net::LDAP::LdapError => e - raise AuthSourceException.new(e.message) - end - - # test the connection to the LDAP - def test_connection - with_timeout do - ldap_con = initialize_ldap_con(self.account, self.account_password) - ldap_con.open { } - end - rescue Net::LDAP::LdapError => e - raise AuthSourceException.new(e.message) - end - - def auth_method_name - "LDAP" - end - - private - - def with_timeout(&block) - timeout = self.timeout - timeout = 20 unless timeout && timeout > 0 - Timeout.timeout(timeout) do - return yield - end - rescue Timeout::Error => e - raise AuthSourceTimeoutException.new(e.message) - end - - def ldap_filter - if filter.present? - Net::LDAP::Filter.construct(filter) - end - rescue Net::LDAP::LdapError - nil - end - - def validate_filter - if filter.present? && ldap_filter.nil? - errors.add(:filter, :invalid) - end - end - - def strip_ldap_attributes - [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr| - write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil? - end - end - - def initialize_ldap_con(ldap_user, ldap_password) - options = { :host => self.host, - :port => self.port, - :encryption => (self.tls ? :simple_tls : nil) - } - options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank? - Net::LDAP.new options - end - - def get_user_attributes_from_ldap_entry(entry) - { - :dn => entry.dn, - :firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname), - :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname), - :mail => AuthSourceLdap.get_attr(entry, self.attr_mail), - :auth_source_id => self.id - } - end - - # Return the attributes needed for the LDAP search. It will only - # include the user attributes if on-the-fly registration is enabled - def search_attributes - if onthefly_register? - ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] - else - ['dn'] - end - end - - # Check if a DN (user record) authenticates with the password - def authenticate_dn(dn, password) - if dn.present? && password.present? - initialize_ldap_con(dn, password).bind - end - end - - # Get the user's dn and any attributes for them, given their login - def get_user_dn(login, password) - ldap_con = nil - if self.account && self.account.include?("$login") - ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password) - else - ldap_con = initialize_ldap_con(self.account, self.account_password) - end - login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) - object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) - attrs = {} - - search_filter = object_filter & login_filter - if f = ldap_filter - search_filter = search_filter & f - end - - ldap_con.search( :base => self.base_dn, - :filter => search_filter, - :attributes=> search_attributes) do |entry| - - if onthefly_register? - attrs = get_user_attributes_from_ldap_entry(entry) - else - attrs = {:dn => entry.dn} - end - - logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug? - end - - attrs - end - - def self.get_attr(entry, attr_name) - if !attr_name.blank? - entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/28/28a718992fc9b78318367f1ea258610932c7f8f4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/28/28a718992fc9b78318367f1ea258610932c7f8f4.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,392 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class AccountControllerTest < ActionController::TestCase + fixtures :users, :roles + + def setup + User.current = nil + end + + def test_get_login + get :login + assert_response :success + assert_template 'login' + + assert_select 'input[name=username]' + assert_select 'input[name=password]' + end + + def test_get_login_while_logged_in_should_redirect_to_home + @request.session[:user_id] = 2 + + get :login + assert_redirected_to '/' + assert_equal 2, @request.session[:user_id] + end + + def test_login_should_redirect_to_back_url_param + # request.uri is "test.host" in test environment + back_urls = [ + 'http://test.host/issues/show/1', + '/' + ] + back_urls.each do |back_url| + post :login, :username => 'jsmith', :password => 'jsmith', :back_url => back_url + assert_redirected_to back_url + end + end + + def test_login_with_suburi_should_redirect_to_back_url_param + @relative_url_root = ApplicationController.relative_url_root + ApplicationController.relative_url_root = '/redmine' + + back_urls = [ + 'http://test.host/redmine/issues/show/1', + '/redmine' + ] + back_urls.each do |back_url| + post :login, :username => 'jsmith', :password => 'jsmith', :back_url => back_url + assert_redirected_to back_url + end + ensure + ApplicationController.relative_url_root = @relative_url_root + end + + def test_login_should_not_redirect_to_another_host + back_urls = [ + 'http://test.foo/fake', + '//test.foo/fake' + ] + back_urls.each do |back_url| + post :login, :username => 'jsmith', :password => 'jsmith', :back_url => back_url + assert_redirected_to '/my/page' + end + end + + def test_login_with_suburi_should_not_redirect_to_another_suburi + @relative_url_root = ApplicationController.relative_url_root + ApplicationController.relative_url_root = '/redmine' + + back_urls = [ + 'http://test.host/', + 'http://test.host/fake', + 'http://test.host/fake/issues', + 'http://test.host/redmine/../fake', + 'http://test.host/redmine/../fake/issues', + 'http://test.host/redmine/%2e%2e/fake' + ] + back_urls.each do |back_url| + post :login, :username => 'jsmith', :password => 'jsmith', :back_url => back_url + assert_redirected_to '/my/page' + end + ensure + ApplicationController.relative_url_root = @relative_url_root + end + + def test_login_with_wrong_password + post :login, :username => 'admin', :password => 'bad' + assert_response :success + assert_template 'login' + + assert_select 'div.flash.error', :text => /Invalid user or password/ + assert_select 'input[name=username][value=admin]' + assert_select 'input[name=password]' + assert_select 'input[name=password][value]', 0 + end + + def test_login_with_locked_account_should_fail + User.find(2).update_attribute :status, User::STATUS_LOCKED + + post :login, :username => 'jsmith', :password => 'jsmith' + assert_redirected_to '/login' + assert_include 'locked', flash[:error] + assert_nil @request.session[:user_id] + end + + def test_login_as_registered_user_with_manual_activation_should_inform_user + User.find(2).update_attribute :status, User::STATUS_REGISTERED + + with_settings :self_registration => '2', :default_language => 'en' do + post :login, :username => 'jsmith', :password => 'jsmith' + assert_redirected_to '/login' + assert_include 'pending administrator approval', flash[:error] + end + end + + def test_login_as_registered_user_with_email_activation_should_propose_new_activation_email + User.find(2).update_attribute :status, User::STATUS_REGISTERED + + with_settings :self_registration => '1', :default_language => 'en' do + post :login, :username => 'jsmith', :password => 'jsmith' + assert_redirected_to '/login' + assert_equal 2, @request.session[:registered_user_id] + assert_include 'new activation email', flash[:error] + end + end + + def test_login_should_rescue_auth_source_exception + source = AuthSource.create!(:name => 'Test') + User.find(2).update_attribute :auth_source_id, source.id + AuthSource.any_instance.stubs(:authenticate).raises(AuthSourceException.new("Something wrong")) + + post :login, :username => 'jsmith', :password => 'jsmith' + assert_response 500 + assert_error_tag :content => /Something wrong/ + end + + def test_login_should_reset_session + @controller.expects(:reset_session).once + + post :login, :username => 'jsmith', :password => 'jsmith' + assert_response 302 + end + + def test_get_logout_should_not_logout + @request.session[:user_id] = 2 + get :logout + assert_response :success + assert_template 'logout' + + assert_equal 2, @request.session[:user_id] + end + + def test_get_logout_with_anonymous_should_redirect + get :logout + assert_redirected_to '/' + end + + def test_logout + @request.session[:user_id] = 2 + post :logout + assert_redirected_to '/' + assert_nil @request.session[:user_id] + end + + def test_logout_should_reset_session + @controller.expects(:reset_session).once + + @request.session[:user_id] = 2 + post :logout + assert_response 302 + end + + def test_get_register_with_registration_on + with_settings :self_registration => '3' do + get :register + assert_response :success + assert_template 'register' + assert_not_nil assigns(:user) + + assert_select 'input[name=?]', 'user[password]' + assert_select 'input[name=?]', 'user[password_confirmation]' + end + end + + def test_get_register_should_detect_user_language + with_settings :self_registration => '3' do + @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' + get :register + assert_response :success + assert_not_nil assigns(:user) + assert_equal 'fr', assigns(:user).language + assert_select 'select[name=?]', 'user[language]' do + assert_select 'option[value=fr][selected=selected]' + end + end + end + + def test_get_register_with_registration_off_should_redirect + with_settings :self_registration => '0' do + get :register + assert_redirected_to '/' + end + end + + # See integration/account_test.rb for the full test + def test_post_register_with_registration_on + with_settings :self_registration => '3' do + assert_difference 'User.count' do + post :register, :user => { + :login => 'register', + :password => 'secret123', + :password_confirmation => 'secret123', + :firstname => 'John', + :lastname => 'Doe', + :mail => 'register@example.com' + } + assert_redirected_to '/my/account' + end + user = User.first(:order => 'id DESC') + assert_equal 'register', user.login + assert_equal 'John', user.firstname + assert_equal 'Doe', user.lastname + assert_equal 'register@example.com', user.mail + assert user.check_password?('secret123') + assert user.active? + end + end + + def test_post_register_with_registration_off_should_redirect + with_settings :self_registration => '0' do + assert_no_difference 'User.count' do + post :register, :user => { + :login => 'register', + :password => 'test', + :password_confirmation => 'test', + :firstname => 'John', + :lastname => 'Doe', + :mail => 'register@example.com' + } + assert_redirected_to '/' + end + end + end + + def test_get_lost_password_should_display_lost_password_form + get :lost_password + assert_response :success + assert_select 'input[name=mail]' + end + + def test_lost_password_for_active_user_should_create_a_token + Token.delete_all + ActionMailer::Base.deliveries.clear + assert_difference 'ActionMailer::Base.deliveries.size' do + assert_difference 'Token.count' do + with_settings :host_name => 'mydomain.foo', :protocol => 'http' do + post :lost_password, :mail => 'JSmith@somenet.foo' + assert_redirected_to '/login' + end + end + end + + token = Token.order('id DESC').first + assert_equal User.find(2), token.user + assert_equal 'recovery', token.action + + assert_select_email do + assert_select "a[href=?]", "http://mydomain.foo/account/lost_password?token=#{token.value}" + end + end + + def test_lost_password_for_unknown_user_should_fail + Token.delete_all + assert_no_difference 'Token.count' do + post :lost_password, :mail => 'invalid@somenet.foo' + assert_response :success + end + end + + def test_lost_password_for_non_active_user_should_fail + Token.delete_all + assert User.find(2).lock! + + assert_no_difference 'Token.count' do + post :lost_password, :mail => 'JSmith@somenet.foo' + assert_redirected_to '/account/lost_password' + end + end + + def test_lost_password_for_user_who_cannot_change_password_should_fail + User.any_instance.stubs(:change_password_allowed?).returns(false) + + assert_no_difference 'Token.count' do + post :lost_password, :mail => 'JSmith@somenet.foo' + assert_response :success + end + end + + def test_get_lost_password_with_token_should_display_the_password_recovery_form + user = User.find(2) + token = Token.create!(:action => 'recovery', :user => user) + + get :lost_password, :token => token.value + assert_response :success + assert_template 'password_recovery' + + assert_select 'input[type=hidden][name=token][value=?]', token.value + end + + def test_get_lost_password_with_invalid_token_should_redirect + get :lost_password, :token => "abcdef" + assert_redirected_to '/' + end + + def test_post_lost_password_with_token_should_change_the_user_password + user = User.find(2) + token = Token.create!(:action => 'recovery', :user => user) + + post :lost_password, :token => token.value, :new_password => 'newpass123', :new_password_confirmation => 'newpass123' + assert_redirected_to '/login' + user.reload + assert user.check_password?('newpass123') + assert_nil Token.find_by_id(token.id), "Token was not deleted" + end + + def test_post_lost_password_with_token_for_non_active_user_should_fail + user = User.find(2) + token = Token.create!(:action => 'recovery', :user => user) + user.lock! + + post :lost_password, :token => token.value, :new_password => 'newpass123', :new_password_confirmation => 'newpass123' + assert_redirected_to '/' + assert ! user.check_password?('newpass123') + end + + def test_post_lost_password_with_token_and_password_confirmation_failure_should_redisplay_the_form + user = User.find(2) + token = Token.create!(:action => 'recovery', :user => user) + + post :lost_password, :token => token.value, :new_password => 'newpass', :new_password_confirmation => 'wrongpass' + assert_response :success + assert_template 'password_recovery' + assert_not_nil Token.find_by_id(token.id), "Token was deleted" + + assert_select 'input[type=hidden][name=token][value=?]', token.value + end + + def test_post_lost_password_with_invalid_token_should_redirect + post :lost_password, :token => "abcdef", :new_password => 'newpass', :new_password_confirmation => 'newpass' + assert_redirected_to '/' + end + + def test_activation_email_should_send_an_activation_email + User.find(2).update_attribute :status, User::STATUS_REGISTERED + @request.session[:registered_user_id] = 2 + + with_settings :self_registration => '1' do + assert_difference 'ActionMailer::Base.deliveries.size' do + get :activation_email + assert_redirected_to '/login' + end + end + end + + def test_activation_email_without_session_data_should_fail + User.find(2).update_attribute :status, User::STATUS_REGISTERED + + with_settings :self_registration => '1' do + assert_no_difference 'ActionMailer::Base.deliveries.size' do + get :activation_email + assert_redirected_to '/' + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/28/28c1e15c05f8a765c8f89dc91afdd6d16aa83f7d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/28/28c1e15c05f8a765c8f89dc91afdd6d16aa83f7d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1116 @@ +# Estonian localization for Redmine +# Copyright (C) 2012 Kaitseministeerium +# +# 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. + +et: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d.%m.%Y" + short: "%d.%b" + long: "%d. %B %Y" + + day_names: [Pühapäev, Esmaspäev, Teisipäev, Kolmapäev, Neljapäev, Reede, Laupäev] + abbr_day_names: [P, E, T, K, N, R, L] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Jaanuar, Veebruar, Märts, Aprill, Mai, Juuni, Juuli, August, September, Oktoober, November, Detsember] + abbr_month_names: [~, jaan, veebr, märts, apr, mai, juuni, juuli, aug, sept, okt, nov, dets] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%d.%m.%Y %H:%M" + time: "%H:%M" + short: "%d.%b %H:%M" + long: "%d. %B %Y %H:%M %z" + am: "enne lõunat" + pm: "peale lõunat" + + datetime: + distance_in_words: + half_a_minute: "pool minutit" + less_than_x_seconds: + one: "vähem kui sekund" + other: "vähem kui %{count} sekundit" + x_seconds: + one: "1 sekund" + other: "%{count} sekundit" + less_than_x_minutes: + one: "vähem kui minut" + other: "vähem kui %{count} minutit" + x_minutes: + one: "1 minut" + other: "%{count} minutit" + about_x_hours: + one: "umbes tund" + other: "umbes %{count} tundi" + x_hours: + one: "1 tund" + other: "%{count} tundi" + x_days: + one: "1 päev" + other: "%{count} päeva" + about_x_months: + one: "umbes kuu" + other: "umbes %{count} kuud" + x_months: + one: "1 kuu" + other: "%{count} kuud" + about_x_years: + one: "umbes aasta" + other: "umbes %{count} aastat" + over_x_years: + one: "rohkem kui aasta" + other: "rohkem kui %{count} aastat" + almost_x_years: + one: "peaaegu aasta" + other: "peaaegu %{count} aastat" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "bait" + other: "baiti" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "ja" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 viga ei võimaldanud selle %{model} salvestamist" + other: "%{count} viga ei võimaldanud selle %{model} salvestamist" + messages: + inclusion: "ei ole nimekirjas" + exclusion: "on reserveeritud" + invalid: "ei sobi" + confirmation: "ei lange kinnitusega kokku" + accepted: "peab olema aktsepteeritud" + empty: "ei või olla tühi" + blank: "ei või olla täitmata" + too_long: "on liiga pikk (lubatud on kuni %{count} märki)" + too_short: "on liiga lühike (vaja on vähemalt %{count} märki)" + wrong_length: "on vale pikkusega (peaks olema %{count} märki)" + taken: "on juba võetud" + not_a_number: "ei ole arv" + not_a_date: "ei ole korrektne kuupäev" + greater_than: "peab olema suurem kui %{count}" + greater_than_or_equal_to: "peab olema võrdne või suurem kui %{count}" + equal_to: "peab võrduma %{count}-ga" + less_than: "peab olema väiksem kui %{count}" + less_than_or_equal_to: "peab olema võrdne või väiksem kui %{count}" + odd: "peab olema paaritu arv" + even: "peab olema paarisarv" + greater_than_start_date: "peab olema suurem kui alguskuupäev" + not_same_project: "ei kuulu sama projekti juurde" + circular_dependency: "See suhe looks vastastikuse sõltuvuse" + cant_link_an_issue_with_a_descendant: "Teemat ei saa sidustada tema enda alamteemaga" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: "Palun vali" + + general_text_No: "Ei" + general_text_Yes: "Jah" + general_text_no: "ei" + general_text_yes: "jah" + general_lang_name: "Eesti" + general_csv_separator: "," + general_csv_decimal_separator: "." + general_csv_encoding: ISO-8859-13 + general_pdf_encoding: UTF-8 + general_first_day_of_week: "1" + + notice_account_updated: "Konto uuendamine õnnestus." + notice_account_invalid_creditentials: "Sobimatu kasutajanimi või parool" + notice_account_password_updated: "Parooli uuendamine õnnestus." + notice_account_wrong_password: "Vale parool" + notice_account_register_done: "Konto loomine õnnestus. Konto aktiveerimiseks vajuta vastaval lingil Sulle saadetud e-kirjas." + notice_account_unknown_email: "Tundmatu kasutaja." + notice_can_t_change_password: "See konto kasutab välist autentimisallikat. Siin ei saa selle konto parooli vahetada." + notice_account_lost_email_sent: "Sulle saadeti e-kiri parooli vahetamise juhistega." + notice_account_activated: "Su konto on aktiveeritud. Saad nüüd sisse logida." + notice_successful_create: "Loomine õnnestus." + notice_successful_update: "Uuendamine õnnestus." + notice_successful_delete: "Kustutamine õnnestus." + notice_successful_connection: "Ühenduse loomine õnnestus." + notice_file_not_found: "Sellist lehte ei leitud." + notice_locking_conflict: "Teine kasutaja uuendas vahepeal neid andmeid." + notice_not_authorized: "Sul ei ole sellele lehele ligipääsuks õigusi." + notice_not_authorized_archived_project: "See projekt on arhiveeritud." + notice_email_sent: "%{value}-le saadeti kiri" + notice_email_error: "Kirja saatmisel tekkis viga (%{value})" + notice_feeds_access_key_reseted: "Sinu Atom juurdepääsuvõti nulliti." + notice_api_access_key_reseted: "Sinu API juurdepääsuvõti nulliti." + notice_failed_to_save_issues: "%{count} teemat %{total}-st ei õnnestunud salvestada: %{ids}." + notice_failed_to_save_time_entries: "%{count} ajakulu kannet %{total}-st ei õnnestunud salvestada: %{ids}." + notice_failed_to_save_members: "Liiget/liikmeid ei õnnestunud salvestada: %{errors}." + notice_no_issue_selected: "Ühtegi teemat ei ole valitud! Palun vali teema(d), mida soovid muuta." + notice_account_pending: "Sinu konto on loodud ja ootab nüüd administraatori kinnitust." + notice_default_data_loaded: "Algseadistuste laadimine õnnestus." + notice_unable_delete_version: "Versiooni kustutamine ei õnnestunud." + notice_unable_delete_time_entry: "Ajakulu kande kustutamine ei õnnestunud." + notice_issue_done_ratios_updated: "Teema edenemise astmed on uuendatud." + notice_gantt_chart_truncated: "Diagrammi kärbiti kuna ületati kuvatavate objektide suurim hulk (%{max})" + notice_issue_successful_create: "Teema %{id} loodud." + notice_issue_update_conflict: "Teine kasutaja uuendas seda teemat Sinuga samaaegselt." + notice_account_deleted: "Sinu konto on lõplikult kustutatud." + + error_can_t_load_default_data: "Algseadistusi ei saanud laadida: %{value}" + error_scm_not_found: "Seda sissekannet hoidlast ei leitud." + error_scm_command_failed: "Hoidla poole pöördumisel tekkis viga: %{value}" + error_scm_annotate: "Sissekannet ei eksisteeri või ei saa annoteerida." + error_scm_annotate_big_text_file: "Sissekannet ei saa annoteerida, kuna see on liiga pikk." + error_issue_not_found_in_project: "Teemat ei leitud või see ei kuulu siia projekti" + error_no_tracker_in_project: "Selle projektiga ei ole seostatud ühtegi valdkonda. Palun vaata üle projekti seaded." + error_no_default_issue_status: 'Teema algolek on määramata. Palun vaata asetused üle ("Seadistused -> Olekud").' + error_can_not_delete_custom_field: "Omaloodud välja kustutamine ei õnnestunud" + error_can_not_delete_tracker: "See valdkond on mõnes teemas kasutusel ja seda ei saa kustutada." + error_can_not_remove_role: "See roll on mõnes projektis kasutusel ja seda ei saa kustutada." + error_can_not_reopen_issue_on_closed_version: "Suletud versiooni juurde kuulunud teemat ei saa taasavada" + error_can_not_archive_project: "Seda projekti ei saa arhiveerida" + error_issue_done_ratios_not_updated: "Teema edenemise astmed jäid uuendamata." + error_workflow_copy_source: "Palun vali algne valdkond või roll" + error_workflow_copy_target: "Palun vali sihtvaldkon(na)d või -roll(id)" + error_unable_delete_issue_status: "Oleku kustutamine ei õnnestunud" + error_unable_to_connect: "Ühenduse loomine ei õnnestunud (%{value})" + error_attachment_too_big: "Faili ei saa üles laadida, sest see on lubatust (%{max_size}) pikem" + warning_attachments_not_saved: "%{count} faili salvestamine ei õnnestunud." + + mail_subject_lost_password: "Sinu %{value} parool" + mail_body_lost_password: "Et vahetada oma parooli, vajuta järgmisele lingile:" + mail_subject_register: "Sinu %{value} konto aktiveerimine" + mail_body_register: "Et aktiveerida oma kontot, vajuta järgmisele lingile:" + mail_body_account_information_external: "Sisse logimiseks saad kasutada oma %{value} kontot." + mail_body_account_information: "Sinu konto teave" + mail_subject_account_activation_request: "%{value} konto aktiveerimise nõue" + mail_body_account_activation_request: "Registreerus uus kasutaja (%{value}). Konto avamine ootab Sinu kinnitust:" + mail_subject_reminder: "%{count} teema tähtaeg jõuab kätte järgmise %{days} päeva jooksul" + mail_body_reminder: "%{count} Sulle määratud teema tähtaeg jõuab kätte järgmise %{days} päeva jooksul:" + mail_subject_wiki_content_added: "Lisati '%{id}' vikileht" + mail_body_wiki_content_added: "'%{id}' vikileht lisati %{author} poolt." + mail_subject_wiki_content_updated: "Uuendati '%{id}' vikilehte" + mail_body_wiki_content_updated: "'%{id}' vikilehte uuendati %{author} poolt." + + + field_name: "Nimi" + field_description: "Kirjeldus" + field_summary: "Kokkuvõte" + field_is_required: "Kohustuslik" + field_firstname: "Eesnimi" + field_lastname: "Perekonnanimi" + field_mail: "E-post" + field_filename: "Fail" + field_filesize: "Pikkus" + field_downloads: "Allalaadimist" + field_author: "Autor" + field_created_on: "Loodud" + field_updated_on: "Uuendatud" + field_field_format: "Tüüp" + field_is_for_all: "Kõigile projektidele" + field_possible_values: "Võimalikud väärtused" + field_regexp: "Regulaarne avaldis" + field_min_length: "Vähim pikkus" + field_max_length: "Suurim pikkus" + field_value: "Väärtus" + field_category: "Kategooria" + field_title: "Pealkiri" + field_project: "Projekt" + field_issue: "Teema" + field_status: "Olek" + field_notes: "Märkused" + field_is_closed: "Sulgeb teema" + field_is_default: "Algolek" + field_tracker: "Valdkond" + field_subject: "Teema" + field_due_date: "Tähtaeg" + field_assigned_to: "Tegeleja" + field_priority: "Prioriteet" + field_fixed_version: "Sihtversioon" + field_user: "Kasutaja" + field_principal: "Vastutav isik" + field_role: "Roll" + field_homepage: "Koduleht" + field_is_public: "Avalik" + field_parent: "Emaprojekt" + field_is_in_roadmap: "Teemad on teekaardil näha" + field_login: "Kasutajanimi" + field_mail_notification: "Teated e-kirjaga" + field_admin: "Admin" + field_last_login_on: "Viimane ühendus" + field_language: "Keel" + field_effective_date: "Tähtaeg" + field_password: "Parool" + field_new_password: "Uus parool" + field_password_confirmation: "Kinnitus" + field_version: "Versioon" + field_type: "Tüüp" + field_host: "Server" + field_port: "Port" + field_account: "Konto" + field_base_dn: "Baas DN" + field_attr_login: "Kasutajanime atribuut" + field_attr_firstname: "Eesnime atribuut" + field_attr_lastname: "Perekonnanime atribuut" + field_attr_mail: "E-posti atribuut" + field_onthefly: "Kasutaja automaatne loomine" + field_start_date: "Alguskuupäev" + field_done_ratio: "% tehtud" + field_auth_source: "Autentimise viis" + field_hide_mail: "Peida e-posti aadress" + field_comments: "Kommentaar" + field_url: "URL" + field_start_page: "Esileht" + field_subproject: "Alamprojekt" + field_hours: "tundi" + field_activity: "Tegevus" + field_spent_on: "Kuupäev" + field_identifier: "Tunnus" + field_is_filter: "Kasutatakse filtrina" + field_issue_to: "Seotud teema" + field_delay: "Viivitus" + field_assignable: "Saab määrata teemadega tegelema" + field_redirect_existing_links: "Suuna olemasolevad lingid ringi" + field_estimated_hours: "Eeldatav ajakulu" + field_column_names: "Veerud" + field_time_entries: "Ajakulu" + field_time_zone: "Ajatsoon" + field_searchable: "Otsitav" + field_default_value: "Vaikimisi" + field_comments_sorting: "Kommentaaride järjestus" + field_parent_title: "Pärineb lehest" + field_editable: "Muudetav" + field_watcher: "Jälgija" + field_identity_url: "OpenID URL" + field_content: "Sisu" + field_group_by: "Grupeeri tulemus" + field_sharing: "Teemade jagamine" + field_parent_issue: "Pärineb teemast" + field_member_of_group: "Tegeleja grupp" + field_assigned_to_role: "Tegeleja roll" + field_text: "Tekstiväli" + field_visible: "Nähtav" + field_warn_on_leaving_unsaved: "Hoiata salvestamata sisuga lehtedelt lahkumisel" + field_issues_visibility: "See roll näeb" + field_is_private: "Privaatne" + field_commit_logs_encoding: "Sissekannete kodeering" + field_scm_path_encoding: "Teeraja märkide kodeering" + field_path_to_repository: "Hoidla teerada" + field_root_directory: "Juurkataloog" + field_cvsroot: "CVSROOT" + field_cvs_module: "Moodul" + field_repository_is_default: "Peamine hoidla" + field_multiple: "Korraga mitu väärtust" + field_auth_source_ldap_filter: "LDAP filter" + + setting_app_title: "Veebilehe pealkiri" + setting_app_subtitle: "Veebilehe alampealkiri" + setting_welcome_text: "Tervitustekst" + setting_default_language: "Vaikimisi keel" + setting_login_required: "Autentimine kohustuslik" + setting_self_registration: "Omaloodud konto aktiveerimine" + setting_attachment_max_size: "Manuse suurim pikkus" + setting_issues_export_limit: "Teemade ekspordi limiit" + setting_mail_from: "Saatja e-posti aadress" + setting_bcc_recipients: "Saajaid ei näidata (lähevad BCC reale)" + setting_plain_text_mail: "E-kiri tavalise tekstina (ilma HTML-ta)" + setting_host_name: "Serveri nimi ja teerada" + setting_text_formatting: "Vormindamise abi" + setting_wiki_compression: "Viki ajaloo pakkimine" + setting_feeds_limit: "Atom voogude suurim objektide arv" + setting_default_projects_public: "Uued projektid on vaikimisi avalikud" + setting_autofetch_changesets: "Lae uuendused automaatselt" + setting_sys_api_enabled: "Hoidlate haldamine veebiteenuse kaudu" + setting_commit_ref_keywords: "Viitade võtmesõnad" + setting_commit_fix_keywords: "Paranduste võtmesõnad" + setting_autologin: "Automaatne sisselogimine" + setting_date_format: "Kuupäevaformaat" + setting_time_format: "Ajaformaat" + setting_cross_project_issue_relations: "Luba siduda eri projektide teemasid" + setting_issue_list_default_columns: "Teemade nimekirja vaikimisi veerud" + setting_repositories_encodings: "Manuste ja hoidlate kodeering" + setting_emails_header: "E-kirja päis" + setting_emails_footer: "E-kirja jalus" + setting_protocol: "Protokoll" + setting_per_page_options: "Objekte lehe kohta variandid" + setting_user_format: "Kasutaja nime esitamise vorm" + setting_activity_days_default: "Projektide ajalugu näidatakse" + setting_display_subprojects_issues: "Näita projektis vaikimisi ka alamprojektide teemasid" + setting_enabled_scm: "Kasutatavad lähtekoodi haldusvahendid" + setting_mail_handler_body_delimiters: "Kärbi e-kirja lõpp peale sellist rida" + setting_mail_handler_api_enabled: "E-kirjade vastuvõtt veebiteenuse kaudu" + setting_mail_handler_api_key: "Veebiteenuse API võti" + setting_sequential_project_identifiers: "Genereeri järjestikused projektitunnused" + setting_gravatar_enabled: "Kasuta Gravatari kasutajaikoone" + setting_gravatar_default: "Vaikimisi kasutatav ikoon" + setting_diff_max_lines_displayed: "Enim korraga näidatavaid erinevusi" + setting_file_max_size_displayed: "Kuvatava tekstifaili suurim pikkus" + setting_repository_log_display_limit: "Enim ajaloos näidatavaid sissekandeid" + setting_openid: "Luba OpenID-ga registreerimine ja sisselogimine" + setting_password_min_length: "Lühima lubatud parooli pikkus" + setting_new_project_user_role_id: "Projekti looja roll oma projektis" + setting_default_projects_modules: "Vaikimisi moodulid uutes projektides" + setting_issue_done_ratio: "Määra teema edenemise aste" + setting_issue_done_ratio_issue_field: "kasutades vastavat välja" + setting_issue_done_ratio_issue_status: "kasutades teema olekut" + setting_start_of_week: "Nädala alguspäev" + setting_rest_api_enabled: "Luba REST API kasutamine" + setting_cache_formatted_text: "Puhverda vormindatud teksti" + setting_default_notification_option: "Vaikimisi teavitatakse" + setting_commit_logtime_enabled: "Luba ajakulu sisestamine" + setting_commit_logtime_activity_id: "Tegevus kulunud ajal" + setting_gantt_items_limit: "Gantti diagrammi objektide suurim hulk" + setting_issue_group_assignment: "Luba teemade andmine gruppidele" + setting_default_issue_start_date_to_creation_date: "Uute teemade alguskuupäevaks teema loomise päev" + setting_commit_cross_project_ref: "Luba viiteid ja parandusi ka kõigi teiste projektide teemadele" + setting_unsubscribe: "Luba kasutajal oma konto kustutada" + + permission_add_project: "Projekte luua" + permission_add_subprojects: "Alamprojekte luua" + permission_edit_project: "Projekte muuta" + permission_select_project_modules: "Projektimooduleid valida" + permission_manage_members: "Liikmeid hallata" + permission_manage_project_activities: "Projekti tegevusi hallata" + permission_manage_versions: "Versioone hallata" + permission_manage_categories: "Kategooriaid hallata" + permission_view_issues: "Teemasid näha" + permission_add_issues: "Teemasid lisada" + permission_edit_issues: "Teemasid uuendada" + permission_manage_issue_relations: "Teemade seoseid hallata" + permission_set_issues_private: "Teemasid avalikeks või privaatseiks seada" + permission_set_own_issues_private: "Omi teemasid avalikeks või privaatseiks seada" + permission_add_issue_notes: "Märkusi lisada" + permission_edit_issue_notes: "Märkusi muuta" + permission_edit_own_issue_notes: "Omi märkusi muuta" + permission_move_issues: "Teemasid teise projekti tõsta" + permission_delete_issues: "Teemasid kustutada" + permission_manage_public_queries: "Avalikke päringuid hallata" + permission_save_queries: "Päringuid salvestada" + permission_view_gantt: "Gantti diagramme näha" + permission_view_calendar: "Kalendrit näha" + permission_view_issue_watchers: "Jälgijate nimekirja näha" + permission_add_issue_watchers: "Jälgijaid lisada" + permission_delete_issue_watchers: "Jälgijaid kustutada" + permission_log_time: "Ajakulu sisestada" + permission_view_time_entries: "Ajakulu näha" + permission_edit_time_entries: "Ajakulu kandeid muuta" + permission_edit_own_time_entries: "Omi ajakulu kandeid muuta" + permission_manage_news: "Uudiseid hallata" + permission_comment_news: "Uudiseid kommenteerida" + permission_view_documents: "Dokumente näha" + permission_manage_files: "Faile hallata" + permission_view_files: "Faile näha" + permission_manage_wiki: "Vikit hallata" + permission_rename_wiki_pages: "Vikilehti ümber nimetada" + permission_delete_wiki_pages: "Vikilehti kustutada" + permission_view_wiki_pages: "Vikit näha" + permission_view_wiki_edits: "Viki ajalugu näha" + permission_edit_wiki_pages: "Vikilehti muuta" + permission_delete_wiki_pages_attachments: "Manuseid kustutada" + permission_protect_wiki_pages: "Vikilehti kaitsta" + permission_manage_repository: "Hoidlaid hallata" + permission_browse_repository: "Hoidlaid sirvida" + permission_view_changesets: "Sissekandeid näha" + permission_commit_access: "Sissekandeid teha" + permission_manage_boards: "Foorumeid hallata" + permission_view_messages: "Postitusi näha" + permission_add_messages: "Postitusi lisada" + permission_edit_messages: "Postitusi muuta" + permission_edit_own_messages: "Omi postitusi muuta" + permission_delete_messages: "Postitusi kustutada" + permission_delete_own_messages: "Omi postitusi kustutada" + permission_export_wiki_pages: "Vikilehti eksportida" + permission_manage_subtasks: "Alamteemasid hallata" + permission_manage_related_issues: "Seotud teemasid hallata" + + project_module_issue_tracking: "Teemade jälgimine" + project_module_time_tracking: "Ajakulu arvestus" + project_module_news: "Uudised" + project_module_documents: "Dokumendid" + project_module_files: "Failid" + project_module_wiki: "Viki" + project_module_repository: "Hoidlad" + project_module_boards: "Foorumid" + project_module_calendar: "Kalender" + project_module_gantt: "Gantt" + + label_user: "Kasutaja" + label_user_plural: "Kasutajad" + label_user_new: "Uus kasutaja" + label_user_anonymous: "Anonüümne" + label_project: "Projekt" + label_project_new: "Uus projekt" + label_project_plural: "Projektid" + label_x_projects: + zero: "pole projekte" + one: "1 projekt" + other: "%{count} projekti" + label_project_all: "Kõik projektid" + label_project_latest: "Viimased projektid" + label_issue: "Teema" + label_issue_new: "Uus teema" + label_issue_plural: "Teemad" + label_issue_view_all: "Teemade nimekiri" + label_issues_by: "Teemad %{value} järgi" + label_issue_added: "Teema lisatud" + label_issue_updated: "Teema uuendatud" + label_issue_note_added: "Märkus lisatud" + label_issue_status_updated: "Olek uuendatud" + label_issue_priority_updated: "Prioriteet uuendatud" + label_document: "Dokument" + label_document_new: "Uus dokument" + label_document_plural: "Dokumendid" + label_document_added: "Dokument lisatud" + label_role: "Roll" + label_role_plural: "Rollid" + label_role_new: "Uus roll" + label_role_and_permissions: "Rollid ja õigused" + label_role_anonymous: "Anonüümne" + label_role_non_member: "Mitteliige" + label_member: "Liige" + label_member_new: "Uus liige" + label_member_plural: "Liikmed" + label_tracker: "Valdkond" + label_tracker_plural: "Valdkonnad" + label_tracker_new: "Uus valdkond" + label_workflow: "Töövood" + label_issue_status: "Olek" + label_issue_status_plural: "Olekud" + label_issue_status_new: "Uus olek" + label_issue_category: "Kategooria" + label_issue_category_plural: "Kategooriad" + label_issue_category_new: "Uus kategooria" + label_custom_field: "Omaloodud väli" + label_custom_field_plural: "Omaloodud väljad" + label_custom_field_new: "Uus väli" + label_enumerations: "Loetelud" + label_enumeration_new: "Uus väärtus" + label_information: "Teave" + label_information_plural: "Teave" + label_please_login: "Palun logi sisse" + label_register: "Registreeru" + label_login_with_open_id_option: "või logi sisse OpenID-ga" + label_password_lost: "Kui parool on ununud..." + label_home: "Kodu" + label_my_page: "Oma leht" + label_my_account: "Oma konto" + label_my_projects: "Oma projektid" + label_my_page_block: "Uus blokk" + label_administration: "Seadistused" + label_login: "Logi sisse" + label_logout: "Logi välja" + label_help: "Abi" + label_reported_issues: "Minu poolt lisatud teemad" + label_assigned_to_me_issues: "Minu teha olevad teemad" + label_last_login: "Viimane ühendus" + label_registered_on: "Registreeritud" + label_activity: "Ajalugu" + label_overall_activity: "Üldine tegevuste ajalugu" + label_user_activity: "%{value} tegevuste ajalugu" + label_new: "Uus" + label_logged_as: "Sisse logitud kui" + label_environment: "Keskkond" + label_authentication: "Autentimine" + label_auth_source: "Autentimisallikas" + label_auth_source_new: "Uus autentimisallikas" + label_auth_source_plural: "Autentimisallikad" + label_subproject_plural: "Alamprojektid" + label_subproject_new: "Uus alamprojekt" + label_and_its_subprojects: "%{value} ja selle alamprojektid" + label_min_max_length: "Min.-maks. pikkus" + label_list: "Nimekiri" + label_date: "Kuupäev" + label_integer: "Täisarv" + label_float: "Ujukomaarv" + label_boolean: "Tõeväärtus" + label_string: "Tekst" + label_text: "Pikk tekst" + label_attribute: "Atribuut" + label_attribute_plural: "Atribuudid" + label_no_data: "Pole" + label_change_status: "Muuda olekut" + label_history: "Ajalugu" + label_attachment: "Fail" + label_attachment_new: "Uus fail" + label_attachment_delete: "Kustuta fail" + label_attachment_plural: "Failid" + label_file_added: "Fail lisatud" + label_report: "Aruanne" + label_report_plural: "Aruanded" + label_news: "Uudised" + label_news_new: "Lisa uudis" + label_news_plural: "Uudised" + label_news_latest: "Viimased uudised" + label_news_view_all: "Kõik uudised" + label_news_added: "Uudis lisatud" + label_news_comment_added: "Kommentaar uudisele lisatud" + label_settings: "Seaded" + label_overview: "Ülevaade" + label_version: "Versioon" + label_version_new: "Uus versioon" + label_version_plural: "Versioonid" + label_close_versions: "Sulge lõpetatud versioonid" + label_confirmation: "Kinnitus" + label_export_to: "Samuti saadaval kujul:" + label_read: "Loe..." + label_public_projects: "Avalikud projektid" + label_open_issues: "avatud" + label_open_issues_plural: "avatud" + label_closed_issues: "suletud" + label_closed_issues_plural: "suletud" + label_x_open_issues_abbr_on_total: + zero: "0 avatud / %{total}" + one: "1 avatud / %{total}" + other: "%{count} avatud / %{total}" + label_x_open_issues_abbr: + zero: "0 avatud" + one: "1 avatud" + other: "%{count} avatud" + label_x_closed_issues_abbr: + zero: "0 suletud" + one: "1 suletud" + other: "%{count} suletud" + label_x_issues: + zero: "0 teemat" + one: "1 teema" + other: "%{count} teemat" + label_total: "Kokku" + label_permissions: "Õigused" + label_current_status: "Praegune olek" + label_new_statuses_allowed: "Uued lubatud olekud" + label_all: "kõik" + label_none: "pole" + label_nobody: "eikeegi" + label_next: "Järgmine" + label_previous: "Eelmine" + label_used_by: "Kasutab" + label_details: "Üksikasjad" + label_add_note: "Lisa märkus" + label_per_page: "Lehe kohta" + label_calendar: "Kalender" + label_months_from: "kuu kaugusel" + label_gantt: "Gantt" + label_internal: "Sisemine" + label_last_changes: "viimased %{count} muudatust" + label_change_view_all: "Kõik muudatused" + label_personalize_page: "Kujunda leht ümber" + label_comment: "Kommentaar" + label_comment_plural: "Kommentaarid" + label_x_comments: + zero: "kommentaare pole" + one: "1 kommentaar" + other: "%{count} kommentaari" + label_comment_add: "Lisa kommentaar" + label_comment_added: "Kommentaar lisatud" + label_comment_delete: "Kustuta kommentaar" + label_query: "Omaloodud päring" + label_query_plural: "Omaloodud päringud" + label_query_new: "Uus päring" + label_my_queries: "Mu omaloodud päringud" + label_filter_add: "Lisa filter" + label_filter_plural: "Filtrid" + label_equals: "on" + label_not_equals: "ei ole" + label_in_less_than: "on väiksem kui" + label_in_more_than: "on suurem kui" + label_greater_or_equal: "suurem-võrdne" + label_less_or_equal: "väiksem-võrdne" + label_between: "vahemikus" + label_in: "sisaldub hulgas" + label_today: "täna" + label_all_time: "piirideta" + label_yesterday: "eile" + label_this_week: "sel nädalal" + label_last_week: "eelmisel nädalal" + label_last_n_days: "viimase %{count} päeva jooksul" + label_this_month: "sel kuul" + label_last_month: "eelmisel kuul" + label_this_year: "sel aastal" + label_date_range: "Kuupäevavahemik" + label_less_than_ago: "uuem kui" + label_more_than_ago: "vanem kui" + label_ago: "vanus" + label_contains: "sisaldab" + label_not_contains: "ei sisalda" + label_day_plural: "päeva" + label_repository: "Hoidla" + label_repository_new: "Uus hoidla" + label_repository_plural: "Hoidlad" + label_browse: "Sirvi" + label_branch: "Haru" + label_tag: "Sildiga" + label_revision: "Sissekanne" + label_revision_plural: "Sissekanded" + label_revision_id: "Sissekande kood %{value}" + label_associated_revisions: "Seotud sissekanded" + label_added: "lisatud" + label_modified: "muudetud" + label_copied: "kopeeritud" + label_renamed: "ümber nimetatud" + label_deleted: "kustutatud" + label_latest_revision: "Viimane sissekanne" + label_latest_revision_plural: "Viimased sissekanded" + label_view_revisions: "Haru ajalugu" + label_view_all_revisions: "Kogu ajalugu" + label_max_size: "Suurim pikkus" + label_sort_highest: "Nihuta esimeseks" + label_sort_higher: "Nihuta üles" + label_sort_lower: "Nihuta alla" + label_sort_lowest: "Nihuta viimaseks" + label_roadmap: "Teekaart" + label_roadmap_due_in: "Tähtaeg %{value}" + label_roadmap_overdue: "%{value} hiljaks jäänud" + label_roadmap_no_issues: "Selles versioonis ei ole teemasid" + label_search: "Otsi" + label_result_plural: "Tulemused" + label_all_words: "Kõik sõnad" + label_wiki: "Viki" + label_wiki_edit: "Viki muutmine" + label_wiki_edit_plural: "Viki muutmised" + label_wiki_page: "Vikileht" + label_wiki_page_plural: "Vikilehed" + label_index_by_title: "Järjesta pealkirja järgi" + label_index_by_date: "Järjesta kuupäeva järgi" + label_current_version: "Praegune versioon" + label_preview: "Eelvaade" + label_feed_plural: "Vood" + label_changes_details: "Kõigi muudatuste üksikasjad" + label_issue_tracking: "Teemade jälgimine" + label_spent_time: "Kulutatud aeg" + label_overall_spent_time: "Kokku kulutatud aeg" + label_f_hour: "%{value} tund" + label_f_hour_plural: "%{value} tundi" + label_time_tracking: "Ajakulu arvestus" + label_change_plural: "Muudatused" + label_statistics: "Statistika" + label_commits_per_month: "Sissekandeid kuu kohta" + label_commits_per_author: "Sissekandeid autori kohta" + label_diff: "erinevused" + label_view_diff: "Vaata erinevusi" + label_diff_inline: "teksti sees" + label_diff_side_by_side: "kõrvuti" + label_options: "Valikud" + label_copy_workflow_from: "Kopeeri see töövoog" + label_permissions_report: "Õiguste aruanne" + label_watched_issues: "Jälgitud teemad" + label_related_issues: "Seotud teemad" + label_applied_status: "Kehtestatud olek" + label_loading: "Laadimas..." + label_relation_new: "Uus seos" + label_relation_delete: "Kustuta seos" + label_relates_to: "seostub" + label_duplicates: "duplitseerib" + label_duplicated_by: "duplitseerija" + label_blocks: "blokeerib" + label_blocked_by: "blokeerija" + label_precedes: "eelneb" + label_follows: "järgneb" + label_end_to_start: "lõpust alguseni" + label_end_to_end: "lõpust lõpuni" + label_start_to_start: "algusest alguseni" + label_start_to_end: "algusest lõpuni" + label_stay_logged_in: "Püsi sisselogituna" + label_disabled: "pole võimalik" + label_show_completed_versions: "Näita lõpetatud versioone" + label_me: "mina" + label_board: "Foorum" + label_board_new: "Uus foorum" + label_board_plural: "Foorumid" + label_board_locked: "Lukus" + label_board_sticky: "Püsiteema" + label_topic_plural: "Teemad" + label_message_plural: "Postitused" + label_message_last: "Viimane postitus" + label_message_new: "Uus postitus" + label_message_posted: "Postitus lisatud" + label_reply_plural: "Vastused" + label_send_information: "Saada teave konto kasutajale" + label_year: "Aasta" + label_month: "Kuu" + label_week: "Nädal" + label_date_from: "Alates" + label_date_to: "Kuni" + label_language_based: "Kasutaja keele põhjal" + label_sort_by: "Sorteeri %{value} järgi" + label_send_test_email: "Saada kontrollkiri" + label_feeds_access_key: "Atom juurdepääsuvõti" + label_missing_feeds_access_key: "Atom juurdepääsuvõti on puudu" + label_feeds_access_key_created_on: "Atom juurdepääsuvõti loodi %{value} tagasi" + label_module_plural: "Moodulid" + label_added_time_by: "Lisatud %{author} poolt %{age} tagasi" + label_updated_time_by: "Uuendatud %{author} poolt %{age} tagasi" + label_updated_time: "Uuendatud %{value} tagasi" + label_jump_to_a_project: "Ava projekt..." + label_file_plural: "Failid" + label_changeset_plural: "Muudatused" + label_default_columns: "Vaikimisi veerud" + label_no_change_option: "(Ei muutu)" + label_bulk_edit_selected_issues: "Muuda valitud teemasid korraga" + label_bulk_edit_selected_time_entries: "Muuda valitud ajakandeid korraga" + label_theme: "Visuaalne teema" + label_default: "Tavaline" + label_search_titles_only: "Ainult pealkirjadest" + label_user_mail_option_all: "Kõigist tegevustest kõigis mu projektides" + label_user_mail_option_selected: "Kõigist tegevustest ainult valitud projektides..." + label_user_mail_option_none: "Teavitusi ei saadeta" + label_user_mail_option_only_my_events: "Ainult mu jälgitavatest või minuga seotud tegevustest" + label_user_mail_option_only_assigned: "Ainult minu teha olevate asjade kohta" + label_user_mail_option_only_owner: "Ainult mu oma asjade kohta" + label_user_mail_no_self_notified: "Ära teavita mind mu enda tehtud muudatustest" + label_registration_activation_by_email: "e-kirjaga" + label_registration_manual_activation: "käsitsi" + label_registration_automatic_activation: "automaatselt" + label_display_per_page: "Lehe kohta: %{value}" + label_age: "Vanus" + label_change_properties: "Muuda omadusi" + label_general: "Üldine" + label_more: "Rohkem" + label_scm: "Lähtekoodi haldusvahend" + label_plugins: "Lisamoodulid" + label_ldap_authentication: "LDAP autentimine" + label_downloads_abbr: "A/L" + label_optional_description: "Teave" + label_add_another_file: "Lisa veel üks fail" + label_preferences: "Eelistused" + label_chronological_order: "kronoloogiline" + label_reverse_chronological_order: "tagurpidi kronoloogiline" + label_planning: "Planeerimine" + label_incoming_emails: "Sissetulevad e-kirjad" + label_generate_key: "Genereeri võti" + label_issue_watchers: "Jälgijad" + label_example: "Näide" + label_display: "Kujundus" + label_sort: "Sorteeri" + label_ascending: "Kasvavalt" + label_descending: "Kahanevalt" + label_date_from_to: "Alates %{start} kuni %{end}" + label_wiki_content_added: "Vikileht lisatud" + label_wiki_content_updated: "Vikileht uuendatud" + label_group: "Grupp" + label_group_plural: "Grupid" + label_group_new: "Uus grupp" + label_time_entry_plural: "Kulutatud aeg" + label_version_sharing_none: "ei toimu" + label_version_sharing_descendants: "alamprojektidega" + label_version_sharing_hierarchy: "projektihierarhiaga" + label_version_sharing_tree: "projektipuuga" + label_version_sharing_system: "kõigi projektidega" + label_update_issue_done_ratios: "Uuenda edenemise astmeid" + label_copy_source: "Allikas" + label_copy_target: "Sihtkoht" + label_copy_same_as_target: "Sama mis sihtkoht" + label_display_used_statuses_only: "Näita ainult selles valdkonnas kasutusel olekuid" + label_api_access_key: "API juurdepääsuvõti" + label_missing_api_access_key: "API juurdepääsuvõti on puudu" + label_api_access_key_created_on: "API juurdepääsuvõti loodi %{value} tagasi" + label_profile: "Profiil" + label_subtask_plural: "Alamteemad" + label_project_copy_notifications: "Saada projekti kopeerimise kohta teavituskiri" + label_principal_search: "Otsi kasutajat või gruppi:" + label_user_search: "Otsi kasutajat:" + label_additional_workflow_transitions_for_author: "Luba ka järgmisi üleminekuid kui kasutaja on teema looja" + label_additional_workflow_transitions_for_assignee: "Luba ka järgmisi üleminekuid kui kasutaja on teemaga tegeleja" + label_issues_visibility_all: "kõiki teemasid" + label_issues_visibility_public: "kõiki mitteprivaatseid teemasid" + label_issues_visibility_own: "enda poolt loodud või enda teha teemasid" + label_git_report_last_commit: "Viimase sissekande teave otse failinimekirja" + label_parent_revision: "Eellane" + label_child_revision: "Järglane" + label_export_options: "%{export_format} ekspordivalikud" + label_copy_attachments: "Kopeeri manused" + label_item_position: "%{position}/%{count}" + label_completed_versions: "Lõpetatud versioonid" + label_search_for_watchers: "Otsi lisamiseks jälgijaid" + + button_login: "Logi sisse" + button_submit: "Sisesta" + button_save: "Salvesta" + button_check_all: "Märgi kõik" + button_uncheck_all: "Nulli valik" + button_collapse_all: "Voldi kõik kokku" + button_expand_all: "Voldi kõik lahti" + button_delete: "Kustuta" + button_create: "Loo" + button_create_and_continue: "Loo ja jätka" + button_test: "Testi" + button_edit: "Muuda" + button_edit_associated_wikipage: "Muuda seotud vikilehte: %{page_title}" + button_add: "Lisa" + button_change: "Muuda" + button_apply: "Lae" + button_clear: "Puhasta" + button_lock: "Lukusta" + button_unlock: "Ava lukust" + button_download: "Lae alla" + button_list: "Listi" + button_view: "Vaata" + button_move: "Tõsta" + button_move_and_follow: "Tõsta ja järgne" + button_back: "Tagasi" + button_cancel: "Katkesta" + button_activate: "Aktiveeri" + button_sort: "Sorteeri" + button_log_time: "Ajakulu" + button_rollback: "Rulli tagasi sellesse versiooni" + button_watch: "Jälgi" + button_unwatch: "Ära jälgi" + button_reply: "Vasta" + button_archive: "Arhiveeri" + button_unarchive: "Arhiivist tagasi" + button_reset: "Nulli" + button_rename: "Nimeta ümber" + button_change_password: "Vaheta parool" + button_copy: "Kopeeri" + button_copy_and_follow: "Kopeeri ja järgne" + button_annotate: "Annoteeri" + button_update: "Muuda" + button_configure: "Konfigureeri" + button_quote: "Tsiteeri" + button_duplicate: "Duplitseeri" + button_show: "Näita" + button_edit_section: "Muuda seda sektsiooni" + button_export: "Ekspordi" + button_delete_my_account: "Kustuta oma konto" + + status_active: "aktiivne" + status_registered: "registreeritud" + status_locked: "lukus" + + version_status_open: "avatud" + version_status_locked: "lukus" + version_status_closed: "suletud" + + field_active: "Aktiivne" + + text_select_mail_notifications: "Tegevused, millest peaks e-kirjaga teavitama" + text_regexp_info: "nt. ^[A-Z0-9]+$" + text_min_max_length_info: "0 tähendab, et piiranguid ei ole" + text_project_destroy_confirmation: "Oled Sa kindel oma soovis see projekt täielikult kustutada?" + text_subprojects_destroy_warning: "Alamprojekt(id) - %{value} - kustutatakse samuti." + text_workflow_edit: "Töövoo muutmiseks vali roll ja valdkond" + text_are_you_sure: "Oled Sa kindel?" + text_journal_changed: "%{label} muudetud %{old} -> %{new}" + text_journal_changed_no_detail: "%{label} uuendatud" + text_journal_set_to: "%{label} uus väärtus on %{value}" + text_journal_deleted: "%{label} kustutatud (%{old})" + text_journal_added: "%{label} %{value} lisatud" + text_tip_issue_begin_day: "teema avamise päev" + text_tip_issue_end_day: "teema sulgemise päev" + text_tip_issue_begin_end_day: "teema avati ja sulgeti samal päeval" + text_project_identifier_info: "Lubatud on ainult väikesed tähed (a-z), numbrid ja kriipsud.
    Peale salvestamist ei saa tunnust enam muuta." + text_caracters_maximum: "%{count} märki kõige rohkem." + text_caracters_minimum: "Peab olema vähemalt %{count} märki pikk." + text_length_between: "Pikkus %{min} kuni %{max} märki." + text_tracker_no_workflow: "Selle valdkonna jaoks ei ole ühtegi töövoogu kirjeldatud" + text_unallowed_characters: "Lubamatud märgid" + text_comma_separated: "Lubatud erinevad väärtused (komaga eraldatult)." + text_line_separated: "Lubatud erinevad väärtused (igaüks eraldi real)." + text_issues_ref_in_commit_messages: "Teemadele ja parandustele viitamine sissekannete märkustes" + text_issue_added: "%{author} lisas uue teema %{id}." + text_issue_updated: "%{author} uuendas teemat %{id}." + text_wiki_destroy_confirmation: "Oled Sa kindel oma soovis kustutada see Viki koos kogu sisuga?" + text_issue_category_destroy_question: "Kustutatavat kategooriat kasutab %{count} teema(t). Mis Sa soovid nendega ette võtta?" + text_issue_category_destroy_assignments: "Jäta teemadel kategooria määramata" + text_issue_category_reassign_to: "Määra teemad teise kategooriasse" + text_user_mail_option: "Valimata projektidest saad teavitusi ainult jälgitavate või Sinuga seotud asjade kohta (nt. Sinu loodud või teha teemad)." + text_no_configuration_data: "Rollid, valdkonnad, olekud ja töövood ei ole veel seadistatud.\nVäga soovitav on laadida vaikeasetused. Peale laadimist saad neid ise muuta." + text_load_default_configuration: "Lae vaikeasetused" + text_status_changed_by_changeset: "Kehtestatakse muudatuses %{value}." + text_time_logged_by_changeset: "Kehtestatakse muudatuses %{value}." + text_issues_destroy_confirmation: "Oled Sa kindel oma soovis valitud teema(d) kustutada?" + text_issues_destroy_descendants_confirmation: "See kustutab samuti %{count} alamteemat." + text_time_entries_destroy_confirmation: "Oled Sa kindel oma soovis valitud ajakulu kanne/kanded kustutada?" + text_select_project_modules: "Projektis kasutatavad moodulid" + text_default_administrator_account_changed: "Algne administraatori konto on muudetud" + text_file_repository_writable: "Manuste kataloog on kirjutatav" + text_plugin_assets_writable: "Lisamoodulite abifailide kataloog on kirjutatav" + text_rmagick_available: "RMagick on kasutatav (mittekohustuslik)" + text_destroy_time_entries_question: "Kustutatavatele teemadele oli kirja pandud %{hours} tundi. Mis Sa soovid ette võtta?" + text_destroy_time_entries: "Kustuta need tunnid" + text_assign_time_entries_to_project: "Vii tunnid üle teise projekti" + text_reassign_time_entries: "Määra tunnid sellele teemale:" + text_user_wrote: "%{value} kirjutas:" + text_enumeration_destroy_question: "Selle väärtusega on seotud %{count} objekt(i)." + text_enumeration_category_reassign_to: "Seo nad teise väärtuse külge:" + text_email_delivery_not_configured: "E-kirjade saatmine ei ole seadistatud ja teavitusi ei saadeta.\nKonfigureeri oma SMTP server failis config/configuration.yml ja taaskäivita Redmine." + text_repository_usernames_mapping: "Seosta Redmine kasutaja hoidlasse sissekannete tegijaga.\nSama nime või e-postiga kasutajad seostatakse automaatselt." + text_diff_truncated: "... Osa erinevusi jäi välja, sest neid on näitamiseks liiga palju." + text_custom_field_possible_values_info: "Üks rida iga väärtuse jaoks" + text_wiki_page_destroy_question: "Sel lehel on %{descendants} järglasleht(e) ja järeltulija(t). Mis Sa soovid ette võtta?" + text_wiki_page_nullify_children: "Muuda järglaslehed uuteks juurlehtedeks" + text_wiki_page_destroy_children: "Kustuta järglaslehed ja kõik nende järglased" + text_wiki_page_reassign_children: "Määra järglaslehed teise lehe külge" + text_own_membership_delete_confirmation: "Sa võtad endalt ära osa või kõik õigused ega saa edaspidi seda projekti võib-olla enam muuta.\nOled Sa jätkamises kindel?" + text_zoom_in: "Vaata lähemalt" + text_zoom_out: "Vaata kaugemalt" + text_warn_on_leaving_unsaved: "Sel lehel on salvestamata teksti, mis läheb kaduma, kui siit lehelt lahkud." + text_scm_path_encoding_note: "Vaikimisi UTF-8" + text_git_repository_note: "Hoidla peab olema paljas (bare) ja kohalik (nt. /gitrepo, c:\\gitrepo)" + text_mercurial_repository_note: "Hoidla peab olema kohalik (nt. /hgrepo, c:\\hgrepo)" + text_scm_command: "Hoidla poole pöördumise käsk" + text_scm_command_version: "Versioon" + text_scm_config: "Hoidlate poole pöördumist saab konfigureerida failis config/configuration.yml. Peale selle muutmist taaskäivita Redmine." + text_scm_command_not_available: "Hoidla poole pöördumine ebaõnnestus. Palun kontrolli seadistusi." + text_issue_conflict_resolution_overwrite: "Kehtesta oma muudatused (kõik märkused jäävad, ent muu võidakse üle kirjutada)" + text_issue_conflict_resolution_add_notes: "Lisa oma märkused, aga loobu teistest muudatustest" + text_issue_conflict_resolution_cancel: "Loobu kõigist muudatustest ja lae %{link} uuesti" + text_account_destroy_confirmation: "Oled Sa kindel?\nSu konto kustutatakse jäädavalt ja seda pole võimalik taastada." + + default_role_manager: "Haldaja" + default_role_developer: "Arendaja" + default_role_reporter: "Edastaja" + default_tracker_bug: "Veaparandus" + default_tracker_feature: "Täiendus" + default_tracker_support: "Klienditugi" + default_issue_status_new: "Avatud" + default_issue_status_in_progress: "Töös" + default_issue_status_resolved: "Lahendatud" + default_issue_status_feedback: "Tagasiside" + default_issue_status_closed: "Suletud" + default_issue_status_rejected: "Tagasi lükatud" + default_doc_category_user: "Juhend lõppkasutajale" + default_doc_category_tech: "Tehniline dokumentatsioon" + default_priority_low: "Aega on" + default_priority_normal: "Tavaline" + default_priority_high: "Pakiline" + default_priority_urgent: "Täna vaja" + default_priority_immediate: "Kohe vaja" + default_activity_design: "Kavandamine" + default_activity_development: "Arendamine" + + enumeration_issue_priorities: "Teemade prioriteedid" + enumeration_doc_categories: "Dokumentide kategooriad" + enumeration_activities: "Tegevused (ajakulu)" + enumeration_system_activity: "Süsteemi aktiivsus" + description_filter: "Filter" + description_search: "Otsinguväli" + description_choose_project: "Projektid" + description_project_scope: "Otsingu ulatus" + description_notes: "Märkused" + description_message_content: "Postituse sisu" + description_query_sort_criteria_attribute: "Sorteerimise kriteerium" + description_query_sort_criteria_direction: "Sorteerimise suund" + description_user_mail_notification: "E-kirjaga teavitamise seaded" + description_available_columns: "Kasutatavad veerud" + description_selected_columns: "Valitud veerud" + description_all_columns: "Kõik veerud" + description_issue_category_reassign: "Vali uus kategooria" + description_wiki_subpages_reassign: "Vali lehele uus vanem" + description_date_range_list: "Vali vahemik nimekirjast" + description_date_range_interval: "Vali vahemik algus- ja lõpukuupäeva abil" + description_date_from: "Sisesta alguskuupäev" + description_date_to: "Sisesta lõpukuupäev" + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: "Lubatud on ainult väikesed tähed (a-z), numbrid ja kriipsud.
    Peale salvestamist ei saa tunnust enam muuta." + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: "kõik" + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: "alamprojektidega" + label_cross_project_tree: "projektipuuga" + label_cross_project_hierarchy: "projektihierarhiaga" + label_cross_project_system: "kõigi projektidega" + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: "Kokku" + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/29/293d0a71c3ab0c1ab7c165e236d1eff8e57cb305.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/29/293d0a71c3ab0c1ab7c165e236d1eff8e57cb305.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,122 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class IssueCategoriesController < ApplicationController + menu_item :settings + model_object IssueCategory + before_filter :find_model_object, :except => [:index, :new, :create] + before_filter :find_project_from_association, :except => [:index, :new, :create] + before_filter :find_project_by_project_id, :only => [:index, :new, :create] + before_filter :authorize + accept_api_auth :index, :show, :create, :update, :destroy + + def index + respond_to do |format| + format.html { redirect_to_settings_in_projects } + format.api { @categories = @project.issue_categories.all } + end + end + + def show + respond_to do |format| + format.html { redirect_to_settings_in_projects } + format.api + end + end + + def new + @category = @project.issue_categories.build + @category.safe_attributes = params[:issue_category] + + respond_to do |format| + format.html + format.js + end + end + + def create + @category = @project.issue_categories.build + @category.safe_attributes = params[:issue_category] + if @category.save + respond_to do |format| + format.html do + flash[:notice] = l(:notice_successful_create) + redirect_to_settings_in_projects + end + format.js + format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) } + end + else + respond_to do |format| + format.html { render :action => 'new'} + format.js { render :action => 'new'} + format.api { render_validation_errors(@category) } + end + end + end + + def edit + end + + def update + @category.safe_attributes = params[:issue_category] + if @category.save + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_to_settings_in_projects + } + format.api { render_api_ok } + end + else + respond_to do |format| + format.html { render :action => 'edit' } + format.api { render_validation_errors(@category) } + end + end + end + + def destroy + @issue_count = @category.issues.size + if @issue_count == 0 || params[:todo] || api_request? + reassign_to = nil + if params[:reassign_to_id] && (params[:todo] == 'reassign' || params[:todo].blank?) + reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) + end + @category.destroy(reassign_to) + respond_to do |format| + format.html { redirect_to_settings_in_projects } + format.api { render_api_ok } + end + return + end + @categories = @project.issue_categories - [@category] + end + + private + + def redirect_to_settings_in_projects + redirect_to settings_project_path(@project, :tab => 'categories') + end + + # Wrap ApplicationController's find_model_object method to set + # @category instead of just @issue_category + def find_model_object + super + @category = @object + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/29/298be127abf8c78d6ccda6e13729844112a36c53.svn-base --- a/.svn/pristine/29/298be127abf8c78d6ccda6e13729844112a36c53.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Journal < ActiveRecord::Base - belongs_to :journalized, :polymorphic => true - # added as a quick fix to allow eager loading of the polymorphic association - # since always associated to an issue, for now - belongs_to :issue, :foreign_key => :journalized_id - - belongs_to :user - has_many :details, :class_name => "JournalDetail", :dependent => :delete_all - attr_accessor :indice - - acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" }, - :description => :notes, - :author => :user, - :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' }, - :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} - - acts_as_activity_provider :type => 'issues', - :author_key => :user_id, - :find_options => {:include => [{:issue => :project}, :details, :user], - :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + - " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} - - before_create :split_private_notes - - scope :visible, lambda {|*args| - user = args.shift || User.current - - includes(:issue => :project). - where(Issue.visible_condition(user, *args)). - where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false) - } - - def save(*args) - # Do not save an empty journal - (details.empty? && notes.blank?) ? false : super - end - - # Returns the new status if the journal contains a status change, otherwise nil - def new_status - c = details.detect {|detail| detail.prop_key == 'status_id'} - (c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil - end - - def new_value_for(prop) - c = details.detect {|detail| detail.prop_key == prop} - c ? c.value : nil - end - - def editable_by?(usr) - usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project))) - end - - def project - journalized.respond_to?(:project) ? journalized.project : nil - end - - def attachments - journalized.respond_to?(:attachments) ? journalized.attachments : nil - end - - # Returns a string of css classes - def css_classes - s = 'journal' - s << ' has-notes' unless notes.blank? - s << ' has-details' unless details.blank? - s << ' private-notes' if private_notes? - s - end - - def notify? - @notify != false - end - - def notify=(arg) - @notify = arg - end - - def recipients - notified = journalized.notified_users - if private_notes? - notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)} - end - notified.map(&:mail) - end - - def watcher_recipients - notified = journalized.notified_watchers - if private_notes? - notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)} - end - notified.map(&:mail) - end - - private - - def split_private_notes - if private_notes? - if notes.present? - if details.any? - # Split the journal (notes/changes) so we don't have half-private journals - journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false) - journal.details = details - journal.save - self.details = [] - self.created_on = journal.created_on - end - else - # Blank notes should not be private - self.private_notes = false - end - end - true - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/29/29a1561941007c06e8cd16ad09f34224b899351f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/29/29a1561941007c06e8cd16ad09f34224b899351f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,56 @@ +<%= render :partial => 'action_menu' %> + +<%= title l(:label_workflow) %> + +
    +
      +
    • <%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %>
    • +
    • <%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker} %>
    • +
    +
    + +

    <%=l(:text_workflow_edit)%>:

    + +<%= form_tag({}, :method => 'get') do %> +

    + + + + + <%= submit_tag l(:button_edit), :name => nil %> + + <%= hidden_field_tag 'used_statuses_only', '0' %> + + +

    +<% end %> + +<% if @tracker && @role && @statuses.any? %> + <%= form_tag({}, :id => 'workflow_form' ) do %> + <%= hidden_field_tag 'tracker_id', @tracker.id %> + <%= hidden_field_tag 'role_id', @role.id %> + <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %> +
    + <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %> + +
    + <%= l(:label_additional_workflow_transitions_for_author) %> +
    + <%= render :partial => 'form', :locals => {:name => 'author', :workflows => @workflows['author']} %> +
    +
    + <%= javascript_tag "hideFieldset($('#author_workflows'))" unless @workflows['author'].present? %> + +
    + <%= l(:label_additional_workflow_transitions_for_assignee) %> +
    + <%= render :partial => 'form', :locals => {:name => 'assignee', :workflows => @workflows['assignee']} %> +
    +
    + <%= javascript_tag "hideFieldset($('#assignee_workflows'))" unless @workflows['assignee'].present? %> +
    + <%= submit_tag l(:button_save) %> + <% end %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/29/29dc17e6511b552f9bb4332f96e34e10ad2b4a77.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/29/29dc17e6511b552f9bb4332f96e34e10ad2b4a77.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,36 @@ +<% if @project.boards.any? %> + + + + + + + + +<% Board.board_tree(@project.boards) do |board, level| + next if board.new_record? %> + + + + + + +<% end %> + +
    <%= l(:label_board) %><%= l(:field_description) %>
    <%= link_to board.name, project_board_path(@project, board) %><%=h board.description %> + <% if authorize_for("boards", "edit") %> + <%= reorder_links('board', {:controller => 'boards', :action => 'update', :project_id => @project, :id => board}, :put) %> + <% end %> + + <% if User.current.allowed_to?(:manage_boards, @project) %> + <%= link_to l(:button_edit), edit_project_board_path(@project, board), :class => 'icon icon-edit' %> + <%= delete_link project_board_path(@project, board) %> + <% end %> +
    +<% else %> +

    <%= l(:label_no_data) %>

    +<% end %> + +<% if User.current.allowed_to?(:manage_boards, @project) %> +

    <%= link_to l(:label_board_new), new_project_board_path(@project), :class => 'icon icon-add' %>

    +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/29/29fe660e228b4c672e9942d71f9138fc1988a1fe.svn-base --- a/.svn/pristine/29/29fe660e228b4c672e9942d71f9138fc1988a1fe.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -

    <%= link_to l(:label_group_plural), groups_path %> » <%=h @group %>

    - -
      -<% @group.users.each do |user| %> -
    • <%=h user %>
    • -<% end %> -
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2a/2a04a2ae3b7214af25318da2be45b4134d571c1b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2a/2a04a2ae3b7214af25318da2be45b4134d571c1b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,74 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class WikiRedirectTest < ActiveSupport::TestCase + fixtures :projects, :wikis, :wiki_pages + + def setup + @wiki = Wiki.find(1) + @original = WikiPage.create(:wiki => @wiki, :title => 'Original title') + end + + def test_create_redirect + @original.title = 'New title' + assert @original.save + @original.reload + + assert_equal 'New_title', @original.title + assert @wiki.redirects.find_by_title('Original_title') + assert @wiki.find_page('Original title') + assert @wiki.find_page('ORIGINAL title') + end + + def test_update_redirect + # create a redirect that point to this page + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') + + @original.title = 'New title' + @original.save + # make sure the old page now points to the new page + assert_equal 'New_title', @wiki.find_page('An old page').title + end + + def test_reverse_rename + # create a redirect that point to this page + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') + + @original.title = 'An old page' + @original.save + assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'An_old_page') + assert @wiki.redirects.find_by_title_and_redirects_to('Original_title', 'An_old_page') + end + + def test_rename_to_already_redirected + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Other_page') + + @original.title = 'An old page' + @original.save + # this redirect have to be removed since 'An old page' page now exists + assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'Other_page') + end + + def test_redirects_removed_when_deleting_page + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') + + @original.destroy + assert_nil @wiki.redirects.first + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2a/2ad19a5d216db9900f8c479e9b99d1592793dd37.svn-base --- a/.svn/pristine/2a/2ad19a5d216db9900f8c479e9b99d1592793dd37.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Acts - module Event - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - def acts_as_event(options = {}) - return if self.included_modules.include?(Redmine::Acts::Event::InstanceMethods) - default_options = { :datetime => :created_on, - :title => :title, - :description => :description, - :author => :author, - :url => {:controller => 'welcome'}, - :type => self.name.underscore.dasherize } - - cattr_accessor :event_options - self.event_options = default_options.merge(options) - send :include, Redmine::Acts::Event::InstanceMethods - end - end - - module InstanceMethods - def self.included(base) - base.extend ClassMethods - end - - %w(datetime title description author type).each do |attr| - src = <<-END_SRC - def event_#{attr} - option = event_options[:#{attr}] - if option.is_a?(Proc) - option.call(self) - elsif option.is_a?(Symbol) - send(option) - else - option - end - end - END_SRC - class_eval src, __FILE__, __LINE__ - end - - def event_date - event_datetime.to_date - end - - def event_url(options = {}) - option = event_options[:url] - if option.is_a?(Proc) - option.call(self).merge(options) - elsif option.is_a?(Hash) - option.merge(options) - elsif option.is_a?(Symbol) - send(option).merge(options) - else - option - end - end - - # Returns the mail adresses of users that should be notified - def recipients - notified = project.notified_users - notified.reject! {|user| !visible?(user)} - notified.collect(&:mail) - end - - module ClassMethods - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2a/2adb0876a1493b681532094202fc51bd8794f71f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2a/2adb0876a1493b681532094202fc51bd8794f71f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,62 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class UserPreference < ActiveRecord::Base + belongs_to :user + serialize :others + + attr_protected :others, :user_id + + before_save :set_others_hash + + def initialize(attributes=nil, *args) + super + self.others ||= {} + end + + def set_others_hash + self.others ||= {} + end + + def [](attr_name) + if has_attribute? attr_name + super + else + others ? others[attr_name] : nil + end + end + + def []=(attr_name, value) + if has_attribute? attr_name + super + else + h = (read_attribute(:others) || {}).dup + h.update(attr_name => value) + write_attribute(:others, h) + value + end + end + + def comments_sorting; self[:comments_sorting] end + def comments_sorting=(order); self[:comments_sorting]=order end + + def warn_on_leaving_unsaved; self[:warn_on_leaving_unsaved] || '1'; end + def warn_on_leaving_unsaved=(value); self[:warn_on_leaving_unsaved]=value; end + + def no_self_notified; (self[:no_self_notified] == true || self[:no_self_notified] == '1'); end + def no_self_notified=(value); self[:no_self_notified]=value; end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2b/2b217a0880dd8ada2c9c72a208a8a9c562cb5d2c.svn-base --- a/.svn/pristine/2b/2b217a0880dd8ada2c9c72a208a8a9c562cb5d2c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingAttachmentsTest < ActionController::IntegrationTest - def test_attachments - assert_routing( - { :method => 'get', :path => "/attachments/1" }, - { :controller => 'attachments', :action => 'show', :id => '1' } - ) - assert_routing( - { :method => 'get', :path => "/attachments/1.xml" }, - { :controller => 'attachments', :action => 'show', :id => '1', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/attachments/1.json" }, - { :controller => 'attachments', :action => 'show', :id => '1', :format => 'json' } - ) - assert_routing( - { :method => 'get', :path => "/attachments/1/filename.ext" }, - { :controller => 'attachments', :action => 'show', :id => '1', - :filename => 'filename.ext' } - ) - assert_routing( - { :method => 'get', :path => "/attachments/download/1" }, - { :controller => 'attachments', :action => 'download', :id => '1' } - ) - assert_routing( - { :method => 'get', :path => "/attachments/download/1/filename.ext" }, - { :controller => 'attachments', :action => 'download', :id => '1', - :filename => 'filename.ext' } - ) - assert_routing( - { :method => 'get', :path => "/attachments/thumbnail/1" }, - { :controller => 'attachments', :action => 'thumbnail', :id => '1' } - ) - assert_routing( - { :method => 'get', :path => "/attachments/thumbnail/1/200" }, - { :controller => 'attachments', :action => 'thumbnail', :id => '1', :size => '200' } - ) - assert_routing( - { :method => 'delete', :path => "/attachments/1" }, - { :controller => 'attachments', :action => 'destroy', :id => '1' } - ) - assert_routing( - { :method => 'post', :path => '/uploads.xml' }, - { :controller => 'attachments', :action => 'upload', :format => 'xml' } - ) - assert_routing( - { :method => 'post', :path => '/uploads.json' }, - { :controller => 'attachments', :action => 'upload', :format => 'json' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2b/2b8a016bf78b5250d2e474ce2a1c8d8864231ad4.svn-base --- a/.svn/pristine/2b/2b8a016bf78b5250d2e474ce2a1c8d8864231ad4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingCustomFieldsTest < ActionController::IntegrationTest - def test_custom_fields - assert_routing( - { :method => 'get', :path => "/custom_fields" }, - { :controller => 'custom_fields', :action => 'index' } - ) - assert_routing( - { :method => 'get', :path => "/custom_fields/new" }, - { :controller => 'custom_fields', :action => 'new' } - ) - assert_routing( - { :method => 'post', :path => "/custom_fields" }, - { :controller => 'custom_fields', :action => 'create' } - ) - assert_routing( - { :method => 'get', :path => "/custom_fields/2/edit" }, - { :controller => 'custom_fields', :action => 'edit', :id => '2' } - ) - assert_routing( - { :method => 'put', :path => "/custom_fields/2" }, - { :controller => 'custom_fields', :action => 'update', :id => '2' } - ) - assert_routing( - { :method => 'delete', :path => "/custom_fields/2" }, - { :controller => 'custom_fields', :action => 'destroy', :id => '2' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2b/2b98859b11810ab4410410a5454271ee65d2638c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2b/2b98859b11810ab4410410a5454271ee65d2638c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,95 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Activity + # Class used to retrieve activity events + class Fetcher + attr_reader :user, :project, :scope + + # Needs to be unloaded in development mode + @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } } + + def initialize(user, options={}) + options.assert_valid_keys(:project, :with_subprojects, :author) + @user = user + @project = options[:project] + @options = options + + @scope = event_types + end + + # Returns an array of available event types + def event_types + return @event_types unless @event_types.nil? + + @event_types = Redmine::Activity.available_event_types + @event_types = @event_types.select {|o| @project.self_and_descendants.detect {|p| @user.allowed_to?("view_#{o}".to_sym, p)}} if @project + @event_types + end + + # Yields to filter the activity scope + def scope_select(&block) + @scope = @scope.select {|t| yield t } + end + + # Sets the scope + # Argument can be :all, :default or an array of event types + def scope=(s) + case s + when :all + @scope = event_types + when :default + default_scope! + else + @scope = s & event_types + end + end + + # Resets the scope to the default scope + def default_scope! + @scope = Redmine::Activity.default_event_types + end + + # Returns an array of events for the given date range + # sorted in reverse chronological order + def events(from = nil, to = nil, options={}) + e = [] + @options[:limit] = options[:limit] + + @scope.each do |event_type| + constantized_providers(event_type).each do |provider| + e += provider.find_events(event_type, @user, from, to, @options) + end + end + + e.sort! {|a,b| b.event_datetime <=> a.event_datetime} + + if options[:limit] + e = e.slice(0, options[:limit]) + end + e + end + + private + + def constantized_providers(event_type) + @@constantized_providers[event_type] + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2b/2bdd14c51d14eeccec1ac400b671772bf40793e4.svn-base --- a/.svn/pristine/2b/2bdd14c51d14eeccec1ac400b671772bf40793e4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1084 +0,0 @@ -hr: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%m/%d/%Y" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [Ponedjeljak, Utorak, Srijeda, ÄŒetvrtak, Petak, Subota, Nedjelja] - abbr_day_names: [Ned, Pon, Uto, Sri, ÄŒet, Pet, Sub] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Sijecanj, Veljaca, Ožujak, Travanj, Svibanj, Lipanj, Srpanj, Kolovoz, Rujan, Listopad, Studeni, Prosinac] - abbr_month_names: [~, Sij, Velj, Ožu, Tra, Svi, Lip, Srp, Kol, Ruj, List, Stu, Pro] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%m/%d/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "pola minute" - less_than_x_seconds: - one: "manje od sekunde" - other: "manje od %{count} sekundi" - x_seconds: - one: "1 sekunda" - other: "%{count} sekundi" - less_than_x_minutes: - one: "manje od minute" - other: "manje od %{count} minuta" - x_minutes: - one: "1 minuta" - other: "%{count} minuta" - about_x_hours: - one: "oko sat vremena" - other: "oko %{count} sati" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 dan" - other: "%{count} dana" - about_x_months: - one: "oko 1 mjesec" - other: "oko %{count} mjeseci" - x_months: - one: "mjesec" - other: "%{count} mjeseci" - about_x_years: - one: "1 godina" - other: "%{count} godina" - over_x_years: - one: "preko 1 godine" - other: "preko %{count} godina" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "i" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "nije ukljuceno u listu" - exclusion: "je rezervirano" - invalid: "nije ispravno" - confirmation: "ne odgovara za potvrdu" - accepted: "mora biti prihvaćen" - empty: "ne može biti prazno" - blank: "ne može biti razmaka" - too_long: "je predug (maximum is %{count} characters)" - too_short: "je prekratak (minimum is %{count} characters)" - wrong_length: "je pogreÅ¡ne dužine (should be %{count} characters)" - taken: "već je zauzeto" - not_a_number: "nije broj" - not_a_date: "nije ispravan datum" - greater_than: "mora biti veći od %{count}" - greater_than_or_equal_to: "mora biti veći ili jednak %{count}" - equal_to: "mora biti jednak %{count}" - less_than: "mora biti manji od %{count}" - less_than_or_equal_to: "mora bit manji ili jednak%{count}" - odd: "mora biti neparan" - even: "mora biti paran" - greater_than_start_date: "mora biti veci nego pocetni datum" - not_same_project: "ne pripada istom projektu" - circular_dependency: "Ovaj relacija stvara kružnu ovisnost" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Molimo odaberite - - general_text_No: 'Ne' - general_text_Yes: 'Da' - general_text_no: 'ne' - general_text_yes: 'da' - general_lang_name: 'Hrvatski' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '7' - - notice_account_updated: VaÅ¡ profil je uspjeÅ¡no promijenjen. - notice_account_invalid_creditentials: Neispravno korisniÄko ime ili zaporka. - notice_account_password_updated: Zaporka je uspjeÅ¡no promijenjena. - notice_account_wrong_password: PogreÅ¡na zaporka - notice_account_register_done: Racun je uspjeÅ¡no napravljen. Da biste aktivirali svoj raÄun, kliknite na link koji vam je poslan na e-mail. - notice_account_unknown_email: Nepoznati korisnik. - notice_can_t_change_password: Ovaj raÄun koristi eksterni izvor prijavljivanja. Nemoguće je promijeniti zaporku. - notice_account_lost_email_sent: E-mail s uputama kako bi odabrali novu zaporku je poslan na na vaÅ¡u e-mail adresu. - notice_account_activated: VaÅ¡ racun je aktiviran. Možete se prijaviti. - notice_successful_create: UspjeÅ¡no napravljeno. - notice_successful_update: UspjeÅ¡na promjena. - notice_successful_delete: UspjeÅ¡no brisanje. - notice_successful_connection: UspjeÅ¡na veza. - notice_file_not_found: Stranica kojoj ste pokuÅ¡ali pristupiti ne postoji ili je uklonjena. - notice_locking_conflict: Podataci su ažurirani od strane drugog korisnika. - notice_not_authorized: Niste ovlaÅ¡teni za pristup ovoj stranici. - notice_email_sent: E-mail je poslan %{value}" - notice_email_error: Dogodila se pogreÅ¡ka tijekom slanja E-maila (%{value})" - notice_feeds_access_key_reseted: VaÅ¡ RSS pristup je resetovan. - notice_api_access_key_reseted: VaÅ¡ API pristup je resetovan. - notice_failed_to_save_issues: "Neuspjelo spremanje %{count} predmeta na %{total} odabrane: %{ids}." - notice_no_issue_selected: "Niti jedan predmet nije odabran! Molim, odaberite predmete koje želite urediti." - notice_account_pending: "VaÅ¡ korisnicki raÄun je otvoren, Äeka odobrenje administratora." - notice_default_data_loaded: Konfiguracija je uspjeÅ¡no uÄitana. - notice_unable_delete_version: Nije moguće izbrisati verziju. - notice_issue_done_ratios_updated: Issue done ratios updated. - - error_can_t_load_default_data: "Zadanu konfiguracija nije uÄitana: %{value}" - error_scm_not_found: "Unos i/ili revizija nije pronaÄ‘en." - error_scm_command_failed: "Dogodila se pogreÅ¡ka prilikom pokuÅ¡aja pristupa: %{value}" - error_scm_annotate: "Ne postoji ili ne može biti obilježen." - error_issue_not_found_in_project: 'Nije pronaÄ‘en ili ne pripada u ovaj projekt' - error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' - error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' - error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' - error_can_not_archive_project: This project can not be archived - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' - - warning_attachments_not_saved: "%{count} Datoteka/e nije mogla biti spremljena." - - mail_subject_lost_password: "VaÅ¡a %{value} zaporka" - mail_body_lost_password: 'Kako biste promijenili VaÅ¡u zaporku slijedite poveznicu:' - mail_subject_register: "Aktivacija korisniÄog raÄuna %{value}" - mail_body_register: 'Da biste aktivirali svoj raÄun, kliknite na sljedeci link:' - mail_body_account_information_external: "Možete koristiti vaÅ¡ raÄun %{value} za prijavu." - mail_body_account_information: VaÅ¡i korisniÄki podaci - mail_subject_account_activation_request: "%{value} predmet za aktivaciju korisniÄkog raÄuna" - mail_body_account_activation_request: "Novi korisnik (%{value}) je registriran. Njegov korisniÄki raÄun Äeka vaÅ¡e odobrenje:" - mail_subject_reminder: "%{count} predmet(a) dospijeva sljedećih %{days} dana" - mail_body_reminder: "%{count} vama dodijeljen(ih) predmet(a) dospijeva u sljedećih %{days} dana:" - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - - gui_validation_error: 1 pogreÅ¡ka - gui_validation_error_plural: "%{count} pogreÅ¡aka" - - field_name: Ime - field_description: Opis - field_summary: Sažetak - field_is_required: Obavezno - field_firstname: Ime - field_lastname: Prezime - field_mail: E-poÅ¡ta - field_filename: Datoteka - field_filesize: VeliÄina - field_downloads: Preuzimanja - field_author: Autor - field_created_on: Napravljen - field_updated_on: Promijenjen - field_field_format: Format - field_is_for_all: Za sve projekte - field_possible_values: Moguće vrijednosti - field_regexp: Regularni izraz - field_min_length: Minimalna dužina - field_max_length: Maksimalna dužina - field_value: Vrijednost - field_category: Kategorija - field_title: Naslov - field_project: Projekt - field_issue: Predmet - field_status: Status - field_notes: Napomene - field_is_closed: Predmet je zatvoren - field_is_default: Zadana vrijednost - field_tracker: Tracker - field_subject: Predmet - field_due_date: Do datuma - field_assigned_to: Dodijeljeno - field_priority: Prioritet - field_fixed_version: Verzija - field_user: Korisnik - field_role: Uloga - field_homepage: Naslovnica - field_is_public: Javni projekt - field_parent: Potprojekt od - field_is_in_roadmap: Predmeti se prikazuju u Putokazu - field_login: KorisniÄko ime - field_mail_notification: Obavijest putem e-poÅ¡te - field_admin: Administrator - field_last_login_on: Zadnja prijava - field_language: Primarni jezik - field_effective_date: Datum - field_password: Zaporka - field_new_password: Nova zaporka - field_password_confirmation: Potvrda zaporke - field_version: Verzija - field_type: Tip - field_host: Host - field_port: Port - field_account: Racun - field_base_dn: Osnovni DN - field_attr_login: Login atribut - field_attr_firstname: Atribut imena - field_attr_lastname: Atribut prezimena - field_attr_mail: Atribut e-poÅ¡te - field_onthefly: "Izrada korisnika \"u hodu\"" - field_start_date: Pocetak - field_done_ratio: "% UÄinjeno" - field_auth_source: Vrsta prijavljivanja - field_hide_mail: Sakrij moju adresu e-poÅ¡te - field_comments: Komentar - field_url: URL - field_start_page: PoÄetna stranica - field_subproject: Potprojekt - field_hours: Sati - field_activity: Aktivnost - field_spent_on: Datum - field_identifier: Identifikator - field_is_filter: KoriÅ¡teno kao filtar - field_issue_to_id: Povezano s predmetom - field_delay: Odgodeno - field_assignable: Predmeti mogu biti dodijeljeni ovoj ulozi - field_redirect_existing_links: Preusmjeravanje postojećih linkova - field_estimated_hours: Procijenjeno vrijeme - field_column_names: Stupci - field_time_zone: Vremenska zona - field_searchable: Pretraživo - field_default_value: Zadana vrijednost - field_comments_sorting: Prikaz komentara - field_parent_title: Parent page - field_editable: Editable - field_watcher: Watcher - field_identity_url: OpenID URL - field_content: Content - field_group_by: Group results by - - setting_app_title: Naziv aplikacije - setting_app_subtitle: Podnaslov aplikacije - setting_welcome_text: Tekst dobrodoÅ¡lice - setting_default_language: Zadani jezik - setting_login_required: Potrebna je prijava - setting_self_registration: Samoregistracija je dozvoljena - setting_attachment_max_size: Maksimalna veliÄina privitka - setting_issues_export_limit: OgraniÄenje izvoza predmeta - setting_mail_from: Izvorna adresa e-poÅ¡te - setting_bcc_recipients: Blind carbon copy primatelja (bcc) - setting_plain_text_mail: obiÄni tekst poÅ¡te (bez HTML-a) - setting_host_name: Naziv domaćina (host) - setting_text_formatting: Oblikovanje teksta - setting_wiki_compression: Sažimanje - setting_feeds_limit: Ogranicenje unosa sadržaja - setting_default_projects_public: Novi projekti su javni po defaultu - setting_autofetch_changesets: Autofetch commits - setting_sys_api_enabled: Omogući WS za upravljanje skladiÅ¡tem - setting_commit_ref_keywords: Referentne kljuÄne rijeÄi - setting_commit_fix_keywords: Fiksne kljuÄne rijeÄi - setting_autologin: Automatska prijava - setting_date_format: Format datuma - setting_time_format: Format vremena - setting_cross_project_issue_relations: Dozvoli povezivanje predmeta izmedu razliÄitih projekata - setting_issue_list_default_columns: Stupci prikazani na listi predmeta - setting_emails_footer: Zaglavlje e-poÅ¡te - setting_protocol: Protokol - setting_per_page_options: Objekata po stranici opcija - setting_user_format: Oblik prikaza korisnika - setting_activity_days_default: Dani prikazane aktivnosti na projektu - setting_display_subprojects_issues: Prikaz predmeta potprojekta na glavnom projektu po defaultu - setting_enabled_scm: Omogućen SCM - setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" - setting_mail_handler_api_enabled: Omoguci WS za dolaznu e-poÅ¡tu - setting_mail_handler_api_key: API kljuÄ - setting_sequential_project_identifiers: Generiraj slijedne identifikatore projekta - setting_gravatar_enabled: Koristi Gravatar korisniÄke ikone - setting_gravatar_default: Default Gravatar image - setting_diff_max_lines_displayed: Maksimalni broj diff linija za prikazati - setting_file_max_size_displayed: Max size of text files displayed inline - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_openid: Allow OpenID login and registration - setting_password_min_length: Minimum password length - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - setting_default_projects_modules: Default enabled modules for new projects - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_field: Use the issue field - setting_issue_done_ratio_issue_status: Use the issue status - setting_start_of_week: Start calendars on - setting_rest_api_enabled: Enable REST web service - - permission_add_project: Dodaj projekt - permission_add_subprojects: Dodaj potprojekt - permission_edit_project: Uredi projekt - permission_select_project_modules: Odaberi projektne module - permission_manage_members: Upravljaj Älanovima - permission_manage_versions: Upravljaj verzijama - permission_manage_categories: Upravljaj kategorijama predmeta - permission_view_issues: Pregledaj zahtjeve - permission_add_issues: Dodaj predmete - permission_edit_issues: Uredi predmete - permission_manage_issue_relations: Upravljaj relacijama predmeta - permission_add_issue_notes: Dodaj biljeÅ¡ke - permission_edit_issue_notes: Uredi biljeÅ¡ke - permission_edit_own_issue_notes: Uredi vlastite biljeÅ¡ke - permission_move_issues: Premjesti predmete - permission_delete_issues: Brisanje predmeta - permission_manage_public_queries: Upravljaj javnim upitima - permission_save_queries: Spremi upite - permission_view_gantt: Pregledaj gantt grafikon - permission_view_calendar: Pregledaj kalendar - permission_view_issue_watchers: Pregledaj listu promatraca - permission_add_issue_watchers: Dodaj promatraÄa - permission_delete_issue_watchers: Delete watchers - permission_log_time: Dnevnik utroÅ¡enog vremena - permission_view_time_entries: Pregledaj utroÅ¡eno vrijeme - permission_edit_time_entries: Uredi vremenske dnevnike - permission_edit_own_time_entries: Edit own time logs - permission_manage_news: Upravljaj novostima - permission_comment_news: Komentiraj novosti - permission_manage_documents: Upravljaj dokumentima - permission_view_documents: Pregledaj dokumente - permission_manage_files: Upravljaj datotekama - permission_view_files: Pregledaj datoteke - permission_manage_wiki: Upravljaj wikijem - permission_rename_wiki_pages: Promijeni ime wiki stranicama - permission_delete_wiki_pages: ObriÅ¡i wiki stranice - permission_view_wiki_pages: Pregledaj wiki - permission_view_wiki_edits: Pregledaj povijest wikija - permission_edit_wiki_pages: Uredi wiki stranice - permission_delete_wiki_pages_attachments: ObriÅ¡i privitke - permission_protect_wiki_pages: ZaÅ¡titi wiki stranice - permission_manage_repository: Upravljaj skladiÅ¡tem - permission_browse_repository: Browse repository - permission_view_changesets: View changesets - permission_commit_access: Mogućnost pohranjivanja - permission_manage_boards: Manage boards - permission_view_messages: Pregledaj poruke - permission_add_messages: Objavi poruke - permission_edit_messages: Uredi poruke - permission_edit_own_messages: Uredi vlastite poruke - permission_delete_messages: ObriÅ¡i poruke - permission_delete_own_messages: ObriÅ¡i vlastite poruke - - project_module_issue_tracking: Praćenje predmeta - project_module_time_tracking: Praćenje vremena - project_module_news: Novosti - project_module_documents: Dokumenti - project_module_files: Datoteke - project_module_wiki: Wiki - project_module_repository: SkladiÅ¡te - project_module_boards: Boards - - label_user: Korisnik - label_user_plural: Korisnici - label_user_new: Novi korisnik - label_user_anonymous: Anonymous - label_project: Projekt - label_project_new: Novi projekt - label_project_plural: Projekti - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: Svi Projekti - label_project_latest: Najnoviji projekt - label_issue: Predmet - label_issue_new: Novi predmet - label_issue_plural: Predmeti - label_issue_view_all: Pregled svih predmeta - label_issues_by: "Predmeti od %{value}" - label_issue_added: Predmet dodan - label_issue_updated: Predmet promijenjen - label_document: Dokument - label_document_new: Novi dokument - label_document_plural: Dokumenti - label_document_added: Dokument dodan - label_role: Uloga - label_role_plural: Uloge - label_role_new: Nova uloga - label_role_and_permissions: Uloge i ovlasti - label_member: ÄŒlan - label_member_new: Novi Älan - label_member_plural: ÄŒlanovi - label_tracker: Vrsta - label_tracker_plural: Vrste predmeta - label_tracker_new: Nova vrsta - label_workflow: Tijek rada - label_issue_status: Status predmeta - label_issue_status_plural: Status predmeta - label_issue_status_new: Novi status - label_issue_category: Kategorija predmeta - label_issue_category_plural: Kategorije predmeta - label_issue_category_new: Nova kategorija - label_custom_field: KorisniÄki definirano polje - label_custom_field_plural: KorisniÄki definirana polja - label_custom_field_new: Novo korisniÄki definirano polje - label_enumerations: Pobrojenice - label_enumeration_new: Nova vrijednost - label_information: Informacija - label_information_plural: Informacije - label_please_login: Molim prijavite se - label_register: Registracija - label_login_with_open_id_option: or login with OpenID - label_password_lost: Izgubljena zaporka - label_home: PoÄetna stranica - label_my_page: Moja stranica - label_my_account: Moj profil - label_my_projects: Moji projekti - label_administration: Administracija - label_login: Korisnik - label_logout: Odjava - label_help: Pomoć - label_reported_issues: Prijavljeni predmeti - label_assigned_to_me_issues: Moji predmeti - label_last_login: Last connection - label_registered_on: Registrirano - label_activity: Aktivnosti - label_overall_activity: Aktivnosti - label_user_activity: "%{value} ova/ina aktivnost" - label_new: Novi - label_logged_as: Prijavljeni ste kao - label_environment: Okolina - label_authentication: Autentikacija - label_auth_source: NaÄin prijavljivanja - label_auth_source_new: Novi naÄin prijavljivanja - label_auth_source_plural: NaÄini prijavljivanja - label_subproject_plural: Potprojekti - label_subproject_new: Novi potprojekt - label_and_its_subprojects: "%{value} i njegovi potprojekti" - label_min_max_length: Min - Maks veliÄina - label_list: Liste - label_date: Datum - label_integer: Integer - label_float: Float - label_boolean: Boolean - label_string: Text - label_text: Long text - label_attribute: Atribut - label_attribute_plural: Atributi - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - label_no_data: Nema podataka za prikaz - label_change_status: Promjena statusa - label_history: Povijest - label_attachment: Datoteka - label_attachment_new: Nova datoteka - label_attachment_delete: Brisanje datoteke - label_attachment_plural: Datoteke - label_file_added: Datoteka dodana - label_report: Izvješće - label_report_plural: Izvješća - label_news: Novosti - label_news_new: Dodaj novost - label_news_plural: Novosti - label_news_latest: Novosti - label_news_view_all: Pregled svih novosti - label_news_added: Novosti dodane - label_settings: Postavke - label_overview: Pregled - label_version: Verzija - label_version_new: Nova verzija - label_version_plural: Verzije - label_confirmation: Potvrda - label_export_to: 'Izvoz u:' - label_read: ÄŒitaj... - label_public_projects: Javni projekti - label_open_issues: Otvoren - label_open_issues_plural: Otvoreno - label_closed_issues: Zatvoren - label_closed_issues_plural: Zatvoreno - label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_total: Ukupno - label_permissions: Dozvole - label_current_status: Trenutni status - label_new_statuses_allowed: Novi status je dozvoljen - label_all: Svi - label_none: nema - label_nobody: nitko - label_next: Naredni - label_previous: Prethodni - label_used_by: KoriÅ¡ten od - label_details: Detalji - label_add_note: Dodaj napomenu - label_per_page: Po stranici - label_calendar: Kalendar - label_months_from: Mjeseci od - label_gantt: Gantt - label_internal: Interno - label_last_changes: "Posljednjih %{count} promjena" - label_change_view_all: Prikaz svih promjena - label_personalize_page: Prilagodite ovu stranicu - label_comment: Komentar - label_comment_plural: Komentari - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: Dodaj komentar - label_comment_added: Komentar dodan - label_comment_delete: Brisanje komentara - label_query: KorisniÄki upit - label_query_plural: KorisniÄki upiti - label_query_new: Novi upit - label_filter_add: Dodaj filtar - label_filter_plural: Filtri - label_equals: je - label_not_equals: nije - label_in_less_than: za manje od - label_in_more_than: za viÅ¡e od - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: za toÄno - label_today: danas - label_all_time: sva vremena - label_yesterday: juÄer - label_this_week: ovog tjedna - label_last_week: proÅ¡log tjedna - label_last_n_days: "zadnjih %{count} dana" - label_this_month: ovog mjeseca - label_last_month: proÅ¡log mjeseca - label_this_year: ove godine - label_date_range: vremenski raspon - label_less_than_ago: manje od - label_more_than_ago: viÅ¡e od - label_ago: prije - label_contains: Sadrži - label_not_contains: ne sadrži - label_day_plural: dana - label_repository: SkladiÅ¡te - label_repository_plural: SkladiÅ¡ta - label_browse: Pregled - label_modification: "%{count} promjena" - label_modification_plural: "%{count} promjena" - label_branch: Branch - label_tag: Tag - label_revision: Revizija - label_revision_plural: Revizije - label_revision_id: "Revision %{value}" - label_associated_revisions: Dodijeljene revizije - label_added: dodano - label_modified: promijenjen - label_copied: kopirano - label_renamed: preimenovano - label_deleted: obrisano - label_latest_revision: Posljednja revizija - label_latest_revision_plural: Posljednje revizije - label_view_revisions: Pregled revizija - label_view_all_revisions: View all revisions - label_max_size: Maksimalna veliÄina - label_sort_highest: Premjesti na vrh - label_sort_higher: Premjesti prema gore - label_sort_lower: Premjesti prema dolje - label_sort_lowest: Premjesti na dno - label_roadmap: Putokaz - label_roadmap_due_in: "ZavrÅ¡ava se za %{value}" - label_roadmap_overdue: "%{value} kasni" - label_roadmap_no_issues: Nema predmeta za ovu verziju - label_search: Traži - label_result_plural: Rezultati - label_all_words: Sve rijeÄi - label_wiki: Wiki - label_wiki_edit: Wiki promjena - label_wiki_edit_plural: Wiki promjene - label_wiki_page: Wiki stranica - label_wiki_page_plural: Wiki stranice - label_index_by_title: Indeks po naslovima - label_index_by_date: Indeks po datumu - label_current_version: Trenutna verzija - label_preview: Brzi pregled - label_feed_plural: Feeds - label_changes_details: Detalji svih promjena - label_issue_tracking: Praćenje predmeta - label_spent_time: UtroÅ¡eno vrijeme - label_f_hour: "%{value} sata" - label_f_hour_plural: "%{value} sati" - label_time_tracking: Praćenje vremena - label_change_plural: Promjene - label_statistics: Statistika - label_commits_per_month: Pohrana po mjesecu - label_commits_per_author: Pohrana po autoru - label_view_diff: Pregled razlika - label_diff_inline: uvuÄeno - label_diff_side_by_side: paralelno - label_options: Opcije - label_copy_workflow_from: Kopiraj tijek rada od - label_permissions_report: Izvješće o dozvolama - label_watched_issues: Praćeni predmeti - label_related_issues: Povezani predmeti - label_applied_status: Primijenjen status - label_loading: UÄitavam... - label_relation_new: Nova relacija - label_relation_delete: Brisanje relacije - label_relates_to: u relaciji sa - label_duplicates: Duplira - label_duplicated_by: ponovljen kao - label_blocks: blokira - label_blocked_by: blokiran od strane - label_precedes: prethodi - label_follows: slijedi - label_end_to_start: od kraja do poÄetka - label_end_to_end: od kraja do kraja - label_end_to_start: od kraja do poÄetka - label_end_to_end: od kraja do kraja - label_stay_logged_in: Ostanite prijavljeni - label_disabled: IskljuÄen - label_show_completed_versions: Prikaži zavrÅ¡ene verzije - label_me: ja - label_board: Forum - label_board_new: Novi forum - label_board_plural: Forumi - label_topic_plural: Teme - label_message_plural: Poruke - label_message_last: Posljednja poruka - label_message_new: Nova poruka - label_message_posted: Poruka dodana - label_reply_plural: Odgovori - label_send_information: PoÅ¡alji korisniku informaciju o profilu - label_year: Godina - label_month: Mjesec - label_week: Tjedan - label_date_from: Od - label_date_to: Do - label_language_based: Zasnovano na jeziku - label_sort_by: "Uredi po %{value}" - label_send_test_email: PoÅ¡alji testno E-pismo - label_feeds_access_key: RSS access key - label_missing_feeds_access_key: Missing a RSS access key - label_feeds_access_key_created_on: "RSS kljuc za pristup je napravljen prije %{value}" - label_module_plural: Moduli - label_added_time_by: "Promijenio %{author} prije %{age}" - label_updated_time_by: "Dodao/la %{author} prije %{age}" - label_updated_time: "Promijenjeno prije %{value}" - label_jump_to_a_project: Prebaci se na projekt... - label_file_plural: Datoteke - label_changeset_plural: Promjene - label_default_columns: Zadani stupci - label_no_change_option: (Bez promjene) - label_bulk_edit_selected_issues: ZajedniÄka promjena izabranih predmeta - label_theme: Tema - label_default: Zadana - label_search_titles_only: Pretraživanje samo naslova - label_user_mail_option_all: "Za bilo koji dogaÄ‘aj na svim mojim projektima" - label_user_mail_option_selected: "Za bilo koji dogaÄ‘aj samo za izabrane projekte..." - label_user_mail_no_self_notified: "Ne želim primati obavijesti o promjenama koje sam napravim" - label_registration_activation_by_email: aktivacija putem e-poÅ¡te - label_registration_manual_activation: ruÄna aktivacija - label_registration_automatic_activation: automatska aktivacija - label_display_per_page: "Po stranici: %{value}" - label_age: Starost - label_change_properties: Promijeni svojstva - label_general: Općenito - label_more: JoÅ¡ - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: LDAP autentikacija - label_downloads_abbr: D/L - label_optional_description: Opcije - label_add_another_file: Dodaj joÅ¡ jednu datoteku - label_preferences: Preferences - label_chronological_order: U kronoloÅ¡kom redoslijedu - label_reverse_chronological_order: U obrnutom kronoloÅ¡kom redoslijedu - label_planning: Planiranje - label_incoming_emails: Dolazne poruke e-poÅ¡te - label_generate_key: Generiraj kljuÄ - label_issue_watchers: PromatraÄi - label_example: Primjer - label_display: Display - label_sort: Sort - label_ascending: Ascending - label_descending: Descending - label_date_from_to: From %{start} to %{end} - label_wiki_content_added: Wiki page added - label_wiki_content_updated: Wiki page updated - label_group: Group - label_group_plural: Grupe - label_group_new: Nova grupa - label_time_entry_plural: Spent time - label_version_sharing_none: Not shared - label_version_sharing_descendants: With subprojects - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_tree: With project tree - label_version_sharing_system: With all projects - label_update_issue_done_ratios: Update issue done ratios - label_copy_source: Source - label_copy_target: Target - label_copy_same_as_target: Same as target - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_api_access_key: API access key - label_missing_api_access_key: Missing an API access key - label_api_access_key_created_on: "API access key created %{value} ago" - - button_login: Prijavi - button_submit: PoÅ¡alji - button_save: Spremi - button_check_all: OznaÄi sve - button_uncheck_all: IskljuÄi sve - button_delete: ObriÅ¡i - button_create: Napravi - button_create_and_continue: Napravi i nastavi - button_test: Test - button_edit: Uredi - button_add: Dodaj - button_change: Promijeni - button_apply: Primijeni - button_clear: Ukloni - button_lock: ZakljuÄaj - button_unlock: OtkljuÄaj - button_download: Preuzmi - button_list: Spisak - button_view: Pregled - button_move: Premjesti - button_move_and_follow: Move and follow - button_back: Nazad - button_cancel: Odustani - button_activate: Aktiviraj - button_sort: Redoslijed - button_log_time: ZapiÅ¡i vrijeme - button_rollback: IzvrÅ¡i rollback na ovu verziju - button_watch: Prati - button_unwatch: Prekini pracenje - button_reply: Odgovori - button_archive: Arhiviraj - button_rollback: Dearhiviraj - button_reset: PoniÅ¡ti - button_rename: Promijeni ime - button_change_password: Promjena zaporke - button_copy: Kopiraj - button_copy_and_follow: Copy and follow - button_annotate: Annotate - button_update: Promijeni - button_configure: Konfiguracija - button_quote: Navod - button_duplicate: Duplicate - button_show: Show - - status_active: aktivan - status_registered: Registriran - status_locked: zakljuÄan - - version_status_open: open - version_status_locked: locked - version_status_closed: closed - - field_active: Active - - text_select_mail_notifications: Izbor akcija za koje će biti poslana obavijest e-poÅ¡tom. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 znaÄi bez ograniÄenja - text_project_destroy_confirmation: Da li ste sigurni da želite izbrisati ovaj projekt i sve njegove podatke? - text_subprojects_destroy_warning: "Njegov(i) potprojekt(i): %{value} će takoÄ‘er biti obrisan." - text_workflow_edit: Select a role and a tracker to edit the workflow - text_are_you_sure: Da li ste sigurni? - text_journal_changed: "%{label} promijenjen iz %{old} u %{new}" - text_journal_set_to: "%{label} postavi na %{value}" - text_journal_deleted: "%{label} izbrisano (%{old})" - text_journal_added: "%{label} %{value} added" - text_tip_issue_begin_day: Zadaci koji poÄinju ovog dana - text_tip_issue_end_day: zadaci koji se zavrÅ¡avaju ovog dana - text_tip_issue_begin_end_day: Zadaci koji poÄinju i zavrÅ¡avaju se ovog dana - text_caracters_maximum: "NajviÅ¡e %{count} znakova." - text_caracters_minimum: "Mora biti dugaÄko najmanje %{count} znakova." - text_length_between: "Dužina izmedu %{min} i %{max} znakova." - text_tracker_no_workflow: Tijek rada nije definiran za ovaj tracker - text_unallowed_characters: Nedozvoljeni znakovi - text_comma_separated: ViÅ¡estruke vrijednosti su dozvoljene (razdvojene zarezom). - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_tracker_no_workflow: No workflow defined for this tracker - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_issue_added: "Predmet %{id} je prijavljen (prijavio %{author})." - text_issue_updated: "Predmet %{id} je promijenjen %{author})." - text_wiki_destroy_confirmation: Da li ste sigurni da želite izbrisati ovaj wiki i njegov sadržaj? - text_issue_category_destroy_question: "Neke predmeti (%{count}) su dodijeljeni ovoj kategoriji. Å to želite uraditi?" - text_issue_category_destroy_assignments: Ukloni dodjeljivanje kategorija - text_issue_category_reassign_to: Ponovo dodijeli predmete ovoj kategoriji - text_user_mail_option: "Za neizabrane projekte, primit ćete obavjesti samo o stvarima koje pratite ili u kojima sudjelujete (npr. predmete koje ste vi napravili ili koje su vama dodjeljeni)." - text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." - text_load_default_configuration: UÄitaj poÄetnu konfiguraciju - text_status_changed_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Jeste li sigurni da želite obrisati izabrani/e predmet(e)?' - text_select_project_modules: 'Odaberite module koji će biti omogućeni za ovaj projekt:' - text_default_administrator_account_changed: Default administrator account changed - text_file_repository_writable: Dozvoljeno pisanje u direktorij za privitke - text_plugin_assets_writable: Plugin assets directory writable - text_rmagick_available: RMagick dostupan (nije obavezno) - text_destroy_time_entries_question: "%{hours} sati je prijavljeno za predmete koje želite obrisati. Å to ćete uÄiniti?" - text_destroy_time_entries: ObriÅ¡i prijavljene sate - text_assign_time_entries_to_project: Pridruži prijavljene sate projektu - text_reassign_time_entries: 'Premjesti prijavljene sate ovom predmetu:' - text_user_wrote: "%{value} je napisao/la:" - text_enumeration_destroy_question: "%{count} objekata je pridruženo toj vrijednosti." - text_enumeration_category_reassign_to: 'Premjesti ih ovoj vrijednosti:' - text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." - text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." - text_diff_truncated: '... Ovaj diff je odrezan zato Å¡to prelazi maksimalnu veliÄinu koja može biti prikazana.' - text_custom_field_possible_values_info: 'One line for each value' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" - text_wiki_page_nullify_children: "Keep child pages as root pages" - text_wiki_page_destroy_children: "Delete child pages and all their descendants" - text_wiki_page_reassign_children: "Reassign child pages to this parent page" - default_role_manager: Upravitelj - default_role_developer: Razvojni inženjer - default_role_reporter: Korisnik - default_tracker_bug: PogreÅ¡ka - default_tracker_feature: Funkcionalnost - default_tracker_support: PodrÅ¡ka - default_issue_status_new: Novo - default_issue_status_assigned: Dodijeljeno - default_issue_status_resolved: RijeÅ¡eno - default_issue_status_feedback: Povratna informacija - default_issue_status_closed: Zatvoreno - default_issue_status_rejected: Odbaceno - default_doc_category_user: KorisniÄka dokumentacija - default_doc_category_tech: TehniÄka dokumentacija - default_priority_low: Nizak - default_priority_normal: Redovan - default_priority_high: Visok - default_priority_urgent: Hitan - default_priority_immediate: Odmah - default_activity_design: Dizajn - default_activity_development: Razvoj - enumeration_issue_priorities: Prioriteti predmeta - enumeration_doc_categories: Kategorija dokumenata - enumeration_activities: Aktivnosti (po vremenu) - enumeration_system_activity: System Activity - field_sharing: Sharing - text_line_separated: Multiple values allowed (one line for each value). - label_close_versions: Close completed versions - button_unarchive: Unarchive - label_start_to_end: start to end - label_start_to_start: start to start - field_issue_to: Related issue - default_issue_status_in_progress: In Progress - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Commit messages encoding - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 predmet - one: 1 predmet - other: "%{count} predmeti" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: Svi - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: With subprojects - label_cross_project_tree: With project tree - label_cross_project_hierarchy: With project hierarchy - label_cross_project_system: With all projects - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2b/2bf169c59e60650b88bcb73540687a2bc02b9736.svn-base --- a/.svn/pristine/2b/2bf169c59e60650b88bcb73540687a2bc02b9736.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../../test_helper', __FILE__) - -class CalendarTest < ActiveSupport::TestCase - - def test_monthly - c = Redmine::Helpers::Calendar.new(Date.today, :fr, :month) - assert_equal [1, 7], [c.startdt.cwday, c.enddt.cwday] - - c = Redmine::Helpers::Calendar.new('2007-07-14'.to_date, :fr, :month) - assert_equal ['2007-06-25'.to_date, '2007-08-05'.to_date], [c.startdt, c.enddt] - - c = Redmine::Helpers::Calendar.new(Date.today, :en, :month) - assert_equal [7, 6], [c.startdt.cwday, c.enddt.cwday] - end - - def test_weekly - c = Redmine::Helpers::Calendar.new(Date.today, :fr, :week) - assert_equal [1, 7], [c.startdt.cwday, c.enddt.cwday] - - c = Redmine::Helpers::Calendar.new('2007-07-14'.to_date, :fr, :week) - assert_equal ['2007-07-09'.to_date, '2007-07-15'.to_date], [c.startdt, c.enddt] - - c = Redmine::Helpers::Calendar.new(Date.today, :en, :week) - assert_equal [7, 6], [c.startdt.cwday, c.enddt.cwday] - end - - def test_monthly_start_day - [1, 6, 7].each do |day| - with_settings :start_of_week => day do - c = Redmine::Helpers::Calendar.new(Date.today, :en, :month) - assert_equal day , c.startdt.cwday - assert_equal (day + 5) % 7 + 1, c.enddt.cwday - end - end - end - - def test_weekly_start_day - [1, 6, 7].each do |day| - with_settings :start_of_week => day do - c = Redmine::Helpers::Calendar.new(Date.today, :en, :week) - assert_equal day, c.startdt.cwday - assert_equal (day + 5) % 7 + 1, c.enddt.cwday - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2b/2bffeae9e506603f05893c49874a197ccc797b57.svn-base --- a/.svn/pristine/2b/2bffeae9e506603f05893c49874a197ccc797b57.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -

    <%= l(:label_board_plural) %>

    - - - - - - - - - -<% Board.board_tree(@boards) do |board, level| %> - - - - - - -<% end %> - -
    <%= l(:label_board) %><%= l(:label_topic_plural) %><%= l(:label_message_plural) %><%= l(:label_message_last) %>
    - <%= link_to h(board.name), {:action => 'show', :id => board}, :class => "board" %>
    - <%=h board.description %> -
    <%= board.topics_count %><%= board.messages_count %> - <% if board.last_message %> - <%= authoring board.last_message.created_on, board.last_message.author %>
    - <%= link_to_message board.last_message %> - <% end %> -
    - -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_messages => 1, :key => User.current.rss_key} %> -<% end %> - -<% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :id => @project, :format => 'atom', :show_messages => 1, :key => User.current.rss_key}) %> -<% end %> - -<% html_title l(:label_board_plural) %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2c/2c0e2ce37084f59c8aba6396a829a848205403ce.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2c/2c0e2ce37084f59c8aba6396a829a848205403ce.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,134 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingIssuesTest < ActionController::IntegrationTest + def test_issues_rest_actions + assert_routing( + { :method => 'get', :path => "/issues" }, + { :controller => 'issues', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/issues.pdf" }, + { :controller => 'issues', :action => 'index', :format => 'pdf' } + ) + assert_routing( + { :method => 'get', :path => "/issues.atom" }, + { :controller => 'issues', :action => 'index', :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/issues.xml" }, + { :controller => 'issues', :action => 'index', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64" }, + { :controller => 'issues', :action => 'show', :id => '64' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64.pdf" }, + { :controller => 'issues', :action => 'show', :id => '64', + :format => 'pdf' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64.atom" }, + { :controller => 'issues', :action => 'show', :id => '64', + :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64.xml" }, + { :controller => 'issues', :action => 'show', :id => '64', + :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/issues.xml" }, + { :controller => 'issues', :action => 'create', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64/edit" }, + { :controller => 'issues', :action => 'edit', :id => '64' } + ) + assert_routing( + { :method => 'put', :path => "/issues/1.xml" }, + { :controller => 'issues', :action => 'update', :id => '1', + :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/issues/1.xml" }, + { :controller => 'issues', :action => 'destroy', :id => '1', + :format => 'xml' } + ) + end + + def test_issues_rest_actions_scoped_under_project + assert_routing( + { :method => 'get', :path => "/projects/23/issues" }, + { :controller => 'issues', :action => 'index', :project_id => '23' } + ) + assert_routing( + { :method => 'get', :path => "/projects/23/issues.pdf" }, + { :controller => 'issues', :action => 'index', :project_id => '23', + :format => 'pdf' } + ) + assert_routing( + { :method => 'get', :path => "/projects/23/issues.atom" }, + { :controller => 'issues', :action => 'index', :project_id => '23', + :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/projects/23/issues.xml" }, + { :controller => 'issues', :action => 'index', :project_id => '23', + :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/projects/23/issues" }, + { :controller => 'issues', :action => 'create', :project_id => '23' } + ) + assert_routing( + { :method => 'get', :path => "/projects/23/issues/new" }, + { :controller => 'issues', :action => 'new', :project_id => '23' } + ) + end + + def test_issues_form_update + ["post", "put"].each do |method| + assert_routing( + { :method => method, :path => "/projects/23/issues/update_form" }, + { :controller => 'issues', :action => 'update_form', :project_id => '23' } + ) + end + end + + def test_issues_extra_actions + assert_routing( + { :method => 'get', :path => "/projects/23/issues/64/copy" }, + { :controller => 'issues', :action => 'new', :project_id => '23', + :copy_from => '64' } + ) + # For updating the bulk edit form + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/issues/bulk_edit" }, + { :controller => 'issues', :action => 'bulk_edit' } + ) + end + assert_routing( + { :method => 'post', :path => "/issues/bulk_update" }, + { :controller => 'issues', :action => 'bulk_update' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2c/2c2690417bf095320f6312e550c41841ed5d033a.svn-base --- a/.svn/pristine/2c/2c2690417bf095320f6312e550c41841ed5d033a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,165 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class AccountControllerOpenidTest < ActionController::TestCase - tests AccountController - fixtures :users, :roles - - def setup - User.current = nil - Setting.openid = '1' - end - - def teardown - Setting.openid = '0' - end - - if Object.const_defined?(:OpenID) - - def test_login_with_openid_for_existing_user - Setting.self_registration = '3' - existing_user = User.new(:firstname => 'Cool', - :lastname => 'User', - :mail => 'user@somedomain.com', - :identity_url => 'http://openid.example.com/good_user') - existing_user.login = 'cool_user' - assert existing_user.save! - - post :login, :openid_url => existing_user.identity_url - assert_redirected_to '/my/page' - end - - def test_login_with_invalid_openid_provider - Setting.self_registration = '0' - post :login, :openid_url => 'http;//openid.example.com/good_user' - assert_redirected_to home_url - end - - def test_login_with_openid_for_existing_non_active_user - Setting.self_registration = '2' - existing_user = User.new(:firstname => 'Cool', - :lastname => 'User', - :mail => 'user@somedomain.com', - :identity_url => 'http://openid.example.com/good_user', - :status => User::STATUS_REGISTERED) - existing_user.login = 'cool_user' - assert existing_user.save! - - post :login, :openid_url => existing_user.identity_url - assert_redirected_to '/login' - end - - def test_login_with_openid_with_new_user_created - Setting.self_registration = '3' - post :login, :openid_url => 'http://openid.example.com/good_user' - assert_redirected_to '/my/account' - user = User.find_by_login('cool_user') - assert user - assert_equal 'Cool', user.firstname - assert_equal 'User', user.lastname - end - - def test_login_with_openid_with_new_user_and_self_registration_off - Setting.self_registration = '0' - post :login, :openid_url => 'http://openid.example.com/good_user' - assert_redirected_to home_url - user = User.find_by_login('cool_user') - assert_nil user - end - - def test_login_with_openid_with_new_user_created_with_email_activation_should_have_a_token - Setting.self_registration = '1' - post :login, :openid_url => 'http://openid.example.com/good_user' - assert_redirected_to '/login' - user = User.find_by_login('cool_user') - assert user - - token = Token.find_by_user_id_and_action(user.id, 'register') - assert token - end - - def test_login_with_openid_with_new_user_created_with_manual_activation - Setting.self_registration = '2' - post :login, :openid_url => 'http://openid.example.com/good_user' - assert_redirected_to '/login' - user = User.find_by_login('cool_user') - assert user - assert_equal User::STATUS_REGISTERED, user.status - end - - def test_login_with_openid_with_new_user_with_conflict_should_register - Setting.self_registration = '3' - existing_user = User.new(:firstname => 'Cool', :lastname => 'User', :mail => 'user@somedomain.com') - existing_user.login = 'cool_user' - assert existing_user.save! - - post :login, :openid_url => 'http://openid.example.com/good_user' - assert_response :success - assert_template 'register' - assert assigns(:user) - assert_equal 'http://openid.example.com/good_user', assigns(:user)[:identity_url] - end - - def test_login_with_openid_with_new_user_with_missing_information_should_register - Setting.self_registration = '3' - - post :login, :openid_url => 'http://openid.example.com/good_blank_user' - assert_response :success - assert_template 'register' - assert assigns(:user) - assert_equal 'http://openid.example.com/good_blank_user', assigns(:user)[:identity_url] - - assert_select 'input[name=?]', 'user[login]' - assert_select 'input[name=?]', 'user[password]' - assert_select 'input[name=?]', 'user[password_confirmation]' - assert_select 'input[name=?][value=?]', 'user[identity_url]', 'http://openid.example.com/good_blank_user' - end - - def test_register_after_login_failure_should_not_require_user_to_enter_a_password - Setting.self_registration = '3' - - assert_difference 'User.count' do - post :register, :user => { - :login => 'good_blank_user', - :password => '', - :password_confirmation => '', - :firstname => 'Cool', - :lastname => 'User', - :mail => 'user@somedomain.com', - :identity_url => 'http://openid.example.com/good_blank_user' - } - assert_response 302 - end - - user = User.first(:order => 'id DESC') - assert_equal 'http://openid.example.com/good_blank_user', user.identity_url - assert user.hashed_password.blank?, "Hashed password was #{user.hashed_password}" - end - - def test_setting_openid_should_return_true_when_set_to_true - assert_equal true, Setting.openid? - end - - else - puts "Skipping openid tests." - - def test_dummy - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2c/2c3a4de5492d19130df8d9d9f0bc6d32734619b7.svn-base --- a/.svn/pristine/2c/2c3a4de5492d19130df8d9d9f0bc6d32734619b7.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,341 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'redmine/scm/adapters/abstract_adapter' -require 'cgi' - -module Redmine - module Scm - module Adapters - class MercurialAdapter < AbstractAdapter - - # Mercurial executable name - HG_BIN = Redmine::Configuration['scm_mercurial_command'] || "hg" - HELPERS_DIR = File.dirname(__FILE__) + "/mercurial" - HG_HELPER_EXT = "#{HELPERS_DIR}/redminehelper.py" - TEMPLATE_NAME = "hg-template" - TEMPLATE_EXTENSION = "tmpl" - - # raised if hg command exited with error, e.g. unknown revision. - class HgCommandAborted < CommandFailed; end - - class << self - def client_command - @@bin ||= HG_BIN - end - - def sq_bin - @@sq_bin ||= shell_quote_command - end - - def client_version - @@client_version ||= (hgversion || []) - end - - def client_available - client_version_above?([1, 2]) - end - - def hgversion - # The hg version is expressed either as a - # release number (eg 0.9.5 or 1.0) or as a revision - # id composed of 12 hexa characters. - theversion = hgversion_from_command_line.dup - if theversion.respond_to?(:force_encoding) - theversion.force_encoding('ASCII-8BIT') - end - if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)}) - m[2].scan(%r{\d+}).collect(&:to_i) - end - end - - def hgversion_from_command_line - shellout("#{sq_bin} --version") { |io| io.read }.to_s - end - - def template_path - @@template_path ||= template_path_for(client_version) - end - - def template_path_for(version) - "#{HELPERS_DIR}/#{TEMPLATE_NAME}-1.0.#{TEMPLATE_EXTENSION}" - end - end - - def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) - super - @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding - end - - def path_encoding - @path_encoding - end - - def info - tip = summary['repository']['tip'] - Info.new(:root_url => CGI.unescape(summary['repository']['root']), - :lastrev => Revision.new(:revision => tip['revision'], - :scmid => tip['node'])) - # rescue HgCommandAborted - rescue Exception => e - logger.error "hg: error during getting info: #{e.message}" - nil - end - - def tags - as_ary(summary['repository']['tag']).map { |e| e['name'] } - end - - # Returns map of {'tag' => 'nodeid', ...} - def tagmap - alist = as_ary(summary['repository']['tag']).map do |e| - e.values_at('name', 'node') - end - Hash[*alist.flatten] - end - - def branches - brs = [] - as_ary(summary['repository']['branch']).each do |e| - br = Branch.new(e['name']) - br.revision = e['revision'] - br.scmid = e['node'] - brs << br - end - brs - end - - # Returns map of {'branch' => 'nodeid', ...} - def branchmap - alist = as_ary(summary['repository']['branch']).map do |e| - e.values_at('name', 'node') - end - Hash[*alist.flatten] - end - - def summary - return @summary if @summary - hg 'rhsummary' do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - @summary = parse_xml(output)['rhsummary'] - rescue - end - end - end - private :summary - - def entries(path=nil, identifier=nil, options={}) - p1 = scm_iconv(@path_encoding, 'UTF-8', path) - manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)), - CGI.escape(without_leading_slash(p1.to_s))) do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - parse_xml(output)['rhmanifest']['repository']['manifest'] - rescue - end - end - path_prefix = path.blank? ? '' : with_trailling_slash(path) - - entries = Entries.new - as_ary(manifest['dir']).each do |e| - n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name'])) - p = "#{path_prefix}#{n}" - entries << Entry.new(:name => n, :path => p, :kind => 'dir') - end - - as_ary(manifest['file']).each do |e| - n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name'])) - p = "#{path_prefix}#{n}" - lr = Revision.new(:revision => e['revision'], :scmid => e['node'], - :identifier => e['node'], - :time => Time.at(e['time'].to_i)) - entries << Entry.new(:name => n, :path => p, :kind => 'file', - :size => e['size'].to_i, :lastrev => lr) - end - - entries - rescue HgCommandAborted - nil # means not found - end - - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) - revs = Revisions.new - each_revision(path, identifier_from, identifier_to, options) { |e| revs << e } - revs - end - - # Iterates the revisions by using a template file that - # makes Mercurial produce a xml output. - def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={}) - hg_args = ['log', '--debug', '-C', '--style', self.class.template_path] - hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}" - hg_args << '--limit' << options[:limit] if options[:limit] - hg_args << hgtarget(path) unless path.blank? - log = hg(*hg_args) do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - # Mercurial < 1.5 does not support footer template for '' - parse_xml("#{output}")['log'] - rescue - end - end - as_ary(log['logentry']).each do |le| - cpalist = as_ary(le['paths']['path-copied']).map do |e| - [e['__content__'], e['copyfrom-path']].map do |s| - scm_iconv('UTF-8', @path_encoding, CGI.unescape(s)) - end - end - cpmap = Hash[*cpalist.flatten] - paths = as_ary(le['paths']['path']).map do |e| - p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) ) - {:action => e['action'], - :path => with_leading_slash(p), - :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil), - :from_revision => (cpmap.member?(p) ? le['node'] : nil)} - end.sort { |a, b| a[:path] <=> b[:path] } - parents_ary = [] - as_ary(le['parents']['parent']).map do |par| - parents_ary << par['__content__'] if par['__content__'] != "000000000000" - end - yield Revision.new(:revision => le['revision'], - :scmid => le['node'], - :author => (le['author']['__content__'] rescue ''), - :time => Time.parse(le['date']['__content__']), - :message => le['msg']['__content__'], - :paths => paths, - :parents => parents_ary) - end - self - end - - # Returns list of nodes in the specified branch - def nodes_in_branch(branch, options={}) - hg_args = ['rhlog', '--template', '{node|short}\n', '--rhbranch', CGI.escape(branch)] - hg_args << '--from' << CGI.escape(branch) - hg_args << '--to' << '0' - hg_args << '--limit' << options[:limit] if options[:limit] - hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } } - end - - def diff(path, identifier_from, identifier_to=nil) - hg_args = %w|rhdiff| - if identifier_to - hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from) - else - hg_args << '-c' << hgrev(identifier_from) - end - unless path.blank? - p = scm_iconv(@path_encoding, 'UTF-8', path) - hg_args << CGI.escape(hgtarget(p)) - end - diff = [] - hg *hg_args do |io| - io.each_line do |line| - diff << line - end - end - diff - rescue HgCommandAborted - nil # means not found - end - - def cat(path, identifier=nil) - p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path)) - hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io| - io.binmode - io.read - end - rescue HgCommandAborted - nil # means not found - end - - def annotate(path, identifier=nil) - p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path)) - blame = Annotate.new - hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io| - io.each_line do |line| - line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding) - next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$} - r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3, - :identifier => $3) - blame.add_line($4.rstrip, r) - end - end - blame - rescue HgCommandAborted - # means not found or cannot be annotated - Annotate.new - end - - class Revision < Redmine::Scm::Adapters::Revision - # Returns the readable identifier - def format_identifier - "#{revision}:#{scmid}" - end - end - - # Runs 'hg' command with the given args - def hg(*args, &block) - repo_path = root_url || url - full_args = ['-R', repo_path, '--encoding', 'utf-8'] - full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}" - full_args << '--config' << 'diff.git=false' - full_args += args - ret = shellout( - self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '), - &block - ) - if $? && $?.exitstatus != 0 - raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}" - end - ret - end - private :hg - - # Returns correct revision identifier - def hgrev(identifier, sq=false) - rev = identifier.blank? ? 'tip' : identifier.to_s - rev = shell_quote(rev) if sq - rev - end - private :hgrev - - def hgtarget(path) - path ||= '' - root_url + '/' + without_leading_slash(path) - end - private :hgtarget - - def as_ary(o) - return [] unless o - o.is_a?(Array) ? o : Array[o] - end - private :as_ary - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2c/2c4595b19c4d38826042063686f09c958ada056c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2c/2c4595b19c4d38826042063686f09c958ada056c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class DocumentCategoryCustomField < CustomField + def type_name + :enumeration_doc_categories + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2c/2c5433d8c76b122107da1fadd0241a5eef71ece6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2c/2c5433d8c76b122107da1fadd0241a5eef71ece6.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,51 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingEnumerationsTest < ActionController::IntegrationTest + def test_enumerations + assert_routing( + { :method => 'get', :path => "/enumerations" }, + { :controller => 'enumerations', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/enumerations/new" }, + { :controller => 'enumerations', :action => 'new' } + ) + assert_routing( + { :method => 'post', :path => "/enumerations" }, + { :controller => 'enumerations', :action => 'create' } + ) + assert_routing( + { :method => 'get', :path => "/enumerations/2/edit" }, + { :controller => 'enumerations', :action => 'edit', :id => '2' } + ) + assert_routing( + { :method => 'put', :path => "/enumerations/2" }, + { :controller => 'enumerations', :action => 'update', :id => '2' } + ) + assert_routing( + { :method => 'delete', :path => "/enumerations/2" }, + { :controller => 'enumerations', :action => 'destroy', :id => '2' } + ) + assert_routing( + { :method => 'get', :path => "/enumerations/issue_priorities.xml" }, + { :controller => 'enumerations', :action => 'index', :type => 'issue_priorities', :format => 'xml' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2c/2c76b08002398b97784edeca2011d8a77c453847.svn-base --- a/.svn/pristine/2c/2c76b08002398b97784edeca2011d8a77c453847.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -

    <%=l(:label_news_latest)%>

    - -<%= render(:partial => 'news/news', - :collection => News.find(:all, - :limit => 10, - :order => "#{News.table_name}.created_on DESC", - :conditions => "#{News.table_name}.project_id in (#{@user.projects.collect{|m| m.id}.join(',')})", - :include => [:project, :author])) unless @user.projects.empty? %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2c/2c94248d7eb61aa48f3ae6ffba79f9dd6a0b9346.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2c/2c94248d7eb61aa48f3ae6ffba79f9dd6a0b9346.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,76 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../test_helper', __FILE__) + +class Redmine::Utils::DateCalculationTest < ActiveSupport::TestCase + include Redmine::Utils::DateCalculation + + def test_working_days_without_non_working_week_days + with_settings :non_working_week_days => [] do + assert_working_days 18, '2012-10-09', '2012-10-27' + assert_working_days 6, '2012-10-09', '2012-10-15' + assert_working_days 5, '2012-10-09', '2012-10-14' + assert_working_days 3, '2012-10-09', '2012-10-12' + assert_working_days 3, '2012-10-14', '2012-10-17' + assert_working_days 16, '2012-10-14', '2012-10-30' + end + end + + def test_working_days_with_non_working_week_days + with_settings :non_working_week_days => %w(6 7) do + assert_working_days 14, '2012-10-09', '2012-10-27' + assert_working_days 4, '2012-10-09', '2012-10-15' + assert_working_days 4, '2012-10-09', '2012-10-14' + assert_working_days 3, '2012-10-09', '2012-10-12' + assert_working_days 8, '2012-10-09', '2012-10-19' + assert_working_days 8, '2012-10-11', '2012-10-23' + assert_working_days 2, '2012-10-14', '2012-10-17' + assert_working_days 11, '2012-10-14', '2012-10-30' + end + end + + def test_add_working_days_without_non_working_week_days + with_settings :non_working_week_days => [] do + assert_add_working_days '2012-10-10', '2012-10-10', 0 + assert_add_working_days '2012-10-11', '2012-10-10', 1 + assert_add_working_days '2012-10-12', '2012-10-10', 2 + assert_add_working_days '2012-10-13', '2012-10-10', 3 + assert_add_working_days '2012-10-25', '2012-10-10', 15 + end + end + + def test_add_working_days_with_non_working_week_days + with_settings :non_working_week_days => %w(6 7) do + assert_add_working_days '2012-10-10', '2012-10-10', 0 + assert_add_working_days '2012-10-11', '2012-10-10', 1 + assert_add_working_days '2012-10-12', '2012-10-10', 2 + assert_add_working_days '2012-10-15', '2012-10-10', 3 + assert_add_working_days '2012-10-31', '2012-10-10', 15 + assert_add_working_days '2012-10-19', '2012-10-09', 8 + assert_add_working_days '2012-10-23', '2012-10-11', 8 + end + end + + def assert_working_days(expected_days, from, to) + assert_equal expected_days, working_days(from.to_date, to.to_date) + end + + def assert_add_working_days(expected_date, from, working_days) + assert_equal expected_date.to_date, add_working_days(from.to_date, working_days) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2c/2caf818a4b5c04288552a4ab14703d73f1006735.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2c/2caf818a4b5c04288552a4ab14703d73f1006735.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,217 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class ProjectEnumerationsControllerTest < ActionController::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :custom_fields, :custom_fields_projects, + :custom_fields_trackers, :custom_values, + :time_entries + + self.use_transactional_fixtures = false + + def setup + @request.session[:user_id] = nil + Setting.default_language = 'en' + end + + def test_update_to_override_system_activities + @request.session[:user_id] = 2 # manager + billable_field = TimeEntryActivityCustomField.find_by_name("Billable") + + put :update, :project_id => 1, :enumerations => { + "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design, De-activate + "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}, # Development, Change custom value + "14"=>{"parent_id"=>"14", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"}, # Inactive Activity, Activate with custom value + "11"=>{"parent_id"=>"11", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"} # QA, no changes + } + + assert_response :redirect + assert_redirected_to '/projects/ecookbook/settings/activities' + + # Created project specific activities... + project = Project.find('ecookbook') + + # ... Design + design = project.time_entry_activities.find_by_name("Design") + assert design, "Project activity not found" + + assert_equal 9, design.parent_id # Relate to the system activity + assert_not_equal design.parent.id, design.id # Different records + assert_equal design.parent.name, design.name # Same name + assert !design.active? + + # ... Development + development = project.time_entry_activities.find_by_name("Development") + assert development, "Project activity not found" + + assert_equal 10, development.parent_id # Relate to the system activity + assert_not_equal development.parent.id, development.id # Different records + assert_equal development.parent.name, development.name # Same name + assert development.active? + assert_equal "0", development.custom_value_for(billable_field).value + + # ... Inactive Activity + previously_inactive = project.time_entry_activities.find_by_name("Inactive Activity") + assert previously_inactive, "Project activity not found" + + assert_equal 14, previously_inactive.parent_id # Relate to the system activity + assert_not_equal previously_inactive.parent.id, previously_inactive.id # Different records + assert_equal previously_inactive.parent.name, previously_inactive.name # Same name + assert previously_inactive.active? + assert_equal "1", previously_inactive.custom_value_for(billable_field).value + + # ... QA + assert_equal nil, project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified" + end + + def test_update_will_update_project_specific_activities + @request.session[:user_id] = 2 # manager + + project_activity = TimeEntryActivity.new({ + :name => 'Project Specific', + :parent => TimeEntryActivity.first, + :project => Project.find(1), + :active => true + }) + assert project_activity.save + project_activity_two = TimeEntryActivity.new({ + :name => 'Project Specific Two', + :parent => TimeEntryActivity.last, + :project => Project.find(1), + :active => true + }) + assert project_activity_two.save + + + put :update, :project_id => 1, :enumerations => { + project_activity.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # De-activate + project_activity_two.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"} # De-activate + } + + assert_response :redirect + assert_redirected_to '/projects/ecookbook/settings/activities' + + # Created project specific activities... + project = Project.find('ecookbook') + assert_equal 2, project.time_entry_activities.count + + activity_one = project.time_entry_activities.find_by_name(project_activity.name) + assert activity_one, "Project activity not found" + assert_equal project_activity.id, activity_one.id + assert !activity_one.active? + + activity_two = project.time_entry_activities.find_by_name(project_activity_two.name) + assert activity_two, "Project activity not found" + assert_equal project_activity_two.id, activity_two.id + assert !activity_two.active? + end + + def test_update_when_creating_new_activities_will_convert_existing_data + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size + + @request.session[:user_id] = 2 # manager + put :update, :project_id => 1, :enumerations => { + "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"} # Design, De-activate + } + assert_response :redirect + + # No more TimeEntries using the system activity + assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries still assigned to system activities" + # All TimeEntries using project activity + project_specific_activity = TimeEntryActivity.find_by_parent_id_and_project_id(9, 1) + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(project_specific_activity.id, 1).size, "No Time Entries assigned to the project activity" + end + + def test_update_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised + # TODO: Need to cause an exception on create but these tests + # aren't setup for mocking. Just create a record now so the + # second one is a dupicate + parent = TimeEntryActivity.find(9) + TimeEntryActivity.create!({:name => parent.name, :project_id => 1, :position => parent.position, :active => true}) + TimeEntry.create!({:project_id => 1, :hours => 1.0, :user => User.find(1), :issue_id => 3, :activity_id => 10, :spent_on => '2009-01-01'}) + + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size + assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size + + @request.session[:user_id] = 2 # manager + put :update, :project_id => 1, :enumerations => { + "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design + "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"} # Development, Change custom value + } + assert_response :redirect + + # TimeEntries shouldn't have been reassigned on the failed record + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries are not assigned to system activities" + # TimeEntries shouldn't have been reassigned on the saved record either + assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size, "Time Entries are not assigned to system activities" + end + + def test_destroy + @request.session[:user_id] = 2 # manager + project_activity = TimeEntryActivity.new({ + :name => 'Project Specific', + :parent => TimeEntryActivity.first, + :project => Project.find(1), + :active => true + }) + assert project_activity.save + project_activity_two = TimeEntryActivity.new({ + :name => 'Project Specific Two', + :parent => TimeEntryActivity.last, + :project => Project.find(1), + :active => true + }) + assert project_activity_two.save + + delete :destroy, :project_id => 1 + assert_response :redirect + assert_redirected_to '/projects/ecookbook/settings/activities' + + assert_nil TimeEntryActivity.find_by_id(project_activity.id) + assert_nil TimeEntryActivity.find_by_id(project_activity_two.id) + end + + def test_destroy_should_reassign_time_entries_back_to_the_system_activity + @request.session[:user_id] = 2 # manager + project_activity = TimeEntryActivity.new({ + :name => 'Project Specific Design', + :parent => TimeEntryActivity.find(9), + :project => Project.find(1), + :active => true + }) + assert project_activity.save + assert TimeEntry.update_all("activity_id = '#{project_activity.id}'", ["project_id = ? AND activity_id = ?", 1, 9]) + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size + + delete :destroy, :project_id => 1 + assert_response :redirect + assert_redirected_to '/projects/ecookbook/settings/activities' + + assert_nil TimeEntryActivity.find_by_id(project_activity.id) + assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size, "TimeEntries still assigned to project specific activity" + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "TimeEntries still assigned to project specific activity" + end + +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2d/2d1b6f7772fdd853c88983e5f12b084f72e3159d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2d1b6f7772fdd853c88983e5f12b084f72e3159d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,13 @@ +class AddCommitAccessPermission < ActiveRecord::Migration + def self.up + Role.all.select { |r| not r.builtin? }.each do |r| + r.add_permission!(:commit_access) + end + end + + def self.down + Role.all.select { |r| not r.builtin? }.each do |r| + r.remove_permission!(:commit_access) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2d/2d32672cd69f23850e3e5406acee5c4056b23479.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2d32672cd69f23850e3e5406acee5c4056b23479.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,74 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class SettingsController < ApplicationController + layout 'admin' + menu_item :plugins, :only => :plugin + + helper :queries + + before_filter :require_admin + + def index + edit + render :action => 'edit' + end + + def edit + @notifiables = Redmine::Notifiable.all + if request.post? && params[:settings] && params[:settings].is_a?(Hash) + settings = (params[:settings] || {}).dup.symbolize_keys + settings.each do |name, value| + Setting.set_from_params name, value + end + flash[:notice] = l(:notice_successful_update) + redirect_to settings_path(:tab => params[:tab]) + else + @options = {} + user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]} + @options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]} + @deliveries = ActionMailer::Base.perform_deliveries + + @guessed_host_and_path = request.host_with_port.dup + @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? + + @commit_update_keywords = Setting.commit_update_keywords.dup + @commit_update_keywords = [{}] unless @commit_update_keywords.is_a?(Array) && @commit_update_keywords.any? + + Redmine::Themes.rescan + end + end + + def plugin + @plugin = Redmine::Plugin.find(params[:id]) + unless @plugin.configurable? + render_404 + return + end + + if request.post? + Setting.send "plugin_#{@plugin.id}=", params[:settings] + flash[:notice] = l(:notice_successful_update) + redirect_to plugin_settings_path(@plugin) + else + @partial = @plugin.settings[:partial] + @settings = Setting.send "plugin_#{@plugin.id}" + end + rescue Redmine::PluginNotFound + render_404 + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2d/2d40a9e2c317c7ef78f729e12dcba72428f142c7.svn-base --- a/.svn/pristine/2d/2d40a9e2c317c7ef78f729e12dcba72428f142c7.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1083 +0,0 @@ -sl: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [Nedelja, Ponedeljek, Torek, Sreda, ÄŒetrtek, Petek, Sobota] - abbr_day_names: [Ned, Pon, To, Sr, ÄŒet, Pet, Sob] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Januar, Februar, Marec, April, Maj, Junij, Julij, Avgust, September, Oktober, November, December] - abbr_month_names: [~, Jan, Feb, Mar, Apr, Maj, Jun, Jul, Aug, Sep, Okt, Nov, Dec] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%a, %d %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "pol minute" - less_than_x_seconds: - one: "manj kot 1. sekundo" - other: "manj kot %{count} sekund" - x_seconds: - one: "1. sekunda" - other: "%{count} sekund" - less_than_x_minutes: - one: "manj kot minuto" - other: "manj kot %{count} minut" - x_minutes: - one: "1 minuta" - other: "%{count} minut" - about_x_hours: - one: "okrog 1. ure" - other: "okrog %{count} ur" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 dan" - other: "%{count} dni" - about_x_months: - one: "okrog 1. mesec" - other: "okrog %{count} mesecev" - x_months: - one: "1 mesec" - other: "%{count} mesecev" - about_x_years: - one: "okrog 1. leto" - other: "okrog %{count} let" - over_x_years: - one: "veÄ kot 1. leto" - other: "veÄ kot %{count} let" - almost_x_years: - one: "skoraj 1. leto" - other: "skoraj %{count} let" - - number: - format: - separator: "," - delimiter: "." - precision: 3 - human: - format: - precision: 3 - delimiter: "" - storage_units: - format: "%n %u" - units: - kb: KB - tb: TB - gb: GB - byte: - one: Byte - other: Bytes - mb: MB - -# Used in array.to_sentence. - support: - array: - sentence_connector: "in" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1. napaka je prepreÄila temu %{model} da bi se shranil" - other: "%{count} napak je prepreÄilo temu %{model} da bi se shranil" - messages: - inclusion: "ni vkljuÄen na seznamu" - exclusion: "je rezerviran" - invalid: "je napaÄen" - confirmation: "ne ustreza potrdilu" - accepted: "mora biti sprejet" - empty: "ne sme biti prazen" - blank: "ne sme biti neizpolnjen" - too_long: "je predolg" - too_short: "je prekratek" - wrong_length: "je napaÄne dolžine" - taken: "je že zaseden" - not_a_number: "ni Å¡tevilo" - not_a_date: "ni veljaven datum" - greater_than: "mora biti veÄji kot %{count}" - greater_than_or_equal_to: "mora biti veÄji ali enak kot %{count}" - equal_to: "mora biti enak kot %{count}" - less_than: "mora biti manjÅ¡i kot %{count}" - less_than_or_equal_to: "mora biti manjÅ¡i ali enak kot %{count}" - odd: "mora biti sodo" - even: "mora biti liho" - greater_than_start_date: "mora biti kasnejÅ¡i kot zaÄetni datum" - not_same_project: "ne pripada istemu projektu" - circular_dependency: "Ta odnos bi povzroÄil krožno odvisnost" - cant_link_an_issue_with_a_descendant: "Zahtevek ne more biti povezan s svojo podnalogo" - - actionview_instancetag_blank_option: Prosimo izberite - - general_text_No: 'Ne' - general_text_Yes: 'Da' - general_text_no: 'ne' - general_text_yes: 'da' - general_lang_name: 'SlovenÅ¡Äina' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: RaÄun je bil uspeÅ¡no posodobljen. - notice_account_invalid_creditentials: NapaÄno uporabniÅ¡ko ime ali geslo - notice_account_password_updated: Geslo je bilo uspeÅ¡no posodobljeno. - notice_account_wrong_password: NapaÄno geslo - notice_account_register_done: RaÄun je bil uspeÅ¡no ustvarjen. Za aktivacijo potrdite povezavo, ki vam je bila poslana v e-nabiralnik. - notice_account_unknown_email: Neznan uporabnik. - notice_can_t_change_password: Ta raÄun za overovljanje uporablja zunanji. Gesla ni mogoÄe spremeniti. - notice_account_lost_email_sent: Poslano vam je bilo e-pismo z navodili za izbiro novega gesla. - notice_account_activated: VaÅ¡ raÄun je bil aktiviran. Sedaj se lahko prijavite. - notice_successful_create: Ustvarjanje uspelo. - notice_successful_update: Posodobitev uspela. - notice_successful_delete: Izbris uspel. - notice_successful_connection: Povezava uspela. - notice_file_not_found: Stran na katero se želite povezati ne obstaja ali pa je bila umaknjena. - notice_locking_conflict: Drug uporabnik je posodobil podatke. - notice_not_authorized: Nimate privilegijev za dostop do te strani. - notice_email_sent: "E-poÅ¡tno sporoÄilo je bilo poslano %{value}" - notice_email_error: "Ob poÅ¡iljanju e-sporoÄila je priÅ¡lo do napake (%{value})" - notice_feeds_access_key_reseted: VaÅ¡ RSS dostopni kljuÄ je bil ponastavljen. - notice_failed_to_save_issues: "Neuspelo shranjevanje %{count} zahtevka na %{total} izbranem: %{ids}." - notice_no_issue_selected: "Izbran ni noben zahtevek! Prosimo preverite zahtevke, ki jih želite urediti." - notice_account_pending: "VaÅ¡ raÄun je bil ustvarjen in Äaka na potrditev s strani administratorja." - notice_default_data_loaded: Privzete nastavitve so bile uspeÅ¡no naložene. - notice_unable_delete_version: Verzije ni bilo mogoÄe izbrisati. - - error_can_t_load_default_data: "Privzetih nastavitev ni bilo mogoÄe naložiti: %{value}" - error_scm_not_found: "Vnos ali revizija v shrambi ni bila najdena ." - error_scm_command_failed: "Med vzpostavljem povezave s shrambo je priÅ¡lo do napake: %{value}" - error_scm_annotate: "Vnos ne obstaja ali pa ga ni mogoÄe komentirati." - error_issue_not_found_in_project: 'Zahtevek ni bil najden ali pa ne pripada temu projektu' - - mail_subject_lost_password: "VaÅ¡e %{value} geslo" - mail_body_lost_password: 'Za spremembo glesla kliknite na naslednjo povezavo:' - mail_subject_register: "Aktivacija %{value} vaÅ¡ega raÄuna" - mail_body_register: 'Za aktivacijo vaÅ¡ega raÄuna kliknite na naslednjo povezavo:' - mail_body_account_information_external: "Za prijavo lahko uporabite vaÅ¡ %{value} raÄun." - mail_body_account_information: Informacije o vaÅ¡em raÄunu - mail_subject_account_activation_request: "%{value} zahtevek za aktivacijo raÄuna" - mail_body_account_activation_request: "Registriral se je nov uporabnik (%{value}). RaÄun Äaka na vaÅ¡o odobritev:" - mail_subject_reminder: "%{count} zahtevek(zahtevki) zapadejo v naslednjih %{days} dneh" - mail_body_reminder: "%{count} zahtevek(zahtevki), ki so vam dodeljeni bodo zapadli v naslednjih %{days} dneh:" - - gui_validation_error: 1 napaka - gui_validation_error_plural: "%{count} napak" - - field_name: Ime - field_description: Opis - field_summary: Povzetek - field_is_required: Zahtevano - field_firstname: Ime - field_lastname: Priimek - field_mail: E-naslov - field_filename: Datoteka - field_filesize: Velikost - field_downloads: Prenosi - field_author: Avtor - field_created_on: Ustvarjen - field_updated_on: Posodobljeno - field_field_format: Format - field_is_for_all: Za vse projekte - field_possible_values: Možne vrednosti - field_regexp: Regularni izraz - field_min_length: Minimalna dolžina - field_max_length: Maksimalna dolžina - field_value: Vrednost - field_category: Kategorija - field_title: Naslov - field_project: Projekt - field_issue: Zahtevek - field_status: Status - field_notes: Zabeležka - field_is_closed: Zahtevek zaprt - field_is_default: Privzeta vrednost - field_tracker: Vrsta zahtevka - field_subject: Tema - field_due_date: Do datuma - field_assigned_to: Dodeljen - field_priority: Prioriteta - field_fixed_version: Ciljna verzija - field_user: Uporabnik - field_role: Vloga - field_homepage: DomaÄa stran - field_is_public: Javno - field_parent: Podprojekt projekta - field_is_in_roadmap: Zahtevki prikazani na zemljevidu - field_login: Prijava - field_mail_notification: E-poÅ¡tna oznanila - field_admin: Administrator - field_last_login_on: ZadnjiÄ povezan(a) - field_language: Jezik - field_effective_date: Datum - field_password: Geslo - field_new_password: Novo geslo - field_password_confirmation: Potrditev - field_version: Verzija - field_type: Tip - field_host: Gostitelj - field_port: Vrata - field_account: RaÄun - field_base_dn: Bazni DN - field_attr_login: Oznaka za prijavo - field_attr_firstname: Oznaka za ime - field_attr_lastname: Oznaka za priimek - field_attr_mail: Oznaka za e-naslov - field_onthefly: Sprotna izdelava uporabnikov - field_start_date: ZaÄetek - field_done_ratio: "% Narejeno" - field_auth_source: NaÄin overovljanja - field_hide_mail: Skrij moj e-naslov - field_comments: Komentar - field_url: URL - field_start_page: ZaÄetna stran - field_subproject: Podprojekt - field_hours: Ur - field_activity: Aktivnost - field_spent_on: Datum - field_identifier: Identifikator - field_is_filter: Uporabljen kot filter - field_issue_to: Povezan zahtevek - field_delay: Zamik - field_assignable: Zahtevki so lahko dodeljeni tej vlogi - field_redirect_existing_links: Preusmeri obstojeÄe povezave - field_estimated_hours: Ocenjen Äas - field_column_names: Stolpci - field_time_zone: ÄŒasovni pas - field_searchable: Zmožen iskanja - field_default_value: Privzeta vrednost - field_comments_sorting: Prikaži komentarje - field_parent_title: MatiÄna stran - - setting_app_title: Naslov aplikacije - setting_app_subtitle: Podnaslov aplikacije - setting_welcome_text: Pozdravno besedilo - setting_default_language: Privzeti jezik - setting_login_required: Zahtevano overovljanje - setting_self_registration: Samostojna registracija - setting_attachment_max_size: Maksimalna velikost priponk - setting_issues_export_limit: Skrajna meja za izvoz zahtevkov - setting_mail_from: E-naslov za emisijo - setting_bcc_recipients: Prejemniki slepih kopij (bcc) - setting_plain_text_mail: navadno e-sporoÄilo (ne HTML) - setting_host_name: Ime gostitelja in pot - setting_text_formatting: Oblikovanje besedila - setting_wiki_compression: Stiskanje Wiki zgodovine - setting_feeds_limit: Meja obsega RSS virov - setting_default_projects_public: Novi projekti so privzeto javni - setting_autofetch_changesets: Samodejni izvleÄek zapisa sprememb - setting_sys_api_enabled: OmogoÄi WS za upravljanje shrambe - setting_commit_ref_keywords: Sklicne kljuÄne besede - setting_commit_fix_keywords: Urejanje kljuÄne besede - setting_autologin: Avtomatska prijava - setting_date_format: Oblika datuma - setting_time_format: Oblika Äasa - setting_cross_project_issue_relations: Dovoli povezave zahtevkov med razliÄnimi projekti - setting_issue_list_default_columns: Privzeti stolpci prikazani na seznamu zahtevkov - setting_emails_footer: Noga e-sporoÄil - setting_protocol: Protokol - setting_per_page_options: Å tevilo elementov na stran - setting_user_format: Oblika prikaza uporabnikov - setting_activity_days_default: Prikaz dni na aktivnost projekta - setting_display_subprojects_issues: Privzeti prikaz zahtevkov podprojektov v glavnem projektu - setting_enabled_scm: OmogoÄen SCM - setting_mail_handler_api_enabled: OmogoÄi WS za prihajajoÄo e-poÅ¡to - setting_mail_handler_api_key: API kljuÄ - setting_sequential_project_identifiers: Generiraj projektne identifikatorje sekvenÄno - setting_gravatar_enabled: Uporabljaj Gravatar ikone - setting_diff_max_lines_displayed: Maksimalno Å¡tevilo prikazanih vrstic razliÄnosti - - permission_edit_project: Uredi projekt - permission_select_project_modules: Izberi module projekta - permission_manage_members: Uredi Älane - permission_manage_versions: Uredi verzije - permission_manage_categories: Urejanje kategorij zahtevkov - permission_add_issues: Dodaj zahtevke - permission_edit_issues: Uredi zahtevke - permission_manage_issue_relations: Uredi odnose med zahtevki - permission_add_issue_notes: Dodaj zabeležke - permission_edit_issue_notes: Uredi zabeležke - permission_edit_own_issue_notes: Uredi lastne zabeležke - permission_move_issues: Premakni zahtevke - permission_delete_issues: IzbriÅ¡i zahtevke - permission_manage_public_queries: Uredi javna povpraÅ¡evanja - permission_save_queries: Shrani povpraÅ¡evanje - permission_view_gantt: Poglej gantogram - permission_view_calendar: Poglej koledar - permission_view_issue_watchers: Oglej si listo spremeljevalcev - permission_add_issue_watchers: Dodaj spremljevalce - permission_log_time: Beleži porabljen Äas - permission_view_time_entries: Poglej porabljen Äas - permission_edit_time_entries: Uredi beležko Äasa - permission_edit_own_time_entries: Uredi beležko lastnega Äasa - permission_manage_news: Uredi novice - permission_comment_news: Komentiraj novice - permission_manage_documents: Uredi dokumente - permission_view_documents: Poglej dokumente - permission_manage_files: Uredi datoteke - permission_view_files: Poglej datoteke - permission_manage_wiki: Uredi wiki - permission_rename_wiki_pages: Preimenuj wiki strani - permission_delete_wiki_pages: IzbriÅ¡i wiki strani - permission_view_wiki_pages: Poglej wiki - permission_view_wiki_edits: Poglej wiki zgodovino - permission_edit_wiki_pages: Uredi wiki strani - permission_delete_wiki_pages_attachments: IzbriÅ¡i priponke - permission_protect_wiki_pages: ZaÅ¡Äiti wiki strani - permission_manage_repository: Uredi shrambo - permission_browse_repository: Prebrskaj shrambo - permission_view_changesets: Poglej zapis sprememb - permission_commit_access: Dostop za predajo - permission_manage_boards: Uredi table - permission_view_messages: Poglej sporoÄila - permission_add_messages: Objavi sporoÄila - permission_edit_messages: Uredi sporoÄila - permission_edit_own_messages: Uredi lastna sporoÄila - permission_delete_messages: IzbriÅ¡i sporoÄila - permission_delete_own_messages: IzbriÅ¡i lastna sporoÄila - - project_module_issue_tracking: Sledenje zahtevkom - project_module_time_tracking: Sledenje Äasa - project_module_news: Novice - project_module_documents: Dokumenti - project_module_files: Datoteke - project_module_wiki: Wiki - project_module_repository: Shramba - project_module_boards: Table - - label_user: Uporabnik - label_user_plural: Uporabniki - label_user_new: Nov uporabnik - label_project: Projekt - label_project_new: Nov projekt - label_project_plural: Projekti - label_x_projects: - zero: ni projektov - one: 1 projekt - other: "%{count} projektov" - label_project_all: Vsi projekti - label_project_latest: Zadnji projekti - label_issue: Zahtevek - label_issue_new: Nov zahtevek - label_issue_plural: Zahtevki - label_issue_view_all: Poglej vse zahtevke - label_issues_by: "Zahtevki od %{value}" - label_issue_added: Zahtevek dodan - label_issue_updated: Zahtevek posodobljen - label_document: Dokument - label_document_new: Nov dokument - label_document_plural: Dokumenti - label_document_added: Dokument dodan - label_role: Vloga - label_role_plural: Vloge - label_role_new: Nova vloga - label_role_and_permissions: Vloge in dovoljenja - label_member: ÄŒlan - label_member_new: Nov Älan - label_member_plural: ÄŒlani - label_tracker: Vrsta zahtevka - label_tracker_plural: Vrste zahtevkov - label_tracker_new: Nova vrsta zahtevka - label_workflow: Potek dela - label_issue_status: Stanje zahtevka - label_issue_status_plural: Stanje zahtevkov - label_issue_status_new: Novo stanje - label_issue_category: Kategorija zahtevka - label_issue_category_plural: Kategorije zahtevkov - label_issue_category_new: Nova kategorija - label_custom_field: Polje po meri - label_custom_field_plural: Polja po meri - label_custom_field_new: Novo polje po meri - label_enumerations: Seznami - label_enumeration_new: Nova vrednost - label_information: Informacija - label_information_plural: Informacije - label_please_login: Prosimo prijavite se - label_register: Registracija - label_password_lost: Izgubljeno geslo - label_home: Domov - label_my_page: Moja stran - label_my_account: Moj raÄun - label_my_projects: Moji projekti - label_administration: Upravljanje - label_login: Prijavi se - label_logout: Odjavi se - label_help: PomoÄ - label_reported_issues: Prijavljeni zahtevki - label_assigned_to_me_issues: Zahtevki dodeljeni meni - label_last_login: Zadnja povezava - label_registered_on: Registriran - label_activity: Aktivnost - label_overall_activity: Celotna aktivnost - label_user_activity: "Aktivnost %{value}" - label_new: Nov - label_logged_as: Prijavljen(a) kot - label_environment: Okolje - label_authentication: Overovitev - label_auth_source: NaÄin overovitve - label_auth_source_new: Nov naÄin overovitve - label_auth_source_plural: NaÄini overovitve - label_subproject_plural: Podprojekti - label_and_its_subprojects: "%{value} in njegovi podprojekti" - label_min_max_length: Min - Max dolžina - label_list: Seznam - label_date: Datum - label_integer: Celo Å¡tevilo - label_float: Decimalno Å¡tevilo - label_boolean: Boolean - label_string: Besedilo - label_text: Dolgo besedilo - label_attribute: Lastnost - label_attribute_plural: Lastnosti - label_download: "%{count} Prenos" - label_download_plural: "%{count} Prenosi" - label_no_data: Ni podatkov za prikaz - label_change_status: Spremeni stanje - label_history: Zgodovina - label_attachment: Datoteka - label_attachment_new: Nova datoteka - label_attachment_delete: IzbriÅ¡i datoteko - label_attachment_plural: Datoteke - label_file_added: Datoteka dodana - label_report: PoroÄilo - label_report_plural: PoroÄila - label_news: Novica - label_news_new: Dodaj novico - label_news_plural: Novice - label_news_latest: Zadnje novice - label_news_view_all: Poglej vse novice - label_news_added: Dodane novice - label_settings: Nastavitve - label_overview: Pregled - label_version: Verzija - label_version_new: Nova verzija - label_version_plural: Verzije - label_confirmation: Potrditev - label_export_to: 'Na razpolago tudi v:' - label_read: Preberi... - label_public_projects: Javni projekti - label_open_issues: odprt zahtevek - label_open_issues_plural: odprti zahtevki - label_closed_issues: zaprt zahtevek - label_closed_issues_plural: zaprti zahtevki - label_x_open_issues_abbr_on_total: - zero: 0 odprtih / %{total} - one: 1 odprt / %{total} - other: "%{count} odprtih / %{total}" - label_x_open_issues_abbr: - zero: 0 odprtih - one: 1 odprt - other: "%{count} odprtih" - label_x_closed_issues_abbr: - zero: 0 zaprtih - one: 1 zaprt - other: "%{count} zaprtih" - label_total: Skupaj - label_permissions: Dovoljenja - label_current_status: Trenutno stanje - label_new_statuses_allowed: Novi zahtevki dovoljeni - label_all: vsi - label_none: noben - label_nobody: nihÄe - label_next: Naslednji - label_previous: PrejÅ¡nji - label_used_by: V uporabi od - label_details: Podrobnosti - label_add_note: Dodaj zabeležko - label_per_page: Na stran - label_calendar: Koledar - label_months_from: mesecev od - label_gantt: Gantogram - label_internal: Notranji - label_last_changes: "zadnjih %{count} sprememb" - label_change_view_all: Poglej vse spremembe - label_personalize_page: Individualiziraj to stran - label_comment: Komentar - label_comment_plural: Komentarji - label_x_comments: - zero: ni komentarjev - one: 1 komentar - other: "%{count} komentarjev" - label_comment_add: Dodaj komentar - label_comment_added: Komentar dodan - label_comment_delete: IzbriÅ¡i komentarje - label_query: Iskanje po meri - label_query_plural: Iskanja po meri - label_query_new: Novo iskanje - label_filter_add: Dodaj filter - label_filter_plural: Filtri - label_equals: je enako - label_not_equals: ni enako - label_in_less_than: v manj kot - label_in_more_than: v veÄ kot - label_in: v - label_today: danes - label_all_time: v vsem Äasu - label_yesterday: vÄeraj - label_this_week: ta teden - label_last_week: pretekli teden - label_last_n_days: "zadnjih %{count} dni" - label_this_month: ta mesec - label_last_month: zadnji mesec - label_this_year: to leto - label_date_range: Razpon datumov - label_less_than_ago: manj kot dni nazaj - label_more_than_ago: veÄ kot dni nazaj - label_ago: dni nazaj - label_contains: vsebuje - label_not_contains: ne vsebuje - label_day_plural: dni - label_repository: Shramba - label_repository_plural: Shrambe - label_browse: Prebrskaj - label_modification: "%{count} sprememba" - label_modification_plural: "%{count} spremembe" - label_revision: Revizija - label_revision_plural: Revizije - label_associated_revisions: Povezane revizije - label_added: dodano - label_modified: spremenjeno - label_copied: kopirano - label_renamed: preimenovano - label_deleted: izbrisano - label_latest_revision: Zadnja revizija - label_latest_revision_plural: Zadnje revizije - label_view_revisions: Poglej revizije - label_max_size: NajveÄja velikost - label_sort_highest: Premakni na vrh - label_sort_higher: Premakni gor - label_sort_lower: Premakni dol - label_sort_lowest: Premakni na dno - label_roadmap: NaÄrt - label_roadmap_due_in: "Do %{value}" - label_roadmap_overdue: "%{value} zakasnel" - label_roadmap_no_issues: Ni zahtevkov za to verzijo - label_search: IÅ¡Äi - label_result_plural: Rezultati - label_all_words: Vse besede - label_wiki: Wiki - label_wiki_edit: Wiki urejanje - label_wiki_edit_plural: Wiki urejanja - label_wiki_page: Wiki stran - label_wiki_page_plural: Wiki strani - label_index_by_title: Razvrsti po naslovu - label_index_by_date: Razvrsti po datumu - label_current_version: Trenutna verzija - label_preview: Predogled - label_feed_plural: RSS viri - label_changes_details: Podrobnosti o vseh spremembah - label_issue_tracking: Sledenje zahtevkom - label_spent_time: Porabljen Äas - label_f_hour: "%{value} ura" - label_f_hour_plural: "%{value} ur" - label_time_tracking: Sledenje Äasu - label_change_plural: Spremembe - label_statistics: Statistika - label_commits_per_month: Predaj na mesec - label_commits_per_author: Predaj na avtorja - label_view_diff: Preglej razlike - label_diff_inline: znotraj - label_diff_side_by_side: vzporedno - label_options: Možnosti - label_copy_workflow_from: Kopiraj potek dela od - label_permissions_report: PoroÄilo o dovoljenjih - label_watched_issues: Spremljani zahtevki - label_related_issues: Povezani zahtevki - label_applied_status: Uveljavljeno stanje - label_loading: Nalaganje... - label_relation_new: Nova povezava - label_relation_delete: IzbriÅ¡i povezavo - label_relates_to: povezan z - label_duplicates: duplikati - label_duplicated_by: dupliciral - label_blocks: blok - label_blocked_by: blokiral - label_precedes: ima prednost pred - label_follows: sledi - label_end_to_start: konec na zaÄetek - label_end_to_end: konec na konec - label_start_to_start: zaÄetek na zaÄetek - label_start_to_end: zaÄetek na konec - label_stay_logged_in: Ostani prijavljen(a) - label_disabled: onemogoÄi - label_show_completed_versions: Prikaži zakljuÄene verzije - label_me: jaz - label_board: Forum - label_board_new: Nov forum - label_board_plural: Forumi - label_topic_plural: Teme - label_message_plural: SporoÄila - label_message_last: Zadnje sporoÄilo - label_message_new: Novo sporoÄilo - label_message_posted: SporoÄilo dodano - label_reply_plural: Odgovori - label_send_information: PoÅ¡lji informacijo o raÄunu uporabniku - label_year: Leto - label_month: Mesec - label_week: Teden - label_date_from: Do - label_date_to: Do - label_language_based: Glede na uporabnikov jezik - label_sort_by: "Razporedi po %{value}" - label_send_test_email: PoÅ¡lji testno e-pismo - label_feeds_access_key_created_on: "RSS dostopni kljuÄ narejen %{value} nazaj" - label_module_plural: Moduli - label_added_time_by: "Dodan %{author} %{age} nazaj" - label_updated_time_by: "Posodobljen od %{author} %{age} nazaj" - label_updated_time: "Posodobljen %{value} nazaj" - label_jump_to_a_project: SkoÄi na projekt... - label_file_plural: Datoteke - label_changeset_plural: Zapisi sprememb - label_default_columns: Privzeti stolpci - label_no_change_option: (Ni spremembe) - label_bulk_edit_selected_issues: Uredi izbrane zahtevke skupaj - label_theme: Tema - label_default: Privzeto - label_search_titles_only: PreiÅ¡Äi samo naslove - label_user_mail_option_all: "Za vsak dogodek v vseh mojih projektih" - label_user_mail_option_selected: "Za vsak dogodek samo na izbranih projektih..." - label_user_mail_no_self_notified: "Ne želim biti opozorjen(a) na spremembe, ki jih naredim sam(a)" - label_registration_activation_by_email: aktivacija raÄuna po e-poÅ¡ti - label_registration_manual_activation: roÄna aktivacija raÄuna - label_registration_automatic_activation: samodejna aktivacija raÄuna - label_display_per_page: "Na stran: %{value}" - label_age: Starost - label_change_properties: Sprememba lastnosti - label_general: SploÅ¡no - label_more: VeÄ - label_scm: SCM - label_plugins: VtiÄniki - label_ldap_authentication: LDAP overovljanje - label_downloads_abbr: D/L - label_optional_description: Neobvezen opis - label_add_another_file: Dodaj Å¡e eno datoteko - label_preferences: Preference - label_chronological_order: KronoloÅ¡ko - label_reverse_chronological_order: Obrnjeno kronoloÅ¡ko - label_planning: NaÄrtovanje - label_incoming_emails: PrihajajoÄa e-poÅ¡ta - label_generate_key: Ustvari kljuÄ - label_issue_watchers: Spremljevalci - label_example: Vzorec - - button_login: Prijavi se - button_submit: PoÅ¡lji - button_save: Shrani - button_check_all: OznaÄi vse - button_uncheck_all: OdznaÄi vse - button_delete: IzbriÅ¡i - button_create: Ustvari - button_test: Testiraj - button_edit: Uredi - button_add: Dodaj - button_change: Spremeni - button_apply: Uporabi - button_clear: PoÄisti - button_lock: Zakleni - button_unlock: Odkleni - button_download: Prenesi - button_list: Seznam - button_view: Pogled - button_move: Premakni - button_back: Nazaj - button_cancel: PrekliÄi - button_activate: Aktiviraj - button_sort: Razvrsti - button_log_time: Beleži Äas - button_rollback: Povrni na to verzijo - button_watch: Spremljaj - button_unwatch: Ne spremljaj - button_reply: Odgovori - button_archive: Arhiviraj - button_unarchive: Odarhiviraj - button_reset: Ponastavi - button_rename: Preimenuj - button_change_password: Spremeni geslo - button_copy: Kopiraj - button_annotate: ZapiÅ¡i pripombo - button_update: Posodobi - button_configure: Konfiguriraj - button_quote: Citiraj - - status_active: aktivni - status_registered: registriran - status_locked: zaklenjen - - text_select_mail_notifications: Izberi dejanja za katera naj bodo poslana oznanila preko e-poÅ¡to. - text_regexp_info: npr. ^[A-Z0-9]+$ - text_min_max_length_info: 0 pomeni brez omejitev - text_project_destroy_confirmation: Ali ste prepriÄani da želite izbrisati izbrani projekt in vse z njim povezane podatke? - text_subprojects_destroy_warning: "Njegov(i) podprojekt(i): %{value} bodo prav tako izbrisani." - text_workflow_edit: Izberite vlogo in zahtevek za urejanje poteka dela - text_are_you_sure: Ali ste prepriÄani? - text_tip_issue_begin_day: naloga z zaÄetkom na ta dan - text_tip_issue_end_day: naloga z zakljuÄkom na ta dan - text_tip_issue_begin_end_day: naloga ki se zaÄne in konÄa ta dan - text_caracters_maximum: "najveÄ %{count} znakov." - text_caracters_minimum: "Mora biti vsaj dolg vsaj %{count} znakov." - text_length_between: "Dolžina med %{min} in %{max} znakov." - text_tracker_no_workflow: Potek dela za to vrsto zahtevka ni doloÄen - text_unallowed_characters: Nedovoljeni znaki - text_comma_separated: Dovoljenih je veÄ vrednosti (loÄenih z vejico). - text_issues_ref_in_commit_messages: Zahtevki sklicev in popravkov v sporoÄilu predaje - text_issue_added: "Zahtevek %{id} je sporoÄil(a) %{author}." - text_issue_updated: "Zahtevek %{id} je posodobil(a) %{author}." - text_wiki_destroy_confirmation: Ali ste prepriÄani da želite izbrisati ta wiki in vso njegovo vsebino? - text_issue_category_destroy_question: "Nekateri zahtevki (%{count}) so dodeljeni tej kategoriji. Kaj želite storiti?" - text_issue_category_destroy_assignments: Odstrani naloge v kategoriji - text_issue_category_reassign_to: Ponovno dodeli zahtevke tej kategoriji - text_user_mail_option: "Na neizbrane projekte boste prejemali le obvestila o zadevah ki jih spremljate ali v katere ste vkljuÄeni (npr. zahtevki katerih avtor(ica) ste)" - text_no_configuration_data: "Vloge, vrste zahtevkov, statusi zahtevkov in potek dela Å¡e niso bili doloÄeni. \nZelo priporoÄljivo je, da naložite privzeto konfiguracijo, ki jo lahko kasneje tudi prilagodite." - text_load_default_configuration: Naloži privzeto konfiguracijo - text_status_changed_by_changeset: "Dodano v zapis sprememb %{value}." - text_issues_destroy_confirmation: 'Ali ste prepriÄani, da želite izbrisati izbrani(e) zahtevek(ke)?' - text_select_project_modules: 'Izberite module, ki jih želite omogoÄiti za ta projekt:' - text_default_administrator_account_changed: Spremenjen privzeti administratorski raÄun - text_file_repository_writable: OmogoÄeno pisanje v shrambo datotek - text_rmagick_available: RMagick je na voljo(neobvezno) - text_destroy_time_entries_question: "%{hours} ur je bilo opravljenih na zahtevku, ki ga želite izbrisati. Kaj želite storiti?" - text_destroy_time_entries: IzbriÅ¡i opravljene ure - text_assign_time_entries_to_project: Predaj opravljene ure projektu - text_reassign_time_entries: 'Prenesi opravljene ure na ta zahtevek:' - text_user_wrote: "%{value} je napisal(a):" - text_enumeration_destroy_question: "%{count} objektov je doloÄenih tej vrednosti." - text_enumeration_category_reassign_to: 'Ponastavi jih na to vrednost:' - text_email_delivery_not_configured: "E-poÅ¡tna dostava ni nastavljena in oznanila so onemogoÄena.\nNastavite vaÅ¡ SMTP strežnik v config/configuration.yml in ponovno zaženite aplikacijo da ga omogoÄite.\n" - text_repository_usernames_mapping: "Izberite ali posodobite Redmine uporabnika dodeljenega vsakemu uporabniÅ¡kemu imenu najdenemu v zapisniku shrambe.\n Uporabniki z enakim Redmine ali shrambinem uporabniÅ¡kem imenu ali e-poÅ¡tnem naslovu so samodejno dodeljeni." - text_diff_truncated: '... Ta sprememba je bila odsekana ker presega najveÄjo velikost ki je lahko prikazana.' - - default_role_manager: Upravnik - default_role_developer: Razvijalec - default_role_reporter: PoroÄevalec - default_tracker_bug: HroÅ¡Ä - default_tracker_feature: Funkcija - default_tracker_support: Podpora - default_issue_status_new: Nov - default_issue_status_in_progress: V teku - default_issue_status_resolved: ReÅ¡en - default_issue_status_feedback: Povratna informacija - default_issue_status_closed: ZakljuÄen - default_issue_status_rejected: Zavrnjen - default_doc_category_user: UporabniÅ¡ka dokumentacija - default_doc_category_tech: TehniÄna dokumentacija - default_priority_low: Nizka - default_priority_normal: ObiÄajna - default_priority_high: Visoka - default_priority_urgent: Urgentna - default_priority_immediate: TakojÅ¡nje ukrepanje - default_activity_design: Oblikovanje - default_activity_development: Razvoj - - enumeration_issue_priorities: Prioritete zahtevkov - enumeration_doc_categories: Kategorije dokumentov - enumeration_activities: Aktivnosti (sledenje Äasa) - warning_attachments_not_saved: "%{count} datotek(e) ni bilo mogoÄe shraniti." - field_editable: Uredljivo - text_plugin_assets_writable: Zapisljiva mapa za vtiÄnike - label_display: Prikaz - button_create_and_continue: Ustvari in nadaljuj - text_custom_field_possible_values_info: 'Ena vrstica za vsako vrednost' - setting_repository_log_display_limit: NajveÄje Å¡tevilo prikazanih revizij v log datoteki - setting_file_max_size_displayed: NajveÄja velikost besedilnih datotek v vkljuÄenem prikazu - field_watcher: Opazovalec - setting_openid: Dovoli OpenID prijavo in registracijo - field_identity_url: OpenID URL - label_login_with_open_id_option: ali se prijavi z OpenID - field_content: Vsebina - label_descending: PadajoÄe - label_sort: Razvrsti - label_ascending: NaraÅ¡ÄajoÄe - label_date_from_to: Od %{start} do %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Ta stran ima %{descendants} podstran(i) in naslednik(ov). Kaj želite storiti? - text_wiki_page_reassign_children: Znova dodeli podstrani tej glavni strani - text_wiki_page_nullify_children: Obdrži podstrani kot glavne strani - text_wiki_page_destroy_children: IzbriÅ¡i podstrani in vse njihove naslednike - setting_password_min_length: Minimalna dolžina gesla - field_group_by: Združi rezultate po - mail_subject_wiki_content_updated: "'%{id}' wiki stran je bila posodobljena" - label_wiki_content_added: Wiki stran dodana - mail_subject_wiki_content_added: "'%{id}' wiki stran je bila dodana" - mail_body_wiki_content_added: "%{author} je dodal '%{id}' wiki stran" - label_wiki_content_updated: Wiki stran posodobljena - mail_body_wiki_content_updated: "%{author} je posodobil '%{id}' wiki stran." - permission_add_project: Ustvari projekt - setting_new_project_user_role_id: Vloga, dodeljena neadministratorskemu uporabniku, ki je ustvaril projekt - label_view_all_revisions: Poglej vse revizije - label_tag: Oznaka - label_branch: Veja - error_no_tracker_in_project: Noben sledilnik ni povezan s tem projektom. Prosimo preverite nastavitve projekta. - error_no_default_issue_status: Privzeti zahtevek ni definiran. Prosimo preverite svoje nastavitve (Pojdite na "Administracija -> Stanje zahtevkov"). - text_journal_changed: "%{label} se je spremenilo iz %{old} v %{new}" - text_journal_set_to: "%{label} nastavljeno na %{value}" - text_journal_deleted: "%{label} izbrisan (%{old})" - label_group_plural: Skupine - label_group: Skupina - label_group_new: Nova skupina - label_time_entry_plural: Porabljen Äas - text_journal_added: "%{label} %{value} dodan" - field_active: Aktiven - enumeration_system_activity: Sistemska aktivnost - permission_delete_issue_watchers: IzbriÅ¡i opazovalce - version_status_closed: zaprt - version_status_locked: zaklenjen - version_status_open: odprt - error_can_not_reopen_issue_on_closed_version: Zahtevek dodeljen zaprti verziji ne more biti ponovno odprt - label_user_anonymous: Anonimni - button_move_and_follow: Premakni in sledi - setting_default_projects_modules: Privzeti moduli za nove projekte - setting_gravatar_default: Privzeta Gravatar slika - field_sharing: Deljenje - label_version_sharing_hierarchy: S projektno hierarhijo - label_version_sharing_system: Z vsemi projekti - label_version_sharing_descendants: S podprojekti - label_version_sharing_tree: Z drevesom projekta - label_version_sharing_none: Ni deljeno - error_can_not_archive_project: Ta projekt ne more biti arhiviran - button_duplicate: Podvoji - button_copy_and_follow: Kopiraj in sledi - label_copy_source: Vir - setting_issue_done_ratio: IzraÄunaj razmerje opravljenega zahtevka z - setting_issue_done_ratio_issue_status: Uporabi stanje zahtevka - error_issue_done_ratios_not_updated: Razmerje opravljenega zahtevka ni bilo posodobljeno. - error_workflow_copy_target: Prosimo izberite ciljni(e) sledilnik(e) in vlogo(e) - setting_issue_done_ratio_issue_field: Uporabi polje zahtevka - label_copy_same_as_target: Enako kot cilj - label_copy_target: Cilj - notice_issue_done_ratios_updated: Razmerje opravljenega zahtevka posodobljeno. - error_workflow_copy_source: Prosimo izberite vir zahtevka ali vlogo - label_update_issue_done_ratios: Posodobi razmerje opravljenega zahtevka - setting_start_of_week: ZaÄni koledarje z - permission_view_issues: Poglej zahtevke - label_display_used_statuses_only: Prikaži samo stanja ki uporabljajo ta sledilnik - label_revision_id: Revizija %{value} - label_api_access_key: API dostopni kljuÄ - label_api_access_key_created_on: API dostopni kljuÄ ustvarjen pred %{value} - label_feeds_access_key: RSS dostopni kljuÄ - notice_api_access_key_reseted: VaÅ¡ API dostopni kljuÄ je bil ponastavljen. - setting_rest_api_enabled: OmogoÄi REST spletni servis - label_missing_api_access_key: ManjkajoÄ API dostopni kljuÄ - label_missing_feeds_access_key: ManjkajoÄ RSS dostopni kljuÄ - button_show: Prikaži - text_line_separated: Dovoljenih veÄ vrednosti (ena vrstica za vsako vrednost). - setting_mail_handler_body_delimiters: Odreži e-poÅ¡to po eni od teh vrstic - permission_add_subprojects: Ustvari podprojekte - label_subproject_new: Nov podprojekt - text_own_membership_delete_confirmation: |- - Odstranili boste nekatere ali vse od dovoljenj zaradi Äesar morda ne boste mogli veÄ urejati tega projekta. - Ali ste prepriÄani, da želite nadaljevati? - label_close_versions: Zapri dokonÄane verzije - label_board_sticky: Lepljivo - label_board_locked: Zaklenjeno - permission_export_wiki_pages: Izvozi wiki strani - setting_cache_formatted_text: Predpomni oblikovano besedilo - permission_manage_project_activities: Uredi aktivnosti projekta - error_unable_delete_issue_status: Stanja zahtevka ni bilo možno spremeniti - label_profile: Profil - permission_manage_subtasks: Uredi podnaloge - field_parent_issue: Nadrejena naloga - label_subtask_plural: Podnaloge - label_project_copy_notifications: Med kopiranjem projekta poÅ¡lji e-poÅ¡tno sporoÄilo - error_can_not_delete_custom_field: Polja po meri ni mogoÄe izbrisati - error_unable_to_connect: Povezava ni mogoÄa (%{value}) - error_can_not_remove_role: Ta vloga je v uporabi in je ni mogoÄe izbrisati. - error_can_not_delete_tracker: Ta sledilnik vsebuje zahtevke in se ga ne more izbrisati. - field_principal: Upravnik varnosti - label_my_page_block: Moj gradnik strani - notice_failed_to_save_members: "Shranjevanje uporabnika(ov) ni uspelo: %{errors}." - text_zoom_out: Približaj - text_zoom_in: Oddalji - notice_unable_delete_time_entry: Brisanje dnevnika porabljenaga Äasa ni mogoÄe. - label_overall_spent_time: Skupni porabljeni Äas - field_time_entries: Beleži porabljeni Äas - project_module_gantt: Gantogram - project_module_calendar: Koledear - button_edit_associated_wikipage: "Uredi povezano Wiki stran: %{page_title}" - field_text: Besedilno polje - label_user_mail_option_only_owner: Samo za stvari katerih lastnik sem - setting_default_notification_option: Privzeta možnost obveÅ¡Äanja - label_user_mail_option_only_my_events: Samo za stvari, ki jih opazujem ali sem v njih vpleten - label_user_mail_option_only_assigned: Samo za stvari, ki smo mi dodeljene - label_user_mail_option_none: Noben dogodek - field_member_of_group: PooblaÅ¡ÄenÄeva skupina - field_assigned_to_role: PooblaÅ¡ÄenÄeva vloga - notice_not_authorized_archived_project: Projekt, do katerega poskuÅ¡ate dostopati, je bil arhiviran. - label_principal_search: "PoiÅ¡Äi uporabnika ali skupino:" - label_user_search: "PoiÅ¡Äi uporabnikia:" - field_visible: Viden - setting_emails_header: Glava e-poÅ¡te - setting_commit_logtime_activity_id: Aktivnost zabeleženega Äasa - text_time_logged_by_changeset: Uporabljeno v spremembi %{value}. - setting_commit_logtime_enabled: OmogoÄi beleženje Äasa - notice_gantt_chart_truncated: Graf je bil odrezan, ker je prekoraÄil najveÄje dovoljeno Å¡tevilo elementov, ki se jih lahko prikaže (%{max}) - setting_gantt_items_limit: NajveÄje Å¡tevilo elementov prikazano na gantogramu - field_warn_on_leaving_unsaved: Opozori me, kadar zapuÅ¡Äam stran z neshranjenim besedilom - text_warn_on_leaving_unsaved: Trenutna stran vsebuje neshranjeno besedilo ki bo izgubljeno, Äe zapustite to stran. - label_my_queries: Moje poizvedbe po meri - text_journal_changed_no_detail: "%{label} posodobljen" - label_news_comment_added: Komentar dodan novici - button_expand_all: RazÅ¡iri vse - button_collapse_all: SkrÄi vse - label_additional_workflow_transitions_for_assignee: Dovoljeni dodatni prehodi kadar je uporabnik pooblaÅ¡Äenec - label_additional_workflow_transitions_for_author: Dovoljeni dodatni prehodi kadar je uporabnik avtor - label_bulk_edit_selected_time_entries: Skupinsko urejanje izbranih Äasovnih zapisov - text_time_entries_destroy_confirmation: Ali ste prepriÄani, da želite izbristai izbran(e) Äasovn(i/e) zapis(e)? - label_role_anonymous: Anonimni - label_role_non_member: NeÄlan - label_issue_note_added: Dodan zaznamek - label_issue_status_updated: Status posodobljen - label_issue_priority_updated: Prioriteta posodobljena - label_issues_visibility_own: Zahtevek ustvarjen s strani uporabnika ali dodeljen uporabniku - field_issues_visibility: Vidljivost zahtevkov - label_issues_visibility_all: Vsi zahtevki - permission_set_own_issues_private: Nastavi lastne zahtevke kot javne ali zasebne - field_is_private: Zaseben - permission_set_issues_private: Nastavi zahtevke kot javne ali zasebne - label_issues_visibility_public: Vsi nezasebni zahtevki - text_issues_destroy_descendants_confirmation: To bo izbrisalo tudi %{count} podnalog(o). - field_commit_logs_encoding: Kodiranje sporoÄil ob predaji - field_scm_path_encoding: Pot do kodiranja - text_scm_path_encoding_note: "Privzeto: UTF-8" - field_path_to_repository: Pot do shrambe - field_root_directory: Korenska mapa - field_cvs_module: Modul - field_cvsroot: CVSROOT - text_mercurial_repository_note: Lokalna shramba (npr. /hgrepo, c:\hgrepo) - text_scm_command: Ukaz - text_scm_command_version: Verzija - label_git_report_last_commit: SporoÄi zadnje uveljavljanje datotek in map - text_scm_config: Svoje SCM ukaze lahko nastavite v datoteki config/configuration.yml. Po urejanju prosimo ponovno zaženite aplikacijo. - text_scm_command_not_available: SCM ukaz ni na voljo. Prosimo preverite nastavitve v upravljalskem podoknu. - - text_git_repository_note: Shramba je prazna in lokalna (npr. /gitrepo, c:\gitrepo) - - notice_issue_successful_create: Ustvarjen zahtevek %{id}. - label_between: med - setting_issue_group_assignment: Dovoli dodeljevanje zahtevka skupinam - label_diff: diff - - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 zahtevek - one: 1 zahtevek - other: "%{count} zahtevki" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: vsi - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: S podprojekti - label_cross_project_tree: Z drevesom projekta - label_cross_project_hierarchy: S projektno hierarhijo - label_cross_project_system: Z vsemi projekti - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2d/2d5c5458ec41dd3315a619640122843b9e0e9952.svn-base --- a/.svn/pristine/2d/2d5c5458ec41dd3315a619640122843b9e0e9952.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'action_view/helpers/form_helper' - -class Redmine::Views::LabelledFormBuilder < ActionView::Helpers::FormBuilder - include Redmine::I18n - - (field_helpers.map(&:to_s) - %w(radio_button hidden_field fields_for) + - %w(date_select)).each do |selector| - src = <<-END_SRC - def #{selector}(field, options = {}) - label_for_field(field, options) + super(field, options.except(:label)).html_safe - end - END_SRC - class_eval src, __FILE__, __LINE__ - end - - def select(field, choices, options = {}, html_options = {}) - label_for_field(field, options) + super(field, choices, options, html_options.except(:label)).html_safe - end - - def time_zone_select(field, priority_zones = nil, options = {}, html_options = {}) - label_for_field(field, options) + super(field, priority_zones, options, html_options.except(:label)).html_safe - end - - # Returns a label tag for the given field - def label_for_field(field, options = {}) - return ''.html_safe if options.delete(:no_label) - text = options[:label].is_a?(Symbol) ? l(options[:label]) : options[:label] - text ||= l(("field_" + field.to_s.gsub(/\_id$/, "")).to_sym) - text += @template.content_tag("span", " *", :class => "required") if options.delete(:required) - @template.content_tag("label", text.html_safe, - :class => (@object && @object.errors[field].present? ? "error" : nil), - :for => (@object_name.to_s + "_" + field.to_s)) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2d/2d6ad8e6f87f6d379c227b9a81e69b5a26399856.svn-base --- a/.svn/pristine/2d/2d6ad8e6f87f6d379c227b9a81e69b5a26399856.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,254 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'account_controller' - -# Re-raise errors caught by the controller. -class AccountController; def rescue_action(e) raise e end; end - -class AccountControllerTest < ActionController::TestCase - fixtures :users, :roles - - def setup - @controller = AccountController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_get_login - get :login - assert_response :success - assert_template 'login' - - assert_select 'input[name=username]' - assert_select 'input[name=password]' - end - - def test_login_should_redirect_to_back_url_param - # request.uri is "test.host" in test environment - post :login, :username => 'jsmith', :password => 'jsmith', :back_url => 'http://test.host/issues/show/1' - assert_redirected_to '/issues/show/1' - end - - def test_login_should_not_redirect_to_another_host - post :login, :username => 'jsmith', :password => 'jsmith', :back_url => 'http://test.foo/fake' - assert_redirected_to '/my/page' - end - - def test_login_with_wrong_password - post :login, :username => 'admin', :password => 'bad' - assert_response :success - assert_template 'login' - - assert_select 'div.flash.error', :text => /Invalid user or password/ - assert_select 'input[name=username][value=admin]' - assert_select 'input[name=password]' - assert_select 'input[name=password][value]', 0 - end - - def test_login_should_rescue_auth_source_exception - source = AuthSource.create!(:name => 'Test') - User.find(2).update_attribute :auth_source_id, source.id - AuthSource.any_instance.stubs(:authenticate).raises(AuthSourceException.new("Something wrong")) - - post :login, :username => 'jsmith', :password => 'jsmith' - assert_response 500 - assert_error_tag :content => /Something wrong/ - end - - def test_login_should_reset_session - @controller.expects(:reset_session).once - - post :login, :username => 'jsmith', :password => 'jsmith' - assert_response 302 - end - - def test_logout - @request.session[:user_id] = 2 - get :logout - assert_redirected_to '/' - assert_nil @request.session[:user_id] - end - - def test_logout_should_reset_session - @controller.expects(:reset_session).once - - @request.session[:user_id] = 2 - get :logout - assert_response 302 - end - - def test_get_register_with_registration_on - with_settings :self_registration => '3' do - get :register - assert_response :success - assert_template 'register' - assert_not_nil assigns(:user) - - assert_tag 'input', :attributes => {:name => 'user[password]'} - assert_tag 'input', :attributes => {:name => 'user[password_confirmation]'} - end - end - - def test_get_register_with_registration_off_should_redirect - with_settings :self_registration => '0' do - get :register - assert_redirected_to '/' - end - end - - # See integration/account_test.rb for the full test - def test_post_register_with_registration_on - with_settings :self_registration => '3' do - assert_difference 'User.count' do - post :register, :user => { - :login => 'register', - :password => 'secret123', - :password_confirmation => 'secret123', - :firstname => 'John', - :lastname => 'Doe', - :mail => 'register@example.com' - } - assert_redirected_to '/my/account' - end - user = User.first(:order => 'id DESC') - assert_equal 'register', user.login - assert_equal 'John', user.firstname - assert_equal 'Doe', user.lastname - assert_equal 'register@example.com', user.mail - assert user.check_password?('secret123') - assert user.active? - end - end - - def test_post_register_with_registration_off_should_redirect - with_settings :self_registration => '0' do - assert_no_difference 'User.count' do - post :register, :user => { - :login => 'register', - :password => 'test', - :password_confirmation => 'test', - :firstname => 'John', - :lastname => 'Doe', - :mail => 'register@example.com' - } - assert_redirected_to '/' - end - end - end - - def test_get_lost_password_should_display_lost_password_form - get :lost_password - assert_response :success - assert_select 'input[name=mail]' - end - - def test_lost_password_for_active_user_should_create_a_token - Token.delete_all - ActionMailer::Base.deliveries.clear - assert_difference 'ActionMailer::Base.deliveries.size' do - assert_difference 'Token.count' do - with_settings :host_name => 'mydomain.foo', :protocol => 'http' do - post :lost_password, :mail => 'JSmith@somenet.foo' - assert_redirected_to '/login' - end - end - end - - token = Token.order('id DESC').first - assert_equal User.find(2), token.user - assert_equal 'recovery', token.action - - assert_select_email do - assert_select "a[href=?]", "http://mydomain.foo/account/lost_password?token=#{token.value}" - end - end - - def test_lost_password_for_unknown_user_should_fail - Token.delete_all - assert_no_difference 'Token.count' do - post :lost_password, :mail => 'invalid@somenet.foo' - assert_response :success - end - end - - def test_lost_password_for_non_active_user_should_fail - Token.delete_all - assert User.find(2).lock! - - assert_no_difference 'Token.count' do - post :lost_password, :mail => 'JSmith@somenet.foo' - assert_response :success - end - end - - def test_get_lost_password_with_token_should_display_the_password_recovery_form - user = User.find(2) - token = Token.create!(:action => 'recovery', :user => user) - - get :lost_password, :token => token.value - assert_response :success - assert_template 'password_recovery' - - assert_select 'input[type=hidden][name=token][value=?]', token.value - end - - def test_get_lost_password_with_invalid_token_should_redirect - get :lost_password, :token => "abcdef" - assert_redirected_to '/' - end - - def test_post_lost_password_with_token_should_change_the_user_password - user = User.find(2) - token = Token.create!(:action => 'recovery', :user => user) - - post :lost_password, :token => token.value, :new_password => 'newpass123', :new_password_confirmation => 'newpass123' - assert_redirected_to '/login' - user.reload - assert user.check_password?('newpass123') - assert_nil Token.find_by_id(token.id), "Token was not deleted" - end - - def test_post_lost_password_with_token_for_non_active_user_should_fail - user = User.find(2) - token = Token.create!(:action => 'recovery', :user => user) - user.lock! - - post :lost_password, :token => token.value, :new_password => 'newpass123', :new_password_confirmation => 'newpass123' - assert_redirected_to '/' - assert ! user.check_password?('newpass123') - end - - def test_post_lost_password_with_token_and_password_confirmation_failure_should_redisplay_the_form - user = User.find(2) - token = Token.create!(:action => 'recovery', :user => user) - - post :lost_password, :token => token.value, :new_password => 'newpass', :new_password_confirmation => 'wrongpass' - assert_response :success - assert_template 'password_recovery' - assert_not_nil Token.find_by_id(token.id), "Token was deleted" - - assert_select 'input[type=hidden][name=token][value=?]', token.value - end - - def test_post_lost_password_with_invalid_token_should_redirect - post :lost_password, :token => "abcdef", :new_password => 'newpass', :new_password_confirmation => 'newpass' - assert_redirected_to '/' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2d/2d704054566a75db6513ae575339e7bf14942054.svn-base --- a/.svn/pristine/2d/2d704054566a75db6513ae575339e7bf14942054.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class Redmine::WikiFormattingTest < ActiveSupport::TestCase - fixtures :issues - - def test_textile_formatter - assert_equal Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting.formatter_for('textile') - assert_equal Redmine::WikiFormatting::Textile::Helper, Redmine::WikiFormatting.helper_for('textile') - end - - def test_null_formatter - assert_equal Redmine::WikiFormatting::NullFormatter::Formatter, Redmine::WikiFormatting.formatter_for('') - assert_equal Redmine::WikiFormatting::NullFormatter::Helper, Redmine::WikiFormatting.helper_for('') - end - - def test_should_link_urls_and_email_addresses - raw = <<-DIFF -This is a sample *text* with a link: http://www.redmine.org -and an email address foo@example.net -DIFF - - expected = <<-EXPECTED -

    This is a sample *text* with a link: http://www.redmine.org
    -and an email address

    -EXPECTED - - assert_equal expected.gsub(%r{[\r\n\t]}, ''), Redmine::WikiFormatting::NullFormatter::Formatter.new(raw).to_html.gsub(%r{[\r\n\t]}, '') - end - - def test_supports_section_edit - with_settings :text_formatting => 'textile' do - assert_equal true, Redmine::WikiFormatting.supports_section_edit? - end - - with_settings :text_formatting => '' do - assert_equal false, Redmine::WikiFormatting.supports_section_edit? - end - end - - def test_cache_key_for_saved_object_should_no_be_nil - assert_not_nil Redmine::WikiFormatting.cache_key_for('textile', 'Text', Issue.find(1), :description) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2d/2d79f51bb3aabe1350946a7c8d7303968d415bb8.svn-base --- a/.svn/pristine/2d/2d79f51bb3aabe1350946a7c8d7303968d415bb8.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,229 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class IssuesTest < ActionController::IntegrationTest - fixtures :projects, - :users, - :roles, - :members, - :member_roles, - :trackers, - :projects_trackers, - :enabled_modules, - :issue_statuses, - :issues, - :enumerations, - :custom_fields, - :custom_values, - :custom_fields_trackers - - # create an issue - def test_add_issue - log_user('jsmith', 'jsmith') - get 'projects/1/issues/new', :tracker_id => '1' - assert_response :success - assert_template 'issues/new' - - post 'projects/1/issues', :tracker_id => "1", - :issue => { :start_date => "2006-12-26", - :priority_id => "4", - :subject => "new test issue", - :category_id => "", - :description => "new issue", - :done_ratio => "0", - :due_date => "", - :assigned_to_id => "" }, - :custom_fields => {'2' => 'Value for field 2'} - # find created issue - issue = Issue.find_by_subject("new test issue") - assert_kind_of Issue, issue - - # check redirection - assert_redirected_to :controller => 'issues', :action => 'show', :id => issue - follow_redirect! - assert_equal issue, assigns(:issue) - - # check issue attributes - assert_equal 'jsmith', issue.author.login - assert_equal 1, issue.project.id - assert_equal 1, issue.status.id - end - - def test_update_issue_form - log_user('jsmith', 'jsmith') - post 'projects/ecookbook/issues/new', :issue => { :tracker_id => "2"} - assert_response :success - assert_tag 'select', - :attributes => {:name => 'issue[tracker_id]'}, - :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}} - end - - # add then remove 2 attachments to an issue - def test_issue_attachments - log_user('jsmith', 'jsmith') - set_tmp_attachments_directory - - put 'issues/1', - :notes => 'Some notes', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}} - assert_redirected_to "/issues/1" - - # make sure attachment was saved - attachment = Issue.find(1).attachments.find_by_filename("testfile.txt") - assert_kind_of Attachment, attachment - assert_equal Issue.find(1), attachment.container - assert_equal 'This is an attachment', attachment.description - # verify the size of the attachment stored in db - #assert_equal file_data_1.length, attachment.filesize - # verify that the attachment was written to disk - assert File.exist?(attachment.diskfile) - - # remove the attachments - Issue.find(1).attachments.each(&:destroy) - assert_equal 0, Issue.find(1).attachments.length - end - - def test_other_formats_links_on_index - get '/projects/ecookbook/issues' - - %w(Atom PDF CSV).each do |format| - assert_tag :a, :content => format, - :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}", - :rel => 'nofollow' } - end - end - - def test_other_formats_links_on_index_without_project_id_in_url - get '/issues', :project_id => 'ecookbook' - - %w(Atom PDF CSV).each do |format| - assert_tag :a, :content => format, - :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}", - :rel => 'nofollow' } - end - end - - def test_pagination_links_on_index - Setting.per_page_options = '2' - get '/projects/ecookbook/issues' - - assert_tag :a, :content => '2', - :attributes => { :href => '/projects/ecookbook/issues?page=2' } - - end - - def test_pagination_links_on_index_without_project_id_in_url - Setting.per_page_options = '2' - get '/issues', :project_id => 'ecookbook' - - assert_tag :a, :content => '2', - :attributes => { :href => '/projects/ecookbook/issues?page=2' } - - end - - def test_issue_with_user_custom_field - @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true, :trackers => Tracker.all) - Role.anonymous.add_permission! :add_issues, :edit_issues - users = Project.find(1).users - tester = users.first - - # Issue form - get '/projects/ecookbook/issues/new' - assert_response :success - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, - :children => {:count => (users.size + 1)}, # +1 for blank value - :child => { - :tag => 'option', - :attributes => {:value => tester.id.to_s}, - :content => tester.name - } - - # Create issue - assert_difference 'Issue.count' do - post '/projects/ecookbook/issues', - :issue => { - :tracker_id => '1', - :priority_id => '4', - :subject => 'Issue with user custom field', - :custom_field_values => {@field.id.to_s => users.first.id.to_s} - } - end - issue = Issue.first(:order => 'id DESC') - assert_response 302 - - # Issue view - follow_redirect! - assert_tag :th, - :content => /Tester/, - :sibling => { - :tag => 'td', - :content => tester.name - } - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, - :children => {:count => (users.size + 1)}, # +1 for blank value - :child => { - :tag => 'option', - :attributes => {:value => tester.id.to_s, :selected => 'selected'}, - :content => tester.name - } - - # Update issue - new_tester = users[1] - assert_difference 'Journal.count' do - put "/issues/#{issue.id}", - :notes => 'Updating custom field', - :issue => { - :custom_field_values => {@field.id.to_s => new_tester.id.to_s} - } - end - assert_response 302 - - # Issue view - follow_redirect! - assert_tag :content => 'Tester', - :ancestor => {:tag => 'ul', :attributes => {:class => /details/}}, - :sibling => { - :content => tester.name, - :sibling => { - :content => new_tester.name - } - } - end - - def test_update_using_invalid_http_verbs - subject = 'Updated by an invalid http verb' - - get '/issues/update/1', {:issue => {:subject => subject}}, credentials('jsmith') - assert_response 404 - assert_not_equal subject, Issue.find(1).subject - - post '/issues/1', {:issue => {:subject => subject}}, credentials('jsmith') - assert_response 404 - assert_not_equal subject, Issue.find(1).subject - end - - def test_get_watch_should_be_invalid - assert_no_difference 'Watcher.count' do - get '/watchers/watch?object_type=issue&object_id=1', {}, credentials('jsmith') - assert_response 404 - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2d/2d90914cb67314627792a9dd72c779cc666b81ba.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2d90914cb67314627792a9dd72c779cc666b81ba.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,40 @@ +<%= wiki_page_breadcrumb(@page) %> + +<%= title [@page.pretty_title, project_wiki_page_path(@page.project, @page.title, :version => nil)], l(:label_history) %> + +<%= form_tag({:controller => 'wiki', :action => 'diff', + :project_id => @page.project, :id => @page.title}, + :method => :get) do %> + + + + + + + + + + + +<% show_diff = @versions.size > 1 %> +<% line_num = 1 %> +<% @versions.each do |ver| %> +"> + + + + + + + + +<% line_num += 1 %> +<% end %> + +
    #<%= l(:field_updated_on) %><%= l(:field_author) %><%= l(:field_comments) %>
    <%= link_to h(ver.version), :action => 'show', :id => @page.title, :project_id => @page.project, :version => ver.version %><%= radio_button_tag('version', ver.version, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('#cbto-#{line_num+1}').attr('checked', true);") if show_diff && (line_num < @versions.size) %><%= radio_button_tag('version_from', ver.version, (line_num==2), :id => "cbto-#{line_num}") if show_diff && (line_num > 1) %><%= format_time(ver.updated_on) %><%= link_to_user ver.author %><%=h ver.comments %> + <%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %> + <%= delete_link wiki_page_path(@page, :version => ver.version) if User.current.allowed_to?(:delete_wiki_pages, @page.project) && @version_count > 1 %> +
    +<%= submit_tag l(:label_view_diff), :class => 'small' if show_diff %> +<%= pagination_links_full @version_pages, @version_count %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2e/2e1157d3f81f3880c0395bb75f0b975ce97e7bd4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2e1157d3f81f3880c0395bb75f0b975ce97e7bd4.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,8 @@ +<%= error_messages_for @group %> + +
    +

    <%= f.text_field :name, :required => true, :size => 60 %>

    + <% @group.custom_field_values.each do |value| %> +

    <%= custom_field_tag_with_label :group, value %>

    + <% end %> +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2e/2e2d614b9e64145af9606e9c3e56e58eb315056d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2e2d614b9e64145af9606e9c3e56e58eb315056d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,34 @@ +
    +
    "> + <%= l(:label_filter_plural) %> +
    "> + <%= render :partial => 'queries/filters', :locals => {:query => @query} %> +
    +
    + +
    + +

    + <%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %> + <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue}, :class => 'icon icon-reload' %> +

    + +
    +<% query_params = params.slice(:f, :op, :v, :sort) %> +
      +
    • <%= link_to(l(:label_details), query_params.merge({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue }), + :class => (action_name == 'index' ? 'selected' : nil)) %>
    • +
    • <%= link_to(l(:label_report), query_params.merge({:controller => 'timelog', :action => 'report', :project_id => @project, :issue_id => @issue}), + :class => (action_name == 'report' ? 'selected' : nil)) %>
    • +
    +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2e/2e6c87fbf008e52f19138f6c4bd2e546325437b5.svn-base --- a/.svn/pristine/2e/2e6c87fbf008e52f19138f6c4bd2e546325437b5.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -<% if issues && issues.any? %> -<%= form_tag({}) do %> - - - - - - - - - <% for issue in issues %> - - - - - - - <% end %> - -
    #<%=l(:field_project)%><%=l(:field_tracker)%><%=l(:field_subject)%>
    - <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;', :id => nil) %> - <%= link_to(h(issue.id), :controller => 'issues', :action => 'show', :id => issue) %> - <%= link_to_project(issue.project) %><%=h issue.tracker %> - <%= link_to h(truncate(issue.subject, :length => 60)), :controller => 'issues', :action => 'show', :id => issue %> (<%=h issue.status %>) -
    -<% end %> -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2e/2e761f17a6eb6248759d869c76abad698e943338.svn-base --- a/.svn/pristine/2e/2e761f17a6eb6248759d869c76abad698e943338.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class GanttsControllerTest < ActionController::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :versions - - def test_gantt_should_work - i2 = Issue.find(2) - i2.update_attribute(:due_date, 1.month.from_now) - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - # Issue with start and due dates - i = Issue.find(1) - assert_not_nil i.due_date - assert_select "div a.issue", /##{i.id}/ - # Issue with on a targeted version should not be in the events but loaded in the html - i = Issue.find(2) - assert_select "div a.issue", /##{i.id}/ - end - - def test_gantt_should_work_without_issue_due_dates - Issue.update_all("due_date = NULL") - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_work_without_issue_and_version_due_dates - Issue.update_all("due_date = NULL") - Version.update_all("effective_date = NULL") - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_work_cross_project - get :show - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - assert_not_nil assigns(:gantt).query - assert_nil assigns(:gantt).project - end - - def test_gantt_should_not_disclose_private_projects - get :show - assert_response :success - assert_template 'gantts/show' - assert_tag 'a', :content => /eCookbook/ - # Root private project - assert_no_tag 'a', {:content => /OnlineStore/} - # Private children of a public project - assert_no_tag 'a', :content => /Private child of eCookbook/ - end - - def test_gantt_should_export_to_pdf - get :show, :project_id => 1, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_export_to_pdf_cross_project - get :show, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - if Object.const_defined?(:Magick) - def test_gantt_should_export_to_png - get :show, :project_id => 1, :format => 'png' - assert_response :success - assert_equal 'image/png', @response.content_type - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2e/2e7adb230b6d192e3aff1f060927ae3a68396262.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2e7adb230b6d192e3aff1f060927ae3a68396262.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,43 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module WikiHelper + + def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0) + pages = pages.group_by(&:parent) unless pages.is_a?(Hash) + s = ''.html_safe + if pages.has_key?(parent) + pages[parent].each do |page| + attrs = "value='#{page.id}'" + attrs << " selected='selected'" if selected == page + indent = (level > 0) ? (' ' * level * 2 + '» ') : '' + + s << content_tag('option', (indent + h(page.pretty_title)).html_safe, :value => page.id.to_s, :selected => selected == page) + + wiki_page_options_for_select(pages, selected, page, level + 1) + end + end + s + end + + def wiki_page_breadcrumb(page) + breadcrumb(page.ancestors.reverse.collect {|parent| + link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project, :version => nil}) + }) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2e/2e8224806b7f3ca405d76c48dfaa7adc74623a47.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2e8224806b7f3ca405d76c48dfaa7adc74623a47.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,37 @@ +module Redmine + module Info + class << self + def app_name; 'Redmine' end + def url; 'http://www.redmine.org/' end + def help_url; 'http://www.redmine.org/guide' end + def versioned_name; "#{app_name} #{Redmine::VERSION}" end + + def environment + s = "Environment:\n" + s << [ + ["Redmine version", Redmine::VERSION], + ["Ruby version", "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"], + ["Rails version", Rails::VERSION::STRING], + ["Environment", Rails.env], + ["Database adapter", ActiveRecord::Base.connection.adapter_name] + ].map {|info| " %-30s %s" % info}.join("\n") + "\n" + + s << "SCM:\n" + Redmine::Scm::Base.all.each do |scm| + scm_class = "Repository::#{scm}".constantize + if scm_class.scm_available + s << " %-30s %s\n" % [scm, scm_class.scm_version_string] + end + end + + s << "Redmine plugins:\n" + plugins = Redmine::Plugin.all + if plugins.any? + s << plugins.map {|plugin| " %-30s %s" % [plugin.id.to_s, plugin.version.to_s]}.join("\n") + else + s << " no plugin installed" + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2e/2eb44b7abed16dd8cc469c08dde7704eb259a823.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2eb44b7abed16dd8cc469c08dde7704eb259a823.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,122 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class GanttsControllerTest < ActionController::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :versions + + def test_gantt_should_work + i2 = Issue.find(2) + i2.update_attribute(:due_date, 1.month.from_now) + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + # Issue with start and due dates + i = Issue.find(1) + assert_not_nil i.due_date + assert_select "div a.issue", /##{i.id}/ + # Issue with on a targeted version should not be in the events but loaded in the html + i = Issue.find(2) + assert_select "div a.issue", /##{i.id}/ + end + + def test_gantt_should_work_without_issue_due_dates + Issue.update_all("due_date = NULL") + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_work_without_issue_and_version_due_dates + Issue.update_all("due_date = NULL") + Version.update_all("effective_date = NULL") + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_work_cross_project + get :show + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + assert_not_nil assigns(:gantt).query + assert_nil assigns(:gantt).project + end + + def test_gantt_should_not_disclose_private_projects + get :show + assert_response :success + assert_template 'gantts/show' + assert_tag 'a', :content => /eCookbook/ + # Root private project + assert_no_tag 'a', {:content => /OnlineStore/} + # Private children of a public project + assert_no_tag 'a', :content => /Private child of eCookbook/ + end + + def test_gantt_should_display_relations + IssueRelation.delete_all + issue1 = Issue.generate!(:start_date => 1.day.from_now.to_date, :due_date => 3.day.from_now.to_date) + issue2 = Issue.generate!(:start_date => 1.day.from_now.to_date, :due_date => 3.day.from_now.to_date) + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => 'precedes') + + get :show + assert_response :success + + relations = assigns(:gantt).relations + assert_kind_of Hash, relations + assert relations.present? + assert_select 'div.task_todo[id=?][data-rels*=?]', "task-todo-issue-#{issue1.id}", issue2.id.to_s + assert_select 'div.task_todo[id=?]:not([data-rels])', "task-todo-issue-#{issue2.id}" + end + + def test_gantt_should_export_to_pdf + get :show, :project_id => 1, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_export_to_pdf_cross_project + get :show, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + assert_not_nil assigns(:gantt) + end + + if Object.const_defined?(:Magick) + def test_gantt_should_export_to_png + get :show, :project_id => 1, :format => 'png' + assert_response :success + assert_equal 'image/png', @response.content_type + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2f/2f281714ddeccdbd5c86760f52d4c587260d33ae.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2f/2f281714ddeccdbd5c86760f52d4c587260d33ae.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,33 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingWikisTest < ActionController::IntegrationTest + def test_wikis_plural_admin_setup + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/projects/ladida/wiki/destroy" }, + { :controller => 'wikis', :action => 'destroy', :id => 'ladida' } + ) + end + assert_routing( + { :method => 'post', :path => "/projects/ladida/wiki" }, + { :controller => 'wikis', :action => 'edit', :id => 'ladida' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2f/2f42ecc608d36e4b4728b04529751c42ed317e4f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2f/2f42ecc608d36e4b4728b04529751c42ed317e4f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,76 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class CustomFieldVersionFormatTest < ActiveSupport::TestCase + fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues, :versions + + def setup + @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'version') + end + + def test_possible_values_with_no_arguments + assert_equal [], @field.possible_values + assert_equal [], @field.possible_values(nil) + end + + def test_possible_values_with_project_resource + project = Project.find(1) + possible_values = @field.possible_values(project.issues.first) + assert possible_values.any? + assert_equal project.shared_versions.sort.collect(&:id).map(&:to_s), possible_values + end + + def test_possible_values_with_nil_project_resource + assert_equal [], @field.possible_values(Issue.new) + end + + def test_possible_values_options_with_no_arguments + assert_equal [], @field.possible_values_options + assert_equal [], @field.possible_values_options(nil) + end + + def test_possible_values_options_with_project_resource + project = Project.find(1) + possible_values_options = @field.possible_values_options(project.issues.first) + assert possible_values_options.any? + assert_equal project.shared_versions.sort.map {|u| [u.name, u.id.to_s]}, possible_values_options + end + + def test_possible_values_options_with_array + projects = Project.find([1, 2]) + possible_values_options = @field.possible_values_options(projects) + assert possible_values_options.any? + assert_equal (projects.first.shared_versions & projects.last.shared_versions).sort.map {|u| [u.name, u.id.to_s]}, possible_values_options + end + + def test_cast_blank_value + assert_equal nil, @field.cast_value(nil) + assert_equal nil, @field.cast_value("") + end + + def test_cast_valid_value + version = @field.cast_value("2") + assert_kind_of Version, version + assert_equal Version.find(2), version + end + + def test_cast_invalid_value + assert_equal nil, @field.cast_value("187") + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2f/2f549bc1e936040c7191cdf4cf1d592cb479c086.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2f/2f549bc1e936040c7191cdf4cf1d592cb479c086.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,141 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class AuthSourceLdapTest < ActiveSupport::TestCase + include Redmine::I18n + fixtures :auth_sources + + def setup + end + + def test_create + a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName') + assert a.save + end + + def test_should_strip_ldap_attributes + a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', + :attr_firstname => 'givenName ') + assert a.save + assert_equal 'givenName', a.reload.attr_firstname + end + + def test_replace_port_zero_to_389 + a = AuthSourceLdap.new( + :name => 'My LDAP', :host => 'ldap.example.net', :port => 0, + :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', + :attr_firstname => 'givenName ') + assert a.save + assert_equal 389, a.port + end + + def test_filter_should_be_validated + set_language_if_valid 'en' + + a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :attr_login => 'sn') + a.filter = "(mail=*@redmine.org" + assert !a.valid? + assert_include "LDAP filter is invalid", a.errors.full_messages + + a.filter = "(mail=*@redmine.org)" + assert a.valid? + end + + if ldap_configured? + test '#authenticate with a valid LDAP user should return the user attributes' do + auth = AuthSourceLdap.find(1) + auth.update_attribute :onthefly_register, true + + attributes = auth.authenticate('example1','123456') + assert attributes.is_a?(Hash), "An hash was not returned" + assert_equal 'Example', attributes[:firstname] + assert_equal 'One', attributes[:lastname] + assert_equal 'example1@redmine.org', attributes[:mail] + assert_equal auth.id, attributes[:auth_source_id] + attributes.keys.each do |attribute| + assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" + end + end + + test '#authenticate with an invalid LDAP user should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('nouser','123456') + end + + test '#authenticate without a login should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('','123456') + end + + test '#authenticate without a password should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('edavis','') + end + + test '#authenticate without filter should return any user' do + auth = AuthSourceLdap.find(1) + assert auth.authenticate('example1','123456') + assert auth.authenticate('edavis', '123456') + end + + test '#authenticate with filter should return user who matches the filter only' do + auth = AuthSourceLdap.find(1) + auth.filter = "(mail=*@redmine.org)" + + assert auth.authenticate('example1','123456') + assert_nil auth.authenticate('edavis', '123456') + end + + def test_authenticate_should_timeout + auth_source = AuthSourceLdap.find(1) + auth_source.timeout = 1 + def auth_source.initialize_ldap_con(*args); sleep(5); end + + assert_raise AuthSourceTimeoutException do + auth_source.authenticate 'example1', '123456' + end + end + + def test_search_should_return_matching_entries + results = AuthSource.search("exa") + assert_equal 1, results.size + result = results.first + assert_kind_of Hash, result + assert_equal "example1", result[:login] + assert_equal "Example", result[:firstname] + assert_equal "One", result[:lastname] + assert_equal "example1@redmine.org", result[:mail] + assert_equal 1, result[:auth_source_id] + end + + def test_search_with_no_match_should_return_an_empty_array + results = AuthSource.search("wro") + assert_equal [], results + end + + def test_search_with_exception_should_return_an_empty_array + Net::LDAP.stubs(:new).raises(Net::LDAP::LdapError, 'Cannot connect') + + results = AuthSource.search("exa") + assert_equal [], results + end + else + puts '(Test LDAP server not configured)' + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2f/2f968415d6662b8ce52a48cbeb3f56b87c5a9e9f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2f/2f968415d6662b8ce52a48cbeb3f56b87c5a9e9f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,118 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# FileSystem adapter +# File written by Paul Rivier, at Demotera. +# +# 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. + +require 'redmine/scm/adapters/abstract_adapter' +require 'find' + +module Redmine + module Scm + module Adapters + class FilesystemAdapter < AbstractAdapter + + class << self + def client_available + true + end + end + + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) + @url = with_trailling_slash(url) + @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding + end + + def path_encoding + @path_encoding + end + + def format_path_ends(path, leading=true, trailling=true) + path = leading ? with_leading_slash(path) : + without_leading_slash(path) + trailling ? with_trailling_slash(path) : + without_trailling_slash(path) + end + + def info + info = Info.new({:root_url => target(), + :lastrev => nil + }) + info + rescue CommandFailed + return nil + end + + def entries(path="", identifier=nil, options={}) + entries = Entries.new + trgt_utf8 = target(path) + trgt = scm_iconv(@path_encoding, 'UTF-8', trgt_utf8) + Dir.new(trgt).each do |e1| + e_utf8 = scm_iconv('UTF-8', @path_encoding, e1) + next if e_utf8.blank? + relative_path_utf8 = format_path_ends( + (format_path_ends(path,false,true) + e_utf8),false,false) + t1_utf8 = target(relative_path_utf8) + t1 = scm_iconv(@path_encoding, 'UTF-8', t1_utf8) + relative_path = scm_iconv(@path_encoding, 'UTF-8', relative_path_utf8) + e1 = scm_iconv(@path_encoding, 'UTF-8', e_utf8) + if File.exist?(t1) and # paranoid test + %w{file directory}.include?(File.ftype(t1)) and # avoid special types + not File.basename(e1).match(/^\.+$/) # avoid . and .. + p1 = File.readable?(t1) ? relative_path : "" + utf_8_path = scm_iconv('UTF-8', @path_encoding, p1) + entries << + Entry.new({ :name => scm_iconv('UTF-8', @path_encoding, File.basename(e1)), + # below : list unreadable files, but dont link them. + :path => utf_8_path, + :kind => (File.directory?(t1) ? 'dir' : 'file'), + :size => (File.directory?(t1) ? nil : [File.size(t1)].pack('l').unpack('L').first), + :lastrev => + Revision.new({:time => (File.mtime(t1)) }) + }) + end + end + entries.sort_by_name + rescue => err + logger.error "scm: filesystem: error: #{err.message}" + raise CommandFailed.new(err.message) + end + + def cat(path, identifier=nil) + p = scm_iconv(@path_encoding, 'UTF-8', target(path)) + File.new(p, "rb").read + rescue => err + logger.error "scm: filesystem: error: #{err.message}" + raise CommandFailed.new(err.message) + end + + private + + # AbstractAdapter::target is implicitly made to quote paths. + # Here we do not shell-out, so we do not want quotes. + def target(path=nil) + # Prevent the use of .. + if path and !path.match(/(^|\/)\.\.(\/|$)/) + return "#{self.url}#{without_leading_slash(path)}" + end + return self.url + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/2f/2fb6efca6ea9d063a58d2593af2f43422f15665e.svn-base --- a/.svn/pristine/2f/2fb6efca6ea9d063a58d2593af2f43422f15665e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,279 +0,0 @@ -module ActiveRecord - module Acts #:nodoc: - module List #:nodoc: - def self.included(base) - base.extend(ClassMethods) - end - - # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list. - # The class that has this specified needs to have a +position+ column defined as an integer on - # the mapped database table. - # - # Todo list example: - # - # class TodoList < ActiveRecord::Base - # has_many :todo_items, :order => "position" - # end - # - # class TodoItem < ActiveRecord::Base - # belongs_to :todo_list - # acts_as_list :scope => :todo_list - # end - # - # todo_list.first.move_to_bottom - # todo_list.last.move_higher - module ClassMethods - # Configuration options are: - # - # * +column+ - specifies the column name to use for keeping the position integer (default: +position+) - # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach _id - # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible - # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key. - # Example: acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0' - def acts_as_list(options = {}) - configuration = { :column => "position", :scope => "1 = 1" } - configuration.update(options) if options.is_a?(Hash) - - configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/ - - if configuration[:scope].is_a?(Symbol) - scope_condition_method = %( - def scope_condition - if #{configuration[:scope].to_s}.nil? - "#{configuration[:scope].to_s} IS NULL" - else - "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}" - end - end - ) - else - scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end" - end - - class_eval <<-EOV - include ActiveRecord::Acts::List::InstanceMethods - - def acts_as_list_class - ::#{self.name} - end - - def position_column - '#{configuration[:column]}' - end - - #{scope_condition_method} - - before_destroy :remove_from_list - before_create :add_to_list_bottom - EOV - end - end - - # All the methods available to a record that has had acts_as_list specified. Each method works - # by assuming the object to be the item in the list, so chapter.move_lower would move that chapter - # lower in the list of all chapters. Likewise, chapter.first? would return +true+ if that chapter is - # the first in the list of all chapters. - module InstanceMethods - # Insert the item at the given position (defaults to the top position of 1). - def insert_at(position = 1) - insert_at_position(position) - end - - # Swap positions with the next lower item, if one exists. - def move_lower - return unless lower_item - - acts_as_list_class.transaction do - lower_item.decrement_position - increment_position - end - end - - # Swap positions with the next higher item, if one exists. - def move_higher - return unless higher_item - - acts_as_list_class.transaction do - higher_item.increment_position - decrement_position - end - end - - # Move to the bottom of the list. If the item is already in the list, the items below it have their - # position adjusted accordingly. - def move_to_bottom - return unless in_list? - acts_as_list_class.transaction do - decrement_positions_on_lower_items - assume_bottom_position - end - end - - # Move to the top of the list. If the item is already in the list, the items above it have their - # position adjusted accordingly. - def move_to_top - return unless in_list? - acts_as_list_class.transaction do - increment_positions_on_higher_items - assume_top_position - end - end - - # Move to the given position - def move_to=(pos) - case pos.to_s - when 'highest' - move_to_top - when 'higher' - move_higher - when 'lower' - move_lower - when 'lowest' - move_to_bottom - end - reset_positions_in_list - end - - def reset_positions_in_list - acts_as_list_class.where(scope_condition).reorder("#{position_column} ASC, id ASC").each_with_index do |item, i| - unless item.send(position_column) == (i + 1) - acts_as_list_class.update_all({position_column => (i + 1)}, {:id => item.id}) - end - end - end - - # Removes the item from the list. - def remove_from_list - if in_list? - decrement_positions_on_lower_items - update_attribute position_column, nil - end - end - - # Increase the position of this item without adjusting the rest of the list. - def increment_position - return unless in_list? - update_attribute position_column, self.send(position_column).to_i + 1 - end - - # Decrease the position of this item without adjusting the rest of the list. - def decrement_position - return unless in_list? - update_attribute position_column, self.send(position_column).to_i - 1 - end - - # Return +true+ if this object is the first in the list. - def first? - return false unless in_list? - self.send(position_column) == 1 - end - - # Return +true+ if this object is the last in the list. - def last? - return false unless in_list? - self.send(position_column) == bottom_position_in_list - end - - # Return the next higher item in the list. - def higher_item - return nil unless in_list? - acts_as_list_class.find(:first, :conditions => - "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}" - ) - end - - # Return the next lower item in the list. - def lower_item - return nil unless in_list? - acts_as_list_class.find(:first, :conditions => - "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}" - ) - end - - # Test if this record is in a list - def in_list? - !send(position_column).nil? - end - - private - def add_to_list_top - increment_positions_on_all_items - end - - def add_to_list_bottom - self[position_column] = bottom_position_in_list.to_i + 1 - end - - # Overwrite this method to define the scope of the list changes - def scope_condition() "1" end - - # Returns the bottom position number in the list. - # bottom_position_in_list # => 2 - def bottom_position_in_list(except = nil) - item = bottom_item(except) - item ? item.send(position_column) : 0 - end - - # Returns the bottom item - def bottom_item(except = nil) - conditions = scope_condition - conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except - acts_as_list_class.where(conditions).reorder("#{position_column} DESC").first - end - - # Forces item to assume the bottom position in the list. - def assume_bottom_position - update_attribute(position_column, bottom_position_in_list(self).to_i + 1) - end - - # Forces item to assume the top position in the list. - def assume_top_position - update_attribute(position_column, 1) - end - - # This has the effect of moving all the higher items up one. - def decrement_positions_on_higher_items(position) - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}" - ) - end - - # This has the effect of moving all the lower items up one. - def decrement_positions_on_lower_items - return unless in_list? - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}" - ) - end - - # This has the effect of moving all the higher items down one. - def increment_positions_on_higher_items - return unless in_list? - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}" - ) - end - - # This has the effect of moving all the lower items down one. - def increment_positions_on_lower_items(position) - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}" - ) - end - - # Increments position (position_column) of all items in the list. - def increment_positions_on_all_items - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} + 1)", "#{scope_condition}" - ) - end - - def insert_at_position(position) - remove_from_list - increment_positions_on_lower_items(position) - self.update_attribute(position_column, position) - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/30/3023c43eca89098e7ad3d81f2db133b0643baf81.svn-base --- a/.svn/pristine/30/3023c43eca89098e7ad3d81f2db133b0643baf81.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'redcloth3' -require 'digest/md5' - -module Redmine - module WikiFormatting - module Textile - class Formatter < RedCloth3 - include ActionView::Helpers::TagHelper - include Redmine::WikiFormatting::LinksHelper - - alias :inline_auto_link :auto_link! - alias :inline_auto_mailto :auto_mailto! - - # auto_link rule after textile rules so that it doesn't break !image_url! tags - RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto] - - def initialize(*args) - super - self.hard_breaks=true - self.no_span_caps=true - self.filter_styles=false - end - - def to_html(*rules) - @toc = [] - super(*RULES).to_s - end - - def get_section(index) - section = extract_sections(index)[1] - hash = Digest::MD5.hexdigest(section) - return section, hash - end - - def update_section(index, update, hash=nil) - t = extract_sections(index) - if hash.present? && hash != Digest::MD5.hexdigest(t[1]) - raise Redmine::WikiFormatting::StaleSectionError - end - t[1] = update unless t[1].blank? - t.reject(&:blank?).join "\n\n" - end - - def extract_sections(index) - @pre_list = [] - text = self.dup - rip_offtags text, false, false - before = '' - s = '' - after = '' - i = 0 - l = 1 - started = false - ended = false - text.scan(/(((?:.*?)(\A|\r?\n\s*\r?\n))(h(\d+)(#{A}#{C})\.(?::(\S+))?[ \t](.*?)$)|.*)/m).each do |all, content, lf, heading, level| - if heading.nil? - if ended - after << all - elsif started - s << all - else - before << all - end - break - end - i += 1 - if ended - after << all - elsif i == index - l = level.to_i - before << content - s << heading - started = true - elsif i > index - s << content - if level.to_i > l - s << heading - else - after << heading - ended = true - end - else - before << all - end - end - sections = [before.strip, s.strip, after.strip] - sections.each {|section| smooth_offtags_without_code_highlighting section} - sections - end - - private - - # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. - # http://code.whytheluckystiff.net/redcloth/changeset/128 - def hard_break( text ) - text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks - end - - alias :smooth_offtags_without_code_highlighting :smooth_offtags - # Patch to add code highlighting support to RedCloth - def smooth_offtags( text ) - unless @pre_list.empty? - ## replace
     content
    -            text.gsub!(//) do
    -              content = @pre_list[$1.to_i]
    -              if content.match(/\s?(.+)/m)
    -                content = "" +
    -                  Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
    -              end
    -              content
    -            end
    -          end
    -        end
    -      end
    -    end
    -  end
    -end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/30/3027fca91424b88657f6c91d9b6b5ddfc109c616.svn-base
    --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    +++ b/.svn/pristine/30/3027fca91424b88657f6c91d9b6b5ddfc109c616.svn-base	Tue Sep 09 09:34:53 2014 +0100
    @@ -0,0 +1,37 @@
    +require 'rexml/document'
    +
    +module Redmine
    +  module VERSION #:nodoc:
    +    MAJOR = 2
    +    MINOR = 4
    +    TINY  = 6
    +
    +    # Branch values:
    +    # * official release: nil
    +    # * stable branch:    stable
    +    # * trunk:            devel
    +    BRANCH = 'stable'
    +
    +    # Retrieves the revision from the working copy
    +    def self.revision
    +      if File.directory?(File.join(Rails.root, '.svn'))
    +        begin
    +          path = Redmine::Scm::Adapters::AbstractAdapter.shell_quote(Rails.root.to_s)
    +          if `svn info --xml #{path}` =~ /revision="(\d+)"/
    +            return $1.to_i
    +          end
    +        rescue
    +          # Could not find the current revision
    +        end
    +      end
    +      nil
    +    end
    +
    +    REVISION = self.revision
    +    ARRAY    = [MAJOR, MINOR, TINY, BRANCH, REVISION].compact
    +    STRING   = ARRAY.join('.')
    +
    +    def self.to_a; ARRAY  end
    +    def self.to_s; STRING end
    +  end
    +end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/30/3036a2491d22fe798c178f3e275812ed2e72870c.svn-base
    --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    +++ b/.svn/pristine/30/3036a2491d22fe798c178f3e275812ed2e72870c.svn-base	Tue Sep 09 09:34:53 2014 +0100
    @@ -0,0 +1,90 @@
    +# Redmine - project management software
    +# Copyright (C) 2006-2014  Jean-Philippe Lang
    +#
    +# 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.
    +
    +require File.expand_path('../../test_helper', __FILE__)
    +
    +class SettingTest < ActiveSupport::TestCase
    +
    +  def teardown
    +    Setting.clear_cache
    +  end
    +
    +  def test_read_default
    +    assert_equal "Redmine", Setting.app_title
    +    assert Setting.self_registration?
    +    assert !Setting.login_required?
    +  end
    +
    +  def test_update
    +    Setting.app_title = "My title"
    +    assert_equal "My title", Setting.app_title
    +    # make sure db has been updated (INSERT)
    +    assert_equal "My title", Setting.find_by_name('app_title').value
    +
    +    Setting.app_title = "My other title"
    +    assert_equal "My other title", Setting.app_title
    +    # make sure db has been updated (UPDATE)
    +    assert_equal "My other title", Setting.find_by_name('app_title').value
    +  end
    +
    +  def test_serialized_setting
    +    Setting.notified_events = ['issue_added', 'issue_updated', 'news_added']
    +    assert_equal ['issue_added', 'issue_updated', 'news_added'], Setting.notified_events
    +    assert_equal ['issue_added', 'issue_updated', 'news_added'], Setting.find_by_name('notified_events').value
    +  end
    +
    +  def test_setting_should_be_reloaded_after_clear_cache
    +    Setting.app_title = "My title"
    +    assert_equal "My title", Setting.app_title
    +
    +    s = Setting.find_by_name("app_title")
    +    s.value = 'New title'
    +    s.save!
    +    assert_equal "My title", Setting.app_title
    +
    +    Setting.clear_cache
    +    assert_equal "New title", Setting.app_title
    +  end
    +
    +  def test_per_page_options_array_should_be_an_empty_array_when_setting_is_blank
    +    with_settings :per_page_options => nil do
    +      assert_equal [], Setting.per_page_options_array
    +    end
    +
    +    with_settings :per_page_options => '' do
    +      assert_equal [], Setting.per_page_options_array
    +    end
    +  end
    +
    +  def test_per_page_options_array_should_be_an_array_of_integers
    +    with_settings :per_page_options => '10, 25, 50' do
    +      assert_equal [10, 25, 50], Setting.per_page_options_array
    +    end
    +  end
    +
    +  def test_per_page_options_array_should_omit_non_numerial_values
    +    with_settings :per_page_options => 'a, 25, 50' do
    +      assert_equal [25, 50], Setting.per_page_options_array
    +    end
    +  end
    +
    +  def test_per_page_options_array_should_be_sorted
    +    with_settings :per_page_options => '25, 10, 50' do
    +      assert_equal [10, 25, 50], Setting.per_page_options_array
    +    end
    +  end
    +end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/30/30a02acbacee457029ab442e649789febcfd2f94.svn-base
    --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    +++ b/.svn/pristine/30/30a02acbacee457029ab442e649789febcfd2f94.svn-base	Tue Sep 09 09:34:53 2014 +0100
    @@ -0,0 +1,36 @@
    +# Redmine - project management software
    +# Copyright (C) 2006-2014  Jean-Philippe Lang
    +#
    +# 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.
    +
    +class WikisController < ApplicationController
    +  menu_item :settings
    +  before_filter :find_project, :authorize
    +
    +  # Create or update a project's wiki
    +  def edit
    +    @wiki = @project.wiki || Wiki.new(:project => @project)
    +    @wiki.safe_attributes = params[:wiki]
    +    @wiki.save if request.post?
    +  end
    +
    +  # Delete a project's wiki
    +  def destroy
    +    if request.post? && params[:confirm] && @project.wiki
    +      @project.wiki.destroy
    +      redirect_to settings_project_path(@project, :tab => 'wiki')
    +    end
    +  end
    +end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/30/30accb20c8e9d14a6bba9ac1e15c6b0323bdc2b1.svn-base
    --- a/.svn/pristine/30/30accb20c8e9d14a6bba9ac1e15c6b0323bdc2b1.svn-base	Wed May 07 14:15:02 2014 +0100
    +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    @@ -1,35 +0,0 @@
    -# Redmine - project management software
    -# Copyright (C) 2006-2012  Jean-Philippe Lang
    -#
    -# 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.
    -
    -
    -namespace :db do
    -  desc 'Encrypts SCM and LDAP passwords in the database.'
    -  task :encrypt => :environment do
    -    unless (Repository.encrypt_all(:password) &&
    -      AuthSource.encrypt_all(:account_password))
    -      raise "Some objects could not be saved after encryption, update was rollback'ed."
    -    end
    -  end
    -
    -  desc 'Decrypts SCM and LDAP passwords in the database.'
    -  task :decrypt => :environment do
    -    unless (Repository.decrypt_all(:password) &&
    -      AuthSource.decrypt_all(:account_password))
    -      raise "Some objects could not be saved after decryption, update was rollback'ed."
    -    end
    -  end
    -end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/31/3153fac39abb0847d15ae1b313574c54e0570e1c.svn-base
    --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    +++ b/.svn/pristine/31/3153fac39abb0847d15ae1b313574c54e0570e1c.svn-base	Tue Sep 09 09:34:53 2014 +0100
    @@ -0,0 +1,37 @@
    +api.user do
    +  api.id         @user.id
    +  api.login      @user.login if User.current.admin? || (User.current == @user)
    +  api.firstname  @user.firstname
    +  api.lastname   @user.lastname
    +  api.mail       @user.mail if User.current.admin? || !@user.pref.hide_mail
    +  api.created_on @user.created_on
    +  api.last_login_on @user.last_login_on
    +  api.api_key    @user.api_key if User.current.admin? || (User.current == @user)
    +  api.status     @user.status if User.current.admin?
    +
    +  render_api_custom_values @user.visible_custom_field_values, api
    +
    +  api.array :groups do |groups|
    +    @user.groups.each do |group|
    +      api.group :id => group.id, :name => group.name
    +    end
    +  end if User.current.admin? && include_in_api_response?('groups')
    +
    +  api.array :memberships do
    +    @memberships.each do |membership|
    +      api.membership do
    +        api.id membership.id
    +        api.project :id => membership.project.id, :name => membership.project.name
    +        api.array :roles do
    +          membership.member_roles.each do |member_role|
    +            if member_role.role
    +              attrs = {:id => member_role.role.id, :name => member_role.role.name}
    +              attrs.merge!(:inherited => true) if member_role.inherited_from.present?
    +              api.role attrs
    +            end 
    +          end
    +        end
    +      end if membership.project
    +    end
    +  end if include_in_api_response?('memberships') && @memberships
    +end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/31/3155f8295657e15516d6d855aaf7eefbf749a747.svn-base
    --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    +++ b/.svn/pristine/31/3155f8295657e15516d6d855aaf7eefbf749a747.svn-base	Tue Sep 09 09:34:53 2014 +0100
    @@ -0,0 +1,195 @@
    +# Redmine - project management software
    +# Copyright (C) 2006-2014  Jean-Philippe Lang
    +#
    +# 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.
    +
    +require File.expand_path('../../test_helper', __FILE__)
    +
    +class WatchersControllerTest < ActionController::TestCase
    +  fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules,
    +           :issues, :trackers, :projects_trackers, :issue_statuses, :enumerations, :watchers
    +
    +  def setup
    +    User.current = nil
    +  end
    +
    +  def test_watch_a_single_object
    +    @request.session[:user_id] = 3
    +    assert_difference('Watcher.count') do
    +      xhr :post, :watch, :object_type => 'issue', :object_id => '1'
    +      assert_response :success
    +      assert_include '$(".issue-1-watcher")', response.body
    +    end
    +    assert Issue.find(1).watched_by?(User.find(3))
    +  end
    +
    +  def test_watch_a_collection_with_a_single_object
    +    @request.session[:user_id] = 3
    +    assert_difference('Watcher.count') do
    +      xhr :post, :watch, :object_type => 'issue', :object_id => ['1']
    +      assert_response :success
    +      assert_include '$(".issue-1-watcher")', response.body
    +    end
    +    assert Issue.find(1).watched_by?(User.find(3))
    +  end
    +
    +  def test_watch_a_collection_with_multiple_objects
    +    @request.session[:user_id] = 3
    +    assert_difference('Watcher.count', 2) do
    +      xhr :post, :watch, :object_type => 'issue', :object_id => ['1', '3']
    +      assert_response :success
    +      assert_include '$(".issue-bulk-watcher")', response.body
    +    end
    +    assert Issue.find(1).watched_by?(User.find(3))
    +    assert Issue.find(3).watched_by?(User.find(3))
    +  end
    +
    +  def test_watch_should_be_denied_without_permission
    +    Role.find(2).remove_permission! :view_issues
    +    @request.session[:user_id] = 3
    +    assert_no_difference('Watcher.count') do
    +      xhr :post, :watch, :object_type => 'issue', :object_id => '1'
    +      assert_response 403
    +    end
    +  end
    +
    +  def test_watch_invalid_class_should_respond_with_404
    +    @request.session[:user_id] = 3
    +    assert_no_difference('Watcher.count') do
    +      xhr :post, :watch, :object_type => 'foo', :object_id => '1'
    +      assert_response 404
    +    end
    +  end
    +
    +  def test_watch_invalid_object_should_respond_with_404
    +    @request.session[:user_id] = 3
    +    assert_no_difference('Watcher.count') do
    +      xhr :post, :watch, :object_type => 'issue', :object_id => '999'
    +      assert_response 404
    +    end
    +  end
    +
    +  def test_unwatch
    +    @request.session[:user_id] = 3
    +    assert_difference('Watcher.count', -1) do
    +      xhr :delete, :unwatch, :object_type => 'issue', :object_id => '2'
    +      assert_response :success
    +      assert_include '$(".issue-2-watcher")', response.body
    +    end
    +    assert !Issue.find(1).watched_by?(User.find(3))
    +  end
    +
    +  def test_unwatch_a_collection_with_multiple_objects
    +    @request.session[:user_id] = 3
    +    Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
    +    Watcher.create!(:user_id => 3, :watchable => Issue.find(3))
    +
    +    assert_difference('Watcher.count', -2) do
    +      xhr :delete, :unwatch, :object_type => 'issue', :object_id => ['1', '3']
    +      assert_response :success
    +      assert_include '$(".issue-bulk-watcher")', response.body
    +    end
    +    assert !Issue.find(1).watched_by?(User.find(3))
    +    assert !Issue.find(3).watched_by?(User.find(3))
    +  end
    +
    +  def test_new
    +    @request.session[:user_id] = 2
    +    xhr :get, :new, :object_type => 'issue', :object_id => '2'
    +    assert_response :success
    +    assert_match /ajax-modal/, response.body
    +  end
    +
    +  def test_new_for_new_record_with_project_id
    +    @request.session[:user_id] = 2
    +    xhr :get, :new, :project_id => 1
    +    assert_response :success
    +    assert_equal Project.find(1), assigns(:project)
    +    assert_match /ajax-modal/, response.body
    +  end
    +
    +  def test_new_for_new_record_with_project_identifier
    +    @request.session[:user_id] = 2
    +    xhr :get, :new, :project_id => 'ecookbook'
    +    assert_response :success
    +    assert_equal Project.find(1), assigns(:project)
    +    assert_match /ajax-modal/, response.body
    +  end
    +
    +  def test_create
    +    @request.session[:user_id] = 2
    +    assert_difference('Watcher.count') do
    +      xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_id => '4'}
    +      assert_response :success
    +      assert_match /watchers/, response.body
    +      assert_match /ajax-modal/, response.body
    +    end
    +    assert Issue.find(2).watched_by?(User.find(4))
    +  end
    +
    +  def test_create_multiple
    +    @request.session[:user_id] = 2
    +    assert_difference('Watcher.count', 2) do
    +      xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_ids => ['4', '7']}
    +      assert_response :success
    +      assert_match /watchers/, response.body
    +      assert_match /ajax-modal/, response.body
    +    end
    +    assert Issue.find(2).watched_by?(User.find(4))
    +    assert Issue.find(2).watched_by?(User.find(7))
    +  end
    +
    +  def test_autocomplete_on_watchable_creation
    +    @request.session[:user_id] = 2
    +    xhr :get, :autocomplete_for_user, :q => 'mi', :project_id => 'ecookbook'
    +    assert_response :success
    +    assert_select 'input', :count => 4
    +    assert_select 'input[name=?][value=1]', 'watcher[user_ids][]'
    +    assert_select 'input[name=?][value=2]', 'watcher[user_ids][]'
    +    assert_select 'input[name=?][value=8]', 'watcher[user_ids][]'
    +    assert_select 'input[name=?][value=9]', 'watcher[user_ids][]'
    +  end
    +
    +  def test_autocomplete_on_watchable_update
    +    @request.session[:user_id] = 2
    +    xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue', :project_id => 'ecookbook'
    +    assert_response :success
    +    assert_select 'input', :count => 3
    +    assert_select 'input[name=?][value=2]', 'watcher[user_ids][]'
    +    assert_select 'input[name=?][value=8]', 'watcher[user_ids][]'
    +    assert_select 'input[name=?][value=9]', 'watcher[user_ids][]'
    +
    +  end
    +
    +  def test_append
    +    @request.session[:user_id] = 2
    +    assert_no_difference 'Watcher.count' do
    +      xhr :post, :append, :watcher => {:user_ids => ['4', '7']}, :project_id => 'ecookbook'
    +      assert_response :success
    +      assert_include 'watchers_inputs', response.body
    +      assert_include 'issue[watcher_user_ids][]', response.body
    +    end
    +  end
    +
    +  def test_remove_watcher
    +    @request.session[:user_id] = 2
    +    assert_difference('Watcher.count', -1) do
    +      xhr :delete, :destroy, :object_type => 'issue', :object_id => '2', :user_id => '3'
    +      assert_response :success
    +      assert_match /watchers/, response.body
    +    end
    +    assert !Issue.find(2).watched_by?(User.find(3))
    +  end
    +end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/31/318f04133e3e32c333e98ac7f8065761353f5042.svn-base
    --- a/.svn/pristine/31/318f04133e3e32c333e98ac7f8065761353f5042.svn-base	Wed May 07 14:15:02 2014 +0100
    +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    @@ -1,10 +0,0 @@
    -class AddIssueStatusPosition < ActiveRecord::Migration
    -  def self.up
    -    add_column :issue_statuses, :position, :integer, :default => 1
    -    IssueStatus.find(:all).each_with_index {|status, i| status.update_attribute(:position, i+1)}
    -  end
    -
    -  def self.down
    -    remove_column :issue_statuses, :position
    -  end
    -end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/31/3190ee598f70a34c3d3f93dfd554a0ad83b8e462.svn-base
    --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    +++ b/.svn/pristine/31/3190ee598f70a34c3d3f93dfd554a0ad83b8e462.svn-base	Tue Sep 09 09:34:53 2014 +0100
    @@ -0,0 +1,637 @@
    +# Redmine - project management software
    +# Copyright (C) 2006-2014  Jean-Philippe Lang
    +#
    +# 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.
    +
    +require 'uri'
    +require 'cgi'
    +
    +class Unauthorized < Exception; end
    +
    +class ApplicationController < ActionController::Base
    +  include Redmine::I18n
    +  include Redmine::Pagination
    +  include RoutesHelper
    +  helper :routes
    +
    +  class_attribute :accept_api_auth_actions
    +  class_attribute :accept_rss_auth_actions
    +  class_attribute :model_object
    +
    +  layout 'base'
    +
    +  protect_from_forgery
    +
    +  def verify_authenticity_token
    +    unless api_request?
    +      super
    +    end
    +  end
    +
    +  def handle_unverified_request
    +    unless api_request?
    +      super
    +      cookies.delete(autologin_cookie_name)
    +      self.logged_user = nil
    +      render_error :status => 422, :message => "Invalid form authenticity token."
    +    end
    +  end
    +
    +  before_filter :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization
    +
    +  rescue_from ::Unauthorized, :with => :deny_access
    +  rescue_from ::ActionView::MissingTemplate, :with => :missing_template
    +
    +  include Redmine::Search::Controller
    +  include Redmine::MenuManager::MenuController
    +  helper Redmine::MenuManager::MenuHelper
    +
    +  def session_expiration
    +    if session[:user_id]
    +      if session_expired? && !try_to_autologin
    +        reset_session
    +        flash[:error] = l(:error_session_expired)
    +        redirect_to signin_url
    +      else
    +        session[:atime] = Time.now.utc.to_i
    +      end
    +    end
    +  end
    +
    +  def session_expired?
    +    if Setting.session_lifetime?
    +      unless session[:ctime] && (Time.now.utc.to_i - session[:ctime].to_i <= Setting.session_lifetime.to_i * 60)
    +        return true
    +      end
    +    end
    +    if Setting.session_timeout?
    +      unless session[:atime] && (Time.now.utc.to_i - session[:atime].to_i <= Setting.session_timeout.to_i * 60)
    +        return true
    +      end
    +    end
    +    false
    +  end
    +
    +  def start_user_session(user)
    +    session[:user_id] = user.id
    +    session[:ctime] = Time.now.utc.to_i
    +    session[:atime] = Time.now.utc.to_i
    +    if user.must_change_password?
    +      session[:pwd] = '1'
    +    end
    +  end
    +
    +  def user_setup
    +    # Check the settings cache for each request
    +    Setting.check_cache
    +    # Find the current user
    +    User.current = find_current_user
    +    logger.info("  Current user: " + (User.current.logged? ? "#{User.current.login} (id=#{User.current.id})" : "anonymous")) if logger
    +  end
    +
    +  # Returns the current user or nil if no user is logged in
    +  # and starts a session if needed
    +  def find_current_user
    +    user = nil
    +    unless api_request?
    +      if session[:user_id]
    +        # existing session
    +        user = (User.active.find(session[:user_id]) rescue nil)
    +      elsif autologin_user = try_to_autologin
    +        user = autologin_user
    +      elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
    +        # RSS key authentication does not start a session
    +        user = User.find_by_rss_key(params[:key])
    +      end
    +    end
    +    if user.nil? && Setting.rest_api_enabled? && accept_api_auth?
    +      if (key = api_key_from_request)
    +        # Use API key
    +        user = User.find_by_api_key(key)
    +      else
    +        # HTTP Basic, either username/password or API key/random
    +        authenticate_with_http_basic do |username, password|
    +          user = User.try_to_login(username, password) || User.find_by_api_key(username)
    +        end
    +        if user && user.must_change_password?
    +          render_error :message => 'You must change your password', :status => 403
    +          return
    +        end
    +      end
    +      # Switch user if requested by an admin user
    +      if user && user.admin? && (username = api_switch_user_from_request)
    +        su = User.find_by_login(username)
    +        if su && su.active?
    +          logger.info("  User switched by: #{user.login} (id=#{user.id})") if logger
    +          user = su
    +        else
    +          render_error :message => 'Invalid X-Redmine-Switch-User header', :status => 412
    +        end
    +      end
    +    end
    +    user
    +  end
    +
    +  def autologin_cookie_name
    +    Redmine::Configuration['autologin_cookie_name'].presence || 'autologin'
    +  end
    +
    +  def try_to_autologin
    +    if cookies[autologin_cookie_name] && Setting.autologin?
    +      # auto-login feature starts a new session
    +      user = User.try_to_autologin(cookies[autologin_cookie_name])
    +      if user
    +        reset_session
    +        start_user_session(user)
    +      end
    +      user
    +    end
    +  end
    +
    +  # Sets the logged in user
    +  def logged_user=(user)
    +    reset_session
    +    if user && user.is_a?(User)
    +      User.current = user
    +      start_user_session(user)
    +    else
    +      User.current = User.anonymous
    +    end
    +  end
    +
    +  # Logs out current user
    +  def logout_user
    +    if User.current.logged?
    +      cookies.delete(autologin_cookie_name)
    +      Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
    +      self.logged_user = nil
    +    end
    +  end
    +
    +  # check if login is globally required to access the application
    +  def check_if_login_required
    +    # no check needed if user is already logged in
    +    return true if User.current.logged?
    +    require_login if Setting.login_required?
    +  end
    +
    +  def check_password_change
    +    if session[:pwd]
    +      if User.current.must_change_password?
    +        redirect_to my_password_path
    +      else
    +        session.delete(:pwd)
    +      end
    +    end
    +  end
    +
    +  def set_localization
    +    lang = nil
    +    if User.current.logged?
    +      lang = find_language(User.current.language)
    +    end
    +    if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
    +      accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
    +      if !accept_lang.blank?
    +        accept_lang = accept_lang.downcase
    +        lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
    +      end
    +    end
    +    lang ||= Setting.default_language
    +    set_language_if_valid(lang)
    +  end
    +
    +  def require_login
    +    if !User.current.logged?
    +      # Extract only the basic url parameters on non-GET requests
    +      if request.get?
    +        url = url_for(params)
    +      else
    +        url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
    +      end
    +      respond_to do |format|
    +        format.html {
    +          if request.xhr?
    +            head :unauthorized
    +          else
    +            redirect_to :controller => "account", :action => "login", :back_url => url
    +          end
    +        }
    +        format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
    +        format.xml  { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
    +        format.js   { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
    +        format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
    +      end
    +      return false
    +    end
    +    true
    +  end
    +
    +  def require_admin
    +    return unless require_login
    +    if !User.current.admin?
    +      render_403
    +      return false
    +    end
    +    true
    +  end
    +
    +  def deny_access
    +    User.current.logged? ? render_403 : require_login
    +  end
    +
    +  # Authorize the user for the requested action
    +  def authorize(ctrl = params[:controller], action = params[:action], global = false)
    +    allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
    +    if allowed
    +      true
    +    else
    +      if @project && @project.archived?
    +        render_403 :message => :notice_not_authorized_archived_project
    +      else
    +        deny_access
    +      end
    +    end
    +  end
    +
    +  # Authorize the user for the requested action outside a project
    +  def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
    +    authorize(ctrl, action, global)
    +  end
    +
    +  # Find project of id params[:id]
    +  def find_project
    +    @project = Project.find(params[:id])
    +  rescue ActiveRecord::RecordNotFound
    +    render_404
    +  end
    +
    +  # Find project of id params[:project_id]
    +  def find_project_by_project_id
    +    @project = Project.find(params[:project_id])
    +  rescue ActiveRecord::RecordNotFound
    +    render_404
    +  end
    +
    +  # Find a project based on params[:project_id]
    +  # TODO: some subclasses override this, see about merging their logic
    +  def find_optional_project
    +    @project = Project.find(params[:project_id]) unless params[:project_id].blank?
    +    allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
    +    allowed ? true : deny_access
    +  rescue ActiveRecord::RecordNotFound
    +    render_404
    +  end
    +
    +  # Finds and sets @project based on @object.project
    +  def find_project_from_association
    +    render_404 unless @object.present?
    +
    +    @project = @object.project
    +  end
    +
    +  def find_model_object
    +    model = self.class.model_object
    +    if model
    +      @object = model.find(params[:id])
    +      self.instance_variable_set('@' + controller_name.singularize, @object) if @object
    +    end
    +  rescue ActiveRecord::RecordNotFound
    +    render_404
    +  end
    +
    +  def self.model_object(model)
    +    self.model_object = model
    +  end
    +
    +  # Find the issue whose id is the :id parameter
    +  # Raises a Unauthorized exception if the issue is not visible
    +  def find_issue
    +    # Issue.visible.find(...) can not be used to redirect user to the login form
    +    # if the issue actually exists but requires authentication
    +    @issue = Issue.find(params[:id])
    +    raise Unauthorized unless @issue.visible?
    +    @project = @issue.project
    +  rescue ActiveRecord::RecordNotFound
    +    render_404
    +  end
    +
    +  # Find issues with a single :id param or :ids array param
    +  # Raises a Unauthorized exception if one of the issues is not visible
    +  def find_issues
    +    @issues = Issue.where(:id => (params[:id] || params[:ids])).preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to).to_a
    +    raise ActiveRecord::RecordNotFound if @issues.empty?
    +    raise Unauthorized unless @issues.all?(&:visible?)
    +    @projects = @issues.collect(&:project).compact.uniq
    +    @project = @projects.first if @projects.size == 1
    +  rescue ActiveRecord::RecordNotFound
    +    render_404
    +  end
    +
    +  def find_attachments
    +    if (attachments = params[:attachments]).present?
    +      att = attachments.values.collect do |attachment|
    +        Attachment.find_by_token( attachment[:token] ) if attachment[:token].present?
    +      end
    +      att.compact!
    +    end
    +    @attachments = att || []
    +  end
    +
    +  # make sure that the user is a member of the project (or admin) if project is private
    +  # used as a before_filter for actions that do not require any particular permission on the project
    +  def check_project_privacy
    +    if @project && !@project.archived?
    +      if @project.visible?
    +        true
    +      else
    +        deny_access
    +      end
    +    else
    +      @project = nil
    +      render_404
    +      false
    +    end
    +  end
    +
    +  def back_url
    +    url = params[:back_url]
    +    if url.nil? && referer = request.env['HTTP_REFERER']
    +      url = CGI.unescape(referer.to_s)
    +    end
    +    url
    +  end
    +
    +  def redirect_back_or_default(default)
    +    back_url = params[:back_url].to_s
    +    if back_url.present? && valid_back_url?(back_url)
    +      redirect_to(back_url)
    +      return
    +    end
    +    redirect_to default
    +    false
    +  end
    +
    +  # Returns true if back_url is a valid url for redirection, otherwise false
    +  def valid_back_url?(back_url)
    +    if CGI.unescape(back_url).include?('..')
    +      return false
    +    end
    +
    +    begin
    +      uri = URI.parse(back_url)
    +    rescue URI::InvalidURIError
    +      return false
    +    end
    +
    +    if uri.host.present? && uri.host != request.host
    +      return false
    +    end
    +
    +    if uri.path.match(%r{/(login|account/register)})
    +      return false
    +    end
    +
    +    if relative_url_root.present? && !uri.path.starts_with?(relative_url_root)
    +      return false
    +    end
    +
    +    return true
    +  end
    +  private :valid_back_url?
    +
    +  # Redirects to the request referer if present, redirects to args or call block otherwise.
    +  def redirect_to_referer_or(*args, &block)
    +    redirect_to :back
    +  rescue ::ActionController::RedirectBackError
    +    if args.any?
    +      redirect_to *args
    +    elsif block_given?
    +      block.call
    +    else
    +      raise "#redirect_to_referer_or takes arguments or a block"
    +    end
    +  end
    +
    +  def render_403(options={})
    +    @project = nil
    +    render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
    +    return false
    +  end
    +
    +  def render_404(options={})
    +    render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
    +    return false
    +  end
    +
    +  # Renders an error response
    +  def render_error(arg)
    +    arg = {:message => arg} unless arg.is_a?(Hash)
    +
    +    @message = arg[:message]
    +    @message = l(@message) if @message.is_a?(Symbol)
    +    @status = arg[:status] || 500
    +
    +    respond_to do |format|
    +      format.html {
    +        render :template => 'common/error', :layout => use_layout, :status => @status
    +      }
    +      format.any { head @status }
    +    end
    +  end
    +
    +  # Handler for ActionView::MissingTemplate exception
    +  def missing_template
    +    logger.warn "Missing template, responding with 404"
    +    @project = nil
    +    render_404
    +  end
    +
    +  # Filter for actions that provide an API response
    +  # but have no HTML representation for non admin users
    +  def require_admin_or_api_request
    +    return true if api_request?
    +    if User.current.admin?
    +      true
    +    elsif User.current.logged?
    +      render_error(:status => 406)
    +    else
    +      deny_access
    +    end
    +  end
    +
    +  # Picks which layout to use based on the request
    +  #
    +  # @return [boolean, string] name of the layout to use or false for no layout
    +  def use_layout
    +    request.xhr? ? false : 'base'
    +  end
    +
    +  def render_feed(items, options={})
    +    @items = items || []
    +    @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
    +    @items = @items.slice(0, Setting.feeds_limit.to_i)
    +    @title = options[:title] || Setting.app_title
    +    render :template => "common/feed", :formats => [:atom], :layout => false,
    +           :content_type => 'application/atom+xml'
    +  end
    +
    +  def self.accept_rss_auth(*actions)
    +    if actions.any?
    +      self.accept_rss_auth_actions = actions
    +    else
    +      self.accept_rss_auth_actions || []
    +    end
    +  end
    +
    +  def accept_rss_auth?(action=action_name)
    +    self.class.accept_rss_auth.include?(action.to_sym)
    +  end
    +
    +  def self.accept_api_auth(*actions)
    +    if actions.any?
    +      self.accept_api_auth_actions = actions
    +    else
    +      self.accept_api_auth_actions || []
    +    end
    +  end
    +
    +  def accept_api_auth?(action=action_name)
    +    self.class.accept_api_auth.include?(action.to_sym)
    +  end
    +
    +  # Returns the number of objects that should be displayed
    +  # on the paginated list
    +  def per_page_option
    +    per_page = nil
    +    if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
    +      per_page = params[:per_page].to_s.to_i
    +      session[:per_page] = per_page
    +    elsif session[:per_page]
    +      per_page = session[:per_page]
    +    else
    +      per_page = Setting.per_page_options_array.first || 25
    +    end
    +    per_page
    +  end
    +
    +  # Returns offset and limit used to retrieve objects
    +  # for an API response based on offset, limit and page parameters
    +  def api_offset_and_limit(options=params)
    +    if options[:offset].present?
    +      offset = options[:offset].to_i
    +      if offset < 0
    +        offset = 0
    +      end
    +    end
    +    limit = options[:limit].to_i
    +    if limit < 1
    +      limit = 25
    +    elsif limit > 100
    +      limit = 100
    +    end
    +    if offset.nil? && options[:page].present?
    +      offset = (options[:page].to_i - 1) * limit
    +      offset = 0 if offset < 0
    +    end
    +    offset ||= 0
    +
    +    [offset, limit]
    +  end
    +
    +  # qvalues http header parser
    +  # code taken from webrick
    +  def parse_qvalues(value)
    +    tmp = []
    +    if value
    +      parts = value.split(/,\s*/)
    +      parts.each {|part|
    +        if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
    +          val = m[1]
    +          q = (m[2] or 1).to_f
    +          tmp.push([val, q])
    +        end
    +      }
    +      tmp = tmp.sort_by{|val, q| -q}
    +      tmp.collect!{|val, q| val}
    +    end
    +    return tmp
    +  rescue
    +    nil
    +  end
    +
    +  # Returns a string that can be used as filename value in Content-Disposition header
    +  def filename_for_content_disposition(name)
    +    request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident)} ? ERB::Util.url_encode(name) : name
    +  end
    +
    +  def api_request?
    +    %w(xml json).include? params[:format]
    +  end
    +
    +  # Returns the API key present in the request
    +  def api_key_from_request
    +    if params[:key].present?
    +      params[:key].to_s
    +    elsif request.headers["X-Redmine-API-Key"].present?
    +      request.headers["X-Redmine-API-Key"].to_s
    +    end
    +  end
    +
    +  # Returns the API 'switch user' value if present
    +  def api_switch_user_from_request
    +    request.headers["X-Redmine-Switch-User"].to_s.presence
    +  end
    +
    +  # Renders a warning flash if obj has unsaved attachments
    +  def render_attachment_warning_if_needed(obj)
    +    flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
    +  end
    +
    +  # Rescues an invalid query statement. Just in case...
    +  def query_statement_invalid(exception)
    +    logger.error "Query::StatementInvalid: #{exception.message}" if logger
    +    session.delete(:query)
    +    sort_clear if respond_to?(:sort_clear)
    +    render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
    +  end
    +
    +  # Renders a 200 response for successfull updates or deletions via the API
    +  def render_api_ok
    +    render_api_head :ok
    +  end
    +
    +  # Renders a head API response
    +  def render_api_head(status)
    +    # #head would return a response body with one space
    +    render :text => '', :status => status, :layout => nil
    +  end
    +
    +  # Renders API response on validation failure
    +  def render_validation_errors(objects)
    +    if objects.is_a?(Array)
    +      @error_messages = objects.map {|object| object.errors.full_messages}.flatten
    +    else
    +      @error_messages = objects.errors.full_messages
    +    end
    +    render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil
    +  end
    +
    +  # Overrides #_include_layout? so that #render with no arguments
    +  # doesn't use the layout for api requests
    +  def _include_layout?(*args)
    +    api_request? ? false : super
    +  end
    +end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/31/31bb85f22f4d21a57af6d104f857fd83f936b8f3.svn-base
    --- a/.svn/pristine/31/31bb85f22f4d21a57af6d104f857fd83f936b8f3.svn-base	Wed May 07 14:15:02 2014 +0100
    +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    @@ -1,65 +0,0 @@
    -# Redmine - project management software
    -# Copyright (C) 2006-2012  Jean-Philippe Lang
    -#
    -# 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.
    -
    -require File.expand_path('../../test_helper', __FILE__)
    -
    -class WorkflowTest < ActiveSupport::TestCase
    -  fixtures :roles, :trackers, :issue_statuses
    -
    -  def test_copy
    -    WorkflowTransition.delete_all
    -    WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 2)
    -    WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 3, :assignee => true)
    -    WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 4, :author => true)
    -
    -    assert_difference 'WorkflowTransition.count', 3 do
    -      WorkflowTransition.copy(Tracker.find(2), Role.find(1), Tracker.find(3), Role.find(2))
    -    end
    -
    -    assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false})
    -    assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 3, :author => false, :assignee => true})
    -    assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 4, :author => true, :assignee => false})
    -  end
    -
    -  def test_workflow_permission_should_validate_rule
    -    wp = WorkflowPermission.new(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :field_name => 'due_date')
    -    assert !wp.save
    -
    -    wp.rule = 'foo'
    -    assert !wp.save
    -
    -    wp.rule = 'required'
    -    assert wp.save
    -
    -    wp.rule = 'readonly'
    -    assert wp.save
    -  end
    -
    -  def test_workflow_permission_should_validate_field_name
    -    wp = WorkflowPermission.new(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :rule => 'required')
    -    assert !wp.save
    -
    -    wp.field_name = 'foo'
    -    assert !wp.save
    -
    -    wp.field_name = 'due_date'
    -    assert wp.save
    -
    -    wp.field_name = '1'
    -    assert wp.save
    -  end
    -end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/31/31dde3fcb99bae1f1bf825ebba05c5ceb4525424.svn-base
    --- a/.svn/pristine/31/31dde3fcb99bae1f1bf825ebba05c5ceb4525424.svn-base	Wed May 07 14:15:02 2014 +0100
    +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    @@ -1,27 +0,0 @@
    -
    -<%= link_to_if_authorized l(:label_query_new), new_project_query_path(:project_id => @project), :class => 'icon icon-add' %> -
    - -

    <%= l(:label_query_plural) %>

    - -<% if @queries.empty? %> -

    <%=l(:label_no_data)%>

    -<% else %> - - <% @queries.each do |query| %> - - - - - <% end %> -
    - <%= link_to h(query.name), :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %> - - - <% if query.editable_by?(User.current) %> - <%= link_to l(:button_edit), edit_query_path(query), :class => 'icon icon-edit' %> - <%= delete_link query_path(query) %> - - <% end %> -
    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/31/31eee5aeaf6a5a394428efea4fd82e6e1daac3a9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/31/31eee5aeaf6a5a394428efea4fd82e6e1daac3a9.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,106 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::CipheringTest < ActiveSupport::TestCase + + def test_password_should_be_encrypted + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') + assert_equal 'foo', r.password + assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) + end + end + + def test_password_should_be_clear_with_blank_key + Redmine::Configuration.with 'database_cipher_key' => '' do + r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') + assert_equal 'foo', r.password + assert_equal 'foo', r.read_attribute(:password) + end + end + + def test_password_should_be_clear_with_nil_key + Redmine::Configuration.with 'database_cipher_key' => nil do + r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') + assert_equal 'foo', r.password + assert_equal 'foo', r.read_attribute(:password) + end + end + + def test_blank_password_should_be_clear + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository::Subversion.create!(:password => '', :url => 'file:///tmp', :identifier => 'svn') + assert_equal '', r.password + assert_equal '', r.read_attribute(:password) + end + end + + def test_unciphered_password_should_be_readable + Redmine::Configuration.with 'database_cipher_key' => nil do + r = Repository::Subversion.create!(:password => 'clear', :url => 'file:///tmp', :identifier => 'svn') + end + + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository.first(:order => 'id DESC') + assert_equal 'clear', r.password + end + end + + def test_ciphered_password_with_no_cipher_key_configured_should_be_returned_ciphered + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository::Subversion.create!(:password => 'clear', :url => 'file:///tmp', :identifier => 'svn') + end + + Redmine::Configuration.with 'database_cipher_key' => '' do + r = Repository.first(:order => 'id DESC') + # password can not be deciphered + assert_nothing_raised do + assert r.password.match(/\Aaes-256-cbc:.+\Z/) + end + end + end + + def test_encrypt_all + Repository.delete_all + Redmine::Configuration.with 'database_cipher_key' => nil do + Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'foo') + Repository::Subversion.create!(:password => 'bar', :url => 'file:///tmp', :identifier => 'bar') + end + + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + assert Repository.encrypt_all(:password) + r = Repository.first(:order => 'id DESC') + assert_equal 'bar', r.password + assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) + end + end + + def test_decrypt_all + Repository.delete_all + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'foo') + Repository::Subversion.create!(:password => 'bar', :url => 'file:///tmp', :identifier => 'bar') + + assert Repository.decrypt_all(:password) + r = Repository.first(:order => 'id DESC') + assert_equal 'bar', r.password + assert_equal 'bar', r.read_attribute(:password) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/31/31f8831dd1cf0c2ef63a3b5e3c999a2d0f32707b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/31/31f8831dd1cf0c2ef63a3b5e3c999a2d0f32707b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,134 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'redcloth3' +require 'digest/md5' + +module Redmine + module WikiFormatting + module Textile + class Formatter < RedCloth3 + include ActionView::Helpers::TagHelper + include Redmine::WikiFormatting::LinksHelper + + alias :inline_auto_link :auto_link! + alias :inline_auto_mailto :auto_mailto! + + # auto_link rule after textile rules so that it doesn't break !image_url! tags + RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto] + + def initialize(*args) + super + self.hard_breaks=true + self.no_span_caps=true + self.filter_styles=false + end + + def to_html(*rules) + @toc = [] + super(*RULES).to_s + end + + def get_section(index) + section = extract_sections(index)[1] + hash = Digest::MD5.hexdigest(section) + return section, hash + end + + def update_section(index, update, hash=nil) + t = extract_sections(index) + if hash.present? && hash != Digest::MD5.hexdigest(t[1]) + raise Redmine::WikiFormatting::StaleSectionError + end + t[1] = update unless t[1].blank? + t.reject(&:blank?).join "\n\n" + end + + def extract_sections(index) + @pre_list = [] + text = self.dup + rip_offtags text, false, false + before = '' + s = '' + after = '' + i = 0 + l = 1 + started = false + ended = false + text.scan(/(((?:.*?)(\A|\r?\n\s*\r?\n))(h(\d+)(#{A}#{C})\.(?::(\S+))?[ \t](.*?)$)|.*)/m).each do |all, content, lf, heading, level| + if heading.nil? + if ended + after << all + elsif started + s << all + else + before << all + end + break + end + i += 1 + if ended + after << all + elsif i == index + l = level.to_i + before << content + s << heading + started = true + elsif i > index + s << content + if level.to_i > l + s << heading + else + after << heading + ended = true + end + else + before << all + end + end + sections = [before.strip, s.strip, after.strip] + sections.each {|section| smooth_offtags_without_code_highlighting section} + sections + end + + private + + # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. + # http://code.whytheluckystiff.net/redcloth/changeset/128 + def hard_break( text ) + text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks + end + + alias :smooth_offtags_without_code_highlighting :smooth_offtags + # Patch to add code highlighting support to RedCloth + def smooth_offtags( text ) + unless @pre_list.empty? + ## replace
     content
    +            text.gsub!(//) do
    +              content = @pre_list[$1.to_i]
    +              if content.match(/\s?(.+)/m)
    +                content = "" +
    +                  Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
    +              end
    +              content
    +            end
    +          end
    +        end
    +      end
    +    end
    +  end
    +end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/32/323c5888f7f213199068cdb2446ac990b6701da1.svn-base
    --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    +++ b/.svn/pristine/32/323c5888f7f213199068cdb2446ac990b6701da1.svn-base	Tue Sep 09 09:34:53 2014 +0100
    @@ -0,0 +1,89 @@
    +# Redmine - project management software
    +# Copyright (C) 2006-2014  Jean-Philippe Lang
    +#
    +# 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.
    +
    +require File.expand_path('../../test_helper', __FILE__)
    +
    +class RepositoryFilesystemTest < ActiveSupport::TestCase
    +  fixtures :projects
    +
    +  include Redmine::I18n
    +
    +  REPOSITORY_PATH = Rails.root.join('tmp/test/filesystem_repository').to_s
    +
    +  def setup
    +    @project = Project.find(3)
    +    Setting.enabled_scm << 'Filesystem' unless Setting.enabled_scm.include?('Filesystem')
    +    @repository = Repository::Filesystem.create(
    +                               :project => @project,
    +                               :url     => REPOSITORY_PATH
    +                                 )
    +    assert @repository
    +  end
    +
    +  def test_blank_root_directory_error_message
    +    set_language_if_valid 'en'
    +    repo = Repository::Filesystem.new(
    +                          :project      => @project,
    +                          :identifier   => 'test'
    +                        )
    +    assert !repo.save
    +    assert_include "Root directory can't be blank",
    +                   repo.errors.full_messages
    +  end
    +
    +  def test_blank_root_directory_error_message_fr
    +    set_language_if_valid 'fr'
    +    str = "R\xc3\xa9pertoire racine doit \xc3\xaatre renseign\xc3\xa9(e)"
    +    str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
    +    repo = Repository::Filesystem.new(
    +                          :project      => @project,
    +                          :url          => "",
    +                          :identifier   => 'test',
    +                          :path_encoding => ''
    +                        )
    +    assert !repo.save
    +    assert_include str, repo.errors.full_messages
    +  end
    +
    +  if File.directory?(REPOSITORY_PATH)
    +    def test_fetch_changesets
    +      assert_equal 0, @repository.changesets.count
    +      assert_equal 0, @repository.filechanges.count
    +      @repository.fetch_changesets
    +      @project.reload
    +      assert_equal 0, @repository.changesets.count
    +      assert_equal 0, @repository.filechanges.count
    +    end
    +
    +    def test_entries
    +      entries = @repository.entries("", 2)
    +      assert_kind_of Redmine::Scm::Adapters::Entries, entries
    +      assert_equal 3, entries.size
    +    end
    +
    +    def test_entries_in_directory
    +      assert_equal 2, @repository.entries("dir", 3).size
    +    end
    +
    +    def test_cat
    +      assert_equal "TEST CAT\n", @repository.scm.cat("test")
    +    end
    +  else
    +    puts "Filesystem test repository NOT FOUND. Skipping unit tests !!! See doc/RUNNING_TESTS."
    +    def test_fake; assert true end
    +  end
    +end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/32/325cbd5f2592da1a1293c4a50be14ad1338348eb.svn-base
    --- a/.svn/pristine/32/325cbd5f2592da1a1293c4a50be14ad1338348eb.svn-base	Wed May 07 14:15:02 2014 +0100
    +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    @@ -1,212 +0,0 @@
    -# Redmine - project management software
    -# Copyright (C) 2006-2012  Jean-Philippe Lang
    -#
    -# 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.
    -
    -require File.expand_path('../../../test_helper', __FILE__)
    -
    -class ApiTest::GroupsTest < ActionController::IntegrationTest
    -  fixtures :users, :groups_users
    -
    -  def setup
    -    Setting.rest_api_enabled = '1'
    -  end
    -
    -  context "GET /groups" do
    -    context ".xml" do
    -      should "require authentication" do
    -        get '/groups.xml'
    -        assert_response 401
    -      end
    -
    -      should "return groups" do
    -        get '/groups.xml', {}, credentials('admin')
    -        assert_response :success
    -        assert_equal 'application/xml', response.content_type
    -
    -        assert_select 'groups' do
    -          assert_select 'group' do
    -            assert_select 'name', :text => 'A Team'
    -            assert_select 'id', :text => '10'
    -          end
    -        end
    -      end
    -    end
    -
    -    context ".json" do
    -      should "require authentication" do
    -        get '/groups.json'
    -        assert_response 401
    -      end
    -
    -      should "return groups" do
    -        get '/groups.json', {}, credentials('admin')
    -        assert_response :success
    -        assert_equal 'application/json', response.content_type
    -
    -        json = MultiJson.load(response.body)
    -        groups = json['groups']
    -        assert_kind_of Array, groups
    -        group = groups.detect {|g| g['name'] == 'A Team'}
    -        assert_not_nil group
    -        assert_equal({'id' => 10, 'name' => 'A Team'}, group)
    -      end
    -    end
    -  end
    -
    -  context "GET /groups/:id" do
    -    context ".xml" do
    -      should "return the group with its users" do
    -        get '/groups/10.xml', {}, credentials('admin')
    -        assert_response :success
    -        assert_equal 'application/xml', response.content_type
    -
    -        assert_select 'group' do
    -          assert_select 'name', :text => 'A Team'
    -          assert_select 'id', :text => '10'
    -        end
    -      end
    -
    -      should "include users if requested" do
    -        get '/groups/10.xml?include=users', {}, credentials('admin')
    -        assert_response :success
    -        assert_equal 'application/xml', response.content_type
    -
    -        assert_select 'group' do
    -          assert_select 'users' do
    -            assert_select 'user', Group.find(10).users.count
    -            assert_select 'user[id=8]'
    -          end
    -        end
    -      end
    -
    -      should "include memberships if requested" do
    -        get '/groups/10.xml?include=memberships', {}, credentials('admin')
    -        assert_response :success
    -        assert_equal 'application/xml', response.content_type
    -
    -        assert_select 'group' do
    -          assert_select 'memberships'
    -        end
    -      end
    -    end
    -  end
    -
    -  context "POST /groups" do
    -    context "with valid parameters" do
    -      context ".xml" do
    -        should "create groups" do
    -          assert_difference('Group.count') do
    -            post '/groups.xml', {:group => {:name => 'Test', :user_ids => [2, 3]}}, credentials('admin')
    -            assert_response :created
    -            assert_equal 'application/xml', response.content_type
    -          end
    -  
    -          group = Group.order('id DESC').first
    -          assert_equal 'Test', group.name
    -          assert_equal [2, 3], group.users.map(&:id).sort
    -
    -          assert_select 'group' do
    -            assert_select 'name', :text => 'Test'
    -          end
    -        end
    -      end
    -    end
    -
    -    context "with invalid parameters" do
    -      context ".xml" do
    -        should "return errors" do
    -          assert_no_difference('Group.count') do
    -            post '/groups.xml', {:group => {:name => ''}}, credentials('admin')
    -          end
    -          assert_response :unprocessable_entity
    -          assert_equal 'application/xml', response.content_type
    -
    -          assert_select 'errors' do
    -            assert_select 'error', :text => /Name can't be blank/
    -          end
    -        end
    -      end
    -    end
    -  end
    -
    -  context "PUT /groups/:id" do
    -    context "with valid parameters" do
    -      context ".xml" do
    -        should "update the group" do
    -          put '/groups/10.xml', {:group => {:name => 'New name', :user_ids => [2, 3]}}, credentials('admin')
    -          assert_response :ok
    -          assert_equal '', @response.body
    -  
    -          group = Group.find(10)
    -          assert_equal 'New name', group.name
    -          assert_equal [2, 3], group.users.map(&:id).sort
    -        end
    -      end
    -    end
    -
    -    context "with invalid parameters" do
    -      context ".xml" do
    -        should "return errors" do
    -          put '/groups/10.xml', {:group => {:name => ''}}, credentials('admin')
    -          assert_response :unprocessable_entity
    -          assert_equal 'application/xml', response.content_type
    -
    -          assert_select 'errors' do
    -            assert_select 'error', :text => /Name can't be blank/
    -          end
    -        end
    -      end
    -    end
    -  end
    -
    -  context "DELETE /groups/:id" do
    -    context ".xml" do
    -      should "delete the group" do
    -        assert_difference 'Group.count', -1 do
    -          delete '/groups/10.xml', {}, credentials('admin')
    -          assert_response :ok
    -          assert_equal '', @response.body
    -        end
    -      end
    -    end
    -  end
    -
    -  context "POST /groups/:id/users" do
    -    context ".xml" do
    -      should "add user to the group" do
    -        assert_difference 'Group.find(10).users.count' do
    -          post '/groups/10/users.xml', {:user_id => 5}, credentials('admin')
    -          assert_response :ok
    -          assert_equal '', @response.body
    -        end
    -        assert_include User.find(5), Group.find(10).users
    -      end
    -    end
    -  end
    -
    -  context "DELETE /groups/:id/users/:user_id" do
    -    context ".xml" do
    -      should "remove user from the group" do
    -        assert_difference 'Group.find(10).users.count', -1 do
    -          delete '/groups/10/users/8.xml', {}, credentials('admin')
    -          assert_response :ok
    -          assert_equal '', @response.body
    -        end
    -        assert_not_include User.find(8), Group.find(10).users
    -      end
    -    end
    -  end
    -end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/32/3263175b802b3311f08d06f669648333f55d611c.svn-base
    --- a/.svn/pristine/32/3263175b802b3311f08d06f669648333f55d611c.svn-base	Wed May 07 14:15:02 2014 +0100
    +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    @@ -1,51 +0,0 @@
    -# Redmine - project management software
    -# Copyright (C) 2006-2012  Jean-Philippe Lang
    -#
    -# 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.
    -
    -require File.expand_path('../../../test_helper', __FILE__)
    -
    -class RoutingWatchersTest < ActionController::IntegrationTest
    -  def test_watchers
    -    assert_routing(
    -        { :method => 'get', :path => "/watchers/new" },
    -        { :controller => 'watchers', :action => 'new' }
    -      )
    -    assert_routing(
    -        { :method => 'post', :path => "/watchers/append" },
    -        { :controller => 'watchers', :action => 'append' }
    -      )
    -    assert_routing(
    -        { :method => 'post', :path => "/watchers" },
    -        { :controller => 'watchers', :action => 'create' }
    -      )
    -    assert_routing(
    -        { :method => 'post', :path => "/watchers/destroy" },
    -        { :controller => 'watchers', :action => 'destroy' }
    -      )
    -    assert_routing(
    -        { :method => 'get', :path => "/watchers/autocomplete_for_user" },
    -        { :controller => 'watchers', :action => 'autocomplete_for_user' }
    -      )
    -    assert_routing(
    -        { :method => 'post', :path => "/watchers/watch" },
    -        { :controller => 'watchers', :action => 'watch' }
    -      )
    -    assert_routing(
    -        { :method => 'post', :path => "/watchers/unwatch" },
    -        { :controller => 'watchers', :action => 'unwatch' }
    -      )
    -  end
    -end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/32/328e5d644f9c7f34ce3b37165d648bfc6611fce5.svn-base
    --- a/.svn/pristine/32/328e5d644f9c7f34ce3b37165d648bfc6611fce5.svn-base	Wed May 07 14:15:02 2014 +0100
    +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    @@ -1,15 +0,0 @@
    -class AddCustomFieldsPosition < ActiveRecord::Migration
    -  def self.up
    -    add_column(:custom_fields, :position, :integer, :default => 1)
    -    CustomField.find(:all).group_by(&:type).each  do |t, fields|
    -      fields.each_with_index do |field, i|
    -        # do not call model callbacks
    -        CustomField.update_all "position = #{i+1}", {:id => field.id}
    -      end
    -    end
    -  end
    -
    -  def self.down
    -    remove_column :custom_fields, :position
    -  end
    -end
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/32/32b483f4b1d72e41eec78bed07706dfe97d3dda2.svn-base
    --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    +++ b/.svn/pristine/32/32b483f4b1d72e41eec78bed07706dfe97d3dda2.svn-base	Tue Sep 09 09:34:53 2014 +0100
    @@ -0,0 +1,41 @@
    +syntax: glob
    +
    +.project
    +.loadpath
    +.powrc
    +.rvmrc
    +config/additional_environment.rb
    +config/configuration.yml
    +config/database.yml
    +config/email.yml
    +config/initializers/session_store.rb
    +config/initializers/secret_token.rb
    +coverage
    +db/*.db
    +db/*.sqlite3
    +db/schema.rb
    +files/*
    +lib/redmine/scm/adapters/mercurial/redminehelper.pyc
    +lib/redmine/scm/adapters/mercurial/redminehelper.pyo
    +log/*.log*
    +log/mongrel_debug
    +public/dispatch.*
    +public/plugin_assets
    +tmp/*
    +tmp/cache/*
    +tmp/pdf/*
    +tmp/sessions/*
    +tmp/sockets/*
    +tmp/test/*
    +tmp/thumbnails/*
    +vendor/cache
    +vendor/rails
    +*.rbc
    +
    +.svn/
    +.git/
    +
    +.bundle
    +Gemfile.lock
    +Gemfile.local
    +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/32/32bb68107ce90e09885cb5aa647d1b1b670aefe9.svn-base
    --- a/.svn/pristine/32/32bb68107ce90e09885cb5aa647d1b1b670aefe9.svn-base	Wed May 07 14:15:02 2014 +0100
    +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    @@ -1,86 +0,0 @@
    -<%= board_breadcrumb(@message) %>
    -
    -
    - <%= watcher_tag(@topic, User.current) %> - <%= link_to( - l(:button_quote), - {:action => 'quote', :id => @topic}, - :remote => true, - :method => 'get', - :class => 'icon icon-comment', - :remote => true) if !@topic.locked? && authorize_for('messages', 'reply') %> - <%= link_to( - l(:button_edit), - {:action => 'edit', :id => @topic}, - :class => 'icon icon-edit' - ) if @message.editable_by?(User.current) %> - <%= link_to( - l(:button_delete), - {:action => 'destroy', :id => @topic}, - :method => :post, - :data => {:confirm => l(:text_are_you_sure)}, - :class => 'icon icon-del' - ) if @message.destroyable_by?(User.current) %> -
    - -

    <%= avatar(@topic.author, :size => "24") %><%=h @topic.subject %>

    - -
    -

    <%= authoring @topic.created_on, @topic.author %>

    -
    -<%= textilizable(@topic, :content) %> -
    -<%= link_to_attachments @topic, :author => false %> -
    -
    - -<% unless @replies.empty? %> -

    <%= l(:label_reply_plural) %> (<%= @reply_count %>)

    -<% @replies.each do |message| %> -
    "> -
    - <%= link_to( - image_tag('comment.png'), - {:action => 'quote', :id => message}, - :remote => true, - :method => 'get', - :title => l(:button_quote)) if !@topic.locked? && authorize_for('messages', 'reply') %> - <%= link_to( - image_tag('edit.png'), - {:action => 'edit', :id => message}, - :title => l(:button_edit) - ) if message.editable_by?(User.current) %> - <%= link_to( - image_tag('delete.png'), - {:action => 'destroy', :id => message}, - :method => :post, - :data => {:confirm => l(:text_are_you_sure)}, - :title => l(:button_delete) - ) if message.destroyable_by?(User.current) %> -
    -

    - <%= avatar(message.author, :size => "24") %> - <%= link_to h(message.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => @topic, :r => message, :anchor => "message-#{message.id}" } %> - - - <%= authoring message.created_on, message.author %> -

    -
    <%= textilizable message, :content, :attachments => message.attachments %>
    - <%= link_to_attachments message, :author => false %> -
    -<% end %> -

    <%= pagination_links_full @reply_pages, @reply_count, :per_page_links => false %>

    -<% end %> - -<% if !@topic.locked? && authorize_for('messages', 'reply') %> -

    <%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %>

    - -<% end %> - -<% html_title @topic.subject %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/32/32c52319e33e839952a511178f3d013ae8279f9b.svn-base --- a/.svn/pristine/32/32c52319e33e839952a511178f3d013ae8279f9b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Helpers - - # Simple class to compute the start and end dates of a calendar - class Calendar - include Redmine::I18n - attr_reader :startdt, :enddt - - def initialize(date, lang = current_language, period = :month) - @date = date - @events = [] - @ending_events_by_days = {} - @starting_events_by_days = {} - set_language_if_valid lang - case period - when :month - @startdt = Date.civil(date.year, date.month, 1) - @enddt = (@startdt >> 1)-1 - # starts from the first day of the week - @startdt = @startdt - (@startdt.cwday - first_wday)%7 - # ends on the last day of the week - @enddt = @enddt + (last_wday - @enddt.cwday)%7 - when :week - @startdt = date - (date.cwday - first_wday)%7 - @enddt = date + (last_wday - date.cwday)%7 - else - raise 'Invalid period' - end - end - - # Sets calendar events - def events=(events) - @events = events - @ending_events_by_days = @events.group_by {|event| event.due_date} - @starting_events_by_days = @events.group_by {|event| event.start_date} - end - - # Returns events for the given day - def events_on(day) - ((@ending_events_by_days[day] || []) + (@starting_events_by_days[day] || [])).uniq - end - - # Calendar current month - def month - @date.month - end - - # Return the first day of week - # 1 = Monday ... 7 = Sunday - def first_wday - case Setting.start_of_week.to_i - when 1 - @first_dow ||= (1 - 1)%7 + 1 - when 6 - @first_dow ||= (6 - 1)%7 + 1 - when 7 - @first_dow ||= (7 - 1)%7 + 1 - else - @first_dow ||= (l(:general_first_day_of_week).to_i - 1)%7 + 1 - end - end - - def last_wday - @last_dow ||= (first_wday + 5)%7 + 1 - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/32/32d36ad9d8096df93d00980c0d2786c39a657ed2.svn-base --- a/.svn/pristine/32/32d36ad9d8096df93d00980c0d2786c39a657ed2.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,247 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module WikiFormatting - module Macros - module Definitions - # Returns true if +name+ is the name of an existing macro - def macro_exists?(name) - Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym) - end - - def exec_macro(name, obj, args, text) - macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym] - return unless macro_options - - method_name = "macro_#{name}" - unless macro_options[:parse_args] == false - args = args.split(',').map(&:strip) - end - - begin - if self.class.instance_method(method_name).arity == 3 - send(method_name, obj, args, text) - elsif text - raise "This macro does not accept a block of text" - else - send(method_name, obj, args) - end - rescue => e - "
    Error executing the #{h name} macro (#{h e.to_s})
    ".html_safe - end - end - - def extract_macro_options(args, *keys) - options = {} - while args.last.to_s.strip =~ %r{^(.+?)\=(.+)$} && keys.include?($1.downcase.to_sym) - options[$1.downcase.to_sym] = $2 - args.pop - end - return [args, options] - end - end - - @@available_macros = {} - mattr_accessor :available_macros - - class << self - # Plugins can use this method to define new macros: - # - # Redmine::WikiFormatting::Macros.register do - # desc "This is my macro" - # macro :my_macro do |obj, args| - # "My macro output" - # end - # - # desc "This is my macro that accepts a block of text" - # macro :my_macro do |obj, args, text| - # "My macro output" - # end - # end - def register(&block) - class_eval(&block) if block_given? - end - - # Defines a new macro with the given name, options and block. - # - # Options: - # * :desc - A description of the macro - # * :parse_args => false - Disables arguments parsing (the whole arguments - # string is passed to the macro) - # - # Macro blocks accept 2 or 3 arguments: - # * obj: the object that is rendered (eg. an Issue, a WikiContent...) - # * args: macro arguments - # * text: the block of text given to the macro (should be present only if the - # macro accepts a block of text). text is a String or nil if the macro is - # invoked without a block of text. - # - # Examples: - # By default, when the macro is invoked, the coma separated list of arguments - # is split and passed to the macro block as an array. If no argument is given - # the macro will be invoked with an empty array: - # - # macro :my_macro do |obj, args| - # # args is an array - # # and this macro do not accept a block of text - # end - # - # You can disable arguments spliting with the :parse_args => false option. In - # this case, the full string of arguments is passed to the macro: - # - # macro :my_macro, :parse_args => false do |obj, args| - # # args is a string - # end - # - # Macro can optionally accept a block of text: - # - # macro :my_macro do |obj, args, text| - # # this macro accepts a block of text - # end - # - # Macros are invoked in formatted text using double curly brackets. Arguments - # must be enclosed in parenthesis if any. A new line after the macro name or the - # arguments starts the block of text that will be passe to the macro (invoking - # a macro that do not accept a block of text with some text will fail). - # Examples: - # - # No arguments: - # {{my_macro}} - # - # With arguments: - # {{my_macro(arg1, arg2)}} - # - # With a block of text: - # {{my_macro - # multiple lines - # of text - # }} - # - # With arguments and a block of text - # {{my_macro(arg1, arg2) - # multiple lines - # of text - # }} - # - # If a block of text is given, the closing tag }} must be at the start of a new line. - def macro(name, options={}, &block) - options.assert_valid_keys(:desc, :parse_args) - unless name.to_s.match(/\A\w+\z/) - raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)" - end - unless block_given? - raise "Can not create a macro without a block!" - end - name = name.to_s.downcase.to_sym - available_macros[name] = {:desc => @@desc || ''}.merge(options) - @@desc = nil - Definitions.send :define_method, "macro_#{name}", &block - end - - # Sets description for the next macro to be defined - def desc(txt) - @@desc = txt - end - end - - # Builtin macros - desc "Sample macro." - macro :hello_world do |obj, args, text| - h("Hello world! Object: #{obj.class.name}, " + - (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") + - " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.") - ) - end - - desc "Displays a list of all available macros, including description if available." - macro :macro_list do |obj, args| - out = ''.html_safe - @@available_macros.each do |macro, options| - out << content_tag('dt', content_tag('code', macro.to_s)) - out << content_tag('dd', textilizable(options[:desc])) - end - content_tag('dl', out) - end - - desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" + - " !{{child_pages}} -- can be used from a wiki page only\n" + - " !{{child_pages(depth=2)}} -- display 2 levels nesting only\n" - " !{{child_pages(Foo)}} -- lists all children of page Foo\n" + - " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo" - macro :child_pages do |obj, args| - args, options = extract_macro_options(args, :parent, :depth) - options[:depth] = options[:depth].to_i if options[:depth].present? - - page = nil - if args.size > 0 - page = Wiki.find_page(args.first.to_s, :project => @project) - elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version) - page = obj.page - else - raise 'With no argument, this macro can be called from wiki pages only.' - end - raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) - pages = page.self_and_descendants(options[:depth]).group_by(&:parent_id) - render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id) - end - - desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}" - macro :include do |obj, args| - page = Wiki.find_page(args.first.to_s, :project => @project) - raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) - @included_wiki_pages ||= [] - raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title) - @included_wiki_pages << page.title - out = textilizable(page.content, :text, :attachments => page.attachments, :headings => false) - @included_wiki_pages.pop - out - end - - desc "Inserts of collapsed block of text. Example:\n\n {{collapse(View details...)\nThis is a block of text that is collapsed by default.\nIt can be expanded by clicking a link.\n}}" - macro :collapse do |obj, args, text| - html_id = "collapse-#{Redmine::Utils.random_hex(4)}" - show_label = args[0] || l(:button_show) - hide_label = args[1] || args[0] || l(:button_hide) - js = "$('##{html_id}-show, ##{html_id}-hide').toggle(); $('##{html_id}').fadeToggle(150);" - out = ''.html_safe - out << link_to_function(show_label, js, :id => "#{html_id}-show", :class => 'collapsible collapsed') - out << link_to_function(hide_label, js, :id => "#{html_id}-hide", :class => 'collapsible', :style => 'display:none;') - out << content_tag('div', textilizable(text, :object => obj), :id => html_id, :class => 'collapsed-text', :style => 'display:none;') - out - end - - desc "Displays a clickable thumbnail of an attached image. Examples:\n\n
    {{thumbnail(image.png)}}\n{{thumbnail(image.png, size=300, title=Thumbnail)}}
    " - macro :thumbnail do |obj, args| - args, options = extract_macro_options(args, :size, :title) - filename = args.first - raise 'Filename required' unless filename.present? - size = options[:size] - raise 'Invalid size parameter' unless size.nil? || size.match(/^\d+$/) - size = size.to_i - size = nil unless size > 0 - if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename) - title = options[:title] || attachment.title - img = image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size), :alt => attachment.filename) - link_to(img, url_for(:controller => 'attachments', :action => 'show', :id => attachment), :class => 'thumbnail', :title => title) - else - raise "Attachment #{filename} not found" - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/32/32f1eb36d32f7e5b14215f433d0d23c189721a5e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/32/32f1eb36d32f7e5b14215f433d0d23c189721a5e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,15 @@ +# Loads the core plugins located in lib/plugins +Dir.glob(File.join(Rails.root, "lib/plugins/*")).sort.each do |directory| + if File.directory?(directory) + lib = File.join(directory, "lib") + if File.directory?(lib) + $:.unshift lib + ActiveSupport::Dependencies.autoload_paths += [lib] + end + initializer = File.join(directory, "init.rb") + if File.file?(initializer) + config = RedmineApp::Application.config + eval(File.read(initializer), binding, initializer) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/33/330c3139ce88daeba1ec5a385dfead443884905c.svn-base --- a/.svn/pristine/33/330c3139ce88daeba1ec5a385dfead443884905c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'mail_handler_controller' - -# Re-raise errors caught by the controller. -class MailHandlerController; def rescue_action(e) raise e end; end - -class MailHandlerControllerTest < ActionController::TestCase - fixtures :users, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses, - :trackers, :projects_trackers, :enumerations - - FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler' - - def setup - @controller = MailHandlerController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_should_create_issue - # Enable API and set a key - Setting.mail_handler_api_enabled = 1 - Setting.mail_handler_api_key = 'secret' - - assert_difference 'Issue.count' do - post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')) - end - assert_response 201 - end - - def test_should_respond_with_422_if_not_created - Project.find('onlinestore').destroy - - Setting.mail_handler_api_enabled = 1 - Setting.mail_handler_api_key = 'secret' - - assert_no_difference 'Issue.count' do - post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')) - end - assert_response 422 - end - - def test_should_not_allow_with_api_disabled - # Disable API - Setting.mail_handler_api_enabled = 0 - Setting.mail_handler_api_key = 'secret' - - assert_no_difference 'Issue.count' do - post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')) - end - assert_response 403 - end - - def test_should_not_allow_with_wrong_key - # Disable API - Setting.mail_handler_api_enabled = 1 - Setting.mail_handler_api_key = 'secret' - - assert_no_difference 'Issue.count' do - post :index, :key => 'wrong', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')) - end - assert_response 403 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/33/33196b4d983ac84071a32086264adeca270eeb1b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/33/33196b4d983ac84071a32086264adeca270eeb1b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1114 @@ +# French translations for Ruby on Rails +# by Christian Lescuyer (christian@flyingcoders.com) +# contributor: Sebastien Grosjean - ZenCocoon.com +# contributor: Thibaut Cuvelier - Developpez.com + +fr: + direction: ltr + date: + formats: + default: "%d/%m/%Y" + short: "%e %b" + long: "%e %B %Y" + long_ordinal: "%e %B %Y" + only_day: "%e" + + day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi] + abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam] + month_names: [~, janvier, février, mars, avril, mai, juin, juillet, août, septembre, octobre, novembre, décembre] + abbr_month_names: [~, jan., fév., mar., avr., mai, juin, juil., août, sept., oct., nov., déc.] + order: + - :day + - :month + - :year + + time: + formats: + default: "%d/%m/%Y %H:%M" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%A %d %B %Y %H:%M:%S %Z" + long_ordinal: "%A %d %B %Y %H:%M:%S %Z" + only_second: "%S" + am: 'am' + pm: 'pm' + + datetime: + distance_in_words: + half_a_minute: "30 secondes" + less_than_x_seconds: + zero: "moins d'une seconde" + one: "moins d'une seconde" + other: "moins de %{count} secondes" + x_seconds: + one: "1 seconde" + other: "%{count} secondes" + less_than_x_minutes: + zero: "moins d'une minute" + one: "moins d'une minute" + other: "moins de %{count} minutes" + x_minutes: + one: "1 minute" + other: "%{count} minutes" + about_x_hours: + one: "environ une heure" + other: "environ %{count} heures" + x_hours: + one: "une heure" + other: "%{count} heures" + x_days: + one: "un jour" + other: "%{count} jours" + about_x_months: + one: "environ un mois" + other: "environ %{count} mois" + x_months: + one: "un mois" + other: "%{count} mois" + about_x_years: + one: "environ un an" + other: "environ %{count} ans" + over_x_years: + one: "plus d'un an" + other: "plus de %{count} ans" + almost_x_years: + one: "presqu'un an" + other: "presque %{count} ans" + prompts: + year: "Année" + month: "Mois" + day: "Jour" + hour: "Heure" + minute: "Minute" + second: "Seconde" + + number: + format: + precision: 3 + separator: ',' + delimiter: ' ' + currency: + format: + unit: '€' + precision: 2 + format: '%n %u' + human: + format: + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "octet" + other: "octet" + kb: "ko" + mb: "Mo" + gb: "Go" + tb: "To" + + support: + array: + sentence_connector: 'et' + skip_last_comma: true + word_connector: ", " + two_words_connector: " et " + last_word_connector: " et " + + activerecord: + errors: + template: + header: + one: "Impossible d'enregistrer %{model} : une erreur" + other: "Impossible d'enregistrer %{model} : %{count} erreurs." + body: "Veuillez vérifier les champs suivants :" + messages: + inclusion: "n'est pas inclus(e) dans la liste" + exclusion: "n'est pas disponible" + invalid: "n'est pas valide" + confirmation: "ne concorde pas avec la confirmation" + accepted: "doit être accepté(e)" + empty: "doit être renseigné(e)" + blank: "doit être renseigné(e)" + too_long: "est trop long (pas plus de %{count} caractères)" + too_short: "est trop court (au moins %{count} caractères)" + wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)" + taken: "est déjà utilisé" + not_a_number: "n'est pas un nombre" + not_a_date: "n'est pas une date valide" + greater_than: "doit être supérieur à %{count}" + greater_than_or_equal_to: "doit être supérieur ou égal à %{count}" + equal_to: "doit être égal à %{count}" + less_than: "doit être inférieur à %{count}" + less_than_or_equal_to: "doit être inférieur ou égal à %{count}" + odd: "doit être impair" + even: "doit être pair" + greater_than_start_date: "doit être postérieure à la date de début" + not_same_project: "n'appartient pas au même projet" + circular_dependency: "Cette relation créerait une dépendance circulaire" + cant_link_an_issue_with_a_descendant: "Une demande ne peut pas être liée à l'une de ses sous-tâches" + earlier_than_minimum_start_date: "ne peut pas être antérieure au %{date} à cause des demandes qui précédent" + + actionview_instancetag_blank_option: Choisir + + general_text_No: 'Non' + general_text_Yes: 'Oui' + general_text_no: 'non' + general_text_yes: 'oui' + general_lang_name: 'Français' + general_csv_separator: ';' + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Le compte a été mis à jour avec succès. + notice_account_invalid_creditentials: Identifiant ou mot de passe invalide. + notice_account_password_updated: Mot de passe mis à jour avec succès. + notice_account_wrong_password: Mot de passe incorrect + notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a été envoyé à l'adresse %{email}. + notice_account_unknown_email: Aucun compte ne correspond à cette adresse. + notice_account_not_activated_yet: Vous n'avez pas encore activé votre compte. Si vous voulez recevoir un nouveau message d'activation, veuillez cliquer sur ce lien. + notice_account_locked: Votre compte est verrouillé. + notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe. + notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a été envoyé. + notice_account_activated: Votre compte a été activé. Vous pouvez à présent vous connecter. + notice_successful_create: Création effectuée avec succès. + notice_successful_update: Mise à jour effectuée avec succès. + notice_successful_delete: Suppression effectuée avec succès. + notice_successful_connection: Connexion réussie. + notice_file_not_found: "La page à laquelle vous souhaitez accéder n'existe pas ou a été supprimée." + notice_locking_conflict: Les données ont été mises à jour par un autre utilisateur. Mise à jour impossible. + notice_not_authorized: "Vous n'êtes pas autorisé à accéder à cette page." + notice_not_authorized_archived_project: Le projet auquel vous tentez d'accéder a été archivé. + notice_email_sent: "Un email a été envoyé à %{value}" + notice_email_error: "Erreur lors de l'envoi de l'email (%{value})" + notice_feeds_access_key_reseted: "Votre clé d'accès aux flux Atom a été réinitialisée." + notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sélectionnées n'ont pas pu être mise(s) à jour : %{ids}." + notice_failed_to_save_time_entries: "%{count} temps passé(s) sur les %{total} sélectionnés n'ont pas pu être mis à jour: %{ids}." + notice_no_issue_selected: "Aucune demande sélectionnée ! Cochez les demandes que vous voulez mettre à jour." + notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur." + notice_default_data_loaded: Paramétrage par défaut chargé avec succès. + notice_unable_delete_version: Impossible de supprimer cette version. + notice_issue_done_ratios_updated: L'avancement des demandes a été mis à jour. + notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée. + notice_gantt_chart_truncated: "Le diagramme a été tronqué car il excède le nombre maximal d'éléments pouvant être affichés (%{max})" + notice_issue_successful_create: "Demande %{id} créée." + notice_issue_update_conflict: "La demande a été mise à jour par un autre utilisateur pendant que vous la modifiez." + notice_account_deleted: "Votre compte a été définitivement supprimé." + notice_user_successful_create: "Utilisateur %{id} créé." + notice_new_password_must_be_different: Votre nouveau mot de passe doit être différent de votre mot de passe actuel + + error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramétrage : %{value}" + error_scm_not_found: "L'entrée et/ou la révision demandée n'existe pas dans le dépôt." + error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}" + error_scm_annotate: "L'entrée n'existe pas ou ne peut pas être annotée." + error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas à ce projet" + error_can_not_reopen_issue_on_closed_version: 'Une demande assignée à une version fermée ne peut pas être réouverte' + error_can_not_archive_project: "Ce projet ne peut pas être archivé" + error_workflow_copy_source: 'Veuillez sélectionner un tracker et/ou un rôle source' + error_workflow_copy_target: 'Veuillez sélectionner les trackers et rôles cibles' + error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu être mis à jour. + error_attachment_too_big: Ce fichier ne peut pas être attaché car il excède la taille maximale autorisée (%{max_size}) + error_session_expired: "Votre session a expiré. Veuillez vous reconnecter." + + warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu être sauvegardés." + + mail_subject_lost_password: "Votre mot de passe %{value}" + mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :' + mail_subject_register: "Activation de votre compte %{value}" + mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :' + mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter." + mail_body_account_information: Paramètres de connexion de votre compte + mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}" + mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nécessite votre approbation :" + mail_subject_reminder: "%{count} demande(s) arrivent à échéance (%{days})" + mail_body_reminder: "%{count} demande(s) qui vous sont assignées arrivent à échéance dans les %{days} prochains jours :" + mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutée" + mail_body_wiki_content_added: "La page wiki '%{id}' a été ajoutée par %{author}." + mail_subject_wiki_content_updated: "Page wiki '%{id}' mise à jour" + mail_body_wiki_content_updated: "La page wiki '%{id}' a été mise à jour par %{author}." + + + field_name: Nom + field_description: Description + field_summary: Résumé + field_is_required: Obligatoire + field_firstname: Prénom + field_lastname: Nom + field_mail: "Email " + field_filename: Fichier + field_filesize: Taille + field_downloads: Téléchargements + field_author: Auteur + field_created_on: "Créé " + field_updated_on: "Mis-à-jour " + field_closed_on: Fermé + field_field_format: Format + field_is_for_all: Pour tous les projets + field_possible_values: Valeurs possibles + field_regexp: Expression régulière + field_min_length: Longueur minimum + field_max_length: Longueur maximum + field_value: Valeur + field_category: Catégorie + field_title: Titre + field_project: Projet + field_issue: Demande + field_status: Statut + field_notes: Notes + field_is_closed: Demande fermée + field_is_default: Valeur par défaut + field_tracker: Tracker + field_subject: Sujet + field_due_date: Echéance + field_assigned_to: Assigné à + field_priority: Priorité + field_fixed_version: Version cible + field_user: Utilisateur + field_role: Rôle + field_homepage: "Site web " + field_is_public: Public + field_parent: Sous-projet de + field_is_in_roadmap: Demandes affichées dans la roadmap + field_login: "Identifiant " + field_mail_notification: Notifications par mail + field_admin: Administrateur + field_last_login_on: "Dernière connexion " + field_language: Langue + field_effective_date: Date + field_password: Mot de passe + field_new_password: Nouveau mot de passe + field_password_confirmation: Confirmation + field_version: Version + field_type: Type + field_host: Hôte + field_port: Port + field_account: Compte + field_base_dn: Base DN + field_attr_login: Attribut Identifiant + field_attr_firstname: Attribut Prénom + field_attr_lastname: Attribut Nom + field_attr_mail: Attribut Email + field_onthefly: Création des utilisateurs à la volée + field_start_date: Début + field_done_ratio: "% réalisé" + field_auth_source: Mode d'authentification + field_hide_mail: Cacher mon adresse mail + field_comments: Commentaire + field_url: URL + field_start_page: Page de démarrage + field_subproject: Sous-projet + field_hours: Heures + field_activity: Activité + field_spent_on: Date + field_identifier: Identifiant + field_is_filter: Utilisé comme filtre + field_issue_to: Demande liée + field_delay: Retard + field_assignable: Demandes assignables à ce rôle + field_redirect_existing_links: Rediriger les liens existants + field_estimated_hours: Temps estimé + field_column_names: Colonnes + field_time_zone: Fuseau horaire + field_searchable: Utilisé pour les recherches + field_default_value: Valeur par défaut + field_comments_sorting: Afficher les commentaires + field_parent_title: Page parent + field_editable: Modifiable + field_watcher: Observateur + field_identity_url: URL OpenID + field_content: Contenu + field_group_by: Grouper par + field_sharing: Partage + field_active: Actif + field_parent_issue: Tâche parente + field_visible: Visible + field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardé" + field_issues_visibility: Visibilité des demandes + field_is_private: Privée + field_commit_logs_encoding: Encodage des messages de commit + field_repository_is_default: Dépôt principal + field_multiple: Valeurs multiples + field_auth_source_ldap_filter: Filtre LDAP + field_core_fields: Champs standards + field_timeout: "Timeout (en secondes)" + field_board_parent: Forum parent + field_private_notes: Notes privées + field_inherit_members: Hériter les membres + field_generate_password: Générer un mot de passe + field_must_change_passwd: Doit changer de mot de passe à la prochaine connexion + + setting_app_title: Titre de l'application + setting_app_subtitle: Sous-titre de l'application + setting_welcome_text: Texte d'accueil + setting_default_language: Langue par défaut + setting_login_required: Authentification obligatoire + setting_self_registration: Inscription des nouveaux utilisateurs + setting_attachment_max_size: Taille maximale des fichiers + setting_issues_export_limit: Limite d'exportation des demandes + setting_mail_from: Adresse d'émission + setting_bcc_recipients: Destinataires en copie cachée (cci) + setting_plain_text_mail: Mail en texte brut (non HTML) + setting_host_name: Nom d'hôte et chemin + setting_text_formatting: Formatage du texte + setting_wiki_compression: Compression de l'historique des pages wiki + setting_feeds_limit: Nombre maximal d'éléments dans les flux Atom + setting_default_projects_public: Définir les nouveaux projets comme publics par défaut + setting_autofetch_changesets: Récupération automatique des commits + setting_sys_api_enabled: Activer les WS pour la gestion des dépôts + setting_commit_ref_keywords: Mots-clés de référencement + setting_commit_fix_keywords: Mots-clés de résolution + setting_autologin: Durée maximale de connexion automatique + setting_date_format: Format de date + setting_time_format: Format d'heure + setting_cross_project_issue_relations: Autoriser les relations entre demandes de différents projets + setting_cross_project_subtasks: Autoriser les sous-tâches dans des projets différents + setting_issue_list_default_columns: Colonnes affichées par défaut sur la liste des demandes + setting_emails_footer: Pied-de-page des emails + setting_protocol: Protocole + setting_per_page_options: Options d'objets affichés par page + setting_user_format: Format d'affichage des utilisateurs + setting_activity_days_default: Nombre de jours affichés sur l'activité des projets + setting_display_subprojects_issues: Afficher par défaut les demandes des sous-projets sur les projets principaux + setting_enabled_scm: SCM activés + setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes" + setting_mail_handler_api_enabled: "Activer le WS pour la réception d'emails" + setting_mail_handler_api_key: Clé de protection de l'API + setting_sequential_project_identifiers: Générer des identifiants de projet séquentiels + setting_gravatar_enabled: Afficher les Gravatar des utilisateurs + setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichées + setting_file_max_size_displayed: Taille maximum des fichiers texte affichés en ligne + setting_repository_log_display_limit: "Nombre maximum de révisions affichées sur l'historique d'un fichier" + setting_openid: "Autoriser l'authentification et l'enregistrement OpenID" + setting_password_min_length: Longueur minimum des mots de passe + setting_new_project_user_role_id: Rôle donné à un utilisateur non-administrateur qui crée un projet + setting_default_projects_modules: Modules activés par défaut pour les nouveaux projets + setting_issue_done_ratio: Calcul de l'avancement des demandes + setting_issue_done_ratio_issue_status: Utiliser le statut + setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectué' + setting_rest_api_enabled: Activer l'API REST + setting_gravatar_default: Image Gravatar par défaut + setting_start_of_week: Jour de début des calendriers + setting_cache_formatted_text: Mettre en cache le texte formaté + setting_commit_logtime_enabled: Permettre la saisie de temps + setting_commit_logtime_activity_id: Activité pour le temps saisi + setting_gantt_items_limit: Nombre maximum d'éléments affichés sur le gantt + setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes + setting_default_issue_start_date_to_creation_date: Donner à la date de début d'une nouvelle demande la valeur de la date du jour + setting_commit_cross_project_ref: Permettre le référencement et la résolution des demandes de tous les autres projets + setting_unsubscribe: Permettre aux utilisateurs de supprimer leur propre compte + setting_session_lifetime: Durée de vie maximale des sessions + setting_session_timeout: Durée maximale d'inactivité + setting_thumbnails_enabled: Afficher les vignettes des images + setting_thumbnails_size: Taille des vignettes (en pixels) + setting_non_working_week_days: Jours non travaillés + setting_jsonp_enabled: Activer le support JSONP + setting_default_projects_tracker_ids: Trackers par défaut pour les nouveaux projets + setting_mail_handler_excluded_filenames: Exclure les fichiers attachés par leur nom + + permission_add_project: Créer un projet + permission_add_subprojects: Créer des sous-projets + permission_edit_project: Modifier le projet + permission_close_project: Fermer / réouvrir le projet + permission_select_project_modules: Choisir les modules + permission_manage_members: Gérer les membres + permission_manage_versions: Gérer les versions + permission_manage_categories: Gérer les catégories de demandes + permission_view_issues: Voir les demandes + permission_add_issues: Créer des demandes + permission_edit_issues: Modifier les demandes + permission_manage_issue_relations: Gérer les relations + permission_set_issues_private: Rendre les demandes publiques ou privées + permission_set_own_issues_private: Rendre ses propres demandes publiques ou privées + permission_add_issue_notes: Ajouter des notes + permission_edit_issue_notes: Modifier les notes + permission_edit_own_issue_notes: Modifier ses propres notes + permission_view_private_notes: Voir les notes privées + permission_set_notes_private: Rendre les notes privées + permission_move_issues: Déplacer les demandes + permission_delete_issues: Supprimer les demandes + permission_manage_public_queries: Gérer les requêtes publiques + permission_save_queries: Sauvegarder les requêtes + permission_view_gantt: Voir le gantt + permission_view_calendar: Voir le calendrier + permission_view_issue_watchers: Voir la liste des observateurs + permission_add_issue_watchers: Ajouter des observateurs + permission_delete_issue_watchers: Supprimer des observateurs + permission_log_time: Saisir le temps passé + permission_view_time_entries: Voir le temps passé + permission_edit_time_entries: Modifier les temps passés + permission_edit_own_time_entries: Modifier son propre temps passé + permission_manage_news: Gérer les annonces + permission_comment_news: Commenter les annonces + permission_view_documents: Voir les documents + permission_add_documents: Ajouter des documents + permission_edit_documents: Modifier les documents + permission_delete_documents: Supprimer les documents + permission_manage_files: Gérer les fichiers + permission_view_files: Voir les fichiers + permission_manage_wiki: Gérer le wiki + permission_rename_wiki_pages: Renommer les pages + permission_delete_wiki_pages: Supprimer les pages + permission_view_wiki_pages: Voir le wiki + permission_view_wiki_edits: "Voir l'historique des modifications" + permission_edit_wiki_pages: Modifier les pages + permission_delete_wiki_pages_attachments: Supprimer les fichiers joints + permission_protect_wiki_pages: Protéger les pages + permission_manage_repository: Gérer le dépôt de sources + permission_browse_repository: Parcourir les sources + permission_view_changesets: Voir les révisions + permission_commit_access: Droit de commit + permission_manage_boards: Gérer les forums + permission_view_messages: Voir les messages + permission_add_messages: Poster un message + permission_edit_messages: Modifier les messages + permission_edit_own_messages: Modifier ses propres messages + permission_delete_messages: Supprimer les messages + permission_delete_own_messages: Supprimer ses propres messages + permission_export_wiki_pages: Exporter les pages + permission_manage_project_activities: Gérer les activités + permission_manage_subtasks: Gérer les sous-tâches + permission_manage_related_issues: Gérer les demandes associées + + project_module_issue_tracking: Suivi des demandes + project_module_time_tracking: Suivi du temps passé + project_module_news: Publication d'annonces + project_module_documents: Publication de documents + project_module_files: Publication de fichiers + project_module_wiki: Wiki + project_module_repository: Dépôt de sources + project_module_boards: Forums de discussion + + label_user: Utilisateur + label_user_plural: Utilisateurs + label_user_new: Nouvel utilisateur + label_user_anonymous: Anonyme + label_project: Projet + label_project_new: Nouveau projet + label_project_plural: Projets + label_x_projects: + zero: aucun projet + one: un projet + other: "%{count} projets" + label_project_all: Tous les projets + label_project_latest: Derniers projets + label_issue: Demande + label_issue_new: Nouvelle demande + label_issue_plural: Demandes + label_issue_view_all: Voir toutes les demandes + label_issue_added: Demande ajoutée + label_issue_updated: Demande mise à jour + label_issue_note_added: Note ajoutée + label_issue_status_updated: Statut changé + label_issue_priority_updated: Priorité changée + label_issues_by: "Demandes par %{value}" + label_document: Document + label_document_new: Nouveau document + label_document_plural: Documents + label_document_added: Document ajouté + label_role: Rôle + label_role_plural: Rôles + label_role_new: Nouveau rôle + label_role_and_permissions: Rôles et permissions + label_role_anonymous: Anonyme + label_role_non_member: Non membre + label_member: Membre + label_member_new: Nouveau membre + label_member_plural: Membres + label_tracker: Tracker + label_tracker_plural: Trackers + label_tracker_new: Nouveau tracker + label_workflow: Workflow + label_issue_status: Statut de demandes + label_issue_status_plural: Statuts de demandes + label_issue_status_new: Nouveau statut + label_issue_category: Catégorie de demandes + label_issue_category_plural: Catégories de demandes + label_issue_category_new: Nouvelle catégorie + label_custom_field: Champ personnalisé + label_custom_field_plural: Champs personnalisés + label_custom_field_new: Nouveau champ personnalisé + label_enumerations: Listes de valeurs + label_enumeration_new: Nouvelle valeur + label_information: Information + label_information_plural: Informations + label_please_login: Identification + label_register: S'enregistrer + label_login_with_open_id_option: S'authentifier avec OpenID + label_password_lost: Mot de passe perdu + label_home: Accueil + label_my_page: Ma page + label_my_account: Mon compte + label_my_projects: Mes projets + label_my_page_block: Blocs disponibles + label_administration: Administration + label_login: Connexion + label_logout: Déconnexion + label_help: Aide + label_reported_issues: "Demandes soumises " + label_assigned_to_me_issues: Demandes qui me sont assignées + label_last_login: "Dernière connexion " + label_registered_on: "Inscrit le " + label_activity: Activité + label_overall_activity: Activité globale + label_user_activity: "Activité de %{value}" + label_new: Nouveau + label_logged_as: Connecté en tant que + label_environment: Environnement + label_authentication: Authentification + label_auth_source: Mode d'authentification + label_auth_source_new: Nouveau mode d'authentification + label_auth_source_plural: Modes d'authentification + label_subproject_plural: Sous-projets + label_subproject_new: Nouveau sous-projet + label_and_its_subprojects: "%{value} et ses sous-projets" + label_min_max_length: Longueurs mini - maxi + label_list: Liste + label_date: Date + label_integer: Entier + label_float: Nombre décimal + label_boolean: Booléen + label_string: Texte + label_text: Texte long + label_attribute: Attribut + label_attribute_plural: Attributs + label_no_data: Aucune donnée à afficher + label_change_status: Changer le statut + label_history: Historique + label_attachment: Fichier + label_attachment_new: Nouveau fichier + label_attachment_delete: Supprimer le fichier + label_attachment_plural: Fichiers + label_file_added: Fichier ajouté + label_report: Rapport + label_report_plural: Rapports + label_news: Annonce + label_news_new: Nouvelle annonce + label_news_plural: Annonces + label_news_latest: Dernières annonces + label_news_view_all: Voir toutes les annonces + label_news_added: Annonce ajoutée + label_news_comment_added: Commentaire ajouté à une annonce + label_settings: Configuration + label_overview: Aperçu + label_version: Version + label_version_new: Nouvelle version + label_version_plural: Versions + label_confirmation: Confirmation + label_export_to: 'Formats disponibles :' + label_read: Lire... + label_public_projects: Projets publics + label_open_issues: ouvert + label_open_issues_plural: ouverts + label_closed_issues: fermé + label_closed_issues_plural: fermés + label_x_open_issues_abbr_on_total: + zero: 0 ouverte sur %{total} + one: 1 ouverte sur %{total} + other: "%{count} ouvertes sur %{total}" + label_x_open_issues_abbr: + zero: 0 ouverte + one: 1 ouverte + other: "%{count} ouvertes" + label_x_closed_issues_abbr: + zero: 0 fermée + one: 1 fermée + other: "%{count} fermées" + label_x_issues: + zero: 0 demande + one: 1 demande + other: "%{count} demandes" + label_total: Total + label_total_time: Temps total + label_permissions: Permissions + label_current_status: Statut actuel + label_new_statuses_allowed: Nouveaux statuts autorisés + label_all: tous + label_any: tous + label_none: aucun + label_nobody: personne + label_next: Suivant + label_previous: Précédent + label_used_by: Utilisé par + label_details: Détails + label_add_note: Ajouter une note + label_per_page: Par page + label_calendar: Calendrier + label_months_from: mois depuis + label_gantt: Gantt + label_internal: Interne + label_last_changes: "%{count} derniers changements" + label_change_view_all: Voir tous les changements + label_personalize_page: Personnaliser cette page + label_comment: Commentaire + label_comment_plural: Commentaires + label_x_comments: + zero: aucun commentaire + one: un commentaire + other: "%{count} commentaires" + label_comment_add: Ajouter un commentaire + label_comment_added: Commentaire ajouté + label_comment_delete: Supprimer les commentaires + label_query: Rapport personnalisé + label_query_plural: Rapports personnalisés + label_query_new: Nouveau rapport + label_my_queries: Mes rapports personnalisés + label_filter_add: "Ajouter le filtre " + label_filter_plural: Filtres + label_equals: égal + label_not_equals: différent + label_in_less_than: dans moins de + label_in_more_than: dans plus de + label_in_the_next_days: dans les prochains jours + label_in_the_past_days: dans les derniers jours + label_in: dans + label_today: aujourd'hui + label_all_time: toute la période + label_yesterday: hier + label_this_week: cette semaine + label_last_week: la semaine dernière + label_last_n_weeks: "les %{count} dernières semaines" + label_last_n_days: "les %{count} derniers jours" + label_this_month: ce mois-ci + label_last_month: le mois dernier + label_this_year: cette année + label_date_range: Période + label_less_than_ago: il y a moins de + label_more_than_ago: il y a plus de + label_ago: il y a + label_contains: contient + label_not_contains: ne contient pas + label_any_issues_in_project: une demande du projet + label_any_issues_not_in_project: une demande hors du projet + label_no_issues_in_project: aucune demande du projet + label_day_plural: jours + label_repository: Dépôt + label_repository_new: Nouveau dépôt + label_repository_plural: Dépôts + label_browse: Parcourir + label_revision: "Révision " + label_revision_plural: Révisions + label_associated_revisions: Révisions associées + label_added: ajouté + label_modified: modifié + label_copied: copié + label_renamed: renommé + label_deleted: supprimé + label_latest_revision: Dernière révision + label_latest_revision_plural: Dernières révisions + label_view_revisions: Voir les révisions + label_max_size: Taille maximale + label_sort_highest: Remonter en premier + label_sort_higher: Remonter + label_sort_lower: Descendre + label_sort_lowest: Descendre en dernier + label_roadmap: Roadmap + label_roadmap_due_in: "Échéance dans %{value}" + label_roadmap_overdue: "En retard de %{value}" + label_roadmap_no_issues: Aucune demande pour cette version + label_search: "Recherche " + label_result_plural: Résultats + label_all_words: Tous les mots + label_wiki: Wiki + label_wiki_edit: Révision wiki + label_wiki_edit_plural: Révisions wiki + label_wiki_page: Page wiki + label_wiki_page_plural: Pages wiki + label_index_by_title: Index par titre + label_index_by_date: Index par date + label_current_version: Version actuelle + label_preview: Prévisualisation + label_feed_plural: Flux Atom + label_changes_details: Détails de tous les changements + label_issue_tracking: Suivi des demandes + label_spent_time: Temps passé + label_f_hour: "%{value} heure" + label_f_hour_plural: "%{value} heures" + label_time_tracking: Suivi du temps + label_change_plural: Changements + label_statistics: Statistiques + label_commits_per_month: Commits par mois + label_commits_per_author: Commits par auteur + label_view_diff: Voir les différences + label_diff_inline: en ligne + label_diff_side_by_side: côte à côte + label_options: Options + label_copy_workflow_from: Copier le workflow de + label_permissions_report: Synthèse des permissions + label_watched_issues: Demandes surveillées + label_related_issues: Demandes liées + label_applied_status: Statut appliqué + label_loading: Chargement... + label_relation_new: Nouvelle relation + label_relation_delete: Supprimer la relation + label_relates_to: Lié à + label_duplicates: Duplique + label_duplicated_by: Dupliqué par + label_blocks: Bloque + label_blocked_by: Bloqué par + label_precedes: Précède + label_follows: Suit + label_copied_to: Copié vers + label_copied_from: Copié depuis + label_end_to_start: fin à début + label_end_to_end: fin à fin + label_start_to_start: début à début + label_start_to_end: début à fin + label_stay_logged_in: Rester connecté + label_disabled: désactivé + label_show_completed_versions: Voir les versions passées + label_me: moi + label_board: Forum + label_board_new: Nouveau forum + label_board_plural: Forums + label_topic_plural: Discussions + label_message_plural: Messages + label_message_last: Dernier message + label_message_new: Nouveau message + label_message_posted: Message ajouté + label_reply_plural: Réponses + label_send_information: Envoyer les informations à l'utilisateur + label_year: Année + label_month: Mois + label_week: Semaine + label_date_from: Du + label_date_to: Au + label_language_based: Basé sur la langue de l'utilisateur + label_sort_by: "Trier par %{value}" + label_send_test_email: Envoyer un email de test + label_feeds_access_key_created_on: "Clé d'accès Atom créée il y a %{value}" + label_module_plural: Modules + label_added_time_by: "Ajouté par %{author} il y a %{age}" + label_updated_time_by: "Mis à jour par %{author} il y a %{age}" + label_updated_time: "Mis à jour il y a %{value}" + label_jump_to_a_project: Aller à un projet... + label_file_plural: Fichiers + label_changeset_plural: Révisions + label_default_columns: Colonnes par défaut + label_no_change_option: (Pas de changement) + label_bulk_edit_selected_issues: Modifier les demandes sélectionnées + label_theme: Thème + label_default: Défaut + label_search_titles_only: Uniquement dans les titres + label_user_mail_option_all: "Pour tous les événements de tous mes projets" + label_user_mail_option_selected: "Pour tous les événements des projets sélectionnés..." + label_user_mail_no_self_notified: "Je ne veux pas être notifié des changements que j'effectue" + label_registration_activation_by_email: activation du compte par email + label_registration_manual_activation: activation manuelle du compte + label_registration_automatic_activation: activation automatique du compte + label_display_per_page: "Par page : %{value}" + label_age: Âge + label_change_properties: Changer les propriétés + label_general: Général + label_more: Plus + label_scm: SCM + label_plugins: Plugins + label_ldap_authentication: Authentification LDAP + label_downloads_abbr: D/L + label_optional_description: Description facultative + label_add_another_file: Ajouter un autre fichier + label_preferences: Préférences + label_chronological_order: Dans l'ordre chronologique + label_reverse_chronological_order: Dans l'ordre chronologique inverse + label_planning: Planning + label_incoming_emails: Emails entrants + label_generate_key: Générer une clé + label_issue_watchers: Observateurs + label_example: Exemple + label_display: Affichage + label_sort: Tri + label_ascending: Croissant + label_descending: Décroissant + label_date_from_to: Du %{start} au %{end} + label_wiki_content_added: Page wiki ajoutée + label_wiki_content_updated: Page wiki mise à jour + label_group_plural: Groupes + label_group: Groupe + label_group_new: Nouveau groupe + label_time_entry_plural: Temps passé + label_version_sharing_none: Non partagé + label_version_sharing_descendants: Avec les sous-projets + label_version_sharing_hierarchy: Avec toute la hiérarchie + label_version_sharing_tree: Avec tout l'arbre + label_version_sharing_system: Avec tous les projets + label_copy_source: Source + label_copy_target: Cible + label_copy_same_as_target: Comme la cible + label_update_issue_done_ratios: Mettre à jour l'avancement des demandes + label_display_used_statuses_only: N'afficher que les statuts utilisés dans ce tracker + label_api_access_key: Clé d'accès API + label_api_access_key_created_on: Clé d'accès API créée il y a %{value} + label_feeds_access_key: Clé d'accès Atom + label_missing_api_access_key: Clé d'accès API manquante + label_missing_feeds_access_key: Clé d'accès Atom manquante + label_close_versions: Fermer les versions terminées + label_revision_id: Révision %{value} + label_profile: Profil + label_subtask_plural: Sous-tâches + label_project_copy_notifications: Envoyer les notifications durant la copie du projet + label_principal_search: "Rechercher un utilisateur ou un groupe :" + label_user_search: "Rechercher un utilisateur :" + label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande + label_additional_workflow_transitions_for_assignee: Autorisations supplémentaires lorsque la demande est assignée à l'utilisateur + label_issues_visibility_all: Toutes les demandes + label_issues_visibility_public: Toutes les demandes non privées + label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur + label_export_options: Options d'exportation %{export_format} + label_copy_attachments: Copier les fichiers + label_copy_subtasks: Copier les sous-tâches + label_item_position: "%{position} sur %{count}" + label_completed_versions: Versions passées + label_session_expiration: Expiration des sessions + label_show_closed_projects: Voir les projets fermés + label_status_transitions: Changements de statut + label_fields_permissions: Permissions sur les champs + label_readonly: Lecture + label_required: Obligatoire + label_hidden: Caché + label_attribute_of_project: "%{name} du projet" + label_attribute_of_issue: "%{name} de la demande" + label_attribute_of_author: "%{name} de l'auteur" + label_attribute_of_assigned_to: "%{name} de l'assigné" + label_attribute_of_user: "%{name} de l'utilisateur" + label_attribute_of_fixed_version: "%{name} de la version cible" + label_cross_project_descendants: Avec les sous-projets + label_cross_project_tree: Avec tout l'arbre + label_cross_project_hierarchy: Avec toute la hiérarchie + label_cross_project_system: Avec tous les projets + label_gantt_progress_line: Ligne de progression + label_visibility_private: par moi uniquement + label_visibility_roles: par ces roles uniquement + label_visibility_public: par tout le monde + + button_login: Connexion + button_submit: Soumettre + button_save: Sauvegarder + button_check_all: Tout cocher + button_uncheck_all: Tout décocher + button_collapse_all: Plier tout + button_expand_all: Déplier tout + button_delete: Supprimer + button_create: Créer + button_create_and_continue: Créer et continuer + button_test: Tester + button_edit: Modifier + button_add: Ajouter + button_change: Changer + button_apply: Appliquer + button_clear: Effacer + button_lock: Verrouiller + button_unlock: Déverrouiller + button_download: Télécharger + button_list: Lister + button_view: Voir + button_move: Déplacer + button_move_and_follow: Déplacer et suivre + button_back: Retour + button_cancel: Annuler + button_activate: Activer + button_sort: Trier + button_log_time: Saisir temps + button_rollback: Revenir à cette version + button_watch: Surveiller + button_unwatch: Ne plus surveiller + button_reply: Répondre + button_archive: Archiver + button_unarchive: Désarchiver + button_reset: Réinitialiser + button_rename: Renommer + button_change_password: Changer de mot de passe + button_copy: Copier + button_copy_and_follow: Copier et suivre + button_annotate: Annoter + button_update: Mettre à jour + button_configure: Configurer + button_quote: Citer + button_duplicate: Dupliquer + button_show: Afficher + button_hide: Cacher + button_edit_section: Modifier cette section + button_export: Exporter + button_delete_my_account: Supprimer mon compte + button_close: Fermer + button_reopen: Réouvrir + + status_active: actif + status_registered: enregistré + status_locked: verrouillé + + project_status_active: actif + project_status_closed: fermé + project_status_archived: archivé + + version_status_open: ouvert + version_status_locked: verrouillé + version_status_closed: fermé + + text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyée + text_regexp_info: ex. ^[A-Z0-9]+$ + text_min_max_length_info: 0 pour aucune restriction + text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ? + text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront également supprimés." + text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow + text_are_you_sure: Êtes-vous sûr ? + text_tip_issue_begin_day: tâche commençant ce jour + text_tip_issue_end_day: tâche finissant ce jour + text_tip_issue_begin_end_day: tâche commençant et finissant ce jour + text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et tirets bas sont autorisés, doit commencer par une minuscule.
    Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' + text_caracters_maximum: "%{count} caractères maximum." + text_caracters_minimum: "%{count} caractères minimum." + text_length_between: "Longueur comprise entre %{min} et %{max} caractères." + text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker + text_unallowed_characters: Caractères non autorisés + text_comma_separated: Plusieurs valeurs possibles (séparées par des virgules). + text_line_separated: Plusieurs valeurs possibles (une valeur par ligne). + text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires de commits + text_issue_added: "La demande %{id} a été soumise par %{author}." + text_issue_updated: "La demande %{id} a été mise à jour par %{author}." + text_wiki_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce wiki et tout son contenu ? + text_issue_category_destroy_question: "%{count} demandes sont affectées à cette catégorie. Que voulez-vous faire ?" + text_issue_category_destroy_assignments: N'affecter les demandes à aucune autre catégorie + text_issue_category_reassign_to: Réaffecter les demandes à cette catégorie + text_user_mail_option: "Pour les projets non sélectionnés, vous recevrez seulement des notifications pour ce que vous surveillez ou à quoi vous participez (exemple: demandes dont vous êtes l'auteur ou la personne assignée)." + text_no_configuration_data: "Les rôles, trackers, statuts et le workflow ne sont pas encore paramétrés.\nIl est vivement recommandé de charger le paramétrage par defaut. Vous pourrez le modifier une fois chargé." + text_load_default_configuration: Charger le paramétrage par défaut + text_status_changed_by_changeset: "Appliqué par commit %{value}." + text_time_logged_by_changeset: "Appliqué par commit %{value}" + text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?' + text_issues_destroy_descendants_confirmation: "Cela entrainera également la suppression de %{count} sous-tâche(s)." + text_select_project_modules: 'Sélectionner les modules à activer pour ce projet :' + text_default_administrator_account_changed: Compte administrateur par défaut changé + text_file_repository_writable: Répertoire de stockage des fichiers accessible en écriture + text_plugin_assets_writable: Répertoire public des plugins accessible en écriture + text_rmagick_available: Bibliothèque RMagick présente (optionnelle) + text_destroy_time_entries_question: "%{hours} heures ont été enregistrées sur les demandes à supprimer. Que voulez-vous faire ?" + text_destroy_time_entries: Supprimer les heures + text_assign_time_entries_to_project: Reporter les heures sur le projet + text_reassign_time_entries: 'Reporter les heures sur cette demande:' + text_user_wrote: "%{value} a écrit :" + text_enumeration_destroy_question: "Cette valeur est affectée à %{count} objets." + text_enumeration_category_reassign_to: 'Réaffecter les objets à cette valeur:' + text_email_delivery_not_configured: "L'envoi de mail n'est pas configuré, les notifications sont désactivées.\nConfigurez votre serveur SMTP dans config/configuration.yml et redémarrez l'application pour les activer." + text_repository_usernames_mapping: "Vous pouvez sélectionner ou modifier l'utilisateur Redmine associé à chaque nom d'utilisateur figurant dans l'historique du dépôt.\nLes utilisateurs avec le même identifiant ou la même adresse mail seront automatiquement associés." + text_diff_truncated: '... Ce différentiel a été tronqué car il excède la taille maximale pouvant être affichée.' + text_custom_field_possible_values_info: 'Une ligne par valeur' + text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?" + text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines" + text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes" + text_wiki_page_reassign_children: "Réaffecter les sous-pages à cette page" + text_own_membership_delete_confirmation: "Vous allez supprimer tout ou partie de vos permissions sur ce projet et ne serez peut-être plus autorisé à modifier ce projet.\nEtes-vous sûr de vouloir continuer ?" + text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardé qui sera perdu si vous quittez la page." + text_issue_conflict_resolution_overwrite: "Appliquer quand même ma mise à jour (les notes précédentes seront conservées mais des changements pourront être écrasés)" + text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements" + text_issue_conflict_resolution_cancel: "Annuler ma mise à jour et réafficher %{link}" + text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver." + text_session_expiration_settings: "Attention : le changement de ces paramètres peut entrainer l'expiration des sessions utilisateurs en cours, y compris la vôtre." + text_project_closed: Ce projet est fermé et accessible en lecture seule. + text_turning_multiple_off: "Si vous désactivez les valeurs multiples, les valeurs multiples seront supprimées pour n'en conserver qu'une par objet." + + default_role_manager: "Manager " + default_role_developer: "Développeur " + default_role_reporter: "Rapporteur " + default_tracker_bug: Anomalie + default_tracker_feature: Evolution + default_tracker_support: Assistance + default_issue_status_new: Nouveau + default_issue_status_in_progress: En cours + default_issue_status_resolved: Résolu + default_issue_status_feedback: Commentaire + default_issue_status_closed: Fermé + default_issue_status_rejected: Rejeté + default_doc_category_user: Documentation utilisateur + default_doc_category_tech: Documentation technique + default_priority_low: Bas + default_priority_normal: Normal + default_priority_high: Haut + default_priority_urgent: Urgent + default_priority_immediate: Immédiat + default_activity_design: Conception + default_activity_development: Développement + + enumeration_issue_priorities: Priorités des demandes + enumeration_doc_categories: Catégories des documents + enumeration_activities: Activités (suivi du temps) + label_greater_or_equal: ">=" + label_less_or_equal: "<=" + label_between: entre + label_view_all_revisions: Voir toutes les révisions + label_tag: Tag + label_branch: Branche + error_no_tracker_in_project: "Aucun tracker n'est associé à ce projet. Vérifier la configuration du projet." + error_no_default_issue_status: "Aucun statut de demande n'est défini par défaut. Vérifier votre configuration (Administration -> Statuts de demandes)." + text_journal_changed: "%{label} changé de %{old} à %{new}" + text_journal_changed_no_detail: "%{label} mis à jour" + text_journal_set_to: "%{label} mis à %{value}" + text_journal_deleted: "%{label} %{old} supprimé" + text_journal_added: "%{label} %{value} ajouté" + enumeration_system_activity: Activité système + label_board_sticky: Sticky + label_board_locked: Verrouillé + error_unable_delete_issue_status: Impossible de supprimer le statut de demande + error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisé + error_unable_to_connect: Connexion impossible (%{value}) + error_can_not_remove_role: Ce rôle est utilisé et ne peut pas être supprimé. + error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas être supprimé. + field_principal: Principal + notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}." + text_zoom_out: Zoom arrière + text_zoom_in: Zoom avant + notice_unable_delete_time_entry: Impossible de supprimer le temps passé. + label_overall_spent_time: Temps passé global + field_time_entries: Temps passé + project_module_gantt: Gantt + project_module_calendar: Calendrier + button_edit_associated_wikipage: "Modifier la page wiki associée: %{page_title}" + field_text: Champ texte + label_user_mail_option_only_owner: Seulement pour ce que j'ai créé + setting_default_notification_option: Option de notification par défaut + label_user_mail_option_only_my_events: Seulement pour ce que je surveille + label_user_mail_option_only_assigned: Seulement pour ce qui m'est assigné + label_user_mail_option_none: Aucune notification + field_member_of_group: Groupe de l'assigné + field_assigned_to_role: Rôle de l'assigné + setting_emails_header: En-tête des emails + label_bulk_edit_selected_time_entries: Modifier les temps passés sélectionnés + text_time_entries_destroy_confirmation: "Etes-vous sûr de vouloir supprimer les temps passés sélectionnés ?" + field_scm_path_encoding: Encodage des chemins + text_scm_path_encoding_note: "Défaut : UTF-8" + field_path_to_repository: Chemin du dépôt + field_root_directory: Répertoire racine + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: "Dépôt local (exemples : /hgrepo, c:\\hgrepo)" + text_scm_command: Commande + text_scm_command_version: Version + label_git_report_last_commit: Afficher le dernier commit des fichiers et répertoires + text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification. + text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration. + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Ordre de tri + description_project_scope: Périmètre de recherche + description_filter: Filtre + description_user_mail_notification: Option de notification + description_date_from: Date de début + description_message_content: Contenu du message + description_available_columns: Colonnes disponibles + description_all_columns: Toutes les colonnes + description_date_range_interval: Choisir une période + description_issue_category_reassign: Choisir une catégorie + description_search: Champ de recherche + description_notes: Notes + description_date_range_list: Choisir une période prédéfinie + description_choose_project: Projets + description_date_to: Date de fin + description_query_sort_criteria_attribute: Critère de tri + description_wiki_subpages_reassign: Choisir une nouvelle page parent + description_selected_columns: Colonnes sélectionnées + label_parent_revision: Parent + label_child_revision: Enfant + error_scm_annotate_big_text_file: Cette entrée ne peut pas être annotée car elle excède la taille maximale. + setting_repositories_encodings: Encodages des fichiers et des dépôts + label_search_for_watchers: Rechercher des observateurs + text_repository_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et tirets bas sont autorisés.
    Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' + text_convert_available: Binaire convert de ImageMagick présent (optionel) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/33/33275847ce5564fa4ff57de7cb764a0f4a778730.svn-base --- a/.svn/pristine/33/33275847ce5564fa4ff57de7cb764a0f4a778730.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -

    <%=l(:label_report_plural)%>

    - -
    -

    <%=l(:field_tracker)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'tracker' %>

    -<%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %> -
    -

    <%=l(:field_priority)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'priority' %>

    -<%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %> -
    -

    <%=l(:field_assigned_to)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'assigned_to' %>

    -<%= render :partial => 'simple', :locals => { :data => @issues_by_assigned_to, :field_name => "assigned_to_id", :rows => @assignees } %> -
    -

    <%=l(:field_author)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'author' %>

    -<%= render :partial => 'simple', :locals => { :data => @issues_by_author, :field_name => "author_id", :rows => @authors } %> -
    -<%= call_hook(:view_reports_issue_report_split_content_left, :project => @project) %> -
    - -
    -

    <%=l(:field_version)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'version' %>

    -<%= render :partial => 'simple', :locals => { :data => @issues_by_version, :field_name => "fixed_version_id", :rows => @versions } %> -
    -<% if @project.children.any? %> -

    <%=l(:field_subproject)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'subproject' %>

    -<%= render :partial => 'simple', :locals => { :data => @issues_by_subproject, :field_name => "project_id", :rows => @subprojects } %> -
    -<% end %> -

    <%=l(:field_category)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'category' %>

    -<%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %> -
    -<%= call_hook(:view_reports_issue_report_split_content_right, :project => @project) %> -
    - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/33/334c69118b9eeb233bc531edf269bf4abad61e24.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/33/334c69118b9eeb233bc531edf269bf4abad61e24.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,51 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingAccountTest < ActionController::IntegrationTest + def test_account + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/login" }, + { :controller => 'account', :action => 'login' } + ) + end + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/logout" }, + { :controller => 'account', :action => 'logout' } + ) + end + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/account/register" }, + { :controller => 'account', :action => 'register' } + ) + end + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/account/lost_password" }, + { :controller => 'account', :action => 'lost_password' } + ) + end + assert_routing( + { :method => 'get', :path => "/account/activate" }, + { :controller => 'account', :action => 'activate' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/33/3370cd22f75e851f0796f6234d205c9ce519841c.svn-base --- a/.svn/pristine/33/3370cd22f75e851f0796f6234d205c9ce519841c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class ProjectsHelperTest < ActionView::TestCase - include ApplicationHelper - include ProjectsHelper - include ERB::Util - - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :versions, - :projects_trackers, - :member_roles, - :members, - :groups_users, - :enabled_modules, - :workflows - - def setup - super - set_language_if_valid('en') - User.current = nil - end - - def test_link_to_version_within_project - @project = Project.find(2) - User.current = User.find(1) - assert_equal 'Alpha', link_to_version(Version.find(5)) - end - - def test_link_to_version - User.current = User.find(1) - assert_equal 'OnlineStore - Alpha', link_to_version(Version.find(5)) - end - - def test_link_to_private_version - assert_equal 'OnlineStore - Alpha', link_to_version(Version.find(5)) - end - - def test_link_to_version_invalid_version - assert_equal '', link_to_version(Object) - end - - def test_format_version_name_within_project - @project = Project.find(1) - assert_equal "0.1", format_version_name(Version.find(1)) - end - - def test_format_version_name - assert_equal "eCookbook - 0.1", format_version_name(Version.find(1)) - end - - def test_format_version_name_for_system_version - assert_equal "OnlineStore - Systemwide visible version", format_version_name(Version.find(7)) - end - - def test_version_options_for_select_with_no_versions - assert_equal '', version_options_for_select([]) - assert_equal '', version_options_for_select([], Version.find(1)) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/33/33f1e1dee1ea9d39975afb9ebca4632d0c5e2445.svn-base --- a/.svn/pristine/33/33f1e1dee1ea9d39975afb9ebca4632d0c5e2445.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class UserPreferenceTest < ActiveSupport::TestCase - fixtures :users, :user_preferences - - def test_create - user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") - user.login = "newuser" - user.password, user.password_confirmation = "password", "password" - assert user.save - - assert_kind_of UserPreference, user.pref - assert_kind_of Hash, user.pref.others - assert user.pref.save - end - - def test_update - user = User.find(1) - assert_equal true, user.pref.hide_mail - user.pref['preftest'] = 'value' - assert user.pref.save - - user.reload - assert_equal 'value', user.pref['preftest'] - end - - def test_others_hash - user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") - user.login = "newuser" - user.password, user.password_confirmation = "password", "password" - assert user.save - assert_nil user.preference - up = UserPreference.new(:user => user) - assert_kind_of Hash, up.others - up.others = nil - assert_nil up.others - assert up.save - assert_kind_of Hash, up.others - end - - def test_reading_value_from_nil_others_hash - up = UserPreference.new(:user => User.new) - up.others = nil - assert_nil up.others - assert_nil up[:foo] - end - - def test_writing_value_to_nil_others_hash - up = UserPreference.new(:user => User.new) - up.others = nil - assert_nil up.others - up[:foo] = 'bar' - assert_equal 'bar', up[:foo] - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/34/342285b027af8a7f1e30f8ee50c822bf5f5c7ecb.svn-base --- a/.svn/pristine/34/342285b027af8a7f1e30f8ee50c822bf5f5c7ecb.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../../../test_helper', __FILE__) - -begin - require 'mocha' - - class SubversionAdapterTest < ActiveSupport::TestCase - - if repository_configured?('subversion') - def setup - @adapter = Redmine::Scm::Adapters::SubversionAdapter.new(self.class.subversion_repository_url) - end - - def test_client_version - v = Redmine::Scm::Adapters::SubversionAdapter.client_version - assert v.is_a?(Array) - end - - def test_scm_version - to_test = { "svn, version 1.6.13 (r1002816)\n" => [1,6,13], - "svn, versione 1.6.13 (r1002816)\n" => [1,6,13], - "1.6.1\n1.7\n1.8" => [1,6,1], - "1.6.2\r\n1.8.1\r\n1.9.1" => [1,6,2]} - to_test.each do |s, v| - test_scm_version_for(s, v) - end - end - - def test_info_not_nil - assert_not_nil @adapter.info - end - - def test_info_nil - adpt = Redmine::Scm::Adapters::SubversionAdapter.new( - "file:///invalid/invalid/" - ) - assert_nil adpt.info - end - - private - - def test_scm_version_for(scm_version, version) - @adapter.class.expects(:scm_version_from_command_line).returns(scm_version) - assert_equal version, @adapter.class.svn_binary_version - end - else - puts "Subversion test repository NOT FOUND. Skipping unit tests !!!" - def test_fake; assert true end - end - end -rescue LoadError - class SubversionMochaFake < ActiveSupport::TestCase - def test_fake; assert(false, "Requires mocha to run those tests") end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/34/345b699f46121e103fdce4587bf3ec87db1cc445.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/34/345b699f46121e103fdce4587bf3ec87db1cc445.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,107 @@ +module ActiveRecord + module Acts + module Tree + def self.included(base) + base.extend(ClassMethods) + end + + # Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children + # association. This requires that you have a foreign key column, which by default is called +parent_id+. + # + # class Category < ActiveRecord::Base + # acts_as_tree :order => "name" + # end + # + # Example: + # root + # \_ child1 + # \_ subchild1 + # \_ subchild2 + # + # root = Category.create("name" => "root") + # child1 = root.children.create("name" => "child1") + # subchild1 = child1.children.create("name" => "subchild1") + # + # root.parent # => nil + # child1.parent # => root + # root.children # => [child1] + # root.children.first.children.first # => subchild1 + # + # In addition to the parent and children associations, the following instance methods are added to the class + # after calling acts_as_tree: + # * siblings - Returns all the children of the parent, excluding the current node ([subchild2] when called on subchild1) + # * self_and_siblings - Returns all the children of the parent, including the current node ([subchild1, subchild2] when called on subchild1) + # * ancestors - Returns all the ancestors of the current node ([child1, root] when called on subchild2) + # * root - Returns the root of the current node (root when called on subchild2) + module ClassMethods + # Configuration options are: + # + # * foreign_key - specifies the column name to use for tracking of the tree (default: +parent_id+) + # * order - makes it possible to sort the children according to this SQL snippet. + # * counter_cache - keeps a count in a +children_count+ column if set to +true+ (default: +false+). + def acts_as_tree(options = {}) + configuration = { :foreign_key => "parent_id", :dependent => :destroy, :order => nil, :counter_cache => nil } + configuration.update(options) if options.is_a?(Hash) + + belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache] + has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => configuration[:dependent] + + scope :roots, where("#{configuration[:foreign_key]} IS NULL").order(configuration[:order]) + + send :include, ActiveRecord::Acts::Tree::InstanceMethods + end + end + + module InstanceMethods + # Returns list of ancestors, starting from parent until root. + # + # subchild1.ancestors # => [child1, root] + def ancestors + node, nodes = self, [] + nodes << node = node.parent while node.parent + nodes + end + + # Returns list of descendants. + # + # root.descendants # => [child1, subchild1, subchild2] + def descendants(depth=nil) + depth ||= 0 + result = children.dup + unless depth == 1 + result += children.collect {|child| child.descendants(depth-1)}.flatten + end + result + end + + # Returns list of descendants and a reference to the current node. + # + # root.self_and_descendants # => [root, child1, subchild1, subchild2] + def self_and_descendants(depth=nil) + [self] + descendants(depth) + end + + # Returns the root node of the tree. + def root + node = self + node = node.parent while node.parent + node + end + + # Returns all siblings of the current node. + # + # subchild1.siblings # => [subchild2] + def siblings + self_and_siblings - [self] + end + + # Returns all siblings and a reference to the current node. + # + # subchild1.self_and_siblings # => [subchild1, subchild2] + def self_and_siblings + parent ? parent.children : self.class.roots + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/34/345f7acae2c1ee68bbdbe5e119f5efcf6fb9fb67.svn-base --- a/.svn/pristine/34/345f7acae2c1ee68bbdbe5e119f5efcf6fb9fb67.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class IssueTransactionTest < ActiveSupport::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, - :trackers, :projects_trackers, - :versions, - :issue_statuses, :issue_categories, :issue_relations, :workflows, - :enumerations, - :issues, - :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, - :time_entries - - self.use_transactional_fixtures = false - - def test_invalid_move_to_another_project - parent1 = Issue.generate! - child = Issue.generate!(:parent_issue_id => parent1.id) - grandchild = Issue.generate!(:parent_issue_id => child.id, :tracker_id => 2) - Project.find(2).tracker_ids = [1] - - parent1.reload - assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] - - # child can not be moved to Project 2 because its child is on a disabled tracker - child = Issue.find(child.id) - child.project = Project.find(2) - assert !child.save - child.reload - grandchild.reload - parent1.reload - - # no change - assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [1, parent1.id, 2, 5], [child.project_id, child.root_id, child.lft, child.rgt] - assert_equal [1, parent1.id, 3, 4], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt] - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/34/345fb72e40574b54a9481a84a098650cf151872f.svn-base --- a/.svn/pristine/34/345fb72e40574b54a9481a84a098650cf151872f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class QueriesController < ApplicationController - menu_item :issues - before_filter :find_query, :except => [:new, :create, :index] - before_filter :find_optional_project, :only => [:new, :create] - - accept_api_auth :index - - include QueriesHelper - - def index - case params[:format] - when 'xml', 'json' - @offset, @limit = api_offset_and_limit - else - @limit = per_page_option - end - - @query_count = Query.visible.count - @query_pages = Paginator.new self, @query_count, @limit, params['page'] - @queries = Query.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name") - - respond_to do |format| - format.html { render :nothing => true } - format.api - end - end - - def new - @query = Query.new - @query.user = User.current - @query.project = @project - @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params - end - - def create - @query = Query.new(params[:query]) - @query.user = User.current - @query.project = params[:query_is_for_all] ? nil : @project - @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params - @query.column_names = nil if params[:default_columns] - - if @query.save - flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query - else - render :action => 'new', :layout => !request.xhr? - end - end - - def edit - end - - def update - @query.attributes = params[:query] - @query.project = nil if params[:query_is_for_all] - @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params - @query.column_names = nil if params[:default_columns] - - if @query.save - flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query - else - render :action => 'edit' - end - end - - def destroy - @query.destroy - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 - end - -private - def find_query - @query = Query.find(params[:id]) - @project = @query.project - render_403 unless @query.editable_by?(User.current) - rescue ActiveRecord::RecordNotFound - render_404 - end - - def find_optional_project - @project = Project.find(params[:project_id]) if params[:project_id] - render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true) - rescue ActiveRecord::RecordNotFound - render_404 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/34/34944e69635f53bef7eb7777ca3abc691bf31c39.svn-base --- a/.svn/pristine/34/34944e69635f53bef7eb7777ca3abc691bf31c39.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -var revisionGraph = null; - -function drawRevisionGraph(holder, commits_hash, graph_space) { - var XSTEP = 20, - CIRCLE_INROW_OFFSET = 10; - var commits_by_scmid = commits_hash, - commits = $.map(commits_by_scmid, function(val,i){return val;}); - var max_rdmid = commits.length - 1; - var commit_table_rows = $('table.changesets tr.changeset'); - - // create graph - if(revisionGraph != null) - revisionGraph.clear(); - else - revisionGraph = Raphael(holder); - - var top = revisionGraph.set(); - // init dimensions - var graph_x_offset = commit_table_rows.first().find('td').first().position().left - $(holder).position().left, - graph_y_offset = $(holder).position().top, - graph_right_side = graph_x_offset + (graph_space + 1) * XSTEP, - graph_bottom = commit_table_rows.last().position().top + commit_table_rows.last().height() - graph_y_offset; - - revisionGraph.setSize(graph_right_side, graph_bottom); - - // init colors - var colors = []; - Raphael.getColor.reset(); - for (var k = 0; k <= graph_space; k++) { - colors.push(Raphael.getColor()); - } - - var parent_commit; - var x, y, parent_x, parent_y; - var path, title; - var revision_dot_overlay; - $.each(commits, function(index, commit) { - if (!commit.hasOwnProperty("space")) - commit.space = 0; - - y = commit_table_rows.eq(max_rdmid - commit.rdmid).position().top - graph_y_offset + CIRCLE_INROW_OFFSET; - x = graph_x_offset + XSTEP / 2 + XSTEP * commit.space; - revisionGraph.circle(x, y, 3) - .attr({ - fill: colors[commit.space], - stroke: 'none', - }).toFront(); - // paths to parents - $.each(commit.parent_scmids, function(index, parent_scmid) { - parent_commit = commits_by_scmid[parent_scmid]; - if (parent_commit) { - if (!parent_commit.hasOwnProperty("space")) - parent_commit.space = 0; - - parent_y = commit_table_rows.eq(max_rdmid - parent_commit.rdmid).position().top - graph_y_offset + CIRCLE_INROW_OFFSET; - parent_x = graph_x_offset + XSTEP / 2 + XSTEP * parent_commit.space; - if (parent_commit.space == commit.space) { - // vertical path - path = revisionGraph.path([ - 'M', x, y, - 'V', parent_y]); - } else { - // path to a commit in a different branch (Bezier curve) - path = revisionGraph.path([ - 'M', x, y, - 'C', x, y, x, y + (parent_y - y) / 2, x + (parent_x - x) / 2, y + (parent_y - y) / 2, - 'C', x + (parent_x - x) / 2, y + (parent_y - y) / 2, parent_x, parent_y-(parent_y-y)/2, parent_x, parent_y]); - } - } else { - // vertical path ending at the bottom of the revisionGraph - path = revisionGraph.path([ - 'M', x, y, - 'V', graph_bottom]); - } - path.attr({stroke: colors[commit.space], "stroke-width": 1.5}).toBack(); - }); - revision_dot_overlay = revisionGraph.circle(x, y, 10); - revision_dot_overlay - .attr({ - fill: '#000', - opacity: 0, - cursor: 'pointer', - href: commit.href - }); - - if(commit.refs != null && commit.refs.length > 0) { - title = document.createElementNS(revisionGraph.canvas.namespaceURI, 'title'); - title.appendChild(document.createTextNode(commit.refs)); - revision_dot_overlay.node.appendChild(title); - } - top.push(revision_dot_overlay); - }); - top.toFront(); -}; diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/34/349bb4552fd5e3ee5085280e36098a4598dd3ace.svn-base --- a/.svn/pristine/34/349bb4552fd5e3ee5085280e36098a4598dd3ace.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class AuthSourceLdapTest < ActiveSupport::TestCase - include Redmine::I18n - fixtures :auth_sources - - def setup - end - - def test_create - a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName') - assert a.save - end - - def test_should_strip_ldap_attributes - a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', - :attr_firstname => 'givenName ') - assert a.save - assert_equal 'givenName', a.reload.attr_firstname - end - - def test_replace_port_zero_to_389 - a = AuthSourceLdap.new( - :name => 'My LDAP', :host => 'ldap.example.net', :port => 0, - :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', - :attr_firstname => 'givenName ') - assert a.save - assert_equal 389, a.port - end - - def test_filter_should_be_validated - set_language_if_valid 'en' - - a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :attr_login => 'sn') - a.filter = "(mail=*@redmine.org" - assert !a.valid? - assert_include "LDAP filter is invalid", a.errors.full_messages - - a.filter = "(mail=*@redmine.org)" - assert a.valid? - end - - if ldap_configured? - context '#authenticate' do - setup do - @auth = AuthSourceLdap.find(1) - @auth.update_attribute :onthefly_register, true - end - - context 'with a valid LDAP user' do - should 'return the user attributes' do - attributes = @auth.authenticate('example1','123456') - assert attributes.is_a?(Hash), "An hash was not returned" - assert_equal 'Example', attributes[:firstname] - assert_equal 'One', attributes[:lastname] - assert_equal 'example1@redmine.org', attributes[:mail] - assert_equal @auth.id, attributes[:auth_source_id] - attributes.keys.each do |attribute| - assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" - end - end - end - - context 'with an invalid LDAP user' do - should 'return nil' do - assert_equal nil, @auth.authenticate('nouser','123456') - end - end - - context 'without a login' do - should 'return nil' do - assert_equal nil, @auth.authenticate('','123456') - end - end - - context 'without a password' do - should 'return nil' do - assert_equal nil, @auth.authenticate('edavis','') - end - end - - context 'without filter' do - should 'return any user' do - assert @auth.authenticate('example1','123456') - assert @auth.authenticate('edavis', '123456') - end - end - - context 'with filter' do - setup do - @auth.filter = "(mail=*@redmine.org)" - end - - should 'return user who matches the filter only' do - assert @auth.authenticate('example1','123456') - assert_nil @auth.authenticate('edavis', '123456') - end - end - end - - def test_authenticate_should_timeout - auth_source = AuthSourceLdap.find(1) - auth_source.timeout = 1 - def auth_source.initialize_ldap_con(*args); sleep(5); end - - assert_raise AuthSourceTimeoutException do - auth_source.authenticate 'example1', '123456' - end - end - else - puts '(Test LDAP server not configured)' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/34/34f59b6c99dab380ca3ed7001d4ae58c9c6a2f50.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/34/34f59b6c99dab380ca3ed7001d4ae58c9c6a2f50.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,89 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class CustomFieldsController < ApplicationController + layout 'admin' + + before_filter :require_admin + before_filter :build_new_custom_field, :only => [:new, :create] + before_filter :find_custom_field, :only => [:edit, :update, :destroy] + accept_api_auth :index + + def index + respond_to do |format| + format.html { + @custom_fields_by_type = CustomField.all.group_by {|f| f.class.name } + @tab = params[:tab] || 'IssueCustomField' + } + format.api { + @custom_fields = CustomField.all + } + end + end + + def new + end + + def create + if @custom_field.save + flash[:notice] = l(:notice_successful_create) + call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field) + redirect_to custom_fields_path(:tab => @custom_field.class.name) + else + render :action => 'new' + end + end + + def edit + end + + def update + if @custom_field.update_attributes(params[:custom_field]) + flash[:notice] = l(:notice_successful_update) + call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field) + redirect_to custom_fields_path(:tab => @custom_field.class.name) + else + render :action => 'edit' + end + end + + def destroy + begin + @custom_field.destroy + rescue + flash[:error] = l(:error_can_not_delete_custom_field) + end + redirect_to custom_fields_path(:tab => @custom_field.class.name) + end + + private + + def build_new_custom_field + @custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field]) + if @custom_field.nil? + render_404 + else + @custom_field.default_value = nil + end + end + + def find_custom_field + @custom_field = CustomField.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/35/351ed79f1a1eb0a05256bd9bbc96463418f0bb9b.svn-base --- a/.svn/pristine/35/351ed79f1a1eb0a05256bd9bbc96463418f0bb9b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -module Redmine - module Scm - class Base - class << self - - def all - @scms - end - - # Add a new SCM adapter and repository - def add(scm_name) - @scms ||= [] - @scms << scm_name - end - - # Remove a SCM adapter from Redmine's list of supported scms - def delete(scm_name) - @scms.delete(scm_name) - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/35/353b4ea784cce222db202d9a47753a1bd3685b81.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/35/353b4ea784cce222db202d9a47753a1bd3685b81.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,70 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module SearchHelper + 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| + if result.length > 1200 + # maximum length of the preview reached + result << '...' + break + end + words = words.mb_chars + if i.even? + result << h(words.length > 100 ? "#{words.slice(0..44)} ... #{words.slice(-45..-1)}" : words) + else + t = (tokens.index(words.downcase) || 0) % 4 + result << content_tag('span', h(words), :class => "highlight token-#{t}") + end + end + result.html_safe + end + + def type_label(t) + l("label_#{t.singularize}_plural", :default => t.to_s.humanize) + end + + def project_select_tag + options = [[l(:label_project_all), 'all']] + options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty? + options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty? + options << [@project.name, ''] unless @project.nil? + label_tag("scope", l(:description_project_scope), :class => "hidden-for-sighted") + + select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1 + end + + def render_results_by_type(results_by_type) + links = [] + # Sorts types by results count + results_by_type.keys.sort {|a, b| results_by_type[b] <=> results_by_type[a]}.each do |t| + c = results_by_type[t] + next if c == 0 + text = "#{type_label(t)} (#{c})" + links << link_to(h(text), :q => params[:q], :titles_only => params[:titles_only], + :all_words => params[:all_words], :scope => params[:scope], t => 1) + end + ('
      '.html_safe + + links.map {|link| content_tag('li', link)}.join(' ').html_safe + + '
    '.html_safe) unless links.empty? + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/35/358efe99a3fb53178835eb3b5414a8ad980d694b.svn-base --- a/.svn/pristine/35/358efe99a3fb53178835eb3b5414a8ad980d694b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -class AddCommitAccessPermission < ActiveRecord::Migration - def self.up - Role.find(:all).select { |r| not r.builtin? }.each do |r| - r.add_permission!(:commit_access) - end - end - - def self.down - Role.find(:all).select { |r| not r.builtin? }.each do |r| - r.remove_permission!(:commit_access) - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/35/35a0a9f6553f729051c01dc102ed98a7a0151c3f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/35/35a0a9f6553f729051c01dc102ed98a7a0151c3f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,243 @@ +# encoding: utf-8 +# +# Helpers to sort tables using clickable column headers. +# +# Author: Stuart Rackham , March 2005. +# Jean-Philippe Lang, 2009 +# License: This source code is released under the MIT license. +# +# - Consecutive clicks toggle the column's sort order. +# - Sort state is maintained by a session hash entry. +# - CSS classes identify sort column and state. +# - Typically used in conjunction with the Pagination module. +# +# Example code snippets: +# +# Controller: +# +# helper :sort +# include SortHelper +# +# def list +# sort_init 'last_name' +# sort_update %w(first_name last_name) +# @items = Contact.find_all nil, sort_clause +# end +# +# Controller (using Pagination module): +# +# helper :sort +# include SortHelper +# +# def list +# sort_init 'last_name' +# sort_update %w(first_name last_name) +# @contact_pages, @items = paginate :contacts, +# :order_by => sort_clause, +# :per_page => 10 +# end +# +# View (table header in list.rhtml): +# +# +# +# <%= sort_header_tag('id', :title => 'Sort by contact ID') %> +# <%= sort_header_tag('last_name', :caption => 'Name') %> +# <%= sort_header_tag('phone') %> +# <%= sort_header_tag('address', :width => 200) %> +# +# +# +# - Introduces instance variables: @sort_default, @sort_criteria +# - Introduces param :sort +# + +module SortHelper + class SortCriteria + + def initialize + @criteria = [] + end + + def available_criteria=(criteria) + unless criteria.is_a?(Hash) + criteria = criteria.inject({}) {|h,k| h[k] = k; h} + end + @available_criteria = criteria + end + + def from_param(param) + @criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]} + normalize! + end + + def criteria=(arg) + @criteria = arg + normalize! + end + + def to_param + @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') + end + + # Returns an array of SQL fragments used to sort the list + def to_sql + sql = @criteria.collect do |k,o| + if s = @available_criteria[k] + (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}) + end + end.flatten.compact + sql.blank? ? nil : sql + end + + def to_a + @criteria.dup + end + + def add!(key, asc) + @criteria.delete_if {|k,o| k == key} + @criteria = [[key, asc]] + @criteria + normalize! + end + + def add(*args) + r = self.class.new.from_param(to_param) + r.add!(*args) + r + end + + def first_key + @criteria.first && @criteria.first.first + end + + def first_asc? + @criteria.first && @criteria.first.last + end + + def empty? + @criteria.empty? + end + + private + + def normalize! + @criteria ||= [] + @criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]} + @criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria + @criteria.slice!(3) + self + end + + # Appends DESC to the sort criterion unless it has a fixed order + def append_desc(criterion) + if criterion =~ / (asc|desc)$/i + criterion + else + "#{criterion} DESC" + end + end + end + + def sort_name + controller_name + '_' + action_name + '_sort' + end + + # Initializes the default sort. + # Examples: + # + # sort_init 'name' + # sort_init 'id', 'desc' + # sort_init ['name', ['id', 'desc']] + # sort_init [['name', 'desc'], ['id', 'desc']] + # + def sort_init(*args) + case args.size + when 1 + @sort_default = args.first.is_a?(Array) ? args.first : [[args.first]] + when 2 + @sort_default = [[args.first, args.last]] + else + raise ArgumentError + end + end + + # Updates the sort state. Call this in the controller prior to calling + # sort_clause. + # - criteria can be either an array or a hash of allowed keys + # + def sort_update(criteria, sort_name=nil) + sort_name ||= self.sort_name + @sort_criteria = SortCriteria.new + @sort_criteria.available_criteria = criteria + @sort_criteria.from_param(params[:sort] || session[sort_name]) + @sort_criteria.criteria = @sort_default if @sort_criteria.empty? + session[sort_name] = @sort_criteria.to_param + end + + # Clears the sort criteria session data + # + def sort_clear + session[sort_name] = nil + end + + # Returns an SQL sort clause corresponding to the current sort state. + # Use this to sort the controller's table items collection. + # + def sort_clause() + @sort_criteria.to_sql + end + + def sort_criteria + @sort_criteria + end + + # Returns a link which sorts by the named column. + # + # - column is the name of an attribute in the sorted record collection. + # - the optional caption explicitly specifies the displayed link text. + # - 2 CSS classes reflect the state of the link: sort and asc or desc + # + def sort_link(column, caption, default_order) + css, order = nil, default_order + + if column.to_s == @sort_criteria.first_key + if @sort_criteria.first_asc? + css = 'sort asc' + order = 'desc' + else + css = 'sort desc' + order = 'asc' + end + end + caption = column.to_s.humanize unless caption + + sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param } + url_options = params.merge(sort_options) + + # Add project_id to url_options + url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id) + + link_to_content_update(h(caption), url_options, :class => css) + end + + # Returns a table header tag with a sort link for the named column + # attribute. + # + # Options: + # :caption The displayed link name (defaults to titleized column name). + # :title The tag's 'title' attribute (defaults to 'Sort by :caption'). + # + # Other options hash entries generate additional table header tag attributes. + # + # Example: + # + # <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %> + # + def sort_header_tag(column, options = {}) + caption = options.delete(:caption) || column.to_s.humanize + default_order = options.delete(:default_order) || 'asc' + options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title] + content_tag('th', sort_link(column, caption, default_order), options) + end +end + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/35/35e3d4caedb7dd68a537eff356ac08671dc6c73f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/35/35e3d4caedb7dd68a537eff356ac08671dc6c73f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,108 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class RolesController < ApplicationController + layout 'admin' + + before_filter :require_admin, :except => [:index, :show] + before_filter :require_admin_or_api_request, :only => [:index, :show] + before_filter :find_role, :only => [:show, :edit, :update, :destroy] + accept_api_auth :index, :show + + def index + respond_to do |format| + format.html { + @role_pages, @roles = paginate Role.sorted, :per_page => 25 + render :action => "index", :layout => false if request.xhr? + } + format.api { + @roles = Role.givable.all + } + end + end + + def show + respond_to do |format| + format.api + end + end + + def new + # Prefills the form with 'Non member' role permissions by default + @role = Role.new(params[:role] || {:permissions => Role.non_member.permissions}) + if params[:copy].present? && @copy_from = Role.find_by_id(params[:copy]) + @role.copy_from(@copy_from) + end + @roles = Role.sorted.all + end + + def create + @role = Role.new(params[:role]) + if request.post? && @role.save + # workflow copy + if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from])) + @role.workflow_rules.copy(copy_from) + end + flash[:notice] = l(:notice_successful_create) + redirect_to roles_path + else + @roles = Role.sorted.all + render :action => 'new' + end + end + + def edit + end + + def update + if request.put? and @role.update_attributes(params[:role]) + flash[:notice] = l(:notice_successful_update) + redirect_to roles_path + else + render :action => 'edit' + end + end + + def destroy + @role.destroy + redirect_to roles_path + rescue + flash[:error] = l(:error_can_not_remove_role) + redirect_to roles_path + end + + def permissions + @roles = Role.sorted.all + @permissions = Redmine::AccessControl.permissions.select { |p| !p.public? } + if request.post? + @roles.each do |role| + role.permissions = params[:permissions][role.id.to_s] + role.save + end + flash[:notice] = l(:notice_successful_update) + redirect_to roles_path + end + end + + private + + def find_role + @role = Role.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/35/35e7528b96f6bca4ea96cc9fa52b687ed6909cb9.svn-base --- a/.svn/pristine/35/35e7528b96f6bca4ea96cc9fa52b687ed6909cb9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,219 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class RolesControllerTest < ActionController::TestCase - fixtures :roles, :users, :members, :member_roles, :workflows, :trackers - - def setup - @controller = RolesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_response :success - assert_template 'index' - - assert_not_nil assigns(:roles) - assert_equal Role.find(:all, :order => 'builtin, position'), assigns(:roles) - - assert_tag :tag => 'a', :attributes => { :href => '/roles/1/edit' }, - :content => 'Manager' - end - - def test_new - get :new - assert_response :success - assert_template 'new' - end - - def test_new_with_copy - copy_from = Role.find(2) - - get :new, :copy => copy_from.id.to_s - assert_response :success - assert_template 'new' - - role = assigns(:role) - assert_equal copy_from.permissions, role.permissions - - assert_select 'form' do - # blank name - assert_select 'input[name=?][value=]', 'role[name]' - # edit_project permission checked - assert_select 'input[type=checkbox][name=?][value=edit_project][checked=checked]', 'role[permissions][]' - # add_project permission not checked - assert_select 'input[type=checkbox][name=?][value=add_project]', 'role[permissions][]' - assert_select 'input[type=checkbox][name=?][value=add_project][checked=checked]', 'role[permissions][]', 0 - # workflow copy selected - assert_select 'select[name=?]', 'copy_workflow_from' do - assert_select 'option[value=2][selected=selected]' - end - end - end - - def test_create_with_validaton_failure - post :create, :role => {:name => '', - :permissions => ['add_issues', 'edit_issues', 'log_time', ''], - :assignable => '0'} - - assert_response :success - assert_template 'new' - assert_tag :tag => 'div', :attributes => { :id => 'errorExplanation' } - end - - def test_create_without_workflow_copy - post :create, :role => {:name => 'RoleWithoutWorkflowCopy', - :permissions => ['add_issues', 'edit_issues', 'log_time', ''], - :assignable => '0'} - - assert_redirected_to '/roles' - role = Role.find_by_name('RoleWithoutWorkflowCopy') - assert_not_nil role - assert_equal [:add_issues, :edit_issues, :log_time], role.permissions - assert !role.assignable? - end - - def test_create_with_workflow_copy - post :create, :role => {:name => 'RoleWithWorkflowCopy', - :permissions => ['add_issues', 'edit_issues', 'log_time', ''], - :assignable => '0'}, - :copy_workflow_from => '1' - - assert_redirected_to '/roles' - role = Role.find_by_name('RoleWithWorkflowCopy') - assert_not_nil role - assert_equal Role.find(1).workflow_rules.size, role.workflow_rules.size - end - - def test_edit - get :edit, :id => 1 - assert_response :success - assert_template 'edit' - assert_equal Role.find(1), assigns(:role) - assert_select 'select[name=?]', 'role[issues_visibility]' - end - - def test_edit_anonymous - get :edit, :id => Role.anonymous.id - assert_response :success - assert_template 'edit' - assert_select 'select[name=?]', 'role[issues_visibility]', 0 - end - - def test_edit_invalid_should_respond_with_404 - get :edit, :id => 999 - assert_response 404 - end - - def test_update - put :update, :id => 1, - :role => {:name => 'Manager', - :permissions => ['edit_project', ''], - :assignable => '0'} - - assert_redirected_to '/roles' - role = Role.find(1) - assert_equal [:edit_project], role.permissions - end - - def test_update_with_failure - put :update, :id => 1, :role => {:name => ''} - assert_response :success - assert_template 'edit' - end - - def test_destroy - r = Role.create!(:name => 'ToBeDestroyed', :permissions => [:view_wiki_pages]) - - delete :destroy, :id => r - assert_redirected_to '/roles' - assert_nil Role.find_by_id(r.id) - end - - def test_destroy_role_in_use - delete :destroy, :id => 1 - assert_redirected_to '/roles' - assert_equal 'This role is in use and cannot be deleted.', flash[:error] - assert_not_nil Role.find_by_id(1) - end - - def test_get_permissions - get :permissions - assert_response :success - assert_template 'permissions' - - assert_not_nil assigns(:roles) - assert_equal Role.find(:all, :order => 'builtin, position'), assigns(:roles) - - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'permissions[3][]', - :value => 'add_issues', - :checked => 'checked' } - - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'permissions[3][]', - :value => 'delete_issues', - :checked => nil } - end - - def test_post_permissions - post :permissions, :permissions => { '0' => '', '1' => ['edit_issues'], '3' => ['add_issues', 'delete_issues']} - assert_redirected_to '/roles' - - assert_equal [:edit_issues], Role.find(1).permissions - assert_equal [:add_issues, :delete_issues], Role.find(3).permissions - assert Role.find(2).permissions.empty? - end - - def test_clear_all_permissions - post :permissions, :permissions => { '0' => '' } - assert_redirected_to '/roles' - assert Role.find(1).permissions.empty? - end - - def test_move_highest - put :update, :id => 3, :role => {:move_to => 'highest'} - assert_redirected_to '/roles' - assert_equal 1, Role.find(3).position - end - - def test_move_higher - position = Role.find(3).position - put :update, :id => 3, :role => {:move_to => 'higher'} - assert_redirected_to '/roles' - assert_equal position - 1, Role.find(3).position - end - - def test_move_lower - position = Role.find(2).position - put :update, :id => 2, :role => {:move_to => 'lower'} - assert_redirected_to '/roles' - assert_equal position + 1, Role.find(2).position - end - - def test_move_lowest - put :update, :id => 2, :role => {:move_to => 'lowest'} - assert_redirected_to '/roles' - assert_equal Role.count, Role.find(2).position - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/35/35f480895683cc5d4622ecd0b8aa6794e388969c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/35/35f480895683cc5d4622ecd0b8aa6794e388969c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,86 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class ContextMenusController < ApplicationController + helper :watchers + helper :issues + + before_filter :find_issues, :only => :issues + + def issues + if (@issues.size == 1) + @issue = @issues.first + end + @issue_ids = @issues.map(&:id).sort + + @allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) + + @can = {:edit => User.current.allowed_to?(:edit_issues, @projects), + :log_time => (@project && User.current.allowed_to?(:log_time, @project)), + :update => (User.current.allowed_to?(:edit_issues, @projects) || (User.current.allowed_to?(:change_status, @projects) && !@allowed_statuses.blank?)), + :move => (@project && User.current.allowed_to?(:move_issues, @project)), + :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), + :delete => User.current.allowed_to?(:delete_issues, @projects) + } + if @project + if @issue + @assignables = @issue.assignable_users + else + @assignables = @project.assignable_users + end + @trackers = @project.trackers + else + #when multiple projects, we only keep the intersection of each set + @assignables = @projects.map(&:assignable_users).reduce(:&) + @trackers = @projects.map(&:trackers).reduce(:&) + end + @versions = @projects.map {|p| p.shared_versions.open}.reduce(:&) + + @priorities = IssuePriority.active.reverse + @back = back_url + + @options_by_custom_field = {} + if @can[:edit] + custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f| + %w(bool list user version).include?(f.field_format) && !f.multiple? + end + custom_fields.each do |field| + values = field.possible_values_options(@projects) + if values.any? + @options_by_custom_field[field] = values + end + end + end + + @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&) + render :layout => false + end + + def time_entries + @time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a + (render_404; return) unless @time_entries.present? + + @projects = @time_entries.collect(&:project).compact.uniq + @project = @projects.first if @projects.size == 1 + @activities = TimeEntryActivity.shared.active + @can = {:edit => User.current.allowed_to?(:edit_time_entries, @projects), + :delete => User.current.allowed_to?(:edit_time_entries, @projects) + } + @back = back_url + render :layout => false + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/36/367408f1f75021a71b939b14007872b6740672f3.svn-base --- a/.svn/pristine/36/367408f1f75021a71b939b14007872b6740672f3.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::VersionsTest < ActionController::IntegrationTest - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :versions - - def setup - Setting.rest_api_enabled = '1' - end - - context "/projects/:project_id/versions" do - context "GET" do - should "return project versions" do - get '/projects/1/versions.xml' - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'versions', - :attributes => {:type => 'array'}, - :child => { - :tag => 'version', - :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'name', - :content => '1.0' - } - } - } - end - end - - context "POST" do - should "create the version" do - assert_difference 'Version.count' do - post '/projects/1/versions.xml', {:version => {:name => 'API test'}}, credentials('jsmith') - end - - version = Version.first(:order => 'id DESC') - assert_equal 'API test', version.name - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s} - end - - should "create the version with due date" do - assert_difference 'Version.count' do - post '/projects/1/versions.xml', {:version => {:name => 'API test', :due_date => '2012-01-24'}}, credentials('jsmith') - end - - version = Version.first(:order => 'id DESC') - assert_equal 'API test', version.name - assert_equal Date.parse('2012-01-24'), version.due_date - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s} - end - - context "with failure" do - should "return the errors" do - assert_no_difference('Version.count') do - post '/projects/1/versions.xml', {:version => {:name => ''}}, credentials('jsmith') - end - - assert_response :unprocessable_entity - assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"} - end - end - end - end - - context "/versions/:id" do - context "GET" do - should "return the version" do - get '/versions/2.xml' - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_select 'version' do - assert_select 'id', :text => '2' - assert_select 'name', :text => '1.0' - assert_select 'sharing', :text => 'none' - end - end - end - - context "PUT" do - should "update the version" do - put '/versions/2.xml', {:version => {:name => 'API update'}}, credentials('jsmith') - - assert_response :ok - assert_equal '', @response.body - assert_equal 'API update', Version.find(2).name - end - end - - context "DELETE" do - should "destroy the version" do - assert_difference 'Version.count', -1 do - delete '/versions/3.xml', {}, credentials('jsmith') - end - - assert_response :ok - assert_equal '', @response.body - assert_nil Version.find_by_id(3) - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/36/36f96aa6819066dbc18f6f0375ee24e3c2d52b96.svn-base --- a/.svn/pristine/36/36f96aa6819066dbc18f6f0375ee24e3c2d52b96.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,296 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class AccountController < ApplicationController - helper :custom_fields - include CustomFieldsHelper - - # prevents login action to be filtered by check_if_login_required application scope filter - skip_before_filter :check_if_login_required - - # Login request and validation - def login - if request.get? - logout_user - else - authenticate_user - end - rescue AuthSourceException => e - logger.error "An error occured when authenticating #{params[:username]}: #{e.message}" - render_error :message => e.message - end - - # Log out current user and redirect to welcome page - def logout - logout_user - redirect_to home_url - end - - # Lets user choose a new password - def lost_password - redirect_to(home_url) && return unless Setting.lost_password? - if params[:token] - @token = Token.find_by_action_and_value("recovery", params[:token].to_s) - if @token.nil? || @token.expired? - redirect_to home_url - return - end - @user = @token.user - unless @user && @user.active? - redirect_to home_url - return - end - if request.post? - @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] - if @user.save - @token.destroy - flash[:notice] = l(:notice_account_password_updated) - redirect_to signin_path - return - end - end - render :template => "account/password_recovery" - return - else - if request.post? - user = User.find_by_mail(params[:mail].to_s) - # user not found or not active - unless user && user.active? - flash.now[:error] = l(:notice_account_unknown_email) - return - end - # user cannot change its password - unless user.change_password_allowed? - flash.now[:error] = l(:notice_can_t_change_password) - return - end - # create a new token for password recovery - token = Token.new(:user => user, :action => "recovery") - if token.save - Mailer.lost_password(token).deliver - flash[:notice] = l(:notice_account_lost_email_sent) - redirect_to signin_path - return - end - end - end - end - - # User self-registration - def register - redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration] - if request.get? - session[:auth_source_registration] = nil - @user = User.new(:language => Setting.default_language) - else - user_params = params[:user] || {} - @user = User.new - @user.safe_attributes = user_params - @user.admin = false - @user.register - if session[:auth_source_registration] - @user.activate - @user.login = session[:auth_source_registration][:login] - @user.auth_source_id = session[:auth_source_registration][:auth_source_id] - if @user.save - session[:auth_source_registration] = nil - self.logged_user = @user - flash[:notice] = l(:notice_account_activated) - redirect_to :controller => 'my', :action => 'account' - end - else - @user.login = params[:user][:login] - unless user_params[:identity_url].present? && user_params[:password].blank? && user_params[:password_confirmation].blank? - @user.password, @user.password_confirmation = user_params[:password], user_params[:password_confirmation] - end - - case Setting.self_registration - when '1' - register_by_email_activation(@user) - when '3' - register_automatically(@user) - else - register_manually_by_administrator(@user) - end - end - end - end - - # Token based account activation - def activate - redirect_to(home_url) && return unless Setting.self_registration? && params[:token] - token = Token.find_by_action_and_value('register', params[:token]) - redirect_to(home_url) && return unless token and !token.expired? - user = token.user - redirect_to(home_url) && return unless user.registered? - user.activate - if user.save - token.destroy - flash[:notice] = l(:notice_account_activated) - end - redirect_to signin_path - end - - private - - def authenticate_user - if Setting.openid? && using_open_id? - open_id_authenticate(params[:openid_url]) - else - password_authentication - end - end - - def password_authentication - user = User.try_to_login(params[:username], params[:password]) - - if user.nil? - invalid_credentials - elsif user.new_record? - onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id }) - else - # Valid user - successful_authentication(user) - end - end - - def open_id_authenticate(openid_url) - authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url, :method => :post) do |result, identity_url, registration| - if result.successful? - user = User.find_or_initialize_by_identity_url(identity_url) - if user.new_record? - # Self-registration off - redirect_to(home_url) && return unless Setting.self_registration? - - # Create on the fly - user.login = registration['nickname'] unless registration['nickname'].nil? - user.mail = registration['email'] unless registration['email'].nil? - user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil? - user.random_password - user.register - - case Setting.self_registration - when '1' - register_by_email_activation(user) do - onthefly_creation_failed(user) - end - when '3' - register_automatically(user) do - onthefly_creation_failed(user) - end - else - register_manually_by_administrator(user) do - onthefly_creation_failed(user) - end - end - else - # Existing record - if user.active? - successful_authentication(user) - else - account_pending - end - end - end - end - end - - def successful_authentication(user) - logger.info "Successful authentication for '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}" - # Valid user - self.logged_user = user - # generate a key and set cookie if autologin - if params[:autologin] && Setting.autologin? - set_autologin_cookie(user) - end - call_hook(:controller_account_success_authentication_after, {:user => user }) - redirect_back_or_default :controller => 'my', :action => 'page' - end - - def set_autologin_cookie(user) - token = Token.create(:user => user, :action => 'autologin') - cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin' - cookie_options = { - :value => token.value, - :expires => 1.year.from_now, - :path => (Redmine::Configuration['autologin_cookie_path'] || '/'), - :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), - :httponly => true - } - cookies[cookie_name] = cookie_options - end - - # Onthefly creation failed, display the registration form to fill/fix attributes - def onthefly_creation_failed(user, auth_source_options = { }) - @user = user - session[:auth_source_registration] = auth_source_options unless auth_source_options.empty? - render :action => 'register' - end - - def invalid_credentials - logger.warn "Failed login for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}" - flash.now[:error] = l(:notice_account_invalid_creditentials) - end - - # Register a user for email activation. - # - # Pass a block for behavior when a user fails to save - def register_by_email_activation(user, &block) - token = Token.new(:user => user, :action => "register") - if user.save and token.save - Mailer.register(token).deliver - flash[:notice] = l(:notice_account_register_done) - redirect_to signin_path - else - yield if block_given? - end - end - - # Automatically register a user - # - # Pass a block for behavior when a user fails to save - def register_automatically(user, &block) - # Automatic activation - user.activate - user.last_login_on = Time.now - if user.save - self.logged_user = user - flash[:notice] = l(:notice_account_activated) - redirect_to :controller => 'my', :action => 'account' - else - yield if block_given? - end - end - - # Manual activation by the administrator - # - # Pass a block for behavior when a user fails to save - def register_manually_by_administrator(user, &block) - if user.save - # Sends an email to the administrators - Mailer.account_activation_request(user).deliver - account_pending - else - yield if block_given? - end - end - - def account_pending - flash[:notice] = l(:notice_account_pending) - redirect_to signin_path - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/37/371760b9ad0d0b40ef98e5a2a6cefda71c92b15e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/37/371760b9ad0d0b40ef98e5a2a6cefda71c92b15e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,4 @@ +
    +<%= link_to l(:button_copy), {:action => 'copy'}, :class => 'icon icon-copy' %> +<%= link_to l(:field_summary), {:action => 'index'}, :class => 'icon icon-summary' %> +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/37/3751ea61d6afde6d3676ff52f3635b315c4f2a51.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/37/3751ea61d6afde6d3676ff52f3635b315c4f2a51.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,41 @@ +<%= form_tag({}) do -%> +<%= hidden_field_tag 'back_url', url_for(params) %> +
    + + + + + <% @query.inline_columns.each do |column| %> + <%= column_header(column) %> + <% end %> + + + + +<% entries.each do |entry| -%> + hascontextmenu"> + + <%= raw @query.inline_columns.map {|column| ""}.join %> + + +<% end -%> + +
    + <%= link_to image_tag('toggle_check.png'), + {}, + :onclick => 'toggleIssuesSelection(this); return false;', + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> +
    <%= check_box_tag("ids[]", entry.id, false, :id => nil) %>#{column_content(column, entry)} + <% if entry.editable_by?(User.current) -%> + <%= link_to image_tag('edit.png'), edit_time_entry_path(entry), + :title => l(:button_edit) %> + <%= link_to image_tag('delete.png'), time_entry_path(entry), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:button_delete) %> + <% end -%> +
    +
    +<% end -%> + +<%= context_menu time_entries_context_menu_path %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/37/3777bfd256635d6f1a4b09b877d82ce7f1424b2a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/37/3777bfd256635d6f1a4b09b877d82ce7f1424b2a.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,58 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingDocumentsTest < ActionController::IntegrationTest + def test_documents_scoped_under_project + assert_routing( + { :method => 'get', :path => "/projects/567/documents" }, + { :controller => 'documents', :action => 'index', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/documents/new" }, + { :controller => 'documents', :action => 'new', :project_id => '567' } + ) + assert_routing( + { :method => 'post', :path => "/projects/567/documents" }, + { :controller => 'documents', :action => 'create', :project_id => '567' } + ) + end + + def test_documents + assert_routing( + { :method => 'get', :path => "/documents/22" }, + { :controller => 'documents', :action => 'show', :id => '22' } + ) + assert_routing( + { :method => 'get', :path => "/documents/22/edit" }, + { :controller => 'documents', :action => 'edit', :id => '22' } + ) + assert_routing( + { :method => 'put', :path => "/documents/22" }, + { :controller => 'documents', :action => 'update', :id => '22' } + ) + assert_routing( + { :method => 'delete', :path => "/documents/22" }, + { :controller => 'documents', :action => 'destroy', :id => '22' } + ) + assert_routing( + { :method => 'post', :path => "/documents/22/add_attachment" }, + { :controller => 'documents', :action => 'add_attachment', :id => '22' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/38/385c6750511d0a36b5d915c83580ce155323ad24.svn-base --- a/.svn/pristine/38/385c6750511d0a36b5d915c83580ce155323ad24.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1085 +0,0 @@ -# Serbian translations for Redmine -# by Vladimir Medarović (vlada@medarovic.com) -sr-YU: - direction: ltr - jquery: - locale: "sr" - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%d.%m.%Y." - short: "%e %b" - long: "%B %e, %Y" - - day_names: [nedelja, ponedeljak, utorak, sreda, Äetvrtak, petak, subota] - abbr_day_names: [ned, pon, uto, sre, Äet, pet, sub] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, januar, februar, mart, april, maj, jun, jul, avgust, septembar, oktobar, novembar, decembar] - abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, avg, sep, okt, nov, dec] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%d.%m.%Y. u %H:%M" - time: "%H:%M" - short: "%d. %b u %H:%M" - long: "%d. %B %Y u %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "pola minuta" - less_than_x_seconds: - one: "manje od jedne sekunde" - other: "manje od %{count} sek." - x_seconds: - one: "jedna sekunda" - other: "%{count} sek." - less_than_x_minutes: - one: "manje od minuta" - other: "manje od %{count} min." - x_minutes: - one: "jedan minut" - other: "%{count} min." - about_x_hours: - one: "približno jedan sat" - other: "približno %{count} sati" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "jedan dan" - other: "%{count} dana" - about_x_months: - one: "približno jedan mesec" - other: "približno %{count} meseci" - x_months: - one: "jedan mesec" - other: "%{count} meseci" - about_x_years: - one: "približno godinu dana" - other: "približno %{count} god." - over_x_years: - one: "preko godinu dana" - other: "preko %{count} god." - almost_x_years: - one: "skoro godinu dana" - other: "skoro %{count} god." - - number: - format: - separator: "," - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - -# Used in array.to_sentence. - support: - array: - sentence_connector: "i" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "nije ukljuÄen u spisak" - exclusion: "je rezervisan" - invalid: "je neispravan" - confirmation: "potvrda ne odgovara" - accepted: "mora biti prihvaćen" - empty: "ne može biti prazno" - blank: "ne može biti prazno" - too_long: "je predugaÄka (maksimum znakova je %{count})" - too_short: "je prekratka (minimum znakova je %{count})" - wrong_length: "je pogreÅ¡ne dužine (broj znakova mora biti %{count})" - taken: "je već u upotrebi" - not_a_number: "nije broj" - not_a_date: "nije ispravan datum" - greater_than: "mora biti veći od %{count}" - greater_than_or_equal_to: "mora biti veći ili jednak %{count}" - equal_to: "mora biti jednak %{count}" - less_than: "mora biti manji od %{count}" - less_than_or_equal_to: "mora biti manji ili jednak %{count}" - odd: "mora biti paran" - even: "mora biti neparan" - greater_than_start_date: "mora biti veći od poÄetnog datuma" - not_same_project: "ne pripada istom projektu" - circular_dependency: "Ova veza će stvoriti kružnu referencu" - cant_link_an_issue_with_a_descendant: "Problem ne može biti povezan sa jednim od svojih podzadataka" - - actionview_instancetag_blank_option: Molim odaberite - - general_text_No: 'Ne' - general_text_Yes: 'Da' - general_text_no: 'ne' - general_text_yes: 'da' - general_lang_name: 'Serbian (Srpski)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Nalog je uspeÅ¡no ažuriran. - notice_account_invalid_creditentials: Neispravno korisniÄko ime ili lozinka. - notice_account_password_updated: Lozinka je uspeÅ¡no ažurirana. - notice_account_wrong_password: PogreÅ¡na lozinka - notice_account_register_done: KorisniÄki nalog je uspeÅ¡no kreiran. Kliknite na link koji ste dobili u e-poruci za aktivaciju. - notice_account_unknown_email: Nepoznat korisnik. - notice_can_t_change_password: Ovaj korisniÄki nalog za potvrdu identiteta koristi spoljni izvor. Nemoguće je promeniti lozinku. - notice_account_lost_email_sent: Poslata vam je e-poruka sa uputstvom za izbor nove lozinke - notice_account_activated: VaÅ¡ korisniÄki nalog je aktiviran. Sada se možete prijaviti. - notice_successful_create: UspeÅ¡no kreiranje. - notice_successful_update: UspeÅ¡no ažuriranje. - notice_successful_delete: UspeÅ¡no brisanje. - notice_successful_connection: UspeÅ¡no povezivanje. - notice_file_not_found: Strana kojoj želite pristupiti ne postoji ili je uklonjena. - notice_locking_conflict: Podatak je ažuriran od strane drugog korisnika. - notice_not_authorized: Niste ovlašćeni za pristup ovoj strani. - notice_email_sent: "E-poruka je poslata na %{value}" - notice_email_error: "Dogodila se greÅ¡ka prilikom slanja e-poruke (%{value})" - notice_feeds_access_key_reseted: VaÅ¡ RSS pristupni kljuÄ je poniÅ¡ten. - notice_api_access_key_reseted: VaÅ¡ API pristupni kljuÄ je poniÅ¡ten. - notice_failed_to_save_issues: "NeuspeÅ¡no snimanje %{count} problema od %{total} odabranih: %{ids}." - notice_failed_to_save_members: "NeuspeÅ¡no snimanje Älana(ova): %{errors}." - notice_no_issue_selected: "Ni jedan problem nije odabran! Molimo, odaberite problem koji želite da menjate." - notice_account_pending: "VaÅ¡ nalog je kreiran i Äeka na odobrenje administratora." - notice_default_data_loaded: Podrazumevano konfigurisanje je uspeÅ¡no uÄitano. - notice_unable_delete_version: Verziju je nemoguće izbrisati. - notice_unable_delete_time_entry: Stavku evidencije vremena je nemoguće izbrisati. - notice_issue_done_ratios_updated: Odnos reÅ¡enih problema je ažuriran. - - error_can_t_load_default_data: "Podrazumevano konfigurisanje je nemoguće uÄitati: %{value}" - error_scm_not_found: "Stavka ili ispravka nisu pronaÄ‘ene u spremiÅ¡tu." - error_scm_command_failed: "GreÅ¡ka se javila prilikom pokuÅ¡aja pristupa spremiÅ¡tu: %{value}" - error_scm_annotate: "Stavka ne postoji ili ne može biti oznaÄena." - error_issue_not_found_in_project: 'Problem nije pronaÄ‘en ili ne pripada ovom projektu.' - error_no_tracker_in_project: 'Ni jedno praćenje nije povezano sa ovim projektom. Molimo proverite podeÅ¡avanja projekta.' - error_no_default_issue_status: 'Podrazumevani status problema nije definisan. Molimo proverite vaÅ¡e konfigurisanje (idite na "Administracija -> Statusi problema").' - error_can_not_delete_custom_field: Nemoguće je izbrisati prilagoÄ‘eno polje - error_can_not_delete_tracker: "Ovo praćenje sadrži probleme i ne može biti obrisano." - error_can_not_remove_role: "Ova uloga je u upotrebi i ne može biti obrisana." - error_can_not_reopen_issue_on_closed_version: 'Problem dodeljen zatvorenoj verziji ne može biti ponovo otvoren' - error_can_not_archive_project: Ovaj projekat se ne može arhivirati - error_issue_done_ratios_not_updated: "Odnos reÅ¡enih problema nije ažuriran." - error_workflow_copy_source: 'Molimo odaberite izvorno praćenje ili ulogu' - error_workflow_copy_target: 'Molimo odaberite odrediÅ¡no praćenje i ulogu' - error_unable_delete_issue_status: 'Status problema je nemoguće obrisati' - error_unable_to_connect: "Povezivanje sa (%{value}) je nemoguće" - warning_attachments_not_saved: "%{count} datoteka ne može biti snimljena." - - mail_subject_lost_password: "VaÅ¡a %{value} lozinka" - mail_body_lost_password: 'Za promenu vaÅ¡e lozinke, kliknite na sledeći link:' - mail_subject_register: "Aktivacija vaÅ¡eg %{value} naloga" - mail_body_register: 'Za aktivaciju vaÅ¡eg naloga, kliknite na sledeći link:' - mail_body_account_information_external: "VaÅ¡ nalog %{value} možete koristiti za prijavu." - mail_body_account_information: Informacije o vaÅ¡em nalogu - mail_subject_account_activation_request: "Zahtev za aktivaciju naloga %{value}" - mail_body_account_activation_request: "Novi korisnik (%{value}) je registrovan. Nalog Äeka na vaÅ¡e odobrenje:" - mail_subject_reminder: "%{count} problema dospeva narednih %{days} dana" - mail_body_reminder: "%{count} problema dodeljenih vama dospeva u narednih %{days} dana:" - mail_subject_wiki_content_added: "Wiki stranica '%{id}' je dodata" - mail_body_wiki_content_added: "%{author} je dodao wiki stranicu '%{id}'." - mail_subject_wiki_content_updated: "Wiki stranica '%{id}' je ažurirana" - mail_body_wiki_content_updated: "%{author} je ažurirao wiki stranicu '%{id}'." - - gui_validation_error: jedna greÅ¡ka - gui_validation_error_plural: "%{count} greÅ¡aka" - - field_name: Naziv - field_description: Opis - field_summary: Rezime - field_is_required: Obavezno - field_firstname: Ime - field_lastname: Prezime - field_mail: E-adresa - field_filename: Datoteka - field_filesize: VeliÄina - field_downloads: Preuzimanja - field_author: Autor - field_created_on: Kreirano - field_updated_on: Ažurirano - field_field_format: Format - field_is_for_all: Za sve projekte - field_possible_values: Moguće vrednosti - field_regexp: Regularan izraz - field_min_length: Minimalna dužina - field_max_length: Maksimalna dužina - field_value: Vrednost - field_category: Kategorija - field_title: Naslov - field_project: Projekat - field_issue: Problem - field_status: Status - field_notes: BeleÅ¡ke - field_is_closed: Zatvoren problem - field_is_default: Podrazumevana vrednost - field_tracker: Praćenje - field_subject: Predmet - field_due_date: Krajnji rok - field_assigned_to: Dodeljeno - field_priority: Prioritet - field_fixed_version: OdrediÅ¡na verzija - field_user: Korisnik - field_principal: Glavni - field_role: Uloga - field_homepage: PoÄetna stranica - field_is_public: Javno objavljivanje - field_parent: Potprojekat od - field_is_in_roadmap: Problemi prikazani u planu rada - field_login: KorisniÄko ime - field_mail_notification: ObaveÅ¡tenja putem e-poÅ¡te - field_admin: Administrator - field_last_login_on: Poslednje povezivanje - field_language: Jezik - field_effective_date: Datum - field_password: Lozinka - field_new_password: Nova lozinka - field_password_confirmation: Potvrda lozinke - field_version: Verzija - field_type: Tip - field_host: Glavni raÄunar - field_port: Port - field_account: KorisniÄki nalog - field_base_dn: Bazni DN - field_attr_login: Atribut prijavljivanja - field_attr_firstname: Atribut imena - field_attr_lastname: Atribut prezimena - field_attr_mail: Atribut e-adrese - field_onthefly: Kreiranje korisnika u toku rada - field_start_date: PoÄetak - field_done_ratio: "% uraÄ‘eno" - field_auth_source: Režim potvrde identiteta - field_hide_mail: Sakrij moju e-adresu - field_comments: Komentar - field_url: URL - field_start_page: PoÄetna stranica - field_subproject: Potprojekat - field_hours: sati - field_activity: Aktivnost - field_spent_on: Datum - field_identifier: Identifikator - field_is_filter: Upotrebi kao filter - field_issue_to: Srodni problemi - field_delay: KaÅ¡njenje - field_assignable: Problem može biti dodeljen ovoj ulozi - field_redirect_existing_links: Preusmeri postojeće veze - field_estimated_hours: Proteklo vreme - field_column_names: Kolone - field_time_zone: Vremenska zona - field_searchable: Može da se pretražuje - field_default_value: Podrazumevana vrednost - field_comments_sorting: Prikaži komentare - field_parent_title: MatiÄna stranica - field_editable: Izmenljivo - field_watcher: PosmatraÄ - field_identity_url: OpenID URL - field_content: Sadržaj - field_group_by: Grupisanje rezultata po - field_sharing: Deljenje - field_parent_issue: MatiÄni zadatak - - setting_app_title: Naslov aplikacije - setting_app_subtitle: Podnaslov aplikacije - setting_welcome_text: Tekst dobrodoÅ¡lice - setting_default_language: Podrazumevani jezik - setting_login_required: Obavezna potvrda identiteta - setting_self_registration: Samoregistracija - setting_attachment_max_size: Maks. veliÄina priložene datoteke - setting_issues_export_limit: OgraniÄenje izvoza „problema“ - setting_mail_from: E-adresa poÅ¡iljaoca - setting_bcc_recipients: Primaoci „Bcc“ kopije - setting_plain_text_mail: Poruka sa Äistim tekstom (bez HTML-a) - setting_host_name: Putanja i naziv glavnog raÄunara - setting_text_formatting: Oblikovanje teksta - setting_wiki_compression: Kompresija Wiki istorije - setting_feeds_limit: OgraniÄenje sadržaja izvora vesti - setting_default_projects_public: Podrazumeva se javno prikazivanje novih projekata - setting_autofetch_changesets: IzvrÅ¡avanje automatskog preuzimanja - setting_sys_api_enabled: Omogućavanje WS za upravljanje spremiÅ¡tem - setting_commit_ref_keywords: Referenciranje kljuÄnih reÄi - setting_commit_fix_keywords: Popravljanje kljuÄnih reÄi - setting_autologin: Automatska prijava - setting_date_format: Format datuma - setting_time_format: Format vremena - setting_cross_project_issue_relations: Dozvoli povezivanje problema iz unakrsnih projekata - setting_issue_list_default_columns: Podrazumevane kolone prikazane na spisku problema - setting_emails_footer: Podnožje stranice e-poruke - setting_protocol: Protokol - setting_per_page_options: Opcije prikaza objekata po stranici - setting_user_format: Format prikaza korisnika - setting_activity_days_default: Broj dana prikazanih na projektnoj aktivnosti - setting_display_subprojects_issues: Prikazuj probleme iz potprojekata na glavnom projektu, ukoliko nije drugaÄije navedeno - setting_enabled_scm: Omogućavanje SCM - setting_mail_handler_body_delimiters: "Skraćivanje e-poruke nakon jedne od ovih linija" - setting_mail_handler_api_enabled: Omogućavanje WS dolazne e-poruke - setting_mail_handler_api_key: API kljuÄ - setting_sequential_project_identifiers: Generisanje sekvencijalnog imena projekta - setting_gravatar_enabled: Koristi Gravatar korisniÄke ikone - setting_gravatar_default: Podrazumevana Gravatar slika - setting_diff_max_lines_displayed: Maks. broj prikazanih razliÄitih linija - setting_file_max_size_displayed: Maks. veliÄina tekst. datoteka prikazanih umetnuto - setting_repository_log_display_limit: Maks. broj revizija prikazanih u datoteci za evidenciju - setting_openid: Dozvoli OpenID prijavu i registraciju - setting_password_min_length: Minimalna dužina lozinke - setting_new_project_user_role_id: Kreatoru projekta (koji nije administrator) dodeljuje je uloga - setting_default_projects_modules: Podrazumevano omogućeni moduli za nove projekte - setting_issue_done_ratio: IzraÄunaj odnos reÅ¡enih problema - setting_issue_done_ratio_issue_field: koristeći polje problema - setting_issue_done_ratio_issue_status: koristeći status problema - setting_start_of_week: Prvi dan u sedmici - setting_rest_api_enabled: Omogući REST web usluge - setting_cache_formatted_text: KeÅ¡iranje obraÄ‘enog teksta - - permission_add_project: Kreiranje projekta - permission_add_subprojects: Kreiranje potpojekta - permission_edit_project: Izmena projekata - permission_select_project_modules: Odabiranje modula projekta - permission_manage_members: Upravljanje Älanovima - permission_manage_project_activities: Upravljanje projektnim aktivnostima - permission_manage_versions: Upravljanje verzijama - permission_manage_categories: Upravljanje kategorijama problema - permission_view_issues: Pregled problema - permission_add_issues: Dodavanje problema - permission_edit_issues: Izmena problema - permission_manage_issue_relations: Upravljanje vezama izmeÄ‘u problema - permission_add_issue_notes: Dodavanje beleÅ¡ki - permission_edit_issue_notes: Izmena beleÅ¡ki - permission_edit_own_issue_notes: Izmena sopstvenih beleÅ¡ki - permission_move_issues: Pomeranje problema - permission_delete_issues: Brisanje problema - permission_manage_public_queries: Upravljanje javnim upitima - permission_save_queries: Snimanje upita - permission_view_gantt: Pregledanje Gantovog dijagrama - permission_view_calendar: Pregledanje kalendara - permission_view_issue_watchers: Pregledanje spiska posmatraÄa - permission_add_issue_watchers: Dodavanje posmatraÄa - permission_delete_issue_watchers: Brisanje posmatraÄa - permission_log_time: Beleženje utroÅ¡enog vremena - permission_view_time_entries: Pregledanje utroÅ¡enog vremena - permission_edit_time_entries: Izmena utroÅ¡enog vremena - permission_edit_own_time_entries: Izmena sopstvenog utroÅ¡enog vremena - permission_manage_news: Upravljanje vestima - permission_comment_news: Komentarisanje vesti - permission_manage_documents: Upravljanje dokumentima - permission_view_documents: Pregledanje dokumenata - permission_manage_files: Upravljanje datotekama - permission_view_files: Pregledanje datoteka - permission_manage_wiki: Upravljanje wiki stranicama - permission_rename_wiki_pages: Promena imena wiki stranicama - permission_delete_wiki_pages: Brisanje wiki stranica - permission_view_wiki_pages: Pregledanje wiki stranica - permission_view_wiki_edits: Pregledanje wiki istorije - permission_edit_wiki_pages: Izmena wiki stranica - permission_delete_wiki_pages_attachments: Brisanje priloženih datoteka - permission_protect_wiki_pages: ZaÅ¡tita wiki stranica - permission_manage_repository: Upravljanje spremiÅ¡tem - permission_browse_repository: Pregledanje spremiÅ¡ta - permission_view_changesets: Pregledanje skupa promena - permission_commit_access: Potvrda pristupa - permission_manage_boards: Upravljanje forumima - permission_view_messages: Pregledanje poruka - permission_add_messages: Slanje poruka - permission_edit_messages: Izmena poruka - permission_edit_own_messages: Izmena sopstvenih poruka - permission_delete_messages: Brisanje poruka - permission_delete_own_messages: Brisanje sopstvenih poruka - permission_export_wiki_pages: Izvoz wiki stranica - permission_manage_subtasks: Upravljanje podzadacima - - project_module_issue_tracking: Praćenje problema - project_module_time_tracking: Praćenje vremena - project_module_news: Vesti - project_module_documents: Dokumenti - project_module_files: Datoteke - project_module_wiki: Wiki - project_module_repository: SpremiÅ¡te - project_module_boards: Forumi - - label_user: Korisnik - label_user_plural: Korisnici - label_user_new: Novi korisnik - label_user_anonymous: Anoniman - label_project: Projekat - label_project_new: Novi projekat - label_project_plural: Projekti - label_x_projects: - zero: nema projekata - one: jedan projekat - other: "%{count} projekata" - label_project_all: Svi projekti - label_project_latest: Poslednji projekti - label_issue: Problem - label_issue_new: Novi problem - label_issue_plural: Problemi - label_issue_view_all: Prikaz svih problema - label_issues_by: "Problemi (%{value})" - label_issue_added: Problem je dodat - label_issue_updated: Problem je ažuriran - label_document: Dokument - label_document_new: Novi dokument - label_document_plural: Dokumenti - label_document_added: Dokument je dodat - label_role: Uloga - label_role_plural: Uloge - label_role_new: Nova uloga - label_role_and_permissions: Uloge i dozvole - label_member: ÄŒlan - label_member_new: Novi Älan - label_member_plural: ÄŒlanovi - label_tracker: Praćenje - label_tracker_plural: Praćenja - label_tracker_new: Novo praćenje - label_workflow: Tok posla - label_issue_status: Status problema - label_issue_status_plural: Statusi problema - label_issue_status_new: Novi status - label_issue_category: Kategorija problema - label_issue_category_plural: Kategorije problema - label_issue_category_new: Nova kategorija - label_custom_field: PrilagoÄ‘eno polje - label_custom_field_plural: PrilagoÄ‘ena polja - label_custom_field_new: Novo prilagoÄ‘eno polje - label_enumerations: Nabrojiva lista - label_enumeration_new: Nova vrednost - label_information: Informacija - label_information_plural: Informacije - label_please_login: Molimo, prijavite se - label_register: Registracija - label_login_with_open_id_option: ili prijava sa OpenID - label_password_lost: Izgubljena lozinka - label_home: PoÄetak - label_my_page: Moja stranica - label_my_account: Moj nalog - label_my_projects: Moji projekti - label_my_page_block: My page block - label_administration: Administracija - label_login: Prijava - label_logout: Odjava - label_help: Pomoć - label_reported_issues: Prijavljeni problemi - label_assigned_to_me_issues: Problemi dodeljeni meni - label_last_login: Poslednje povezivanje - label_registered_on: Registrovan - label_activity: Aktivnost - label_overall_activity: Celokupna aktivnost - label_user_activity: "Aktivnost korisnika %{value}" - label_new: Novo - label_logged_as: Prijavljeni ste kao - label_environment: Okruženje - label_authentication: Potvrda identiteta - label_auth_source: Režim potvrde identiteta - label_auth_source_new: Novi režim potvrde identiteta - label_auth_source_plural: Režimi potvrde identiteta - label_subproject_plural: Potprojekti - label_subproject_new: Novi potprojekat - label_and_its_subprojects: "%{value} i njegovi potprojekti" - label_min_max_length: Min. - Maks. dužina - label_list: Spisak - label_date: Datum - label_integer: Ceo broj - label_float: Sa pokretnim zarezom - label_boolean: LogiÄki operator - label_string: Tekst - label_text: Dugi tekst - label_attribute: Osobina - label_attribute_plural: Osobine - label_download: "%{count} preuzimanje" - label_download_plural: "%{count} preuzimanja" - label_no_data: Nema podataka za prikazivanje - label_change_status: Promena statusa - label_history: Istorija - label_attachment: Datoteka - label_attachment_new: Nova datoteka - label_attachment_delete: Brisanje datoteke - label_attachment_plural: Datoteke - label_file_added: Datoteka je dodata - label_report: IzveÅ¡taj - label_report_plural: IzveÅ¡taji - label_news: Vesti - label_news_new: Dodavanje vesti - label_news_plural: Vesti - label_news_latest: Poslednje vesti - label_news_view_all: Prikaz svih vesti - label_news_added: Vesti su dodate - label_settings: PodeÅ¡avanja - label_overview: Pregled - label_version: Verzija - label_version_new: Nova verzija - label_version_plural: Verzije - label_close_versions: Zatvori zavrÅ¡ene verzije - label_confirmation: Potvrda - label_export_to: 'TakoÄ‘e dostupno i u varijanti:' - label_read: ÄŒitanje... - label_public_projects: Javni projekti - label_open_issues: otvoren - label_open_issues_plural: otvorenih - label_closed_issues: zatvoren - label_closed_issues_plural: zatvorenih - label_x_open_issues_abbr_on_total: - zero: 0 otvorenih / %{total} - one: 1 otvoren / %{total} - other: "%{count} otvorenih / %{total}" - label_x_open_issues_abbr: - zero: 0 otvorenih - one: 1 otvoren - other: "%{count} otvorenih" - label_x_closed_issues_abbr: - zero: 0 zatvorenih - one: 1 zatvoren - other: "%{count} zatvorenih" - label_total: Ukupno - label_permissions: Dozvole - label_current_status: Trenutni status - label_new_statuses_allowed: Novi statusi dozvoljeni - label_all: svi - label_none: nijedan - label_nobody: nikome - label_next: Sledeće - label_previous: Prethodno - label_used_by: Koristio - label_details: Detalji - label_add_note: Dodaj beleÅ¡ku - label_per_page: Po strani - label_calendar: Kalendar - label_months_from: meseci od - label_gantt: Gantov dijagram - label_internal: UnutraÅ¡nji - label_last_changes: "poslednjih %{count} promena" - label_change_view_all: Prikaži sve promene - label_personalize_page: Personalizuj ovu stranu - label_comment: Komentar - label_comment_plural: Komentari - label_x_comments: - zero: bez komentara - one: jedan komentar - other: "%{count} komentara" - label_comment_add: Dodaj komentar - label_comment_added: Komentar dodat - label_comment_delete: ObriÅ¡i komentare - label_query: PrilagoÄ‘en upit - label_query_plural: PrilagoÄ‘eni upiti - label_query_new: Novi upit - label_filter_add: Dodavanje filtera - label_filter_plural: Filteri - label_equals: je - label_not_equals: nije - label_in_less_than: manje od - label_in_more_than: viÅ¡e od - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: u - label_today: danas - label_all_time: sve vreme - label_yesterday: juÄe - label_this_week: ove sedmice - label_last_week: poslednje sedmice - label_last_n_days: "poslednjih %{count} dana" - label_this_month: ovog meseca - label_last_month: poslednjeg meseca - label_this_year: ove godine - label_date_range: Vremenski period - label_less_than_ago: pre manje od nekoliko dana - label_more_than_ago: pre viÅ¡e od nekoliko dana - label_ago: pre nekoliko dana - label_contains: sadrži - label_not_contains: ne sadrži - label_day_plural: dana - label_repository: SpremiÅ¡te - label_repository_plural: SpremiÅ¡ta - label_browse: Pregledanje - label_modification: "%{count} promena" - label_modification_plural: "%{count} promena" - label_branch: Grana - label_tag: Oznaka - label_revision: Revizija - label_revision_plural: Revizije - label_revision_id: "Revizija %{value}" - label_associated_revisions: Pridružene revizije - label_added: dodato - label_modified: promenjeno - label_copied: kopirano - label_renamed: preimenovano - label_deleted: izbrisano - label_latest_revision: Poslednja revizija - label_latest_revision_plural: Poslednje revizije - label_view_revisions: Pregled revizija - label_view_all_revisions: Pregled svih revizija - label_max_size: Maksimalna veliÄina - label_sort_highest: PremeÅ¡tanje na vrh - label_sort_higher: PremeÅ¡tanje na gore - label_sort_lower: PremeÅ¡tanje na dole - label_sort_lowest: PremeÅ¡tanje na dno - label_roadmap: Plan rada - label_roadmap_due_in: "Dospeva %{value}" - label_roadmap_overdue: "%{value} najkasnije" - label_roadmap_no_issues: Nema problema za ovu verziju - label_search: Pretraga - label_result_plural: Rezultati - label_all_words: Sve reÄi - label_wiki: Wiki - label_wiki_edit: Wiki izmena - label_wiki_edit_plural: Wiki izmene - label_wiki_page: Wiki stranica - label_wiki_page_plural: Wiki stranice - label_index_by_title: Indeksiranje po naslovu - label_index_by_date: Indeksiranje po datumu - label_current_version: Trenutna verzija - label_preview: Pregled - label_feed_plural: Izvori vesti - label_changes_details: Detalji svih promena - label_issue_tracking: Praćenje problema - label_spent_time: UtroÅ¡eno vreme - label_overall_spent_time: Celokupno utroÅ¡eno vreme - label_f_hour: "%{value} sat" - label_f_hour_plural: "%{value} sati" - label_time_tracking: Praćenje vremena - label_change_plural: Promene - label_statistics: Statistika - label_commits_per_month: IzvrÅ¡enja meseÄno - label_commits_per_author: IzvrÅ¡enja po autoru - label_view_diff: Pogledaj razlike - label_diff_inline: unutra - label_diff_side_by_side: uporedo - label_options: Opcije - label_copy_workflow_from: Kopiranje toka posla od - label_permissions_report: IzveÅ¡taj o dozvolama - label_watched_issues: Posmatrani problemi - label_related_issues: Srodni problemi - label_applied_status: Primenjeni statusi - label_loading: UÄitavanje... - label_relation_new: Nova relacija - label_relation_delete: Brisanje relacije - label_relates_to: srodnih sa - label_duplicates: dupliranih - label_duplicated_by: dupliranih od - label_blocks: odbijenih - label_blocked_by: odbijenih od - label_precedes: prethodi - label_follows: praćenih - label_end_to_start: od kraja do poÄetka - label_end_to_end: od kraja do kraja - label_start_to_start: od poÄetka do poÄetka - label_start_to_end: od poÄetka do kraja - label_stay_logged_in: Ostanite prijavljeni - label_disabled: onemogućeno - label_show_completed_versions: Prikazivanje zavrÅ¡ene verzije - label_me: meni - label_board: Forum - label_board_new: Novi forum - label_board_plural: Forumi - label_board_locked: ZakljuÄana - label_board_sticky: Lepljiva - label_topic_plural: Teme - label_message_plural: Poruke - label_message_last: Poslednja poruka - label_message_new: Nova poruka - label_message_posted: Poruka je dodata - label_reply_plural: Odgovori - label_send_information: PoÅ¡alji korisniku detalje naloga - label_year: Godina - label_month: Mesec - label_week: Sedmica - label_date_from: Å alje - label_date_to: Prima - label_language_based: Bazirano na jeziku korisnika - label_sort_by: "Sortirano po %{value}" - label_send_test_email: Slanje probne e-poruke - label_feeds_access_key: RSS pristupni kljuÄ - label_missing_feeds_access_key: RSS pristupni kljuÄ nedostaje - label_feeds_access_key_created_on: "RSS pristupni kljuÄ je napravljen pre %{value}" - label_module_plural: Moduli - label_added_time_by: "Dodao %{author} pre %{age}" - label_updated_time_by: "Ažurirao %{author} pre %{age}" - label_updated_time: "Ažurirano pre %{value}" - label_jump_to_a_project: Skok na projekat... - label_file_plural: Datoteke - label_changeset_plural: Skupovi promena - label_default_columns: Podrazumevane kolone - label_no_change_option: (Bez promena) - label_bulk_edit_selected_issues: Grupna izmena odabranih problema - label_theme: Tema - label_default: Podrazumevano - label_search_titles_only: Pretražuj samo naslove - label_user_mail_option_all: "Za bilo koji dogaÄ‘aj na svim mojim projektima" - label_user_mail_option_selected: "Za bilo koji dogaÄ‘aj na samo odabranim projektima..." - label_user_mail_no_self_notified: "Ne želim biti obaveÅ¡tavan za promene koje sam pravim" - label_registration_activation_by_email: aktivacija naloga putem e-poruke - label_registration_manual_activation: ruÄna aktivacija naloga - label_registration_automatic_activation: automatska aktivacija naloga - label_display_per_page: "Broj stavki po stranici: %{value}" - label_age: Starost - label_change_properties: Promeni svojstva - label_general: OpÅ¡ti - label_more: ViÅ¡e - label_scm: SCM - label_plugins: Dodatne komponente - label_ldap_authentication: LDAP potvrda identiteta - label_downloads_abbr: D/L - label_optional_description: Opciono opis - label_add_another_file: Dodaj joÅ¡ jednu datoteku - label_preferences: PodeÅ¡avanja - label_chronological_order: po hronoloÅ¡kom redosledu - label_reverse_chronological_order: po obrnutom hronoloÅ¡kom redosledu - label_planning: Planiranje - label_incoming_emails: Dolazne e-poruke - label_generate_key: Generisanje kljuÄa - label_issue_watchers: PosmatraÄi - label_example: Primer - label_display: Prikaz - label_sort: Sortiranje - label_ascending: Rastući niz - label_descending: Opadajući niz - label_date_from_to: Od %{start} do %{end} - label_wiki_content_added: Wiki stranica je dodata - label_wiki_content_updated: Wiki stranica je ažurirana - label_group: Grupa - label_group_plural: Grupe - label_group_new: Nova grupa - label_time_entry_plural: UtroÅ¡eno vreme - label_version_sharing_none: Nije deljeno - label_version_sharing_descendants: Sa potprojektima - label_version_sharing_hierarchy: Sa hijerarhijom projekta - label_version_sharing_tree: Sa stablom projekta - label_version_sharing_system: Sa svim projektima - label_update_issue_done_ratios: Ažuriraj odnos reÅ¡enih problema - label_copy_source: Izvor - label_copy_target: OdrediÅ¡te - label_copy_same_as_target: Isto kao odrediÅ¡te - label_display_used_statuses_only: Prikazuj statuse korišćene samo od strane ovog praćenja - label_api_access_key: API pristupni kljuÄ - label_missing_api_access_key: Nedostaje API pristupni kljuÄ - label_api_access_key_created_on: "API pristupni kljuÄ je kreiran pre %{value}" - label_profile: Profil - label_subtask_plural: Podzadatak - label_project_copy_notifications: PoÅ¡alji e-poruku sa obaveÅ¡tenjem prilikom kopiranja projekta - - button_login: Prijava - button_submit: PoÅ¡alji - button_save: Snimi - button_check_all: UkljuÄi sve - button_uncheck_all: IskljuÄi sve - button_delete: IzbriÅ¡i - button_create: Kreiraj - button_create_and_continue: Kreiraj i nastavi - button_test: Test - button_edit: Izmeni - button_add: Dodaj - button_change: Promeni - button_apply: Primeni - button_clear: ObriÅ¡i - button_lock: ZakljuÄaj - button_unlock: OtkljuÄaj - button_download: Preuzmi - button_list: Spisak - button_view: Prikaži - button_move: Pomeri - button_move_and_follow: Pomeri i prati - button_back: Nazad - button_cancel: PoniÅ¡ti - button_activate: Aktiviraj - button_sort: Sortiraj - button_log_time: Evidentiraj vreme - button_rollback: Povratak na ovu verziju - button_watch: Prati - button_unwatch: Ne prati viÅ¡e - button_reply: Odgovori - button_archive: Arhiviraj - button_unarchive: Vrati iz arhive - button_reset: PoniÅ¡ti - button_rename: Preimenuj - button_change_password: Promeni lozinku - button_copy: Kopiraj - button_copy_and_follow: Kopiraj i prati - button_annotate: Pribeleži - button_update: Ažuriraj - button_configure: Podesi - button_quote: Pod navodnicima - button_duplicate: Dupliraj - button_show: Prikaži - - status_active: aktivni - status_registered: registrovani - status_locked: zakljuÄani - - version_status_open: otvoren - version_status_locked: zakljuÄan - version_status_closed: zatvoren - - field_active: Aktivan - - text_select_mail_notifications: Odaberi akcije za koje će obaveÅ¡tenje biti poslato putem e-poÅ¡te. - text_regexp_info: npr. ^[A-Z0-9]+$ - text_min_max_length_info: 0 znaÄi bez ograniÄenja - text_project_destroy_confirmation: Jeste li sigurni da želite da izbriÅ¡ete ovaj projekat i sve pripadajuće podatke? - text_subprojects_destroy_warning: "Potprojekti: %{value} će takoÄ‘e biti izbrisan." - text_workflow_edit: Odaberite ulogu i praćenje za izmenu toka posla - text_are_you_sure: Jeste li sigurni? - text_journal_changed: "%{label} promenjen od %{old} u %{new}" - text_journal_set_to: "%{label} postavljen u %{value}" - text_journal_deleted: "%{label} izbrisano (%{old})" - text_journal_added: "%{label} %{value} dodato" - text_tip_issue_begin_day: zadatak poÄinje ovog dana - text_tip_issue_end_day: zadatak se zavrÅ¡ava ovog dana - text_tip_issue_begin_end_day: zadatak poÄinje i zavrÅ¡ava ovog dana - text_caracters_maximum: "NajviÅ¡e %{count} znak(ova)." - text_caracters_minimum: "Broj znakova mora biti najmanje %{count}." - text_length_between: "Broj znakova mora biti izmeÄ‘u %{min} i %{max}." - text_tracker_no_workflow: Ovo praćenje nema definisan tok posla - text_unallowed_characters: Nedozvoljeni znakovi - text_comma_separated: Dozvoljene su viÅ¡estruke vrednosti (odvojene zarezom). - text_line_separated: Dozvoljene su viÅ¡estruke vrednosti (jedan red za svaku vrednost). - text_issues_ref_in_commit_messages: Referenciranje i popravljanje problema u izvrÅ¡nim porukama - text_issue_added: "%{author} je prijavio problem %{id}." - text_issue_updated: "%{author} je ažurirao problem %{id}." - text_wiki_destroy_confirmation: Jeste li sigurni da želite da obriÅ¡ete wiki i sav sadržaj? - text_issue_category_destroy_question: "Nekoliko problema (%{count}) je dodeljeno ovoj kategoriji. Å ta želite da uradite?" - text_issue_category_destroy_assignments: Ukloni dodeljene kategorije - text_issue_category_reassign_to: Dodeli ponovo probleme ovoj kategoriji - text_user_mail_option: "Za neizabrane projekte, dobićete samo obaveÅ¡tenje o stvarima koje pratite ili ste ukljuÄeni (npr. problemi Äiji ste vi autor ili zastupnik)." - text_no_configuration_data: "Uloge, praćenja, statusi problema i toka posla joÅ¡ uvek nisu podeÅ¡eni.\nPreporuÄljivo je da uÄitate podrazumevano konfigurisanje. Izmena je moguća nakon prvog uÄitavanja." - text_load_default_configuration: UÄitaj podrazumevano konfigurisanje - text_status_changed_by_changeset: "Primenjeno u skupu sa promenama %{value}." - text_issues_destroy_confirmation: 'Jeste li sigurni da želite da izbriÅ¡ete odabrane probleme?' - text_select_project_modules: 'Odaberite module koje želite omogućiti za ovaj projekat:' - text_default_administrator_account_changed: Podrazumevani administratorski nalog je promenjen - text_file_repository_writable: Fascikla priloženih datoteka je upisiva - text_plugin_assets_writable: Fascikla elemenata dodatnih komponenti je upisiva - text_rmagick_available: RMagick je dostupan (opciono) - text_destroy_time_entries_question: "%{hours} sati je prijavljeno za ovaj problem koji želite izbrisati. Å ta želite da uradite?" - text_destroy_time_entries: IzbriÅ¡i prijavljene sate - text_assign_time_entries_to_project: Dodeli prijavljene sate projektu - text_reassign_time_entries: 'Dodeli ponovo prijavljene sate ovom problemu:' - text_user_wrote: "%{value} je napisao:" - text_enumeration_destroy_question: "%{count} objekat(a) je dodeljeno ovoj vrednosti." - text_enumeration_category_reassign_to: 'Dodeli ih ponovo ovoj vrednosti:' - text_email_delivery_not_configured: "Isporuka e-poruka nije konfigurisana i obaveÅ¡tenja su onemogućena.\nPodesite vaÅ¡ SMTP server u config/configuration.yml i pokrenite ponovo aplikaciju za njihovo omogućavanje." - text_repository_usernames_mapping: "Odaberite ili ažurirajte Redmine korisnike mapiranjem svakog korisniÄkog imena pronaÄ‘enog u evidenciji spremiÅ¡ta.\nKorisnici sa istim Redmine imenom i imenom spremiÅ¡ta ili e-adresom su automatski mapirani." - text_diff_truncated: '... Ova razlika je iseÄena jer je dostignuta maksimalna veliÄina prikaza.' - text_custom_field_possible_values_info: 'Jedan red za svaku vrednost' - text_wiki_page_destroy_question: "Ova stranica ima %{descendants} podreÄ‘enih stranica i podstranica. Å ta želite da uradite?" - text_wiki_page_nullify_children: "Zadrži podreÄ‘ene stranice kao korene stranice" - text_wiki_page_destroy_children: "IzbriÅ¡i podreÄ‘ene stranice i sve njihove podstranice" - text_wiki_page_reassign_children: "Dodeli ponovo podreÄ‘ene stranice ovoj matiÄnoj stranici" - text_own_membership_delete_confirmation: "Nakon uklanjanja pojedinih ili svih vaÅ¡ih dozvola nećete viÅ¡e moći da ureÄ‘ujete ovaj projekat.\nŽelite li da nastavite?" - text_zoom_in: Uvećaj - text_zoom_out: Umanji - - default_role_manager: Menadžer - default_role_developer: Programer - default_role_reporter: IzveÅ¡taÄ - default_tracker_bug: GreÅ¡ka - default_tracker_feature: Funkcionalnost - default_tracker_support: PodrÅ¡ka - default_issue_status_new: Novo - default_issue_status_in_progress: U toku - default_issue_status_resolved: ReÅ¡eno - default_issue_status_feedback: Povratna informacija - default_issue_status_closed: Zatvoreno - default_issue_status_rejected: Odbijeno - default_doc_category_user: KorisniÄka dokumentacija - default_doc_category_tech: TehniÄka dokumentacija - default_priority_low: Nizak - default_priority_normal: Normalan - default_priority_high: Visok - default_priority_urgent: Hitno - default_priority_immediate: Neposredno - default_activity_design: Dizajn - default_activity_development: Razvoj - - enumeration_issue_priorities: Prioriteti problema - enumeration_doc_categories: Kategorije dokumenta - enumeration_activities: Aktivnosti (praćenje vremena) - enumeration_system_activity: Sistemska aktivnost - - field_time_entries: Vreme evidencije - project_module_gantt: Gantov dijagram - project_module_calendar: Kalendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Samo za stvari koje posedujem - setting_default_notification_option: Podrazumevana opcija za notifikaciju - label_user_mail_option_only_my_events: Za dogadjaje koje pratim ili sam u njih ukljuÄen - label_user_mail_option_only_assigned: Za dogadjaje koji su mi dodeljeni liÄno - label_user_mail_option_none: Bez obaveÅ¡tenja - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: Projekat kome pokuÅ¡avate da pristupite je arhiviran - label_principal_search: "Traži korisnike ili grupe:" - label_user_search: "Traži korisnike:" - field_visible: Vidljivo - setting_emails_header: Email zaglavlje - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Omogući praćenje vremena - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maksimalan broj stavki na gant grafiku - field_warn_on_leaving_unsaved: Upozori me ako napuÅ¡tam stranu sa tekstom koji nije snimljen - text_warn_on_leaving_unsaved: Strana sadrži tekst koji nije snimljen i biće izgubljen ako je napustite. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} ažuriran" - label_news_comment_added: Komentar dodat u novosti - button_expand_all: ProÅ¡iri sve - button_collapse_all: Zatvori sve - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Da li ste sigurni da želite da obriÅ¡ete selektovane stavke ? - label_role_anonymous: Anonimus - label_role_non_member: Nije Älan - label_issue_note_added: Nota dodana - label_issue_status_updated: Status ažuriran - label_issue_priority_updated: Prioritet ažuriran - label_issues_visibility_own: Problem kreiran od strane ili je dodeljen korisniku - field_issues_visibility: Vidljivost problema - label_issues_visibility_all: Svi problemi - permission_set_own_issues_private: Podesi sopstveni problem kao privatan ili javan - field_is_private: Privatno - permission_set_issues_private: Podesi problem kao privatan ili javan - label_issues_visibility_public: Svi javni problemi - text_issues_destroy_descendants_confirmation: Ova operacija će takoÄ‘e obrisati %{count} podzadataka. - field_commit_logs_encoding: Kodiranje izvrÅ¡nih poruka - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 problem - one: 1 problem - other: "%{count} problemi" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: svi - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Sa potprojektima - label_cross_project_tree: Sa stablom projekta - label_cross_project_hierarchy: Sa hijerarhijom projekta - label_cross_project_system: Sa svim projektima - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/38/385f3eab1c979f41797640419fbb6f558b89820f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/38/385f3eab1c979f41797640419fbb6f558b89820f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,306 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class RepositoryBazaarTest < ActiveSupport::TestCase + fixtures :projects + + include Redmine::I18n + + REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s + REPOSITORY_PATH_TRUNK = File.join(REPOSITORY_PATH, "trunk") + NUM_REV = 4 + + REPOSITORY_PATH_NON_ASCII = Rails.root.join(REPOSITORY_PATH + '/' + 'non_ascii').to_s + + # Bazaar core does not support xml output such as Subversion and Mercurial. + # "bzr" command output and command line parameter depend on locale. + # So, non ASCII path tests cannot run independent locale. + # + # If you want to run Bazaar non ASCII path tests on Linux *Ruby 1.9*, + # you need to set locale character set "ISO-8859-1". + # E.g. "LANG=en_US.ISO-8859-1". + # On Linux other platforms (e.g. Ruby 1.8, JRuby), + # you need to set "RUN_LATIN1_OUTPUT_TEST = true" manually. + # + # On Windows, because it is too hard to change system locale, + # you cannot run Bazaar non ASCII path tests. + # + RUN_LATIN1_OUTPUT_TEST = (RUBY_PLATFORM != 'java' && + REPOSITORY_PATH.respond_to?(:force_encoding) && + Encoding.locale_charmap == "ISO-8859-1") + + CHAR_1_UTF8_HEX = "\xc3\x9c" + CHAR_1_LATIN1_HEX = "\xdc" + + def setup + @project = Project.find(3) + @repository = Repository::Bazaar.create( + :project => @project, :url => REPOSITORY_PATH_TRUNK, + :log_encoding => 'UTF-8') + assert @repository + @char_1_utf8 = CHAR_1_UTF8_HEX.dup + @char_1_ascii8bit = CHAR_1_LATIN1_HEX.dup + if @char_1_utf8.respond_to?(:force_encoding) + @char_1_utf8.force_encoding('UTF-8') + @char_1_ascii8bit.force_encoding('ASCII-8BIT') + end + end + + def test_blank_path_to_repository_error_message + set_language_if_valid 'en' + repo = Repository::Bazaar.new( + :project => @project, + :identifier => 'test', + :log_encoding => 'UTF-8' + ) + assert !repo.save + assert_include "Path to repository can't be blank", + repo.errors.full_messages + end + + def test_blank_path_to_repository_error_message_fr + set_language_if_valid 'fr' + str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + repo = Repository::Bazaar.new( + :project => @project, + :url => "", + :identifier => 'test', + :log_encoding => 'UTF-8' + ) + assert !repo.save + assert_include str, repo.errors.full_messages + end + + if File.directory?(REPOSITORY_PATH_TRUNK) + def test_fetch_changesets_from_scratch + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + + assert_equal NUM_REV, @repository.changesets.count + assert_equal 9, @repository.filechanges.count + assert_equal 'Initial import', @repository.changesets.find_by_revision('1').comments + end + + def test_fetch_changesets_incremental + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + # Remove changesets with revision > 5 + @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 2} + @project.reload + assert_equal 2, @repository.changesets.count + + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + end + + def test_entries + entries = @repository.entries + assert_kind_of Redmine::Scm::Adapters::Entries, entries + assert_equal 2, entries.size + + assert_equal 'dir', entries[0].kind + assert_equal 'directory', entries[0].name + assert_equal 'directory', entries[0].path + + assert_equal 'file', entries[1].kind + assert_equal 'doc-mkdir.txt', entries[1].name + assert_equal 'doc-mkdir.txt', entries[1].path + end + + def test_entries_in_subdirectory + entries = @repository.entries('directory') + assert_equal 3, entries.size + + assert_equal 'file', entries.last.kind + assert_equal 'edit.png', entries.last.name + assert_equal 'directory/edit.png', entries.last.path + end + + def test_previous + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changeset = @repository.find_changeset_by_name('3') + assert_equal @repository.find_changeset_by_name('2'), changeset.previous + end + + def test_previous_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changeset = @repository.find_changeset_by_name('1') + assert_nil changeset.previous + end + + def test_next + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changeset = @repository.find_changeset_by_name('2') + assert_equal @repository.find_changeset_by_name('3'), changeset.next + end + + def test_next_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changeset = @repository.find_changeset_by_name('4') + assert_nil changeset.next + end + + if File.directory?(REPOSITORY_PATH_NON_ASCII) && RUN_LATIN1_OUTPUT_TEST + def test_cat_latin1_path + latin1_repo = create_latin1_repo + buf = latin1_repo.cat( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", 2) + assert buf + lines = buf.split("\n") + assert_equal 2, lines.length + assert_equal 'It is written in Python.', lines[1] + + buf = latin1_repo.cat( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", 2) + assert buf + lines = buf.split("\n") + assert_equal 1, lines.length + assert_equal "test-#{@char_1_ascii8bit}.txt", lines[0] + end + + def test_annotate_latin1_path + latin1_repo = create_latin1_repo + ann1 = latin1_repo.annotate( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", 2) + assert_equal 2, ann1.lines.size + assert_equal '2', ann1.revisions[0].identifier + assert_equal 'test00@', ann1.revisions[0].author + assert_equal 'It is written in Python.', ann1.lines[1] + ann2 = latin1_repo.annotate( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", 2) + assert_equal 1, ann2.lines.size + assert_equal '2', ann2.revisions[0].identifier + assert_equal 'test00@', ann2.revisions[0].author + assert_equal "test-#{@char_1_ascii8bit}.txt", ann2.lines[0] + end + + def test_diff_latin1_path + latin1_repo = create_latin1_repo + diff1 = latin1_repo.diff( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", 2, 1) + assert_equal 7, diff1.size + buf = diff1[5].gsub(/\r\n|\r|\n/, "") + assert_equal "+test-#{@char_1_ascii8bit}.txt", buf + end + + def test_entries_latin1_path + latin1_repo = create_latin1_repo + entries = latin1_repo.entries("test-#{@char_1_utf8}-dir", 2) + assert_kind_of Redmine::Scm::Adapters::Entries, entries + assert_equal 3, entries.size + assert_equal 'file', entries[1].kind + assert_equal "test-#{@char_1_utf8}-1.txt", entries[0].name + assert_equal "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", entries[0].path + end + + def test_entry_latin1_path + latin1_repo = create_latin1_repo + ["test-#{@char_1_utf8}-dir", + "/test-#{@char_1_utf8}-dir", + "/test-#{@char_1_utf8}-dir/" + ].each do |path| + entry = latin1_repo.entry(path, 2) + assert_equal "test-#{@char_1_utf8}-dir", entry.path + assert_equal "dir", entry.kind + end + ["test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", + "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt" + ].each do |path| + entry = latin1_repo.entry(path, 2) + assert_equal "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", + entry.path + assert_equal "file", entry.kind + end + end + + def test_changeset_latin1_path + latin1_repo = create_latin1_repo + assert_equal 0, latin1_repo.changesets.count + latin1_repo.fetch_changesets + @project.reload + assert_equal 3, latin1_repo.changesets.count + + cs2 = latin1_repo.changesets.find_by_revision('2') + assert_not_nil cs2 + assert_equal "test-#{@char_1_utf8}", cs2.comments + c2 = cs2.filechanges.sort_by(&:path) + assert_equal 4, c2.size + assert_equal 'A', c2[0].action + assert_equal "/test-#{@char_1_utf8}-dir/", c2[0].path + assert_equal 'A', c2[1].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", c2[1].path + assert_equal 'A', c2[2].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", c2[2].path + assert_equal 'A', c2[3].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}.txt", c2[3].path + + cs3 = latin1_repo.changesets.find_by_revision('3') + assert_not_nil cs3 + assert_equal "modify, move and delete #{@char_1_utf8} files", cs3.comments + c3 = cs3.filechanges.sort_by(&:path) + assert_equal 3, c3.size + assert_equal 'M', c3[0].action + assert_equal "/test-#{@char_1_utf8}-1.txt", c3[0].path + assert_equal 'D', c3[1].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", c3[1].path + assert_equal 'M', c3[2].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}.txt", c3[2].path + end + else + msg = "Bazaar non ASCII output test cannot run this environment." + "\n" + if msg.respond_to?(:force_encoding) + msg += "Encoding.locale_charmap: " + Encoding.locale_charmap + "\n" + end + puts msg + end + + private + + def create_latin1_repo + repo = Repository::Bazaar.create( + :project => @project, + :identifier => 'latin1', + :url => REPOSITORY_PATH_NON_ASCII, + :log_encoding => 'ISO-8859-1' + ) + assert repo + repo + end + else + puts "Bazaar test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/38/387c7328dffa5d4f176a177e772369b5a7044e34.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/38/387c7328dffa5d4f176a177e772369b5a7044e34.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,149 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::AttachmentsTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :attachments + + def setup + Setting.rest_api_enabled = '1' + set_fixtures_attachments_directory + end + + def teardown + set_tmp_attachments_directory + end + + test "GET /attachments/:id.xml should return the attachment" do + get '/attachments/7.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'attachment', + :child => { + :tag => 'id', + :content => '7', + :sibling => { + :tag => 'filename', + :content => 'archive.zip', + :sibling => { + :tag => 'content_url', + :content => 'http://www.example.com/attachments/download/7/archive.zip' + } + } + } + end + + test "GET /attachments/:id.xml should deny access without credentials" do + get '/attachments/7.xml' + assert_response 401 + set_tmp_attachments_directory + end + + test "GET /attachments/download/:id/:filename should return the attachment content" do + get '/attachments/download/7/archive.zip', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/octet-stream', @response.content_type + set_tmp_attachments_directory + end + + test "GET /attachments/download/:id/:filename should deny access without credentials" do + get '/attachments/download/7/archive.zip' + assert_response 302 + set_tmp_attachments_directory + end + + test "POST /uploads.xml should return the token" do + set_tmp_attachments_directory + assert_difference 'Attachment.count' do + post '/uploads.xml', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + assert_equal 'application/xml', response.content_type + end + + xml = Hash.from_xml(response.body) + assert_kind_of Hash, xml['upload'] + token = xml['upload']['token'] + assert_not_nil token + + attachment = Attachment.first(:order => 'id DESC') + assert_equal token, attachment.token + assert_nil attachment.container + assert_equal 2, attachment.author_id + assert_equal 'File content'.size, attachment.filesize + assert attachment.content_type.blank? + assert attachment.filename.present? + assert_match /\d+_[0-9a-z]+/, attachment.diskfile + assert File.exist?(attachment.diskfile) + assert_equal 'File content', File.read(attachment.diskfile) + end + + test "POST /uploads.json should return the token" do + set_tmp_attachments_directory + assert_difference 'Attachment.count' do + post '/uploads.json', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + assert_equal 'application/json', response.content_type + end + + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json['upload'] + token = json['upload']['token'] + assert_not_nil token + + attachment = Attachment.first(:order => 'id DESC') + assert_equal token, attachment.token + end + + test "POST /uploads.xml should accept :filename param as the attachment filename" do + set_tmp_attachments_directory + assert_difference 'Attachment.count' do + post '/uploads.xml?filename=test.txt', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + end + + attachment = Attachment.order('id DESC').first + assert_equal 'test.txt', attachment.filename + assert_match /_test\.txt$/, attachment.diskfile + end + + test "POST /uploads.xml should not accept other content types" do + set_tmp_attachments_directory + assert_no_difference 'Attachment.count' do + post '/uploads.xml', 'PNG DATA', {"CONTENT_TYPE" => 'image/png'}.merge(credentials('jsmith')) + assert_response 406 + end + end + + test "POST /uploads.xml should return errors if file is too big" do + set_tmp_attachments_directory + with_settings :attachment_max_size => 1 do + assert_no_difference 'Attachment.count' do + post '/uploads.xml', ('x' * 2048), {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response 422 + assert_tag 'error', :content => /exceeds the maximum allowed file size/ + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/38/38cb9cea714e4d54066f4f27ed2bd0f760f4fd27.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/38/38cb9cea714e4d54066f4f27ed2bd0f760f4fd27.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,12 @@ +class RemoveEolsFromAttachmentsFilename < ActiveRecord::Migration + def up + Attachment.where("filename like ? or filename like ?", "%\r%", "%\n%").each do |attachment| + filename = attachment.filename.to_s.tr("\r\n", "_") + Attachment.where(:id => attachment.id).update_all(:filename => filename) + end + end + + def down + # nop + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/39/39238668b625cbb6806dee35367f698ce0fb8c33.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/39/39238668b625cbb6806dee35367f698ce0fb8c33.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,43 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module ReportsHelper + + def aggregate(data, criteria) + a = 0 + data.each { |row| + match = 1 + criteria.each { |k, v| + match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && (v == 0 ? ['f', false] : ['t', true]).include?(row[k])) + } unless criteria.nil? + a = a + row["total"].to_i if match == 1 + } unless data.nil? + a + end + + def aggregate_link(data, criteria, *args) + a = aggregate data, criteria + a > 0 ? link_to(h(a), *args) : '-' + end + + def aggregate_path(project, field, row, options={}) + parameters = {:set_filter => 1, :subproject_id => '!*', field => row.id}.merge(options) + project_issues_path(row.is_a?(Project) ? row : project, parameters) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/39/3983d667a8cb000996c40e808d65d88d04de0c60.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/39/3983d667a8cb000996c40e808d65d88d04de0c60.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1103 @@ +mk: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d/%m/%Y" + short: "%d %b" + long: "%d %B, %Y" + + day_names: [недела, понеделник, вторник, Ñреда, четврток, петок, Ñабота] + abbr_day_names: [нед, пон, вто, Ñре, чет, пет, Ñаб] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, јануари, февруари, март, април, мај, јуни, јули, авгуÑÑ‚, Ñептември, октомври, ноември, декември] + abbr_month_names: [~, јан, фев, мар, апр, мај, јун, јул, авг, Ñеп, окт, ное, дек] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%d/%m/%Y %H:%M" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%d %B, %Y %H:%M" + am: "предпладне" + pm: "попладне" + + datetime: + distance_in_words: + half_a_minute: "пола минута" + less_than_x_seconds: + one: "помалку од 1 Ñекунда" + other: "помалку од %{count} Ñекунди" + x_seconds: + one: "1 Ñекунда" + other: "%{count} Ñекунди" + less_than_x_minutes: + one: "помалку од 1 минута" + other: "помалку од %{count} минути" + x_minutes: + one: "1 минута" + other: "%{count} минути" + about_x_hours: + one: "околу 1 чаÑ" + other: "околу %{count} чаÑа" + x_hours: + one: "1 чаÑ" + other: "%{count} чаÑа" + x_days: + one: "1 ден" + other: "%{count} дена" + about_x_months: + one: "околу 1 меÑец" + other: "околу %{count} меÑеци" + x_months: + one: "1 меÑец" + other: "%{count} меÑеци" + about_x_years: + one: "околу 1 година" + other: "околу %{count} години" + over_x_years: + one: "преку 1 година" + other: "преку %{count} години" + almost_x_years: + one: "Ñкоро 1 година" + other: "Ñкоро %{count} години" + + number: + # Default format for numbers + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + +# Used in array.to_sentence. + support: + array: + sentence_connector: "и" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "не е вклучено во лиÑтата" + exclusion: "е резервирано" + invalid: "е невалидно" + confirmation: "не Ñе Ñовпаѓа Ñо потврдата" + accepted: "мора да е прифатено" + empty: "неможе да е празно" + blank: "неможе да е празно" + too_long: "е предолго (макÑ. %{count} знаци)" + too_short: "е прекратко (мин. %{count} знаци)" + wrong_length: "е погрешна должина (треба да е %{count} знаци)" + taken: "е веќе зафатено" + not_a_number: "не е број" + not_a_date: "не е валидна дата" + greater_than: "мора да е поголемо од %{count}" + greater_than_or_equal_to: "мора да е поголемо или еднакво на %{count}" + equal_to: "мора да е еднакво на %{count}" + less_than: "мора да е помало од %{count}" + less_than_or_equal_to: "мора да е помало или еднакво на %{count}" + odd: "мора да е непарно" + even: "мора да е парно" + greater_than_start_date: "мора да е поголема од почетната дата" + not_same_project: "не припаѓа на иÑтиот проект" + circular_dependency: "Оваа врÑка ќе креира кружна завиÑноÑÑ‚" + cant_link_an_issue_with_a_descendant: "Задача неможе да Ñе поврзе Ñо една од нејзините подзадачи" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Изберете + + general_text_No: 'Ðе' + general_text_Yes: 'Да' + general_text_no: 'не' + general_text_yes: 'да' + general_lang_name: 'Macedonian (МакедонÑки)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Профилот е уÑпешно ажуриран. + notice_account_invalid_creditentials: Ðеточен кориÑник или лозинка + notice_account_password_updated: Лозинката е уÑпешно ажурирана. + notice_account_wrong_password: Погрешна лозинка + notice_account_register_done: Профилот е уÑпешно креиран. За активација, клкнете на врÑката што ви е пратена по е-пошта. + notice_account_unknown_email: Ðепознат кориÑник. + notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. + notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. + notice_account_activated: Your account has been activated. You can now log in. + notice_successful_create: УÑпешно креирање. + notice_successful_update: УÑпешно ажурирање. + notice_successful_delete: УÑпешно бришење. + notice_successful_connection: УÑпешна конекција. + notice_file_not_found: The page you were trying to access doesn't exist or has been removed. + notice_locking_conflict: Data has been updated by another user. + notice_not_authorized: You are not authorized to access this page. + notice_email_sent: "Е-порака е пратена на %{value}" + notice_email_error: "Се Ñлучи грешка при праќање на е-пораката (%{value})" + notice_feeds_access_key_reseted: Вашиот Atom клуч за приÑтап е reset. + notice_api_access_key_reseted: Вашиот API клуч за приÑтап е reset. + notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." + notice_account_pending: "Your account was created and is now pending administrator approval." + notice_default_data_loaded: Default configuration successfully loaded. + notice_unable_delete_version: Unable to delete version. + notice_unable_delete_time_entry: Unable to delete time log entry. + notice_issue_done_ratios_updated: Issue done ratios updated. + + error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" + error_scm_not_found: "The entry or revision was not found in the repository." + error_scm_command_failed: "An error occurred when trying to access the repository: %{value}" + error_scm_annotate: "The entry does not exist or can not be annotated." + error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' + error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' + error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' + error_can_not_delete_custom_field: Unable to delete custom field + error_can_not_delete_tracker: "This tracker contains issues and can't be deleted." + error_can_not_remove_role: "This role is in use and can not be deleted." + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' + error_can_not_archive_project: This project can not be archived + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + error_unable_delete_issue_status: 'Unable to delete issue status' + error_unable_to_connect: "Unable to connect (%{value})" + warning_attachments_not_saved: "%{count} file(s) could not be saved." + + mail_subject_lost_password: "Вашата %{value} лозинка" + mail_body_lost_password: 'To change your password, click on the following link:' + mail_subject_register: "Your %{value} account activation" + mail_body_register: 'To activate your account, click on the following link:' + mail_body_account_information_external: "You can use your %{value} account to log in." + mail_body_account_information: Your account information + mail_subject_account_activation_request: "%{value} account activation request" + mail_body_account_activation_request: "Ðов кориÑник (%{value}) е региÑтриран. The account is pending your approval:" + mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" + mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." + + + field_name: Име + field_description: ÐžÐ¿Ð¸Ñ + field_summary: Краток Ð¾Ð¿Ð¸Ñ + field_is_required: Задолжително + field_firstname: Име + field_lastname: Презиме + field_mail: Е-пошта + field_filename: Датотека + field_filesize: Големина + field_downloads: Превземања + field_author: Ðвтор + field_created_on: Креиран + field_updated_on: Ðжурирано + field_field_format: Формат + field_is_for_all: За Ñите проекти + field_possible_values: Можни вредноÑти + field_regexp: Regular expression + field_min_length: Минимална должина + field_max_length: МакÑимална должина + field_value: ВредноÑÑ‚ + field_category: Категорија + field_title: ÐаÑлов + field_project: Проект + field_issue: Задача + field_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ + field_notes: Белешки + field_is_closed: Задачата е затворена + field_is_default: Default value + field_tracker: Tracker + field_subject: ÐаÑлов + field_due_date: Краен рок + field_assigned_to: Доделена на + field_priority: Приоритет + field_fixed_version: Target version + field_user: КориÑник + field_principal: Principal + field_role: Улога + field_homepage: Веб Ñтрана + field_is_public: Јавен + field_parent: Подпроект на + field_is_in_roadmap: Issues displayed in roadmap + field_login: КориÑник + field_mail_notification: ИзвеÑтувања по e-пошта + field_admin: ÐдминиÑтратор + field_last_login_on: ПоÑледна најава + field_language: Јазик + field_effective_date: Дата + field_password: Лозинка + field_new_password: Ðова лозинка + field_password_confirmation: Потврда + field_version: Верзија + field_type: Тип + field_host: ХоÑÑ‚ + field_port: Порт + field_account: Account + field_base_dn: Base DN + field_attr_login: Login attribute + field_attr_firstname: Firstname attribute + field_attr_lastname: Lastname attribute + field_attr_mail: Email attribute + field_onthefly: Моментално (On-the-fly) креирање на кориÑници + field_start_date: Почеток + field_done_ratio: "% Завршено" + field_auth_source: Режим на автентикација + field_hide_mail: Криј ја мојата адреÑа на е-пошта + field_comments: Коментар + field_url: URL + field_start_page: Почетна Ñтрана + field_subproject: Подпроект + field_hours: ЧаÑови + field_activity: ÐктивноÑÑ‚ + field_spent_on: Дата + field_identifier: Идентификатор + field_is_filter: КориÑти како филтер + field_issue_to: Поврзана задача + field_delay: Доцнење + field_assignable: Ðа оваа улога може да Ñе доделуваат задачи + field_redirect_existing_links: ПренаÑочи ги поÑтоечките врÑки + field_estimated_hours: Проценето време + field_column_names: Колони + field_time_entries: Бележи време + field_time_zone: ВременÑка зона + field_searchable: Може да Ñе пребарува + field_default_value: Default value + field_comments_sorting: Прикажувај коментари + field_parent_title: Parent page + field_editable: Може да Ñе уредува + field_watcher: Watcher + field_identity_url: OpenID URL + field_content: Содржина + field_group_by: Групирај ги резултатите Ñпоред + field_sharing: Споделување + field_parent_issue: Parent task + + setting_app_title: ÐаÑлов на апликацијата + setting_app_subtitle: ПоднаÑлов на апликацијата + setting_welcome_text: ТекÑÑ‚ за добредојде + setting_default_language: Default јазик + setting_login_required: Задолжителна автентикација + setting_self_registration: Само-региÑтрација + setting_attachment_max_size: МакÑ. големина на прилог + setting_issues_export_limit: Issues export limit + setting_mail_from: Emission email address + setting_bcc_recipients: Blind carbon copy recipients (bcc) + setting_plain_text_mail: ТекÑтуални е-пораки (без HTML) + setting_host_name: Име на хоÑÑ‚ и патека + setting_text_formatting: Форматирање на текÑÑ‚ + setting_wiki_compression: КомпреÑија на иÑторијата на вики + setting_feeds_limit: Feed content limit + setting_default_projects_public: Ðовите проекти Ñе иницијално јавни + setting_autofetch_changesets: Autofetch commits + setting_sys_api_enabled: Enable WS for repository management + setting_commit_ref_keywords: Referencing keywords + setting_commit_fix_keywords: Fixing keywords + setting_autologin: ÐвтоматÑка најава + setting_date_format: Формат на дата + setting_time_format: Формат на време + setting_cross_project_issue_relations: Дозволи релации на задачи меѓу проекти + setting_issue_list_default_columns: Default columns displayed on the issue list + setting_protocol: Протокол + setting_per_page_options: Objects per page options + setting_user_format: Приказ на кориÑниците + setting_activity_days_default: Денови прикажана во активноÑта на проектот + setting_display_subprojects_issues: Прикажи ги задачите на подпроектите во главните проекти + setting_enabled_scm: Овозможи SCM + setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" + setting_mail_handler_api_enabled: Enable WS for incoming emails + setting_mail_handler_api_key: API клуч + setting_sequential_project_identifiers: Генерирај поÑледователни идентификатори на проекти + setting_gravatar_enabled: КориÑти Gravatar кориÑнички икони + setting_gravatar_default: Default Gravatar image + setting_diff_max_lines_displayed: Max number of diff lines displayed + setting_file_max_size_displayed: Max size of text files displayed inline + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_openid: Дозволи OpenID најава и региÑтрација + setting_password_min_length: Мин. должина на лозинка + setting_new_project_user_role_id: Улога доделена на неадминиÑтраторÑки кориÑник кој креира проект + setting_default_projects_modules: Default enabled modules for new projects + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_field: Use the issue field + setting_issue_done_ratio_issue_status: Use the issue status + setting_start_of_week: Start calendars on + setting_rest_api_enabled: Enable REST web service + setting_cache_formatted_text: Cache formatted text + + permission_add_project: Креирај проекти + permission_add_subprojects: Креирај подпроекти + permission_edit_project: Уреди проект + permission_select_project_modules: Изберете модули за проект + permission_manage_members: Manage members + permission_manage_project_activities: Manage project activities + permission_manage_versions: Manage versions + permission_manage_categories: Manage issue categories + permission_view_issues: Прегледај задачи + permission_add_issues: Додавај задачи + permission_edit_issues: Уредувај задачи + permission_manage_issue_relations: Manage issue relations + permission_add_issue_notes: Додавај белешки + permission_edit_issue_notes: Уредувај белешки + permission_edit_own_issue_notes: Уредувај ÑопÑтвени белешки + permission_move_issues: ПремеÑтувај задачи + permission_delete_issues: Бриши задачи + permission_manage_public_queries: Manage public queries + permission_save_queries: Save queries + permission_view_gantt: View gantt chart + permission_view_calendar: View calendar + permission_view_issue_watchers: View watchers list + permission_add_issue_watchers: Add watchers + permission_delete_issue_watchers: Delete watchers + permission_log_time: Бележи потрошено време + permission_view_time_entries: Прегледај потрошено време + permission_edit_time_entries: Уредувај белешки за потрошено време + permission_edit_own_time_entries: Уредувај ÑопÑтвени белешки за потрошено време + permission_manage_news: Manage news + permission_comment_news: Коментирај на веÑти + permission_view_documents: Прегледувај документи + permission_manage_files: Manage files + permission_view_files: Прегледувај датотеки + permission_manage_wiki: Manage wiki + permission_rename_wiki_pages: Преименувај вики Ñтраници + permission_delete_wiki_pages: Бриши вики Ñтраници + permission_view_wiki_pages: Прегледувај вики + permission_view_wiki_edits: Прегледувај вики иÑторија + permission_edit_wiki_pages: Уредувај вики Ñтраници + permission_delete_wiki_pages_attachments: Бриши прилози + permission_protect_wiki_pages: Заштитувај вики Ñтраници + permission_manage_repository: Manage repository + permission_browse_repository: Browse repository + permission_view_changesets: View changesets + permission_commit_access: Commit access + permission_manage_boards: Manage boards + permission_view_messages: View messages + permission_add_messages: Post messages + permission_edit_messages: Уредувај пораки + permission_edit_own_messages: Уредувај ÑопÑтвени пораки + permission_delete_messages: Бриши пораки + permission_delete_own_messages: Бриши ÑопÑтвени пораки + permission_export_wiki_pages: Export wiki pages + permission_manage_subtasks: Manage subtasks + + project_module_issue_tracking: Следење на задачи + project_module_time_tracking: Следење на време + project_module_news: ВеÑти + project_module_documents: Документи + project_module_files: Датотеки + project_module_wiki: Вики + project_module_repository: Repository + project_module_boards: Форуми + project_module_calendar: Календар + project_module_gantt: Gantt + + label_user: КориÑник + label_user_plural: КориÑници + label_user_new: Ðов кориÑник + label_user_anonymous: Ðнонимен + label_project: Проект + label_project_new: Ðов проект + label_project_plural: Проекти + label_x_projects: + zero: нема проекти + one: 1 проект + other: "%{count} проекти" + label_project_all: Сите проекти + label_project_latest: ПоÑледните проекти + label_issue: Задача + label_issue_new: Ðова задача + label_issue_plural: Задачи + label_issue_view_all: Прегледај ги Ñите задачи + label_issues_by: "Задачи по %{value}" + label_issue_added: Задачата е додадена + label_issue_updated: Задачата е ажурирана + label_document: Документ + label_document_new: Ðов документ + label_document_plural: Документи + label_document_added: Документот е додаден + label_role: Улога + label_role_plural: Улоги + label_role_new: Ðова улога + label_role_and_permissions: Улоги и овлаÑтувања + label_member: Член + label_member_new: Ðов член + label_member_plural: Членови + label_tracker: Tracker + label_tracker_plural: Trackers + label_tracker_new: New tracker + label_workflow: Workflow + label_issue_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð½Ð° задача + label_issue_status_plural: СтатуÑи на задачи + label_issue_status_new: Ðов ÑÑ‚Ð°Ñ‚ÑƒÑ + label_issue_category: Категорија на задача + label_issue_category_plural: Категории на задачи + label_issue_category_new: Ðова категорија + label_custom_field: Прилагодено поле + label_custom_field_plural: Прилагодени полиња + label_custom_field_new: Ðово прилагодено поле + label_enumerations: Enumerations + label_enumeration_new: Ðова вредноÑÑ‚ + label_information: Информација + label_information_plural: Информации + label_please_login: Ðајави Ñе + label_register: РегиÑтрирај Ñе + label_login_with_open_id_option: или најави Ñе Ñо OpenID + label_password_lost: Изгубена лозинка + label_home: Почетна + label_my_page: Мојата Ñтрана + label_my_account: Мојот профил + label_my_projects: Мои проекти + label_my_page_block: Блок елемент + label_administration: ÐдминиÑтрација + label_login: Ðајави Ñе + label_logout: Одјави Ñе + label_help: Помош + label_reported_issues: Пријавени задачи + label_assigned_to_me_issues: Задачи доделени на мене + label_last_login: ПоÑледна најава + label_registered_on: РегиÑтриран на + label_activity: ÐктивноÑÑ‚ + label_overall_activity: Севкупна активноÑÑ‚ + label_user_activity: "ÐктивноÑÑ‚ на %{value}" + label_new: Ðова + label_logged_as: Ðајавени Ñте како + label_environment: Опкружување + label_authentication: Ðвтентикација + label_auth_source: Режим на автентикација + label_auth_source_new: Ðов режим на автентикација + label_auth_source_plural: Режими на автентикација + label_subproject_plural: Подпроекти + label_subproject_new: Ðов подпроект + label_and_its_subprojects: "%{value} и неговите подпроекти" + label_min_max_length: Мин. - МакÑ. должина + label_list: ЛиÑта + label_date: Дата + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: ТекÑÑ‚ + label_text: Долг текÑÑ‚ + label_attribute: Ðтрибут + label_attribute_plural: Ðтрибути + label_no_data: Ðема податоци за прикажување + label_change_status: Промени ÑÑ‚Ð°Ñ‚ÑƒÑ + label_history: ИÑторија + label_attachment: Датотека + label_attachment_new: Ðова датотека + label_attachment_delete: Избриши датотека + label_attachment_plural: Датотеки + label_file_added: Датотеката е додадена + label_report: Извештај + label_report_plural: Извештаи + label_news: ÐовоÑÑ‚ + label_news_new: Додади новоÑÑ‚ + label_news_plural: ÐовоÑти + label_news_latest: ПоÑледни новоÑти + label_news_view_all: Прегледај ги Ñите новоÑти + label_news_added: ÐовоÑтта е додадена + label_settings: Settings + label_overview: Преглед + label_version: Верзија + label_version_new: Ðова верзија + label_version_plural: Верзии + label_close_versions: Затвори ги завршените врзии + label_confirmation: Потврда + label_export_to: 'ДоÑтапно и во:' + label_read: Прочитај... + label_public_projects: Јавни проекти + label_open_issues: отворена + label_open_issues_plural: отворени + label_closed_issues: затворена + label_closed_issues_plural: затворени + label_x_open_issues_abbr_on_total: + zero: 0 отворени / %{total} + one: 1 отворена / %{total} + other: "%{count} отворени / %{total}" + label_x_open_issues_abbr: + zero: 0 отворени + one: 1 отворена + other: "%{count} отворени" + label_x_closed_issues_abbr: + zero: 0 затворени + one: 1 затворена + other: "%{count} затворени" + label_total: Вкупно + label_permissions: ОвлаÑтувања + label_current_status: Моментален ÑÑ‚Ð°Ñ‚ÑƒÑ + label_new_statuses_allowed: Дозволени нови ÑтатуÑи + label_all: Ñите + label_none: ниеден + label_nobody: никој + label_next: Следно + label_previous: Претходно + label_used_by: КориÑтено од + label_details: Детали + label_add_note: Додади белешка + label_per_page: По Ñтрана + label_calendar: Календар + label_months_from: меÑеци од + label_gantt: Gantt + label_internal: Internal + label_last_changes: "поÑледни %{count} промени" + label_change_view_all: Прегледај ги Ñите промени + label_personalize_page: Прилагоди ја Ñтранава + label_comment: Коментар + label_comment_plural: Коментари + label_x_comments: + zero: нема коментари + one: 1 коментар + other: "%{count} коментари" + label_comment_add: Додади коментар + label_comment_added: Коментарот е додаден + label_comment_delete: Избриши коментари + label_query: Custom query + label_query_plural: Custom queries + label_query_new: New query + label_filter_add: Додади филтер + label_filter_plural: Филтри + label_equals: е + label_not_equals: не е + label_in_less_than: за помалку од + label_in_more_than: за повеќе од + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: во + label_today: Ð´ÐµÐ½ÐµÑ + label_all_time: цело време + label_yesterday: вчера + label_this_week: оваа недела + label_last_week: минатата недела + label_last_n_days: "поÑледните %{count} дена" + label_this_month: овој меÑец + label_last_month: минатиот меÑец + label_this_year: оваа година + label_date_range: Date range + label_less_than_ago: пред помалку од денови + label_more_than_ago: пред повеќе од денови + label_ago: пред денови + label_contains: Ñодржи + label_not_contains: не Ñодржи + label_day_plural: денови + label_repository: Складиште + label_repository_plural: Складишта + label_browse: ПрелиÑтувај + label_branch: Гранка + label_tag: Tag + label_revision: Ревизија + label_revision_plural: Ревизии + label_revision_id: "Ревизија %{value}" + label_associated_revisions: Associated revisions + label_added: added + label_modified: modified + label_copied: copied + label_renamed: renamed + label_deleted: deleted + label_latest_revision: ПоÑледна ревизија + label_latest_revision_plural: ПоÑледни ревизии + label_view_revisions: Прегледај ги ревизиите + label_view_all_revisions: Прегледај ги Ñите ревизии + label_max_size: МакÑ. големина + label_sort_highest: ПремеÑти најгоре + label_sort_higher: ПремеÑти нагоре + label_sort_lower: ПремеÑти надоле + label_sort_lowest: ПремеÑти најдоле + label_roadmap: Roadmap + label_roadmap_due_in: "Due in %{value}" + label_roadmap_overdue: "КаÑни %{value}" + label_roadmap_no_issues: Ðема задачи за оваа верзија + label_search: Барај + label_result_plural: Резултати + label_all_words: Сите зборови + label_wiki: Вики + label_wiki_edit: Вики уредување + label_wiki_edit_plural: Вики уредувања + label_wiki_page: Вики Ñтраница + label_wiki_page_plural: Вики Ñтраници + label_index_by_title: Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ð¾ наÑлов + label_index_by_date: Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ð¾ дата + label_current_version: Current version + label_preview: Preview + label_feed_plural: Feeds + label_changes_details: Детали за Ñите промени + label_issue_tracking: Следење на задачи + label_spent_time: Потрошено време + label_overall_spent_time: Вкупно потрошено време + label_f_hour: "%{value} чаÑ" + label_f_hour_plural: "%{value} чаÑа" + label_time_tracking: Следење на време + label_change_plural: Промени + label_statistics: СтатиÑтики + label_commits_per_month: Commits per month + label_commits_per_author: Commits per author + label_view_diff: View differences + label_diff_inline: inline + label_diff_side_by_side: side by side + label_options: Опции + label_copy_workflow_from: Copy workflow from + label_permissions_report: Permissions report + label_watched_issues: Watched issues + label_related_issues: Поврзани задачи + label_applied_status: Applied status + label_loading: Loading... + label_relation_new: Ðова релација + label_relation_delete: Избриши релација + label_relates_to: related to + label_duplicates: дупликати + label_duplicated_by: duplicated by + label_blocks: blocks + label_blocked_by: блокирано од + label_precedes: претходи + label_follows: Ñледи + label_end_to_start: крај до почеток + label_end_to_end: крај до крај + label_start_to_start: почеток до почеток + label_start_to_end: почеток до крај + label_stay_logged_in: ОÑтанете најавени + label_disabled: disabled + label_show_completed_versions: Show completed versions + label_me: Ñ˜Ð°Ñ + label_board: Форум + label_board_new: Ðов форум + label_board_plural: Форуми + label_board_locked: Заклучен + label_board_sticky: Sticky + label_topic_plural: Теми + label_message_plural: Пораки + label_message_last: ПоÑледна порака + label_message_new: Ðова порака + label_message_posted: Поракате е додадена + label_reply_plural: Одговори + label_send_information: ИÑпрати ги информациите за профилот на кориÑникот + label_year: Година + label_month: МеÑец + label_week: Ðедела + label_date_from: Од + label_date_to: До + label_language_based: Според јазикот на кориÑникот + label_sort_by: "Подреди Ñпоред %{value}" + label_send_test_email: ИÑпрати теÑÑ‚ е-порака + label_feeds_access_key: Atom клуч за приÑтап + label_missing_feeds_access_key: ÐедоÑтика Atom клуч за приÑтап + label_feeds_access_key_created_on: "Atom клучот за приÑтап креиран пред %{value}" + label_module_plural: Модули + label_added_time_by: "Додадено од %{author} пред %{age}" + label_updated_time_by: "Ðжурирано од %{author} пред %{age}" + label_updated_time: "Ðжурирано пред %{value}" + label_jump_to_a_project: Префрли Ñе на проект... + label_file_plural: Датотеки + label_changeset_plural: Changesets + label_default_columns: ОÑновни колони + label_no_change_option: (Без промена) + label_bulk_edit_selected_issues: Групно уредување на задачи + label_theme: Тема + label_default: Default + label_search_titles_only: Пребарувај Ñамо наÑлови + label_user_mail_option_all: "За било кој наÑтан во Ñите мои проекти" + label_user_mail_option_selected: "За било кој наÑтан Ñамо во избраните проекти..." + label_user_mail_no_self_notified: "Ðе ме извеÑтувај за промените што Ñ˜Ð°Ñ Ð³Ð¸ правам" + label_registration_activation_by_email: активација на профил преку е-пошта + label_registration_manual_activation: мануелна активација на профил + label_registration_automatic_activation: автоматÑка активација на профил + label_display_per_page: "По Ñтрана: %{value}" + label_age: Age + label_change_properties: Change properties + label_general: Општо + label_more: Повеќе + label_scm: SCM + label_plugins: Додатоци + label_ldap_authentication: LDAP автентикација + label_downloads_abbr: Превземања + label_optional_description: ÐžÐ¿Ð¸Ñ (незадолжително) + label_add_another_file: Додади уште една датотека + label_preferences: Preferences + label_chronological_order: Во хронолошки ред + label_reverse_chronological_order: In reverse chronological order + label_planning: Планирање + label_incoming_emails: Дојдовни е-пораки + label_generate_key: Генерирај клуч + label_issue_watchers: Watchers + label_example: Пример + label_display: Прикажи + label_sort: Подреди + label_ascending: РаÑтечки + label_descending: Опаѓачки + label_date_from_to: Од %{start} до %{end} + label_wiki_content_added: Вики Ñтраница додадена + label_wiki_content_updated: Вики Ñтраница ажурирана + label_group: Група + label_group_plural: Групи + label_group_new: Ðова група + label_time_entry_plural: Потрошено време + label_version_sharing_none: Ðе Ñподелено + label_version_sharing_descendants: Со Ñите подпроекти + label_version_sharing_hierarchy: Со хиерархијата на проектот + label_version_sharing_tree: Со дрвото на проектот + label_version_sharing_system: Со Ñите проекти + label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Извор + label_copy_target: ДеÑтинација + label_copy_same_as_target: ИÑто како деÑтинацијата + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_api_access_key: API клуч за приÑтап + label_missing_api_access_key: ÐедоÑтига API клуч за приÑтап + label_api_access_key_created_on: "API клучот за приÑтап е креиран пред %{value}" + label_profile: Профил + label_subtask_plural: Подзадачи + label_project_copy_notifications: Праќај извеÑтувања по е-пошта при копирање на проект + + button_login: Ðајави Ñе + button_submit: ИÑпрати + button_save: Зачувај + button_check_all: Штиклирај ги Ñите + button_uncheck_all: Одштиклирај ги Ñите + button_delete: Избриши + button_create: Креирај + button_create_and_continue: Креирај и продолжи + button_test: ТеÑÑ‚ + button_edit: Уреди + button_add: Додади + button_change: Промени + button_apply: Примени + button_clear: Избриши + button_lock: Заклучи + button_unlock: Отклучи + button_download: Превземи + button_list: List + button_view: Прегледај + button_move: ПремеÑти + button_move_and_follow: ПремеÑти и Ñледи + button_back: Back + button_cancel: Откажи + button_activate: Ðктивирај + button_sort: Подреди + button_log_time: Бележи време + button_rollback: Rollback to this version + button_watch: Следи + button_unwatch: Ðе Ñледи + button_reply: Одговори + button_archive: Ðрхивирај + button_unarchive: Одархивирај + button_reset: Reset + button_rename: Преименувај + button_change_password: Промени лозинка + button_copy: Копирај + button_copy_and_follow: Копирај и Ñледи + button_annotate: Annotate + button_update: Ðжурирај + button_configure: Конфигурирај + button_quote: Цитирај + button_duplicate: Копирај + button_show: Show + + status_active: активни + status_registered: региÑтрирани + status_locked: заклучени + + version_status_open: отворени + version_status_locked: заклучени + version_status_closed: затворени + + field_active: Active + + text_select_mail_notifications: Изберете за кои наÑтани да Ñе праќаат извеÑтувања по е-пошта да Ñе праќаат. + text_regexp_info: eg. ^[A-Z0-9]+$ + text_min_max_length_info: 0 значи без ограничување + text_project_destroy_confirmation: Дали Ñте Ñигурни дека Ñакате да го избришете проектот и Ñите поврзани податоци? + text_subprojects_destroy_warning: "Ðеговите подпроекти: %{value} иÑто така ќе бидат избришани." + text_workflow_edit: Select a role and a tracker to edit the workflow + text_are_you_sure: Дали Ñте Ñигурни? + text_journal_changed: "%{label} променето од %{old} во %{new}" + text_journal_set_to: "%{label} set to %{value}" + text_journal_deleted: "%{label} избришан (%{old})" + text_journal_added: "%{label} %{value} додаден" + text_tip_issue_begin_day: задачи што почнуваат овој ден + text_tip_issue_end_day: задачи што завршуваат овој ден + text_tip_issue_begin_end_day: задачи што почнуваат и завршуваат овој ден + text_caracters_maximum: "%{count} знаци макÑимум." + text_caracters_minimum: "Мора да е најмалку %{count} знаци долго." + text_length_between: "Должина помеѓу %{min} и %{max} знаци." + text_tracker_no_workflow: No workflow defined for this tracker + text_unallowed_characters: Ðедозволени знаци + text_comma_separated: Дозволени Ñе повеќе вредноÑти (разделени Ñо запирка). + text_line_separated: Дозволени Ñе повеќе вредноÑти (една линија за Ñекоја вредноÑÑ‚). + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_issue_added: "Задачата %{id} е пријавена од %{author}." + text_issue_updated: "Задачата %{id} е ажурирана од %{author}." + text_wiki_destroy_confirmation: Дали Ñте Ñигурни дека Ñакате да го избришете ова вики и целата негова Ñодржина? + text_issue_category_destroy_question: "Ðекои задачи (%{count}) Ñе доделени на оваа категорија. Што Ñакате да правите?" + text_issue_category_destroy_assignments: Remove category assignments + text_issue_category_reassign_to: Додели ги задачите на оваа категорија + text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." + text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." + text_load_default_configuration: Load the default configuration + text_status_changed_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Дали Ñте Ñигурни дека Ñакате да ги избришете избраните задачи?' + text_select_project_modules: 'Изберете модули за овој проект:' + text_default_administrator_account_changed: Default administrator account changed + text_file_repository_writable: Во папката за прилози може да Ñе запишува + text_plugin_assets_writable: Во папката за додатоци може да Ñе запишува + text_rmagick_available: RMagick available (незадолжително) + text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do ?" + text_destroy_time_entries: Delete reported hours + text_assign_time_entries_to_project: Додели ги пријавените чаÑови на проектот + text_reassign_time_entries: 'Reassign reported hours to this issue:' + text_user_wrote: "%{value} напиша:" + text_enumeration_destroy_question: "%{count} objects are assigned to this value." + text_enumeration_category_reassign_to: 'Reassign them to this value:' + text_email_delivery_not_configured: "ДоÑтавата по е-пошта не е конфигурирана, и извеÑтувањата Ñе оневозможени.\nКонфигурирајте го Вашиот SMTP Ñервер во config/configuration.yml и реÑтартирајте ја апликацијата." + text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." + text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' + text_custom_field_possible_values_info: 'One line for each value' + text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" + text_wiki_page_nullify_children: "Keep child pages as root pages" + text_wiki_page_destroy_children: "Delete child pages and all their descendants" + text_wiki_page_reassign_children: "Reassign child pages to this parent page" + text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" + text_zoom_in: Zoom in + text_zoom_out: Zoom out + + default_role_manager: Менаџер + default_role_developer: Developer + default_role_reporter: Reporter + default_tracker_bug: Грешка + default_tracker_feature: ФункционалноÑÑ‚ + default_tracker_support: Поддршка + default_issue_status_new: Ðова + default_issue_status_in_progress: Во Ð¿Ñ€Ð¾Ð³Ñ€ÐµÑ + default_issue_status_resolved: Разрешена + default_issue_status_feedback: Feedback + default_issue_status_closed: Затворена + default_issue_status_rejected: Одбиена + default_doc_category_user: КориÑничка документација + default_doc_category_tech: Техничка документација + default_priority_low: Ðизок + default_priority_normal: Ðормален + default_priority_high: ВиÑок + default_priority_urgent: Итно + default_priority_immediate: Веднаш + default_activity_design: Дизајн + default_activity_development: Развој + + enumeration_issue_priorities: Приоритети на задача + enumeration_doc_categories: Категории на документ + enumeration_activities: ÐктивноÑти (Ñледење на време) + enumeration_system_activity: СиÑтемÑка активноÑÑ‚ + + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Commit messages encoding + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 Задача + one: 1 Задача + other: "%{count} Задачи" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: Ñите + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Со Ñите подпроекти + label_cross_project_tree: Со дрвото на проектот + label_cross_project_hierarchy: Со хиерархијата на проектот + label_cross_project_system: Со Ñите проекти + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Вкупно + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_footer: Email footer + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/39/398856c44c1ca7b71583b09a6c42cfc1b0073c0e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/39/398856c44c1ca7b71583b09a6c42cfc1b0073c0e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,151 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Helpers + class TimeReport + attr_reader :criteria, :columns, :hours, :total_hours, :periods + + def initialize(project, issue, criteria, columns, time_entry_scope) + @project = project + @issue = issue + + @criteria = criteria || [] + @criteria = @criteria.select{|criteria| available_criteria.has_key? criteria} + @criteria.uniq! + @criteria = @criteria[0,3] + + @columns = (columns && %w(year month week day).include?(columns)) ? columns : 'month' + @scope = time_entry_scope + + run + end + + def available_criteria + @available_criteria || load_available_criteria + end + + private + + def run + unless @criteria.empty? + time_columns = %w(tyear tmonth tweek spent_on) + @hours = [] + @scope.sum(:hours, + :include => [:issue, :activity], + :group => @criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns, + :joins => @criteria.collect{|criteria| @available_criteria[criteria][:joins]}.compact).each do |hash, hours| + h = {'hours' => hours} + (@criteria + time_columns).each_with_index do |name, i| + h[name] = hash[i] + end + @hours << h + end + + @hours.each do |row| + case @columns + when 'year' + row['year'] = row['tyear'] + when 'month' + row['month'] = "#{row['tyear']}-#{row['tmonth']}" + when 'week' + row['week'] = "#{row['spent_on'].cwyear}-#{row['tweek']}" + when 'day' + row['day'] = "#{row['spent_on']}" + end + end + + min = @hours.collect {|row| row['spent_on']}.min + @from = min ? min.to_date : Date.today + + max = @hours.collect {|row| row['spent_on']}.max + @to = max ? max.to_date : Date.today + + @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f} + + @periods = [] + # Date#at_beginning_of_ not supported in Rails 1.2.x + date_from = @from.to_time + # 100 columns max + while date_from <= @to.to_time && @periods.length < 100 + case @columns + when 'year' + @periods << "#{date_from.year}" + date_from = (date_from + 1.year).at_beginning_of_year + when 'month' + @periods << "#{date_from.year}-#{date_from.month}" + date_from = (date_from + 1.month).at_beginning_of_month + when 'week' + @periods << "#{date_from.to_date.cwyear}-#{date_from.to_date.cweek}" + date_from = (date_from + 7.day).at_beginning_of_week + when 'day' + @periods << "#{date_from.to_date}" + date_from = date_from + 1.day + end + end + end + end + + def load_available_criteria + @available_criteria = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", + :klass => Project, + :label => :label_project}, + 'status' => {:sql => "#{Issue.table_name}.status_id", + :klass => IssueStatus, + :label => :field_status}, + 'version' => {:sql => "#{Issue.table_name}.fixed_version_id", + :klass => Version, + :label => :label_version}, + 'category' => {:sql => "#{Issue.table_name}.category_id", + :klass => IssueCategory, + :label => :field_category}, + 'user' => {:sql => "#{TimeEntry.table_name}.user_id", + :klass => User, + :label => :label_user}, + 'tracker' => {:sql => "#{Issue.table_name}.tracker_id", + :klass => Tracker, + :label => :label_tracker}, + 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id", + :klass => TimeEntryActivity, + :label => :label_activity}, + 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id", + :klass => Issue, + :label => :label_issue} + } + + # Add time entry custom fields + custom_fields = TimeEntryCustomField.all + # Add project custom fields + custom_fields += ProjectCustomField.all + # Add issue custom fields + custom_fields += (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields) + # Add time entry activity custom fields + custom_fields += TimeEntryActivityCustomField.all + + # Add list and boolean custom fields as available criteria + custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| + @available_criteria["cf_#{cf.id}"] = {:sql => "#{cf.join_alias}.value", + :joins => cf.join_for_order_statement, + :format => cf.field_format, + :label => cf.name} + end + + @available_criteria + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/39/39c71682ebab394069659920f9082d90bb8389ce.svn-base --- a/.svn/pristine/39/39c71682ebab394069659920f9082d90bb8389ce.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class DefaultDataTest < ActiveSupport::TestCase - include Redmine::I18n - fixtures :roles - - def test_no_data - assert !Redmine::DefaultData::Loader::no_data? - Role.delete_all("builtin = 0") - Tracker.delete_all - IssueStatus.delete_all - Enumeration.delete_all - assert Redmine::DefaultData::Loader::no_data? - end - - def test_load - valid_languages.each do |lang| - begin - Role.delete_all("builtin = 0") - Tracker.delete_all - IssueStatus.delete_all - Enumeration.delete_all - assert Redmine::DefaultData::Loader::load(lang) - assert_not_nil DocumentCategory.first - assert_not_nil IssuePriority.first - assert_not_nil TimeEntryActivity.first - rescue ActiveRecord::RecordInvalid => e - assert false, ":#{lang} default data is invalid (#{e.message})." - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/39/39db13c4cad8ea5def9b39b1a667edc1cb253f4e.svn-base --- a/.svn/pristine/39/39db13c4cad8ea5def9b39b1a667edc1cb253f4e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,185 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -begin - require 'mocha' -rescue - # Won't run some tests -end - -class AccountTest < ActionController::IntegrationTest - fixtures :users, :roles - - # Replace this with your real tests. - def test_login - get "my/page" - assert_redirected_to "/login?back_url=http%3A%2F%2Fwww.example.com%2Fmy%2Fpage" - log_user('jsmith', 'jsmith') - - get "my/account" - assert_response :success - assert_template "my/account" - end - - def test_autologin - user = User.find(1) - Setting.autologin = "7" - Token.delete_all - - # User logs in with 'autologin' checked - post '/login', :username => user.login, :password => 'admin', :autologin => 1 - assert_redirected_to '/my/page' - token = Token.find :first - assert_not_nil token - assert_equal user, token.user - assert_equal 'autologin', token.action - assert_equal user.id, session[:user_id] - assert_equal token.value, cookies['autologin'] - - # Session is cleared - reset! - User.current = nil - # Clears user's last login timestamp - user.update_attribute :last_login_on, nil - assert_nil user.reload.last_login_on - - # User comes back with his autologin cookie - cookies[:autologin] = token.value - get '/my/page' - assert_response :success - assert_template 'my/page' - assert_equal user.id, session[:user_id] - assert_not_nil user.reload.last_login_on - end - - def test_lost_password - Token.delete_all - - get "account/lost_password" - assert_response :success - assert_template "account/lost_password" - assert_select 'input[name=mail]' - - post "account/lost_password", :mail => 'jSmith@somenet.foo' - assert_redirected_to "/login" - - token = Token.find(:first) - assert_equal 'recovery', token.action - assert_equal 'jsmith@somenet.foo', token.user.mail - assert !token.expired? - - get "account/lost_password", :token => token.value - assert_response :success - assert_template "account/password_recovery" - assert_select 'input[type=hidden][name=token][value=?]', token.value - assert_select 'input[name=new_password]' - assert_select 'input[name=new_password_confirmation]' - - post "account/lost_password", :token => token.value, :new_password => 'newpass123', :new_password_confirmation => 'newpass123' - assert_redirected_to "/login" - assert_equal 'Password was successfully updated.', flash[:notice] - - log_user('jsmith', 'newpass123') - assert_equal 0, Token.count - end - - def test_register_with_automatic_activation - Setting.self_registration = '3' - - get 'account/register' - assert_response :success - assert_template 'account/register' - - post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", - :password => "newpass123", :password_confirmation => "newpass123"} - assert_redirected_to '/my/account' - follow_redirect! - assert_response :success - assert_template 'my/account' - - user = User.find_by_login('newuser') - assert_not_nil user - assert user.active? - assert_not_nil user.last_login_on - end - - def test_register_with_manual_activation - Setting.self_registration = '2' - - post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", - :password => "newpass123", :password_confirmation => "newpass123"} - assert_redirected_to '/login' - assert !User.find_by_login('newuser').active? - end - - def test_register_with_email_activation - Setting.self_registration = '1' - Token.delete_all - - post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", - :password => "newpass123", :password_confirmation => "newpass123"} - assert_redirected_to '/login' - assert !User.find_by_login('newuser').active? - - token = Token.find(:first) - assert_equal 'register', token.action - assert_equal 'newuser@foo.bar', token.user.mail - assert !token.expired? - - get 'account/activate', :token => token.value - assert_redirected_to '/login' - log_user('newuser', 'newpass123') - end - - def test_onthefly_registration - # disable registration - Setting.self_registration = '0' - AuthSource.expects(:authenticate).returns({:login => 'foo', :firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com', :auth_source_id => 66}) - - post '/login', :username => 'foo', :password => 'bar' - assert_redirected_to '/my/page' - - user = User.find_by_login('foo') - assert user.is_a?(User) - assert_equal 66, user.auth_source_id - assert user.hashed_password.blank? - end - - def test_onthefly_registration_with_invalid_attributes - # disable registration - Setting.self_registration = '0' - AuthSource.expects(:authenticate).returns({:login => 'foo', :lastname => 'Smith', :auth_source_id => 66}) - - post '/login', :username => 'foo', :password => 'bar' - assert_response :success - assert_template 'account/register' - assert_tag :input, :attributes => { :name => 'user[firstname]', :value => '' } - assert_tag :input, :attributes => { :name => 'user[lastname]', :value => 'Smith' } - assert_no_tag :input, :attributes => { :name => 'user[login]' } - assert_no_tag :input, :attributes => { :name => 'user[password]' } - - post 'account/register', :user => {:firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com'} - assert_redirected_to '/my/account' - - user = User.find_by_login('foo') - assert user.is_a?(User) - assert_equal 66, user.auth_source_id - assert user.hashed_password.blank? - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3a/3a0e01441fbdb0f5b1aa103fddb2a58126ec7f0e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3a/3a0e01441fbdb0f5b1aa103fddb2a58126ec7f0e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,33 @@ +

    <%=l(:label_report_plural)%>

    + +
    +

    <%=l(:field_tracker)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'tracker') %>

    +<%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %> +
    +

    <%=l(:field_priority)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'priority') %>

    +<%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %> +
    +

    <%=l(:field_assigned_to)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'assigned_to') %>

    +<%= render :partial => 'simple', :locals => { :data => @issues_by_assigned_to, :field_name => "assigned_to_id", :rows => @assignees } %> +
    +

    <%=l(:field_author)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'author') %>

    +<%= render :partial => 'simple', :locals => { :data => @issues_by_author, :field_name => "author_id", :rows => @authors } %> +
    +<%= call_hook(:view_reports_issue_report_split_content_left, :project => @project) %> +
    + +
    +

    <%=l(:field_version)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'version') %>

    +<%= render :partial => 'simple', :locals => { :data => @issues_by_version, :field_name => "fixed_version_id", :rows => @versions } %> +
    +<% if @project.children.any? %> +

    <%=l(:field_subproject)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'subproject') %>

    +<%= render :partial => 'simple', :locals => { :data => @issues_by_subproject, :field_name => "project_id", :rows => @subprojects } %> +
    +<% end %> +

    <%=l(:field_category)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'category') %>

    +<%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %> +
    +<%= call_hook(:view_reports_issue_report_split_content_right, :project => @project) %> +
    + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3a/3a4efd0e9afcc8108cd60a444d52b8e7ec19f986.svn-base --- a/.svn/pristine/3a/3a4efd0e9afcc8108cd60a444d52b8e7ec19f986.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -/* Lithuanian (UTF-8) initialisation for the jQuery UI date picker plugin. */ -/* @author Arturas Paleicikas */ -jQuery(function($){ - $.datepicker.regional['lt'] = { - closeText: 'Uždaryti', - prevText: '<Atgal', - nextText: 'Pirmyn>', - currentText: 'Å iandien', - monthNames: ['Sausis','Vasaris','Kovas','Balandis','Gegužė','Birželis', - 'Liepa','RugpjÅ«tis','RugsÄ—jis','Spalis','Lapkritis','Gruodis'], - monthNamesShort: ['Sau','Vas','Kov','Bal','Geg','Bir', - 'Lie','Rugp','Rugs','Spa','Lap','Gru'], - dayNames: ['sekmadienis','pirmadienis','antradienis','treÄiadienis','ketvirtadienis','penktadienis','Å¡eÅ¡tadienis'], - dayNamesShort: ['sek','pir','ant','tre','ket','pen','Å¡eÅ¡'], - dayNamesMin: ['Se','Pr','An','Tr','Ke','Pe','Å e'], - weekHeader: 'Wk', - dateFormat: 'yy-mm-dd', - firstDay: 1, - isRTL: false, - showMonthAfterYear: false, - yearSuffix: ''}; - $.datepicker.setDefaults($.datepicker.regional['lt']); -}); \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3b/3b25f721498253f8de97d3cfe43fa4fbc377c464.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3b/3b25f721498253f8de97d3cfe43fa4fbc377c464.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,170 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::GroupsTest < Redmine::ApiTest::Base + fixtures :users, :groups_users + + def setup + Setting.rest_api_enabled = '1' + end + + test "GET /groups.xml should require authentication" do + get '/groups.xml' + assert_response 401 + end + + test "GET /groups.xml should return groups" do + get '/groups.xml', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'groups' do + assert_select 'group' do + assert_select 'name', :text => 'A Team' + assert_select 'id', :text => '10' + end + end + end + + test "GET /groups.json should require authentication" do + get '/groups.json' + assert_response 401 + end + + test "GET /groups.json should return groups" do + get '/groups.json', {}, credentials('admin') + assert_response :success + assert_equal 'application/json', response.content_type + + json = MultiJson.load(response.body) + groups = json['groups'] + assert_kind_of Array, groups + group = groups.detect {|g| g['name'] == 'A Team'} + assert_not_nil group + assert_equal({'id' => 10, 'name' => 'A Team'}, group) + end + + test "GET /groups/:id.xml should return the group with its users" do + get '/groups/10.xml', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'group' do + assert_select 'name', :text => 'A Team' + assert_select 'id', :text => '10' + end + end + + test "GET /groups/:id.xml should include users if requested" do + get '/groups/10.xml?include=users', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'group' do + assert_select 'users' do + assert_select 'user', Group.find(10).users.count + assert_select 'user[id=8]' + end + end + end + + test "GET /groups/:id.xml include memberships if requested" do + get '/groups/10.xml?include=memberships', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'group' do + assert_select 'memberships' + end + end + + test "POST /groups.xml with valid parameters should create the group" do + assert_difference('Group.count') do + post '/groups.xml', {:group => {:name => 'Test', :user_ids => [2, 3]}}, credentials('admin') + assert_response :created + assert_equal 'application/xml', response.content_type + end + + group = Group.order('id DESC').first + assert_equal 'Test', group.name + assert_equal [2, 3], group.users.map(&:id).sort + + assert_select 'group' do + assert_select 'name', :text => 'Test' + end + end + + test "POST /groups.xml with invalid parameters should return errors" do + assert_no_difference('Group.count') do + post '/groups.xml', {:group => {:name => ''}}, credentials('admin') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', response.content_type + + assert_select 'errors' do + assert_select 'error', :text => /Name can't be blank/ + end + end + + test "PUT /groups/:id.xml with valid parameters should update the group" do + put '/groups/10.xml', {:group => {:name => 'New name', :user_ids => [2, 3]}}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + + group = Group.find(10) + assert_equal 'New name', group.name + assert_equal [2, 3], group.users.map(&:id).sort + end + + test "PUT /groups/:id.xml with invalid parameters should return errors" do + put '/groups/10.xml', {:group => {:name => ''}}, credentials('admin') + assert_response :unprocessable_entity + assert_equal 'application/xml', response.content_type + + assert_select 'errors' do + assert_select 'error', :text => /Name can't be blank/ + end + end + + test "DELETE /groups/:id.xml should delete the group" do + assert_difference 'Group.count', -1 do + delete '/groups/10.xml', {}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + end + end + + test "POST /groups/:id/users.xml should add user to the group" do + assert_difference 'Group.find(10).users.count' do + post '/groups/10/users.xml', {:user_id => 5}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + end + assert_include User.find(5), Group.find(10).users + end + + test "DELETE /groups/:id/users/:user_id.xml should remove user from the group" do + assert_difference 'Group.find(10).users.count', -1 do + delete '/groups/10/users/8.xml', {}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + end + assert_not_include User.find(8), Group.find(10).users + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3b/3b31c4786bae9b8126d2ce8808d4fe7afdaa1e91.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3b/3b31c4786bae9b8126d2ce8808d4fe7afdaa1e91.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,341 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'redmine/scm/adapters/abstract_adapter' +require 'cgi' + +module Redmine + module Scm + module Adapters + class MercurialAdapter < AbstractAdapter + + # Mercurial executable name + HG_BIN = Redmine::Configuration['scm_mercurial_command'] || "hg" + HELPERS_DIR = File.dirname(__FILE__) + "/mercurial" + HG_HELPER_EXT = "#{HELPERS_DIR}/redminehelper.py" + TEMPLATE_NAME = "hg-template" + TEMPLATE_EXTENSION = "tmpl" + + # raised if hg command exited with error, e.g. unknown revision. + class HgCommandAborted < CommandFailed; end + + class << self + def client_command + @@bin ||= HG_BIN + end + + def sq_bin + @@sq_bin ||= shell_quote_command + end + + def client_version + @@client_version ||= (hgversion || []) + end + + def client_available + client_version_above?([1, 2]) + end + + def hgversion + # The hg version is expressed either as a + # release number (eg 0.9.5 or 1.0) or as a revision + # id composed of 12 hexa characters. + theversion = hgversion_from_command_line.dup + if theversion.respond_to?(:force_encoding) + theversion.force_encoding('ASCII-8BIT') + end + if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)}) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def hgversion_from_command_line + shellout("#{sq_bin} --version") { |io| io.read }.to_s + end + + def template_path + @@template_path ||= template_path_for(client_version) + end + + def template_path_for(version) + "#{HELPERS_DIR}/#{TEMPLATE_NAME}-1.0.#{TEMPLATE_EXTENSION}" + end + end + + def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) + super + @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding + end + + def path_encoding + @path_encoding + end + + def info + tip = summary['repository']['tip'] + Info.new(:root_url => CGI.unescape(summary['repository']['root']), + :lastrev => Revision.new(:revision => tip['revision'], + :scmid => tip['node'])) + # rescue HgCommandAborted + rescue Exception => e + logger.error "hg: error during getting info: #{e.message}" + nil + end + + def tags + as_ary(summary['repository']['tag']).map { |e| e['name'] } + end + + # Returns map of {'tag' => 'nodeid', ...} + def tagmap + alist = as_ary(summary['repository']['tag']).map do |e| + e.values_at('name', 'node') + end + Hash[*alist.flatten] + end + + def branches + brs = [] + as_ary(summary['repository']['branch']).each do |e| + br = Branch.new(e['name']) + br.revision = e['revision'] + br.scmid = e['node'] + brs << br + end + brs + end + + # Returns map of {'branch' => 'nodeid', ...} + def branchmap + alist = as_ary(summary['repository']['branch']).map do |e| + e.values_at('name', 'node') + end + Hash[*alist.flatten] + end + + def summary + return @summary if @summary + hg 'rhsummary' do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + @summary = parse_xml(output)['rhsummary'] + rescue + end + end + end + private :summary + + def entries(path=nil, identifier=nil, options={}) + p1 = scm_iconv(@path_encoding, 'UTF-8', path) + manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)), + CGI.escape(without_leading_slash(p1.to_s))) do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + parse_xml(output)['rhmanifest']['repository']['manifest'] + rescue + end + end + path_prefix = path.blank? ? '' : with_trailling_slash(path) + + entries = Entries.new + as_ary(manifest['dir']).each do |e| + n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name'])) + p = "#{path_prefix}#{n}" + entries << Entry.new(:name => n, :path => p, :kind => 'dir') + end + + as_ary(manifest['file']).each do |e| + n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name'])) + p = "#{path_prefix}#{n}" + lr = Revision.new(:revision => e['revision'], :scmid => e['node'], + :identifier => e['node'], + :time => Time.at(e['time'].to_i)) + entries << Entry.new(:name => n, :path => p, :kind => 'file', + :size => e['size'].to_i, :lastrev => lr) + end + + entries + rescue HgCommandAborted + nil # means not found + end + + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + revs = Revisions.new + each_revision(path, identifier_from, identifier_to, options) { |e| revs << e } + revs + end + + # Iterates the revisions by using a template file that + # makes Mercurial produce a xml output. + def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={}) + hg_args = ['log', '--debug', '-C', '--style', self.class.template_path] + hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}" + hg_args << '--limit' << options[:limit] if options[:limit] + hg_args << hgtarget(path) unless path.blank? + log = hg(*hg_args) do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + # Mercurial < 1.5 does not support footer template for '' + parse_xml("#{output}")['log'] + rescue + end + end + as_ary(log['logentry']).each do |le| + cpalist = as_ary(le['paths']['path-copied']).map do |e| + [e['__content__'], e['copyfrom-path']].map do |s| + scm_iconv('UTF-8', @path_encoding, CGI.unescape(s)) + end + end + cpmap = Hash[*cpalist.flatten] + paths = as_ary(le['paths']['path']).map do |e| + p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) ) + {:action => e['action'], + :path => with_leading_slash(p), + :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil), + :from_revision => (cpmap.member?(p) ? le['node'] : nil)} + end.sort { |a, b| a[:path] <=> b[:path] } + parents_ary = [] + as_ary(le['parents']['parent']).map do |par| + parents_ary << par['__content__'] if par['__content__'] != "000000000000" + end + yield Revision.new(:revision => le['revision'], + :scmid => le['node'], + :author => (le['author']['__content__'] rescue ''), + :time => Time.parse(le['date']['__content__']), + :message => le['msg']['__content__'], + :paths => paths, + :parents => parents_ary) + end + self + end + + # Returns list of nodes in the specified branch + def nodes_in_branch(branch, options={}) + hg_args = ['rhlog', '--template', '{node|short}\n', '--rhbranch', CGI.escape(branch)] + hg_args << '--from' << CGI.escape(branch) + hg_args << '--to' << '0' + hg_args << '--limit' << options[:limit] if options[:limit] + hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } } + end + + def diff(path, identifier_from, identifier_to=nil) + hg_args = %w|rhdiff| + if identifier_to + hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from) + else + hg_args << '-c' << hgrev(identifier_from) + end + unless path.blank? + p = scm_iconv(@path_encoding, 'UTF-8', path) + hg_args << CGI.escape(hgtarget(p)) + end + diff = [] + hg *hg_args do |io| + io.each_line do |line| + diff << line + end + end + diff + rescue HgCommandAborted + nil # means not found + end + + def cat(path, identifier=nil) + p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path)) + hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io| + io.binmode + io.read + end + rescue HgCommandAborted + nil # means not found + end + + def annotate(path, identifier=nil) + p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path)) + blame = Annotate.new + hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io| + io.each_line do |line| + line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding) + next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$} + r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3, + :identifier => $3) + blame.add_line($4.rstrip, r) + end + end + blame + rescue HgCommandAborted + # means not found or cannot be annotated + Annotate.new + end + + class Revision < Redmine::Scm::Adapters::Revision + # Returns the readable identifier + def format_identifier + "#{revision}:#{scmid}" + end + end + + # Runs 'hg' command with the given args + def hg(*args, &block) + repo_path = root_url || url + full_args = ['-R', repo_path, '--encoding', 'utf-8'] + full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}" + full_args << '--config' << 'diff.git=false' + full_args += args + ret = shellout( + self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '), + &block + ) + if $? && $?.exitstatus != 0 + raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}" + end + ret + end + private :hg + + # Returns correct revision identifier + def hgrev(identifier, sq=false) + rev = identifier.blank? ? 'tip' : identifier.to_s + rev = shell_quote(rev) if sq + rev + end + private :hgrev + + def hgtarget(path) + path ||= '' + root_url + '/' + without_leading_slash(path) + end + private :hgtarget + + def as_ary(o) + return [] unless o + o.is_a?(Array) ? o : Array[o] + end + private :as_ary + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3b/3b3afc176abbdfdc583cfc581e9a14b0aaec317e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3b/3b3afc176abbdfdc583cfc581e9a14b0aaec317e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,39 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class WorkflowTransition < WorkflowRule + validates_presence_of :new_status + + # Returns workflow transitions count by tracker and role + def self.count_by_tracker_and_role + counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{table_name} WHERE type = 'WorkflowTransition' GROUP BY role_id, tracker_id") + roles = Role.sorted.all + trackers = Tracker.sorted.all + + result = [] + trackers.each do |tracker| + t = [] + roles.each do |role| + row = counts.detect {|c| c['role_id'].to_s == role.id.to_s && c['tracker_id'].to_s == tracker.id.to_s} + t << [role, (row.nil? ? 0 : row['c'].to_i)] + end + result << [tracker, t] + end + + result + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3b/3b9b2d2576b7f54c4c40091aa286f522953a9ec0.svn-base --- a/.svn/pristine/3b/3b9b2d2576b7f54c4c40091aa286f522953a9ec0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ ---- -journal_details_001: - old_value: "1" - property: attr - id: 1 - value: "2" - prop_key: status_id - journal_id: 1 -journal_details_002: - old_value: "40" - property: attr - id: 2 - value: "30" - prop_key: done_ratio - journal_id: 1 -journal_details_003: - old_value: nil - property: attr - id: 3 - value: "6" - prop_key: fixed_version_id - journal_id: 4 -journal_details_004: - old_value: "This word was removed and an other was" - property: attr - id: 4 - value: "This word was and an other was added" - prop_key: description - journal_id: 3 -journal_details_005: - old_value: Old value - property: cf - id: 5 - value: New value - prop_key: 2 - journal_id: 3 -journal_details_006: - old_value: nil - property: attachment - id: 6 - value: 060719210727_picture.jpg - prop_key: 4 - journal_id: 3 diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3b/3bb5e5f9c78e113c7cca95c62e3d7f0c93e77d6c.svn-base --- a/.svn/pristine/3b/3bb5e5f9c78e113c7cca95c62e3d7f0c93e77d6c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1138 +0,0 @@ -html {overflow-y:scroll;} -body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; } - -h1, h2, h3, h4 {font-family: "Trebuchet MS", Verdana, sans-serif;padding: 2px 10px 1px 0px;margin: 0 0 10px 0;} -#content h1, h2, h3, h4 {color: #555;} -h2, .wiki h1 {font-size: 20px;} -h3, .wiki h2 {font-size: 16px;} -h4, .wiki h3 {font-size: 13px;} -h4 {border-bottom: 1px dotted #bbb;} - -/***** Layout *****/ -#wrapper {background: white;} - -#top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;} -#top-menu ul {margin: 0; padding: 0;} -#top-menu li { - float:left; - list-style-type:none; - margin: 0px 0px 0px 0px; - padding: 0px 0px 0px 0px; - white-space:nowrap; -} -#top-menu a {color: #fff; margin-right: 8px; font-weight: bold;} -#top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; } - -#account {float:right;} - -#header {min-height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 20px 6px; position:relative;} -#header a {color:#f8f8f8;} -#header h1 a.ancestor { font-size: 80%; } -#quick-search {float:right;} - -#main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;} -#main-menu ul {margin: 0; padding: 0;} -#main-menu li { - float:left; - list-style-type:none; - margin: 0px 2px 0px 0px; - padding: 0px 0px 0px 0px; - white-space:nowrap; -} -#main-menu li a { - display: block; - color: #fff; - text-decoration: none; - font-weight: bold; - margin: 0; - padding: 4px 10px 4px 10px; -} -#main-menu li a:hover {background:#759FCF; color:#fff;} -#main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;} - -#admin-menu ul {margin: 0; padding: 0;} -#admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;} - -#admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;} -#admin-menu a.projects { background-image: url(../images/projects.png); } -#admin-menu a.users { background-image: url(../images/user.png); } -#admin-menu a.groups { background-image: url(../images/group.png); } -#admin-menu a.roles { background-image: url(../images/database_key.png); } -#admin-menu a.trackers { background-image: url(../images/ticket.png); } -#admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); } -#admin-menu a.workflows { background-image: url(../images/ticket_go.png); } -#admin-menu a.custom_fields { background-image: url(../images/textfield.png); } -#admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); } -#admin-menu a.settings { background-image: url(../images/changeset.png); } -#admin-menu a.plugins { background-image: url(../images/plugin.png); } -#admin-menu a.info { background-image: url(../images/help.png); } -#admin-menu a.server_authentication { background-image: url(../images/server_key.png); } - -#main {background-color:#EEEEEE;} - -#sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;} -* html #sidebar{ width: 22%; } -#sidebar h3{ font-size: 14px; margin-top:14px; color: #666; } -#sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; } -* html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; } -#sidebar .contextual { margin-right: 1em; } - -#content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; } -* html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;} -html>body #content { min-height: 600px; } -* html body #content { height: 600px; } /* IE */ - -#main.nosidebar #sidebar{ display: none; } -#main.nosidebar #content{ width: auto; border-right: 0; } - -#footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;} - -#login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; } -#login-form table td {padding: 6px;} -#login-form label {font-weight: bold;} -#login-form input#username, #login-form input#password { width: 300px; } - -div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;} -div.modal h3.title {display:none;} -div.modal p.buttons {text-align:right; margin-bottom:0;} - -input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; } - -.clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; } - -/***** Links *****/ -a, a:link, a:visited{ color: #169; text-decoration: none; } -a:hover, a:active{ color: #c61a1a; text-decoration: underline;} -a img{ border: 0; } - -a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; } -a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; } -a.user.locked, a.user.locked:link, a.user.locked:visited {color: #999;} - -#sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;} -#sidebar a.selected:hover {text-decoration:none;} -#admin-menu a {line-height:1.7em;} -#admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;} - -a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;} -a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;} - -a#toggle-completed-versions {color:#999;} -/***** Tables *****/ -table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; } -table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; } -table.list td { vertical-align: top; padding-right:10px; } -table.list td.id { width: 2%; text-align: center;} -table.list td.checkbox { width: 15px; padding: 2px 0 0 0; } -table.list td.checkbox input {padding:0px;} -table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; } -table.list td.buttons a { padding-right: 0.6em; } -table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; } - -tr.project td.name a { white-space:nowrap; } -tr.project.closed, tr.project.archived { color: #aaa; } -tr.project.closed a, tr.project.archived a { color: #aaa; } - -tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;} -tr.project.idnt-1 td.name {padding-left: 0.5em;} -tr.project.idnt-2 td.name {padding-left: 2em;} -tr.project.idnt-3 td.name {padding-left: 3.5em;} -tr.project.idnt-4 td.name {padding-left: 5em;} -tr.project.idnt-5 td.name {padding-left: 6.5em;} -tr.project.idnt-6 td.name {padding-left: 8em;} -tr.project.idnt-7 td.name {padding-left: 9.5em;} -tr.project.idnt-8 td.name {padding-left: 11em;} -tr.project.idnt-9 td.name {padding-left: 12.5em;} - -tr.issue { text-align: center; white-space: nowrap; } -tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text, tr.issue td.relations { white-space: normal; } -tr.issue td.subject, tr.issue td.relations { text-align: left; } -tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;} -tr.issue td.relations span {white-space: nowrap;} -table.issues td.description {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;} -table.issues td.description pre {white-space:normal;} - -tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;} -tr.issue.idnt-1 td.subject {padding-left: 0.5em;} -tr.issue.idnt-2 td.subject {padding-left: 2em;} -tr.issue.idnt-3 td.subject {padding-left: 3.5em;} -tr.issue.idnt-4 td.subject {padding-left: 5em;} -tr.issue.idnt-5 td.subject {padding-left: 6.5em;} -tr.issue.idnt-6 td.subject {padding-left: 8em;} -tr.issue.idnt-7 td.subject {padding-left: 9.5em;} -tr.issue.idnt-8 td.subject {padding-left: 11em;} -tr.issue.idnt-9 td.subject {padding-left: 12.5em;} - -tr.entry { border: 1px solid #f8f8f8; } -tr.entry td { white-space: nowrap; } -tr.entry td.filename { width: 30%; } -tr.entry td.filename_no_report { width: 70%; } -tr.entry td.size { text-align: right; font-size: 90%; } -tr.entry td.revision, tr.entry td.author { text-align: center; } -tr.entry td.age { text-align: right; } -tr.entry.file td.filename a { margin-left: 16px; } -tr.entry.file td.filename_no_report a { margin-left: 16px; } - -tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;} -tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);} - -tr.changeset { height: 20px } -tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; } -tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; } -tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;} -tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;} - -table.files tr.file td { text-align: center; } -table.files tr.file td.filename { text-align: left; padding-left: 24px; } -table.files tr.file td.digest { font-size: 80%; } - -table.members td.roles, table.memberships td.roles { width: 45%; } - -tr.message { height: 2.6em; } -tr.message td.subject { padding-left: 20px; } -tr.message td.created_on { white-space: nowrap; } -tr.message td.last_message { font-size: 80%; white-space: nowrap; } -tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; } -tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; } - -tr.version.closed, tr.version.closed a { color: #999; } -tr.version td.name { padding-left: 20px; } -tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; } -tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; } - -tr.user td { width:13%; } -tr.user td.email { width:18%; } -tr.user td { white-space: nowrap; } -tr.user.locked, tr.user.registered { color: #aaa; } -tr.user.locked a, tr.user.registered a { color: #aaa; } - -table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;} - -tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;} - -tr.time-entry { text-align: center; white-space: nowrap; } -tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; } -td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; } -td.hours .hours-dec { font-size: 0.9em; } - -table.plugins td { vertical-align: middle; } -table.plugins td.configure { text-align: right; padding-right: 1em; } -table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; } -table.plugins span.description { display: block; font-size: 0.9em; } -table.plugins span.url { display: block; font-size: 0.9em; } - -table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; } -table.list tbody tr.group span.count {position:relative; top:-1px; color:#fff; font-size:10px; background:#9DB9D5; padding:0px 6px 1px 6px; border-radius:3px; margin-left:4px;} -tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;} -tr.group:hover a.toggle-all { display:inline;} -a.toggle-all:hover {text-decoration:none;} - -table.list tbody tr:hover { background-color:#ffffdd; } -table.list tbody tr.group:hover { background-color:inherit; } -table td {padding:2px;} -table p {margin:0;} -.odd {background-color:#f6f7f8;} -.even {background-color: #fff;} - -a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; } -a.sort.asc { background-image: url(../images/sort_asc.png); } -a.sort.desc { background-image: url(../images/sort_desc.png); } - -table.attributes { width: 100% } -table.attributes th { vertical-align: top; text-align: left; } -table.attributes td { vertical-align: top; } - -table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; } -table.boards td.topic-count, table.boards td.message-count {text-align:center;} -table.boards td.last-message {font-size:80%;} - -table.messages td.author, table.messages td.created_on, table.messages td.reply-count {text-align:center;} - -table.query-columns { - border-collapse: collapse; - border: 0; -} - -table.query-columns td.buttons { - vertical-align: middle; - text-align: center; -} - -td.center {text-align:center;} - -h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; } - -div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; } -div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; } -div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; } -div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; } - -#watchers ul {margin: 0; padding: 0;} -#watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;} -#watchers select {width: 95%; display: block;} -#watchers a.delete {opacity: 0.4;} -#watchers a.delete:hover {opacity: 1;} -#watchers img.gravatar {margin: 0 4px 2px 0;} - -span#watchers_inputs {overflow:auto; display:block;} -span.search_for_watchers {display:block;} -span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;} -span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; } - - -.highlight { background-color: #FCFD8D;} -.highlight.token-1 { background-color: #faa;} -.highlight.token-2 { background-color: #afa;} -.highlight.token-3 { background-color: #aaf;} - -.box{ - padding:6px; - margin-bottom: 10px; - background-color:#f6f6f6; - color:#505050; - line-height:1.5em; - border: 1px solid #e4e4e4; -} - -div.square { - border: 1px solid #999; - float: left; - margin: .3em .4em 0 .4em; - overflow: hidden; - width: .6em; height: .6em; -} -.contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;} -.contextual input, .contextual select {font-size:0.9em;} -.message .contextual { margin-top: 0; } - -.splitcontent {overflow:auto;} -.splitcontentleft{float:left; width:49%;} -.splitcontentright{float:right; width:49%;} -form {display: inline;} -input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;} -fieldset {border: 1px solid #e4e4e4; margin:0;} -legend {color: #484848;} -hr { width: 100%; height: 1px; background: #ccc; border: 0;} -blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;} -blockquote blockquote { margin-left: 0;} -acronym { border-bottom: 1px dotted; cursor: help; } -textarea.wiki-edit {width:99%; resize:vertical;} -li p {margin-top: 0;} -div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;} -p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;} -p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; } -p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; } - -div.issue div.subject div div { padding-left: 16px; } -div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;} -div.issue div.subject>div>p { margin-top: 0.5em; } -div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;} -div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px;} -div.issue .next-prev-links {color:#999;} -div.issue table.attributes th {width:22%;} -div.issue table.attributes td {width:28%;} - -#issue_tree table.issues, #relations table.issues { border: 0; } -#issue_tree td.checkbox, #relations td.checkbox {display:none;} -#relations td.buttons {padding:0;} - -fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; } -fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; } -fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); } - -fieldset#date-range p { margin: 2px 0 2px 0; } -fieldset#filters table { border-collapse: collapse; } -fieldset#filters table td { padding: 0; vertical-align: middle; } -fieldset#filters tr.filter { height: 2.1em; } -fieldset#filters td.field { width:230px; } -fieldset#filters td.operator { width:180px; } -fieldset#filters td.operator select {max-width:170px;} -fieldset#filters td.values { white-space:nowrap; } -fieldset#filters td.values select {min-width:130px;} -fieldset#filters td.values input {height:1em;} -fieldset#filters td.add-filter { text-align: right; vertical-align: top; } - -.toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;} -.buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; } - -div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;} -div#issue-changesets div.changeset { padding: 4px;} -div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; } -div#issue-changesets p { margin-top: 0; margin-bottom: 1em;} - -.journal ul.details img {margin:0 0 -3px 4px;} -div.journal {overflow:auto;} -div.journal.private-notes {border-left:2px solid #d22; padding-left:4px; margin-left:-6px;} - -div#activity dl, #search-results { margin-left: 2em; } -div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; } -div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; } -div#activity dt.me .time { border-bottom: 1px solid #999; } -div#activity dt .time { color: #777; font-size: 80%; } -div#activity dd .description, #search-results dd .description { font-style: italic; } -div#activity span.project:after, #search-results span.project:after { content: " -"; } -div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; } - -#search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; } - -div#search-results-counts {float:right;} -div#search-results-counts ul { margin-top: 0.5em; } -div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; } - -dt.issue { background-image: url(../images/ticket.png); } -dt.issue-edit { background-image: url(../images/ticket_edit.png); } -dt.issue-closed { background-image: url(../images/ticket_checked.png); } -dt.issue-note { background-image: url(../images/ticket_note.png); } -dt.changeset { background-image: url(../images/changeset.png); } -dt.news { background-image: url(../images/news.png); } -dt.message { background-image: url(../images/message.png); } -dt.reply { background-image: url(../images/comments.png); } -dt.wiki-page { background-image: url(../images/wiki_edit.png); } -dt.attachment { background-image: url(../images/attachment.png); } -dt.document { background-image: url(../images/document.png); } -dt.project { background-image: url(../images/projects.png); } -dt.time-entry { background-image: url(../images/time.png); } - -#search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); } - -div#roadmap .related-issues { margin-bottom: 1em; } -div#roadmap .related-issues td.checkbox { display: none; } -div#roadmap .wiki h1:first-child { display: none; } -div#roadmap .wiki h1 { font-size: 120%; } -div#roadmap .wiki h2 { font-size: 110%; } -body.controller-versions.action-show div#roadmap .related-issues {width:70%;} - -div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; } -div#version-summary fieldset { margin-bottom: 1em; } -div#version-summary fieldset.time-tracking table { width:100%; } -div#version-summary th, div#version-summary td.total-hours { text-align: right; } - -table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; } -table#time-report tbody tr.subtotal { font-style: italic; color:#777;} -table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; } -table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;} -table#time-report .hours-dec { font-size: 0.9em; } - -div.wiki-page .contextual a {opacity: 0.4} -div.wiki-page .contextual a:hover {opacity: 1} - -form .attributes select { width: 60%; } -input#issue_subject { width: 99%; } -select#issue_done_ratio { width: 95px; } - -ul.projects {margin:0; padding-left:1em;} -ul.projects ul {padding-left:1.6em;} -ul.projects.root {margin:0; padding:0;} -ul.projects li {list-style-type:none;} - -#projects-index ul.projects ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;} -#projects-index ul.projects li.root {margin-bottom: 1em;} -#projects-index ul.projects li.child {margin-top: 1em;} -#projects-index ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; } -.my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; } - -#notified-projects ul, #tracker_project_ids ul {max-height:250px; overflow-y:auto;} - -#related-issues li img {vertical-align:middle;} - -ul.properties {padding:0; font-size: 0.9em; color: #777;} -ul.properties li {list-style-type:none;} -ul.properties li span {font-style:italic;} - -.total-hours { font-size: 110%; font-weight: bold; } -.total-hours span.hours-int { font-size: 120%; } - -.autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;} -#user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; } - -#workflow_copy_form select { width: 200px; } -table.transitions td.enabled {background: #bfb;} -table.fields_permissions select {font-size:90%} -table.fields_permissions td.readonly {background:#ddd;} -table.fields_permissions td.required {background:#d88;} - -textarea#custom_field_possible_values {width: 99%} -input#content_comments {width: 99%} - -.pagination {font-size: 90%} -p.pagination {margin-top:8px;} - -/***** Tabular forms ******/ -.tabular p{ - margin: 0; - padding: 3px 0 3px 0; - padding-left: 180px; /* width of left column containing the label elements */ - min-height: 1.8em; - clear:left; -} - -html>body .tabular p {overflow:hidden;} - -.tabular label{ - font-weight: bold; - float: left; - text-align: right; - /* width of left column */ - margin-left: -180px; - /* width of labels. Should be smaller than left column to create some right margin */ - width: 175px; -} - -.tabular label.floating{ - font-weight: normal; - margin-left: 0px; - text-align: left; - width: 270px; -} - -.tabular label.block{ - font-weight: normal; - margin-left: 0px !important; - text-align: left; - float: none; - display: block; - width: auto; -} - -.tabular label.inline{ - font-weight: normal; - float:none; - margin-left: 5px !important; - width: auto; -} - -label.no-css { - font-weight: inherit; - float:none; - text-align:left; - margin-left:0px; - width:auto; -} -input#time_entry_comments { width: 90%;} - -#preview fieldset {margin-top: 1em; background: url(../images/draft.png)} - -.tabular.settings p{ padding-left: 300px; } -.tabular.settings label{ margin-left: -300px; width: 295px; } -.tabular.settings textarea { width: 99%; } - -.settings.enabled_scm table {width:100%} -.settings.enabled_scm td.scm_name{ font-weight: bold; } - -fieldset.settings label { display: block; } -fieldset#notified_events .parent { padding-left: 20px; } - -span.required {color: #bb0000;} -.summary {font-style: italic;} - -#attachments_fields input.description {margin-left: 8px; width:340px;} -#attachments_fields span {display:block; white-space:nowrap;} -#attachments_fields img {vertical-align: middle;} - -div.attachments { margin-top: 12px; } -div.attachments p { margin:4px 0 2px 0; } -div.attachments img { vertical-align: middle; } -div.attachments span.author { font-size: 0.9em; color: #888; } - -div.thumbnails {margin-top:0.6em;} -div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;} -div.thumbnails img {margin: 3px;} - -p.other-formats { text-align: right; font-size:0.9em; color: #666; } -.other-formats span + span:before { content: "| "; } - -a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; } - -em.info {font-style:normal;font-size:90%;color:#888;display:block;} -em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;} - -textarea.text_cf {width:90%;} - -/* Project members tab */ -div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% } -div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% } -div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; } -div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; } -div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; } -div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; } - -#users_for_watcher {height: 200px; overflow:auto;} -#users_for_watcher label {display: block;} - -table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; } - -input#principal_search, input#user_search {width:100%} -input#principal_search, input#user_search { - background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px; - border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%; -} -input#principal_search.ajax-loading, input#user_search.ajax-loading { - background-image: url(../images/loading.gif); -} - -* html div#tab-content-members fieldset div { height: 450px; } - -/***** Flash & error messages ****/ -#errorExplanation, div.flash, .nodata, .warning, .conflict { - padding: 4px 4px 4px 30px; - margin-bottom: 12px; - font-size: 1.1em; - border: 2px solid; -} - -div.flash {margin-top: 8px;} - -div.flash.error, #errorExplanation { - background: url(../images/exclamation.png) 8px 50% no-repeat; - background-color: #ffe3e3; - border-color: #dd0000; - color: #880000; -} - -div.flash.notice { - background: url(../images/true.png) 8px 5px no-repeat; - background-color: #dfffdf; - border-color: #9fcf9f; - color: #005f00; -} - -div.flash.warning, .conflict { - background: url(../images/warning.png) 8px 5px no-repeat; - background-color: #FFEBC1; - border-color: #FDBF3B; - color: #A6750C; - text-align: left; -} - -.nodata, .warning { - text-align: center; - background-color: #FFEBC1; - border-color: #FDBF3B; - color: #A6750C; -} - -#errorExplanation ul { font-size: 0.9em;} -#errorExplanation h2, #errorExplanation p { display: none; } - -.conflict-details {font-size:80%;} - -/***** Ajax indicator ******/ -#ajax-indicator { -position: absolute; /* fixed not supported by IE */ -background-color:#eee; -border: 1px solid #bbb; -top:35%; -left:40%; -width:20%; -font-weight:bold; -text-align:center; -padding:0.6em; -z-index:100; -opacity: 0.5; -} - -html>body #ajax-indicator { position: fixed; } - -#ajax-indicator span { -background-position: 0% 40%; -background-repeat: no-repeat; -background-image: url(../images/loading.gif); -padding-left: 26px; -vertical-align: bottom; -} - -/***** Calendar *****/ -table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;} -table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; } -table.cal thead th.week-number {width: auto;} -table.cal tbody tr {height: 100px;} -table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;} -table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;} -table.cal td p.day-num {font-size: 1.1em; text-align:right;} -table.cal td.odd p.day-num {color: #bbb;} -table.cal td.today {background:#ffffdd;} -table.cal td.today p.day-num {font-weight: bold;} -table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;} -table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;} -table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;} -p.cal.legend span {display:block;} - -/***** Tooltips ******/ -.tooltip{position:relative;z-index:24;} -.tooltip:hover{z-index:25;color:#000;} -.tooltip span.tip{display: none; text-align:left;} - -div.tooltip:hover span.tip{ -display:block; -position:absolute; -top:12px; left:24px; width:270px; -border:1px solid #555; -background-color:#fff; -padding: 4px; -font-size: 0.8em; -color:#505050; -} - -img.ui-datepicker-trigger { - cursor: pointer; - vertical-align: middle; - margin-left: 4px; -} - -/***** Progress bar *****/ -table.progress { - border-collapse: collapse; - border-spacing: 0pt; - empty-cells: show; - text-align: center; - float:left; - margin: 1px 6px 1px 0px; -} - -table.progress td { height: 1em; } -table.progress td.closed { background: #BAE0BA none repeat scroll 0%; } -table.progress td.done { background: #D3EDD3 none repeat scroll 0%; } -table.progress td.todo { background: #eee none repeat scroll 0%; } -p.pourcent {font-size: 80%;} -p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;} - -#roadmap table.progress td { height: 1.2em; } -/***** Tabs *****/ -#content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;} -#content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;} -#content .tabs ul li { - float:left; - list-style-type:none; - white-space:nowrap; - margin-right:4px; - background:#fff; - position:relative; - margin-bottom:-1px; -} -#content .tabs ul li a{ - display:block; - font-size: 0.9em; - text-decoration:none; - line-height:1.3em; - padding:4px 6px 4px 6px; - border: 1px solid #ccc; - border-bottom: 1px solid #bbbbbb; - background-color: #f6f6f6; - color:#999; - font-weight:bold; - border-top-left-radius:3px; - border-top-right-radius:3px; -} - -#content .tabs ul li a:hover { - background-color: #ffffdd; - text-decoration:none; -} - -#content .tabs ul li a.selected { - background-color: #fff; - border: 1px solid #bbbbbb; - border-bottom: 1px solid #fff; - color:#444; -} - -#content .tabs ul li a.selected:hover {background-color: #fff;} - -div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; } - -button.tab-left, button.tab-right { - font-size: 0.9em; - cursor: pointer; - height:24px; - border: 1px solid #ccc; - border-bottom: 1px solid #bbbbbb; - position:absolute; - padding:4px; - width: 20px; - bottom: -1px; -} - -button.tab-left { - right: 20px; - background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%; - border-top-left-radius:3px; -} - -button.tab-right { - right: 0; - background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%; - border-top-right-radius:3px; -} - -/***** Diff *****/ -.diff_out { background: #fcc; } -.diff_out span { background: #faa; } -.diff_in { background: #cfc; } -.diff_in span { background: #afa; } - -.text-diff { - padding: 1em; - background-color:#f6f6f6; - color:#505050; - border: 1px solid #e4e4e4; -} - -/***** Wiki *****/ -div.wiki table { - border-collapse: collapse; - margin-bottom: 1em; -} - -div.wiki table, div.wiki td, div.wiki th { - border: 1px solid #bbb; - padding: 4px; -} - -div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;} - -div.wiki .external { - background-position: 0% 60%; - background-repeat: no-repeat; - padding-left: 12px; - background-image: url(../images/external.png); -} - -div.wiki a.new {color: #b73535;} - -div.wiki ul, div.wiki ol {margin-bottom:1em;} - -div.wiki pre { - margin: 1em 1em 1em 1.6em; - padding: 8px; - background-color: #fafafa; - border: 1px solid #e2e2e2; - width:auto; - overflow-x: auto; - overflow-y: hidden; -} - -div.wiki ul.toc { - background-color: #ffffdd; - border: 1px solid #e4e4e4; - padding: 4px; - line-height: 1.2em; - margin-bottom: 12px; - margin-right: 12px; - margin-left: 0; - display: table -} -* html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */ - -div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; } -div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; } -div.wiki ul.toc ul { margin: 0; padding: 0; } -div.wiki ul.toc li {list-style-type:none; margin: 0; font-size:12px;} -div.wiki ul.toc li li {margin-left: 1.5em; font-size:10px;} -div.wiki ul.toc a { - font-size: 0.9em; - font-weight: normal; - text-decoration: none; - color: #606060; -} -div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;} - -a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; } -a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; } -h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; } - -div.wiki img { vertical-align: middle; } - -/***** My page layout *****/ -.block-receiver { - border:1px dashed #c0c0c0; - margin-bottom: 20px; - padding: 15px 0 15px 0; -} - -.mypage-box { - margin:0 0 20px 0; - color:#505050; - line-height:1.5em; -} - -.handle {cursor: move;} - -a.close-icon { - display:block; - margin-top:3px; - overflow:hidden; - width:12px; - height:12px; - background-repeat: no-repeat; - cursor:pointer; - background-image:url('../images/close.png'); -} -a.close-icon:hover {background-image:url('../images/close_hl.png');} - -/***** Gantt chart *****/ -.gantt_hdr { - position:absolute; - top:0; - height:16px; - border-top: 1px solid #c0c0c0; - border-bottom: 1px solid #c0c0c0; - border-right: 1px solid #c0c0c0; - text-align: center; - overflow: hidden; -} - -.gantt_hdr.nwday {background-color:#f1f1f1;} - -.gantt_subjects { font-size: 0.8em; } -.gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; } - -.task { - position: absolute; - height:8px; - font-size:0.8em; - color:#888; - padding:0; - margin:0; - line-height:16px; - white-space:nowrap; -} - -.task.label {width:100%;} -.task.label.project, .task.label.version { font-weight: bold; } - -.task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; } -.task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; } -.task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; } - -.task_todo.parent { background: #888; border: 1px solid #888; height: 3px;} -.task_late.parent, .task_done.parent { height: 3px;} -.task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;} -.task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;} - -.version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;} -.version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;} -.version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;} -.version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; } - -.project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;} -.project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;} -.project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;} -.project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; } - -.version-behind-schedule a, .issue-behind-schedule a {color: #f66914;} -.version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;} - -/***** Icons *****/ -.icon { - background-position: 0% 50%; - background-repeat: no-repeat; - padding-left: 20px; - padding-top: 2px; - padding-bottom: 3px; -} - -.icon-add { background-image: url(../images/add.png); } -.icon-edit { background-image: url(../images/edit.png); } -.icon-copy { background-image: url(../images/copy.png); } -.icon-duplicate { background-image: url(../images/duplicate.png); } -.icon-del { background-image: url(../images/delete.png); } -.icon-move { background-image: url(../images/move.png); } -.icon-save { background-image: url(../images/save.png); } -.icon-cancel { background-image: url(../images/cancel.png); } -.icon-multiple { background-image: url(../images/table_multiple.png); } -.icon-folder { background-image: url(../images/folder.png); } -.open .icon-folder { background-image: url(../images/folder_open.png); } -.icon-package { background-image: url(../images/package.png); } -.icon-user { background-image: url(../images/user.png); } -.icon-projects { background-image: url(../images/projects.png); } -.icon-help { background-image: url(../images/help.png); } -.icon-attachment { background-image: url(../images/attachment.png); } -.icon-history { background-image: url(../images/history.png); } -.icon-time { background-image: url(../images/time.png); } -.icon-time-add { background-image: url(../images/time_add.png); } -.icon-stats { background-image: url(../images/stats.png); } -.icon-warning { background-image: url(../images/warning.png); } -.icon-fav { background-image: url(../images/fav.png); } -.icon-fav-off { background-image: url(../images/fav_off.png); } -.icon-reload { background-image: url(../images/reload.png); } -.icon-lock { background-image: url(../images/locked.png); } -.icon-unlock { background-image: url(../images/unlock.png); } -.icon-checked { background-image: url(../images/true.png); } -.icon-details { background-image: url(../images/zoom_in.png); } -.icon-report { background-image: url(../images/report.png); } -.icon-comment { background-image: url(../images/comment.png); } -.icon-summary { background-image: url(../images/lightning.png); } -.icon-server-authentication { background-image: url(../images/server_key.png); } -.icon-issue { background-image: url(../images/ticket.png); } -.icon-zoom-in { background-image: url(../images/zoom_in.png); } -.icon-zoom-out { background-image: url(../images/zoom_out.png); } -.icon-passwd { background-image: url(../images/textfield_key.png); } -.icon-test { background-image: url(../images/bullet_go.png); } - -.icon-file { background-image: url(../images/files/default.png); } -.icon-file.text-plain { background-image: url(../images/files/text.png); } -.icon-file.text-x-c { background-image: url(../images/files/c.png); } -.icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); } -.icon-file.text-x-java { background-image: url(../images/files/java.png); } -.icon-file.text-x-javascript { background-image: url(../images/files/js.png); } -.icon-file.text-x-php { background-image: url(../images/files/php.png); } -.icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); } -.icon-file.text-xml { background-image: url(../images/files/xml.png); } -.icon-file.text-css { background-image: url(../images/files/css.png); } -.icon-file.text-html { background-image: url(../images/files/html.png); } -.icon-file.image-gif { background-image: url(../images/files/image.png); } -.icon-file.image-jpeg { background-image: url(../images/files/image.png); } -.icon-file.image-png { background-image: url(../images/files/image.png); } -.icon-file.image-tiff { background-image: url(../images/files/image.png); } -.icon-file.application-pdf { background-image: url(../images/files/pdf.png); } -.icon-file.application-zip { background-image: url(../images/files/zip.png); } -.icon-file.application-x-gzip { background-image: url(../images/files/zip.png); } - -img.gravatar { - padding: 2px; - border: solid 1px #d5d5d5; - background: #fff; - vertical-align: middle; -} - -div.issue img.gravatar { - float: left; - margin: 0 6px 0 0; - padding: 5px; -} - -div.issue table img.gravatar { - height: 14px; - width: 14px; - padding: 2px; - float: left; - margin: 0 0.5em 0 0; -} - -h2 img.gravatar {margin: -2px 4px -4px 0;} -h3 img.gravatar {margin: -4px 4px -4px 0;} -h4 img.gravatar {margin: -6px 4px -4px 0;} -td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;} -#activity dt img.gravatar {float: left; margin: 0 1em 1em 0;} -/* Used on 12px Gravatar img tags without the icon background */ -.icon-gravatar {float: left; margin-right: 4px;} - -#activity dt, .journal {clear: left;} - -.journal-link {float: right;} - -h2 img { vertical-align:middle; } - -.hascontextmenu { cursor: context-menu; } - -/************* CodeRay styles *************/ -.syntaxhl div {display: inline;} -.syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;} -.syntaxhl .code pre { overflow: auto } -.syntaxhl .debug { color: white !important; background: blue !important; } - -.syntaxhl .annotation { color:#007 } -.syntaxhl .attribute-name { color:#b48 } -.syntaxhl .attribute-value { color:#700 } -.syntaxhl .binary { color:#509 } -.syntaxhl .char .content { color:#D20 } -.syntaxhl .char .delimiter { color:#710 } -.syntaxhl .char { color:#D20 } -.syntaxhl .class { color:#258; font-weight:bold } -.syntaxhl .class-variable { color:#369 } -.syntaxhl .color { color:#0A0 } -.syntaxhl .comment { color:#385 } -.syntaxhl .comment .char { color:#385 } -.syntaxhl .comment .delimiter { color:#385 } -.syntaxhl .complex { color:#A08 } -.syntaxhl .constant { color:#258; font-weight:bold } -.syntaxhl .decorator { color:#B0B } -.syntaxhl .definition { color:#099; font-weight:bold } -.syntaxhl .delimiter { color:black } -.syntaxhl .directive { color:#088; font-weight:bold } -.syntaxhl .doc { color:#970 } -.syntaxhl .doc-string { color:#D42; font-weight:bold } -.syntaxhl .doctype { color:#34b } -.syntaxhl .entity { color:#800; font-weight:bold } -.syntaxhl .error { color:#F00; background-color:#FAA } -.syntaxhl .escape { color:#666 } -.syntaxhl .exception { color:#C00; font-weight:bold } -.syntaxhl .float { color:#06D } -.syntaxhl .function { color:#06B; font-weight:bold } -.syntaxhl .global-variable { color:#d70 } -.syntaxhl .hex { color:#02b } -.syntaxhl .imaginary { color:#f00 } -.syntaxhl .include { color:#B44; font-weight:bold } -.syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black } -.syntaxhl .inline-delimiter { font-weight: bold; color: #666 } -.syntaxhl .instance-variable { color:#33B } -.syntaxhl .integer { color:#06D } -.syntaxhl .key .char { color: #60f } -.syntaxhl .key .delimiter { color: #404 } -.syntaxhl .key { color: #606 } -.syntaxhl .keyword { color:#939; font-weight:bold } -.syntaxhl .label { color:#970; font-weight:bold } -.syntaxhl .local-variable { color:#963 } -.syntaxhl .namespace { color:#707; font-weight:bold } -.syntaxhl .octal { color:#40E } -.syntaxhl .operator { } -.syntaxhl .predefined { color:#369; font-weight:bold } -.syntaxhl .predefined-constant { color:#069 } -.syntaxhl .predefined-type { color:#0a5; font-weight:bold } -.syntaxhl .preprocessor { color:#579 } -.syntaxhl .pseudo-class { color:#00C; font-weight:bold } -.syntaxhl .regexp .content { color:#808 } -.syntaxhl .regexp .delimiter { color:#404 } -.syntaxhl .regexp .modifier { color:#C2C } -.syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); } -.syntaxhl .reserved { color:#080; font-weight:bold } -.syntaxhl .shell .content { color:#2B2 } -.syntaxhl .shell .delimiter { color:#161 } -.syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); } -.syntaxhl .string .char { color: #46a } -.syntaxhl .string .content { color: #46a } -.syntaxhl .string .delimiter { color: #46a } -.syntaxhl .string .modifier { color: #46a } -.syntaxhl .symbol .content { color:#d33 } -.syntaxhl .symbol .delimiter { color:#d33 } -.syntaxhl .symbol { color:#d33 } -.syntaxhl .tag { color:#070 } -.syntaxhl .type { color:#339; font-weight:bold } -.syntaxhl .value { color: #088; } -.syntaxhl .variable { color:#037 } - -.syntaxhl .insert { background: hsla(120,100%,50%,0.12) } -.syntaxhl .delete { background: hsla(0,100%,50%,0.12) } -.syntaxhl .change { color: #bbf; background: #007; } -.syntaxhl .head { color: #f8f; background: #505 } -.syntaxhl .head .filename { color: white; } - -.syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; } -.syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; } - -.syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold } -.syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold } -.syntaxhl .change .change { color: #88f } -.syntaxhl .head .head { color: #f4f } - -/***** Media print specific styles *****/ -@media print { - #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; } - #main { background: #fff; } - #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;} - #wiki_add_attachment { display:none; } - .hide-when-print { display: none; } - .autoscroll {overflow-x: visible;} - table.list {margin-top:0.5em;} - table.list th, table.list td {border: 1px solid #aaa;} -} - -/* Accessibility specific styles */ -.hidden-for-sighted { - position:absolute; - left:-10000px; - top:auto; - width:1px; - height:1px; - overflow:hidden; -} diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3b/3bcd85a0da76d9d004854a6074056d5f89671907.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3b/3bcd85a0da76d9d004854a6074056d5f89671907.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,167 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class AdminControllerTest < ActionController::TestCase + fixtures :projects, :users, :roles + + def setup + User.current = nil + @request.session[:user_id] = 1 # admin + end + + def test_index + get :index + assert_select 'div.nodata', 0 + end + + def test_index_with_no_configuration_data + delete_configuration_data + get :index + assert_select 'div.nodata' + end + + def test_projects + get :projects + assert_response :success + assert_template 'projects' + assert_not_nil assigns(:projects) + # active projects only + assert_nil assigns(:projects).detect {|u| !u.active?} + end + + def test_projects_with_status_filter + get :projects, :status => 1 + assert_response :success + assert_template 'projects' + assert_not_nil assigns(:projects) + # active projects only + assert_nil assigns(:projects).detect {|u| !u.active?} + end + + def test_projects_with_name_filter + get :projects, :name => 'store', :status => '' + assert_response :success + assert_template 'projects' + projects = assigns(:projects) + assert_not_nil projects + assert_equal 1, projects.size + assert_equal 'OnlineStore', projects.first.name + end + + def test_load_default_configuration_data + delete_configuration_data + post :default_configuration, :lang => 'fr' + assert_response :redirect + assert_nil flash[:error] + assert IssueStatus.find_by_name('Nouveau') + end + + def test_load_default_configuration_data_should_rescue_error + delete_configuration_data + Redmine::DefaultData::Loader.stubs(:load).raises(Exception.new("Something went wrong")) + post :default_configuration, :lang => 'fr' + assert_response :redirect + assert_not_nil flash[:error] + assert_match /Something went wrong/, flash[:error] + end + + def test_test_email + user = User.find(1) + user.pref.no_self_notified = '1' + user.pref.save! + ActionMailer::Base.deliveries.clear + + get :test_email + assert_redirected_to '/settings?tab=notifications' + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + user = User.find(1) + assert_equal [user.mail], mail.bcc + end + + def test_test_email_failure_should_display_the_error + Mailer.stubs(:test_email).raises(Exception, 'Some error message') + get :test_email + assert_redirected_to '/settings?tab=notifications' + assert_match /Some error message/, flash[:error] + end + + def test_no_plugins + Redmine::Plugin.clear + + get :plugins + assert_response :success + assert_template 'plugins' + end + + def test_plugins + # Register a few plugins + Redmine::Plugin.register :foo do + name 'Foo plugin' + author 'John Smith' + description 'This is a test plugin' + version '0.0.1' + settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings' + end + Redmine::Plugin.register :bar do + end + + get :plugins + assert_response :success + assert_template 'plugins' + + assert_select 'tr#plugin-foo' do + assert_select 'td span.name', :text => 'Foo plugin' + assert_select 'td.configure a[href=/settings/plugin/foo]' + end + assert_select 'tr#plugin-bar' do + assert_select 'td span.name', :text => 'Bar' + assert_select 'td.configure a', 0 + end + end + + def test_info + get :info + assert_response :success + assert_template 'info' + end + + def test_admin_menu_plugin_extension + Redmine::MenuManager.map :admin_menu do |menu| + menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test' + end + + get :index + assert_response :success + assert_select 'div#admin-menu a[href=/foo/bar]', :text => 'Test' + + Redmine::MenuManager.map :admin_menu do |menu| + menu.delete :test_admin_menu_plugin_extension + end + end + + private + + def delete_configuration_data + Role.delete_all('builtin = 0') + Tracker.delete_all + IssueStatus.delete_all + Enumeration.delete_all + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3b/3be6fd0d486a6e17b661fab21dd1711cec95c05e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3b/3be6fd0d486a6e17b661fab21dd1711cec95c05e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,19 @@ +

    <%=l(:label_information_plural)%>

    + +

    <%= Redmine::Info.versioned_name %>

    + + +<% @checklist.each do |label, result| %> + + + + +<% end %> +
    <%= l(label) %><%= image_tag((result ? 'true.png' : 'exclamation.png'), + :style => "vertical-align:bottom;") %>
    +
    +
    +
    <%= Redmine::Info.environment %>
    +
    + +<% html_title(l(:label_information_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3c/3c182a61315731239b388b13cfd0c9e853524d29.svn-base --- a/.svn/pristine/3c/3c182a61315731239b388b13cfd0c9e853524d29.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -<% diff = Redmine::UnifiedDiff.new( - diff, :type => diff_type, - :max_lines => Setting.diff_max_lines_displayed.to_i, - :style => diff_style) -%> - -<% diff.each do |table_file| -%> -
    -<% if diff.diff_type == 'sbs' -%> - - - - - - - -<% table_file.each_line do |spacing, line| -%> -<% if spacing -%> - - - -<% end -%> - - - - - - -<% end -%> - -
    - <%= h(Redmine::CodesetUtil.to_utf8_by_setting(table_file.file_name)) %> -
    ......
    <%= line.nb_line_left %> -
    <%= Redmine::CodesetUtil.to_utf8_by_setting(line.html_line_left).html_safe %>
    -
    <%= line.nb_line_right %> -
    <%= Redmine::CodesetUtil.to_utf8_by_setting(line.html_line_right).html_safe %>
    -
    - -<% else -%> - - - - - - - -<% table_file.each_line do |spacing, line| %> -<% if spacing -%> - - - -<% end -%> - - - - - -<% end -%> - -
    - <%= h(Redmine::CodesetUtil.to_utf8_by_setting(table_file.file_name)) %> -
    ......
    <%= line.nb_line_left %><%= line.nb_line_right %> -
    <%= Redmine::CodesetUtil.to_utf8_by_setting(line.html_line).html_safe %>
    -
    -<% end -%> -
    -<% end -%> - -<%= l(:text_diff_truncated) if diff.truncated? %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3c/3c57aa09eea8e28ae469dce0482885ac576762ef.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3c/3c57aa09eea8e28ae469dce0482885ac576762ef.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,47 @@ +<%= form_tag({}) do -%> +<%= hidden_field_tag 'back_url', url_for(params), :id => nil %> +
    + + + + + <% query.inline_columns.each do |column| %> + <%= column_header(column) %> + <% end %> + + + <% previous_group = false %> + + <% issue_list(issues) do |issue, level| -%> + <% if @query.grouped? && (group = @query.group_by_column.value(issue)) != previous_group %> + <% reset_cycle %> + + + + <% previous_group = group %> + <% end %> + "> + + <%= raw query.inline_columns.map {|column| ""}.join %> + + <% @query.block_columns.each do |column| + if (text = column_content(column, issue)) && text.present? -%> + + + + <% end -%> + <% end -%> + <% end -%> + +
    + <%= link_to image_tag('toggle_check.png'), {}, + :onclick => 'toggleIssuesSelection(this); return false;', + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> +
    +   + <%= group.blank? ? l(:label_none) : column_content(@query.group_by_column, issue) %> <%= @issue_count_by_group[group] %> + <%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", + "toggleAllRowGroups(this)", :class => 'toggle-all') %> +
    <%= check_box_tag("ids[]", issue.id, false, :id => nil) %>#{column_content(column, issue)}
    <%= text %>
    +
    +<% end -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3c/3c8754104d5e4e67df7c4def3f7671c87678a154.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3c/3c8754104d5e4e67df7c4def3f7671c87678a154.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,81 @@ +<%= error_messages_for 'query' %> + +
    +
    +<%= hidden_field_tag 'gantt', '1' if params[:gantt] %> + +

    +<%= text_field 'query', 'name', :size => 80 %>

    + +<% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @project) %> +

    + + + <% Role.givable.sorted.each do |role| %> + + <% end %> + + <%= hidden_field_tag 'query[role_ids][]', '' %> +

    +<% end %> + +

    +<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, + :disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %>

    + +
    <%= l(:label_options) %> +

    +<%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', + :onclick => 'if (this.checked) {$("#columns").hide();} else {$("#columns").show();}' %>

    + +

    +<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %>

    + +

    +<%= available_block_columns_tags(@query) %>

    + +<% if params[:gantt] %> +

    + + +

    +<% end %> +
    +
    + +
    <%= l(:label_filter_plural) %> +<%= render :partial => 'queries/filters', :locals => {:query => query}%> +
    + +
    <%= l(:label_sort) %> +<% 3.times do |i| %> +<%= i+1 %>: +<%= label_tag "query_sort_criteria_attribute_" + i.to_s, + l(:description_query_sort_criteria_attribute), :class => "hidden-for-sighted" %> +<%= select_tag("query[sort_criteria][#{i}][]", + options_for_select([[]] + query.available_columns.select(&:sortable?).collect {|column| [column.caption, column.name.to_s]}, @query.sort_criteria_key(i)), + :id => "query_sort_criteria_attribute_" + i.to_s)%> +<%= label_tag "query_sort_criteria_direction_" + i.to_s, + l(:description_query_sort_criteria_direction), :class => "hidden-for-sighted" %> +<%= select_tag("query[sort_criteria][#{i}][]", + options_for_select([[], [l(:label_ascending), 'asc'], [l(:label_descending), 'desc']], @query.sort_criteria_order(i)), + :id => "query_sort_criteria_direction_" + i.to_s) %> +
    +<% end %> +
    + +<%= content_tag 'fieldset', :id => 'columns', :style => (query.has_default_columns? ? 'display:none;' : nil) do %> +<%= l(:field_column_names) %> +<%= render_query_columns_selection(query) %> +<% end %> + +
    + +<%= javascript_tag do %> +$(document).ready(function(){ + $("input[name='query[visibility]']").change(function(){ + var checked = $('#query_visibility_1').is(':checked'); + $("input[name='query[role_ids][]'][type=checkbox]").attr('disabled', !checked); + }).trigger('change'); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3c/3c9d342ac8ea509336a4fbcb76da57ef9ad85e78.svn-base --- a/.svn/pristine/3c/3c9d342ac8ea509336a4fbcb76da57ef9ad85e78.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,253 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../../test_helper', __FILE__) - -class Redmine::MenuManager::MenuHelperTest < ActionView::TestCase - - include Redmine::MenuManager::MenuHelper - include ERB::Util - fixtures :users, :members, :projects, :enabled_modules, :roles, :member_roles - - def setup - setup_with_controller - # Stub the current menu item in the controller - def current_menu_item - :index - end - end - - context "MenuManager#current_menu_item" do - should "be tested" - end - - context "MenuManager#render_main_menu" do - should "be tested" - end - - context "MenuManager#render_menu" do - should "be tested" - end - - context "MenuManager#menu_item_and_children" do - should "be tested" - end - - context "MenuManager#extract_node_details" do - should "be tested" - end - - def test_render_single_menu_node - node = Redmine::MenuManager::MenuItem.new(:testing, '/test', { }) - @output_buffer = render_single_menu_node(node, 'This is a test', node.url, false) - - assert_select("a.testing", "This is a test") - end - - def test_render_menu_node - single_node = Redmine::MenuManager::MenuItem.new(:single_node, '/test', { }) - @output_buffer = render_menu_node(single_node, nil) - - assert_select("li") do - assert_select("a.single-node", "Single node") - end - end - - def test_render_menu_node_with_nested_items - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, '/test', { }) - parent_node << Redmine::MenuManager::MenuItem.new(:child_one_node, '/test', { }) - parent_node << Redmine::MenuManager::MenuItem.new(:child_two_node, '/test', { }) - parent_node << - Redmine::MenuManager::MenuItem.new(:child_three_node, '/test', { }) << - Redmine::MenuManager::MenuItem.new(:child_three_inner_node, '/test', { }) - - @output_buffer = render_menu_node(parent_node, nil) - - assert_select("li") do - assert_select("a.parent-node", "Parent node") - assert_select("ul") do - assert_select("li a.child-one-node", "Child one node") - assert_select("li a.child-two-node", "Child two node") - assert_select("li") do - assert_select("a.child-three-node", "Child three node") - assert_select("ul") do - assert_select("li a.child-three-inner-node", "Child three inner node") - end - end - end - end - - end - - def test_render_menu_node_with_children - User.current = User.find(2) - - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| - children = [] - 3.times do |time| - children << Redmine::MenuManager::MenuItem.new("test_child_#{time}", - {:controller => 'issues', :action => 'index'}, - {}) - end - children - } - }) - @output_buffer = render_menu_node(parent_node, Project.find(1)) - - assert_select("li") do - assert_select("a.parent-node", "Parent node") - assert_select("ul") do - assert_select("li a.test-child-0", "Test child 0") - assert_select("li a.test-child-1", "Test child 1") - assert_select("li a.test-child-2", "Test child 2") - end - end - end - - def test_render_menu_node_with_nested_items_and_children - User.current = User.find(2) - - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| - children = [] - 3.times do |time| - children << Redmine::MenuManager::MenuItem.new("test_child_#{time}", {:controller => 'issues', :action => 'index'}, {}) - end - children - } - }) - - parent_node << Redmine::MenuManager::MenuItem.new(:child_node, - '/test', - { - :children => Proc.new {|p| - children = [] - 6.times do |time| - children << Redmine::MenuManager::MenuItem.new("test_dynamic_child_#{time}", {:controller => 'issues', :action => 'index'}, {}) - end - children - } - }) - - @output_buffer = render_menu_node(parent_node, Project.find(1)) - - assert_select("li") do - assert_select("a.parent-node", "Parent node") - assert_select("ul") do - assert_select("li a.child-node", "Child node") - assert_select("ul") do - assert_select("li a.test-dynamic-child-0", "Test dynamic child 0") - assert_select("li a.test-dynamic-child-1", "Test dynamic child 1") - assert_select("li a.test-dynamic-child-2", "Test dynamic child 2") - assert_select("li a.test-dynamic-child-3", "Test dynamic child 3") - assert_select("li a.test-dynamic-child-4", "Test dynamic child 4") - assert_select("li a.test-dynamic-child-5", "Test dynamic child 5") - end - assert_select("li a.test-child-0", "Test child 0") - assert_select("li a.test-child-1", "Test child 1") - assert_select("li a.test-child-2", "Test child 2") - end - end - end - - def test_render_menu_node_with_children_without_an_array - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| Redmine::MenuManager::MenuItem.new("test_child", "/testing", {})} - }) - - assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do - @output_buffer = render_menu_node(parent_node, Project.find(1)) - end - end - - def test_render_menu_node_with_incorrect_children - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| ["a string"] } - }) - - assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do - @output_buffer = render_menu_node(parent_node, Project.find(1)) - end - - end - - def test_menu_items_for_should_yield_all_items_if_passed_a_block - menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, '/', { }) - menu.push(:a_menu_2, '/', { }) - menu.push(:a_menu_3, '/', { }) - end - - items_yielded = [] - menu_items_for(menu_name) do |item| - items_yielded << item - end - - assert_equal 3, items_yielded.size - end - - def test_menu_items_for_should_return_all_items - menu_name = :test_menu_items_for_should_return_all_items - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, '/', { }) - menu.push(:a_menu_2, '/', { }) - menu.push(:a_menu_3, '/', { }) - end - - items = menu_items_for(menu_name) - assert_equal 3, items.size - end - - def test_menu_items_for_should_skip_unallowed_items_on_a_project - menu_name = :test_menu_items_for_should_skip_unallowed_items_on_a_project - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { }) - menu.push(:a_menu_2, {:controller => 'issues', :action => 'index' }, { }) - menu.push(:unallowed, {:controller => 'issues', :action => 'unallowed' }, { }) - end - - User.current = User.find(2) - - items = menu_items_for(menu_name, Project.find(1)) - assert_equal 2, items.size - end - - def test_menu_items_for_should_skip_items_that_fail_the_conditions - menu_name = :test_menu_items_for_should_skip_items_that_fail_the_conditions - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { }) - menu.push(:unallowed, - {:controller => 'issues', :action => 'index' }, - { :if => Proc.new { false }}) - end - - User.current = User.find(2) - - items = menu_items_for(menu_name, Project.find(1)) - assert_equal 1, items.size - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3c/3cbd1a22df619e52f4440d56f4eefc4b5f35d719.svn-base --- a/.svn/pristine/3c/3cbd1a22df619e52f4440d56f4eefc4b5f35d719.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class TimeEntryTest < ActiveSupport::TestCase - fixtures :issues, :projects, :users, :time_entries, - :members, :roles, :member_roles, - :trackers, :issue_statuses, - :projects_trackers, - :journals, :journal_details, - :issue_categories, :enumerations, - :groups_users, - :enabled_modules, - :workflows - - def test_hours_format - assertions = { "2" => 2.0, - "21.1" => 21.1, - "2,1" => 2.1, - "1,5h" => 1.5, - "7:12" => 7.2, - "10h" => 10.0, - "10 h" => 10.0, - "45m" => 0.75, - "45 m" => 0.75, - "3h15" => 3.25, - "3h 15" => 3.25, - "3 h 15" => 3.25, - "3 h 15m" => 3.25, - "3 h 15 m" => 3.25, - "3 hours" => 3.0, - "12min" => 0.2, - "12 Min" => 0.2, - } - - assertions.each do |k, v| - t = TimeEntry.new(:hours => k) - assert_equal v, t.hours, "Converting #{k} failed:" - end - end - - def test_hours_should_default_to_nil - assert_nil TimeEntry.new.hours - end - - def test_spent_on_with_blank - c = TimeEntry.new - c.spent_on = '' - assert_nil c.spent_on - end - - def test_spent_on_with_nil - c = TimeEntry.new - c.spent_on = nil - assert_nil c.spent_on - end - - def test_spent_on_with_string - c = TimeEntry.new - c.spent_on = "2011-01-14" - assert_equal Date.parse("2011-01-14"), c.spent_on - end - - def test_spent_on_with_invalid_string - c = TimeEntry.new - c.spent_on = "foo" - assert_nil c.spent_on - end - - def test_spent_on_with_date - c = TimeEntry.new - c.spent_on = Date.today - assert_equal Date.today, c.spent_on - end - - def test_spent_on_with_time - c = TimeEntry.new - c.spent_on = Time.now - assert_equal Date.today, c.spent_on - end - - def test_validate_time_entry - anon = User.anonymous - project = Project.find(1) - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1, - :priority => IssuePriority.all.first, :subject => 'test_create', - :description => 'IssueTest#test_create', :estimated_hours => '1:30') - assert issue.save - activity = TimeEntryActivity.find_by_name('Design') - te = TimeEntry.create(:spent_on => '2010-01-01', - :hours => 100000, - :issue => issue, - :project => project, - :user => anon, - :activity => activity) - assert_equal 1, te.errors.count - end - - def test_set_project_if_nil - anon = User.anonymous - project = Project.find(1) - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1, - :priority => IssuePriority.all.first, :subject => 'test_create', - :description => 'IssueTest#test_create', :estimated_hours => '1:30') - assert issue.save - activity = TimeEntryActivity.find_by_name('Design') - te = TimeEntry.create(:spent_on => '2010-01-01', - :hours => 10, - :issue => issue, - :user => anon, - :activity => activity) - assert_equal project.id, te.project.id - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3c/3cf05aeb2dd4e16e8ca1c088225e0f9cb575e497.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3c/3cf05aeb2dd4e16e8ca1c088225e0f9cb575e497.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1103 @@ +ar: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: rtl + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%m/%d/%Y" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [الاحد, الاثنين, الثلاثاء, الاربعاء, الخميس, الجمعة, السبت] + abbr_day_names: [أح, اث, Ø«, ار, Ø®, ج, س] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, كانون الثاني, شباط, آذار, نيسان, أيار, حزيران, تموز, آب, أيلول, تشرين الأول, تشرين الثاني, كانون الأول] + abbr_month_names: [~, كانون الثاني, شباط, آذار, نيسان, أيار, حزيران, تموز, آب, أيلول, تشرين الأول, تشرين الثاني, كانون الأول] + # Used in date_select and datime_select. + order: + - :السنة + - :الشهر + - :اليوم + + time: + formats: + default: "%m/%d/%Y %I:%M %p" + time: "%I:%M %p" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "صباحا" + pm: "مساءاً" + + datetime: + distance_in_words: + half_a_minute: "نص٠دقيقة" + less_than_x_seconds: + one: "أقل من ثانية" + other: "ثواني %{count}أقل من " + x_seconds: + one: "ثانية" + other: "%{count}ثواني " + less_than_x_minutes: + one: "أقل من دقيقة" + other: "دقائق%{count}أقل من " + x_minutes: + one: "دقيقة" + other: "%{count} دقائق" + about_x_hours: + one: "حوالي ساعة" + other: "ساعات %{count}حوالي " + x_hours: + one: "%{count} ساعة" + other: "%{count} ساعات" + x_days: + one: "يوم" + other: "%{count} أيام" + about_x_months: + one: "حوالي شهر" + other: "أشهر %{count} حوالي" + x_months: + one: "شهر" + other: "%{count} أشهر" + about_x_years: + one: "حوالي سنة" + other: "سنوات %{count}حوالي " + over_x_years: + one: "اكثر من سنة" + other: "سنوات %{count}أكثر من " + almost_x_years: + one: "تقريبا سنة" + other: "سنوات %{count} نقريبا" + number: + format: + separator: "." + delimiter: "" + precision: 3 + + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "Ùˆ" + skip_last_comma: خطأ + + activerecord: + errors: + template: + header: + one: " %{model} خطأ يمنع تخزين" + other: " %{model} يمنع تخزين%{count}خطأ رقم " + messages: + inclusion: "غير مدرجة على القائمة" + exclusion: "محجوز" + invalid: "غير صالح" + confirmation: "غير متطابق" + accepted: "مقبولة" + empty: "لا يمكن ان تكون ÙØ§Ø±ØºØ©" + blank: "لا يمكن ان تكون ÙØ§Ø±ØºØ©" + too_long: " %{count} طويلة جدا، الحد الاقصى هو " + too_short: " %{count} قصيرة جدا، الحد الادنى هو " + wrong_length: " %{count} خطأ ÙÙŠ الطول، يجب ان يكون " + taken: "لقد اتخذت سابقا" + not_a_number: "ليس رقما" + not_a_date: "ليس تاريخا صالحا" + greater_than: "%{count}يجب ان تكون اكثر من " + greater_than_or_equal_to: "%{count} يجب ان تكون اكثر من او تساوي" + equal_to: "%{count}يجب ان تساوي" + less_than: " %{count}يجب ان تكون اقل من" + less_than_or_equal_to: " %{count} يجب ان تكون اقل من او تساوي" + odd: "must be odd" + even: "must be even" + greater_than_start_date: "يجب ان تكون اكثر من تاريخ البداية" + not_same_project: "لا ينتمي الى Ù†ÙØ³ المشروع" + circular_dependency: "هذه العلاقة سو٠تخلق علاقة تبعية دائرية" + cant_link_an_issue_with_a_descendant: "لا يمكن ان تكون المشكلة مرتبطة بواحدة من المهام Ø§Ù„ÙØ±Ø¹ÙŠØ©" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: الرجاء التحديد + + general_text_No: 'لا' + general_text_Yes: 'نعم' + general_text_no: 'لا' + general_text_yes: 'نعم' + general_lang_name: 'Arabic (عربي)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_updated: لقد تم تجديد الحساب بنجاح. + notice_account_invalid_creditentials: اسم المستخدم او كلمة المرور غير صحيحة + notice_account_password_updated: لقد تم تجديد كلمة المرور بنجاح. + notice_account_wrong_password: كلمة المرور غير صحيحة + notice_account_register_done: لقد تم انشاء حسابك بنجاح، الرجاء تأكيد الطلب من البريد الالكتروني + notice_account_unknown_email: مستخدم غير معروÙ. + notice_can_t_change_password: هذا الحساب يستخدم جهاز خارجي غير مصرح به لا يمكن تغير كلمة المرور + notice_account_lost_email_sent: لقد تم ارسال رسالة على بريدك بالتعليمات اللازمة لتغير كلمة المرور + notice_account_activated: لقد تم ØªÙØ¹ÙŠÙ„ حسابك، يمكنك الدخول الان + notice_successful_create: لقد تم الانشاء بنجاح + notice_successful_update: لقد تم التحديث بنجاح + notice_successful_delete: لقد تم الحذ٠بنجاح + notice_successful_connection: لقد تم الربط بنجاح + notice_file_not_found: Ø§Ù„ØµÙØ­Ø© التي تحاول الدخول اليها غير موجوده او تم حذÙها + notice_locking_conflict: تم تحديث البيانات عن طريق مستخدم آخر. + notice_not_authorized: غير مصرح لك الدخول الى هذه المنطقة. + notice_not_authorized_archived_project: المشروع الذي تحاول الدخول اليه تم Ø§Ø±Ø´ÙØªÙ‡ + notice_email_sent: "%{value}تم ارسال رسالة الى " + notice_email_error: " (%{value})لقد حدث خطأ ما اثناء ارسال الرسالة الى " + notice_feeds_access_key_reseted: كلمة الدخول Atomلقد تم تعديل . + notice_api_access_key_reseted: كلمة الدخولAPIلقد تم تعديل . + notice_failed_to_save_issues: "ÙØ´Ù„ ÙÙŠ Ø­ÙØ¸ الملÙ" + notice_failed_to_save_members: "ÙØ´Ù„ ÙÙŠ Ø­ÙØ¸ الاعضاء: %{errors}." + notice_no_issue_selected: "لم يتم تحديد شيء، الرجاء تحديد المسألة التي تريد" + notice_account_pending: "لقد تم انشاء حسابك، الرجاء الانتظار حتى تتم المواÙقة" + notice_default_data_loaded: تم تحميل التكوين Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ Ø¨Ù†Ø¬Ø§Ø­ + notice_unable_delete_version: غير قادر على مسح النسخة. + notice_unable_delete_time_entry: غير قادر على مسح وقت الدخول. + notice_issue_done_ratios_updated: لقد تم تحديث النسب. + notice_gantt_chart_truncated: " (%{max})لقد تم اقتطاع الرسم البياني لانه تجاوز الاحد الاقصى لعدد العناصر المسموح عرضها " + notice_issue_successful_create: "%{id}لقد تم انشاء " + + + error_can_t_load_default_data: "لم يتم تحميل التكوين Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ ÙƒØ§Ù…Ù„Ø§ %{value}" + error_scm_not_found: "لم يتم العثور على ادخال ÙÙŠ المستودع" + error_scm_command_failed: "حدث خطأ عند محاولة الوصول الى المستودع: %{value}" + error_scm_annotate: "الادخال غير موجود." + error_scm_annotate_big_text_file: "لا يمكن Ø­ÙØ¸ الادخال لانه تجاوز الحد الاقصى لحجم الملÙ." + error_issue_not_found_in_project: 'لم يتم العثور على المخرج او انه ينتمي الى مشروع اخر' + error_no_tracker_in_project: 'لا يوجد انواع بنود عمل لهذا المشروع، الرجاء التحقق من إعدادات المشروع. ' + error_no_default_issue_status: 'لم يتم التعر٠على اي وضع Ø§ÙØªØ±Ø§Ø¶ÙŠØŒ الرجاء التحقق من التكوين الخاص بك (اذهب الى إدارة-إصدار الحالات)' + error_can_not_delete_custom_field: غير قادر على حذ٠الحقل المظلل + error_can_not_delete_tracker: "هذا النوع من بنود العمل يحتوي على بنود نشطة ولا يمكن حذÙÙ‡" + error_can_not_remove_role: "هذا الدور قيد الاستخدام، لا يمكن حذÙÙ‡" + error_can_not_reopen_issue_on_closed_version: 'لا يمكن إعادة ÙØªØ­ بند عمل معين لاصدار مقÙÙ„' + error_can_not_archive_project: لا يمكن Ø§Ø±Ø´ÙØ© هذا المشروع + error_issue_done_ratios_not_updated: "لم يتم تحديث النسب" + error_workflow_copy_source: 'الرجاء اختيار نوع بند العمل او الادوار' + error_workflow_copy_target: 'الرجاء اختيار نوع بند العمل المستهد٠او الادوار Ø§Ù„Ù…Ø³ØªÙ‡Ø¯ÙØ©' + error_unable_delete_issue_status: 'غير قادر على حذ٠حالة بند العمل' + error_unable_to_connect: "تعذر الاتصال(%{value})" + error_attachment_too_big: " (%{max_size})لا يمكن تحميل هذا Ø§Ù„Ù…Ù„ÙØŒ لقد تجاوز الحد الاقصى المسموح به " + warning_attachments_not_saved: "%{count}تعذر Ø­ÙØ¸ الملÙ" + + mail_subject_lost_password: " %{value}كلمة المرور الخاصة بك " + mail_body_lost_password: 'لتغير كلمة المرور، انقر على الروابط التالية:' + mail_subject_register: " %{value}ØªÙØ¹ÙŠÙ„ حسابك " + mail_body_register: 'Ù„ØªÙØ¹ÙŠÙ„ حسابك، انقر على الروابط التالية:' + mail_body_account_information_external: " %{value}اصبح بامكانك استخدام حسابك للدخول" + mail_body_account_information: معلومات حسابك + mail_subject_account_activation_request: "%{value}طلب ØªÙØ¹ÙŠÙ„ الحساب " + mail_body_account_activation_request: " (%{value})تم تسجيل حساب جديد، بانتظار المواÙقة:" + mail_subject_reminder: "%{count}تم تأجيل المهام التالية " + mail_body_reminder: "%{count}يجب ان تقوم بتسليم المهام التالية:" + mail_subject_wiki_content_added: "'%{id}' تم Ø§Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي" + mail_body_wiki_content_added: "The '%{id}' تم Ø§Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي من قبل %{author}." + mail_subject_wiki_content_updated: "'%{id}' تم تحديث ØµÙØ­Ø© ويكي" + mail_body_wiki_content_updated: "The '%{id}'تم تحديث ØµÙØ­Ø© ويكي من قبل %{author}." + + + field_name: الاسم + field_description: الوص٠+ field_summary: الملخص + field_is_required: مطلوب + field_firstname: الاسم الاول + field_lastname: الاسم الاخير + field_mail: البريد الالكتروني + field_filename: اسم المل٠+ field_filesize: حجم المل٠+ field_downloads: التنزيل + field_author: المؤل٠+ field_created_on: تم الانشاء ÙÙŠ + field_updated_on: تم التحديث + field_field_format: تنسيق الحقل + field_is_for_all: لكل المشروعات + field_possible_values: قيم محتملة + field_regexp: التعبير العادي + field_min_length: الحد الادنى للطول + field_max_length: الحد الاعلى للطول + field_value: القيمة + field_category: Ø§Ù„ÙØ¦Ø© + field_title: العنوان + field_project: المشروع + field_issue: بند العمل + field_status: الحالة + field_notes: ملاحظات + field_is_closed: بند العمل مغلق + field_is_default: القيمة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + field_tracker: نوع بند العمل + field_subject: الموضوع + field_due_date: تاريخ النهاية + field_assigned_to: المحال اليه + field_priority: الأولوية + field_fixed_version: الاصدار المستهد٠+ field_user: المستخدم + field_principal: الرئيسي + field_role: دور + field_homepage: Ø§Ù„ØµÙØ­Ø© الرئيسية + field_is_public: عام + field_parent: مشروع ÙØ±Ø¹ÙŠ Ù…Ù† + field_is_in_roadmap: معروض ÙÙŠ خارطة الطريق + field_login: تسجيل الدخول + field_mail_notification: ملاحظات على البريد الالكتروني + field_admin: المدير + field_last_login_on: اخر اتصال + field_language: لغة + field_effective_date: تاريخ + field_password: كلمة المرور + field_new_password: كلمة المرور الجديدة + field_password_confirmation: تأكيد + field_version: إصدار + field_type: نوع + field_host: المضي٠+ field_port: Ø§Ù„Ù…Ù†ÙØ° + field_account: الحساب + field_base_dn: DN قاعدة + field_attr_login: سمة الدخول + field_attr_firstname: سمة الاسم الاول + field_attr_lastname: سمة الاسم الاخير + field_attr_mail: سمة البريد الالكتروني + field_onthefly: إنشاء حساب مستخدم على تحرك + field_start_date: تاريخ البداية + field_done_ratio: "نسبة الانجاز" + field_auth_source: وضع المصادقة + field_hide_mail: Ø¥Ø®ÙØ§Ø¡ بريدي الإلكتروني + field_comments: تعليق + field_url: رابط + field_start_page: ØµÙØ­Ø© البداية + field_subproject: المشروع Ø§Ù„ÙØ±Ø¹ÙŠ + field_hours: ساعات + field_activity: النشاط + field_spent_on: تاريخ + field_identifier: المعر٠+ field_is_filter: استخدم كتصÙية + field_issue_to: بنود العمل المتصلة + field_delay: تأخير + field_assignable: يمكن اسناد بنود العمل الى هذا الدور + field_redirect_existing_links: إعادة توجيه الروابط الموجودة + field_estimated_hours: الوقت المتوقع + field_column_names: أعمدة + field_time_entries: وقت الدخول + field_time_zone: المنطقة الزمنية + field_searchable: يمكن البحث Ùيه + field_default_value: القيمة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + field_comments_sorting: اعرض التعليقات + field_parent_title: ØµÙØ­Ø© الوالدين + field_editable: يمكن اعادة تحريره + field_watcher: مراقب + field_identity_url: Ø§ÙØªØ­ الرابط الخاص بالهوية الشخصية + field_content: المحتويات + field_group_by: تصني٠النتائج بواسطة + field_sharing: مشاركة + field_parent_issue: بند العمل الأصلي + field_member_of_group: "مجموعة المحال" + field_assigned_to_role: "دور المحال" + field_text: حقل نصي + field_visible: غير مرئي + field_warn_on_leaving_unsaved: "الرجاء التحذير عند مغادرة ØµÙØ­Ø© والنص غير محÙوظ" + field_issues_visibility: بنود العمل المرئية + field_is_private: خاص + field_commit_logs_encoding: رسائل الترميز + field_scm_path_encoding: ترميز المسار + field_path_to_repository: مسار المستودع + field_root_directory: دليل الجذر + field_cvsroot: CVSجذر + field_cvs_module: وحدة + + setting_app_title: عنوان التطبيق + setting_app_subtitle: العنوان Ø§Ù„ÙØ±Ø¹ÙŠ Ù„Ù„ØªØ·Ø¨ÙŠÙ‚ + setting_welcome_text: نص الترحيب + setting_default_language: اللغة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + setting_login_required: مطلوب المصادقة + setting_self_registration: التسجيل الذاتي + setting_attachment_max_size: الحد الاقصى Ù„Ù„Ù…Ù„ÙØ§Øª المرÙقة + setting_issues_export_limit: الحد الاقصى لتصدير بنود العمل Ù„Ù…Ù„ÙØ§Øª خارجية + setting_mail_from: انبعاثات عنوان بريدك + setting_bcc_recipients: مستلمين النسخ المخÙية (bcc) + setting_plain_text_mail: نص عادي (no HTML) + setting_host_name: اسم ومسار المستخدم + setting_text_formatting: تنسيق النص + setting_wiki_compression: ضغط تاريخ الويكي + setting_feeds_limit: Atom feeds الحد الاقصى لعدد البنود ÙÙŠ + setting_default_projects_public: المشاريع الجديده متاحة للجميع Ø§ÙØªØ±Ø§Ø¶ÙŠØ§ + setting_autofetch_changesets: الإحضار التلقائي + setting_sys_api_enabled: من ادارة المستودع WS تمكين + setting_commit_ref_keywords: مرجعية الكلمات Ø§Ù„Ù…ÙØªØ§Ø­ÙŠØ© + setting_commit_fix_keywords: تصحيح الكلمات Ø§Ù„Ù…ÙØªØ§Ø­ÙŠØ© + setting_autologin: الدخول التلقائي + setting_date_format: تنسيق التاريخ + setting_time_format: تنسيق الوقت + setting_cross_project_issue_relations: السماح بإدراج بنود العمل ÙÙŠ هذا المشروع + setting_issue_list_default_columns: الاعمدة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© المعروضة ÙÙŠ قائمة بند العمل + setting_repositories_encodings: ترميز المرÙقات والمستودعات + setting_emails_header: رأس رسائل البريد الإلكتروني + setting_emails_footer: ذيل رسائل البريد الإلكتروني + setting_protocol: بروتوكول + setting_per_page_options: الكائنات لكل خيارات Ø§Ù„ØµÙØ­Ø© + setting_user_format: تنسيق عرض المستخدم + setting_activity_days_default: الايام المعروضة على نشاط المشروع + setting_display_subprojects_issues: عرض بنود العمل للمشارع الرئيسية بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ + setting_enabled_scm: SCM تمكين + setting_mail_handler_body_delimiters: "اقتطاع رسائل البريد الإلكتروني بعد هذه الخطوط" + setting_mail_handler_api_enabled: للرسائل الواردةWS تمكين + setting_mail_handler_api_key: API Ù…ÙØªØ§Ø­ + setting_sequential_project_identifiers: انشاء Ù…Ø¹Ø±ÙØ§Øª المشروع المتسلسلة + setting_gravatar_enabled: كأيقونة مستخدمGravatar استخدام + setting_gravatar_default: Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ©Gravatar صورة + setting_diff_max_lines_displayed: الحد الاقصى لعدد الخطوط + setting_file_max_size_displayed: الحد الأقصى لحجم النص المعروض على Ø§Ù„Ù…Ù„ÙØ§Øª المرÙقة + setting_repository_log_display_limit: الحد الاقصى لعدد التنقيحات المعروضة على مل٠السجل + setting_openid: السماح بدخول اسم المستخدم Ø§Ù„Ù…ÙØªÙˆØ­ والتسجيل + setting_password_min_length: الحد الادني لطول كلمة المرور + setting_new_project_user_role_id: الدور المسند الى المستخدم غير المسؤول الذي يقوم بإنشاء المشروع + setting_default_projects_modules: تمكين الوحدات النمطية للمشاريع الجديدة بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ + setting_issue_done_ratio: حساب نسبة بند العمل المنتهية + setting_issue_done_ratio_issue_field: استخدم حقل بند العمل + setting_issue_done_ratio_issue_status: استخدم وضع بند العمل + setting_start_of_week: بدأ التقويم + setting_rest_api_enabled: تمكين باقي خدمات الويب + setting_cache_formatted_text: النص المسبق تنسيقه ÙÙŠ ذاكرة التخزين المؤقت + setting_default_notification_option: خيار الاعلام Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ + setting_commit_logtime_enabled: تميكن وقت الدخول + setting_commit_logtime_activity_id: النشاط ÙÙŠ وقت الدخول + setting_gantt_items_limit: الحد الاقصى لعدد العناصر المعروضة على مخطط جانت + setting_issue_group_assignment: السماح للإحالة الى المجموعات + setting_default_issue_start_date_to_creation_date: استخدام التاريخ الحالي كتاريخ بدأ لبنود العمل الجديدة + + permission_add_project: إنشاء مشروع + permission_add_subprojects: إنشاء مشاريع ÙØ±Ø¹ÙŠØ© + permission_edit_project: تعديل مشروع + permission_select_project_modules: تحديد شكل المشروع + permission_manage_members: إدارة الاعضاء + permission_manage_project_activities: ادارة اصدارات المشروع + permission_manage_versions: ادارة الاصدارات + permission_manage_categories: ادارة انواع بنود العمل + permission_view_issues: عرض بنود العمل + permission_add_issues: Ø§Ø¶Ø§ÙØ© بنود العمل + permission_edit_issues: تعديل بنود العمل + permission_manage_issue_relations: ادارة علاقات بنود العمل + permission_set_issues_private: تعين بنود العمل عامة او خاصة + permission_set_own_issues_private: تعين بنود العمل الخاصة بك كبنود عمل عامة او خاصة + permission_add_issue_notes: Ø§Ø¶Ø§ÙØ© ملاحظات + permission_edit_issue_notes: تعديل ملاحظات + permission_edit_own_issue_notes: تعديل ملاحظاتك + permission_move_issues: تحريك بنود العمل + permission_delete_issues: حذ٠بنود العمل + permission_manage_public_queries: ادارة الاستعلامات العامة + permission_save_queries: Ø­ÙØ¸ الاستعلامات + permission_view_gantt: عرض طريقة"جانت" + permission_view_calendar: عرض التقويم + permission_view_issue_watchers: عرض قائمة المراقبين + permission_add_issue_watchers: Ø§Ø¶Ø§ÙØ© مراقبين + permission_delete_issue_watchers: حذ٠مراقبين + permission_log_time: الوقت المستغرق بالدخول + permission_view_time_entries: عرض الوقت المستغرق + permission_edit_time_entries: تعديل الدخولات الزمنية + permission_edit_own_time_entries: تعديل الدخولات الشخصية + permission_manage_news: ادارة الاخبار + permission_comment_news: اخبار التعليقات + permission_view_documents: عرض المستندات + permission_manage_files: ادارة Ø§Ù„Ù…Ù„ÙØ§Øª + permission_view_files: عرض Ø§Ù„Ù…Ù„ÙØ§Øª + permission_manage_wiki: ادارة ويكي + permission_rename_wiki_pages: اعادة تسمية ØµÙØ­Ø§Øª ويكي + permission_delete_wiki_pages: حذق ØµÙØ­Ø§Øª ويكي + permission_view_wiki_pages: عرض ويكي + permission_view_wiki_edits: عرض تاريخ ويكي + permission_edit_wiki_pages: تعديل ØµÙØ­Ø§Øª ويكي + permission_delete_wiki_pages_attachments: حذ٠المرÙقات + permission_protect_wiki_pages: حماية ØµÙØ­Ø§Øª ويكي + permission_manage_repository: ادارة المستودعات + permission_browse_repository: استعراض المستودعات + permission_view_changesets: عرض طاقم التغيير + permission_commit_access: الوصول + permission_manage_boards: ادارة المنتديات + permission_view_messages: عرض الرسائل + permission_add_messages: نشر الرسائل + permission_edit_messages: تحرير الرسائل + permission_edit_own_messages: تحرير الرسائل الخاصة + permission_delete_messages: حذ٠الرسائل + permission_delete_own_messages: حذ٠الرسائل الخاصة + permission_export_wiki_pages: تصدير ØµÙØ­Ø§Øª ويكي + permission_manage_subtasks: ادارة المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© + + project_module_issue_tracking: تعقب بنود العمل + project_module_time_tracking: التعقب الزمني + project_module_news: الاخبار + project_module_documents: المستندات + project_module_files: Ø§Ù„Ù…Ù„ÙØ§Øª + project_module_wiki: ويكي + project_module_repository: المستودع + project_module_boards: المنتديات + project_module_calendar: التقويم + project_module_gantt: جانت + + label_user: المستخدم + label_user_plural: المستخدمين + label_user_new: مستخدم جديد + label_user_anonymous: مجهول الهوية + label_project: مشروع + label_project_new: مشروع جديد + label_project_plural: مشاريع + label_x_projects: + zero: لا يوجد مشاريع + one: مشروع واحد + other: "%{count} مشاريع" + label_project_all: كل المشاريع + label_project_latest: احدث المشاريع + label_issue: بند عمل + label_issue_new: بند عمل جديد + label_issue_plural: بنود عمل + label_issue_view_all: عرض كل بنود العمل + label_issues_by: " %{value}بند العمل لصحابها" + label_issue_added: تم Ø§Ø¶Ø§ÙØ© بند العمل + label_issue_updated: تم تحديث بند العمل + label_issue_note_added: تم Ø§Ø¶Ø§ÙØ© الملاحظة + label_issue_status_updated: تم تحديث الحالة + label_issue_priority_updated: تم تحديث الاولويات + label_document: مستند + label_document_new: مستند جديد + label_document_plural: مستندات + label_document_added: تم Ø§Ø¶Ø§ÙØ© مستند + label_role: دور + label_role_plural: ادوار + label_role_new: دور جديد + label_role_and_permissions: الأدوار والصلاحيات + label_role_anonymous: مجهول الهوية + label_role_non_member: ليس عضو + label_member: عضو + label_member_new: عضو جديد + label_member_plural: اعضاء + label_tracker: نوع بند عمل + label_tracker_plural: أنواع بنود العمل + label_tracker_new: نوع بند عمل جديد + label_workflow: سير العمل + label_issue_status: حالة بند العمل + label_issue_status_plural: حالات بند العمل + label_issue_status_new: حالة جديدة + label_issue_category: ÙØ¦Ø© بند العمل + label_issue_category_plural: ÙØ¦Ø§Øª بنود العمل + label_issue_category_new: ÙØ¦Ø© جديدة + label_custom_field: تخصيص حقل + label_custom_field_plural: تخصيص حقول + label_custom_field_new: حقل مخصص جديد + label_enumerations: التعدادات + label_enumeration_new: قيمة جديدة + label_information: معلومة + label_information_plural: معلومات + label_please_login: برجى تسجيل الدخول + label_register: تسجيل + label_login_with_open_id_option: او الدخول بهوية Ù…ÙØªÙˆØ­Ø© + label_password_lost: Ùقدت كلمة السر + label_home: "Ø§Ù„ØµÙØ­Ø© الرئيسية" + label_my_page: Ø§Ù„ØµÙØ­Ø© الخاصة بي + label_my_account: حسابي + label_my_projects: مشاريعي الخاصة + label_my_page_block: حجب ØµÙØ­ØªÙŠ Ø§Ù„Ø®Ø§ØµØ© + label_administration: إدارة النظام + label_login: تسجيل الدخول + label_logout: تسجيل الخروج + label_help: مساعدة + label_reported_issues: بنود العمل التي أدخلتها + label_assigned_to_me_issues: بنود العمل المسندة إلي + label_last_login: آخر اتصال + label_registered_on: مسجل على + label_activity: النشاط + label_overall_activity: النشاط العام + label_user_activity: "قيمة النشاط" + label_new: جديدة + label_logged_as: تم تسجيل دخولك + label_environment: البيئة + label_authentication: المصادقة + label_auth_source: وضع المصادقة + label_auth_source_new: وضع مصادقة جديدة + label_auth_source_plural: أوضاع المصادقة + label_subproject_plural: مشاريع ÙØ±Ø¹ÙŠØ© + label_subproject_new: مشروع ÙØ±Ø¹ÙŠ Ø¬Ø¯ÙŠØ¯ + label_and_its_subprojects: "قيمة المشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ© الخاصة بك" + label_min_max_length: الحد الاقصى والادنى للطول + label_list: قائمة + label_date: تاريخ + label_integer: عدد صحيح + label_float: عدد كسري + label_boolean: "نعم/لا" + label_string: نص + label_text: نص طويل + label_attribute: سمة + label_attribute_plural: السمات + label_no_data: لا توجد بيانات للعرض + label_change_status: تغيير الحالة + label_history: التاريخ + label_attachment: المل٠+ label_attachment_new: مل٠جديد + label_attachment_delete: حذ٠المل٠+ label_attachment_plural: Ø§Ù„Ù…Ù„ÙØ§Øª + label_file_added: المل٠المضا٠+ label_report: تقرير + label_report_plural: التقارير + label_news: الأخبار + label_news_new: Ø¥Ø¶Ø§ÙØ© الأخبار + label_news_plural: الأخبار + label_news_latest: آخر الأخبار + label_news_view_all: عرض كل الأخبار + label_news_added: الأخبار Ø§Ù„Ù…Ø¶Ø§ÙØ© + label_news_comment_added: Ø¥Ø¶Ø§ÙØ© التعليقات على أخبار + label_settings: إعدادات + label_overview: لمحة عامة + label_version: الإصدار + label_version_new: الإصدار الجديد + label_version_plural: الإصدارات + label_close_versions: أكملت إغلاق الإصدارات + label_confirmation: تأكيد + label_export_to: 'Ù…ØªÙˆÙØ±Ø© أيضا ÙÙŠ:' + label_read: القراءة... + label_public_projects: المشاريع العامة + label_open_issues: Ù…ÙØªÙˆØ­ + label_open_issues_plural: بنود عمل Ù…ÙØªÙˆØ­Ø© + label_closed_issues: مغلق + label_closed_issues_plural: بنود عمل مغلقة + label_x_open_issues_abbr_on_total: + zero: 0 Ù…ÙØªÙˆØ­ / %{total} + one: 1 Ù…ÙØªÙˆØ­ / %{total} + other: "%{count} Ù…ÙØªÙˆØ­ / %{total}" + label_x_open_issues_abbr: + zero: 0 Ù…ÙØªÙˆØ­ + one: 1 مقتوح + other: "%{count} Ù…ÙØªÙˆØ­" + label_x_closed_issues_abbr: + zero: 0 مغلق + one: 1 مغلق + other: "%{count} مغلق" + label_total: الإجمالي + label_permissions: صلاحيات + label_current_status: الحالة الحالية + label_new_statuses_allowed: يسمح بادراج حالات جديدة + label_all: جميع + label_none: لا شيء + label_nobody: لا أحد + label_next: القادم + label_previous: السابق + label_used_by: التي يستخدمها + label_details: Ø§Ù„ØªÙØ§ØµÙŠÙ„ + label_add_note: Ø¥Ø¶Ø§ÙØ© ملاحظة + label_per_page: كل ØµÙØ­Ø© + label_calendar: التقويم + label_months_from: بعد أشهر من + label_gantt: جانت + label_internal: الداخلية + label_last_changes: "آخر التغييرات %{count}" + label_change_view_all: عرض ÙƒØ§ÙØ© التغييرات + label_personalize_page: تخصيص هذه Ø§Ù„ØµÙØ­Ø© + label_comment: تعليق + label_comment_plural: تعليقات + label_x_comments: + zero: لا يوجد تعليقات + one: تعليق واحد + other: "%{count} تعليقات" + label_comment_add: Ø¥Ø¶Ø§ÙØ© تعليق + label_comment_added: تم Ø¥Ø¶Ø§ÙØ© التعليق + label_comment_delete: حذ٠التعليقات + label_query: استعلام مخصص + label_query_plural: استعلامات مخصصة + label_query_new: استعلام جديد + label_my_queries: استعلاماتي المخصصة + label_filter_add: Ø¥Ø¶Ø§ÙØ© عامل تصÙية + label_filter_plural: عوامل التصÙية + label_equals: يساوي + label_not_equals: لا يساوي + label_in_less_than: أقل من + label_in_more_than: أكثر من + label_greater_or_equal: '>=' + label_less_or_equal: '< =' + label_between: بين + label_in: ÙÙŠ + label_today: اليوم + label_all_time: كل الوقت + label_yesterday: بالأمس + label_this_week: هذا الأسبوع + label_last_week: الأسبوع الماضي + label_last_n_days: "آخر %{count} أيام" + label_this_month: هذا الشهر + label_last_month: الشهر الماضي + label_this_year: هذا العام + label_date_range: نطاق التاريخ + label_less_than_ago: أقل من عدد أيام + label_more_than_ago: أكثر من عدد أيام + label_ago: منذ أيام + label_contains: يحتوي على + label_not_contains: لا يحتوي على + label_day_plural: أيام + label_repository: المستودع + label_repository_plural: المستودعات + label_browse: ØªØµÙØ­ + label_branch: ÙØ±Ø¹ + label_tag: ربط + label_revision: مراجعة + label_revision_plural: تنقيحات + label_revision_id: " %{value}مراجعة" + label_associated_revisions: التنقيحات المرتبطة + label_added: مضا٠+ label_modified: معدل + label_copied: منسوخ + label_renamed: إعادة تسمية + label_deleted: محذو٠+ label_latest_revision: آخر تنقيح + label_latest_revision_plural: أحدث المراجعات + label_view_revisions: عرض التنقيحات + label_view_all_revisions: عرض ÙƒØ§ÙØ© المراجعات + label_max_size: الحد الأقصى للحجم + label_sort_highest: التحرك لأعلى مرتبة + label_sort_higher: تحريك لأعلى + label_sort_lower: تحريك لأسÙÙ„ + label_sort_lowest: التحرك لأسÙÙ„ مرتبة + label_roadmap: خارطة الطريق + label_roadmap_due_in: " %{value}تستحق ÙÙŠ " + label_roadmap_overdue: "%{value}تأخير" + label_roadmap_no_issues: لا يوجد بنود عمل لهذا الإصدار + label_search: البحث + label_result_plural: النتائج + label_all_words: كل الكلمات + label_wiki: ويكي + label_wiki_edit: تحرير ويكي + label_wiki_edit_plural: عمليات تحرير ويكي + label_wiki_page: ØµÙØ­Ø© ويكي + label_wiki_page_plural: ويكي ØµÙØ­Ø§Øª + label_index_by_title: الÙهرس حسب العنوان + label_index_by_date: الÙهرس حسب التاريخ + label_current_version: الإصدار الحالي + label_preview: معاينة + label_feed_plural: موجز ويب + label_changes_details: ØªÙØ§ØµÙŠÙ„ جميع التغييرات + label_issue_tracking: تعقب بنود العمل + label_spent_time: ما تم Ø¥Ù†ÙØ§Ù‚Ù‡ من الوقت + label_overall_spent_time: الوقت الذي تم Ø§Ù†ÙØ§Ù‚Ù‡ كاملا + label_f_hour: "%{value} ساعة" + label_f_hour_plural: "%{value} ساعات" + label_time_tracking: تعقب الوقت + label_change_plural: التغييرات + label_statistics: إحصاءات + label_commits_per_month: يثبت ÙÙŠ الشهر + label_commits_per_author: يثبت لكل مؤل٠+ label_diff: Ø§Ù„Ø§Ø®ØªÙ„Ø§ÙØ§Øª + label_view_diff: عرض Ø§Ù„Ø§Ø®ØªÙ„Ø§ÙØ§Øª + label_diff_inline: مضمنة + label_diff_side_by_side: جنبا إلى جنب + label_options: خيارات + label_copy_workflow_from: نسخ سير العمل من + label_permissions_report: تقرير الصلاحيات + label_watched_issues: بنود العمل المتابعة بريدياً + label_related_issues: بنود العمل ذات الصلة + label_applied_status: الحالة المطبقة + label_loading: تحميل... + label_relation_new: علاقة جديدة + label_relation_delete: حذ٠العلاقة + label_relates_to: ذات علاقة بـ + label_duplicates: مكرر من + label_duplicated_by: مكرر بواسطة + label_blocks: يجب تنÙيذه قبل + label_blocked_by: لا يمكن تنÙيذه إلا بعد + label_precedes: يسبق + label_follows: يتبع + label_end_to_start: نهاية لبدء + label_end_to_end: نهاية لنهاية + label_start_to_start: بدء لبدء + label_start_to_end: بداية لنهاية + label_stay_logged_in: تسجيل الدخول ÙÙŠ + label_disabled: تعطيل + label_show_completed_versions: إظهار الإصدارات الكاملة + label_me: لي + label_board: المنتدى + label_board_new: منتدى جديد + label_board_plural: المنتديات + label_board_locked: تأمين + label_board_sticky: لزجة + label_topic_plural: المواضيع + label_message_plural: رسائل + label_message_last: آخر رسالة + label_message_new: رسالة جديدة + label_message_posted: تم Ø§Ø¶Ø§ÙØ© الرسالة + label_reply_plural: الردود + label_send_information: إرسال معلومات الحساب للمستخدم + label_year: سنة + label_month: شهر + label_week: أسبوع + label_date_from: من + label_date_to: إلى + label_language_based: استناداً إلى لغة المستخدم + label_sort_by: " %{value}الترتيب حسب " + label_send_test_email: ارسل رسالة الكترونية كاختبار + label_feeds_access_key: Atom Ù…ÙØªØ§Ø­ دخول + label_missing_feeds_access_key: Ù…ÙقودAtom Ù…ÙØªØ§Ø­ دخول + label_feeds_access_key_created_on: "Atom تم انشاء Ù…ÙØªØ§Ø­ %{value} منذ" + label_module_plural: الوحدات النمطية + label_added_time_by: " تم Ø§Ø¶Ø§ÙØªÙ‡ من قبل %{author} منذ %{age}" + label_updated_time_by: " تم تحديثه من قبل %{author} منذ %{age}" + label_updated_time: "تم التحديث منذ %{value}" + label_jump_to_a_project: الانتقال إلى مشروع... + label_file_plural: Ø§Ù„Ù…Ù„ÙØ§Øª + label_changeset_plural: اعدادات التغير + label_default_columns: الاعمدة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + label_no_change_option: (لا تغيير) + label_bulk_edit_selected_issues: تحرير بنود العمل المظللة + label_bulk_edit_selected_time_entries: تعديل بنود الأوقات المظللة + label_theme: الموضوع + label_default: Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ + label_search_titles_only: البحث ÙÙŠ العناوين Ùقط + label_user_mail_option_all: "جميع الخيارات" + label_user_mail_option_selected: "الخيارات المظللة Ùقط" + label_user_mail_option_none: "لم يتم تحديد اي خيارات" + label_user_mail_option_only_my_events: "السماح لي Ùقط بمشاهدة الاحداث الخاصة" + label_user_mail_option_only_assigned: "Ùقط الخيارات التي تم تعيينها" + label_user_mail_option_only_owner: "Ùقط للخيارات التي املكها" + label_user_mail_no_self_notified: "لا تريد إعلامك بالتغيرات التي تجريها Ø¨Ù†ÙØ³Ùƒ" + label_registration_activation_by_email: حساب التنشيط عبر البريد الإلكتروني + label_registration_manual_activation: تنشيط الحساب اليدوي + label_registration_automatic_activation: تنشيط الحساب التلقائي + label_display_per_page: "لكل ØµÙØ­Ø©: %{value}" + label_age: العمر + label_change_properties: تغيير الخصائص + label_general: عامة + label_more: أكثر + label_scm: scm + label_plugins: Ø§Ù„Ø¥Ø¶Ø§ÙØ§Øª + label_ldap_authentication: مصادقة LDAP + label_downloads_abbr: تنزيل + label_optional_description: وص٠اختياري + label_add_another_file: Ø¥Ø¶Ø§ÙØ© مل٠آخر + label_preferences: ØªÙØ¶ÙŠÙ„ات + label_chronological_order: ÙÙŠ ترتيب زمني + label_reverse_chronological_order: ÙÙŠ ترتيب زمني عكسي + label_planning: التخطيط + label_incoming_emails: رسائل البريد الإلكتروني الوارد + label_generate_key: إنشاء Ù…ÙØªØ§Ø­ + label_issue_watchers: المراقبون + label_example: مثال + label_display: العرض + label_sort: ÙØ±Ø² + label_ascending: تصاعدي + label_descending: تنازلي + label_date_from_to: من %{start} الى %{end} + label_wiki_content_added: Ø¥Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي + label_wiki_content_updated: تحديث ØµÙØ­Ø© ويكي + label_group: مجموعة + label_group_plural: المجموعات + label_group_new: مجموعة جديدة + label_time_entry_plural: الأوقات المنÙقة + label_version_sharing_none: غير متاح + label_version_sharing_descendants: متاح للمشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ© + label_version_sharing_hierarchy: متاح للتسلسل الهرمي للمشروع + label_version_sharing_tree: مع شجرة المشروع + label_version_sharing_system: مع جميع المشاريع + label_update_issue_done_ratios: تحديث نسبة الأداء لبند العمل + label_copy_source: المصدر + label_copy_target: الهد٠+ label_copy_same_as_target: مطابق للهد٠+ label_display_used_statuses_only: عرض الحالات المستخدمة من قبل هذا النوع من بنود العمل Ùقط + label_api_access_key: Ù…ÙØªØ§Ø­ الوصول إلى API + label_missing_api_access_key: API لم يتم الحصول على Ù…ÙØªØ§Ø­ الوصول + label_api_access_key_created_on: " API إنشاء Ù…ÙØªØ§Ø­ الوصول إلى" + label_profile: المل٠الشخصي + label_subtask_plural: المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© + label_project_copy_notifications: إرسال إشعار الى البريد الإلكتروني عند نسخ المشروع + label_principal_search: "البحث عن مستخدم أو مجموعة:" + label_user_search: "البحث عن المستخدم:" + label_additional_workflow_transitions_for_author: الانتقالات الإضاÙية المسموح بها عند المستخدم صاحب البلاغ + label_additional_workflow_transitions_for_assignee: الانتقالات الإضاÙية المسموح بها عند المستخدم المحال إليه + label_issues_visibility_all: جميع بنود العمل + label_issues_visibility_public: جميع بنود العمل الخاصة + label_issues_visibility_own: بنود العمل التي أنشأها المستخدم + label_git_report_last_commit: اعتماد التقرير الأخير Ù„Ù„Ù…Ù„ÙØ§Øª والدلائل + label_parent_revision: الوالدين + label_child_revision: الطÙÙ„ + label_export_options: "%{export_format} خيارات التصدير" + + button_login: دخول + button_submit: تثبيت + button_save: Ø­ÙØ¸ + button_check_all: تحديد الكل + button_uncheck_all: عدم تحديد الكل + button_collapse_all: تقليص الكل + button_expand_all: عرض الكل + button_delete: حذ٠+ button_create: إنشاء + button_create_and_continue: إنشاء واستمرار + button_test: اختبار + button_edit: تعديل + button_edit_associated_wikipage: "تغير ØµÙØ­Ø© ويكي: %{page_title}" + button_add: Ø¥Ø¶Ø§ÙØ© + button_change: تغيير + button_apply: تطبيق + button_clear: إخلاء الحقول + button_lock: Ù‚ÙÙ„ + button_unlock: الغاء القÙÙ„ + button_download: تنزيل + button_list: قائمة + button_view: عرض + button_move: تحرك + button_move_and_follow: تحرك واتبع + button_back: رجوع + button_cancel: إلغاء + button_activate: تنشيط + button_sort: ترتيب + button_log_time: وقت الدخول + button_rollback: الرجوع الى هذا الاصدار + button_watch: تابع عبر البريد + button_unwatch: إلغاء المتابعة عبر البريد + button_reply: رد + button_archive: Ø£Ø±Ø´ÙØ© + button_unarchive: إلغاء Ø§Ù„Ø£Ø±Ø´ÙØ© + button_reset: إعادة + button_rename: إعادة التسمية + button_change_password: تغير كلمة المرور + button_copy: نسخ + button_copy_and_follow: نسخ واتباع + button_annotate: تعليق + button_update: تحديث + button_configure: تكوين + button_quote: اقتباس + button_duplicate: عمل نسخة + button_show: إظهار + button_edit_section: تعديل هذا الجزء + button_export: تصدير لمل٠+ + status_active: نشيط + status_registered: مسجل + status_locked: مقÙÙ„ + + version_status_open: Ù…ÙØªÙˆØ­ + version_status_locked: مقÙÙ„ + version_status_closed: مغلق + + field_active: ÙØ¹Ø§Ù„ + + text_select_mail_notifications: حدد الامور التي يجب ابلاغك بها عن طريق البريد الالكتروني + text_regexp_info: مثال. ^[A-Z0-9]+$ + text_min_max_length_info: الحد الاقصى والادني لطول المعلومات + text_project_destroy_confirmation: هل أنت متأكد من أنك تريد حذ٠هذا المشروع والبيانات ذات الصلة؟ + text_subprojects_destroy_warning: "المشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ©: %{value} سيتم حذÙها أيضاً." + text_workflow_edit: حدد دوراً Ùˆ نوع بند عمل لتحرير سير العمل + text_are_you_sure: هل أنت متأكد؟ + text_journal_changed: "%{label} تغير %{old} الى %{new}" + text_journal_changed_no_detail: "%{label} تم التحديث" + text_journal_set_to: "%{label} تغير الى %{value}" + text_journal_deleted: "%{label} تم الحذ٠(%{old})" + text_journal_added: "%{label} %{value} تم Ø§Ù„Ø§Ø¶Ø§ÙØ©" + text_tip_issue_begin_day: بند عمل بدأ اليوم + text_tip_issue_end_day: بند عمل انتهى اليوم + text_tip_issue_begin_end_day: بند عمل بدأ وانتهى اليوم + text_caracters_maximum: "%{count} الحد الاقصى." + text_caracters_minimum: "الحد الادنى %{count}" + text_length_between: "الطول %{min} بين %{max} رمز" + text_tracker_no_workflow: لم يتم تحديد سير العمل لهذا النوع من بنود العمل + text_unallowed_characters: رموز غير مسموحة + text_comma_separated: مسموح رموز متنوعة ÙŠÙØµÙ„ها ÙØ§ØµÙ„Ø© . + text_line_separated: مسموح رموز متنوعة ÙŠÙØµÙ„ها سطور + text_issues_ref_in_commit_messages: الارتباط وتغيير حالة بنود العمل ÙÙŠ رسائل تحرير Ø§Ù„Ù…Ù„ÙØ§Øª + text_issue_added: "بند العمل %{id} تم ابلاغها عن طريق %{author}." + text_issue_updated: "بند العمل %{id} تم تحديثها عن طريق %{author}." + text_wiki_destroy_confirmation: هل انت متأكد من رغبتك ÙÙŠ حذ٠هذا الويكي ومحتوياته؟ + text_issue_category_destroy_question: "بعض بنود العمل (%{count}) مرتبطة بهذه Ø§Ù„ÙØ¦Ø©ØŒ ماذا تريد ان ØªÙØ¹Ù„ بها؟" + text_issue_category_destroy_assignments: Ø­Ø°Ù Ø§Ù„ÙØ¦Ø© + text_issue_category_reassign_to: اعادة تثبيت البنود ÙÙŠ Ø§Ù„ÙØ¦Ø© + text_user_mail_option: "بالنسبة للمشاريع غير المحددة، سو٠يتم ابلاغك عن المشاريع التي تشاهدها او تشارك بها Ùقط!" + text_no_configuration_data: "الادوار والمتتبع وحالات بند العمل ومخطط سير العمل لم يتم تحديد وضعها Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ Ø¨Ø¹Ø¯. " + text_load_default_configuration: تحميل الاعدادات Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + text_status_changed_by_changeset: " طبق التغيرات المعينة على %{value}." + text_time_logged_by_changeset: "تم تطبيق التغيرات المعينة على %{value}." + text_issues_destroy_confirmation: هل انت متأكد من حذ٠البنود المظللة؟' + text_issues_destroy_descendants_confirmation: "سو٠يؤدي هذا الى حذ٠%{count} المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© ايضا." + text_time_entries_destroy_confirmation: "هل انت متأكد من رغبتك ÙÙŠ حذ٠الادخالات الزمنية المحددة؟" + text_select_project_modules: قم بتحديد الوضع المناسب لهذا المشروع:' + text_default_administrator_account_changed: تم تعديل الاعدادات Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© لحساب المدير + text_file_repository_writable: المرÙقات قابلة للكتابة + text_plugin_assets_writable: الدليل المساعد قابل للكتابة + text_destroy_time_entries_question: " ساعة على بند العمل التي تود حذÙها، ماذا تريد ان ØªÙØ¹Ù„ØŸ %{hours} تم تثبيت" + text_destroy_time_entries: قم بحذ٠الساعات المسجلة + text_assign_time_entries_to_project: ثبت الساعات المسجلة على التقرير + text_reassign_time_entries: 'اعادة تثبيت الساعات المسجلة لبند العمل هذا:' + text_user_wrote: "%{value} كتب:" + text_enumeration_destroy_question: "%{count} الكائنات المعنية لهذه القيمة" + text_enumeration_category_reassign_to: اعادة تثبيت الكائنات التالية لهذه القيمة:' + text_email_delivery_not_configured: "لم يتم تسليم البريد الالكتروني" + text_diff_truncated: '... لقد تم اقتطاع هذا الجزء لانه تجاوز الحد الاقصى المسموح بعرضه' + text_custom_field_possible_values_info: 'سطر لكل قيمة' + text_wiki_page_nullify_children: "Ø§Ù„Ø§Ø­ØªÙØ§Ø¸ Ø¨ØµÙØ­Ø§Øª الابن ÙƒØµÙØ­Ø§Øª جذر" + text_wiki_page_destroy_children: "Ø­Ø°Ù ØµÙØ­Ø§Øª الابن وجميع أولادهم" + text_wiki_page_reassign_children: "إعادة تعيين ØµÙØ­Ø§Øª تابعة لهذه Ø§Ù„ØµÙØ­Ø© الأصلية" + text_own_membership_delete_confirmation: "انت على وشك إزالة بعض أو ÙƒØ§ÙØ© الصلاحيات الخاصة بك، لن تكون قادراً على تحرير هذا المشروع بعد ذلك. هل أنت متأكد من أنك تريد المتابعة؟" + text_zoom_in: تصغير + text_zoom_out: تكبير + text_warn_on_leaving_unsaved: "Ø§Ù„ØµÙØ­Ø© تحتوي على نص غير مخزن، سو٠يÙقد النص اذا تم الخروج من Ø§Ù„ØµÙØ­Ø©." + text_scm_path_encoding_note: "Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ: UTF-8" + text_git_repository_note: مستودع ÙØ§Ø±Øº ومحلي + text_mercurial_repository_note: مستودع محلي + text_scm_command: امر + text_scm_command_version: اصدار + text_scm_config: الرجاء اعادة تشغيل التطبيق + text_scm_command_not_available: الامر غير Ù…ØªÙˆÙØ±ØŒ الرجاء التحقق من لوحة التحكم + + default_role_manager: مدير + default_role_developer: مطور + default_role_reporter: مراسل + default_tracker_bug: الشوائب + default_tracker_feature: خاصية + default_tracker_support: دعم + default_issue_status_new: جديد + default_issue_status_in_progress: جاري التحميل + default_issue_status_resolved: الحل + default_issue_status_feedback: التغذية الراجعة + default_issue_status_closed: مغلق + default_issue_status_rejected: مرÙوض + default_doc_category_user: مستندات المستخدم + default_doc_category_tech: المستندات التقنية + default_priority_low: قليل + default_priority_normal: عادي + default_priority_high: عالي + default_priority_urgent: طارئ + default_priority_immediate: طارئ الآن + default_activity_design: تصميم + default_activity_development: تطوير + + enumeration_issue_priorities: الاولويات + enumeration_doc_categories: تصني٠المستندات + enumeration_activities: الانشطة + enumeration_system_activity: نشاط النظام + description_filter: Ùلترة + description_search: حقل البحث + description_choose_project: المشاريع + description_project_scope: مجال البحث + description_notes: ملاحظات + description_message_content: محتويات الرسالة + description_query_sort_criteria_attribute: نوع الترتيب + description_query_sort_criteria_direction: اتجاه الترتيب + description_user_mail_notification: إعدادات البريد الالكتروني + description_available_columns: الاعمدة Ø§Ù„Ù…ØªÙˆÙØ±Ø© + description_selected_columns: الاعمدة المحددة + description_all_columns: كل الاعمدة + description_issue_category_reassign: اختر التصني٠+ description_wiki_subpages_reassign: اختر ØµÙØ­Ø© جديدة + description_date_range_list: اختر المجال من القائمة + description_date_range_interval: اختر المدة عن طريق اختيار تاريخ البداية والنهاية + description_date_from: ادخل تاريخ البداية + description_date_to: ادخل تاريخ الانتهاء + text_rmagick_available: RMagick available (optional) + text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? + text_repository_usernames_mapping: |- + Select or update the Redmine user mapped to each username found in the repository log. + Users with the same Redmine and repository username or email are automatically mapped. + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: لا يوجد بنود عمل + one: بند عمل واحد + other: "%{count} بنود عمل" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: منسوخ لـ + label_copied_from: منسوخ من + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: جميع + label_last_n_weeks: آخر %{count} أسبوع/أسابيع + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: متاح للمشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ© + label_cross_project_tree: متاح مع شجرة المشروع + label_cross_project_hierarchy: متاح مع التسلسل الهرمي للمشروع + label_cross_project_system: متاح مع جميع المشاريع + button_hide: Ø¥Ø®ÙØ§Ø¡ + setting_non_working_week_days: "أيام أجازة/راحة أسبوعية" + label_in_the_next_days: ÙÙŠ الأيام المقبلة + label_in_the_past_days: ÙÙŠ الأيام الماضية + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: الإجمالي + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3d0ca7a502303ce3f467223a43161b3d96602b36.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3d/3d0ca7a502303ce3f467223a43161b3d96602b36.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,230 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + + +# DO NOT MODIFY THIS FILE !!! +# Settings can be defined through the application in Admin -> Settings + +app_title: + default: Redmine +app_subtitle: + default: Project management +welcome_text: + default: +login_required: + default: 0 +self_registration: + default: '2' +lost_password: + default: 1 +unsubscribe: + default: 1 +password_min_length: + format: int + default: 8 +# Maximum lifetime of user sessions in minutes +session_lifetime: + format: int + default: 0 +# User session timeout in minutes +session_timeout: + format: int + default: 0 +attachment_max_size: + format: int + default: 5120 +issues_export_limit: + format: int + default: 500 +activity_days_default: + format: int + default: 30 +per_page_options: + default: '25,50,100' +mail_from: + default: redmine@example.net +bcc_recipients: + default: 1 +plain_text_mail: + default: 0 +text_formatting: + default: textile +cache_formatted_text: + default: 0 +wiki_compression: + default: "" +default_language: + default: en +host_name: + default: localhost:3000 +protocol: + default: http +feeds_limit: + format: int + default: 15 +gantt_items_limit: + format: int + default: 500 +# Maximum size of files that can be displayed +# inline through the file viewer (in KB) +file_max_size_displayed: + format: int + default: 512 +diff_max_lines_displayed: + format: int + default: 1500 +enabled_scm: + serialized: true + default: + - Subversion + - Darcs + - Mercurial + - Cvs + - Bazaar + - Git +autofetch_changesets: + default: 1 +sys_api_enabled: + default: 0 +sys_api_key: + default: '' +commit_cross_project_ref: + default: 0 +commit_ref_keywords: + default: 'refs,references,IssueID' +commit_update_keywords: + serialized: true + default: [] +commit_logtime_enabled: + default: 0 +commit_logtime_activity_id: + format: int + default: 0 +# autologin duration in days +# 0 means autologin is disabled +autologin: + format: int + default: 0 +# date format +date_format: + default: '' +time_format: + default: '' +user_format: + default: :firstname_lastname + format: symbol +cross_project_issue_relations: + default: 0 +# Enables subtasks to be in other projects +cross_project_subtasks: + default: 'tree' +issue_group_assignment: + default: 0 +default_issue_start_date_to_creation_date: + default: 1 +notified_events: + serialized: true + default: + - issue_added + - issue_updated +mail_handler_body_delimiters: + default: '' +mail_handler_excluded_filenames: + default: '' +mail_handler_api_enabled: + default: 0 +mail_handler_api_key: + default: +issue_list_default_columns: + serialized: true + default: + - tracker + - status + - priority + - subject + - assigned_to + - updated_on +display_subprojects_issues: + default: 1 +issue_done_ratio: + default: 'issue_field' +default_projects_public: + default: 1 +default_projects_modules: + serialized: true + default: + - issue_tracking + - time_tracking + - news + - documents + - files + - wiki + - repository + - boards + - calendar + - gantt +default_projects_tracker_ids: + serialized: true + default: +# Role given to a non-admin user who creates a project +new_project_user_role_id: + format: int + default: '' +sequential_project_identifiers: + default: 0 +# encodings used to convert repository files content to UTF-8 +# multiple values accepted, comma separated +repositories_encodings: + default: '' +# encoding used to convert commit logs to UTF-8 +commit_logs_encoding: + default: 'UTF-8' +repository_log_display_limit: + format: int + default: 100 +ui_theme: + default: '' +emails_footer: + default: |- + You have received this notification because you have either subscribed to it, or are involved in it. + To change your notification preferences, please click here: http://hostname/my/account +gravatar_enabled: + default: 0 +openid: + default: 0 +gravatar_default: + default: '' +start_of_week: + default: '' +rest_api_enabled: + default: 0 +jsonp_enabled: + default: 0 +default_notification_option: + default: 'only_my_events' +emails_header: + default: '' +thumbnails_enabled: + default: 0 +thumbnails_size: + format: int + default: 100 +non_working_week_days: + serialized: true + default: + - '6' + - '7' diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3d32001255668e3c83c7a368c346637efc3f891b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3d/3d32001255668e3c83c7a368c346637efc3f891b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,182 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module CustomFieldsHelper + + CUSTOM_FIELDS_TABS = [ + {:name => 'IssueCustomField', :partial => 'custom_fields/index', + :label => :label_issue_plural}, + {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', + :label => :label_spent_time}, + {:name => 'ProjectCustomField', :partial => 'custom_fields/index', + :label => :label_project_plural}, + {:name => 'VersionCustomField', :partial => 'custom_fields/index', + :label => :label_version_plural}, + {:name => 'UserCustomField', :partial => 'custom_fields/index', + :label => :label_user_plural}, + {:name => 'GroupCustomField', :partial => 'custom_fields/index', + :label => :label_group_plural}, + {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', + :label => TimeEntryActivity::OptionName}, + {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', + :label => IssuePriority::OptionName}, + {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', + :label => DocumentCategory::OptionName} + ] + + def custom_fields_tabs + CUSTOM_FIELDS_TABS + end + + # Return custom field html tag corresponding to its format + def custom_field_tag(name, custom_value) + custom_field = custom_value.custom_field + field_name = "#{name}[custom_field_values][#{custom_field.id}]" + field_name << "[]" if custom_field.multiple? + field_id = "#{name}_custom_field_values_#{custom_field.id}" + + tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"} + + field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) + case field_format.try(:edit_as) + when "date" + text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) + + calendar_for(field_id) + when "text" + text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3)) + when "bool" + hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options) + when "list" + blank_option = ''.html_safe + unless custom_field.multiple? + if custom_field.is_required? + unless custom_field.default_value.present? + blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '') + end + else + blank_option = content_tag('option') + end + end + s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), + tag_options.merge(:multiple => custom_field.multiple?)) + if custom_field.multiple? + s << hidden_field_tag(field_name, '') + end + s + else + text_field_tag(field_name, custom_value.value, tag_options) + end + end + + # Return custom field label tag + def custom_field_label_tag(name, custom_value, options={}) + required = options[:required] || custom_value.custom_field.is_required? + + content_tag "label", h(custom_value.custom_field.name) + + (required ? " *".html_safe : ""), + :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}" + end + + # Return custom field tag with its label tag + def custom_field_tag_with_label(name, custom_value, options={}) + custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value) + end + + def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil, value='') + field_name = "#{name}[custom_field_values][#{custom_field.id}]" + field_name << "[]" if custom_field.multiple? + field_id = "#{name}_custom_field_values_#{custom_field.id}" + + tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"} + + unset_tag = '' + unless custom_field.is_required? + unset_tag = content_tag('label', + check_box_tag(field_name, '__none__', (value == '__none__'), :id => nil, :data => {:disables => "##{field_id}"}) + l(:button_clear), + :class => 'inline' + ) + end + + field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) + case field_format.try(:edit_as) + when "date" + text_field_tag(field_name, value, tag_options.merge(:size => 10)) + + calendar_for(field_id) + + unset_tag + when "text" + text_area_tag(field_name, value, tag_options.merge(:rows => 3)) + + '
    '.html_safe + + unset_tag + when "bool" + select_tag(field_name, options_for_select([[l(:label_no_change_option), ''], + [l(:general_text_yes), '1'], + [l(:general_text_no), '0']], value), tag_options) + when "list" + options = [] + options << [l(:label_no_change_option), ''] unless custom_field.multiple? + options << [l(:label_none), '__none__'] unless custom_field.is_required? + options += custom_field.possible_values_options(projects) + select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?)) + else + text_field_tag(field_name, value, tag_options) + + unset_tag + end + end + + # Return a string used to display a custom value + def show_value(custom_value) + return "" unless custom_value + format_value(custom_value.value, custom_value.custom_field.field_format) + end + + # Return a string used to display a custom value + def format_value(value, field_format) + if value.is_a?(Array) + value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ') + else + Redmine::CustomFieldFormat.format_value(value, field_format) + end + end + + # Return an array of custom field formats which can be used in select_tag + def custom_field_formats_for_select(custom_field) + Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name) + end + + # Renders the custom_values in api views + def render_api_custom_values(custom_values, api) + api.array :custom_fields do + custom_values.each do |custom_value| + attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name} + attrs.merge!(:multiple => true) if custom_value.custom_field.multiple? + api.custom_field attrs do + if custom_value.value.is_a?(Array) + api.array :value do + custom_value.value.each do |value| + api.value value unless value.blank? + end + end + else + api.value custom_value.value + end + end + end + end unless custom_values.empty? + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3d39eaf1a13ab4e59f2f962003d5f13e35d876e3.svn-base --- a/.svn/pristine/3d/3d39eaf1a13ab4e59f2f962003d5f13e35d876e3.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,347 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class TimelogController < ApplicationController - menu_item :issues - - before_filter :find_project_for_new_time_entry, :only => [:create] - before_filter :find_time_entry, :only => [:show, :edit, :update] - before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy] - before_filter :authorize, :except => [:new, :index, :report] - - before_filter :find_optional_project, :only => [:index, :report] - before_filter :find_optional_project_for_new_time_entry, :only => [:new] - before_filter :authorize_global, :only => [:new, :index, :report] - - accept_rss_auth :index - accept_api_auth :index, :show, :create, :update, :destroy - - helper :sort - include SortHelper - helper :issues - include TimelogHelper - helper :custom_fields - include CustomFieldsHelper - - def index - sort_init 'spent_on', 'desc' - sort_update 'spent_on' => ['spent_on', "#{TimeEntry.table_name}.created_on"], - 'user' => 'user_id', - 'activity' => 'activity_id', - 'project' => "#{Project.table_name}.name", - 'issue' => 'issue_id', - 'hours' => 'hours' - - retrieve_date_range - - scope = TimeEntry.visible.spent_between(@from, @to) - if @issue - scope = scope.on_issue(@issue) - elsif @project - scope = scope.on_project(@project, Setting.display_subprojects_issues?) - end - - respond_to do |format| - format.html { - # Paginate results - @entry_count = scope.count - @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] - @entries = scope.all( - :include => [:project, :activity, :user, {:issue => :tracker}], - :order => sort_clause, - :limit => @entry_pages.items_per_page, - :offset => @entry_pages.current.offset - ) - @total_hours = scope.sum(:hours).to_f - - render :layout => !request.xhr? - } - format.api { - @entry_count = scope.count - @offset, @limit = api_offset_and_limit - @entries = scope.all( - :include => [:project, :activity, :user, {:issue => :tracker}], - :order => sort_clause, - :limit => @limit, - :offset => @offset - ) - } - format.atom { - entries = scope.all( - :include => [:project, :activity, :user, {:issue => :tracker}], - :order => "#{TimeEntry.table_name}.created_on DESC", - :limit => Setting.feeds_limit.to_i - ) - render_feed(entries, :title => l(:label_spent_time)) - } - format.csv { - # Export all entries - @entries = scope.all( - :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], - :order => sort_clause - ) - send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv') - } - end - end - - def report - retrieve_date_range - @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to) - - respond_to do |format| - format.html { render :layout => !request.xhr? } - format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') } - end - end - - def show - respond_to do |format| - # TODO: Implement html response - format.html { render :nothing => true, :status => 406 } - format.api - end - end - - def new - @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) - @time_entry.safe_attributes = params[:time_entry] - end - - def create - @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) - @time_entry.safe_attributes = params[:time_entry] - - call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) - - if @time_entry.save - respond_to do |format| - format.html { - flash[:notice] = l(:notice_successful_create) - if params[:continue] - if params[:project_id] - redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue, - :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, - :back_url => params[:back_url] - else - redirect_to :action => 'new', - :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, - :back_url => params[:back_url] - end - else - redirect_back_or_default :action => 'index', :project_id => @time_entry.project - end - } - format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } - end - else - respond_to do |format| - format.html { render :action => 'new' } - format.api { render_validation_errors(@time_entry) } - end - end - end - - def edit - @time_entry.safe_attributes = params[:time_entry] - end - - def update - @time_entry.safe_attributes = params[:time_entry] - - call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) - - if @time_entry.save - respond_to do |format| - format.html { - flash[:notice] = l(:notice_successful_update) - redirect_back_or_default :action => 'index', :project_id => @time_entry.project - } - format.api { render_api_ok } - end - else - respond_to do |format| - format.html { render :action => 'edit' } - format.api { render_validation_errors(@time_entry) } - end - end - end - - def bulk_edit - @available_activities = TimeEntryActivity.shared.active - @custom_fields = TimeEntry.first.available_custom_fields - end - - def bulk_update - attributes = parse_params_for_bulk_time_entry_attributes(params) - - unsaved_time_entry_ids = [] - @time_entries.each do |time_entry| - time_entry.reload - time_entry.safe_attributes = attributes - call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry }) - unless time_entry.save - # Keep unsaved time_entry ids to display them in flash error - unsaved_time_entry_ids << time_entry.id - end - end - set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids) - redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first}) - end - - def destroy - destroyed = TimeEntry.transaction do - @time_entries.each do |t| - unless t.destroy && t.destroyed? - raise ActiveRecord::Rollback - end - end - end - - respond_to do |format| - format.html { - if destroyed - flash[:notice] = l(:notice_successful_delete) - else - flash[:error] = l(:notice_unable_delete_time_entry) - end - redirect_back_or_default(:action => 'index', :project_id => @projects.first) - } - format.api { - if destroyed - render_api_ok - else - render_validation_errors(@time_entries) - end - } - end - end - -private - def find_time_entry - @time_entry = TimeEntry.find(params[:id]) - unless @time_entry.editable_by?(User.current) - render_403 - return false - end - @project = @time_entry.project - rescue ActiveRecord::RecordNotFound - render_404 - end - - def find_time_entries - @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids]) - raise ActiveRecord::RecordNotFound if @time_entries.empty? - @projects = @time_entries.collect(&:project).compact.uniq - @project = @projects.first if @projects.size == 1 - rescue ActiveRecord::RecordNotFound - render_404 - end - - def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids) - if unsaved_time_entry_ids.empty? - flash[:notice] = l(:notice_successful_update) unless time_entries.empty? - else - flash[:error] = l(:notice_failed_to_save_time_entries, - :count => unsaved_time_entry_ids.size, - :total => time_entries.size, - :ids => '#' + unsaved_time_entry_ids.join(', #')) - end - end - - def find_optional_project_for_new_time_entry - if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present? - @project = Project.find(project_id) - end - if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present? - @issue = Issue.find(issue_id) - @project ||= @issue.project - end - rescue ActiveRecord::RecordNotFound - render_404 - end - - def find_project_for_new_time_entry - find_optional_project_for_new_time_entry - if @project.nil? - render_404 - end - end - - def find_optional_project - if !params[:issue_id].blank? - @issue = Issue.find(params[:issue_id]) - @project = @issue.project - elsif !params[:project_id].blank? - @project = Project.find(params[:project_id]) - end - end - - # Retrieves the date range based on predefined ranges or specific from/to param dates - def retrieve_date_range - @free_period = false - @from, @to = nil, nil - - if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?) - case params[:period].to_s - when 'today' - @from = @to = Date.today - when 'yesterday' - @from = @to = Date.today - 1 - when 'current_week' - @from = Date.today - (Date.today.cwday - 1)%7 - @to = @from + 6 - when 'last_week' - @from = Date.today - 7 - (Date.today.cwday - 1)%7 - @to = @from + 6 - when 'last_2_weeks' - @from = Date.today - 14 - (Date.today.cwday - 1)%7 - @to = @from + 13 - when '7_days' - @from = Date.today - 7 - @to = Date.today - when 'current_month' - @from = Date.civil(Date.today.year, Date.today.month, 1) - @to = (@from >> 1) - 1 - when 'last_month' - @from = Date.civil(Date.today.year, Date.today.month, 1) << 1 - @to = (@from >> 1) - 1 - when '30_days' - @from = Date.today - 30 - @to = Date.today - when 'current_year' - @from = Date.civil(Date.today.year, 1, 1) - @to = Date.civil(Date.today.year, 12, 31) - end - elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?)) - begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end - begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end - @free_period = true - else - # default - end - - @from, @to = @to, @from if @from && @to && @from > @to - end - - def parse_params_for_bulk_time_entry_attributes(params) - attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?} - attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} - attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values] - attributes - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3d3f4dc404ea9324f3dc61ea78926c927911ae38.svn-base --- a/.svn/pristine/3d/3d3f4dc404ea9324f3dc61ea78926c927911ae38.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,156 +0,0 @@ ---- -queries_001: - id: 1 - project_id: 1 - is_public: true - name: Multiple custom fields query - filters: | - --- - cf_1: - :values: - - MySQL - :operator: "=" - status_id: - :values: - - "1" - :operator: o - cf_2: - :values: - - "125" - :operator: "=" - - user_id: 1 - column_names: -queries_002: - id: 2 - project_id: 1 - is_public: false - name: Private query for cookbook - filters: | - --- - tracker_id: - :values: - - "3" - :operator: "=" - status_id: - :values: - - "1" - :operator: o - - user_id: 3 - column_names: -queries_003: - id: 3 - project_id: - is_public: false - name: Private query for all projects - filters: | - --- - tracker_id: - :values: - - "3" - :operator: "=" - - user_id: 3 - column_names: -queries_004: - id: 4 - project_id: - is_public: true - name: Public query for all projects - filters: | - --- - tracker_id: - :values: - - "3" - :operator: "=" - - user_id: 2 - column_names: -queries_005: - id: 5 - project_id: - is_public: true - name: Open issues by priority and tracker - filters: | - --- - status_id: - :values: - - "1" - :operator: o - - user_id: 1 - column_names: - sort_criteria: | - --- - - - priority - - desc - - - tracker - - asc -queries_006: - id: 6 - project_id: - is_public: true - name: Open issues grouped by tracker - filters: | - --- - status_id: - :values: - - "1" - :operator: o - - user_id: 1 - column_names: - group_by: tracker - sort_criteria: | - --- - - - priority - - desc -queries_007: - id: 7 - project_id: 2 - is_public: true - name: Public query for project 2 - filters: | - --- - tracker_id: - :values: - - "3" - :operator: "=" - - user_id: 2 - column_names: -queries_008: - id: 8 - project_id: 2 - is_public: false - name: Private query for project 2 - filters: | - --- - tracker_id: - :values: - - "3" - :operator: "=" - - user_id: 2 - column_names: -queries_009: - id: 9 - project_id: - is_public: true - name: Open issues grouped by list custom field - filters: | - --- - status_id: - :values: - - "1" - :operator: o - - user_id: 1 - column_names: - group_by: cf_1 - sort_criteria: | - --- - - - priority - - desc - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3d5008520dcd5bbad8e7395cb596f99479e7e453.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3d/3d5008520dcd5bbad8e7395cb596f99479e7e453.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class ProjectCustomField < CustomField + def type_name + :label_project_plural + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3d5312afd2b7a1501b0ec17ef249ac149a4a5a3f.svn-base --- a/.svn/pristine/3d/3d5312afd2b7a1501b0ec17ef249ac149a4a5a3f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,383 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'attachments_controller' - -# Re-raise errors caught by the controller. -class AttachmentsController; def rescue_action(e) raise e end; end - -class AttachmentsControllerTest < ActionController::TestCase - fixtures :users, :projects, :roles, :members, :member_roles, - :enabled_modules, :issues, :trackers, :attachments, - :versions, :wiki_pages, :wikis, :documents - - def setup - @controller = AttachmentsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - set_fixtures_attachments_directory - end - - def teardown - set_tmp_attachments_directory - end - - def test_show_diff - ['inline', 'sbs'].each do |dt| - # 060719210727_changeset_utf8.diff - get :show, :id => 14, :type => dt - assert_response :success - assert_template 'diff' - assert_equal 'text/html', @response.content_type - assert_tag 'th', - :attributes => {:class => /filename/}, - :content => /issues_controller.rb\t\(révision 1484\)/ - assert_tag 'td', - :attributes => {:class => /line-code/}, - :content => /Demande créée avec succès/ - end - set_tmp_attachments_directory - end - - def test_show_diff_replcace_cannot_convert_content - with_settings :repositories_encodings => 'UTF-8' do - ['inline', 'sbs'].each do |dt| - # 060719210727_changeset_iso8859-1.diff - get :show, :id => 5, :type => dt - assert_response :success - assert_template 'diff' - assert_equal 'text/html', @response.content_type - assert_tag 'th', - :attributes => {:class => "filename"}, - :content => /issues_controller.rb\t\(r\?vision 1484\)/ - assert_tag 'td', - :attributes => {:class => /line-code/}, - :content => /Demande cr\?\?e avec succ\?s/ - end - end - set_tmp_attachments_directory - end - - def test_show_diff_latin_1 - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - ['inline', 'sbs'].each do |dt| - # 060719210727_changeset_iso8859-1.diff - get :show, :id => 5, :type => dt - assert_response :success - assert_template 'diff' - assert_equal 'text/html', @response.content_type - assert_tag 'th', - :attributes => {:class => "filename"}, - :content => /issues_controller.rb\t\(révision 1484\)/ - assert_tag 'td', - :attributes => {:class => /line-code/}, - :content => /Demande créée avec succès/ - end - end - set_tmp_attachments_directory - end - - def test_save_diff_type - user1 = User.find(1) - user1.pref[:diff_type] = nil - user1.preference.save - user = User.find(1) - assert_nil user.pref[:diff_type] - - @request.session[:user_id] = 1 # admin - get :show, :id => 5 - assert_response :success - assert_template 'diff' - user.reload - assert_equal "inline", user.pref[:diff_type] - get :show, :id => 5, :type => 'sbs' - assert_response :success - assert_template 'diff' - user.reload - assert_equal "sbs", user.pref[:diff_type] - end - - def test_diff_show_filename_in_mercurial_export - set_tmp_attachments_directory - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("hg-export.diff", "text/plain"), - :author => User.find(1)) - assert a.save - assert_equal 'hg-export.diff', a.filename - - get :show, :id => a.id, :type => 'inline' - assert_response :success - assert_template 'diff' - assert_equal 'text/html', @response.content_type - assert_select 'th.filename', :text => 'test1.txt' - end - - def test_show_text_file - get :show, :id => 4 - assert_response :success - assert_template 'file' - assert_equal 'text/html', @response.content_type - set_tmp_attachments_directory - end - - def test_show_text_file_utf_8 - set_tmp_attachments_directory - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("japanese-utf-8.txt", "text/plain"), - :author => User.find(1)) - assert a.save - assert_equal 'japanese-utf-8.txt', a.filename - - str_japanese = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" - str_japanese.force_encoding('UTF-8') if str_japanese.respond_to?(:force_encoding) - - get :show, :id => a.id - assert_response :success - assert_template 'file' - assert_equal 'text/html', @response.content_type - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', :content => /#{str_japanese}/ } - end - - def test_show_text_file_replcace_cannot_convert_content - set_tmp_attachments_directory - with_settings :repositories_encodings => 'UTF-8' do - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("iso8859-1.txt", "text/plain"), - :author => User.find(1)) - assert a.save - assert_equal 'iso8859-1.txt', a.filename - - get :show, :id => a.id - assert_response :success - assert_template 'file' - assert_equal 'text/html', @response.content_type - assert_tag :tag => 'th', - :content => '7', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', :content => /Demande cr\?\?e avec succ\?s/ } - end - end - - def test_show_text_file_latin_1 - set_tmp_attachments_directory - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("iso8859-1.txt", "text/plain"), - :author => User.find(1)) - assert a.save - assert_equal 'iso8859-1.txt', a.filename - - get :show, :id => a.id - assert_response :success - assert_template 'file' - assert_equal 'text/html', @response.content_type - assert_tag :tag => 'th', - :content => '7', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', :content => /Demande créée avec succès/ } - end - end - - def test_show_text_file_should_send_if_too_big - Setting.file_max_size_displayed = 512 - Attachment.find(4).update_attribute :filesize, 754.kilobyte - - get :show, :id => 4 - assert_response :success - assert_equal 'application/x-ruby', @response.content_type - set_tmp_attachments_directory - end - - def test_show_other - get :show, :id => 6 - assert_response :success - assert_equal 'application/octet-stream', @response.content_type - set_tmp_attachments_directory - end - - def test_show_file_from_private_issue_without_permission - get :show, :id => 15 - assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2F15' - set_tmp_attachments_directory - end - - def test_show_file_from_private_issue_with_permission - @request.session[:user_id] = 2 - get :show, :id => 15 - assert_response :success - assert_tag 'h2', :content => /private.diff/ - set_tmp_attachments_directory - end - - def test_show_file_without_container_should_be_denied - set_tmp_attachments_directory - attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2) - - @request.session[:user_id] = 2 - get :show, :id => attachment.id - assert_response 403 - end - - def test_show_invalid_should_respond_with_404 - get :show, :id => 999 - assert_response 404 - end - - def test_download_text_file - get :download, :id => 4 - assert_response :success - assert_equal 'application/x-ruby', @response.content_type - set_tmp_attachments_directory - end - - def test_download_version_file_with_issue_tracking_disabled - Project.find(1).disable_module! :issue_tracking - get :download, :id => 9 - assert_response :success - end - - def test_download_should_assign_content_type_if_blank - Attachment.find(4).update_attribute(:content_type, '') - - get :download, :id => 4 - assert_response :success - assert_equal 'text/x-ruby', @response.content_type - set_tmp_attachments_directory - end - - def test_download_missing_file - get :download, :id => 2 - assert_response 404 - set_tmp_attachments_directory - end - - def test_download_should_be_denied_without_permission - get :download, :id => 7 - assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdownload%2F7' - set_tmp_attachments_directory - end - - if convert_installed? - def test_thumbnail - Attachment.clear_thumbnails - @request.session[:user_id] = 2 - - get :thumbnail, :id => 16 - assert_response :success - assert_equal 'image/png', response.content_type - end - - def test_thumbnail_should_not_exceed_maximum_size - Redmine::Thumbnail.expects(:generate).with {|source, target, size| size == 800} - - @request.session[:user_id] = 2 - get :thumbnail, :id => 16, :size => 2000 - end - - def test_thumbnail_should_round_size - Redmine::Thumbnail.expects(:generate).with {|source, target, size| size == 250} - - @request.session[:user_id] = 2 - get :thumbnail, :id => 16, :size => 260 - end - - def test_thumbnail_should_return_404_for_non_image_attachment - @request.session[:user_id] = 2 - - get :thumbnail, :id => 15 - assert_response 404 - end - - def test_thumbnail_should_return_404_if_thumbnail_generation_failed - Attachment.any_instance.stubs(:thumbnail).returns(nil) - @request.session[:user_id] = 2 - - get :thumbnail, :id => 16 - assert_response 404 - end - - def test_thumbnail_should_be_denied_without_permission - get :thumbnail, :id => 16 - assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fthumbnail%2F16' - end - else - puts '(ImageMagick convert not available)' - end - - def test_destroy_issue_attachment - set_tmp_attachments_directory - issue = Issue.find(3) - @request.session[:user_id] = 2 - - assert_difference 'issue.attachments.count', -1 do - assert_difference 'Journal.count' do - delete :destroy, :id => 1 - assert_redirected_to '/projects/ecookbook' - end - end - assert_nil Attachment.find_by_id(1) - j = Journal.first(:order => 'id DESC') - assert_equal issue, j.journalized - assert_equal 'attachment', j.details.first.property - assert_equal '1', j.details.first.prop_key - assert_equal 'error281.txt', j.details.first.old_value - assert_equal User.find(2), j.user - end - - def test_destroy_wiki_page_attachment - set_tmp_attachments_directory - @request.session[:user_id] = 2 - assert_difference 'Attachment.count', -1 do - delete :destroy, :id => 3 - assert_response 302 - end - end - - def test_destroy_project_attachment - set_tmp_attachments_directory - @request.session[:user_id] = 2 - assert_difference 'Attachment.count', -1 do - delete :destroy, :id => 8 - assert_response 302 - end - end - - def test_destroy_version_attachment - set_tmp_attachments_directory - @request.session[:user_id] = 2 - assert_difference 'Attachment.count', -1 do - delete :destroy, :id => 9 - assert_response 302 - end - end - - def test_destroy_without_permission - set_tmp_attachments_directory - assert_no_difference 'Attachment.count' do - delete :destroy, :id => 3 - end - assert_response 302 - assert Attachment.find_by_id(3) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3d6e2d667e45eba59fe91797f0ad566ce557a39e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3d/3d6e2d667e45eba59fe91797f0ad566ce557a39e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,47 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingAdminTest < ActionController::IntegrationTest + def test_administration_panel + assert_routing( + { :method => 'get', :path => "/admin" }, + { :controller => 'admin', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/admin/projects" }, + { :controller => 'admin', :action => 'projects' } + ) + assert_routing( + { :method => 'get', :path => "/admin/plugins" }, + { :controller => 'admin', :action => 'plugins' } + ) + assert_routing( + { :method => 'get', :path => "/admin/info" }, + { :controller => 'admin', :action => 'info' } + ) + assert_routing( + { :method => 'get', :path => "/admin/test_email" }, + { :controller => 'admin', :action => 'test_email' } + ) + assert_routing( + { :method => 'post', :path => "/admin/default_configuration" }, + { :controller => 'admin', :action => 'default_configuration' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3d70721df6b551b90581f71def1a31b05dec4f44.svn-base --- a/.svn/pristine/3d/3d70721df6b551b90581f71def1a31b05dec4f44.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ - - - - -<%=h html_title %> - - -<%= csrf_meta_tag %> -<%= favicon %> -<%= stylesheet_link_tag 'jquery/jquery-ui-1.8.21', 'application', :media => 'all' %> -<%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %> -<%= javascript_heads %> -<%= heads_for_theme %> -<%= call_hook :view_layouts_base_html_head %> - -<%= yield :header_tags -%> - - -
    -
    -
    -
    -
    - <%= render_menu :account_menu -%> -
    - <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}".html_safe, :id => 'loggedas') if User.current.logged? %> - <%= render_menu :top_menu if User.current.logged? || !Setting.login_required? -%> -
    - - - - -
    - - - - - -
    -
    -<%= call_hook :view_layouts_base_body_bottom %> - - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3d911a7995f6f6d40cec83d7a109bb146cc550bd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3d/3d911a7995f6f6d40cec83d7a109bb146cc550bd.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,262 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::I18nTest < ActiveSupport::TestCase + include Redmine::I18n + include ActionView::Helpers::NumberHelper + + def setup + User.current.language = nil + end + + def teardown + set_language_if_valid 'en' + end + + def test_date_format_default + set_language_if_valid 'en' + today = Date.today + Setting.date_format = '' + assert_equal I18n.l(today), format_date(today) + end + + def test_date_format + set_language_if_valid 'en' + today = Date.today + Setting.date_format = '%d %m %Y' + assert_equal today.strftime('%d %m %Y'), format_date(today) + end + + def test_date_format_default_with_user_locale + set_language_if_valid 'es' + today = now = Time.parse('2011-02-20 14:00:00') + Setting.date_format = '%d %B %Y' + User.current.language = 'fr' + s1 = "20 f\xc3\xa9vrier 2011" + s1.force_encoding("UTF-8") if s1.respond_to?(:force_encoding) + assert_equal s1, format_date(today) + User.current.language = nil + assert_equal '20 Febrero 2011', format_date(today) + end + + def test_date_and_time_for_each_language + Setting.date_format = '' + valid_languages.each do |lang| + set_language_if_valid lang + assert_nothing_raised "#{lang} failure" do + format_date(Date.today) + format_time(Time.now) + format_time(Time.now, false) + assert_not_equal 'default', ::I18n.l(Date.today, :format => :default), + "date.formats.default missing in #{lang}" + assert_not_equal 'time', ::I18n.l(Time.now, :format => :time), + "time.formats.time missing in #{lang}" + end + assert l('date.day_names').is_a?(Array) + assert_equal 7, l('date.day_names').size + + assert l('date.month_names').is_a?(Array) + assert_equal 13, l('date.month_names').size + end + end + + def test_time_for_each_zone + ActiveSupport::TimeZone.all.each do |zone| + User.current.stubs(:time_zone).returns(zone.name) + assert_nothing_raised "#{zone} failure" do + format_time(Time.now) + end + end + end + + def test_time_format + set_language_if_valid 'en' + now = Time.parse('2011-02-20 15:45:22') + with_settings :time_format => '%H:%M' do + with_settings :date_format => '' do + assert_equal '02/20/2011 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + with_settings :date_format => '%Y-%m-%d' do + assert_equal '2011-02-20 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + end + end + + def test_time_format_default + set_language_if_valid 'en' + now = Time.parse('2011-02-20 15:45:22') + with_settings :time_format => '' do + with_settings :date_format => '' do + assert_equal '02/20/2011 03:45 PM', format_time(now) + assert_equal '03:45 PM', format_time(now, false) + end + with_settings :date_format => '%Y-%m-%d' do + assert_equal '2011-02-20 03:45 PM', format_time(now) + assert_equal '03:45 PM', format_time(now, false) + end + end + end + + def test_time_format_default_with_user_locale + set_language_if_valid 'en' + User.current.language = 'fr' + now = Time.parse('2011-02-20 15:45:22') + with_settings :time_format => '' do + with_settings :date_format => '' do + assert_equal '20/02/2011 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + with_settings :date_format => '%Y-%m-%d' do + assert_equal '2011-02-20 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + end + end + + def test_utc_time_format + set_language_if_valid 'en' + now = Time.now + Setting.date_format = '%d %m %Y' + Setting.time_format = '%H %M' + assert_equal now.strftime('%d %m %Y %H %M'), format_time(now.utc) + assert_equal now.strftime('%H %M'), format_time(now.utc, false) + end + + def test_number_to_human_size_for_each_language + valid_languages.each do |lang| + set_language_if_valid lang + assert_nothing_raised "#{lang} failure" do + size = number_to_human_size(257024) + assert_match /251/, size + end + end + end + + def test_day_name + set_language_if_valid 'fr' + assert_equal 'dimanche', day_name(0) + assert_equal 'jeudi', day_name(4) + end + + def test_day_letter + set_language_if_valid 'fr' + assert_equal 'd', day_letter(0) + assert_equal 'j', day_letter(4) + end + + def test_number_to_currency_for_each_language + valid_languages.each do |lang| + set_language_if_valid lang + assert_nothing_raised "#{lang} failure" do + number_to_currency(-1000.2) + end + end + end + + def test_number_to_currency_default + set_language_if_valid 'bs' + assert_equal "KM -1000,20", number_to_currency(-1000.2) + set_language_if_valid 'de' + euro_sign = "\xe2\x82\xac" + euro_sign.force_encoding('UTF-8') if euro_sign.respond_to?(:force_encoding) + assert_equal "-1000,20 #{euro_sign}", number_to_currency(-1000.2) + end + + def test_valid_languages + assert valid_languages.is_a?(Array) + assert valid_languages.first.is_a?(Symbol) + end + + def test_languages_options + options = languages_options + assert options.is_a?(Array) + assert_equal valid_languages.size, options.size + assert_nil options.detect {|option| !option.is_a?(Array)} + assert_nil options.detect {|option| option.size != 2} + assert_nil options.detect {|option| !option.first.is_a?(String) || !option.last.is_a?(String)} + assert_include ["English", "en"], options + ja = "Japanese (\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e)" + ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) + assert_include [ja, "ja"], options + end + + def test_locales_validness + lang_files_count = Dir["#{Rails.root}/config/locales/*.yml"].size + assert_equal lang_files_count, valid_languages.size + valid_languages.each do |lang| + assert set_language_if_valid(lang) + end + set_language_if_valid('en') + end + + def test_valid_language + to_test = {'fr' => :fr, + 'Fr' => :fr, + 'zh' => :zh, + 'zh-tw' => :"zh-TW", + 'zh-TW' => :"zh-TW", + 'zh-ZZ' => nil } + to_test.each {|lang, expected| assert_equal expected, find_language(lang)} + end + + def test_fallback + ::I18n.backend.store_translations(:en, {:untranslated => "Untranslated string"}) + ::I18n.locale = 'en' + assert_equal "Untranslated string", l(:untranslated) + ::I18n.locale = 'fr' + assert_equal "Untranslated string", l(:untranslated) + + ::I18n.backend.store_translations(:fr, {:untranslated => "Pas de traduction"}) + ::I18n.locale = 'en' + assert_equal "Untranslated string", l(:untranslated) + ::I18n.locale = 'fr' + assert_equal "Pas de traduction", l(:untranslated) + end + + def test_utf8 + set_language_if_valid 'ja' + str_ja_yes = "\xe3\x81\xaf\xe3\x81\x84" + i18n_ja_yes = l(:general_text_Yes) + if str_ja_yes.respond_to?(:force_encoding) + str_ja_yes.force_encoding('UTF-8') + assert_equal "UTF-8", i18n_ja_yes.encoding.to_s + end + assert_equal str_ja_yes, i18n_ja_yes + end + + def test_traditional_chinese_locale + set_language_if_valid 'zh-TW' + str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)" + if str_tw.respond_to?(:force_encoding) + str_tw.force_encoding('UTF-8') + end + assert_equal str_tw, l(:general_lang_name) + end + + def test_french_locale + set_language_if_valid 'fr' + str_fr = "Fran\xc3\xa7ais" + if str_fr.respond_to?(:force_encoding) + str_fr.force_encoding('UTF-8') + end + assert_equal str_fr, l(:general_lang_name) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3d/3de9a7c0e8857c78fd6236475c621cbde49a6cbd.svn-base Binary file .svn/pristine/3d/3de9a7c0e8857c78fd6236475c621cbde49a6cbd.svn-base has changed diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3e14269f7957b94ea32fcfb1a7c31d6ea6b0f300.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3e14269f7957b94ea32fcfb1a7c31d6ea6b0f300.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,50 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# FileSystem adapter +# File written by Paul Rivier, at Demotera. +# +# 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. + +require 'redmine/scm/adapters/filesystem_adapter' + +class Repository::Filesystem < Repository + attr_protected :root_url + validates_presence_of :url + + def self.human_attribute_name(attribute_key_name, *args) + attr_name = attribute_key_name.to_s + if attr_name == "url" + attr_name = "root_directory" + end + super(attr_name, *args) + end + + def self.scm_adapter_class + Redmine::Scm::Adapters::FilesystemAdapter + end + + def self.scm_name + 'Filesystem' + end + + def supports_all_revisions? + false + end + + def fetch_changesets + nil + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3e374c2424494c6e31846aefb54cd562b60e1174.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3e374c2424494c6e31846aefb54cd562b60e1174.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,50 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class RepositoriesGitTest < ActionController::IntegrationTest + fixtures :projects, :users, :roles, :members, :member_roles, + :repositories, :enabled_modules + + REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s + REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? + PRJ_ID = 3 + + def setup + User.current = nil + @project = Project.find(PRJ_ID) + @repository = Repository::Git.create( + :project => @project, + :url => REPOSITORY_PATH, + :path_encoding => 'ISO-8859-1' + ) + assert @repository + end + + if File.directory?(REPOSITORY_PATH) + def test_index + get '/projects/subproject1/repository/' + assert_response :success + end + + def test_diff_two_revs + get '/projects/subproject1/repository/diff?rev=61b685fbe&rev_to=2f9c0091' + assert_response :success + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3e3965205c1d0192fd841d76b3912a082dfcf332.svn-base --- a/.svn/pristine/3e/3e3965205c1d0192fd841d76b3912a082dfcf332.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class EnabledModuleTest < ActiveSupport::TestCase - fixtures :projects, :wikis - - def test_enabling_wiki_should_create_a_wiki - CustomField.delete_all - project = Project.create!(:name => 'Project with wiki', :identifier => 'wikiproject') - assert_nil project.wiki - project.enabled_module_names = ['wiki'] - project.reload - assert_not_nil project.wiki - assert_equal 'Wiki', project.wiki.start_page - end - - def test_reenabling_wiki_should_not_create_another_wiki - project = Project.find(1) - assert_not_nil project.wiki - project.enabled_module_names = [] - project.reload - assert_no_difference 'Wiki.count' do - project.enabled_module_names = ['wiki'] - end - assert_not_nil project.wiki - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3e62f1e51cbbdbddb3c1ccc8b354945e6ef528ac.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3e62f1e51cbbdbddb3c1ccc8b354945e6ef528ac.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,154 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class AttachmentsController < ApplicationController + before_filter :find_project, :except => :upload + before_filter :file_readable, :read_authorize, :only => [:show, :download, :thumbnail] + before_filter :delete_authorize, :only => :destroy + before_filter :authorize_global, :only => :upload + + accept_api_auth :show, :download, :upload + + def show + respond_to do |format| + format.html { + if @attachment.is_diff? + @diff = File.new(@attachment.diskfile, "rb").read + @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' + @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) + # Save diff type as user preference + if User.current.logged? && @diff_type != User.current.pref[:diff_type] + User.current.pref[:diff_type] = @diff_type + User.current.preference.save + end + render :action => 'diff' + elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte + @content = File.new(@attachment.diskfile, "rb").read + render :action => 'file' + else + download + end + } + format.api + end + end + + def download + if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project) + @attachment.increment_download + end + + if stale?(:etag => @attachment.digest) + # images are sent inline + send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), + :type => detect_content_type(@attachment), + :disposition => (@attachment.image? ? 'inline' : 'attachment') + end + end + + def thumbnail + if @attachment.thumbnailable? && thumbnail = @attachment.thumbnail(:size => params[:size]) + if stale?(:etag => thumbnail) + send_file thumbnail, + :filename => filename_for_content_disposition(@attachment.filename), + :type => detect_content_type(@attachment), + :disposition => 'inline' + end + else + # No thumbnail for the attachment or thumbnail could not be created + render :nothing => true, :status => 404 + end + end + + def upload + # Make sure that API users get used to set this content type + # as it won't trigger Rails' automatic parsing of the request body for parameters + unless request.content_type == 'application/octet-stream' + render :nothing => true, :status => 406 + return + end + + @attachment = Attachment.new(:file => request.raw_post) + @attachment.author = User.current + @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16) + saved = @attachment.save + + respond_to do |format| + format.js + format.api { + if saved + render :action => 'upload', :status => :created + else + render_validation_errors(@attachment) + end + } + end + end + + def destroy + if @attachment.container.respond_to?(:init_journal) + @attachment.container.init_journal(User.current) + end + if @attachment.container + # Make sure association callbacks are called + @attachment.container.attachments.delete(@attachment) + else + @attachment.destroy + end + + respond_to do |format| + format.html { redirect_to_referer_or project_path(@project) } + format.js + end + end + +private + def find_project + @attachment = Attachment.find(params[:id]) + # Show 404 if the filename in the url is wrong + raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename + @project = @attachment.project + rescue ActiveRecord::RecordNotFound + render_404 + end + + # Checks that the file exists and is readable + def file_readable + if @attachment.readable? + true + else + logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable." + render_404 + end + end + + def read_authorize + @attachment.visible? ? true : deny_access + end + + def delete_authorize + @attachment.deletable? ? true : deny_access + end + + def detect_content_type(attachment) + content_type = attachment.content_type + if content_type.blank? + content_type = Redmine::MimeType.of(attachment.filename) + end + content_type.to_s + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3e6b4c56b9e0a4f47f38d77dae80e5ffe88063a8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3e6b4c56b9e0a4f47f38d77dae80e5ffe88063a8.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,47 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'builder' + +module Redmine + module Views + module Builders + class Xml < ::Builder::XmlMarkup + def initialize(request, response) + super() + instruct! + end + + def output + target! + end + + def method_missing(sym, *args, &block) + if args.size == 1 && args.first.is_a?(::Time) + __send__ sym, args.first.xmlschema, &block + else + super + end + end + + def array(name, options={}, &block) + __send__ name, (options || {}).merge(:type => 'array'), &block + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3e6b94e5f06c2c16daf8b3fdee45e4f66096d317.svn-base --- a/.svn/pristine/3e/3e6b94e5f06c2c16daf8b3fdee45e4f66096d317.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -

    <%=l(:label_watched_issues)%> (<%= Issue.visible.watched_by(user.id).count %>)

    -<% watched_issues = Issue.visible.on_active_project.watched_by(user.id).recently_updated.limit(10) %> - -<%= render :partial => 'issues/list_simple', :locals => { :issues => watched_issues } %> -<% if watched_issues.length > 0 %> -

    <%= link_to l(:label_issue_view_all), :controller => 'issues', - :action => 'index', - :set_filter => 1, - :watcher_id => 'me', - :sort => 'updated_on:desc' %>

    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3e7da359af4ad8a51a0841b79dbc50ddb307b033.svn-base --- a/.svn/pristine/3e/3e7da359af4ad8a51a0841b79dbc50ddb307b033.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1102 +0,0 @@ -# Hungarian translations for Ruby on Rails -# by Richard Abonyi (richard.abonyi@gmail.com) -# thanks to KKata, replaced and #hup.hu -# Cleaned up by László Bácsi (http://lackac.hu) -# updated by kfl62 kfl62g@gmail.com -# updated by Gábor Takács (taky77@gmail.com) - -"hu": - direction: ltr - date: - formats: - default: "%Y.%m.%d." - short: "%b %e." - long: "%Y. %B %e." - day_names: [vasárnap, hétfÅ‘, kedd, szerda, csütörtök, péntek, szombat] - abbr_day_names: [v., h., k., sze., cs., p., szo.] - month_names: [~, január, február, március, április, május, június, július, augusztus, szeptember, október, november, december] - abbr_month_names: [~, jan., febr., márc., ápr., máj., jún., júl., aug., szept., okt., nov., dec.] - order: - - :year - - :month - - :day - - time: - formats: - default: "%Y. %b %d., %H:%M" - time: "%H:%M" - short: "%b %e., %H:%M" - long: "%Y. %B %e., %A, %H:%M" - am: "de." - pm: "du." - - datetime: - distance_in_words: - half_a_minute: 'fél perc' - less_than_x_seconds: -# zero: 'kevesebb, mint 1 másodperce' - one: 'kevesebb, mint 1 másodperce' - other: 'kevesebb, mint %{count} másodperce' - x_seconds: - one: '1 másodperce' - other: '%{count} másodperce' - less_than_x_minutes: -# zero: 'kevesebb, mint 1 perce' - one: 'kevesebb, mint 1 perce' - other: 'kevesebb, mint %{count} perce' - x_minutes: - one: '1 perce' - other: '%{count} perce' - about_x_hours: - one: 'csaknem 1 órája' - other: 'csaknem %{count} órája' - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: '1 napja' - other: '%{count} napja' - about_x_months: - one: 'csaknem 1 hónapja' - other: 'csaknem %{count} hónapja' - x_months: - one: '1 hónapja' - other: '%{count} hónapja' - about_x_years: - one: 'csaknem 1 éve' - other: 'csaknem %{count} éve' - over_x_years: - one: 'több, mint 1 éve' - other: 'több, mint %{count} éve' - almost_x_years: - one: "csaknem 1 éve" - other: "csaknem %{count} éve" - prompts: - year: "Év" - month: "Hónap" - day: "Nap" - hour: "Óra" - minute: "Perc" - second: "Másodperc" - - number: - format: - precision: 2 - separator: ',' - delimiter: ' ' - currency: - format: - unit: 'Ft' - precision: 0 - format: '%n %u' - separator: "," - delimiter: "" - percentage: - format: - delimiter: "" - precision: - format: - delimiter: "" - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "bájt" - other: "bájt" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - support: - array: -# sentence_connector: "és" -# skip_last_comma: true - words_connector: ", " - two_words_connector: " és " - last_word_connector: " és " - activerecord: - errors: - template: - header: - one: "1 hiba miatt nem menthetÅ‘ a következÅ‘: %{model}" - other: "%{count} hiba miatt nem menthetÅ‘ a következÅ‘: %{model}" - body: "Problémás mezÅ‘k:" - messages: - inclusion: "nincs a listában" - exclusion: "nem elérhetÅ‘" - invalid: "nem megfelelÅ‘" - confirmation: "nem egyezik" - accepted: "nincs elfogadva" - empty: "nincs megadva" - blank: "nincs megadva" - too_long: "túl hosszú (nem lehet több %{count} karakternél)" - too_short: "túl rövid (legalább %{count} karakter kell legyen)" - wrong_length: "nem megfelelÅ‘ hosszúságú (%{count} karakter szükséges)" - taken: "már foglalt" - not_a_number: "nem szám" - greater_than: "nagyobb kell legyen, mint %{count}" - greater_than_or_equal_to: "legalább %{count} kell legyen" - equal_to: "pontosan %{count} kell legyen" - less_than: "kevesebb, mint %{count} kell legyen" - less_than_or_equal_to: "legfeljebb %{count} lehet" - odd: "páratlan kell legyen" - even: "páros kell legyen" - greater_than_start_date: "nagyobbnak kell lennie, mint az indítás dátuma" - not_same_project: "nem azonos projekthez tartozik" - circular_dependency: "Ez a kapcsolat egy körkörös függÅ‘séget eredményez" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Kérem válasszon - - general_text_No: 'Nem' - general_text_Yes: 'Igen' - general_text_no: 'nem' - general_text_yes: 'igen' - general_lang_name: 'Magyar' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-2 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: A fiók adatai sikeresen frissítve. - notice_account_invalid_creditentials: Hibás felhasználói név, vagy jelszó - notice_account_password_updated: A jelszó módosítása megtörtént. - notice_account_wrong_password: Hibás jelszó - notice_account_register_done: A fiók sikeresen létrehozva. Aktiválásához kattints az e-mailben kapott linkre - notice_account_unknown_email: Ismeretlen felhasználó. - notice_can_t_change_password: A fiók külsÅ‘ azonosítási forrást használ. A jelszó megváltoztatása nem lehetséges. - notice_account_lost_email_sent: Egy e-mail üzenetben postáztunk Önnek egy leírást az új jelszó beállításáról. - notice_account_activated: Fiókját aktiváltuk. Most már be tud jelentkezni a rendszerbe. - notice_successful_create: Sikeres létrehozás. - notice_successful_update: Sikeres módosítás. - notice_successful_delete: Sikeres törlés. - notice_successful_connection: Sikeres bejelentkezés. - notice_file_not_found: Az oldal, amit meg szeretne nézni nem található, vagy átkerült egy másik helyre. - notice_locking_conflict: Az adatot egy másik felhasználó idÅ‘ közben módosította. - notice_not_authorized: Nincs hozzáférési engedélye ehhez az oldalhoz. - notice_email_sent: "Egy e-mail üzenetet küldtünk a következÅ‘ címre %{value}" - notice_email_error: "Hiba történt a levél küldése közben (%{value})" - notice_feeds_access_key_reseted: Az RSS hozzáférési kulcsát újra generáltuk. - notice_failed_to_save_issues: "Nem sikerült a %{count} feladat(ok) mentése a %{total} -ban kiválasztva: %{ids}." - notice_no_issue_selected: "Nincs feladat kiválasztva! Kérem jelölje meg melyik feladatot szeretné szerkeszteni!" - notice_account_pending: "A fiókja létrejött, és adminisztrátori jóváhagyásra vár." - notice_default_data_loaded: Az alapértelmezett konfiguráció betöltése sikeresen megtörtént. - - error_can_t_load_default_data: "Az alapértelmezett konfiguráció betöltése nem lehetséges: %{value}" - error_scm_not_found: "A bejegyzés, vagy revízió nem található a tárolóban." - error_scm_command_failed: "A tároló elérése közben hiba lépett fel: %{value}" - error_scm_annotate: "A bejegyzés nem létezik, vagy nics jegyzetekkel ellátva." - error_issue_not_found_in_project: 'A feladat nem található, vagy nem ehhez a projekthez tartozik' - - mail_subject_lost_password: Az Ön Redmine jelszava - mail_body_lost_password: 'A Redmine jelszó megváltoztatásához, kattintson a következÅ‘ linkre:' - mail_subject_register: Redmine azonosító aktiválása - mail_body_register: 'A Redmine azonosítója aktiválásához, kattintson a következÅ‘ linkre:' - mail_body_account_information_external: "A %{value} azonosító használatával bejelentkezhet a Redmine-ba." - mail_body_account_information: Az Ön Redmine azonosítójának információi - mail_subject_account_activation_request: Redmine azonosító aktiválási kérelem - mail_body_account_activation_request: "Egy új felhasználó (%{value}) regisztrált, azonosítója jóváhasgyásra várakozik:" - - gui_validation_error: 1 hiba - gui_validation_error_plural: "%{count} hiba" - - field_name: Név - field_description: Leírás - field_summary: Összegzés - field_is_required: KötelezÅ‘ - field_firstname: Keresztnév - field_lastname: Vezetéknév - field_mail: E-mail - field_filename: Fájl - field_filesize: Méret - field_downloads: Letöltések - field_author: SzerzÅ‘ - field_created_on: Létrehozva - field_updated_on: Módosítva - field_field_format: Formátum - field_is_for_all: Minden projekthez - field_possible_values: Lehetséges értékek - field_regexp: Reguláris kifejezés - field_min_length: Minimum hossz - field_max_length: Maximum hossz - field_value: Érték - field_category: Kategória - field_title: Cím - field_project: Projekt - field_issue: Feladat - field_status: Státusz - field_notes: Feljegyzések - field_is_closed: Feladat lezárva - field_is_default: Alapértelmezett érték - field_tracker: Típus - field_subject: Tárgy - field_due_date: Befejezés dátuma - field_assigned_to: FelelÅ‘s - field_priority: Prioritás - field_fixed_version: Cél verzió - field_user: Felhasználó - field_role: Szerepkör - field_homepage: Weboldal - field_is_public: Nyilvános - field_parent: SzülÅ‘ projekt - field_is_in_roadmap: Feladatok látszanak az életútban - field_login: Azonosító - field_mail_notification: E-mail értesítések - field_admin: Adminisztrátor - field_last_login_on: Utolsó bejelentkezés - field_language: Nyelv - field_effective_date: Dátum - field_password: Jelszó - field_new_password: Új jelszó - field_password_confirmation: MegerÅ‘sítés - field_version: Verzió - field_type: Típus - field_host: Kiszolgáló - field_port: Port - field_account: Felhasználói fiók - field_base_dn: Base DN - field_attr_login: Bejelentkezési tulajdonság - field_attr_firstname: Keresztnév - field_attr_lastname: Vezetéknév - field_attr_mail: E-mail - field_onthefly: On-the-fly felhasználó létrehozás - field_start_date: Kezdés dátuma - field_done_ratio: Készültség (%) - field_auth_source: Azonosítási mód - field_hide_mail: Rejtse el az e-mail címem - field_comments: Megjegyzés - field_url: URL - field_start_page: KezdÅ‘lap - field_subproject: Alprojekt - field_hours: Óra - field_activity: Aktivitás - field_spent_on: Dátum - field_identifier: Azonosító - field_is_filter: SzűrÅ‘ként használható - field_issue_to: Kapcsolódó feladat - field_delay: Késés - field_assignable: Feladat rendelhetÅ‘ ehhez a szerepkörhöz - field_redirect_existing_links: LétezÅ‘ linkek átirányítása - field_estimated_hours: Becsült idÅ‘igény - field_column_names: Oszlopok - field_time_zone: IdÅ‘zóna - field_searchable: KereshetÅ‘ - field_default_value: Alapértelmezett érték - field_comments_sorting: Feljegyzések megjelenítése - - setting_app_title: Alkalmazás címe - setting_app_subtitle: Alkalmazás alcíme - setting_welcome_text: ÜdvözlÅ‘ üzenet - setting_default_language: Alapértelmezett nyelv - setting_login_required: Azonosítás szükséges - setting_self_registration: Regisztráció - setting_attachment_max_size: Melléklet max. mérete - setting_issues_export_limit: Feladatok exportálásának korlátja - setting_mail_from: Kibocsátó e-mail címe - setting_bcc_recipients: Titkos másolat címzet (bcc) - setting_host_name: Kiszolgáló neve - setting_text_formatting: Szöveg formázás - setting_wiki_compression: Wiki történet tömörítés - setting_feeds_limit: RSS tartalom korlát - setting_default_projects_public: Az új projektek alapértelmezés szerint nyilvánosak - setting_autofetch_changesets: Commitok automatikus lehúzása - setting_sys_api_enabled: WS engedélyezése a tárolók kezeléséhez - setting_commit_ref_keywords: Hivatkozó kulcsszavak - setting_commit_fix_keywords: Javítások kulcsszavai - setting_autologin: Automatikus bejelentkezés - setting_date_format: Dátum formátum - setting_time_format: IdÅ‘ formátum - setting_cross_project_issue_relations: Kereszt-projekt feladat hivatkozások engedélyezése - setting_issue_list_default_columns: Az alapértelmezésként megjelenített oszlopok a feladat listában - setting_emails_footer: E-mail lábléc - setting_protocol: Protokol - setting_per_page_options: Objektum / oldal opciók - setting_user_format: Felhasználók megjelenítésének formája - setting_activity_days_default: Napok megjelenítése a project aktivitásnál - setting_display_subprojects_issues: Alapértelmezettként mutassa az alprojektek feladatait is a projekteken - setting_start_of_week: A hét elsÅ‘ napja - - project_module_issue_tracking: Feladat követés - project_module_time_tracking: IdÅ‘ rögzítés - project_module_news: Hírek - project_module_documents: Dokumentumok - project_module_files: Fájlok - project_module_wiki: Wiki - project_module_repository: Forráskód - project_module_boards: Fórumok - - label_user: Felhasználó - label_user_plural: Felhasználók - label_user_new: Új felhasználó - label_project: Projekt - label_project_new: Új projekt - label_project_plural: Projektek - label_x_projects: - zero: nincsenek projektek - one: 1 projekt - other: "%{count} projekt" - label_project_all: Az összes projekt - label_project_latest: Legutóbbi projektek - label_issue: Feladat - label_issue_new: Új feladat - label_issue_plural: Feladatok - label_issue_view_all: Minden feladat - label_issues_by: "%{value} feladatai" - label_issue_added: Feladat hozzáadva - label_issue_updated: Feladat frissítve - label_document: Dokumentum - label_document_new: Új dokumentum - label_document_plural: Dokumentumok - label_document_added: Dokumentum hozzáadva - label_role: Szerepkör - label_role_plural: Szerepkörök - label_role_new: Új szerepkör - label_role_and_permissions: Szerepkörök, és jogosultságok - label_member: RésztvevÅ‘ - label_member_new: Új résztvevÅ‘ - label_member_plural: RésztvevÅ‘k - label_tracker: Feladat típus - label_tracker_plural: Feladat típusok - label_tracker_new: Új feladat típus - label_workflow: Workflow - label_issue_status: Feladat státusz - label_issue_status_plural: Feladat státuszok - label_issue_status_new: Új státusz - label_issue_category: Feladat kategória - label_issue_category_plural: Feladat kategóriák - label_issue_category_new: Új kategória - label_custom_field: Egyéni mezÅ‘ - label_custom_field_plural: Egyéni mezÅ‘k - label_custom_field_new: Új egyéni mezÅ‘ - label_enumerations: Felsorolások - label_enumeration_new: Új érték - label_information: Információ - label_information_plural: Információk - label_please_login: Jelentkezzen be - label_register: Regisztráljon - label_password_lost: Elfelejtett jelszó - label_home: KezdÅ‘lap - label_my_page: Saját kezdÅ‘lapom - label_my_account: Fiókom adatai - label_my_projects: Saját projektem - label_administration: Adminisztráció - label_login: Bejelentkezés - label_logout: Kijelentkezés - label_help: Súgó - label_reported_issues: Bejelentett feladatok - label_assigned_to_me_issues: A nekem kiosztott feladatok - label_last_login: Utolsó bejelentkezés - label_registered_on: Regisztrált - label_activity: Történések - label_overall_activity: Teljes aktivitás - label_new: Új - label_logged_as: Bejelentkezve, mint - label_environment: Környezet - label_authentication: Azonosítás - label_auth_source: Azonosítás módja - label_auth_source_new: Új azonosítási mód - label_auth_source_plural: Azonosítási módok - label_subproject_plural: Alprojektek - label_and_its_subprojects: "%{value} és alprojektjei" - label_min_max_length: Min - Max hossz - label_list: Lista - label_date: Dátum - label_integer: Egész - label_float: LebegÅ‘pontos - label_boolean: Logikai - label_string: Szöveg - label_text: Hosszú szöveg - label_attribute: Tulajdonság - label_attribute_plural: Tulajdonságok - label_download: "%{count} Letöltés" - label_download_plural: "%{count} Letöltés" - label_no_data: Nincs megjeleníthetÅ‘ adat - label_change_status: Státusz módosítása - label_history: Történet - label_attachment: Fájl - label_attachment_new: Új fájl - label_attachment_delete: Fájl törlése - label_attachment_plural: Fájlok - label_file_added: Fájl hozzáadva - label_report: Jelentés - label_report_plural: Jelentések - label_news: Hírek - label_news_new: Hír hozzáadása - label_news_plural: Hírek - label_news_latest: Legutóbbi hírek - label_news_view_all: Minden hír megtekintése - label_news_added: Hír hozzáadva - label_settings: Beállítások - label_overview: Ãttekintés - label_version: Verzió - label_version_new: Új verzió - label_version_plural: Verziók - label_confirmation: Jóváhagyás - label_export_to: Exportálás - label_read: Olvas... - label_public_projects: Nyilvános projektek - label_open_issues: nyitott - label_open_issues_plural: nyitott - label_closed_issues: lezárt - label_closed_issues_plural: lezárt - label_x_open_issues_abbr_on_total: - zero: nyitott 0 / %{total} - one: nyitott 1 / %{total} - other: "nyitott %{count} / %{total}" - label_x_open_issues_abbr: - zero: 0 nyitott - one: 1 nyitott - other: "%{count} nyitott" - label_x_closed_issues_abbr: - zero: 0 lezárt - one: 1 lezárt - other: "%{count} lezárt" - label_total: Összesen - label_permissions: Jogosultságok - label_current_status: Jelenlegi státusz - label_new_statuses_allowed: Státusz változtatások engedélyei - label_all: mind - label_none: nincs - label_nobody: senki - label_next: KövetkezÅ‘ - label_previous: ElÅ‘zÅ‘ - label_used_by: Használja - label_details: Részletek - label_add_note: Jegyzet hozzáadása - label_per_page: Oldalanként - label_calendar: Naptár - label_months_from: hónap, kezdve - label_gantt: Gantt - label_internal: BelsÅ‘ - label_last_changes: "utolsó %{count} változás" - label_change_view_all: Minden változás megtekintése - label_personalize_page: Az oldal testreszabása - label_comment: Megjegyzés - label_comment_plural: Megjegyzés - label_x_comments: - zero: nincs megjegyzés - one: 1 megjegyzés - other: "%{count} megjegyzés" - label_comment_add: Megjegyzés hozzáadása - label_comment_added: Megjegyzés hozzáadva - label_comment_delete: Megjegyzések törlése - label_query: Egyéni lekérdezés - label_query_plural: Egyéni lekérdezések - label_query_new: Új lekérdezés - label_filter_add: SzűrÅ‘ hozzáadása - label_filter_plural: SzűrÅ‘k - label_equals: egyenlÅ‘ - label_not_equals: nem egyenlÅ‘ - label_in_less_than: kevesebb, mint - label_in_more_than: több, mint - label_in: in - label_today: ma - label_all_time: mindenkor - label_yesterday: tegnap - label_this_week: aktuális hét - label_last_week: múlt hét - label_last_n_days: "az elmúlt %{count} nap" - label_this_month: aktuális hónap - label_last_month: múlt hónap - label_this_year: aktuális év - label_date_range: Dátum intervallum - label_less_than_ago: kevesebb, mint nappal ezelÅ‘tt - label_more_than_ago: több, mint nappal ezelÅ‘tt - label_ago: nappal ezelÅ‘tt - label_contains: tartalmazza - label_not_contains: nem tartalmazza - label_day_plural: nap - label_repository: Forráskód - label_repository_plural: Forráskódok - label_browse: Tallóz - label_modification: "%{count} változás" - label_modification_plural: "%{count} változás" - label_revision: Revízió - label_revision_plural: Revíziók - label_associated_revisions: Kapcsolt revíziók - label_added: hozzáadva - label_modified: módosítva - label_deleted: törölve - label_latest_revision: Legutolsó revízió - label_latest_revision_plural: Legutolsó revíziók - label_view_revisions: Revíziók megtekintése - label_max_size: Maximális méret - label_sort_highest: Az elejére - label_sort_higher: Eggyel feljebb - label_sort_lower: Eggyel lejjebb - label_sort_lowest: Az aljára - label_roadmap: Életút - label_roadmap_due_in: "Elkészültéig várhatóan még %{value}" - label_roadmap_overdue: "%{value} késésben" - label_roadmap_no_issues: Nincsenek feladatok ehhez a verzióhoz - label_search: Keresés - label_result_plural: Találatok - label_all_words: Minden szó - label_wiki: Wiki - label_wiki_edit: Wiki szerkesztés - label_wiki_edit_plural: Wiki szerkesztések - label_wiki_page: Wiki oldal - label_wiki_page_plural: Wiki oldalak - label_index_by_title: Cím szerint indexelve - label_index_by_date: Dátum szerint indexelve - label_current_version: Jelenlegi verzió - label_preview: ElÅ‘nézet - label_feed_plural: Visszajelzések - label_changes_details: Változások részletei - label_issue_tracking: Feladat követés - label_spent_time: Ráfordított idÅ‘ - label_f_hour: "%{value} óra" - label_f_hour_plural: "%{value} óra" - label_time_tracking: IdÅ‘ rögzítés - label_change_plural: Változások - label_statistics: Statisztikák - label_commits_per_month: Commitok havonta - label_commits_per_author: Commitok szerzÅ‘nként - label_view_diff: Különbségek megtekintése - label_diff_inline: soronként - label_diff_side_by_side: egymás mellett - label_options: Opciók - label_copy_workflow_from: Workflow másolása innen - label_permissions_report: Jogosultsági riport - label_watched_issues: Megfigyelt feladatok - label_related_issues: Kapcsolódó feladatok - label_applied_status: Alkalmazandó státusz - label_loading: Betöltés... - label_relation_new: Új kapcsolat - label_relation_delete: Kapcsolat törlése - label_relates_to: kapcsolódik - label_duplicates: duplikálja - label_blocks: zárolja - label_blocked_by: zárolta - label_precedes: megelÅ‘zi - label_follows: követi - label_end_to_start: végétÅ‘l indulásig - label_end_to_end: végétÅ‘l végéig - label_start_to_start: indulástól indulásig - label_start_to_end: indulástól végéig - label_stay_logged_in: Emlékezzen rám - label_disabled: kikapcsolva - label_show_completed_versions: A kész verziók mutatása - label_me: én - label_board: Fórum - label_board_new: Új fórum - label_board_plural: Fórumok - label_topic_plural: Témák - label_message_plural: Üzenetek - label_message_last: Utolsó üzenet - label_message_new: Új üzenet - label_message_posted: Üzenet hozzáadva - label_reply_plural: Válaszok - label_send_information: Fiók infomációk küldése a felhasználónak - label_year: Év - label_month: Hónap - label_week: Hét - label_date_from: 'Kezdet:' - label_date_to: 'Vége:' - label_language_based: A felhasználó nyelve alapján - label_sort_by: "%{value} szerint rendezve" - label_send_test_email: Teszt e-mail küldése - label_feeds_access_key_created_on: "RSS hozzáférési kulcs létrehozva %{value}" - label_module_plural: Modulok - label_added_time_by: "%{author} adta hozzá %{age}" - label_updated_time: "Utolsó módosítás %{value}" - label_jump_to_a_project: Ugrás projekthez... - label_file_plural: Fájlok - label_changeset_plural: Changesets - label_default_columns: Alapértelmezett oszlopok - label_no_change_option: (Nincs változás) - label_bulk_edit_selected_issues: A kiválasztott feladatok kötegelt szerkesztése - label_theme: Téma - label_default: Alapértelmezett - label_search_titles_only: Keresés csak a címekben - label_user_mail_option_all: "Minden eseményrÅ‘l minden saját projektemben" - label_user_mail_option_selected: "Minden eseményrÅ‘l a kiválasztott projektekben..." - label_user_mail_no_self_notified: "Nem kérek értesítést az általam végzett módosításokról" - label_registration_activation_by_email: Fiók aktiválása e-mailben - label_registration_manual_activation: Manuális fiók aktiválás - label_registration_automatic_activation: Automatikus fiók aktiválás - label_display_per_page: "Oldalanként: %{value}" - label_age: Kor - label_change_properties: Tulajdonságok változtatása - label_general: Ãltalános - label_more: továbbiak - label_scm: SCM - label_plugins: Pluginek - label_ldap_authentication: LDAP azonosítás - label_downloads_abbr: D/L - label_optional_description: Opcionális leírás - label_add_another_file: Újabb fájl hozzáadása - label_preferences: Tulajdonságok - label_chronological_order: IdÅ‘rendben - label_reverse_chronological_order: Fordított idÅ‘rendben - label_planning: Tervezés - - button_login: Bejelentkezés - button_submit: Elfogad - button_save: Mentés - button_check_all: Mindent kijelöl - button_uncheck_all: Kijelölés törlése - button_delete: Töröl - button_create: Létrehoz - button_test: Teszt - button_edit: Szerkeszt - button_add: Hozzáad - button_change: Változtat - button_apply: Alkalmaz - button_clear: Töröl - button_lock: Zárol - button_unlock: Felold - button_download: Letöltés - button_list: Lista - button_view: Megnéz - button_move: Mozgat - button_back: Vissza - button_cancel: Mégse - button_activate: Aktivál - button_sort: Rendezés - button_log_time: IdÅ‘ rögzítés - button_rollback: Visszaáll erre a verzióra - button_watch: Megfigyel - button_unwatch: Megfigyelés törlése - button_reply: Válasz - button_archive: Archivál - button_unarchive: Dearchivál - button_reset: Reset - button_rename: Ãtnevez - button_change_password: Jelszó megváltoztatása - button_copy: Másol - button_annotate: Jegyzetel - button_update: Módosít - button_configure: Konfigurál - - status_active: aktív - status_registered: regisztrált - status_locked: zárolt - - text_select_mail_notifications: Válasszon eseményeket, amelyekrÅ‘l e-mail értesítést kell küldeni. - text_regexp_info: pl. ^[A-Z0-9]+$ - text_min_max_length_info: 0 = nincs korlátozás - text_project_destroy_confirmation: Biztosan törölni szeretné a projektet és vele együtt minden kapcsolódó adatot ? - text_subprojects_destroy_warning: "Az alprojekt(ek): %{value} szintén törlésre kerülnek." - text_workflow_edit: Válasszon egy szerepkört, és egy feladat típust a workflow szerkesztéséhez - text_are_you_sure: Biztos benne ? - text_tip_issue_begin_day: a feladat ezen a napon kezdÅ‘dik - text_tip_issue_end_day: a feladat ezen a napon ér véget - text_tip_issue_begin_end_day: a feladat ezen a napon kezdÅ‘dik és ér véget - text_caracters_maximum: "maximum %{count} karakter." - text_caracters_minimum: "Legkevesebb %{count} karakter hosszúnek kell lennie." - text_length_between: "Legalább %{min} és legfeljebb %{max} hosszú karakter." - text_tracker_no_workflow: Nincs workflow definiálva ehhez a feladat típushoz - text_unallowed_characters: Tiltott karakterek - text_comma_separated: Több érték megengedett (vesszÅ‘vel elválasztva) - text_issues_ref_in_commit_messages: Hivatkozás feladatokra, feladatok javítása a commit üzenetekben - text_issue_added: "%{author} új feladatot hozott létre %{id} sorszámmal." - text_issue_updated: "%{author} módosította a %{id} sorszámú feladatot." - text_wiki_destroy_confirmation: Biztosan törölni szeretné ezt a wiki-t minden tartalmával együtt ? - text_issue_category_destroy_question: "Néhány feladat (%{count}) hozzá van rendelve ehhez a kategóriához. Mit szeretne tenni?" - text_issue_category_destroy_assignments: Kategória hozzárendelés megszüntetése - text_issue_category_reassign_to: Feladatok újra hozzárendelése másik kategóriához - text_user_mail_option: "A nem kiválasztott projektekrÅ‘l csak akkor kap értesítést, ha figyelést kér rá, vagy részt vesz benne (pl. Ön a létrehozó, vagy a hozzárendelÅ‘)" - text_no_configuration_data: "Szerepkörök, feladat típusok, feladat státuszok, és workflow adatok még nincsenek konfigurálva.\nErÅ‘sen ajánlott, az alapértelmezett konfiguráció betöltése, és utána módosíthatja azt." - text_load_default_configuration: Alapértelmezett konfiguráció betöltése - text_status_changed_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Biztos benne, hogy törölni szeretné a kijelölt feladato(ka)t ?' - text_select_project_modules: 'Válassza ki az engedélyezett modulokat ehhez a projekthez:' - text_default_administrator_account_changed: Alapértelmezett adminisztrátor fiók megváltoztatva - text_file_repository_writable: Fájl tároló írható - text_rmagick_available: RMagick elérhetÅ‘ (nem kötelezÅ‘) - text_destroy_time_entries_question: "%{hours} órányi munka van rögzítve a feladatokon, amiket törölni szeretne. Mit szeretne tenni?" - text_destroy_time_entries: A rögzített órák törlése - text_assign_time_entries_to_project: A rögzített órák hozzárendelése a projekthez - text_reassign_time_entries: 'A rögzített órák újra hozzárendelése másik feladathoz:' - - default_role_manager: VezetÅ‘ - default_role_developer: FejlesztÅ‘ - default_role_reporter: BejelentÅ‘ - default_tracker_bug: Hiba - default_tracker_feature: Fejlesztés - default_tracker_support: Támogatás - default_issue_status_new: Új - default_issue_status_in_progress: Folyamatban - default_issue_status_resolved: Megoldva - default_issue_status_feedback: Visszajelzés - default_issue_status_closed: Lezárt - default_issue_status_rejected: Elutasított - default_doc_category_user: Felhasználói dokumentáció - default_doc_category_tech: Technikai dokumentáció - default_priority_low: Alacsony - default_priority_normal: Normál - default_priority_high: Magas - default_priority_urgent: SürgÅ‘s - default_priority_immediate: Azonnal - default_activity_design: Tervezés - default_activity_development: Fejlesztés - - enumeration_issue_priorities: Feladat prioritások - enumeration_doc_categories: Dokumentum kategóriák - enumeration_activities: Tevékenységek (idÅ‘ rögzítés) - mail_body_reminder: "%{count} neked kiosztott feladat határidÅ‘s az elkövetkezÅ‘ %{days} napban:" - mail_subject_reminder: "%{count} feladat határidÅ‘s az elkövetkezÅ‘ %{days} napban" - text_user_wrote: "%{value} írta:" - label_duplicated_by: duplikálta - setting_enabled_scm: ForráskódkezelÅ‘ (SCM) engedélyezése - text_enumeration_category_reassign_to: 'Újra hozzárendelés ehhez:' - text_enumeration_destroy_question: "%{count} objektum van hozzárendelve ehhez az értékhez." - label_incoming_emails: Beérkezett levelek - label_generate_key: Kulcs generálása - setting_mail_handler_api_enabled: Web Service engedélyezése a beérkezett levelekhez - setting_mail_handler_api_key: API kulcs - text_email_delivery_not_configured: "Az E-mail küldés nincs konfigurálva, és az értesítések ki vannak kapcsolva.\nÃllítsd be az SMTP szervert a config/configuration.yml fájlban és indítsd újra az alkalmazást, hogy érvénybe lépjen." - field_parent_title: SzülÅ‘ oldal - label_issue_watchers: MegfigyelÅ‘k - button_quote: Hozzászólás / Idézet - setting_sequential_project_identifiers: Szekvenciális projekt azonosítók generálása - notice_unable_delete_version: A verziót nem lehet törölni - label_renamed: átnevezve - label_copied: lemásolva - setting_plain_text_mail: csak szöveg (nem HTML) - permission_view_files: Fájlok megtekintése - permission_edit_issues: Feladatok szerkesztése - permission_edit_own_time_entries: Saját idÅ‘napló szerkesztése - permission_manage_public_queries: Nyilvános kérések kezelése - permission_add_issues: Feladat felvétele - permission_log_time: IdÅ‘ rögzítése - permission_view_changesets: Változáskötegek megtekintése - permission_view_time_entries: IdÅ‘rögzítések megtekintése - permission_manage_versions: Verziók kezelése - permission_manage_wiki: Wiki kezelése - permission_manage_categories: Feladat kategóriák kezelése - permission_protect_wiki_pages: Wiki oldalak védelme - permission_comment_news: Hírek kommentelése - permission_delete_messages: Üzenetek törlése - permission_select_project_modules: Projekt modulok kezelése - permission_manage_documents: Dokumentumok kezelése - permission_edit_wiki_pages: Wiki oldalak szerkesztése - permission_add_issue_watchers: MegfigyelÅ‘k felvétele - permission_view_gantt: Gannt diagramm megtekintése - permission_move_issues: Feladatok mozgatása - permission_manage_issue_relations: Feladat kapcsolatok kezelése - permission_delete_wiki_pages: Wiki oldalak törlése - permission_manage_boards: Fórumok kezelése - permission_delete_wiki_pages_attachments: Csatolmányok törlése - permission_view_wiki_edits: Wiki történet megtekintése - permission_add_messages: Üzenet beküldése - permission_view_messages: Üzenetek megtekintése - permission_manage_files: Fájlok kezelése - permission_edit_issue_notes: Jegyzetek szerkesztése - permission_manage_news: Hírek kezelése - permission_view_calendar: Naptár megtekintése - permission_manage_members: Tagok kezelése - permission_edit_messages: Üzenetek szerkesztése - permission_delete_issues: Feladatok törlése - permission_view_issue_watchers: MegfigyelÅ‘k listázása - permission_manage_repository: Tárolók kezelése - permission_commit_access: Commit hozzáférés - permission_browse_repository: Tároló böngészése - permission_view_documents: Dokumetumok megtekintése - permission_edit_project: Projekt szerkesztése - permission_add_issue_notes: Jegyzet rögzítése - permission_save_queries: Kérések mentése - permission_view_wiki_pages: Wiki megtekintése - permission_rename_wiki_pages: Wiki oldalak átnevezése - permission_edit_time_entries: IdÅ‘naplók szerkesztése - permission_edit_own_issue_notes: Saját jegyzetek szerkesztése - setting_gravatar_enabled: Felhasználói fényképek engedélyezése - label_example: Példa - text_repository_usernames_mapping: "Ãllítsd be a felhasználó összerendeléseket a Redmine, és a tároló logban található felhasználók között.\nAz azonos felhasználó nevek összerendelése automatikusan megtörténik." - permission_edit_own_messages: Saját üzenetek szerkesztése - permission_delete_own_messages: Saját üzenetek törlése - label_user_activity: "%{value} tevékenységei" - label_updated_time_by: "Módosította %{author} %{age}" - text_diff_truncated: '... A diff fájl vége nem jelenik meg, mert hosszab, mint a megjeleníthetÅ‘ sorok száma.' - setting_diff_max_lines_displayed: A megjelenítendÅ‘ sorok száma (maximum) a diff fájloknál - text_plugin_assets_writable: Plugin eszközök könyvtár írható - warning_attachments_not_saved: "%{count} fájl mentése nem sikerült." - button_create_and_continue: Létrehozás és folytatás - text_custom_field_possible_values_info: 'Értékenként egy sor' - label_display: Megmutat - field_editable: SzerkeszthetÅ‘ - setting_repository_log_display_limit: Maximum hány revíziót mutasson meg a log megjelenítésekor - setting_file_max_size_displayed: Maximum mekkora szövegfájlokat jelenítsen meg soronkénti összehasonlításnál - field_watcher: MegfigyelÅ‘ - setting_openid: OpenID regisztráció és bejelentkezés engedélyezése - field_identity_url: OpenID URL - label_login_with_open_id_option: bejelentkezés OpenID használatával - field_content: Tartalom - label_descending: CsökkenÅ‘ - label_sort: Rendezés - label_ascending: NövekvÅ‘ - label_date_from_to: "%{start} -tól %{end} -ig" - label_greater_or_equal: ">=" - label_less_or_equal: "<=" - text_wiki_page_destroy_question: Ennek az oldalnak %{descendants} gyermek-, és leszármazott oldala van. Mit szeretne tenni? - text_wiki_page_reassign_children: Aloldalak hozzárendelése ehhez a szülÅ‘ oldalhoz - text_wiki_page_nullify_children: Aloldalak átalakítása fÅ‘oldallá - text_wiki_page_destroy_children: Minden aloldal és leszármazottjának törlése - setting_password_min_length: Minimum jelszó hosszúság - field_group_by: Szerint csoportosítva - mail_subject_wiki_content_updated: "'%{id}' wiki oldal frissítve" - label_wiki_content_added: Wiki oldal hozzáadva - mail_subject_wiki_content_added: "Új wiki oldal: '%{id}'" - mail_body_wiki_content_added: "%{author} létrehozta a '%{id}' wiki oldalt." - label_wiki_content_updated: Wiki oldal frissítve - mail_body_wiki_content_updated: "%{author} frissítette a '%{id}' wiki oldalt." - permission_add_project: Projekt létrehozása - setting_new_project_user_role_id: Projekt létrehozási jog nem adminisztrátor felhasználóknak - label_view_all_revisions: Összes verzió - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: Nincs feladat típus hozzárendelve ehhez a projekthez. Kérem ellenÅ‘rizze a projekt beállításait. - error_no_default_issue_status: Nincs alapértelmezett feladat státusz beállítva. Kérem ellenÅ‘rizze a beállításokat (Itt találja "Adminisztráció -> Feladat státuszok"). - text_journal_changed: "%{label} megváltozott, %{old} helyett %{new} lett" - text_journal_set_to: "%{label} új értéke: %{value}" - text_journal_deleted: "%{label} törölve lett (%{old})" - label_group_plural: Csoportok - label_group: Csoport - label_group_new: Új csoport - label_time_entry_plural: IdÅ‘ráfordítás - text_journal_added: "%{label} %{value} hozzáadva" - field_active: Aktív - enumeration_system_activity: Rendszertevékenység - permission_delete_issue_watchers: MegfigyelÅ‘k törlése - version_status_closed: lezárt - version_status_locked: zárolt - version_status_open: nyitott - error_can_not_reopen_issue_on_closed_version: Lezárt verzióhoz rendelt feladatot nem lehet újranyitni - label_user_anonymous: Névtelen - button_move_and_follow: Mozgatás és követés - setting_default_projects_modules: Alapértelmezett modulok az új projektekhez - setting_gravatar_default: Alapértelmezett Gravatar kép - field_sharing: Megosztás - label_version_sharing_hierarchy: Projekt hierarchiával - label_version_sharing_system: Minden projekttel - label_version_sharing_descendants: Alprojektekkel - label_version_sharing_tree: Projekt fával - label_version_sharing_none: Nincs megosztva - error_can_not_archive_project: A projektet nem lehet archiválni - button_duplicate: Duplikálás - button_copy_and_follow: Másolás és követés - label_copy_source: Forrás - setting_issue_done_ratio: Feladat készültségi szint számolása a következÅ‘ alapján - setting_issue_done_ratio_issue_status: Feladat státusz alapján - error_issue_done_ratios_not_updated: A feladat készültségi szintek nem lettek frissítve. - error_workflow_copy_target: Kérem válasszon cél feladat típus(oka)t és szerepkör(öke)t. - setting_issue_done_ratio_issue_field: A feladat mezÅ‘ alapján - label_copy_same_as_target: A céllal egyezÅ‘ - label_copy_target: Cél - notice_issue_done_ratios_updated: Feladat készültségi szintek frissítve. - error_workflow_copy_source: Kérem válasszon forrás feladat típust vagy szerepkört - label_update_issue_done_ratios: Feladat készültségi szintek frissítése - setting_start_of_week: A hét elsÅ‘ napja - permission_view_issues: Feladatok megtekintése - label_display_used_statuses_only: Csak olyan feladat státuszok megjelenítése, amit ez a feladat típus használ - label_revision_id: Revízió %{value} - label_api_access_key: API hozzáférési kulcs - label_api_access_key_created_on: API hozzáférési kulcs létrehozva %{value} ezelÅ‘tt - label_feeds_access_key: RSS hozzáférési kulcs - notice_api_access_key_reseted: Az API hozzáférési kulcsa újragenerálva. - setting_rest_api_enabled: REST web service engedélyezése - label_missing_api_access_key: Egy API hozzáférési kulcs hiányzik - label_missing_feeds_access_key: RSS hozzáférési kulcs hiányzik - button_show: Megmutat - text_line_separated: Több érték megadása lehetséges (soronként 1 érték). - setting_mail_handler_body_delimiters: E-mailek levágása a következÅ‘ sorok valamelyike esetén - permission_add_subprojects: Alprojektek létrehozása - label_subproject_new: Új alprojekt - text_own_membership_delete_confirmation: |- - Arra készül, hogy eltávolítja egyes vagy minden jogosultságát! Ezt követÅ‘en lehetséges, hogy nem fogja tudni szerkeszteni ezt a projektet! - Biztosan folyatni szeretné? - label_close_versions: Kész verziók lezárása - label_board_sticky: Sticky - setting_cache_formatted_text: Formázott szöveg gyorsítótárazása (Cache) - permission_export_wiki_pages: Wiki oldalak exportálása - permission_manage_project_activities: Projekt tevékenységek kezelése - label_board_locked: Zárolt - error_can_not_delete_custom_field: Nem lehet törölni az egyéni mezÅ‘t - permission_manage_subtasks: Alfeladatok kezelése - label_profile: Profil - error_unable_to_connect: Nem lehet csatlakozni (%{value}) - error_can_not_remove_role: Ez a szerepkör használatban van és ezért nem törölhetÅ‘- - field_parent_issue: SzülÅ‘ feladat - error_unable_delete_issue_status: Nem lehet törölni a feladat állapotát - label_subtask_plural: Alfeladatok - error_can_not_delete_tracker: Ebbe a kategóriába feladatok tartoznak és ezért nem törölhetÅ‘. - label_project_copy_notifications: Küldjön e-mail értesítéseket projektmásolás közben. - field_principal: FelelÅ‘s - label_my_page_block: Saját kezdÅ‘lap-blokk - notice_failed_to_save_members: "Nem sikerült menteni a tago(ka)t: %{errors}." - text_zoom_out: Kicsinyít - text_zoom_in: Nagyít - notice_unable_delete_time_entry: Az idÅ‘rögzítés nem törölhetÅ‘ - label_overall_spent_time: Összes ráfordított idÅ‘ - field_time_entries: IdÅ‘ rögzítés - project_module_gantt: Gantt - project_module_calendar: Naptár - button_edit_associated_wikipage: "Hozzárendelt Wiki oldal szerkesztése: %{page_title}" - field_text: Szöveg mezÅ‘ - label_user_mail_option_only_owner: Csak arról, aminek én vagyok a tulajdonosa - setting_default_notification_option: Alapértelmezett értesítési beállítások - label_user_mail_option_only_my_events: Csak az általam megfigyelt dolgokról vagy amiben részt veszek - label_user_mail_option_only_assigned: Csak a hozzámrendelt dolgokról - label_user_mail_option_none: Semilyen eseményrÅ‘l - field_member_of_group: Hozzárendelt csoport - field_assigned_to_role: Hozzárendelt szerepkör - notice_not_authorized_archived_project: A projekt, amihez hozzá szeretnél férni archiválva lett. - label_principal_search: "Felhasználó vagy csoport keresése:" - label_user_search: "Felhasználó keresése:" - field_visible: Látható - setting_emails_header: Emailek fejléce - setting_commit_logtime_activity_id: A rögzített idÅ‘höz tartozó tevékenység - text_time_logged_by_changeset: Alkalmazva a %{value} changeset-ben. - setting_commit_logtime_enabled: IdÅ‘rögzítés engedélyezése - notice_gantt_chart_truncated: A diagram le lett vágva, mert elérte a maximálisan megjeleníthetÅ‘ elemek számát (%{max}) - setting_gantt_items_limit: A gantt diagrammon megjeleníthetÅ‘ maximális elemek száma - field_warn_on_leaving_unsaved: Figyelmeztessen, nem mentett módosításokat tartalmazó oldal elhagyásakor - text_warn_on_leaving_unsaved: A jelenlegi oldal nem mentett módosításokat tartalmaz, ami elvész, ha elhagyja az oldalt. - label_my_queries: Egyéni lekérdezéseim - text_journal_changed_no_detail: "%{label} módosítva" - label_news_comment_added: Megjegyzés hozzáadva a hírhez - button_expand_all: Mindet kibont - button_collapse_all: Mindet összecsuk - label_additional_workflow_transitions_for_assignee: További átmenetek engedélyezettek, ha a felhasználó a hozzárendelt - label_additional_workflow_transitions_for_author: További átmenetek engedélyezettek, ha a felhasználó a szerzÅ‘ - label_bulk_edit_selected_time_entries: A kiválasztott idÅ‘ bejegyzések csoportos szerkesztése - text_time_entries_destroy_confirmation: Biztos benne, hogy törölni szeretné a kiválasztott idÅ‘ bejegyzés(eke)t? - label_role_anonymous: Anonymous - label_role_non_member: Nem tag - label_issue_note_added: Jegyzet hozzáadva - label_issue_status_updated: Ãllapot módosítva - label_issue_priority_updated: Prioritás módosítva - label_issues_visibility_own: A felhasználó által létrehozott vagy hozzárendelt feladatok - field_issues_visibility: Feladatok láthatósága - label_issues_visibility_all: Minden feladat - permission_set_own_issues_private: Saját feladatok beállítása nyilvánosra vagy privátra - field_is_private: Privát - permission_set_issues_private: Feladatok beállítása nyilvánosra vagy privátra - label_issues_visibility_public: Minden nem privát feladat - text_issues_destroy_descendants_confirmation: Ezzel törölni fog %{count} alfeladatot is. - field_commit_logs_encoding: Commit üzenetek kódlapja - field_scm_path_encoding: Elérési útvonal kódlapja - text_scm_path_encoding_note: "Alapértelmezett: UTF-8" - field_path_to_repository: A repository elérési útja - field_root_directory: Gyökér könyvtár - field_cvs_module: Modul - field_cvsroot: CVSROOT - text_mercurial_repository_note: Helyi repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Parancs - text_scm_command_version: Verzió - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 feladat - one: 1 feladat - other: "%{count} feladatok" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: mind - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Alprojektekkel - label_cross_project_tree: Projekt fával - label_cross_project_hierarchy: Projekt hierarchiával - label_cross_project_system: Minden projekttel - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3e8fae3ebee67b280950bc292ca802d88a6d99d2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3e8fae3ebee67b280950bc292ca802d88a6d99d2.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,114 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class PrincipalTest < ActiveSupport::TestCase + fixtures :users, :projects, :members, :member_roles + + def test_active_scope_should_return_groups_and_active_users + result = Principal.active.all + assert_include Group.first, result + assert_not_nil result.detect {|p| p.is_a?(User)} + assert_nil result.detect {|p| p.is_a?(User) && !p.active?} + assert_nil result.detect {|p| p.is_a?(AnonymousUser)} + end + + def test_member_of_scope_should_return_the_union_of_all_members + projects = Project.find_all_by_id(1, 2) + assert_equal projects.map(&:principals).flatten.sort, Principal.member_of(projects).sort + end + + def test_member_of_scope_should_be_empty_for_no_projects + assert_equal [], Principal.member_of([]).sort + end + + def test_not_member_of_scope_should_return_users_that_have_no_memberships + projects = Project.find_all_by_id(1, 2) + expected = (Principal.all - projects.map(&:memberships).flatten.map(&:principal)).sort + assert_equal expected, Principal.not_member_of(projects).sort + end + + def test_not_member_of_scope_should_be_empty_for_no_projects + assert_equal [], Principal.not_member_of([]).sort + end + + def test_sorted_scope_should_sort_users_before_groups + scope = Principal.where("type <> ?", 'AnonymousUser') + expected_order = scope.all.sort do |a, b| + if a.is_a?(User) && b.is_a?(Group) + -1 + elsif a.is_a?(Group) && b.is_a?(User) + 1 + else + a.name.downcase <=> b.name.downcase + end + end + assert_equal expected_order.map(&:name).map(&:downcase), scope.sorted.all.map(&:name).map(&:downcase) + end + + test "like scope should search login" do + results = Principal.like('jsmi') + + assert results.any? + assert results.all? {|u| u.login.match(/jsmi/i) } + end + + test "like scope should search firstname" do + results = Principal.like('john') + + assert results.any? + assert results.all? {|u| u.firstname.match(/john/i) } + end + + test "like scope should search lastname" do + results = Principal.like('smi') + + assert results.any? + assert results.all? {|u| u.lastname.match(/smi/i) } + end + + test "like scope should search mail" do + results = Principal.like('somenet') + + assert results.any? + assert results.all? {|u| u.mail.match(/somenet/i) } + end + + test "like scope should search firstname and lastname" do + results = Principal.like('john smi') + + assert_equal 1, results.count + assert_equal User.find(2), results.first + end + + test "like scope should search lastname and firstname" do + results = Principal.like('smith joh') + + assert_equal 1, results.count + assert_equal User.find(2), results.first + end + + def test_like_scope_with_cyrillic_name + user = User.generate!(:firstname => 'Соболев', :lastname => 'ДениÑ') + results = Principal.like('Собо') + assert_equal 1, results.count + assert_equal user, results.first + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3ecbdf2b380b8b58c6f31309dfb99930076f3961.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3ecbdf2b380b8b58c6f31309dfb99930076f3961.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,374 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * This file is part of DotClear. + * Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All + * rights reserved. + * + * DotClear 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. + * + * DotClear 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 DotClear; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ***** END LICENSE BLOCK ***** +*/ + +/* Modified by JP LANG for textile formatting */ + +function jsToolBar(textarea) { + if (!document.createElement) { return; } + + if (!textarea) { return; } + + if ((typeof(document["selection"]) == "undefined") + && (typeof(textarea["setSelectionRange"]) == "undefined")) { + return; + } + + this.textarea = textarea; + + this.editor = document.createElement('div'); + this.editor.className = 'jstEditor'; + + this.textarea.parentNode.insertBefore(this.editor,this.textarea); + this.editor.appendChild(this.textarea); + + this.toolbar = document.createElement("div"); + this.toolbar.className = 'jstElements'; + this.editor.parentNode.insertBefore(this.toolbar,this.editor); + + // Dragable resizing + if (this.editor.addEventListener && navigator.appVersion.match(/\bMSIE\b/)) + { + this.handle = document.createElement('div'); + this.handle.className = 'jstHandle'; + var dragStart = this.resizeDragStart; + var This = this; + this.handle.addEventListener('mousedown',function(event) { dragStart.call(This,event); },false); + // fix memory leak in Firefox (bug #241518) + window.addEventListener('unload',function() { + var del = This.handle.parentNode.removeChild(This.handle); + delete(This.handle); + },false); + + this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling); + } + + this.context = null; + this.toolNodes = {}; // lorsque la toolbar est dessinée , cet objet est garni + // de raccourcis vers les éléments DOM correspondants aux outils. +} + +function jsButton(title, fn, scope, className) { + if(typeof jsToolBar.strings == 'undefined') { + this.title = title || null; + } else { + this.title = jsToolBar.strings[title] || title || null; + } + this.fn = fn || function(){}; + this.scope = scope || null; + this.className = className || null; +} +jsButton.prototype.draw = function() { + if (!this.scope) return null; + + var button = document.createElement('button'); + button.setAttribute('type','button'); + button.tabIndex = 200; + if (this.className) button.className = this.className; + button.title = this.title; + var span = document.createElement('span'); + span.appendChild(document.createTextNode(this.title)); + button.appendChild(span); + + if (this.icon != undefined) { + button.style.backgroundImage = 'url('+this.icon+')'; + } + if (typeof(this.fn) == 'function') { + var This = this; + button.onclick = function() { try { This.fn.apply(This.scope, arguments) } catch (e) {} return false; }; + } + return button; +} + +function jsSpace(id) { + this.id = id || null; + this.width = null; +} +jsSpace.prototype.draw = function() { + var span = document.createElement('span'); + if (this.id) span.id = this.id; + span.appendChild(document.createTextNode(String.fromCharCode(160))); + span.className = 'jstSpacer'; + if (this.width) span.style.marginRight = this.width+'px'; + + return span; +} + +function jsCombo(title, options, scope, fn, className) { + this.title = title || null; + this.options = options || null; + this.scope = scope || null; + this.fn = fn || function(){}; + this.className = className || null; +} +jsCombo.prototype.draw = function() { + if (!this.scope || !this.options) return null; + + var select = document.createElement('select'); + if (this.className) select.className = className; + select.title = this.title; + + for (var o in this.options) { + //var opt = this.options[o]; + var option = document.createElement('option'); + option.value = o; + option.appendChild(document.createTextNode(this.options[o])); + select.appendChild(option); + } + + var This = this; + select.onchange = function() { + try { + This.fn.call(This.scope, this.value); + } catch (e) { alert(e); } + + return false; + } + + return select; +} + + +jsToolBar.prototype = { + base_url: '', + mode: 'wiki', + elements: {}, + help_link: '', + + getMode: function() { + return this.mode; + }, + + setMode: function(mode) { + this.mode = mode || 'wiki'; + }, + + switchMode: function(mode) { + mode = mode || 'wiki'; + this.draw(mode); + }, + + setHelpLink: function(link) { + this.help_link = link; + }, + + button: function(toolName) { + var tool = this.elements[toolName]; + if (typeof tool.fn[this.mode] != 'function') return null; + var b = new jsButton(tool.title, tool.fn[this.mode], this, 'jstb_'+toolName); + if (tool.icon != undefined) b.icon = tool.icon; + return b; + }, + space: function(toolName) { + var tool = new jsSpace(toolName) + if (this.elements[toolName].width !== undefined) + tool.width = this.elements[toolName].width; + return tool; + }, + combo: function(toolName) { + var tool = this.elements[toolName]; + var length = tool[this.mode].list.length; + + if (typeof tool[this.mode].fn != 'function' || length == 0) { + return null; + } else { + var options = {}; + for (var i=0; i < length; i++) { + var opt = tool[this.mode].list[i]; + options[opt] = tool.options[opt]; + } + return new jsCombo(tool.title, options, this, tool[this.mode].fn); + } + }, + draw: function(mode) { + this.setMode(mode); + + // Empty toolbar + while (this.toolbar.hasChildNodes()) { + this.toolbar.removeChild(this.toolbar.firstChild) + } + this.toolNodes = {}; // vide les raccourcis DOM/**/ + + // Draw toolbar elements + var b, tool, newTool; + + for (var i in this.elements) { + b = this.elements[i]; + + var disabled = + b.type == undefined || b.type == '' + || (b.disabled != undefined && b.disabled) + || (b.context != undefined && b.context != null && b.context != this.context); + + if (!disabled && typeof this[b.type] == 'function') { + tool = this[b.type](i); + if (tool) newTool = tool.draw(); + if (newTool) { + this.toolNodes[i] = newTool; //mémorise l'accès DOM pour usage éventuel ultérieur + this.toolbar.appendChild(newTool); + } + } + } + }, + + singleTag: function(stag,etag) { + stag = stag || null; + etag = etag || stag; + + if (!stag || !etag) { return; } + + this.encloseSelection(stag,etag); + }, + + encloseLineSelection: function(prefix, suffix, fn) { + this.textarea.focus(); + + prefix = prefix || ''; + suffix = suffix || ''; + + var start, end, sel, scrollPos, subst, res; + + if (typeof(document["selection"]) != "undefined") { + sel = document.selection.createRange().text; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + start = this.textarea.selectionStart; + end = this.textarea.selectionEnd; + scrollPos = this.textarea.scrollTop; + // go to the start of the line + start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length; + // go to the end of the line + end = this.textarea.value.length - this.textarea.value.substring(end, this.textarea.value.length).replace(/^[^\r\n]*/, '').length; + sel = this.textarea.value.substring(start, end); + } + + if (sel.match(/ $/)) { // exclude ending space char, if any + sel = sel.substring(0, sel.length - 1); + suffix = suffix + " "; + } + + if (typeof(fn) == 'function') { + res = (sel) ? fn.call(this,sel) : fn(''); + } else { + res = (sel) ? sel : ''; + } + + subst = prefix + res + suffix; + + if (typeof(document["selection"]) != "undefined") { + document.selection.createRange().text = subst; + var range = this.textarea.createTextRange(); + range.collapse(false); + range.move('character', -suffix.length); + range.select(); + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + this.textarea.value = this.textarea.value.substring(0, start) + subst + + this.textarea.value.substring(end); + if (sel) { + this.textarea.setSelectionRange(start + subst.length, start + subst.length); + } else { + this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); + } + this.textarea.scrollTop = scrollPos; + } + }, + + encloseSelection: function(prefix, suffix, fn) { + this.textarea.focus(); + + prefix = prefix || ''; + suffix = suffix || ''; + + var start, end, sel, scrollPos, subst, res; + + if (typeof(document["selection"]) != "undefined") { + sel = document.selection.createRange().text; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + start = this.textarea.selectionStart; + end = this.textarea.selectionEnd; + scrollPos = this.textarea.scrollTop; + sel = this.textarea.value.substring(start, end); + } + + if (sel.match(/ $/)) { // exclude ending space char, if any + sel = sel.substring(0, sel.length - 1); + suffix = suffix + " "; + } + + if (typeof(fn) == 'function') { + res = (sel) ? fn.call(this,sel) : fn(''); + } else { + res = (sel) ? sel : ''; + } + + subst = prefix + res + suffix; + + if (typeof(document["selection"]) != "undefined") { + document.selection.createRange().text = subst; + var range = this.textarea.createTextRange(); + range.collapse(false); + range.move('character', -suffix.length); + range.select(); +// this.textarea.caretPos -= suffix.length; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + this.textarea.value = this.textarea.value.substring(0, start) + subst + + this.textarea.value.substring(end); + if (sel) { + this.textarea.setSelectionRange(start + subst.length, start + subst.length); + } else { + this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); + } + this.textarea.scrollTop = scrollPos; + } + }, + + stripBaseURL: function(url) { + if (this.base_url != '') { + var pos = url.indexOf(this.base_url); + if (pos == 0) { + url = url.substr(this.base_url.length); + } + } + + return url; + } +}; + +/** Resizer +-------------------------------------------------------- */ +jsToolBar.prototype.resizeSetStartH = function() { + this.dragStartH = this.textarea.offsetHeight + 0; +}; +jsToolBar.prototype.resizeDragStart = function(event) { + var This = this; + this.dragStartY = event.clientY; + this.resizeSetStartH(); + document.addEventListener('mousemove', this.dragMoveHdlr=function(event){This.resizeDragMove(event);}, false); + document.addEventListener('mouseup', this.dragStopHdlr=function(event){This.resizeDragStop(event);}, false); +}; + +jsToolBar.prototype.resizeDragMove = function(event) { + this.textarea.style.height = (this.dragStartH+event.clientY-this.dragStartY)+'px'; +}; + +jsToolBar.prototype.resizeDragStop = function(event) { + document.removeEventListener('mousemove', this.dragMoveHdlr, false); + document.removeEventListener('mouseup', this.dragStopHdlr, false); +}; diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3ed19787e1d57a5080fdcab4117423d0be237d87.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3ed19787e1d57a5080fdcab4117423d0be237d87.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,84 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class CalendarsControllerTest < ActionController::TestCase + fixtures :projects, + :trackers, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + def test_show + get :show, :project_id => 1 + assert_response :success + assert_template 'calendar' + assert_not_nil assigns(:calendar) + end + + def test_show_should_run_custom_queries + @query = IssueQuery.create!(:name => 'Calendar', :visibility => IssueQuery::VISIBILITY_PUBLIC) + + get :show, :query_id => @query.id + assert_response :success + end + + def test_cross_project_calendar + get :show + assert_response :success + assert_template 'calendar' + assert_not_nil assigns(:calendar) + end + + def test_week_number_calculation + Setting.start_of_week = 7 + + get :show, :month => '1', :year => '2010' + assert_response :success + + assert_select 'tr' do + assert_select 'td.week-number', :text => '53' + assert_select 'td.odd', :text => '27' + assert_select 'td.even', :text => '2' + end + + assert_select 'tr' do + assert_select 'td.week-number', :text => '1' + assert_select 'td.odd', :text => '3' + assert_select 'td.even', :text => '9' + end + + Setting.start_of_week = 1 + get :show, :month => '1', :year => '2010' + assert_response :success + + assert_select 'tr' do + assert_select 'td.week-number', :text => '53' + assert_select 'td.even', :text => '28' + assert_select 'td.even', :text => '3' + end + + assert_select 'tr' do + assert_select 'td.week-number', :text => '1' + assert_select 'td.even', :text => '4' + assert_select 'td.even', :text => '10' + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3e/3ed8c1b8169d56ca657416dab8458700070dce49.svn-base --- a/.svn/pristine/3e/3ed8c1b8169d56ca657416dab8458700070dce49.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,605 +0,0 @@ -/* Redmine - project management software - Copyright (C) 2006-2012 Jean-Philippe Lang */ - -function checkAll(id, checked) { - if (checked) { - $('#'+id).find('input[type=checkbox]').attr('checked', true); - } else { - $('#'+id).find('input[type=checkbox]').removeAttr('checked'); - } -} - -function toggleCheckboxesBySelector(selector) { - var all_checked = true; - $(selector).each(function(index) { - if (!$(this).is(':checked')) { all_checked = false; } - }); - $(selector).attr('checked', !all_checked) -} - -function showAndScrollTo(id, focus) { - $('#'+id).show(); - if (focus!=null) { - $('#'+focus).focus(); - } - $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100); -} - -function toggleRowGroup(el) { - var tr = $(el).parents('tr').first(); - var n = tr.next(); - tr.toggleClass('open'); - while (n.length && !n.hasClass('group')) { - n.toggle(); - n = n.next('tr'); - } -} - -function collapseAllRowGroups(el) { - var tbody = $(el).parents('tbody').first(); - tbody.children('tr').each(function(index) { - if ($(this).hasClass('group')) { - $(this).removeClass('open'); - } else { - $(this).hide(); - } - }); -} - -function expandAllRowGroups(el) { - var tbody = $(el).parents('tbody').first(); - tbody.children('tr').each(function(index) { - if ($(this).hasClass('group')) { - $(this).addClass('open'); - } else { - $(this).show(); - } - }); -} - -function toggleAllRowGroups(el) { - var tr = $(el).parents('tr').first(); - if (tr.hasClass('open')) { - collapseAllRowGroups(el); - } else { - expandAllRowGroups(el); - } -} - -function toggleFieldset(el) { - var fieldset = $(el).parents('fieldset').first(); - fieldset.toggleClass('collapsed'); - fieldset.children('div').toggle(); -} - -function hideFieldset(el) { - var fieldset = $(el).parents('fieldset').first(); - fieldset.toggleClass('collapsed'); - fieldset.children('div').hide(); -} - -function initFilters(){ - $('#add_filter_select').change(function(){ - addFilter($(this).val(), '', []); - }); - $('#filters-table td.field input[type=checkbox]').each(function(){ - toggleFilter($(this).val()); - }); - $('#filters-table td.field input[type=checkbox]').live('click',function(){ - toggleFilter($(this).val()); - }); - $('#filters-table .toggle-multiselect').live('click',function(){ - toggleMultiSelect($(this).siblings('select')); - }); - $('#filters-table input[type=text]').live('keypress', function(e){ - if (e.keyCode == 13) submit_query_form("query_form"); - }); -} - -function addFilter(field, operator, values) { - var fieldId = field.replace('.', '_'); - var tr = $('#tr_'+fieldId); - if (tr.length > 0) { - tr.show(); - } else { - buildFilterRow(field, operator, values); - } - $('#cb_'+fieldId).attr('checked', true); - toggleFilter(field); - $('#add_filter_select').val('').children('option').each(function(){ - if ($(this).attr('value') == field) { - $(this).attr('disabled', true); - } - }); -} - -function buildFilterRow(field, operator, values) { - var fieldId = field.replace('.', '_'); - var filterTable = $("#filters-table"); - var filterOptions = availableFilters[field]; - var operators = operatorByType[filterOptions['type']]; - var filterValues = filterOptions['values']; - var i, select; - - var tr = $('').attr('id', 'tr_'+fieldId).html( - '' + - '' + - '  ' - ); - select = tr.find('td.values select'); - if (values.length > 1) {select.attr('multiple', true)}; - for (i=0;i'); - if ($.isArray(filterValue)) { - option.val(filterValue[1]).text(filterValue[0]); - if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);} - } else { - option.val(filterValue).text(filterValue); - if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);} - } - select.append(option); - } - break; - case "date": - case "date_past": - tr.find('td.values').append( - '' + - ' ' + - ' '+labelDayPlural+'' - ); - $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions); - $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions); - $('#values_'+fieldId).val(values[0]); - break; - case "string": - case "text": - tr.find('td.values').append( - '' - ); - $('#values_'+fieldId).val(values[0]); - break; - case "relation": - tr.find('td.values').append( - '' + - '' - ); - $('#values_'+fieldId).val(values[0]); - select = tr.find('td.values select'); - for (i=0;i'); - option.val(filterValue[1]).text(filterValue[0]); - if (values[0] == filterValue[1]) {option.attr('selected', true)}; - select.append(option); - } - case "integer": - case "float": - tr.find('td.values').append( - '' + - ' ' - ); - $('#values_'+fieldId+'_1').val(values[0]); - $('#values_'+fieldId+'_2').val(values[1]); - break; - } -} - -function toggleFilter(field) { - var fieldId = field.replace('.', '_'); - if ($('#cb_' + fieldId).is(':checked')) { - $("#operators_" + fieldId).show().removeAttr('disabled'); - toggleOperator(field); - } else { - $("#operators_" + fieldId).hide().attr('disabled', true); - enableValues(field, []); - } -} - -function enableValues(field, indexes) { - var fieldId = field.replace('.', '_'); - $('#tr_'+fieldId+' td.values .value').each(function(index) { - if ($.inArray(index, indexes) >= 0) { - $(this).removeAttr('disabled'); - $(this).parents('span').first().show(); - } else { - $(this).val(''); - $(this).attr('disabled', true); - $(this).parents('span').first().hide(); - } - - if ($(this).hasClass('group')) { - $(this).addClass('open'); - } else { - $(this).show(); - } - }); -} - -function toggleOperator(field) { - var fieldId = field.replace('.', '_'); - var operator = $("#operators_" + fieldId); - switch (operator.val()) { - case "!*": - case "*": - case "t": - case "w": - case "o": - case "c": - enableValues(field, []); - break; - case "><": - enableValues(field, [0,1]); - break; - case "t+": - case ">t-": - case "= 10) return false; - fileFieldCount++; - var s = fields.children('span').first().clone(); - s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val(''); - s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val(''); - fields.append(s); -} - -function removeFileField(el) { - var fields = $('#attachments_fields'); - var s = $(el).parents('span').first(); - if (fields.children().length > 1) { - s.remove(); - } else { - s.children('input.file').val(''); - s.children('input.description').val(''); - } -} - -function checkFileSize(el, maxSize, message) { - var files = el.files; - if (files) { - for (var i=0; i maxSize) { - alert(message); - el.value = ""; - } - } - } -} - -function showTab(name) { - $('div#content .tab-content').hide(); - $('div.tabs a').removeClass('selected'); - $('#tab-content-' + name).show(); - $('#tab-' + name).addClass('selected'); - return false; -} - -function moveTabRight(el) { - var lis = $(el).parents('div.tabs').first().find('ul').children(); - var tabsWidth = 0; - var i = 0; - lis.each(function(){ - if ($(this).is(':visible')) { - tabsWidth += $(this).width() + 6; - } - }); - if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; } - while (i0) { - lis.eq(i-1).show(); - } -} - -function displayTabsButtons() { - var lis; - var tabsWidth = 0; - var el; - $('div.tabs').each(function() { - el = $(this); - lis = el.find('ul').children(); - lis.each(function(){ - if ($(this).is(':visible')) { - tabsWidth += $(this).width() + 6; - } - }); - if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) { - el.find('div.tabs-buttons').hide(); - } else { - el.find('div.tabs-buttons').show(); - } - }); -} - -function setPredecessorFieldsVisibility() { - var relationType = $('#relation_relation_type'); - if (relationType.val() == "precedes" || relationType.val() == "follows") { - $('#predecessor_fields').show(); - } else { - $('#predecessor_fields').hide(); - } -} - -function showModal(id, width) { - var el = $('#'+id).first(); - if (el.length == 0 || el.is(':visible')) {return;} - var title = el.find('h3.title').text(); - el.dialog({ - width: width, - modal: true, - resizable: false, - dialogClass: 'modal', - title: title - }); - el.find("input[type=text], input[type=submit]").first().focus(); -} - -function hideModal(el) { - var modal; - if (el) { - modal = $(el).parents('.ui-dialog-content'); - } else { - modal = $('#ajax-modal'); - } - modal.dialog("close"); -} - -function submitPreview(url, form, target) { - $.ajax({ - url: url, - type: 'post', - data: $('#'+form).serialize(), - success: function(data){ - $('#'+target).html(data); - } - }); -} - -function collapseScmEntry(id) { - $('.'+id).each(function() { - if ($(this).hasClass('open')) { - collapseScmEntry($(this).attr('id')); - } - $(this).hide(); - }); - $('#'+id).removeClass('open'); -} - -function expandScmEntry(id) { - $('.'+id).each(function() { - $(this).show(); - if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) { - expandScmEntry($(this).attr('id')); - } - }); - $('#'+id).addClass('open'); -} - -function scmEntryClick(id, url) { - el = $('#'+id); - if (el.hasClass('open')) { - collapseScmEntry(id); - el.addClass('collapsed'); - return false; - } else if (el.hasClass('loaded')) { - expandScmEntry(id); - el.removeClass('collapsed'); - return false; - } - if (el.hasClass('loading')) { - return false; - } - el.addClass('loading'); - $.ajax({ - url: url, - success: function(data){ - el.after(data); - el.addClass('open').addClass('loaded').removeClass('loading'); - } - }); - return true; -} - -function randomKey(size) { - var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'); - var key = ''; - for (i = 0; i < size; i++) { - key += chars[Math.floor(Math.random() * chars.length)]; - } - return key; -} - -// Can't use Rails' remote select because we need the form data -function updateIssueFrom(url) { - $.ajax({ - url: url, - type: 'post', - data: $('#issue-form').serialize() - }); -} - -function updateBulkEditFrom(url) { - $.ajax({ - url: url, - type: 'post', - data: $('#bulk_edit_form').serialize() - }); -} - -function observeAutocompleteField(fieldId, url) { - $(document).ready(function() { - $('#'+fieldId).autocomplete({ - source: url, - minLength: 2 - }); - }); -} - -function observeSearchfield(fieldId, targetId, url) { - $('#'+fieldId).each(function() { - var $this = $(this); - $this.attr('data-value-was', $this.val()); - var check = function() { - var val = $this.val(); - if ($this.attr('data-value-was') != val){ - $this.attr('data-value-was', val); - $.ajax({ - url: url, - type: 'get', - data: {q: $this.val()}, - success: function(data){ $('#'+targetId).html(data); }, - beforeSend: function(){ $this.addClass('ajax-loading'); }, - complete: function(){ $this.removeClass('ajax-loading'); } - }); - } - }; - var reset = function() { - if (timer) { - clearInterval(timer); - timer = setInterval(check, 300); - } - }; - var timer = setInterval(check, 300); - $this.bind('keyup click mousemove', reset); - }); -} - -function observeProjectModules() { - var f = function() { - /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */ - if ($('#project_enabled_module_names_issue_tracking').attr('checked')) { - $('#project_trackers').show(); - }else{ - $('#project_trackers').hide(); - } - }; - - $(window).load(f); - $('#project_enabled_module_names_issue_tracking').change(f); -} - -function initMyPageSortable(list, url) { - $('#list-'+list).sortable({ - connectWith: '.block-receiver', - tolerance: 'pointer', - update: function(){ - $.ajax({ - url: url, - type: 'post', - data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})} - }); - } - }); - $("#list-top, #list-left, #list-right").disableSelection(); -} - -var warnLeavingUnsavedMessage; -function warnLeavingUnsaved(message) { - warnLeavingUnsavedMessage = message; - - $('form').submit(function(){ - $('textarea').removeData('changed'); - }); - $('textarea').change(function(){ - $(this).data('changed', 'changed'); - }); - window.onbeforeunload = function(){ - var warn = false; - $('textarea').blur().each(function(){ - if ($(this).data('changed')) { - warn = true; - } - }); - if (warn) {return warnLeavingUnsavedMessage;} - }; -}; - -$(document).ready(function(){ - $('#ajax-indicator').bind('ajaxSend', function(){ - if ($('.ajax-loading').length == 0) { - $('#ajax-indicator').show(); - } - }); - $('#ajax-indicator').bind('ajaxStop', function(){ - $('#ajax-indicator').hide(); - }); -}); - -function hideOnLoad() { - $('.hol').hide(); -} - -function addFormObserversForDoubleSubmit() { - $('form[method=post]').each(function() { - if (!$(this).hasClass('multiple-submit')) { - $(this).submit(function(form_submission) { - if ($(form_submission.target).attr('data-submitted')) { - form_submission.preventDefault(); - } else { - $(form_submission.target).attr('data-submitted', true); - } - }); - } - }); -} - -$(document).ready(hideOnLoad); -$(document).ready(addFormObserversForDoubleSubmit); diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3f/3f96fdb167ef6475d848f64ee14b65e8b759b8e8.svn-base --- a/.svn/pristine/3f/3f96fdb167ef6475d848f64ee14b65e8b759b8e8.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Watcher < ActiveRecord::Base - belongs_to :watchable, :polymorphic => true - belongs_to :user - - validates_presence_of :user - validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] - validate :validate_user - - # Unwatch things that users are no longer allowed to view - def self.prune(options={}) - if options.has_key?(:user) - prune_single_user(options[:user], options) - else - pruned = 0 - User.find(:all, :conditions => "id IN (SELECT DISTINCT user_id FROM #{table_name})").each do |user| - pruned += prune_single_user(user, options) - end - pruned - end - end - - protected - - def validate_user - errors.add :user_id, :invalid unless user.nil? || user.active? - end - - private - - def self.prune_single_user(user, options={}) - return unless user.is_a?(User) - pruned = 0 - find(:all, :conditions => {:user_id => user.id}).each do |watcher| - next if watcher.watchable.nil? - - if options.has_key?(:project) - next unless watcher.watchable.respond_to?(:project) && watcher.watchable.project == options[:project] - end - - if watcher.watchable.respond_to?(:visible?) - unless watcher.watchable.visible?(user) - watcher.destroy - pruned += 1 - end - end - end - pruned - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3f/3fa16be8b85d9643db5001e63e3d93b330392b4b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3f/3fa16be8b85d9643db5001e63e3d93b330392b4b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,61 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingWatchersTest < ActionController::IntegrationTest + def test_watchers + assert_routing( + { :method => 'get', :path => "/watchers/new" }, + { :controller => 'watchers', :action => 'new' } + ) + assert_routing( + { :method => 'post', :path => "/watchers/append" }, + { :controller => 'watchers', :action => 'append' } + ) + assert_routing( + { :method => 'post', :path => "/watchers" }, + { :controller => 'watchers', :action => 'create' } + ) + assert_routing( + { :method => 'delete', :path => "/watchers" }, + { :controller => 'watchers', :action => 'destroy' } + ) + assert_routing( + { :method => 'get', :path => "/watchers/autocomplete_for_user" }, + { :controller => 'watchers', :action => 'autocomplete_for_user' } + ) + assert_routing( + { :method => 'post', :path => "/watchers/watch" }, + { :controller => 'watchers', :action => 'watch' } + ) + assert_routing( + { :method => 'delete', :path => "/watchers/watch" }, + { :controller => 'watchers', :action => 'unwatch' } + ) + assert_routing( + { :method => 'post', :path => "/issues/12/watchers.xml" }, + { :controller => 'watchers', :action => 'create', + :object_type => 'issue', :object_id => '12', :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/issues/12/watchers/3.xml" }, + { :controller => 'watchers', :action => 'destroy', + :object_type => 'issue', :object_id => '12', :user_id => '3', :format => 'xml'} + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3f/3fac3fc235a53c2e2d4d021dffb9a965b1506411.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3f/3fac3fc235a53c2e2d4d021dffb9a965b1506411.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddQueriesType < ActiveRecord::Migration + def up + add_column :queries, :type, :string + end + + def down + remove_column :queries, :type + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3f/3fc44a4cc04ed1435106069e4fc48b4490b1b6a0.svn-base --- a/.svn/pristine/3f/3fc44a4cc04ed1435106069e4fc48b4490b1b6a0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -<%= form_tag({:action => 'edit', :tab => 'repositories'}) do %> - -
    -<%= l(:setting_enabled_scm) %> -<%= hidden_field_tag 'settings[enabled_scm][]', '' %> - - - - - - - <% Redmine::Scm::Base.all.collect do |choice| %> - <% scm_class = "Repository::#{choice}".constantize %> - <% text, value = (choice.is_a?(Array) ? choice : [choice, choice]) %> - <% setting = :enabled_scm %> - <% enabled = Setting.send(setting).include?(value) %> - - - - - - <% end %> -
    <%= l(:text_scm_command) %><%= l(:text_scm_command_version) %>
    - - - <% if enabled %> - <%= - image_tag( - (scm_class.scm_available ? 'true.png' : 'exclamation.png'), - :style => "vertical-align:bottom;" - ) - %> - <%= scm_class.scm_command %> - <% end %> - - <%= scm_class.scm_version_string if enabled %> -
    -

    <%= l(:text_scm_config) %>

    -
    - -
    -

    <%= setting_check_box :autofetch_changesets %>

    - -

    <%= setting_check_box :sys_api_enabled, - :onclick => - "if (this.checked) { $('#settings_sys_api_key').removeAttr('disabled'); } else { $('#settings_sys_api_key').attr('disabled', true); }" %>

    - -

    <%= setting_text_field :sys_api_key, - :size => 30, - :id => 'settings_sys_api_key', - :disabled => !Setting.sys_api_enabled?, - :label => :setting_mail_handler_api_key %> - <%= link_to_function l(:label_generate_key), - "if (!$('#settings_sys_api_key').attr('disabled')) { $('#settings_sys_api_key').val(randomKey(20)) }" %> -

    - -

    <%= setting_text_field :repository_log_display_limit, :size => 6 %>

    -
    - -
    -<%= l(:text_issues_ref_in_commit_messages) %> -

    <%= setting_text_field :commit_ref_keywords, :size => 30 %> -<%= l(:text_comma_separated) %>

    - -

    <%= setting_text_field :commit_fix_keywords, :size => 30 %> - <%= l(:label_applied_status) %>: <%= setting_select :commit_fix_status_id, - [["", 0]] + - IssueStatus.find(:all).collect{ - |status| [status.name, status.id.to_s] - }, - :label => false %> - <%= l(:field_done_ratio) %>: <%= setting_select :commit_fix_done_ratio, - (0..10).to_a.collect {|r| ["#{r*10} %", "#{r*10}"] }, - :blank => :label_no_change_option, - :label => false %> -<%= l(:text_comma_separated) %>

    - -

    <%= setting_check_box :commit_cross_project_ref %>

    - -

    <%= setting_check_box :commit_logtime_enabled, - :onclick => - "if (this.checked) { $('#settings_commit_logtime_activity_id').removeAttr('disabled'); } else { $('#settings_commit_logtime_activity_id').attr('disabled', true); }"%>

    - -

    <%= setting_select :commit_logtime_activity_id, - [[l(:label_default), 0]] + - TimeEntryActivity.shared.active.collect{|activity| [activity.name, activity.id.to_s]}, - :disabled => !Setting.commit_logtime_enabled?%>

    -
    - -<%= submit_tag l(:button_save) %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3f/3feb470624b1737721fc273bdc7bfd57c3f458eb.svn-base --- a/.svn/pristine/3f/3feb470624b1737721fc273bdc7bfd57c3f458eb.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class Redmine::CipheringTest < ActiveSupport::TestCase - - def test_password_should_be_encrypted - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') - assert_equal 'foo', r.password - assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) - end - end - - def test_password_should_be_clear_with_blank_key - Redmine::Configuration.with 'database_cipher_key' => '' do - r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') - assert_equal 'foo', r.password - assert_equal 'foo', r.read_attribute(:password) - end - end - - def test_password_should_be_clear_with_nil_key - Redmine::Configuration.with 'database_cipher_key' => nil do - r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') - assert_equal 'foo', r.password - assert_equal 'foo', r.read_attribute(:password) - end - end - - def test_blank_password_should_be_clear - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - r = Repository::Subversion.create!(:password => '', :url => 'file:///tmp', :identifier => 'svn') - assert_equal '', r.password - assert_equal '', r.read_attribute(:password) - end - end - - def test_unciphered_password_should_be_readable - Redmine::Configuration.with 'database_cipher_key' => nil do - r = Repository::Subversion.create!(:password => 'clear', :url => 'file:///tmp', :identifier => 'svn') - end - - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - r = Repository.first(:order => 'id DESC') - assert_equal 'clear', r.password - end - end - - def test_ciphered_password_with_no_cipher_key_configured_should_be_returned_ciphered - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - r = Repository::Subversion.create!(:password => 'clear', :url => 'file:///tmp', :identifier => 'svn') - end - - Redmine::Configuration.with 'database_cipher_key' => '' do - r = Repository.first(:order => 'id DESC') - # password can not be deciphered - assert_nothing_raised do - assert r.password.match(/\Aaes-256-cbc:.+\Z/) - end - end - end - - def test_encrypt_all - Repository.delete_all - Redmine::Configuration.with 'database_cipher_key' => nil do - Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'foo') - Repository::Subversion.create!(:password => 'bar', :url => 'file:///tmp', :identifier => 'bar') - end - - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - assert Repository.encrypt_all(:password) - r = Repository.first(:order => 'id DESC') - assert_equal 'bar', r.password - assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) - end - end - - def test_decrypt_all - Repository.delete_all - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'foo') - Repository::Subversion.create!(:password => 'bar', :url => 'file:///tmp', :identifier => 'bar') - - assert Repository.decrypt_all(:password) - r = Repository.first(:order => 'id DESC') - assert_equal 'bar', r.password - assert_equal 'bar', r.read_attribute(:password) - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3f/3fecfb31c324a69d1d6187e611496beedde573c3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3f/3fecfb31c324a69d1d6187e611496beedde573c3.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,217 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class MessagesControllerTest < ActionController::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, :boards, :messages, :enabled_modules + + def setup + User.current = nil + end + + def test_show + get :show, :board_id => 1, :id => 1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:board) + assert_not_nil assigns(:project) + assert_not_nil assigns(:topic) + end + + def test_show_should_contain_reply_field_tags_for_quoting + @request.session[:user_id] = 2 + get :show, :board_id => 1, :id => 1 + assert_response :success + + # tags required by MessagesController#quote + assert_tag 'input', :attributes => {:id => 'message_subject'} + assert_tag 'textarea', :attributes => {:id => 'message_content'} + assert_tag 'div', :attributes => {:id => 'reply'} + end + + def test_show_with_pagination + message = Message.find(1) + assert_difference 'Message.count', 30 do + 30.times do + message.children << Message.new(:subject => 'Reply', :content => 'Reply body', :author_id => 2, :board_id => 1) + end + end + get :show, :board_id => 1, :id => 1, :r => message.children.last(:order => 'id').id + assert_response :success + assert_template 'show' + replies = assigns(:replies) + assert_not_nil replies + assert !replies.include?(message.children.first(:order => 'id')) + assert replies.include?(message.children.last(:order => 'id')) + end + + def test_show_with_reply_permission + @request.session[:user_id] = 2 + get :show, :board_id => 1, :id => 1 + assert_response :success + assert_template 'show' + assert_tag :div, :attributes => { :id => 'reply' }, + :descendant => { :tag => 'textarea', :attributes => { :id => 'message_content' } } + end + + def test_show_message_not_found + get :show, :board_id => 1, :id => 99999 + assert_response 404 + end + + def test_show_message_from_invalid_board_should_respond_with_404 + get :show, :board_id => 999, :id => 1 + assert_response 404 + end + + def test_get_new + @request.session[:user_id] = 2 + get :new, :board_id => 1 + assert_response :success + assert_template 'new' + end + + def test_get_new_with_invalid_board + @request.session[:user_id] = 2 + get :new, :board_id => 99 + assert_response 404 + end + + def test_post_new + @request.session[:user_id] = 2 + ActionMailer::Base.deliveries.clear + + with_settings :notified_events => %w(message_posted) do + post :new, :board_id => 1, + :message => { :subject => 'Test created message', + :content => 'Message body'} + end + message = Message.find_by_subject('Test created message') + assert_not_nil message + assert_redirected_to "/boards/1/topics/#{message.to_param}" + assert_equal 'Message body', message.content + assert_equal 2, message.author_id + assert_equal 1, message.board_id + + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert_equal "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] Test created message", mail.subject + assert_mail_body_match 'Message body', mail + # author + assert mail.bcc.include?('jsmith@somenet.foo') + # project member + assert mail.bcc.include?('dlopper@somenet.foo') + end + + def test_get_edit + @request.session[:user_id] = 2 + get :edit, :board_id => 1, :id => 1 + assert_response :success + assert_template 'edit' + end + + def test_post_edit + @request.session[:user_id] = 2 + post :edit, :board_id => 1, :id => 1, + :message => { :subject => 'New subject', + :content => 'New body'} + assert_redirected_to '/boards/1/topics/1' + message = Message.find(1) + assert_equal 'New subject', message.subject + assert_equal 'New body', message.content + end + + def test_post_edit_sticky_and_locked + @request.session[:user_id] = 2 + post :edit, :board_id => 1, :id => 1, + :message => { :subject => 'New subject', + :content => 'New body', + :locked => '1', + :sticky => '1'} + assert_redirected_to '/boards/1/topics/1' + message = Message.find(1) + assert_equal true, message.sticky? + assert_equal true, message.locked? + end + + def test_post_edit_should_allow_to_change_board + @request.session[:user_id] = 2 + post :edit, :board_id => 1, :id => 1, + :message => { :subject => 'New subject', + :content => 'New body', + :board_id => 2} + assert_redirected_to '/boards/2/topics/1' + message = Message.find(1) + assert_equal Board.find(2), message.board + end + + def test_reply + @request.session[:user_id] = 2 + post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' } + reply = Message.order('id DESC').first + assert_redirected_to "/boards/1/topics/1?r=#{reply.id}" + assert Message.find_by_subject('Test reply') + end + + def test_destroy_topic + @request.session[:user_id] = 2 + assert_difference 'Message.count', -3 do + post :destroy, :board_id => 1, :id => 1 + end + assert_redirected_to '/projects/ecookbook/boards/1' + assert_nil Message.find_by_id(1) + end + + def test_destroy_reply + @request.session[:user_id] = 2 + assert_difference 'Message.count', -1 do + post :destroy, :board_id => 1, :id => 2 + end + assert_redirected_to '/boards/1/topics/1?r=2' + assert_nil Message.find_by_id(2) + end + + def test_quote + @request.session[:user_id] = 2 + xhr :get, :quote, :board_id => 1, :id => 3 + assert_response :success + assert_equal 'text/javascript', response.content_type + assert_template 'quote' + assert_include 'RE: First post', response.body + assert_include '> An other reply', response.body + end + + def test_preview_new + @request.session[:user_id] = 2 + post :preview, + :board_id => 1, + :message => {:subject => "", :content => "Previewed text"} + assert_response :success + assert_template 'common/_preview' + end + + def test_preview_edit + @request.session[:user_id] = 2 + post :preview, + :id => 4, + :board_id => 1, + :message => {:subject => "", :content => "Previewed text"} + assert_response :success + assert_template 'common/_preview' + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3f/3fecfd23ccdcab419a5392dfa395ed433f4bc031.svn-base --- a/.svn/pristine/3f/3fecfd23ccdcab419a5392dfa395ed433f4bc031.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Acts - module Searchable - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - # Options: - # * :columns - a column or an array of columns to search - # * :project_key - project foreign key (default to project_id) - # * :date_column - name of the datetime column (default to created_on) - # * :sort_order - name of the column used to sort results (default to :date_column or created_on) - # * :permission - permission required to search the model (default to :view_"objects") - def acts_as_searchable(options = {}) - return if self.included_modules.include?(Redmine::Acts::Searchable::InstanceMethods) - - cattr_accessor :searchable_options - self.searchable_options = options - - if searchable_options[:columns].nil? - raise 'No searchable column defined.' - elsif !searchable_options[:columns].is_a?(Array) - searchable_options[:columns] = [] << searchable_options[:columns] - end - - searchable_options[:project_key] ||= "#{table_name}.project_id" - searchable_options[:date_column] ||= "#{table_name}.created_on" - searchable_options[:order_column] ||= searchable_options[:date_column] - - # Should we search custom fields on this model ? - searchable_options[:search_custom_fields] = !reflect_on_association(:custom_values).nil? - - send :include, Redmine::Acts::Searchable::InstanceMethods - end - end - - module InstanceMethods - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - # Searches the model for the given tokens - # projects argument can be either nil (will search all projects), a project or an array of projects - # Returns the results and the results count - def search(tokens, projects=nil, options={}) - if projects.is_a?(Array) && projects.empty? - # no results - return [[], 0] - end - - # TODO: make user an argument - user = User.current - tokens = [] << tokens unless tokens.is_a?(Array) - projects = [] << projects unless projects.nil? || projects.is_a?(Array) - - find_options = {:include => searchable_options[:include]} - find_options[:order] = "#{searchable_options[:order_column]} " + (options[:before] ? 'DESC' : 'ASC') - - limit_options = {} - limit_options[:limit] = options[:limit] if options[:limit] - if options[:offset] - limit_options[:conditions] = "(#{searchable_options[:date_column]} " + (options[:before] ? '<' : '>') + "'#{connection.quoted_date(options[:offset])}')" - end - - columns = searchable_options[:columns] - columns = columns[0..0] if options[:titles_only] - - token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"} - - if !options[:titles_only] && searchable_options[:search_custom_fields] - searchable_custom_field_ids = CustomField.find(:all, - :select => 'id', - :conditions => { :type => "#{self.name}CustomField", - :searchable => true }).collect(&:id) - if searchable_custom_field_ids.any? - custom_field_sql = "#{table_name}.id IN (SELECT customized_id FROM #{CustomValue.table_name}" + - " WHERE customized_type='#{self.name}' AND customized_id=#{table_name}.id AND LOWER(value) LIKE ?" + - " AND #{CustomValue.table_name}.custom_field_id IN (#{searchable_custom_field_ids.join(',')}))" - token_clauses << custom_field_sql - end - end - - sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') - - find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] - - scope = self - project_conditions = [] - if searchable_options.has_key?(:permission) - project_conditions << Project.allowed_to_condition(user, searchable_options[:permission] || :view_project) - elsif respond_to?(:visible) - scope = scope.visible(user) - else - ActiveSupport::Deprecation.warn "acts_as_searchable with implicit :permission option is deprecated. Add a visible scope to the #{self.name} model or use explicit :permission option." - project_conditions << Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym) - end - # TODO: use visible scope options instead - project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? - project_conditions = project_conditions.empty? ? nil : project_conditions.join(' AND ') - - results = [] - results_count = 0 - - scope = scope.scoped({:conditions => project_conditions}).scoped(find_options) - results_count = scope.count(:all) - results = scope.find(:all, limit_options) - - [results, results_count] - end - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/3f/3ff1394a5f4587b766a85396728251b1808dcd45.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3f/3ff1394a5f4587b766a85396728251b1808dcd45.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,61 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class AdminTest < ActionController::IntegrationTest + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + def test_add_user + log_user("admin", "admin") + get "/users/new" + assert_response :success + assert_template "users/new" + post "/users", + :user => { :login => "psmith", :firstname => "Paul", + :lastname => "Smith", :mail => "psmith@somenet.foo", + :language => "en", :password => "psmith09", + :password_confirmation => "psmith09" } + + user = User.find_by_login("psmith") + assert_kind_of User, user + assert_redirected_to "/users/#{ user.id }/edit" + + logged_user = User.try_to_login("psmith", "psmith09") + assert_kind_of User, logged_user + assert_equal "Paul", logged_user.firstname + + put "users/#{user.id}", :id => user.id, :user => { :status => User::STATUS_LOCKED } + assert_redirected_to "/users/#{ user.id }/edit" + locked_user = User.try_to_login("psmith", "psmith09") + assert_equal nil, locked_user + end + + test "Add a user as an anonymous user should fail" do + post '/users', + :user => { :login => 'psmith', :firstname => 'Paul'}, + :password => "psmith09", :password_confirmation => "psmith09" + assert_response :redirect + assert_redirected_to "/login?back_url=http%3A%2F%2Fwww.example.com%2Fusers" + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/40/400efc2314d70f2ca223797d0eeb166b7c6d3222.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/40/400efc2314d70f2ca223797d0eeb166b7c6d3222.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1156 @@ +# Vietnamese translation for Ruby on Rails +# by +# Do Hai Bac (dohaibac@gmail.com) +# Dao Thanh Ngoc (ngocdaothanh@gmail.com, http://github.com/ngocdaothanh/rails-i18n/tree/master) +# Nguyen Minh Thien (thiencdcn@gmail.com, http://www.eDesignLab.org) + +vi: + number: + # Used in number_with_delimiter() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "," + # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "." + # Number of decimals, behind the separator (1 with a precision of 2 gives: 1.00) + precision: 3 + + # Used in number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + format: "%n %u" + unit: "đồng" + # These three are to override number.format and are optional + separator: "," + delimiter: "." + precision: 2 + + # Used in number_to_percentage() + percentage: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_precision() + precision: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_human_size() + human: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() + datetime: + distance_in_words: + half_a_minute: "30 giây" + less_than_x_seconds: + one: "chưa tá»›i 1 giây" + other: "chưa tá»›i %{count} giây" + x_seconds: + one: "1 giây" + other: "%{count} giây" + less_than_x_minutes: + one: "chưa tá»›i 1 phút" + other: "chưa tá»›i %{count} phút" + x_minutes: + one: "1 phút" + other: "%{count} phút" + about_x_hours: + one: "khoảng 1 giá»" + other: "khoảng %{count} giá»" + x_hours: + one: "1 giá»" + other: "%{count} giá»" + x_days: + one: "1 ngày" + other: "%{count} ngày" + about_x_months: + one: "khoảng 1 tháng" + other: "khoảng %{count} tháng" + x_months: + one: "1 tháng" + other: "%{count} tháng" + about_x_years: + one: "khoảng 1 năm" + other: "khoảng %{count} năm" + over_x_years: + one: "hÆ¡n 1 năm" + other: "hÆ¡n %{count} năm" + almost_x_years: + one: "gần 1 năm" + other: "gần %{count} năm" + prompts: + year: "Năm" + month: "Tháng" + day: "Ngày" + hour: "Giá»" + minute: "Phút" + second: "Giây" + + activerecord: + errors: + template: + header: + one: "1 lá»—i ngăn không cho lưu %{model} này" + other: "%{count} lá»—i ngăn không cho lưu %{model} này" + # The variable :count is also available + body: "Có lá»—i vá»›i các mục sau:" + + # The values :model, :attribute and :value are always available for interpolation + # The value :count is available when applicable. Can be used for pluralization. + messages: + inclusion: "không có trong danh sách" + exclusion: "đã được giành trước" + invalid: "không hợp lệ" + confirmation: "không khá»›p vá»›i xác nhận" + accepted: "phải được đồng ý" + empty: "không thể rá»—ng" + blank: "không thể để trắng" + too_long: "quá dài (tối Ä‘a %{count} ký tá»±)" + too_short: "quá ngắn (tối thiểu %{count} ký tá»±)" + wrong_length: "độ dài không đúng (phải là %{count} ký tá»±)" + taken: "đã có" + not_a_number: "không phải là số" + greater_than: "phải lá»›n hÆ¡n %{count}" + greater_than_or_equal_to: "phải lá»›n hÆ¡n hoặc bằng %{count}" + equal_to: "phải bằng %{count}" + less_than: "phải nhá» hÆ¡n %{count}" + less_than_or_equal_to: "phải nhá» hÆ¡n hoặc bằng %{count}" + odd: "phải là số chẵn" + even: "phải là số lẻ" + greater_than_start_date: "phải Ä‘i sau ngày bắt đầu" + not_same_project: "không thuá»™c cùng dá»± án" + circular_dependency: "quan hệ có thể gây ra lặp vô tận" + cant_link_an_issue_with_a_descendant: "Má»™t vấn đỠkhông thể liên kết tá»›i má»™t trong số những tác vụ con cá»§a nó" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d-%m-%Y" + short: "%d %b" + long: "%d %B, %Y" + + day_names: ["Chá»§ nhật", "Thứ hai", "Thứ ba", "Thứ tư", "Thứ năm", "Thứ sáu", "Thứ bảy"] + abbr_day_names: ["Chá»§ nhật", "Thứ hai", "Thứ ba", "Thứ tư", "Thứ năm", "Thứ sáu", "Thứ bảy"] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, "Tháng má»™t", "Tháng hai", "Tháng ba", "Tháng tư", "Tháng năm", "Tháng sáu", "Tháng bảy", "Tháng tám", "Tháng chín", "Tháng mưá»i", "Tháng mưá»i má»™t", "Tháng mưá»i hai"] + abbr_month_names: [~, "Tháng má»™t", "Tháng hai", "Tháng ba", "Tháng tư", "Tháng năm", "Tháng sáu", "Tháng bảy", "Tháng tám", "Tháng chín", "Tháng mưá»i", "Tháng mưá»i má»™t", "Tháng mưá»i hai"] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%d %B, %Y %H:%M" + am: "sáng" + pm: "chiá»u" + + # Used in array.to_sentence. + support: + array: + words_connector: ", " + two_words_connector: " và " + last_word_connector: ", và " + + actionview_instancetag_blank_option: Vui lòng chá»n + + general_text_No: 'Không' + general_text_Yes: 'Có' + general_text_no: 'không' + general_text_yes: 'có' + general_lang_name: 'Tiếng Việt' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Cập nhật tài khoản thành công. + notice_account_invalid_creditentials: Tài khoản hoặc mật mã không hợp lệ + notice_account_password_updated: Cập nhật mật mã thành công. + notice_account_wrong_password: Sai mật mã + notice_account_register_done: Tài khoản được tạo thành công. Äể kích hoạt vui lòng làm theo hướng dẫn trong email gá»­i đến bạn. + notice_account_unknown_email: Không rõ tài khoản. + notice_can_t_change_password: Tài khoản được chứng thá»±c từ nguồn bên ngoài. Không thể đổi mật mã cho loại chứng thá»±c này. + notice_account_lost_email_sent: Thông tin để đổi mật mã má»›i đã gá»­i đến bạn qua email. + notice_account_activated: Tài khoản vừa được kích hoạt. Bây giá» bạn có thể đăng nhập. + notice_successful_create: Tạo thành công. + notice_successful_update: Cập nhật thành công. + notice_successful_delete: Xóa thành công. + notice_successful_connection: Kết nối thành công. + notice_file_not_found: Trang bạn cố xem không tồn tại hoặc đã chuyển. + notice_locking_conflict: Thông tin Ä‘ang được cập nhật bởi ngưá»i khác. Hãy chép ná»™i dung cập nhật cá»§a bạn vào clipboard. + notice_not_authorized: Bạn không có quyá»n xem trang này. + notice_email_sent: "Email đã được gá»­i tá»›i %{value}" + notice_email_error: "Lá»—i xảy ra khi gá»­i email (%{value})" + notice_feeds_access_key_reseted: Mã số chứng thá»±c Atom đã được tạo lại. + notice_failed_to_save_issues: "Thất bại khi lưu %{count} vấn đỠtrong %{total} lá»±a chá»n: %{ids}." + notice_no_issue_selected: "Không có vấn đỠđược chá»n! Vui lòng kiểm tra các vấn đỠbạn cần chỉnh sá»­a." + notice_account_pending: "Thông tin tài khoản đã được tạo ra và Ä‘ang chá» chứng thá»±c từ ban quản trị." + notice_default_data_loaded: Äã nạp cấu hình mặc định. + notice_unable_delete_version: Không thể xóa phiên bản. + + error_can_t_load_default_data: "Không thể nạp cấu hình mặc định: %{value}" + error_scm_not_found: "Không tìm thấy dữ liệu trong kho chứa." + error_scm_command_failed: "Lá»—i xảy ra khi truy cập vào kho lưu trữ: %{value}" + error_scm_annotate: "Äầu vào không tồn tại hoặc không thể chú thích." + error_issue_not_found_in_project: 'Vấn đỠkhông tồn tại hoặc không thuá»™c dá»± án' + + mail_subject_lost_password: "%{value}: mật mã cá»§a bạn" + mail_body_lost_password: "Äể đổi mật mã, hãy click chuá»™t vào liên kết sau:" + mail_subject_register: "%{value}: kích hoạt tài khoản" + mail_body_register: "Äể kích hoạt tài khoản, hãy click chuá»™t vào liên kết sau:" + mail_body_account_information_external: " Bạn có thể dùng tài khoản %{value} để đăng nhập." + mail_body_account_information: Thông tin vá» tài khoản + mail_subject_account_activation_request: "%{value}: Yêu cầu chứng thá»±c tài khoản" + mail_body_account_activation_request: "Ngưá»i dùng (%{value}) má»›i đăng ký và cần bạn xác nhận:" + mail_subject_reminder: "%{count} vấn đỠhết hạn trong các %{days} ngày tá»›i" + mail_body_reminder: "%{count} công việc bạn được phân công sẽ hết hạn trong %{days} ngày tá»›i:" + + field_name: Tên dá»± án + field_description: Mô tả + field_summary: Tóm tắt + field_is_required: Bắt buá»™c + field_firstname: Tên đệm và Tên + field_lastname: Há» + field_mail: Email + field_filename: Tập tin + field_filesize: Cỡ + field_downloads: Tải vá» + field_author: Tác giả + field_created_on: Tạo + field_updated_on: Cập nhật + field_field_format: Äịnh dạng + field_is_for_all: Cho má»i dá»± án + field_possible_values: Giá trị hợp lệ + field_regexp: Biểu thức chính quy + field_min_length: Chiá»u dài tối thiểu + field_max_length: Chiá»u dài tối Ä‘a + field_value: Giá trị + field_category: Chá»§ đỠ+ field_title: Tiêu đỠ+ field_project: Dá»± án + field_issue: Vấn đỠ+ field_status: Trạng thái + field_notes: Ghi chú + field_is_closed: Vấn đỠđóng + field_is_default: Giá trị mặc định + field_tracker: Kiểu vấn đỠ+ field_subject: Chá»§ đỠ+ field_due_date: Hết hạn + field_assigned_to: Phân công cho + field_priority: Mức ưu tiên + field_fixed_version: Phiên bản + field_user: Ngưá»i dùng + field_role: Quyá»n + field_homepage: Trang chá»§ + field_is_public: Công cá»™ng + field_parent: Dá»± án con cá»§a + field_is_in_roadmap: Có thể thấy trong Kế hoạch + field_login: Äăng nhập + field_mail_notification: Thông báo qua email + field_admin: Quản trị + field_last_login_on: Kết nối cuối + field_language: Ngôn ngữ + field_effective_date: Ngày + field_password: Mật khẩu + field_new_password: Mật khẩu má»›i + field_password_confirmation: Nhập lại mật khẩu + field_version: Phiên bản + field_type: Kiểu + field_host: Host + field_port: Cổng + field_account: Tài khoản + field_base_dn: Base DN + field_attr_login: Thuá»™c tính đăng nhập + field_attr_firstname: Thuá»™c tính tên đệm và Tên + field_attr_lastname: Thuá»™c tính Há» + field_attr_mail: Thuá»™c tính Email + field_onthefly: Tạo ngưá»i dùng tức thì + field_start_date: Bắt đầu + field_done_ratio: Tiến độ + field_auth_source: Chế độ xác thá»±c + field_hide_mail: Không hiện email cá»§a tôi + field_comments: Bình luận + field_url: URL + field_start_page: Trang bắt đầu + field_subproject: Dá»± án con + field_hours: Giá» + field_activity: Hoạt động + field_spent_on: Ngày + field_identifier: Mã nhận dạng + field_is_filter: Dùng như bá»™ lá»c + field_issue_to: Vấn đỠliên quan + field_delay: Äá»™ trá»… + field_assignable: Vấn đỠcó thể gán cho vai trò này + field_redirect_existing_links: Chuyển hướng trang đã có + field_estimated_hours: Thá»i gian ước lượng + field_column_names: Cá»™t + field_time_zone: Múi giá» + field_searchable: Tìm kiếm được + field_default_value: Giá trị mặc định + field_comments_sorting: Liệt kê bình luận + field_parent_title: Trang mẹ + + setting_app_title: Tá»±a đỠứng dụng + setting_app_subtitle: Tá»±a đỠnhá» cá»§a ứng dụng + setting_welcome_text: Thông Ä‘iệp chào mừng + setting_default_language: Ngôn ngữ mặc định + setting_login_required: Cần đăng nhập + setting_self_registration: Tá»± chứng thá»±c + setting_attachment_max_size: Cỡ tối Ä‘a cá»§a tập tin đính kèm + setting_issues_export_limit: Giá»›i hạn Export vấn đỠ+ setting_mail_from: Äịa chỉ email gá»­i thông báo + setting_bcc_recipients: Tạo bản CC bí mật (bcc) + setting_host_name: Tên miá»n và đưá»ng dẫn + setting_text_formatting: Äịnh dạng bài viết + setting_wiki_compression: Nén lịch sá»­ Wiki + setting_feeds_limit: Giá»›i hạn ná»™i dung cá»§a feed + setting_default_projects_public: Dá»± án mặc định là public + setting_autofetch_changesets: Tá»± động tìm nạp commits + setting_sys_api_enabled: Cho phép WS quản lý kho chứa + setting_commit_ref_keywords: Từ khóa tham khảo + setting_commit_fix_keywords: Từ khóa chỉ vấn đỠđã giải quyết + setting_autologin: Tá»± động đăng nhập + setting_date_format: Äịnh dạng ngày + setting_time_format: Äịnh dạng giá» + setting_cross_project_issue_relations: Cho phép quan hệ chéo giữa các dá»± án + setting_issue_list_default_columns: Các cá»™t mặc định hiển thị trong danh sách vấn đỠ+ setting_emails_footer: Chữ ký cuối thư + setting_protocol: Giao thức + setting_per_page_options: Tùy chá»n đối tượng má»—i trang + setting_user_format: Äịnh dạng hiển thị ngưá»i dùng + setting_activity_days_default: Ngày hiển thị hoạt động cá»§a dá»± án + setting_display_subprojects_issues: Hiển thị mặc định vấn đỠcá»§a dá»± án con ở dá»± án chính + setting_enabled_scm: Cho phép SCM + setting_mail_handler_api_enabled: Cho phép WS cho các email tá»›i + setting_mail_handler_api_key: Mã số API + setting_sequential_project_identifiers: Tá»± sinh chuá»—i ID dá»± án + + project_module_issue_tracking: Theo dõi vấn đỠ+ project_module_time_tracking: Theo dõi thá»i gian + project_module_news: Tin tức + project_module_documents: Tài liệu + project_module_files: Tập tin + project_module_wiki: Wiki + project_module_repository: Kho lưu trữ + project_module_boards: Diá»…n đàn + + label_user: Tài khoản + label_user_plural: Tài khoản + label_user_new: Tài khoản má»›i + label_project: Dá»± án + label_project_new: Dá»± án má»›i + label_project_plural: Dá»± án + label_x_projects: + zero: không có dá»± án + one: má»™t dá»± án + other: "%{count} dá»± án" + label_project_all: Má»i dá»± án + label_project_latest: Dá»± án má»›i nhất + label_issue: Vấn đỠ+ label_issue_new: Tạo vấn đỠmá»›i + label_issue_plural: Vấn đỠ+ label_issue_view_all: Tất cả vấn đỠ+ label_issues_by: "Vấn đỠcá»§a %{value}" + label_issue_added: Äã thêm vấn đỠ+ label_issue_updated: Vấn đỠđược cập nhật + label_document: Tài liệu + label_document_new: Tài liệu má»›i + label_document_plural: Tài liệu + label_document_added: Äã thêm tài liệu + label_role: Vai trò + label_role_plural: Vai trò + label_role_new: Vai trò má»›i + label_role_and_permissions: Vai trò và Quyá»n hạn + label_member: Thành viên + label_member_new: Thành viên má»›i + label_member_plural: Thành viên + label_tracker: Kiểu vấn đỠ+ label_tracker_plural: Kiểu vấn đỠ+ label_tracker_new: Tạo kiểu vấn đỠmá»›i + label_workflow: Quy trình làm việc + label_issue_status: Trạng thái vấn đỠ+ label_issue_status_plural: Trạng thái vấn đỠ+ label_issue_status_new: Thêm trạng thái + label_issue_category: Chá»§ đỠ+ label_issue_category_plural: Chá»§ đỠ+ label_issue_category_new: Chá»§ đỠmá»›i + label_custom_field: Trưá»ng tùy biến + label_custom_field_plural: Trưá»ng tùy biến + label_custom_field_new: Thêm Trưá»ng tùy biến + label_enumerations: Liệt kê + label_enumeration_new: Thêm giá trị + label_information: Thông tin + label_information_plural: Thông tin + label_please_login: Vui lòng đăng nhập + label_register: Äăng ký + label_password_lost: Phục hồi mật mã + label_home: Trang chính + label_my_page: Trang riêng + label_my_account: Cá nhân + label_my_projects: Dá»± án cá»§a bạn + label_administration: Quản trị + label_login: Äăng nhập + label_logout: Thoát + label_help: Giúp đỡ + label_reported_issues: Công việc bạn phân công + label_assigned_to_me_issues: Công việc được phân công + label_last_login: Kết nối cuối + label_registered_on: Ngày tham gia + label_activity: Hoạt động + label_overall_activity: Tất cả hoạt động + label_new: Má»›i + label_logged_as: Tài khoản » + label_environment: Môi trưá»ng + label_authentication: Xác thá»±c + label_auth_source: Chế độ xác thá»±c + label_auth_source_new: Chế độ xác thá»±c má»›i + label_auth_source_plural: Chế độ xác thá»±c + label_subproject_plural: Dá»± án con + label_and_its_subprojects: "%{value} và dá»± án con" + label_min_max_length: Äá»™ dài nhá» nhất - lá»›n nhất + label_list: Danh sách + label_date: Ngày + label_integer: Số nguyên + label_float: Số thá»±c + label_boolean: Boolean + label_string: Văn bản + label_text: Văn bản dài + label_attribute: Thuá»™c tính + label_attribute_plural: Các thuá»™c tính + label_no_data: Chưa có thông tin gì + label_change_status: Äổi trạng thái + label_history: Lược sá»­ + label_attachment: Tập tin + label_attachment_new: Thêm tập tin má»›i + label_attachment_delete: Xóa tập tin + label_attachment_plural: Tập tin + label_file_added: Äã thêm tập tin + label_report: Báo cáo + label_report_plural: Báo cáo + label_news: Tin tức + label_news_new: Thêm tin + label_news_plural: Tin tức + label_news_latest: Tin má»›i + label_news_view_all: Xem má»i tin + label_news_added: Äã thêm tin + label_settings: Thiết lập + label_overview: Tóm tắt + label_version: Phiên bản + label_version_new: Phiên bản má»›i + label_version_plural: Phiên bản + label_confirmation: Khẳng định + label_export_to: 'Äịnh dạng khác cá»§a trang này:' + label_read: Äá»c... + label_public_projects: Các dá»± án công cá»™ng + label_open_issues: mở + label_open_issues_plural: mở + label_closed_issues: đóng + label_closed_issues_plural: đóng + label_x_open_issues_abbr_on_total: + zero: "0 mở / %{total}" + one: "1 mở / %{total}" + other: "%{count} mở / %{total}" + label_x_open_issues_abbr: + zero: 0 mở + one: 1 mở + other: "%{count} mở" + label_x_closed_issues_abbr: + zero: 0 đóng + one: 1 đóng + other: "%{count} đóng" + label_total: Tổng cá»™ng + label_permissions: Quyá»n + label_current_status: Trạng thái hiện tại + label_new_statuses_allowed: Trạng thái má»›i được phép + label_all: Tất cả + label_none: không + label_nobody: Chẳng ai + label_next: Sau + label_previous: Trước + label_used_by: ÄÆ°á»£c dùng bởi + label_details: Chi tiết + label_add_note: Thêm ghi chú + label_per_page: Má»—i trang + label_calendar: Lịch + label_months_from: tháng từ + label_gantt: Biểu đồ sá»± kiện + label_internal: Ná»™i bá»™ + label_last_changes: "%{count} thay đổi cuối" + label_change_view_all: Xem má»i thay đổi + label_personalize_page: Äiá»u chỉnh trang này + label_comment: Bình luận + label_comment_plural: Bình luận + label_x_comments: + zero: không có bình luận + one: 1 bình luận + other: "%{count} bình luận" + label_comment_add: Thêm bình luận + label_comment_added: Äã thêm bình luận + label_comment_delete: Xóa bình luận + label_query: Truy vấn riêng + label_query_plural: Truy vấn riêng + label_query_new: Truy vấn má»›i + label_filter_add: Thêm lá»c + label_filter_plural: Bá»™ lá»c + label_equals: là + label_not_equals: không là + label_in_less_than: ít hÆ¡n + label_in_more_than: nhiá»u hÆ¡n + label_in: trong + label_today: hôm nay + label_all_time: má»i thá»i gian + label_yesterday: hôm qua + label_this_week: tuần này + label_last_week: tuần trước + label_last_n_days: "%{count} ngày cuối" + label_this_month: tháng này + label_last_month: tháng cuối + label_this_year: năm này + label_date_range: Thá»i gian + label_less_than_ago: cách đây dưới + label_more_than_ago: cách đây hÆ¡n + label_ago: cách đây + label_contains: chứa + label_not_contains: không chứa + label_day_plural: ngày + label_repository: Kho lưu trữ + label_repository_plural: Kho lưu trữ + label_browse: Duyệt + label_revision: Bản Ä‘iá»u chỉnh + label_revision_plural: Bản Ä‘iá»u chỉnh + label_associated_revisions: Các bản Ä‘iá»u chỉnh được ghép + label_added: thêm + label_modified: đổi + label_copied: chép + label_renamed: đổi tên + label_deleted: xóa + label_latest_revision: Bản Ä‘iá»u chỉnh cuối cùng + label_latest_revision_plural: Bản Ä‘iá»u chỉnh cuối cùng + label_view_revisions: Xem các bản Ä‘iá»u chỉnh + label_max_size: Dung lượng tối Ä‘a + label_sort_highest: Lên trên cùng + label_sort_higher: Dịch lên + label_sort_lower: Dịch xuống + label_sort_lowest: Xuống dưới cùng + label_roadmap: Kế hoạch + label_roadmap_due_in: "Hết hạn trong %{value}" + label_roadmap_overdue: "Trá»… %{value}" + label_roadmap_no_issues: Không có vấn đỠcho phiên bản này + label_search: Tìm + label_result_plural: Kết quả + label_all_words: Má»i từ + label_wiki: Wiki + label_wiki_edit: Sá»­a Wiki + label_wiki_edit_plural: Thay đổi wiki + label_wiki_page: Trang wiki + label_wiki_page_plural: Trang wiki + label_index_by_title: Danh sách theo tên + label_index_by_date: Danh sách theo ngày + label_current_version: Bản hiện tại + label_preview: Xem trước + label_feed_plural: Nguồn cấp tin + label_changes_details: Chi tiết cá»§a má»i thay đổi + label_issue_tracking: Vấn đỠ+ label_spent_time: Thá»i gian + label_f_hour: "%{value} giá»" + label_f_hour_plural: "%{value} giá»" + label_time_tracking: Theo dõi thá»i gian + label_change_plural: Thay đổi + label_statistics: Thống kê + label_commits_per_month: Commits má»—i tháng + label_commits_per_author: Commits má»—i tác giả + label_view_diff: So sánh + label_diff_inline: inline + label_diff_side_by_side: bên cạnh nhau + label_options: Tùy chá»n + label_copy_workflow_from: Sao chép quy trình từ + label_permissions_report: Thống kê các quyá»n + label_watched_issues: Chá»§ đỠđang theo dõi + label_related_issues: Liên quan + label_applied_status: Trạng thái áp dụng + label_loading: Äang xá»­ lý... + label_relation_new: Quan hệ má»›i + label_relation_delete: Xóa quan hệ + label_relates_to: liên quan + label_duplicates: trùng vá»›i + label_duplicated_by: bị trùng bởi + label_blocks: chặn + label_blocked_by: chặn bởi + label_precedes: Ä‘i trước + label_follows: Ä‘i sau + label_end_to_start: cuối tá»›i đầu + label_end_to_end: cuối tá»›i cuối + label_start_to_start: đầu tá»› đầu + label_start_to_end: đầu tá»›i cuối + label_stay_logged_in: Lưu thông tin đăng nhập + label_disabled: Bị vô hiệu + label_show_completed_versions: Xem phiên bản đã hoàn thành + label_me: tôi + label_board: Diá»…n đàn + label_board_new: Tạo diá»…n đàn má»›i + label_board_plural: Diá»…n đàn + label_topic_plural: Chá»§ đỠ+ label_message_plural: Diá»…n đàn + label_message_last: Bài cuối + label_message_new: Tạo bài má»›i + label_message_posted: Äã thêm bài viết + label_reply_plural: Hồi âm + label_send_information: Gá»­i thông tin đến ngưá»i dùng qua email + label_year: Năm + label_month: Tháng + label_week: Tuần + label_date_from: Từ + label_date_to: Äến + label_language_based: Theo ngôn ngữ ngưá»i dùng + label_sort_by: "Sắp xếp theo %{value}" + label_send_test_email: Gá»­i má»™t email kiểm tra + label_feeds_access_key_created_on: "Mã chứng thá»±c Atom được tạo ra cách đây %{value}" + label_module_plural: Module + label_added_time_by: "Thêm bởi %{author} cách đây %{age}" + label_updated_time: "Cập nhật cách đây %{value}" + label_jump_to_a_project: Nhảy đến dá»± án... + label_file_plural: Tập tin + label_changeset_plural: Thay đổi + label_default_columns: Cá»™t mặc định + label_no_change_option: (không đổi) + label_bulk_edit_selected_issues: Sá»­a nhiá»u vấn đỠ+ label_theme: Giao diện + label_default: Mặc định + label_search_titles_only: Chỉ tìm trong tá»±a đỠ+ label_user_mail_option_all: "Má»i sá»± kiện trên má»i dá»± án cá»§a tôi" + label_user_mail_option_selected: "Má»i sá»± kiện trên các dá»± án được chá»n..." + label_user_mail_no_self_notified: "Äừng gá»­i email vá» các thay đổi do chính tôi thá»±c hiện" + label_registration_activation_by_email: kích hoạt tài khoản qua email + label_registration_manual_activation: kích hoạt tài khoản thá»§ công + label_registration_automatic_activation: kích hoạt tài khoản tá»± động + label_display_per_page: "má»—i trang: %{value}" + label_age: Thá»i gian + label_change_properties: Thay đổi thuá»™c tính + label_general: Tổng quan + label_more: Chi tiết + label_scm: SCM + label_plugins: Module + label_ldap_authentication: Chứng thá»±c LDAP + label_downloads_abbr: Số lượng Download + label_optional_description: Mô tả bổ sung + label_add_another_file: Thêm tập tin khác + label_preferences: Cấu hình + label_chronological_order: Bài cÅ© xếp trước + label_reverse_chronological_order: Bài má»›i xếp trước + label_planning: Kế hoạch + label_incoming_emails: Nhận mail + label_generate_key: Tạo mã + label_issue_watchers: Theo dõi + + button_login: Äăng nhập + button_submit: Gá»­i + button_save: Lưu + button_check_all: Äánh dấu tất cả + button_uncheck_all: Bá» dấu tất cả + button_delete: Xóa + button_create: Tạo + button_test: Kiểm tra + button_edit: Sá»­a + button_add: Thêm + button_change: Äổi + button_apply: Ãp dụng + button_clear: Xóa + button_lock: Khóa + button_unlock: Mở khóa + button_download: Tải vá» + button_list: Liệt kê + button_view: Xem + button_move: Chuyển + button_back: Quay lại + button_cancel: Bá» qua + button_activate: Kích hoạt + button_sort: Sắp xếp + button_log_time: Thêm thá»i gian + button_rollback: Quay trở lại phiên bản này + button_watch: Theo dõi + button_unwatch: Bá» theo dõi + button_reply: Trả lá»i + button_archive: Äóng băng + button_unarchive: Xả băng + button_reset: Tạo lại + button_rename: Äổi tên + button_change_password: Äổi mật mã + button_copy: Sao chép + button_annotate: Chú giải + button_update: Cập nhật + button_configure: Cấu hình + button_quote: Trích dẫn + + status_active: Äang hoạt động + status_registered: Má»›i đăng ký + status_locked: Äã khóa + + text_select_mail_notifications: Chá»n hành động đối vá»›i má»—i email sẽ gá»­i. + text_regexp_info: eg. ^[A-Z0-9]+$ + text_min_max_length_info: 0 để chỉ không hạn chế + text_project_destroy_confirmation: Bạn có chắc chắn muốn xóa dá»± án này và các dữ liệu liên quan ? + text_subprojects_destroy_warning: "Dá»± án con cá»§a : %{value} cÅ©ng sẽ bị xóa." + text_workflow_edit: Chá»n má»™t vai trò và má»™t vấn đỠđể sá»­a quy trình + text_are_you_sure: Bạn chắc chứ? + text_tip_issue_begin_day: ngày bắt đầu + text_tip_issue_end_day: ngày kết thúc + text_tip_issue_begin_end_day: bắt đầu và kết thúc cùng ngày + text_caracters_maximum: "Tối Ä‘a %{count} ký tá»±." + text_caracters_minimum: "Phải gồm ít nhất %{count} ký tá»±." + text_length_between: "Chiá»u dài giữa %{min} và %{max} ký tá»±." + text_tracker_no_workflow: Không có quy trình được định nghÄ©a cho theo dõi này + text_unallowed_characters: Ký tá»± không hợp lệ + text_comma_separated: Nhiá»u giá trị được phép (cách nhau bởi dấu phẩy). + text_issues_ref_in_commit_messages: Vấn đỠtham khảo và cố định trong ghi chú commit + text_issue_added: "Vấn đỠ%{id} đã được báo cáo bởi %{author}." + text_issue_updated: "Vấn đỠ%{id} đã được cập nhật bởi %{author}." + text_wiki_destroy_confirmation: Bạn có chắc chắn muốn xóa trang wiki này và tất cả ná»™i dung cá»§a nó ? + text_issue_category_destroy_question: "Má»™t số vấn đỠ(%{count}) được gán cho danh mục này. Bạn muốn làm gì ?" + text_issue_category_destroy_assignments: Gỡ bá» danh mục được phân công + text_issue_category_reassign_to: Gán lại vấn đỠcho danh mục này + text_user_mail_option: "Vá»›i các dá»± án không được chá»n, bạn chỉ có thể nhận được thông báo vá» các vấn đỠbạn đăng ký theo dõi hoặc có liên quan đến bạn (chẳng hạn, vấn đỠđược gán cho bạn)." + text_no_configuration_data: "Quyá»n, theo dõi, tình trạng vấn đỠvà quy trình chưa được cấu hình.\nBắt buá»™c phải nạp cấu hình mặc định. Bạn sẽ thay đổi nó được sau khi đã nạp." + text_load_default_configuration: Nạp lại cấu hình mặc định + text_status_changed_by_changeset: "Ãp dụng trong changeset : %{value}." + text_issues_destroy_confirmation: 'Bạn có chắc chắn muốn xóa các vấn đỠđã chá»n ?' + text_select_project_modules: 'Chá»n các module cho dá»± án:' + text_default_administrator_account_changed: Thay đổi tài khoản quản trị mặc định + text_file_repository_writable: Cho phép ghi thư mục đính kèm + text_rmagick_available: Trạng thái RMagick + text_destroy_time_entries_question: "Thá»i gian %{hours} giỠđã báo cáo trong vấn đỠbạn định xóa. Bạn muốn làm gì tiếp ?" + text_destroy_time_entries: Xóa thá»i gian báo cáo + text_assign_time_entries_to_project: Gán thá»i gian báo cáo cho dá»± án + text_reassign_time_entries: 'Gán lại thá»i gian báo cáo cho Vấn đỠnày:' + text_user_wrote: "%{value} đã viết:" + text_enumeration_destroy_question: "%{count} đối tượng được gán giá trị này." + text_enumeration_category_reassign_to: 'Gán lại giá trị này:' + text_email_delivery_not_configured: "Cấu hình gá»­i Email chưa được đặt, và chức năng thông báo bị loại bá».\nCấu hình máy chá»§ SMTP cá»§a bạn ở file config/configuration.yml và khởi động lại để kích hoạt chúng." + + default_role_manager: 'Äiá»u hành ' + default_role_developer: 'Phát triển ' + default_role_reporter: Báo cáo + default_tracker_bug: Lá»—i + default_tracker_feature: Tính năng + default_tracker_support: Há»— trợ + default_issue_status_new: Má»›i + default_issue_status_in_progress: Äang tiến hành + default_issue_status_resolved: Äã được giải quyết + default_issue_status_feedback: Phản hồi + default_issue_status_closed: Äã đóng + default_issue_status_rejected: Từ chối + default_doc_category_user: Tài liệu ngưá»i dùng + default_doc_category_tech: Tài liệu kỹ thuật + default_priority_low: Thấp + default_priority_normal: Bình thưá»ng + default_priority_high: Cao + default_priority_urgent: Khẩn cấp + default_priority_immediate: Trung bình + default_activity_design: Thiết kế + default_activity_development: Phát triển + + enumeration_issue_priorities: Mức độ ưu tiên vấn đỠ+ enumeration_doc_categories: Danh mục tài liệu + enumeration_activities: Hoạt động + + setting_plain_text_mail: Mail dạng text đơn giản (không dùng HTML) + setting_gravatar_enabled: Dùng biểu tượng Gravatar + permission_edit_project: Chỉnh dá»± án + permission_select_project_modules: Chá»n Module + permission_manage_members: Quản lý thành viên + permission_manage_versions: Quản lý phiên bản + permission_manage_categories: Quản lý chá»§ đỠ+ permission_add_issues: Thêm vấn đỠ+ permission_edit_issues: Sá»­a vấn đỠ+ permission_manage_issue_relations: Quản lý quan hệ vấn đỠ+ permission_add_issue_notes: Thêm chú thích + permission_edit_issue_notes: Sá»­a chú thích + permission_edit_own_issue_notes: Sá»­a chú thích cá nhân + permission_move_issues: Chuyển vấn đỠ+ permission_delete_issues: Xóa vấn đỠ+ permission_manage_public_queries: Quản lý truy vấn công cá»™ng + permission_save_queries: Lưu truy vấn + permission_view_gantt: Xem biểu đồ sá»± kiện + permission_view_calendar: Xem lịch + permission_view_issue_watchers: Xem những ngưá»i theo dõi + permission_add_issue_watchers: Thêm ngưá»i theo dõi + permission_log_time: Lưu thá»i gian đã qua + permission_view_time_entries: Xem thá»i gian đã qua + permission_edit_time_entries: Xem nhật ký thá»i gian + permission_edit_own_time_entries: Sá»­a thá»i gian đã lưu + permission_manage_news: Quản lý tin má»›i + permission_comment_news: Chú thích vào tin má»›i + permission_view_documents: Xem tài liệu + permission_manage_files: Quản lý tập tin + permission_view_files: Xem tập tin + permission_manage_wiki: Quản lý wiki + permission_rename_wiki_pages: Äổi tên trang wiki + permission_delete_wiki_pages: Xóa trang wiki + permission_view_wiki_pages: Xem wiki + permission_view_wiki_edits: Xem lược sá»­ trang wiki + permission_edit_wiki_pages: Sá»­a trang wiki + permission_delete_wiki_pages_attachments: Xóa tệp đính kèm + permission_protect_wiki_pages: Bảo vệ trang wiki + permission_manage_repository: Quản lý kho lưu trữ + permission_browse_repository: Duyệt kho lưu trữ + permission_view_changesets: Xem các thay đổi + permission_commit_access: Truy cập commit + permission_manage_boards: Quản lý diá»…n đàn + permission_view_messages: Xem bài viết + permission_add_messages: Gá»­i bài viết + permission_edit_messages: Sá»­a bài viết + permission_edit_own_messages: Sá»­a bài viết cá nhân + permission_delete_messages: Xóa bài viết + permission_delete_own_messages: Xóa bài viết cá nhân + label_example: Ví dụ + text_repository_usernames_mapping: "Lá»±a chá»n hoặc cập nhật ánh xạ ngưá»i dùng hệ thống vá»›i ngưá»i dùng trong kho lưu trữ.\nKhi ngưá»i dùng trùng hợp vá» tên và email sẽ được tá»± động ánh xạ." + permission_delete_own_messages: Xóa thông Ä‘iệp + label_user_activity: "%{value} hoạt động" + label_updated_time_by: "Cập nhật bởi %{author} cách đây %{age}" + text_diff_truncated: '... Thay đổi này đã được cắt bá»›t do nó vượt qua giá»›i hạn kích thước có thể hiển thị.' + setting_diff_max_lines_displayed: Số dòng thay đổi tối Ä‘a được hiển thị + text_plugin_assets_writable: Cho phép ghi thư mục Plugin + warning_attachments_not_saved: "%{count} file không được lưu." + button_create_and_continue: Tạo và tiếp tục + text_custom_field_possible_values_info: 'Má»™t dòng cho má»—i giá trị' + label_display: Hiển thị + field_editable: Có thể sá»­a được + setting_repository_log_display_limit: Số lượng tối Ä‘a các bản Ä‘iá»u chỉnh hiển thị trong file log + setting_file_max_size_displayed: Kích thước tối Ä‘a cá»§a tệp tin văn bản + field_watcher: Ngưá»i quan sát + setting_openid: Cho phép đăng nhập và đăng ký dùng OpenID + field_identity_url: OpenID URL + label_login_with_open_id_option: hoặc đăng nhập vá»›i OpenID + field_content: Ná»™i dung + label_descending: Giảm dần + label_sort: Sắp xếp + label_ascending: Tăng dần + label_date_from_to: "Từ %{start} tá»›i %{end}" + label_greater_or_equal: ">=" + label_less_or_equal: "<=" + text_wiki_page_destroy_question: "Trang này có %{descendants} trang con và trang cháu. Bạn muốn làm gì tiếp?" + text_wiki_page_reassign_children: Gán lại trang con vào trang mẹ này + text_wiki_page_nullify_children: Giữ trang con như trang gốc + text_wiki_page_destroy_children: Xóa trang con và tất cả trang con cháu cá»§a nó + setting_password_min_length: Chiá»u dài tối thiểu cá»§a mật khẩu + field_group_by: Nhóm kết quả bởi + mail_subject_wiki_content_updated: "%{id} trang wiki đã được cập nhật" + label_wiki_content_added: Äã thêm trang Wiki + mail_subject_wiki_content_added: "%{id} trang wiki đã được thêm vào" + mail_body_wiki_content_added: "Có %{id} trang wiki đã được thêm vào bởi %{author}." + label_wiki_content_updated: Trang Wiki đã được cập nhật + mail_body_wiki_content_updated: "Có %{id} trang wiki đã được cập nhật bởi %{author}." + permission_add_project: Tạo dá»± án + setting_new_project_user_role_id: Quyá»n được gán cho ngưá»i dùng không phải quản trị viên khi tạo dá»± án má»›i + label_view_all_revisions: Xem tất cả bản Ä‘iá»u chỉnh + label_tag: Thẻ + label_branch: Nhánh + error_no_tracker_in_project: Không có ai theo dõi dá»± án này. Hãy kiểm tra lại phần thiết lập cho dá»± án. + error_no_default_issue_status: Không có vấn đỠmặc định được định nghÄ©a. Vui lòng kiểm tra cấu hình cá»§a bạn (Vào "Quản trị -> Trạng thái vấn Ä‘á»"). + text_journal_changed: "%{label} thay đổi từ %{old} tá»›i %{new}" + text_journal_set_to: "%{label} gán cho %{value}" + text_journal_deleted: "%{label} xóa (%{old})" + label_group_plural: Các nhóm + label_group: Nhóm + label_group_new: Thêm nhóm + label_time_entry_plural: Thá»i gian đã sá»­ dụng + text_journal_added: "%{label} %{value} được thêm" + field_active: Tích cá»±c + enumeration_system_activity: Hoạt động hệ thống + permission_delete_issue_watchers: Xóa ngưá»i quan sát + version_status_closed: đóng + version_status_locked: khóa + version_status_open: mở + error_can_not_reopen_issue_on_closed_version: Má»™t vấn đỠđược gán cho phiên bản đã đóng không thể mở lại được + label_user_anonymous: Ẩn danh + button_move_and_follow: Di chuyển và theo + setting_default_projects_modules: Các Module được kích hoạt mặc định cho dá»± án má»›i + setting_gravatar_default: Ảnh Gravatar mặc định + field_sharing: Chia sẻ + label_version_sharing_hierarchy: Vá»›i thứ bậc dá»± án + label_version_sharing_system: Vá»›i tất cả dá»± án + label_version_sharing_descendants: Vá»›i dá»± án con + label_version_sharing_tree: Vá»›i cây dá»± án + label_version_sharing_none: Không chia sẻ + error_can_not_archive_project: Dá»±a án này không thể lưu trữ được + button_duplicate: Nhân đôi + button_copy_and_follow: Sao chép và theo + label_copy_source: Nguồn + setting_issue_done_ratio: Tính toán tá»· lệ hoàn thành vấn đỠvá»›i + setting_issue_done_ratio_issue_status: Sá»­ dụng trạng thái cá»§a vấn đỠ+ error_issue_done_ratios_not_updated: Tá»· lệ hoàn thành vấn đỠkhông được cập nhật. + error_workflow_copy_target: Vui lòng lá»±a chá»n đích cá»§a theo dấu và quyá»n + setting_issue_done_ratio_issue_field: Dùng trưá»ng vấn đỠ+ label_copy_same_as_target: Tương tá»± như đích + label_copy_target: Äích + notice_issue_done_ratios_updated: Tá»· lệ hoàn thành vấn đỠđược cập nhật. + error_workflow_copy_source: Vui lòng lá»±a chá»n nguồn cá»§a theo dấu hoặc quyá»n + label_update_issue_done_ratios: Cập nhật tá»· lệ hoàn thành vấn đỠ+ setting_start_of_week: Äịnh dạng lịch + permission_view_issues: Xem Vấn đỠ+ label_display_used_statuses_only: Chỉ hiển thị trạng thái đã được dùng bởi theo dõi này + label_revision_id: "Bản Ä‘iá»u chỉnh %{value}" + label_api_access_key: Khoá truy cập API + label_api_access_key_created_on: "Khoá truy cập API đựơc tạo cách đây %{value}. Khóa này được dùng cho eDesignLab Client." + label_feeds_access_key: Khoá truy cập Atom + notice_api_access_key_reseted: Khoá truy cập API cá»§a bạn đã được đặt lại. + setting_rest_api_enabled: Cho phép dịch vụ web REST + label_missing_api_access_key: Mất Khoá truy cập API + label_missing_feeds_access_key: Mất Khoá truy cập Atom + button_show: Hiện + text_line_separated: Nhiá»u giá trị được phép(má»—i dòng má»™t giá trị). + setting_mail_handler_body_delimiters: "Cắt bá»›t email sau những dòng :" + permission_add_subprojects: Tạo Dá»± án con + label_subproject_new: Thêm dá»± án con + text_own_membership_delete_confirmation: |- + Bạn Ä‘ang cố gỡ bá» má»™t số hoặc tất cả quyá»n cá»§a bạn vá»›i dá»± án này và có thể sẽ mất quyá»n thay đổi nó sau đó. + Bạn có muốn tiếp tục? + label_close_versions: Äóng phiên bản đã hoàn thành + label_board_sticky: Chú ý + label_board_locked: Äã khóa + permission_export_wiki_pages: Xuất trang wiki + setting_cache_formatted_text: Cache định dạng các ký tá»± + permission_manage_project_activities: Quản lý hoạt động cá»§a dá»± án + error_unable_delete_issue_status: Không thể xóa trạng thái vấn đỠ+ label_profile: Hồ sÆ¡ + permission_manage_subtasks: Quản lý tác vụ con + field_parent_issue: Tác vụ cha + label_subtask_plural: Tác vụ con + label_project_copy_notifications: Gá»­i email thông báo trong khi dá»± án được sao chép + error_can_not_delete_custom_field: Không thể xóa trưá»ng tùy biến + error_unable_to_connect: "Không thể kết nối (%{value})" + error_can_not_remove_role: Quyá»n này Ä‘ang được dùng và không thể xóa được. + error_can_not_delete_tracker: Theo dõi này chứa vấn đỠvà không thể xóa được. + field_principal: Chá»§ yếu + label_my_page_block: Block trang cá»§a tôi + notice_failed_to_save_members: "Thất bại khi lưu thành viên : %{errors}." + text_zoom_out: Thu nhá» + text_zoom_in: Phóng to + notice_unable_delete_time_entry: Không thể xóa mục time log. + label_overall_spent_time: Tổng thá»i gian sá»­ dụng + field_time_entries: Log time + project_module_gantt: Biểu đồ Gantt + project_module_calendar: Lịch + button_edit_associated_wikipage: "Chỉnh sá»­a trang Wiki liên quan: %{page_title}" + text_are_you_sure_with_children: Xóa vấn đỠvà tất cả vấn đỠcon? + field_text: Trưá»ng văn bản + label_user_mail_option_only_owner: Chỉ những thứ tôi sở hữu + setting_default_notification_option: Tuỳ chá»n thông báo mặc định + label_user_mail_option_only_my_events: Chỉ những thứ tôi theo dõi hoặc liên quan + label_user_mail_option_only_assigned: Chỉ những thứ tôi được phân công + label_user_mail_option_none: Không có sá»± kiện + field_member_of_group: Nhóm thụ hưởng + field_assigned_to_role: Quyá»n thụ hưởng + notice_not_authorized_archived_project: Dá»± án bạn Ä‘ang có truy cập đã được lưu trữ. + label_principal_search: "Tìm kiếm ngưá»i dùng hoặc nhóm:" + label_user_search: "Tìm kiếm ngưá»i dùng:" + field_visible: Nhìn thấy + setting_emails_header: Tiêu đỠEmail + setting_commit_logtime_activity_id: Cho phép ghi lại thá»i gian + text_time_logged_by_changeset: "Ãp dụng trong changeset : %{value}." + setting_commit_logtime_enabled: Cho phép time logging + notice_gantt_chart_truncated: "Äồ thị đã được cắt bá»›t bởi vì nó đã vượt qua lượng thông tin tối Ä‘a có thể hiển thị :(%{max})" + setting_gantt_items_limit: Lượng thông tin tối Ä‘a trên đồ thị gantt + description_selected_columns: Các cá»™t được lá»±a chá»n + field_warn_on_leaving_unsaved: Cảnh báo tôi khi rá»i má»™t trang có các ná»™i dung chưa lưu + text_warn_on_leaving_unsaved: Trang hiện tại chứa ná»™i dung chưa lưu và sẽ bị mất nếu bạn rá»i trang này. + label_my_queries: Các truy vấn tùy biến + text_journal_changed_no_detail: "%{label} cập nhật" + label_news_comment_added: Bình luận đã được thêm cho má»™t tin tức + button_expand_all: Mở rá»™ng tất cả + button_collapse_all: Thu gá»n tất cả + label_additional_workflow_transitions_for_assignee: Chuyển đổi bổ sung cho phép khi ngưá»i sá»­ dụng là ngưá»i nhận chuyển nhượng + label_additional_workflow_transitions_for_author: Các chuyển đổi bổ xung được phép khi ngưá»i dùng là tác giả + label_bulk_edit_selected_time_entries: Sá»­a nhiá»u mục đã chá»n + text_time_entries_destroy_confirmation: Bạn có chắc chắn muốn xóa bá» các mục đã chá»n? + label_role_anonymous: Ẩn danh + label_role_non_member: Không là thành viên + label_issue_note_added: Ghi chú được thêm + label_issue_status_updated: Trạng thái cập nhật + label_issue_priority_updated: Cập nhật ưu tiên + label_issues_visibility_own: Vấn đỠtạo bởi hoặc gán cho ngưá»i dùng + field_issues_visibility: Vấn đỠđược nhìn thấy + label_issues_visibility_all: Tất cả vấn đỠ+ permission_set_own_issues_private: Äặt vấn đỠsở hữu là riêng tư hoặc công cá»™ng + field_is_private: Riêng tư + permission_set_issues_private: Gán vấn đỠlà riêng tư hoặc công cá»™ng + label_issues_visibility_public: Tất cả vấn đỠkhông riêng tư + text_issues_destroy_descendants_confirmation: "Hành động này sẽ xóa %{count} tác vụ con." + field_commit_logs_encoding: Mã hóa ghi chú Commit + field_scm_path_encoding: Mã hóa đưá»ng dẫn + text_scm_path_encoding_note: "Mặc định: UTF-8" + field_path_to_repository: ÄÆ°á»ng dẫn tá»›i kho chứa + field_root_directory: Thư mục gốc + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Kho chứa cục bá»™ (vd. /hgrepo, c:\hgrepo) + text_scm_command: Lệnh + text_scm_command_version: Phiên bản + label_git_report_last_commit: Báo cáo lần Commit cuối cùng cho file và thư mục + text_scm_config: Bạn có thể cấu hình lệnh Scm trong file config/configuration.yml. Vui lòng khởi động lại ứng dụng sau khi chỉnh sá»­a nó. + text_scm_command_not_available: Lệnh Scm không có sẵn. Vui lòng kiểm tra lại thiết đặt trong phần Quản trị. + notice_issue_successful_create: "Vấn đỠ%{id} đã được tạo." + label_between: Ở giữa + setting_issue_group_assignment: Cho phép gán vấn đỠđến các nhóm + label_diff: Sá»± khác nhau + text_git_repository_note: Kho chứa cục bá»™ và công cá»™ng (vd. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Chiá»u sắp xếp + description_project_scope: Phạm vi tìm kiếm + description_filter: Lá»c + description_user_mail_notification: Thiết lập email thông báo + description_date_from: Nhập ngày bắt đầu + description_message_content: Ná»™i dung thông Ä‘iệp + description_available_columns: Các cá»™t có sẵn + description_date_range_interval: Chá»n khoảng thá»i gian giữa ngày bắt đầu và kết thúc + description_issue_category_reassign: Chá»n danh mục vấn đỠ+ description_search: Trưá»ng tìm kiếm + description_notes: Các chú ý + description_date_range_list: Chá»n khoảng từ danh sách + description_choose_project: Các dá»± án + description_date_to: Nhập ngày kết thúc + description_query_sort_criteria_attribute: Sắp xếp thuá»™c tính + description_wiki_subpages_reassign: Chá»n má»™t trang cấp trên + label_parent_revision: Cha + label_child_revision: Con + error_scm_annotate_big_text_file: Các mục không được chú thích, vì nó vượt quá kích thước tập tin văn bản tối Ä‘a. + setting_default_issue_start_date_to_creation_date: Sá»­ dụng thá»i gian hiện tại khi tạo vấn đỠmá»›i + button_edit_section: Soạn thảo sá»± lá»±a chá»n này + setting_repositories_encodings: Mã hóa kho chứa + description_all_columns: Các cá»™t + button_export: Export + label_export_options: "%{export_format} tùy chá»n Export" + error_attachment_too_big: "File này không thể tải lên vì nó vượt quá kích thước cho phép : (%{max_size})" + notice_failed_to_save_time_entries: "Lá»—i khi lưu %{count} lần trên %{total} sá»± lá»±a chá»n : %{ids}." + label_x_issues: + zero: 0 vấn đỠ+ one: 1 vấn đỠ+ other: "%{count} vấn Ä‘á»" + label_repository_new: Kho lưu trữ má»›i + field_repository_is_default: Kho lưu trữ chính + label_copy_attachments: Copy các file đính kèm + label_item_position: "%{position}/%{count}" + label_completed_versions: Các phiên bản hoàn thành + text_project_identifier_info: Chỉ cho phép chữ cái thưá»ng (a-z), con số và dấu gạch ngang.
    Sau khi lưu, chỉ số ID không thể thay đổi. + field_multiple: Nhiá»u giá trị + setting_commit_cross_project_ref: Sá»­ dụng thá»i gian hiện tại khi tạo vấn đỠmá»›i + text_issue_conflict_resolution_add_notes: Thêm ghi chú cá»§a tôi và loại bá» các thay đổi khác + text_issue_conflict_resolution_overwrite: Ãp dụng thay đổi bằng bất cứ giá nào, ghi chú trước đó có thể bị ghi đè + notice_issue_update_conflict: Vấn đỠnày đã được cập nhật bởi má»™t ngưá»i dùng khác trong khi bạn Ä‘ang chỉnh sá»­a nó. + text_issue_conflict_resolution_cancel: "Loại bá» tất cả các thay đổi và hiển thị lại %{link}" + permission_manage_related_issues: Quản lý các vấn đỠliên quan + field_auth_source_ldap_filter: Bá»™ lá»c LDAP + label_search_for_watchers: Tìm kiếm ngưá»i theo dõi để thêm + notice_account_deleted: Tài khoản cá»§a bạn đã được xóa vÄ©nh viá»…n. + button_delete_my_account: Xóa tài khoản cá»§a tôi + setting_unsubscribe: Cho phép ngưá»i dùng xóa Account + text_account_destroy_confirmation: |- + Bạn đồng ý không ? + Tài khoản cá»§a bạn sẽ bị xóa vÄ©nh viá»…n, không thể khôi phục lại! + error_session_expired: Phiên làm việc cá»§a bạn bị quá hạn, hãy đăng nhập lại + text_session_expiration_settings: "Chú ý : Thay đổi các thiết lập này có thể gây vô hiệu hóa Session hiện tại" + setting_session_lifetime: Thá»i gian tồn tại lá»›n nhất cá»§a Session + setting_session_timeout: Thá»i gian vô hiệu hóa Session + label_session_expiration: Phiên làm việc bị quá hạn + permission_close_project: Äóng / Mở lại dá»± án + label_show_closed_projects: Xem các dá»± án đã đóng + button_close: Äóng + button_reopen: Mở lại + project_status_active: Kích hoạt + project_status_closed: Äã đóng + project_status_archived: Lưu trữ + text_project_closed: Dá»± án này đã đóng và chỉ Ä‘á»c + notice_user_successful_create: "Ngưá»i dùng %{id} đã được tạo." + field_core_fields: Các trưá»ng tiêu chuẩn + field_timeout: Quá hạn + setting_thumbnails_enabled: Hiển thị các thumbnail đính kèm + setting_thumbnails_size: Kích thước Thumbnails(pixel) + setting_session_lifetime: Thá»i gian tồn tại lá»›n nhất cá»§a Session + setting_session_timeout: Thá»i gian vô hiệu hóa Session + label_status_transitions: Trạng thái chuyển tiếp + label_fields_permissions: Cho phép các trưá»ng + label_readonly: Chỉ Ä‘á»c + label_required: Yêu cầu + text_repository_identifier_info: Chỉ có các chữ thưá»ng (a-z), các số (0-9), dấu gạch ngang và gạch dưới là hợp lệ.
    Khi đã lưu, tên định danh sẽ không thể thay đổi. + field_board_parent: Diá»…n đàn cha + label_attribute_of_project: "Cá»§a dá»± án : %{name}" + label_attribute_of_author: "Cá»§a tác giả : %{name}" + label_attribute_of_assigned_to: "ÄÆ°á»£c phân công bởi %{name}" + label_attribute_of_fixed_version: "Phiên bản mục tiêu cá»§a %{name}" + label_copy_subtasks: Sao chép các nhiệm vụ con + label_copied_to: Sao chép đến + label_copied_from: Sao chép từ + label_any_issues_in_project: Bất kỳ vấn đỠnào trong dá»± án + label_any_issues_not_in_project: Bất kỳ vấn đỠnào không thuá»™c dá»± án + field_private_notes: Ghi chú riêng tư + permission_view_private_notes: Xem ghi chú riêng tư + permission_set_notes_private: Äặt ghi chú thành riêng tư + label_no_issues_in_project: Không có vấn đỠnào trong dá»± án + label_any: tất cả + label_last_n_weeks: "%{count} tuần qua" + setting_cross_project_subtasks: Cho phép các nhiệm vụ con liên dá»± án + label_cross_project_descendants: Trong các dá»± án con + label_cross_project_tree: Trong cùng cây dá»± án + label_cross_project_hierarchy: Trong dá»± án cùng cấp bậc + label_cross_project_system: Trong tất cả các dá»± án + button_hide: Ẩn + setting_non_working_week_days: Các ngày không làm việc + label_in_the_next_days: Trong tương lai + label_in_the_past_days: Trong quá khứ + label_attribute_of_user: "Cá»§a ngưá»i dùng %{name}" + text_turning_multiple_off: Nếu bạn vô hiệu hóa nhiá»u giá trị, chúng sẽ bị loại bỠđể duy trì chỉ có má»™t giá trị cho má»—i mục. + label_attribute_of_issue: "Vấn đỠcá»§a %{name}" + permission_add_documents: Thêm tài liệu + permission_edit_documents: Soạn thảo tài liệu + permission_delete_documents: Xóa tài liệu + label_gantt_progress_line: Tiến độ + setting_jsonp_enabled: Cho phép trợ giúp JSONP + field_inherit_members: Các thành viên kế thừa + field_closed_on: Äã đóng + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Tổng cá»™ng + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/40/404d23708006067ec3634f10ae1644b4210d6b71.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/40/404d23708006067ec3634f10ae1644b4210d6b71.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,8 @@ +<% if Setting.emails_header.present? -%> +<%= Setting.emails_header %> +<% end -%> +<%= yield %> +<% if Setting.emails_footer.present? -%> +-- +<%= Setting.emails_footer %> +<% end -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/40/40a074fa4b99359e32098a2704385ed2f567928a.svn-base --- a/.svn/pristine/40/40a074fa4b99359e32098a2704385ed2f567928a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,264 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class ProjectsController < ApplicationController - menu_item :overview - menu_item :roadmap, :only => :roadmap - menu_item :settings, :only => :settings - - before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ] - before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy] - before_filter :authorize_global, :only => [:new, :create] - before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] - accept_rss_auth :index - accept_api_auth :index, :show, :create, :update, :destroy - - after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| - if controller.request.post? - controller.send :expire_action, :controller => 'welcome', :action => 'robots' - end - end - - helper :sort - include SortHelper - helper :custom_fields - include CustomFieldsHelper - helper :issues - helper :queries - include QueriesHelper - helper :repositories - include RepositoriesHelper - include ProjectsHelper - - # Lists visible projects - def index - respond_to do |format| - format.html { - scope = Project - unless params[:closed] - scope = scope.active - end - @projects = scope.visible.order('lft').all - } - format.api { - @offset, @limit = api_offset_and_limit - @project_count = Project.visible.count - @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft') - } - 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 - - def new - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") - @trackers = Tracker.sorted.all - @project = Project.new - @project.safe_attributes = params[:project] - end - - def create - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") - @trackers = Tracker.sorted.all - @project = Project.new - @project.safe_attributes = params[:project] - - if validate_parent_id && @project.save - @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') - # Add current user as a project member if he is not admin - unless User.current.admin? - r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first - m = Member.new(:user => User.current, :roles => [r]) - @project.members << m - end - respond_to do |format| - format.html { - flash[:notice] = l(:notice_successful_create) - redirect_to(params[:continue] ? - {:controller => 'projects', :action => 'new', :project => {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}} : - {:controller => 'projects', :action => 'settings', :id => @project} - ) - } - format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } - end - else - respond_to do |format| - format.html { render :action => 'new' } - format.api { render_validation_errors(@project) } - end - end - - end - - def copy - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") - @trackers = Tracker.sorted.all - @root_projects = Project.find(:all, - :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}", - :order => 'name') - @source_project = Project.find(params[:id]) - if request.get? - @project = Project.copy_from(@source_project) - @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers? - else - Mailer.with_deliveries(params[:notifications] == '1') do - @project = Project.new - @project.safe_attributes = params[:project] - if validate_parent_id && @project.copy(@source_project, :only => params[:only]) - @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') - flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'projects', :action => 'settings', :id => @project - elsif !@project.new_record? - # Project was created - # But some objects were not copied due to validation failures - # (eg. issues from disabled trackers) - # TODO: inform about that - redirect_to :controller => 'projects', :action => 'settings', :id => @project - end - end - end - rescue ActiveRecord::RecordNotFound - # source_project not found - render_404 - end - - # Show @project - def show - if params[:jump] - # try to redirect to the requested menu item - redirect_to_project_menu_item(@project, params[:jump]) && return - end - - @users_by_role = @project.users_by_role - @subprojects = @project.children.visible.all - @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") - @trackers = @project.rolled_up_trackers - - cond = @project.project_condition(Setting.display_subprojects_issues?) - - @open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker) - @total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker) - - if User.current.allowed_to?(:view_time_entries, @project) - @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f - end - - @key = User.current.rss_key - - respond_to do |format| - format.html - format.api - end - end - - def settings - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") - @issue_category ||= IssueCategory.new - @member ||= @project.members.new - @trackers = Tracker.sorted.all - @wiki ||= @project.wiki - end - - def edit - end - - def update - @project.safe_attributes = params[:project] - if validate_parent_id && @project.save - @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') - respond_to do |format| - format.html { - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'settings', :id => @project - } - format.api { render_api_ok } - end - else - respond_to do |format| - format.html { - settings - render :action => 'settings' - } - format.api { render_validation_errors(@project) } - end - end - end - - def modules - @project.enabled_module_names = params[:enabled_module_names] - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'settings', :id => @project, :tab => 'modules' - end - - def archive - if request.post? - unless @project.archive - flash[:error] = l(:error_can_not_archive_project) - end - end - redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status])) - end - - def unarchive - @project.unarchive if request.post? && !@project.active? - redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status])) - end - - def close - @project.close - redirect_to project_path(@project) - end - - def reopen - @project.reopen - redirect_to project_path(@project) - end - - # Delete @project - def destroy - @project_to_destroy = @project - if api_request? || params[:confirm] - @project_to_destroy.destroy - respond_to do |format| - format.html { redirect_to :controller => 'admin', :action => 'projects' } - format.api { render_api_ok } - end - end - # hide project in layout - @project = nil - end - - private - - # Validates parent_id param according to user's permissions - # TODO: move it to Project model in a validation that depends on User.current - def validate_parent_id - return true if User.current.admin? - parent_id = params[:project] && params[:project][:parent_id] - if parent_id || @project.new_record? - parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i) - unless @project.allowed_parents.include?(parent) - @project.errors.add :parent_id, :invalid - return false - end - end - true - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/40/40d501b98138649467e7f69b1429131c75dbdbeb.svn-base --- a/.svn/pristine/40/40d501b98138649467e7f69b1429131c75dbdbeb.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingIssuesTest < ActionController::IntegrationTest - def test_issues_rest_actions - assert_routing( - { :method => 'get', :path => "/issues" }, - { :controller => 'issues', :action => 'index' } - ) - assert_routing( - { :method => 'get', :path => "/issues.pdf" }, - { :controller => 'issues', :action => 'index', :format => 'pdf' } - ) - assert_routing( - { :method => 'get', :path => "/issues.atom" }, - { :controller => 'issues', :action => 'index', :format => 'atom' } - ) - assert_routing( - { :method => 'get', :path => "/issues.xml" }, - { :controller => 'issues', :action => 'index', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/issues/64" }, - { :controller => 'issues', :action => 'show', :id => '64' } - ) - assert_routing( - { :method => 'get', :path => "/issues/64.pdf" }, - { :controller => 'issues', :action => 'show', :id => '64', - :format => 'pdf' } - ) - assert_routing( - { :method => 'get', :path => "/issues/64.atom" }, - { :controller => 'issues', :action => 'show', :id => '64', - :format => 'atom' } - ) - assert_routing( - { :method => 'get', :path => "/issues/64.xml" }, - { :controller => 'issues', :action => 'show', :id => '64', - :format => 'xml' } - ) - assert_routing( - { :method => 'post', :path => "/issues.xml" }, - { :controller => 'issues', :action => 'create', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/issues/64/edit" }, - { :controller => 'issues', :action => 'edit', :id => '64' } - ) - assert_routing( - { :method => 'put', :path => "/issues/1.xml" }, - { :controller => 'issues', :action => 'update', :id => '1', - :format => 'xml' } - ) - assert_routing( - { :method => 'delete', :path => "/issues/1.xml" }, - { :controller => 'issues', :action => 'destroy', :id => '1', - :format => 'xml' } - ) - end - - def test_issues_rest_actions_scoped_under_project - assert_routing( - { :method => 'get', :path => "/projects/23/issues" }, - { :controller => 'issues', :action => 'index', :project_id => '23' } - ) - assert_routing( - { :method => 'get', :path => "/projects/23/issues.pdf" }, - { :controller => 'issues', :action => 'index', :project_id => '23', - :format => 'pdf' } - ) - assert_routing( - { :method => 'get', :path => "/projects/23/issues.atom" }, - { :controller => 'issues', :action => 'index', :project_id => '23', - :format => 'atom' } - ) - assert_routing( - { :method => 'get', :path => "/projects/23/issues.xml" }, - { :controller => 'issues', :action => 'index', :project_id => '23', - :format => 'xml' } - ) - assert_routing( - { :method => 'post', :path => "/projects/23/issues" }, - { :controller => 'issues', :action => 'create', :project_id => '23' } - ) - assert_routing( - { :method => 'get', :path => "/projects/23/issues/new" }, - { :controller => 'issues', :action => 'new', :project_id => '23' } - ) - end - - def test_issues_form_update - ["post", "put"].each do |method| - assert_routing( - { :method => method, :path => "/projects/23/issues/new" }, - { :controller => 'issues', :action => 'new', :project_id => '23' } - ) - end - end - - def test_issues_extra_actions - assert_routing( - { :method => 'get', :path => "/projects/23/issues/64/copy" }, - { :controller => 'issues', :action => 'new', :project_id => '23', - :copy_from => '64' } - ) - # For updating the bulk edit form - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/issues/bulk_edit" }, - { :controller => 'issues', :action => 'bulk_edit' } - ) - end - assert_routing( - { :method => 'post', :path => "/issues/bulk_update" }, - { :controller => 'issues', :action => 'bulk_update' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/40/40da41a99bbaf8c8dd342e000dfd662f0b546a22.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/40/40da41a99bbaf8c8dd342e000dfd662f0b546a22.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,492 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../test_helper', __FILE__) +require 'digest/md5' + +class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase + + def setup + @formatter = Redmine::WikiFormatting::Textile::Formatter + end + + MODIFIERS = { + "*" => 'strong', # bold + "_" => 'em', # italic + "+" => 'ins', # underline + "-" => 'del', # deleted + "^" => 'sup', # superscript + "~" => 'sub' # subscript + } + + def test_modifiers + assert_html_output( + '*bold*' => 'bold', + 'before *bold*' => 'before bold', + '*bold* after' => 'bold after', + '*two words*' => 'two words', + '*two*words*' => 'two*words', + '*two * words*' => 'two * words', + '*two* *words*' => 'two words', + '*(two)* *(words)*' => '(two) (words)', + # with class + '*(foo)two words*' => 'two words' + ) + end + + def test_modifiers_combination + MODIFIERS.each do |m1, tag1| + MODIFIERS.each do |m2, tag2| + next if m1 == m2 + text = "#{m2}#{m1}Phrase modifiers#{m1}#{m2}" + html = "<#{tag2}><#{tag1}>Phrase modifiers" + assert_html_output text => html + end + end + end + + def test_styles + # single style + assert_html_output({ + 'p{color:red}. text' => '

    text

    ', + 'p{color:red;}. text' => '

    text

    ', + 'p{color: red}. text' => '

    text

    ', + 'p{color:#f00}. text' => '

    text

    ', + 'p{color:#ff0000}. text' => '

    text

    ', + 'p{border:10px}. text' => '

    text

    ', + 'p{border:10}. text' => '

    text

    ', + 'p{border:10%}. text' => '

    text

    ', + 'p{border:10em}. text' => '

    text

    ', + 'p{border:1.5em}. text' => '

    text

    ', + 'p{border-left:1px}. text' => '

    text

    ', + 'p{border-right:1px}. text' => '

    text

    ', + 'p{border-top:1px}. text' => '

    text

    ', + 'p{border-bottom:1px}. text' => '

    text

    ', + }, false) + + # multiple styles + assert_html_output({ + 'p{color:red; border-top:1px}. text' => '

    text

    ', + 'p{color:red ; border-top:1px}. text' => '

    text

    ', + 'p{color:red;border-top:1px}. text' => '

    text

    ', + }, false) + + # styles with multiple values + assert_html_output({ + 'p{border:1px solid red;}. text' => '

    text

    ', + 'p{border-top-left-radius: 10px 5px;}. text' => '

    text

    ', + }, false) + end + + def test_invalid_styles_should_be_filtered + assert_html_output({ + 'p{invalid}. text' => '

    text

    ', + 'p{invalid:red}. text' => '

    text

    ', + 'p{color:(red)}. text' => '

    text

    ', + 'p{color:red;invalid:blue}. text' => '

    text

    ', + 'p{invalid:blue;color:red}. text' => '

    text

    ', + 'p{color:"}. text' => '

    p{color:"}. text

    ', + }, false) + end + + def test_inline_code + assert_html_output( + 'this is @some code@' => 'this is some code', + '@@' => '<Location /redmine>' + ) + end + + def test_nested_lists + raw = <<-RAW +# Item 1 +# Item 2 +** Item 2a +** Item 2b +# Item 3 +** Item 3a +RAW + + expected = <<-EXPECTED +
      +
    1. Item 1
    2. +
    3. Item 2 +
        +
      • Item 2a
      • +
      • Item 2b
      • +
      +
    4. +
    5. Item 3 +
        +
      • Item 3a
      • +
      +
    6. +
    +EXPECTED + + assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '') + end + + def test_escaping + assert_html_output( + 'this is a " => "

    <script>some script;</script>

    ", + # do not escape pre/code tags + "
    \nline 1\nline2
    " => "
    \nline 1\nline2
    ", + "
    \nline 1\nline2
    " => "
    \nline 1\nline2
    ", + "
    content
    " => "
    <div>content</div>
    ", + "HTML comment: " => "

    HTML comment: <!-- no comments -->

    ", + " -

    -<%= text_field 'auth_source', 'name' %>

    - -

    -<%= text_field 'auth_source', 'host' %>

    - -

    -<%= text_field 'auth_source', 'port', :size => 6 %> <%= check_box 'auth_source', 'tls' %> LDAPS

    - -

    -<%= text_field 'auth_source', 'account' %>

    - -

    -<%= password_field 'auth_source', 'account_password', :name => 'ignore', - :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), - :onfocus => "this.value=''; this.name='auth_source[account_password]';", - :onchange => "this.name='auth_source[account_password]';" %>

    - -

    -<%= text_field 'auth_source', 'base_dn', :size => 60 %>

    - -

    -<%= text_field 'auth_source', 'filter', :size => 60 %>

    - -

    -<%= text_field 'auth_source', 'timeout', :size => 4 %>

    - -

    -<%= check_box 'auth_source', 'onthefly_register' %>

    - - -
    <%=l(:label_attribute_plural)%> -

    -<%= text_field 'auth_source', 'attr_login', :size => 20 %>

    - -

    -<%= text_field 'auth_source', 'attr_firstname', :size => 20 %>

    - -

    -<%= text_field 'auth_source', 'attr_lastname', :size => 20 %>

    - -

    -<%= text_field 'auth_source', 'attr_mail', :size => 20 %>

    -
    - - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a1/a1714901d6819a8b467120ed258d4fc0049a8e19.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a1/a1714901d6819a8b467120ed258d4fc0049a8e19.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,522 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +desc 'Mantis migration script' + +require 'active_record' +require 'iconv' if RUBY_VERSION < '1.9' +require 'pp' + +namespace :redmine do +task :migrate_from_mantis => :environment do + + module MantisMigrate + + DEFAULT_STATUS = IssueStatus.default + assigned_status = IssueStatus.find_by_position(2) + resolved_status = IssueStatus.find_by_position(3) + feedback_status = IssueStatus.find_by_position(4) + closed_status = IssueStatus.where(:is_closed => true).first + STATUS_MAPPING = {10 => DEFAULT_STATUS, # new + 20 => feedback_status, # feedback + 30 => DEFAULT_STATUS, # acknowledged + 40 => DEFAULT_STATUS, # confirmed + 50 => assigned_status, # assigned + 80 => resolved_status, # resolved + 90 => closed_status # closed + } + + priorities = IssuePriority.all + DEFAULT_PRIORITY = priorities[2] + PRIORITY_MAPPING = {10 => priorities[1], # none + 20 => priorities[1], # low + 30 => priorities[2], # normal + 40 => priorities[3], # high + 50 => priorities[4], # urgent + 60 => priorities[5] # immediate + } + + TRACKER_BUG = Tracker.find_by_position(1) + TRACKER_FEATURE = Tracker.find_by_position(2) + + roles = Role.where(:builtin => 0).order('position ASC').all + manager_role = roles[0] + developer_role = roles[1] + DEFAULT_ROLE = roles.last + ROLE_MAPPING = {10 => DEFAULT_ROLE, # viewer + 25 => DEFAULT_ROLE, # reporter + 40 => DEFAULT_ROLE, # updater + 55 => developer_role, # developer + 70 => manager_role, # manager + 90 => manager_role # administrator + } + + CUSTOM_FIELD_TYPE_MAPPING = {0 => 'string', # String + 1 => 'int', # Numeric + 2 => 'int', # Float + 3 => 'list', # Enumeration + 4 => 'string', # Email + 5 => 'bool', # Checkbox + 6 => 'list', # List + 7 => 'list', # Multiselection list + 8 => 'date', # Date + } + + RELATION_TYPE_MAPPING = {1 => IssueRelation::TYPE_RELATES, # related to + 2 => IssueRelation::TYPE_RELATES, # parent of + 3 => IssueRelation::TYPE_RELATES, # child of + 0 => IssueRelation::TYPE_DUPLICATES, # duplicate of + 4 => IssueRelation::TYPE_DUPLICATES # has duplicate + } + + class MantisUser < ActiveRecord::Base + self.table_name = :mantis_user_table + + def firstname + @firstname = realname.blank? ? username : realname.split.first[0..29] + @firstname + end + + def lastname + @lastname = realname.blank? ? '-' : realname.split[1..-1].join(' ')[0..29] + @lastname = '-' if @lastname.blank? + @lastname + end + + def email + if read_attribute(:email).match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) && + !User.find_by_mail(read_attribute(:email)) + @email = read_attribute(:email) + else + @email = "#{username}@foo.bar" + end + end + + def username + read_attribute(:username)[0..29].gsub(/[^a-zA-Z0-9_\-@\.]/, '-') + end + end + + class MantisProject < ActiveRecord::Base + self.table_name = :mantis_project_table + has_many :versions, :class_name => "MantisVersion", :foreign_key => :project_id + has_many :categories, :class_name => "MantisCategory", :foreign_key => :project_id + has_many :news, :class_name => "MantisNews", :foreign_key => :project_id + has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id + + def identifier + read_attribute(:name).downcase.gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH) + end + end + + class MantisVersion < ActiveRecord::Base + self.table_name = :mantis_project_version_table + + def version + read_attribute(:version)[0..29] + end + + def description + read_attribute(:description)[0..254] + end + end + + class MantisCategory < ActiveRecord::Base + self.table_name = :mantis_project_category_table + end + + class MantisProjectUser < ActiveRecord::Base + self.table_name = :mantis_project_user_list_table + end + + class MantisBug < ActiveRecord::Base + self.table_name = :mantis_bug_table + belongs_to :bug_text, :class_name => "MantisBugText", :foreign_key => :bug_text_id + has_many :bug_notes, :class_name => "MantisBugNote", :foreign_key => :bug_id + has_many :bug_files, :class_name => "MantisBugFile", :foreign_key => :bug_id + has_many :bug_monitors, :class_name => "MantisBugMonitor", :foreign_key => :bug_id + end + + class MantisBugText < ActiveRecord::Base + self.table_name = :mantis_bug_text_table + + # Adds Mantis steps_to_reproduce and additional_information fields + # to description if any + def full_description + full_description = description + full_description += "\n\n*Steps to reproduce:*\n\n#{steps_to_reproduce}" unless steps_to_reproduce.blank? + full_description += "\n\n*Additional information:*\n\n#{additional_information}" unless additional_information.blank? + full_description + end + end + + class MantisBugNote < ActiveRecord::Base + self.table_name = :mantis_bugnote_table + belongs_to :bug, :class_name => "MantisBug", :foreign_key => :bug_id + belongs_to :bug_note_text, :class_name => "MantisBugNoteText", :foreign_key => :bugnote_text_id + end + + class MantisBugNoteText < ActiveRecord::Base + self.table_name = :mantis_bugnote_text_table + end + + class MantisBugFile < ActiveRecord::Base + self.table_name = :mantis_bug_file_table + + def size + filesize + end + + def original_filename + MantisMigrate.encode(filename) + end + + def content_type + file_type + end + + def read(*args) + if @read_finished + nil + else + @read_finished = true + content + end + end + end + + class MantisBugRelationship < ActiveRecord::Base + self.table_name = :mantis_bug_relationship_table + end + + class MantisBugMonitor < ActiveRecord::Base + self.table_name = :mantis_bug_monitor_table + end + + class MantisNews < ActiveRecord::Base + self.table_name = :mantis_news_table + end + + class MantisCustomField < ActiveRecord::Base + self.table_name = :mantis_custom_field_table + set_inheritance_column :none + has_many :values, :class_name => "MantisCustomFieldString", :foreign_key => :field_id + has_many :projects, :class_name => "MantisCustomFieldProject", :foreign_key => :field_id + + def format + read_attribute :type + end + + def name + read_attribute(:name)[0..29] + end + end + + class MantisCustomFieldProject < ActiveRecord::Base + self.table_name = :mantis_custom_field_project_table + end + + class MantisCustomFieldString < ActiveRecord::Base + self.table_name = :mantis_custom_field_string_table + end + + def self.migrate + + # Users + print "Migrating users" + User.delete_all "login <> 'admin'" + users_map = {} + users_migrated = 0 + MantisUser.all.each do |user| + u = User.new :firstname => encode(user.firstname), + :lastname => encode(user.lastname), + :mail => user.email, + :last_login_on => user.last_visit + u.login = user.username + u.password = 'mantis' + u.status = User::STATUS_LOCKED if user.enabled != 1 + u.admin = true if user.access_level == 90 + next unless u.save! + users_migrated += 1 + users_map[user.id] = u.id + print '.' + end + puts + + # Projects + print "Migrating projects" + Project.destroy_all + projects_map = {} + versions_map = {} + categories_map = {} + MantisProject.all.each do |project| + p = Project.new :name => encode(project.name), + :description => encode(project.description) + p.identifier = project.identifier + next unless p.save + projects_map[project.id] = p.id + p.enabled_module_names = ['issue_tracking', 'news', 'wiki'] + p.trackers << TRACKER_BUG unless p.trackers.include?(TRACKER_BUG) + p.trackers << TRACKER_FEATURE unless p.trackers.include?(TRACKER_FEATURE) + print '.' + + # Project members + project.members.each do |member| + m = Member.new :user => User.find_by_id(users_map[member.user_id]), + :roles => [ROLE_MAPPING[member.access_level] || DEFAULT_ROLE] + m.project = p + m.save + end + + # Project versions + project.versions.each do |version| + v = Version.new :name => encode(version.version), + :description => encode(version.description), + :effective_date => (version.date_order ? version.date_order.to_date : nil) + v.project = p + v.save + versions_map[version.id] = v.id + end + + # Project categories + project.categories.each do |category| + g = IssueCategory.new :name => category.category[0,30] + g.project = p + g.save + categories_map[category.category] = g.id + end + end + puts + + # Bugs + print "Migrating bugs" + Issue.destroy_all + issues_map = {} + keep_bug_ids = (Issue.count == 0) + MantisBug.find_each(:batch_size => 200) do |bug| + next unless projects_map[bug.project_id] && users_map[bug.reporter_id] + i = Issue.new :project_id => projects_map[bug.project_id], + :subject => encode(bug.summary), + :description => encode(bug.bug_text.full_description), + :priority => PRIORITY_MAPPING[bug.priority] || DEFAULT_PRIORITY, + :created_on => bug.date_submitted, + :updated_on => bug.last_updated + i.author = User.find_by_id(users_map[bug.reporter_id]) + i.category = IssueCategory.find_by_project_id_and_name(i.project_id, bug.category[0,30]) unless bug.category.blank? + i.fixed_version = Version.find_by_project_id_and_name(i.project_id, bug.fixed_in_version) unless bug.fixed_in_version.blank? + i.status = STATUS_MAPPING[bug.status] || DEFAULT_STATUS + i.tracker = (bug.severity == 10 ? TRACKER_FEATURE : TRACKER_BUG) + i.id = bug.id if keep_bug_ids + next unless i.save + issues_map[bug.id] = i.id + print '.' + STDOUT.flush + + # Assignee + # Redmine checks that the assignee is a project member + if (bug.handler_id && users_map[bug.handler_id]) + i.assigned_to = User.find_by_id(users_map[bug.handler_id]) + i.save(:validate => false) + end + + # Bug notes + bug.bug_notes.each do |note| + next unless users_map[note.reporter_id] + n = Journal.new :notes => encode(note.bug_note_text.note), + :created_on => note.date_submitted + n.user = User.find_by_id(users_map[note.reporter_id]) + n.journalized = i + n.save + end + + # Bug files + bug.bug_files.each do |file| + a = Attachment.new :created_on => file.date_added + a.file = file + a.author = User.first + a.container = i + a.save + end + + # Bug monitors + bug.bug_monitors.each do |monitor| + next unless users_map[monitor.user_id] + i.add_watcher(User.find_by_id(users_map[monitor.user_id])) + end + end + + # update issue id sequence if needed (postgresql) + Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!') + puts + + # Bug relationships + print "Migrating bug relations" + MantisBugRelationship.all.each do |relation| + next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id] + r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type] + r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id]) + r.issue_to = Issue.find_by_id(issues_map[relation.destination_bug_id]) + pp r unless r.save + print '.' + STDOUT.flush + end + puts + + # News + print "Migrating news" + News.destroy_all + MantisNews.where('project_id > 0').all.each do |news| + next unless projects_map[news.project_id] + n = News.new :project_id => projects_map[news.project_id], + :title => encode(news.headline[0..59]), + :description => encode(news.body), + :created_on => news.date_posted + n.author = User.find_by_id(users_map[news.poster_id]) + n.save + print '.' + STDOUT.flush + end + puts + + # Custom fields + print "Migrating custom fields" + IssueCustomField.destroy_all + MantisCustomField.all.each do |field| + f = IssueCustomField.new :name => field.name[0..29], + :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format], + :min_length => field.length_min, + :max_length => field.length_max, + :regexp => field.valid_regexp, + :possible_values => field.possible_values.split('|'), + :is_required => field.require_report? + next unless f.save + print '.' + STDOUT.flush + # Trackers association + f.trackers = Tracker.all + + # Projects association + field.projects.each do |project| + f.projects << Project.find_by_id(projects_map[project.project_id]) if projects_map[project.project_id] + end + + # Values + field.values.each do |value| + v = CustomValue.new :custom_field_id => f.id, + :value => value.value + v.customized = Issue.find_by_id(issues_map[value.bug_id]) if issues_map[value.bug_id] + v.save + end unless f.new_record? + end + puts + + puts + puts "Users: #{users_migrated}/#{MantisUser.count}" + puts "Projects: #{Project.count}/#{MantisProject.count}" + puts "Memberships: #{Member.count}/#{MantisProjectUser.count}" + puts "Versions: #{Version.count}/#{MantisVersion.count}" + puts "Categories: #{IssueCategory.count}/#{MantisCategory.count}" + puts "Bugs: #{Issue.count}/#{MantisBug.count}" + puts "Bug notes: #{Journal.count}/#{MantisBugNote.count}" + puts "Bug files: #{Attachment.count}/#{MantisBugFile.count}" + puts "Bug relations: #{IssueRelation.count}/#{MantisBugRelationship.count}" + puts "Bug monitors: #{Watcher.count}/#{MantisBugMonitor.count}" + puts "News: #{News.count}/#{MantisNews.count}" + puts "Custom fields: #{IssueCustomField.count}/#{MantisCustomField.count}" + end + + def self.encoding(charset) + @charset = charset + end + + def self.establish_connection(params) + constants.each do |const| + klass = const_get(const) + next unless klass.respond_to? 'establish_connection' + klass.establish_connection params + end + end + + def self.encode(text) + if RUBY_VERSION < '1.9' + @ic ||= Iconv.new('UTF-8', @charset) + @ic.iconv text + else + text.to_s.force_encoding(@charset).encode('UTF-8') + end + end + end + + puts + if Redmine::DefaultData::Loader.no_data? + puts "Redmine configuration need to be loaded before importing data." + puts "Please, run this first:" + puts + puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" + exit + end + + puts "WARNING: Your Redmine data will be deleted during this process." + print "Are you sure you want to continue ? [y/N] " + STDOUT.flush + break unless STDIN.gets.match(/^y$/i) + + # Default Mantis database settings + db_params = {:adapter => 'mysql2', + :database => 'bugtracker', + :host => 'localhost', + :username => 'root', + :password => '' } + + puts + puts "Please enter settings for your Mantis database" + [:adapter, :host, :database, :username, :password].each do |param| + print "#{param} [#{db_params[param]}]: " + value = STDIN.gets.chomp! + db_params[param] = value unless value.blank? + end + + while true + print "encoding [UTF-8]: " + STDOUT.flush + encoding = STDIN.gets.chomp! + encoding = 'UTF-8' if encoding.blank? + break if MantisMigrate.encoding encoding + puts "Invalid encoding!" + end + puts + + # Make sure bugs can refer bugs in other projects + Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations' + + old_notified_events = Setting.notified_events + old_password_min_length = Setting.password_min_length + begin + # Turn off email notifications temporarily + Setting.notified_events = [] + Setting.password_min_length = 4 + # Run the migration + MantisMigrate.establish_connection db_params + MantisMigrate.migrate + ensure + # Restore previous settings + Setting.notified_events = old_notified_events + Setting.password_min_length = old_password_min_length + end + +end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a1/a17bebd39a4fcf9d00edebf243b30988cc1c3de1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a1/a17bebd39a4fcf9d00edebf243b30988cc1c3de1.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +<%= title [l(:label_group_plural), groups_path], @group.name %> + +
      +<% @group.users.each do |user| %> +
    • <%=h user %>
    • +<% end %> +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a1/a1b77b806397cc5fdef1b8ec65e5b9652bc154db.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a1/a1b77b806397cc5fdef1b8ec65e5b9652bc154db.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,201 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class MyController < ApplicationController + before_filter :require_login + # let user change user's password when user has to + skip_before_filter :check_password_change, :only => :password + + helper :issues + helper :users + helper :custom_fields + + BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues, + 'issuesreportedbyme' => :label_reported_issues, + 'issueswatched' => :label_watched_issues, + 'news' => :label_news_latest, + 'calendar' => :label_calendar, + 'documents' => :label_document_plural, + 'timelog' => :label_spent_time + }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze + + DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'], + 'right' => ['issuesreportedbyme'] + }.freeze + + def index + page + render :action => 'page' + end + + # Show user's page + def page + @user = User.current + @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT + end + + # Edit user's account + def account + @user = User.current + @pref = @user.pref + if request.post? + @user.safe_attributes = params[:user] + @user.pref.attributes = params[:pref] + if @user.save + @user.pref.save + set_language_if_valid @user.language + flash[:notice] = l(:notice_account_updated) + redirect_to my_account_path + return + end + end + end + + # Destroys user's account + def destroy + @user = User.current + unless @user.own_account_deletable? + redirect_to my_account_path + return + end + + if request.post? && params[:confirm] + @user.destroy + if @user.destroyed? + logout_user + flash[:notice] = l(:notice_account_deleted) + end + redirect_to home_path + end + end + + # Manage user's password + def password + @user = User.current + unless @user.change_password_allowed? + flash[:error] = l(:notice_can_t_change_password) + redirect_to my_account_path + return + end + if request.post? + if !@user.check_password?(params[:password]) + flash.now[:error] = l(:notice_account_wrong_password) + elsif params[:password] == params[:new_password] + flash.now[:error] = l(:notice_new_password_must_be_different) + else + @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] + @user.must_change_passwd = false + if @user.save + flash[:notice] = l(:notice_account_password_updated) + redirect_to my_account_path + end + end + end + end + + # Create a new feeds key + def reset_rss_key + if request.post? + if User.current.rss_token + User.current.rss_token.destroy + User.current.reload + end + User.current.rss_key + flash[:notice] = l(:notice_feeds_access_key_reseted) + end + redirect_to my_account_path + end + + # Create a new API key + def reset_api_key + if request.post? + if User.current.api_token + User.current.api_token.destroy + User.current.reload + end + User.current.api_key + flash[:notice] = l(:notice_api_access_key_reseted) + end + redirect_to my_account_path + end + + # User's page layout configuration + def page_layout + @user = User.current + @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup + @block_options = [] + BLOCKS.each do |k, v| + unless %w(top left right).detect {|f| (@blocks[f] ||= []).include?(k)} + @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize] + end + end + end + + # Add a block to user's page + # The block is added on top of the page + # params[:block] : id of the block to add + def add_block + block = params[:block].to_s.underscore + if block.present? && BLOCKS.key?(block) + @user = User.current + layout = @user.pref[:my_page_layout] || {} + # remove if already present in a group + %w(top left right).each {|f| (layout[f] ||= []).delete block } + # add it on top + layout['top'].unshift block + @user.pref[:my_page_layout] = layout + @user.pref.save + end + redirect_to my_page_layout_path + end + + # Remove a block to user's page + # params[:block] : id of the block to remove + def remove_block + block = params[:block].to_s.underscore + @user = User.current + # remove block in all groups + layout = @user.pref[:my_page_layout] || {} + %w(top left right).each {|f| (layout[f] ||= []).delete block } + @user.pref[:my_page_layout] = layout + @user.pref.save + redirect_to my_page_layout_path + end + + # Change blocks order on user's page + # params[:group] : group to order (top, left or right) + # params[:list-(top|left|right)] : array of block ids of the group + def order_blocks + group = params[:group] + @user = User.current + if group.is_a?(String) + group_items = (params["blocks"] || []).collect(&:underscore) + group_items.each {|s| s.sub!(/^block_/, '')} + if group_items and group_items.is_a? Array + layout = @user.pref[:my_page_layout] || {} + # remove group blocks if they are presents in other groups + %w(top left right).each {|f| + layout[f] = (layout[f] || []) - group_items + } + layout[group] = group_items + @user.pref[:my_page_layout] = layout + @user.pref.save + end + end + render :nothing => true + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a2/a228c3d0b7910e4eb07f95e583bb22e9efe2f4c4.svn-base --- a/.svn/pristine/a2/a228c3d0b7910e4eb07f95e583bb22e9efe2f4c4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,220 +0,0 @@ -# redminehelper: Redmine helper extension for Mercurial -# -# Copyright 2010 Alessio Franceschelli (alefranz.net) -# Copyright 2010-2011 Yuya Nishihara -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. -"""helper commands for Redmine to reduce the number of hg calls - -To test this extension, please try:: - - $ hg --config extensions.redminehelper=redminehelper.py rhsummary - -I/O encoding: - -:file path: urlencoded, raw string -:tag name: utf-8 -:branch name: utf-8 -:node: 12-digits (short) hex string - -Output example of rhsummary:: - - - - - - - - ... - - - -Output example of rhmanifest:: - - - - - - - ... - - ... - - - -""" -import re, time, cgi, urllib -from mercurial import cmdutil, commands, node, error, hg - -_x = cgi.escape -_u = lambda s: cgi.escape(urllib.quote(s)) - -def _tip(ui, repo): - # see mercurial/commands.py:tip - def tiprev(): - try: - return len(repo) - 1 - except TypeError: # Mercurial < 1.1 - return repo.changelog.count() - 1 - tipctx = repo.changectx(tiprev()) - ui.write('\n' - % (tipctx.rev(), _x(node.short(tipctx.node())))) - -_SPECIAL_TAGS = ('tip',) - -def _tags(ui, repo): - # see mercurial/commands.py:tags - for t, n in reversed(repo.tagslist()): - if t in _SPECIAL_TAGS: - continue - try: - r = repo.changelog.rev(n) - except error.LookupError: - continue - ui.write('\n' - % (r, _x(node.short(n)), _x(t))) - -def _branches(ui, repo): - # see mercurial/commands.py:branches - def iterbranches(): - for t, n in repo.branchtags().iteritems(): - yield t, n, repo.changelog.rev(n) - def branchheads(branch): - try: - return repo.branchheads(branch, closed=False) - except TypeError: # Mercurial < 1.2 - return repo.branchheads(branch) - for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True): - if repo.lookup(r) in branchheads(t): - ui.write('\n' - % (r, _x(node.short(n)), _x(t))) - -def _manifest(ui, repo, path, rev): - ctx = repo.changectx(rev) - ui.write('\n' - % (ctx.rev(), _u(path))) - - known = set() - pathprefix = (path.rstrip('/') + '/').lstrip('/') - for f, n in sorted(ctx.manifest().iteritems(), key=lambda e: e[0]): - if not f.startswith(pathprefix): - continue - name = re.sub(r'/.*', '/', f[len(pathprefix):]) - if name in known: - continue - known.add(name) - - if name.endswith('/'): - ui.write('\n' - % _x(urllib.quote(name[:-1]))) - else: - fctx = repo.filectx(f, fileid=n) - tm, tzoffset = fctx.date() - ui.write('\n' - % (_u(name), fctx.rev(), _x(node.short(fctx.node())), - tm, fctx.size(), )) - - ui.write('\n') - -def rhannotate(ui, repo, *pats, **opts): - rev = urllib.unquote_plus(opts.pop('rev', None)) - opts['rev'] = rev - return commands.annotate(ui, repo, *map(urllib.unquote_plus, pats), **opts) - -def rhcat(ui, repo, file1, *pats, **opts): - rev = urllib.unquote_plus(opts.pop('rev', None)) - opts['rev'] = rev - return commands.cat(ui, repo, urllib.unquote_plus(file1), *map(urllib.unquote_plus, pats), **opts) - -def rhdiff(ui, repo, *pats, **opts): - """diff repository (or selected files)""" - change = opts.pop('change', None) - if change: # add -c option for Mercurial<1.1 - base = repo.changectx(change).parents()[0].rev() - opts['rev'] = [str(base), change] - opts['nodates'] = True - return commands.diff(ui, repo, *map(urllib.unquote_plus, pats), **opts) - -def rhlog(ui, repo, *pats, **opts): - rev = opts.pop('rev') - bra0 = opts.pop('branch') - from_rev = urllib.unquote_plus(opts.pop('from', None)) - to_rev = urllib.unquote_plus(opts.pop('to' , None)) - bra = urllib.unquote_plus(opts.pop('rhbranch', None)) - from_rev = from_rev.replace('"', '\\"') - to_rev = to_rev.replace('"', '\\"') - if hg.util.version() >= '1.6': - opts['rev'] = ['"%s":"%s"' % (from_rev, to_rev)] - else: - opts['rev'] = ['%s:%s' % (from_rev, to_rev)] - opts['branch'] = [bra] - return commands.log(ui, repo, *map(urllib.unquote_plus, pats), **opts) - -def rhmanifest(ui, repo, path='', **opts): - """output the sub-manifest of the specified directory""" - ui.write('\n') - ui.write('\n') - ui.write('\n' % _u(repo.root)) - try: - _manifest(ui, repo, urllib.unquote_plus(path), urllib.unquote_plus(opts.get('rev'))) - finally: - ui.write('\n') - ui.write('\n') - -def rhsummary(ui, repo, **opts): - """output the summary of the repository""" - ui.write('\n') - ui.write('\n') - ui.write('\n' % _u(repo.root)) - try: - _tip(ui, repo) - _tags(ui, repo) - _branches(ui, repo) - # TODO: bookmarks in core (Mercurial>=1.8) - finally: - ui.write('\n') - ui.write('\n') - -cmdtable = { - 'rhannotate': (rhannotate, - [('r', 'rev', '', 'revision'), - ('u', 'user', None, 'list the author (long with -v)'), - ('n', 'number', None, 'list the revision number (default)'), - ('c', 'changeset', None, 'list the changeset'), - ], - 'hg rhannotate [-r REV] [-u] [-n] [-c] FILE...'), - 'rhcat': (rhcat, - [('r', 'rev', '', 'revision')], - 'hg rhcat ([-r REV] ...) FILE...'), - 'rhdiff': (rhdiff, - [('r', 'rev', [], 'revision'), - ('c', 'change', '', 'change made by revision')], - 'hg rhdiff ([-c REV] | [-r REV] ...) [FILE]...'), - 'rhlog': (rhlog, - [ - ('r', 'rev', [], 'show the specified revision'), - ('b', 'branch', [], - 'show changesets within the given named branch'), - ('l', 'limit', '', - 'limit number of changes displayed'), - ('d', 'date', '', - 'show revisions matching date spec'), - ('u', 'user', [], - 'revisions committed by user'), - ('', 'from', '', - ''), - ('', 'to', '', - ''), - ('', 'rhbranch', '', - ''), - ('', 'template', '', - 'display with template')], - 'hg rhlog [OPTION]... [FILE]'), - 'rhmanifest': (rhmanifest, - [('r', 'rev', '', 'show the specified revision')], - 'hg rhmanifest [-r REV] [PATH]'), - 'rhsummary': (rhsummary, [], 'hg rhsummary'), -} diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a2/a268aadf60b30880792c7f50cf7c1f07f63819c9.svn-base --- a/.svn/pristine/a2/a268aadf60b30880792c7f50cf7c1f07f63819c9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::JsonpTest < ActionController::IntegrationTest - fixtures :trackers - - def test_jsonp_should_accept_callback_param - get '/trackers.json?callback=handler' - - assert_response :success - assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body - assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] - end - - def test_jsonp_should_accept_jsonp_param - get '/trackers.json?jsonp=handler' - - assert_response :success - assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body - assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] - end - - def test_jsonp_should_strip_invalid_characters_from_callback - get '/trackers.json?callback=+-aA$1_' - - assert_response :success - assert_match %r{^aA1_\(\{"trackers":.+\}\)$}, response.body - assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] - end - - def test_jsonp_without_callback_should_return_json - get '/trackers.json?callback=' - - assert_response :success - assert_match %r{^\{"trackers":.+\}$}, response.body - assert_equal 'application/json; charset=utf-8', response.headers['Content-Type'] - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a2/a2c9e135b5c07d36380e77abbbd164596d15660d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a2/a2c9e135b5c07d36380e77abbbd164596d15660d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,100 @@ +.jstEditor { + padding-left: 0px; +} +.jstEditor textarea, .jstEditor iframe { + margin: 0; +} + +.jstHandle { + height: 10px; + font-size: 0.1em; + cursor: s-resize; + /*background: transparent url(img/resizer.png) no-repeat 45% 50%;*/ +} + +.jstElements { + padding: 3px 3px 3px 0; +} + +.jstElements button { + margin-right: 4px; + width : 24px; + height: 24px; + padding: 4px; + border-style: solid; + border-width: 1px; + border-color: #ddd; + background-color : #f7f7f7; + background-position : 50% 50%; + background-repeat: no-repeat; +} +.jstElements button:hover { + border-color: #bbb; + background-color: #e5e5e5; +} +.jstElements button span { + display : none; +} +.jstElements span { + display : inline; +} + +.jstSpacer { + width : 0px; + font-size: 1px; + margin-right: 6px; +} + +.jstElements .help { float: right; margin-right: 0.5em; padding-top: 8px; font-size: 0.9em; } +.jstElements .help a {padding: 2px 0 2px 20px; background: url(../images/help.png) no-repeat 0 50%;} + +/* Buttons +-------------------------------------------------------- */ +.jstb_strong { + background-image: url(../images/jstoolbar/bt_strong.png); +} +.jstb_em { + background-image: url(../images/jstoolbar/bt_em.png); +} +.jstb_ins { + background-image: url(../images/jstoolbar/bt_ins.png); +} +.jstb_del { + background-image: url(../images/jstoolbar/bt_del.png); +} +.jstb_code { + background-image: url(../images/jstoolbar/bt_code.png); +} +.jstb_h1 { + background-image: url(../images/jstoolbar/bt_h1.png); +} +.jstb_h2 { + background-image: url(../images/jstoolbar/bt_h2.png); +} +.jstb_h3 { + background-image: url(../images/jstoolbar/bt_h3.png); +} +.jstb_ul { + background-image: url(../images/jstoolbar/bt_ul.png); +} +.jstb_ol { + background-image: url(../images/jstoolbar/bt_ol.png); +} +.jstb_bq { + background-image: url(../images/jstoolbar/bt_bq.png); +} +.jstb_unbq { + background-image: url(../images/jstoolbar/bt_bq_remove.png); +} +.jstb_pre { + background-image: url(../images/jstoolbar/bt_pre.png); +} +.jstb_link { + background-image: url(../images/jstoolbar/bt_link.png); +} +.jstb_img { + background-image: url(../images/jstoolbar/bt_img.png); +} +.jstb_help { + background-image: url(../images/help.png); +} diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a2/a2df3b427a668a9b7e89ac99fd87ef2e91829150.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a2/a2df3b427a668a9b7e89ac99fd87ef2e91829150.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-07-27 04:20:45.973229414 +0900 ++++ b.txt 2013-07-27 04:20:52.366228105 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本記 ++日本誘 + bbbb diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a2/a2f5d2cfdb3401fa081b47084d01e011e03ca83c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a2/a2f5d2cfdb3401fa081b47084d01e011e03ca83c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,11 @@ +<% if @notes %> +
    <%= l(:field_notes) %> + <%= textilizable @notes, :attachments => @attachments, :object => @issue %> +
    +<% end %> + +<% if @description %> +
    <%= l(:field_description) %> + <%= textilizable @description, :attachments => @attachments, :object => @issue %> +
    +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a314ca86d401cd6c66c2c275d0d98bd9585e53c9.svn-base --- a/.svn/pristine/a3/a314ca86d401cd6c66c2c275d0d98bd9585e53c9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module ProjectsHelper - def link_to_version(version, options = {}) - return '' unless version && version.is_a?(Version) - link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options - end - - def project_settings_tabs - tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural}, - {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural}, - {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural}, - {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural}, - {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural}, - {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki}, - {:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural}, - {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}, - {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities} - ] - tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)} - end - - def parent_project_select_tag(project) - selected = project.parent - # retrieve the requested parent project - parent_id = (params[:project] && params[:project][:parent_id]) || params[:parent_id] - if parent_id - selected = (parent_id.blank? ? nil : Project.find(parent_id)) - end - - options = '' - options << "" if project.allowed_parents.include?(nil) - options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected) - content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id') - end - - # Renders the projects index - def render_project_hierarchy(projects) - render_project_nested_lists(projects) do |project| - s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'my-project' : nil}") - if project.description.present? - s << content_tag('div', textilizable(project.short_description, :project => project), :class => 'wiki description') - end - s - end - end - - # Returns a set of options for a select field, grouped by project. - def version_options_for_select(versions, selected=nil) - grouped = Hash.new {|h,k| h[k] = []} - versions.each do |version| - grouped[version.project.name] << [version.name, version.id] - end - - if grouped.keys.size > 1 - grouped_options_for_select(grouped, selected && selected.id) - else - options_for_select((grouped.values.first || []), selected && selected.id) - end - end - - def format_version_sharing(sharing) - sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing) - l("label_version_sharing_#{sharing}") - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a326dc11b9640e3425d44fc1fc230394f2aa2cdc.svn-base --- a/.svn/pristine/a3/a326dc11b9640e3425d44fc1fc230394f2aa2cdc.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -class ChangeChangesetsRevisionToString < ActiveRecord::Migration - def self.up - change_column :changesets, :revision, :string, :null => false - end - - def self.down - change_column :changesets, :revision, :integer, :null => false - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a3633f20835dc5cd5c9d5ebc3b95831af84b847b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a3/a3633f20835dc5cd5c9d5ebc3b95831af84b847b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1109 @@ +# Portuguese localization for Ruby on Rails +# by Ricardo Otero +# by Alberto Ferreira +# by Rui Rebelo +pt: + support: + array: + sentence_connector: "e" + skip_last_comma: true + + direction: ltr + date: + formats: + default: "%d/%m/%Y" + short: "%d de %B" + long: "%d de %B de %Y" + only_day: "%d" + day_names: [Domingo, Segunda, Terça, Quarta, Quinta, Sexta, Sábado] + abbr_day_names: [Dom, Seg, Ter, Qua, Qui, Sex, Sáb] + month_names: [~, Janeiro, Fevereiro, Março, Abril, Maio, Junho, Julho, Agosto, Setembro, Outubro, Novembro, Dezembro] + abbr_month_names: [~, Jan, Fev, Mar, Abr, Mai, Jun, Jul, Ago, Set, Out, Nov, Dez] + order: + - :day + - :month + - :year + + time: + formats: + default: "%A, %d de %B de %Y, %H:%Mh" + time: "%H:%M" + short: "%d/%m, %H:%M hs" + long: "%A, %d de %B de %Y, %H:%Mh" + am: '' + pm: '' + + datetime: + distance_in_words: + half_a_minute: "meio minuto" + less_than_x_seconds: + one: "menos de 1 segundo" + other: "menos de %{count} segundos" + x_seconds: + one: "1 segundo" + other: "%{count} segundos" + less_than_x_minutes: + one: "menos de um minuto" + other: "menos de %{count} minutos" + x_minutes: + one: "1 minuto" + other: "%{count} minutos" + about_x_hours: + one: "aproximadamente 1 hora" + other: "aproximadamente %{count} horas" + x_hours: + one: "1 hora" + other: "%{count} horas" + x_days: + one: "1 dia" + other: "%{count} dias" + about_x_months: + one: "aproximadamente 1 mês" + other: "aproximadamente %{count} meses" + x_months: + one: "1 mês" + other: "%{count} meses" + about_x_years: + one: "aproximadamente 1 ano" + other: "aproximadamente %{count} anos" + over_x_years: + one: "mais de 1 ano" + other: "mais de %{count} anos" + almost_x_years: + one: "quase 1 ano" + other: "quase %{count} anos" + + number: + format: + precision: 3 + separator: ',' + delimiter: '.' + currency: + format: + unit: '€' + precision: 2 + format: "%u %n" + separator: ',' + delimiter: '.' + percentage: + format: + delimiter: '' + precision: + format: + delimiter: '' + human: + format: + precision: 3 + delimiter: '' + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + activerecord: + errors: + template: + header: + one: "Não foi possível guardar %{model}: 1 erro" + other: "Não foi possível guardar %{model}: %{count} erros" + body: "Por favor, verifique os seguintes campos:" + messages: + inclusion: "não está incluído na lista" + exclusion: "não está disponível" + invalid: "não é válido" + confirmation: "não está de acordo com a confirmação" + accepted: "precisa de ser aceite" + empty: "não pode estar em branco" + blank: "não pode estar em branco" + too_long: "tem demasiados caracteres (máximo: %{count} caracteres)" + too_short: "tem poucos caracteres (mínimo: %{count} caracteres)" + wrong_length: "não é do tamanho correcto (necessita de ter %{count} caracteres)" + taken: "não está disponível" + not_a_number: "não é um número" + greater_than: "tem de ser maior do que %{count}" + greater_than_or_equal_to: "tem de ser maior ou igual a %{count}" + equal_to: "tem de ser igual a %{count}" + less_than: "tem de ser menor do que %{count}" + less_than_or_equal_to: "tem de ser menor ou igual a %{count}" + odd: "tem de ser ímpar" + even: "tem de ser par" + greater_than_start_date: "deve ser maior que a data inicial" + not_same_project: "não pertence ao mesmo projecto" + circular_dependency: "Esta relação iria criar uma dependência circular" + cant_link_an_issue_with_a_descendant: "Não é possível ligar uma tarefa a uma sub-tarefa que lhe é pertencente" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + ## Translated by: Pedro Araújo + actionview_instancetag_blank_option: Seleccione + + general_text_No: 'Não' + general_text_Yes: 'Sim' + general_text_no: 'não' + general_text_yes: 'sim' + general_lang_name: 'Português' + general_csv_separator: ';' + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-15 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: A conta foi actualizada com sucesso. + notice_account_invalid_creditentials: Utilizador ou palavra-chave inválidos. + notice_account_password_updated: A palavra-chave foi alterada com sucesso. + notice_account_wrong_password: Palavra-chave errada. + notice_account_unknown_email: Utilizador desconhecido. + notice_can_t_change_password: Esta conta utiliza uma fonte de autenticação externa. Não é possível alterar a palavra-chave. + notice_account_lost_email_sent: Foi-lhe enviado um e-mail com as instruções para escolher uma nova palavra-chave. + notice_account_activated: A sua conta foi activada. É agora possível autenticar-se. + notice_successful_create: Criado com sucesso. + notice_successful_update: Alterado com sucesso. + notice_successful_delete: Apagado com sucesso. + notice_successful_connection: Ligado com sucesso. + notice_file_not_found: A página que está a tentar aceder não existe ou foi removida. + notice_locking_conflict: Os dados foram actualizados por outro utilizador. + notice_not_authorized: Não está autorizado a visualizar esta página. + notice_email_sent: "Foi enviado um e-mail para %{value}" + notice_email_error: "Ocorreu um erro ao enviar o e-mail (%{value})" + notice_feeds_access_key_reseted: A sua chave de Atom foi inicializada. + notice_failed_to_save_issues: "Não foi possível guardar %{count} tarefa(s) das %{total} seleccionadas: %{ids}." + notice_no_issue_selected: "Nenhuma tarefa seleccionada! Por favor, seleccione as tarefas que quer editar." + notice_account_pending: "A sua conta foi criada e está agora à espera de aprovação do administrador." + notice_default_data_loaded: Configuração padrão carregada com sucesso. + notice_unable_delete_version: Não foi possível apagar a versão. + + error_can_t_load_default_data: "Não foi possível carregar a configuração padrão: %{value}" + error_scm_not_found: "A entrada ou revisão não foi encontrada no repositório." + error_scm_command_failed: "Ocorreu um erro ao tentar aceder ao repositório: %{value}" + error_scm_annotate: "A entrada não existe ou não pode ser anotada." + error_issue_not_found_in_project: 'A tarefa não foi encontrada ou não pertence a este projecto.' + + mail_subject_lost_password: "Palavra-chave de %{value}" + mail_body_lost_password: 'Para mudar a sua palavra-chave, clique na ligação abaixo:' + mail_subject_register: "Activação de conta de %{value}" + mail_body_register: 'Para activar a sua conta, clique na ligação abaixo:' + mail_body_account_information_external: "Pode utilizar a conta %{value} para autenticar-se." + mail_body_account_information: Informação da sua conta + mail_subject_account_activation_request: "Pedido de activação da conta %{value}" + mail_body_account_activation_request: "Um novo utilizador (%{value}) registou-se. A sua conta está à espera de aprovação:" + mail_subject_reminder: "%{count} tarefa(s) para entregar nos próximos %{days} dias" + mail_body_reminder: "%{count} tarefa(s) que estão atribuídas a si estão agendadas para estarem completas nos próximos %{days} dias:" + + + field_name: Nome + field_description: Descrição + field_summary: Sumário + field_is_required: Obrigatório + field_firstname: Nome + field_lastname: Apelido + field_mail: E-mail + field_filename: Ficheiro + field_filesize: Tamanho + field_downloads: Downloads + field_author: Autor + field_created_on: Criado + field_updated_on: Alterado + field_field_format: Formato + field_is_for_all: Para todos os projectos + field_possible_values: Valores possíveis + field_regexp: Expressão regular + field_min_length: Tamanho mínimo + field_max_length: Tamanho máximo + field_value: Valor + field_category: Categoria + field_title: Título + field_project: Projecto + field_issue: Tarefa + field_status: Estado + field_notes: Notas + field_is_closed: Tarefa fechada + field_is_default: Valor por omissão + field_tracker: Tipo + field_subject: Assunto + field_due_date: Data fim + field_assigned_to: Atribuído a + field_priority: Prioridade + field_fixed_version: Versão + field_user: Utilizador + field_role: Função + field_homepage: Página + field_is_public: Público + field_parent: Sub-projecto de + field_is_in_roadmap: Tarefas mostradas no mapa de planificação + field_login: Nome de utilizador + field_mail_notification: Notificações por e-mail + field_admin: Administrador + field_last_login_on: Última visita + field_language: Língua + field_effective_date: Data + field_password: Palavra-chave + field_new_password: Nova palavra-chave + field_password_confirmation: Confirmação + field_version: Versão + field_type: Tipo + field_host: Servidor + field_port: Porta + field_account: Conta + field_base_dn: Base DN + field_attr_login: Atributo utilizador + field_attr_firstname: Atributo nome próprio + field_attr_lastname: Atributo último nome + field_attr_mail: Atributo e-mail + field_onthefly: Criação imediata de utilizadores + field_start_date: Data início + field_done_ratio: "% Completo" + field_auth_source: Modo de autenticação + field_hide_mail: Esconder endereço de e-mail + field_comments: Comentário + field_url: URL + field_start_page: Página inicial + field_subproject: Subprojecto + field_hours: Horas + field_activity: Actividade + field_spent_on: Data + field_identifier: Identificador + field_is_filter: Usado como filtro + field_issue_to: Tarefa relacionada + field_delay: Atraso + field_assignable: As tarefas podem ser associadas a esta função + field_redirect_existing_links: Redireccionar ligações existentes + field_estimated_hours: Tempo estimado + field_column_names: Colunas + field_time_zone: Fuso horário + field_searchable: Procurável + field_default_value: Valor por omissão + field_comments_sorting: Mostrar comentários + field_parent_title: Página pai + + setting_app_title: Título da aplicação + setting_app_subtitle: Sub-título da aplicação + setting_welcome_text: Texto de boas vindas + setting_default_language: Língua por omissão + setting_login_required: Autenticação obrigatória + setting_self_registration: Auto-registo + setting_attachment_max_size: Tamanho máximo do anexo + setting_issues_export_limit: Limite de exportação das tarefas + setting_mail_from: E-mail enviado de + setting_bcc_recipients: Recipientes de BCC + setting_host_name: Hostname + setting_text_formatting: Formatação do texto + setting_wiki_compression: Compressão do histórico do Wiki + setting_feeds_limit: Limite de conteúdo do feed + setting_default_projects_public: Projectos novos são públicos por omissão + setting_autofetch_changesets: Buscar automaticamente commits + setting_sys_api_enabled: Activar Web Service para gestão do repositório + setting_commit_ref_keywords: Palavras-chave de referência + setting_commit_fix_keywords: Palavras-chave de fecho + setting_autologin: Login automático + setting_date_format: Formato da data + setting_time_format: Formato do tempo + setting_cross_project_issue_relations: Permitir relações entre tarefas de projectos diferentes + setting_issue_list_default_columns: Colunas na lista de tarefas por omissão + setting_emails_footer: Rodapé do e-mails + setting_protocol: Protocolo + setting_per_page_options: Opções de objectos por página + setting_user_format: Formato de apresentaão de utilizadores + setting_activity_days_default: Dias mostrados na actividade do projecto + setting_display_subprojects_issues: Mostrar as tarefas dos sub-projectos nos projectos principais + setting_enabled_scm: Activar SCM + setting_mail_handler_api_enabled: Activar Web Service para e-mails recebidos + setting_mail_handler_api_key: Chave da API + setting_sequential_project_identifiers: Gerar identificadores de projecto sequênciais + + project_module_issue_tracking: Tarefas + project_module_time_tracking: Registo de tempo + project_module_news: Notícias + project_module_documents: Documentos + project_module_files: Ficheiros + project_module_wiki: Wiki + project_module_repository: Repositório + project_module_boards: Forum + + label_user: Utilizador + label_user_plural: Utilizadores + label_user_new: Novo utilizador + label_project: Projecto + label_project_new: Novo projecto + label_project_plural: Projectos + label_x_projects: + zero: no projects + one: 1 project + other: "%{count} projects" + label_project_all: Todos os projectos + label_project_latest: Últimos projectos + label_issue: Tarefa + label_issue_new: Nova tarefa + label_issue_plural: Tarefas + label_issue_view_all: Ver todas as tarefas + label_issues_by: "Tarefas por %{value}" + label_issue_added: Tarefa adicionada + label_issue_updated: Tarefa actualizada + label_document: Documento + label_document_new: Novo documento + label_document_plural: Documentos + label_document_added: Documento adicionado + label_role: Função + label_role_plural: Funções + label_role_new: Nova função + label_role_and_permissions: Funções e permissões + label_member: Membro + label_member_new: Novo membro + label_member_plural: Membros + label_tracker: Tipo + label_tracker_plural: Tipos + label_tracker_new: Novo tipo + label_workflow: Fluxo de trabalho + label_issue_status: Estado da tarefa + label_issue_status_plural: Estados da tarefa + label_issue_status_new: Novo estado + label_issue_category: Categoria de tarefa + label_issue_category_plural: Categorias de tarefa + label_issue_category_new: Nova categoria + label_custom_field: Campo personalizado + label_custom_field_plural: Campos personalizados + label_custom_field_new: Novo campo personalizado + label_enumerations: Enumerações + label_enumeration_new: Novo valor + label_information: Informação + label_information_plural: Informações + label_please_login: Por favor autentique-se + label_register: Registar + label_password_lost: Perdi a palavra-chave + label_home: Página Inicial + label_my_page: Página Pessoal + label_my_account: Minha conta + label_my_projects: Meus projectos + label_administration: Administração + label_login: Entrar + label_logout: Sair + label_help: Ajuda + label_reported_issues: Tarefas criadas + label_assigned_to_me_issues: Tarefas atribuídas a mim + label_last_login: Último acesso + label_registered_on: Registado em + label_activity: Actividade + label_overall_activity: Actividade geral + label_new: Novo + label_logged_as: Ligado como + label_environment: Ambiente + label_authentication: Autenticação + label_auth_source: Modo de autenticação + label_auth_source_new: Novo modo de autenticação + label_auth_source_plural: Modos de autenticação + label_subproject_plural: Sub-projectos + label_and_its_subprojects: "%{value} e sub-projectos" + label_min_max_length: Tamanho mínimo-máximo + label_list: Lista + label_date: Data + label_integer: Inteiro + label_float: Decimal + label_boolean: Booleano + label_string: Texto + label_text: Texto longo + label_attribute: Atributo + label_attribute_plural: Atributos + label_no_data: Sem dados para mostrar + label_change_status: Mudar estado + label_history: Histórico + label_attachment: Ficheiro + label_attachment_new: Novo ficheiro + label_attachment_delete: Apagar ficheiro + label_attachment_plural: Ficheiros + label_file_added: Ficheiro adicionado + label_report: Relatório + label_report_plural: Relatórios + label_news: Notícia + label_news_new: Nova notícia + label_news_plural: Notícias + label_news_latest: Últimas notícias + label_news_view_all: Ver todas as notícias + label_news_added: Notícia adicionada + label_settings: Configurações + label_overview: Visão geral + label_version: Versão + label_version_new: Nova versão + label_version_plural: Versões + label_confirmation: Confirmação + label_export_to: 'Também disponível em:' + label_read: Ler... + label_public_projects: Projectos públicos + label_open_issues: aberto + label_open_issues_plural: abertos + label_closed_issues: fechado + label_closed_issues_plural: fechados + label_x_open_issues_abbr_on_total: + zero: 0 abertas / %{total} + one: 1 aberta / %{total} + other: "%{count} abertas / %{total}" + label_x_open_issues_abbr: + zero: 0 abertas + one: 1 aberta + other: "%{count} abertas" + label_x_closed_issues_abbr: + zero: 0 fechadas + one: 1 fechada + other: "%{count} fechadas" + label_total: Total + label_permissions: Permissões + label_current_status: Estado actual + label_new_statuses_allowed: Novos estados permitidos + label_all: todos + label_none: nenhum + label_nobody: ninguém + label_next: Próximo + label_previous: Anterior + label_used_by: Usado por + label_details: Detalhes + label_add_note: Adicionar nota + label_per_page: Por página + label_calendar: Calendário + label_months_from: meses de + label_gantt: Gantt + label_internal: Interno + label_last_changes: "últimas %{count} alterações" + label_change_view_all: Ver todas as alterações + label_personalize_page: Personalizar esta página + label_comment: Comentário + label_comment_plural: Comentários + label_x_comments: + zero: sem comentários + one: 1 comentário + other: "%{count} comentários" + label_comment_add: Adicionar comentário + label_comment_added: Comentário adicionado + label_comment_delete: Apagar comentários + label_query: Consulta personalizada + label_query_plural: Consultas personalizadas + label_query_new: Nova consulta + label_filter_add: Adicionar filtro + label_filter_plural: Filtros + label_equals: é + label_not_equals: não é + label_in_less_than: em menos de + label_in_more_than: em mais de + label_in: em + label_today: hoje + label_all_time: sempre + label_yesterday: ontem + label_this_week: esta semana + label_last_week: semana passada + label_last_n_days: "últimos %{count} dias" + label_this_month: este mês + label_last_month: mês passado + label_this_year: este ano + label_date_range: Date range + label_less_than_ago: menos de dias atrás + label_more_than_ago: mais de dias atrás + label_ago: dias atrás + label_contains: contém + label_not_contains: não contém + label_day_plural: dias + label_repository: Repositório + label_repository_plural: Repositórios + label_browse: Navegar + label_revision: Revisão + label_revision_plural: Revisões + label_associated_revisions: Revisões associadas + label_added: adicionado + label_modified: modificado + label_copied: copiado + label_renamed: renomeado + label_deleted: apagado + label_latest_revision: Última revisão + label_latest_revision_plural: Últimas revisões + label_view_revisions: Ver revisões + label_max_size: Tamanho máximo + label_sort_highest: Mover para o início + label_sort_higher: Mover para cima + label_sort_lower: Mover para baixo + label_sort_lowest: Mover para o fim + label_roadmap: Planificação + label_roadmap_due_in: "Termina em %{value}" + label_roadmap_overdue: "Atrasado %{value}" + label_roadmap_no_issues: Sem tarefas para esta versão + label_search: Procurar + label_result_plural: Resultados + label_all_words: Todas as palavras + label_wiki: Wiki + label_wiki_edit: Edição da Wiki + label_wiki_edit_plural: Edições da Wiki + label_wiki_page: Página da Wiki + label_wiki_page_plural: Páginas da Wiki + label_index_by_title: Ãndice por título + label_index_by_date: Ãndice por data + label_current_version: Versão actual + label_preview: Pré-visualizar + label_feed_plural: Feeds + label_changes_details: Detalhes de todas as mudanças + label_issue_tracking: Tarefas + label_spent_time: Tempo gasto + label_f_hour: "%{value} hora" + label_f_hour_plural: "%{value} horas" + label_time_tracking: Registo de tempo + label_change_plural: Mudanças + label_statistics: Estatísticas + label_commits_per_month: Commits por mês + label_commits_per_author: Commits por autor + label_view_diff: Ver diferenças + label_diff_inline: inline + label_diff_side_by_side: lado a lado + label_options: Opções + label_copy_workflow_from: Copiar fluxo de trabalho de + label_permissions_report: Relatório de permissões + label_watched_issues: Tarefas observadas + label_related_issues: Tarefas relacionadas + label_applied_status: Estado aplicado + label_loading: A carregar... + label_relation_new: Nova relação + label_relation_delete: Apagar relação + label_relates_to: relacionado a + label_duplicates: duplica + label_duplicated_by: duplicado por + label_blocks: bloqueia + label_blocked_by: bloqueado por + label_precedes: precede + label_follows: segue + label_end_to_start: fim a início + label_end_to_end: fim a fim + label_start_to_start: início a início + label_start_to_end: início a fim + label_stay_logged_in: Guardar sessão + label_disabled: desactivado + label_show_completed_versions: Mostrar versões acabadas + label_me: eu + label_board: Forum + label_board_new: Novo forum + label_board_plural: Forums + label_topic_plural: Tópicos + label_message_plural: Mensagens + label_message_last: Última mensagem + label_message_new: Nova mensagem + label_message_posted: Mensagem adicionada + label_reply_plural: Respostas + label_send_information: Enviar dados da conta para o utilizador + label_year: Ano + label_month: mês + label_week: Semana + label_date_from: De + label_date_to: Para + label_language_based: Baseado na língua do utilizador + label_sort_by: "Ordenar por %{value}" + label_send_test_email: enviar um e-mail de teste + label_feeds_access_key_created_on: "Chave Atom criada há %{value} atrás" + label_module_plural: Módulos + label_added_time_by: "Adicionado por %{author} há %{age} atrás" + label_updated_time: "Alterado há %{value} atrás" + label_jump_to_a_project: Ir para o projecto... + label_file_plural: Ficheiros + label_changeset_plural: Changesets + label_default_columns: Colunas por omissão + label_no_change_option: (sem alteração) + label_bulk_edit_selected_issues: Editar tarefas seleccionadas em conjunto + label_theme: Tema + label_default: Padrão + label_search_titles_only: Procurar apenas em títulos + label_user_mail_option_all: "Para qualquer evento em todos os meus projectos" + label_user_mail_option_selected: "Para qualquer evento apenas nos projectos seleccionados..." + label_user_mail_no_self_notified: "Não quero ser notificado de alterações feitas por mim" + label_registration_activation_by_email: Activação da conta por e-mail + label_registration_manual_activation: Activação manual da conta + label_registration_automatic_activation: Activação automática da conta + label_display_per_page: "Por página: %{value}" + label_age: Idade + label_change_properties: Mudar propriedades + label_general: Geral + label_more: Mais + label_scm: SCM + label_plugins: Extensões + label_ldap_authentication: Autenticação LDAP + label_downloads_abbr: D/L + label_optional_description: Descrição opcional + label_add_another_file: Adicionar outro ficheiro + label_preferences: Preferências + label_chronological_order: Em ordem cronológica + label_reverse_chronological_order: Em ordem cronológica inversa + label_planning: Planeamento + label_incoming_emails: E-mails a chegar + label_generate_key: Gerar uma chave + label_issue_watchers: Observadores + + button_login: Entrar + button_submit: Submeter + button_save: Guardar + button_check_all: Marcar tudo + button_uncheck_all: Desmarcar tudo + button_delete: Apagar + button_create: Criar + button_test: Testar + button_edit: Editar + button_add: Adicionar + button_change: Alterar + button_apply: Aplicar + button_clear: Limpar + button_lock: Bloquear + button_unlock: Desbloquear + button_download: Download + button_list: Listar + button_view: Ver + button_move: Mover + button_back: Voltar + button_cancel: Cancelar + button_activate: Activar + button_sort: Ordenar + button_log_time: Tempo de trabalho + button_rollback: Voltar para esta versão + button_watch: Observar + button_unwatch: Deixar de observar + button_reply: Responder + button_archive: Arquivar + button_unarchive: Desarquivar + button_reset: Reinicializar + button_rename: Renomear + button_change_password: Mudar palavra-chave + button_copy: Copiar + button_annotate: Anotar + button_update: Actualizar + button_configure: Configurar + button_quote: Citar + + status_active: activo + status_registered: registado + status_locked: bloqueado + + text_select_mail_notifications: Seleccionar as acções que originam uma notificação por e-mail. + text_regexp_info: ex. ^[A-Z0-9]+$ + text_min_max_length_info: 0 siginifica sem restrição + text_project_destroy_confirmation: Tem a certeza que deseja apagar o projecto e todos os dados relacionados? + text_subprojects_destroy_warning: "O(s) seu(s) sub-projecto(s): %{value} também será/serão apagado(s)." + text_workflow_edit: Seleccione uma função e um tipo de tarefa para editar o fluxo de trabalho + text_are_you_sure: Tem a certeza? + text_tip_issue_begin_day: tarefa a começar neste dia + text_tip_issue_end_day: tarefa a acabar neste dia + text_tip_issue_begin_end_day: tarefa a começar e acabar neste dia + text_caracters_maximum: "máximo %{count} caracteres." + text_caracters_minimum: "Deve ter pelo menos %{count} caracteres." + text_length_between: "Deve ter entre %{min} e %{max} caracteres." + text_tracker_no_workflow: Sem fluxo de trabalho definido para este tipo de tarefa. + text_unallowed_characters: Caracteres não permitidos + text_comma_separated: Permitidos múltiplos valores (separados por vírgula). + text_issues_ref_in_commit_messages: Referenciando e fechando tarefas em mensagens de commit + text_issue_added: "Tarefa %{id} foi criada por %{author}." + text_issue_updated: "Tarefa %{id} foi actualizada por %{author}." + text_wiki_destroy_confirmation: Tem a certeza que deseja apagar este wiki e todo o seu conteúdo? + text_issue_category_destroy_question: "Algumas tarefas (%{count}) estão atribuídas a esta categoria. O que quer fazer?" + text_issue_category_destroy_assignments: Remover as atribuições à categoria + text_issue_category_reassign_to: Re-atribuir as tarefas para esta categoria + text_user_mail_option: "Para projectos não seleccionados, apenas receberá notificações acerca de coisas que está a observar ou está envolvido (ex. tarefas das quais foi o criador ou lhes foram atribuídas)." + text_no_configuration_data: "Perfis, tipos de tarefas, estados das tarefas e workflows ainda não foram configurados.\nÉ extremamente recomendado carregar as configurações padrão. Será capaz de as modificar depois de estarem carregadas." + text_load_default_configuration: Carregar as configurações padrão + text_status_changed_by_changeset: "Aplicado no changeset %{value}." + text_issues_destroy_confirmation: 'Tem a certeza que deseja apagar a(s) tarefa(s) seleccionada(s)?' + text_select_project_modules: 'Seleccione os módulos a activar para este projecto:' + text_default_administrator_account_changed: Conta default de administrador alterada. + text_file_repository_writable: Repositório de ficheiros com permissões de escrita + text_rmagick_available: RMagick disponível (opcional) + text_destroy_time_entries_question: "%{hours} horas de trabalho foram atribuídas a estas tarefas que vai apagar. O que deseja fazer?" + text_destroy_time_entries: Apagar as horas + text_assign_time_entries_to_project: Atribuir as horas ao projecto + text_reassign_time_entries: 'Re-atribuir as horas para esta tarefa:' + text_user_wrote: "%{value} escreveu:" + text_enumeration_destroy_question: "%{count} objectos estão atribuídos a este valor." + text_enumeration_category_reassign_to: 'Re-atribuí-los para este valor:' + text_email_delivery_not_configured: "Entrega por e-mail não está configurada, e as notificação estão desactivadas.\nConfigure o seu servidor de SMTP em config/configuration.yml e reinicie a aplicação para activar estas funcionalidades." + + default_role_manager: Gestor + default_role_developer: Programador + default_role_reporter: Repórter + default_tracker_bug: Bug + default_tracker_feature: Funcionalidade + default_tracker_support: Suporte + default_issue_status_new: Novo + default_issue_status_in_progress: Em curso + default_issue_status_resolved: Resolvido + default_issue_status_feedback: Feedback + default_issue_status_closed: Fechado + default_issue_status_rejected: Rejeitado + default_doc_category_user: Documentação de utilizador + default_doc_category_tech: Documentação técnica + default_priority_low: Baixa + default_priority_normal: Normal + default_priority_high: Alta + default_priority_urgent: Urgente + default_priority_immediate: Imediata + default_activity_design: Planeamento + default_activity_development: Desenvolvimento + + enumeration_issue_priorities: Prioridade de tarefas + enumeration_doc_categories: Categorias de documentos + enumeration_activities: Actividades (Registo de tempo) + setting_plain_text_mail: Apenas texto simples (sem HTML) + permission_view_files: Ver ficheiros + permission_edit_issues: Editar tarefas + permission_edit_own_time_entries: Editar horas pessoais + permission_manage_public_queries: Gerir queries públicas + permission_add_issues: Adicionar tarefas + permission_log_time: Registar tempo gasto + permission_view_changesets: Ver changesets + permission_view_time_entries: Ver tempo gasto + permission_manage_versions: Gerir versões + permission_manage_wiki: Gerir wiki + permission_manage_categories: Gerir categorias de tarefas + permission_protect_wiki_pages: Proteger páginas de wiki + permission_comment_news: Comentar notícias + permission_delete_messages: Apagar mensagens + permission_select_project_modules: Seleccionar módulos do projecto + permission_edit_wiki_pages: Editar páginas de wiki + permission_add_issue_watchers: Adicionar observadores + permission_view_gantt: ver diagrama de Gantt + permission_move_issues: Mover tarefas + permission_manage_issue_relations: Gerir relações de tarefas + permission_delete_wiki_pages: Apagar páginas de wiki + permission_manage_boards: Gerir forums + permission_delete_wiki_pages_attachments: Apagar anexos + permission_view_wiki_edits: Ver histórico da wiki + permission_add_messages: Submeter mensagens + permission_view_messages: Ver mensagens + permission_manage_files: Gerir ficheiros + permission_edit_issue_notes: Editar notas de tarefas + permission_manage_news: Gerir notícias + permission_view_calendar: Ver calendário + permission_manage_members: Gerir membros + permission_edit_messages: Editar mensagens + permission_delete_issues: Apagar tarefas + permission_view_issue_watchers: Ver lista de observadores + permission_manage_repository: Gerir repositório + permission_commit_access: Acesso a submissão + permission_browse_repository: Navegar em repositório + permission_view_documents: Ver documentos + permission_edit_project: Editar projecto + permission_add_issue_notes: Adicionar notas a tarefas + permission_save_queries: Guardar queries + permission_view_wiki_pages: Ver wiki + permission_rename_wiki_pages: Renomear páginas de wiki + permission_edit_time_entries: Editar entradas de tempo + permission_edit_own_issue_notes: Editar as prórpias notas + setting_gravatar_enabled: Utilizar ícones Gravatar + label_example: Exemplo + text_repository_usernames_mapping: "Seleccionar ou actualizar o utilizador de Redmine mapeado a cada nome de utilizador encontrado no repositório.\nUtilizadores com o mesmo nome de utilizador ou email no Redmine e no repositório são mapeados automaticamente." + permission_edit_own_messages: Editar as próprias mensagens + permission_delete_own_messages: Apagar as próprias mensagens + label_user_activity: "Actividade de %{value}" + label_updated_time_by: "Actualizado por %{author} há %{age}" + text_diff_truncated: '... Este diff foi truncado porque excede o tamanho máximo que pode ser mostrado.' + setting_diff_max_lines_displayed: Número máximo de linhas de diff mostradas + text_plugin_assets_writable: Escrita na pasta de activos dos módulos de extensão possível + warning_attachments_not_saved: "Não foi possível gravar %{count} ficheiro(s) ." + button_create_and_continue: Criar e continuar + text_custom_field_possible_values_info: 'Uma linha para cada valor' + label_display: Mostrar + field_editable: Editável + setting_repository_log_display_limit: Número máximo de revisões exibido no relatório de ficheiro + setting_file_max_size_displayed: Tamanho máximo dos ficheiros de texto exibidos inline + field_watcher: Observador + setting_openid: Permitir início de sessão e registo com OpenID + field_identity_url: URL do OpenID + label_login_with_open_id_option: ou início de sessão com OpenID + field_content: Conteúdo + label_descending: Descendente + label_sort: Ordenar + label_ascending: Ascendente + label_date_from_to: De %{start} a %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Esta página tem %{descendants} página(s) subordinada(s) e descendente(s). O que deseja fazer? + text_wiki_page_reassign_children: Reatribuir páginas subordinadas a esta página principal + text_wiki_page_nullify_children: Manter páginas subordinadas como páginas raíz + text_wiki_page_destroy_children: Apagar as páginas subordinadas e todos os seus descendentes + setting_password_min_length: Tamanho mínimo de palavra-chave + field_group_by: Agrupar resultados por + mail_subject_wiki_content_updated: "A página Wiki '%{id}' foi actualizada" + label_wiki_content_added: Página Wiki adicionada + mail_subject_wiki_content_added: "A página Wiki '%{id}' foi adicionada" + mail_body_wiki_content_added: A página Wiki '%{id}' foi adicionada por %{author}. + label_wiki_content_updated: Página Wiki actualizada + mail_body_wiki_content_updated: A página Wiki '%{id}' foi actualizada por %{author}. + permission_add_project: Criar projecto + setting_new_project_user_role_id: Função atribuída a um utilizador não-administrador que cria um projecto + label_view_all_revisions: Ver todas as revisões + label_tag: Etiqueta + label_branch: Ramo + error_no_tracker_in_project: Este projecto não tem associado nenhum tipo de tarefas. Verifique as definições do projecto. + error_no_default_issue_status: Não está definido um estado padrão para as tarefas. Verifique a sua configuração (dirija-se a "Administração -> Estados da tarefa"). + label_group_plural: Grupos + label_group: Grupo + label_group_new: Novo grupo + label_time_entry_plural: Tempo registado + text_journal_changed: "%{label} alterado de %{old} para %{new}" + text_journal_set_to: "%{label} configurado como %{value}" + text_journal_deleted: "%{label} apagou (%{old})" + text_journal_added: "%{label} %{value} adicionado" + field_active: Activo + enumeration_system_activity: Actividade de sistema + permission_delete_issue_watchers: Apagar observadores + version_status_closed: fechado + version_status_locked: protegido + version_status_open: aberto + error_can_not_reopen_issue_on_closed_version: Não é possível voltar a abrir uma tarefa atribuída a uma versão fechada + label_user_anonymous: Anónimo + button_move_and_follow: Mover e seguir + setting_default_projects_modules: Módulos activos por predefinição para novos projectos + setting_gravatar_default: Imagem Gravatar predefinida + field_sharing: Partilha + label_version_sharing_hierarchy: Com hierarquia do projecto + label_version_sharing_system: Com todos os projectos + label_version_sharing_descendants: Com os sub-projectos + label_version_sharing_tree: Com árvore do projecto + label_version_sharing_none: Não partilhado + error_can_not_archive_project: Não é possível arquivar este projecto + button_duplicate: Duplicar + button_copy_and_follow: Copiar e seguir + label_copy_source: Origem + setting_issue_done_ratio: Calcular a percentagem de progresso da tarefa + setting_issue_done_ratio_issue_status: Através do estado da tarefa + error_issue_done_ratios_not_updated: Percentagens de progresso da tarefa não foram actualizadas. + error_workflow_copy_target: Seleccione os tipos de tarefas e funções desejadas + setting_issue_done_ratio_issue_field: Através do campo da tarefa + label_copy_same_as_target: Mesmo que o alvo + label_copy_target: Alvo + notice_issue_done_ratios_updated: Percentagens de progresso da tarefa actualizadas. + error_workflow_copy_source: Seleccione um tipo de tarefa ou função de origem + label_update_issue_done_ratios: Actualizar percentagens de progresso da tarefa + setting_start_of_week: Iniciar calendários a + permission_view_issues: Ver tarefas + label_display_used_statuses_only: Só exibir estados empregues por este tipo de tarefa + label_revision_id: Revisão %{value} + label_api_access_key: Chave de acesso API + label_api_access_key_created_on: Chave de acesso API criada há %{value} + label_feeds_access_key: Chave de acesso Atom + notice_api_access_key_reseted: A sua chave de acesso API foi reinicializada. + setting_rest_api_enabled: Activar serviço Web REST + label_missing_api_access_key: Chave de acesso API em falta + label_missing_feeds_access_key: Chave de acesso Atom em falta + button_show: Mostrar + text_line_separated: Vários valores permitidos (uma linha para cada valor). + setting_mail_handler_body_delimiters: Truncar mensagens de correio electrónico após uma destas linhas + permission_add_subprojects: Criar sub-projectos + label_subproject_new: Novo sub-projecto + text_own_membership_delete_confirmation: |- + Está prestes a eliminar parcial ou totalmente as suas permissões. É possível que não possa editar o projecto após esta acção. + Tem a certeza de que deseja continuar? + label_close_versions: Fechar versões completas + label_board_sticky: Fixar mensagem + label_board_locked: Proteger + permission_export_wiki_pages: Exportar páginas Wiki + setting_cache_formatted_text: Colocar formatação do texto na memória cache + permission_manage_project_activities: Gerir actividades do projecto + error_unable_delete_issue_status: Não foi possível apagar o estado da tarefa + label_profile: Perfil + permission_manage_subtasks: Gerir sub-tarefas + field_parent_issue: Tarefa principal + label_subtask_plural: Sub-tarefa + label_project_copy_notifications: Enviar notificações por e-mail durante a cópia do projecto + error_can_not_delete_custom_field: Não foi possível apagar o campo personalizado + error_unable_to_connect: Não foi possível ligar (%{value}) + error_can_not_remove_role: Esta função está actualmente em uso e não pode ser apagada. + error_can_not_delete_tracker: Existem ainda tarefas nesta categoria. Não é possível apagar este tipo de tarefa. + field_principal: Principal + label_my_page_block: Bloco da minha página + notice_failed_to_save_members: "Erro ao guardar o(s) membro(s): %{errors}." + text_zoom_out: Ampliar + text_zoom_in: Reduzir + notice_unable_delete_time_entry: Não foi possível apagar a entrada de tempo registado. + label_overall_spent_time: Total de tempo registado + field_time_entries: Tempo registado + project_module_gantt: Gantt + project_module_calendar: Calendário + button_edit_associated_wikipage: "Editar página Wiki associada: %{page_title}" + field_text: Campo de texto + label_user_mail_option_only_owner: Apenas para tarefas das quais sou proprietário + setting_default_notification_option: Opção predefinida de notificação + label_user_mail_option_only_my_events: Apenas para tarefas que observo ou em que estou envolvido + label_user_mail_option_only_assigned: Apenas para tarefas que me foram atribuídas + label_user_mail_option_none: Sem eventos + field_member_of_group: Grupo do detentor de atribuição + field_assigned_to_role: Papel do detentor de atribuição + notice_not_authorized_archived_project: O projecto a que tentou aceder foi arquivado. + label_principal_search: "Procurar utilizador ou grupo:" + label_user_search: "Procurar utilizador:" + field_visible: Visível + setting_emails_header: Cabeçalho dos e-mails + setting_commit_logtime_activity_id: Actividade para tempo registado + text_time_logged_by_changeset: Aplicado no conjunto de alterações %{value}. + setting_commit_logtime_enabled: Activar registo de tempo + notice_gantt_chart_truncated: O gráfico foi truncado porque excede o número máximo de itens visíveis (%{max.}) + setting_gantt_items_limit: Número máximo de itens exibidos no gráfico Gantt + field_warn_on_leaving_unsaved: Avisar-me quando deixar uma página com texto por salvar + text_warn_on_leaving_unsaved: A página actual contém texto por salvar que será perdido caso saia desta página. + label_my_queries: As minhas consultas + text_journal_changed_no_detail: "%{label} actualizada" + label_news_comment_added: Comentário adicionado a uma notícia + button_expand_all: Expandir todos + button_collapse_all: Minimizar todos + label_additional_workflow_transitions_for_assignee: Transições adicionais permitidas quando a tarefa está atribuida ao utilizador + label_additional_workflow_transitions_for_author: Transições adicionais permitidas quando o utilizador é o autor da tarefa + label_bulk_edit_selected_time_entries: Edição em massa de registos de tempo + text_time_entries_destroy_confirmation: Têm a certeza que pretende apagar o(s) registo(s) de tempo selecionado(s)? + label_role_anonymous: Anónimo + label_role_non_member: Não membro + label_issue_note_added: Nota adicionada + label_issue_status_updated: Estado actualizado + label_issue_priority_updated: Prioridade adicionada + label_issues_visibility_own: Tarefas criadas ou atribuídas ao utilizador + field_issues_visibility: Visibilidade das tarefas + label_issues_visibility_all: Todas as tarefas + permission_set_own_issues_private: Configurar as suas tarefas como públicas ou privadas + field_is_private: Privado + permission_set_issues_private: Configurar tarefas como públicas ou privadas + label_issues_visibility_public: Todas as tarefas públicas + text_issues_destroy_descendants_confirmation: Irá apagar também %{count} subtarefa(s). + field_commit_logs_encoding: Codificação das mensagens de commit + field_scm_path_encoding: Codificação do caminho + text_scm_path_encoding_note: "Por omissão: UTF-8" + field_path_to_repository: Caminho para o repositório + field_root_directory: Raíz do directório + field_cvs_module: Módulo + field_cvsroot: CVSROOT + text_mercurial_repository_note: "Repositório local (ex: /hgrepo, c:\\hgrepo)" + text_scm_command: Comando + text_scm_command_version: Versão + label_git_report_last_commit: Analisar último commit por ficheiros e pastas + text_scm_config: Pode configurar os comando SCM em config/configuration.yml. Por favor reinicie a aplicação depois de alterar o ficheiro. + text_scm_command_not_available: O comando SCM não está disponível. Por favor verifique as configurações no painel de administração. + notice_issue_successful_create: Tarefa %{id} criada. + label_between: entre + setting_issue_group_assignment: Permitir atribuir tarefas a grupos + label_diff: diferença + text_git_repository_note: O repositório é local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Direcção da ordenação + description_project_scope: Âmbito da pesquisa + description_filter: Filtro + description_user_mail_notification: Configurações das notificações por email + description_date_from: Introduza data de início + description_message_content: Conteúdo da mensagem + description_available_columns: Colunas disponíveis + description_date_range_interval: Escolha o intervalo seleccionando a data de início e de fim + description_issue_category_reassign: Escolha a categoria da tarefa + description_search: Campo de pesquisa + description_notes: Notas + description_date_range_list: Escolha o intervalo da lista + description_choose_project: Projecto + description_date_to: Introduza data de fim + description_query_sort_criteria_attribute: Ordenar atributos + description_wiki_subpages_reassign: Escolha nova página pai + description_selected_columns: Colunas seleccionadas + label_parent_revision: Pai + label_child_revision: Filha + error_scm_annotate_big_text_file: Esta entrada não pode ser anotada, excede o tamanha máximo. + setting_default_issue_start_date_to_creation_date: Utilizar a data actual como data de início para novas tarefas + button_edit_section: Editar esta secção + setting_repositories_encodings: Codificação dos anexos e repositórios + description_all_columns: Todas as colunas + button_export: Exportar + label_export_options: "%{export_format} opções de exportação" + error_attachment_too_big: Este ficheiro não pode ser carregado pois excede o tamanho máximo permitido por ficheiro (%{max_size}) + notice_failed_to_save_time_entries: "Falha ao guardar %{count} registo(s) de tempo dos %{total} seleccionados: %{ids}." + label_x_issues: + zero: 0 tarefas + one: 1 tarefa + other: "%{count} tarefas" + label_repository_new: Novo repositório + field_repository_is_default: Repositório principal + label_copy_attachments: Copiar anexos + label_item_position: "%{position}/%{count}" + label_completed_versions: Versões completas + text_project_identifier_info: Apenas letras minúsculas (a-z), números, traços e sublinhados são permitidos.
    Depois de guardar não é possível alterar. + field_multiple: Múltiplos valores + setting_commit_cross_project_ref: Permitir que tarefas dos restantes projectos sejam referenciadas e resolvidas + text_issue_conflict_resolution_add_notes: Adicionar as minhas notas e descartar as minhas restantes alterações + text_issue_conflict_resolution_overwrite: Aplicar as minhas alterações (notas antigas serão mantidas mas algumas alterações podem se perder) + notice_issue_update_conflict: Esta tarefa foi actualizada por outro utilizador enquanto estava a edita-la. + text_issue_conflict_resolution_cancel: Descartar todas as minhas alterações e actualizar %{link} + permission_manage_related_issues: Gerir tarefas relacionadas + field_auth_source_ldap_filter: Filtro LDAP + label_search_for_watchers: Pesquisar por observadores para adicionar + notice_account_deleted: A sua conta foi apagada permanentemente. + setting_unsubscribe: Permitir aos utilizadores apagarem a sua própria conta + button_delete_my_account: Apagar a minha conta + text_account_destroy_confirmation: |- + Têm a certeza que pretende avançar? + A sua conta vai ser permanentemente apagada, não será possível recupera-la. + error_session_expired: A sua sessão expirou. Por-favor autentique-se novamente. + text_session_expiration_settings: "Atenção: alterar estas configurações pode fazer expirar as sessões em curso, incluíndo a sua." + setting_session_lifetime: Duração máxima da sessão + setting_session_timeout: Tempo limite de inactividade da sessão + label_session_expiration: Expiração da sessão + permission_close_project: Fechar / re-abrir o projecto + label_show_closed_projects: Ver os projectos fechados + button_close: Fechar + button_reopen: Re-abrir + project_status_active: activo + project_status_closed: fechado + project_status_archived: arquivado + text_project_closed: Este projecto está fechado e é apenas de leitura. + notice_user_successful_create: Utilizador %{id} criado. + field_core_fields: Campos padrão + field_timeout: Tempo limite (em segundos) + setting_thumbnails_enabled: Apresentar miniaturas dos anexos + setting_thumbnails_size: Tamanho das miniaturas (em pixeis) + label_status_transitions: Estado das transições + label_fields_permissions: Permissões do campo + label_readonly: Apenas de leitura + label_required: Obrigatório + text_repository_identifier_info: Apenas letras minúsculas (a-z), números, traços e sublinhados são permitidos.
    Depois de guardar não é possível alterar. + field_board_parent: Fórum pai + label_attribute_of_project: "%{name} do Projecto" + label_attribute_of_author: "%{name} do Autor" + label_attribute_of_assigned_to: "%{name} do atribuído" + label_attribute_of_fixed_version: "%{name} da Versão" + label_copy_subtasks: Copiar sub-tarefas + label_copied_to: copiado para + label_copied_from: copiado de + label_any_issues_in_project: tarefas do projecto + label_any_issues_not_in_project: tarefas sem projecto + field_private_notes: Notas privadas + permission_view_private_notes: Ver notas privadas + permission_set_notes_private: Configurar notas como privadas + label_no_issues_in_project: sem tarefas no projecto + label_any: todos + label_last_n_weeks: últimas %{count} semanas + setting_cross_project_subtasks: Permitir sub-tarefas entre projectos + label_cross_project_descendants: Com os sub-projectos + label_cross_project_tree: Com árvore do projecto + label_cross_project_hierarchy: Com hierarquia do projecto + label_cross_project_system: Com todos os projectos + button_hide: Esconder + setting_non_working_week_days: Dias não úteis + label_in_the_next_days: no futuro + label_in_the_past_days: no passado + label_attribute_of_user: Do utilizador %{name} + text_turning_multiple_off: Se desactivar a escolha múltipla, + a escolha múltipla será apagada de modo a manter apenas um valor por item. + label_attribute_of_issue: Tarefa de %{name} + permission_add_documents: Adicionar documentos + permission_edit_documents: Editar documentos + permission_delete_documents: Apagar documentos + label_gantt_progress_line: Barra de progresso + setting_jsonp_enabled: Activar suporte JSONP + field_inherit_members: Herdar membros + field_closed_on: Fechado + field_generate_password: Gerar palavra-chave + setting_default_projects_tracker_ids: Tipo de tarefa padrão para novos projectos + label_total_time: Total + notice_account_not_activated_yet: Ainda não activou a sua conta. Se quiser + receber um novo email de activação, por favor carregue nesta ligação. + notice_account_locked: A sua conta está bloqueada. + notice_account_register_done: A conta foi criada com sucesso. Um email contendo + as instruções para activar a sua conta foi enviado para %{email}. + label_hidden: Escondido + label_visibility_private: apenas para mim + label_visibility_roles: apenas para estas funções + label_visibility_public: para qualquer utilizador + field_must_change_passwd: Tem que alterar a palavra-passe no próximo início de sessão + notice_new_password_must_be_different: A palavra-passe tem de ser diferente da actual + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a364456950eb9dddb03948fc2af9610fa32b4fac.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a3/a364456950eb9dddb03948fc2af9610fa32b4fac.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,13 @@ +class AddDeleteWikiPagesAttachmentsPermission < ActiveRecord::Migration + def self.up + Role.all.each do |r| + r.add_permission!(:delete_wiki_pages_attachments) if r.has_permission?(:edit_wiki_pages) + end + end + + def self.down + Role.all.each do |r| + r.remove_permission!(:delete_wiki_pages_attachments) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a3a57388b3ae4f50b030906e001d30172793704b.svn-base --- a/.svn/pristine/a3/a3a57388b3ae4f50b030906e001d30172793704b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class IssueCategoryTest < ActiveSupport::TestCase - fixtures :issue_categories, :issues, :users, :groups_users - - def setup - @category = IssueCategory.find(1) - end - - def test_create - assert IssueCategory.new(:project_id => 2, :name => 'New category').save - category = IssueCategory.first(:order => 'id DESC') - assert_equal 'New category', category.name - end - - def test_create_with_group_assignment - assert IssueCategory.new(:project_id => 2, :name => 'Group assignment', :assigned_to_id => 11).save - category = IssueCategory.first(:order => 'id DESC') - assert_kind_of Group, category.assigned_to - assert_equal Group.find(11), category.assigned_to - end - - def test_destroy - issue = @category.issues.first - @category.destroy - # Make sure the category was nullified on the issue - assert_nil issue.reload.category - end - - def test_destroy_with_reassign - issue = @category.issues.first - reassign_to = IssueCategory.find(2) - @category.destroy(reassign_to) - # Make sure the issue was reassigned - assert_equal reassign_to, issue.reload.category - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a3a58ab9f9fc5bc4302fa7dbcd592c4ba267cc9b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a3/a3a58ab9f9fc5bc4302fa7dbcd592c4ba267cc9b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,129 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class RoleTest < ActiveSupport::TestCase + fixtures :roles, :workflows, :trackers + + def test_sorted_scope + assert_equal Role.all.sort, Role.sorted.all + end + + def test_givable_scope + assert_equal Role.all.reject(&:builtin?).sort, Role.givable.all + end + + def test_builtin_scope + assert_equal Role.all.select(&:builtin?).sort, Role.builtin(true).all.sort + assert_equal Role.all.reject(&:builtin?).sort, Role.builtin(false).all.sort + end + + def test_copy_from + role = Role.find(1) + copy = Role.new.copy_from(role) + + assert_nil copy.id + assert_equal '', copy.name + assert_equal role.permissions, copy.permissions + + copy.name = 'Copy' + assert copy.save + end + + def test_copy_workflows + source = Role.find(1) + assert_equal 90, source.workflow_rules.size + + target = Role.new(:name => 'Target') + assert target.save + target.workflow_rules.copy(source) + target.reload + assert_equal 90, target.workflow_rules.size + end + + def test_permissions_should_be_unserialized_with_its_coder + Role::PermissionsAttributeCoder.expects(:load).once + Role.find(1).permissions + end + + def test_add_permission + role = Role.find(1) + size = role.permissions.size + role.add_permission!("apermission", "anotherpermission") + role.reload + assert role.permissions.include?(:anotherpermission) + assert_equal size + 2, role.permissions.size + end + + def test_remove_permission + role = Role.find(1) + size = role.permissions.size + perm = role.permissions[0..1] + role.remove_permission!(*perm) + role.reload + assert ! role.permissions.include?(perm[0]) + assert_equal size - 2, role.permissions.size + end + + def test_name + I18n.locale = 'fr' + assert_equal 'Manager', Role.find(1).name + assert_equal 'Anonyme', Role.anonymous.name + assert_equal 'Non membre', Role.non_member.name + end + + def test_find_all_givable + assert_equal Role.all.reject(&:builtin?).sort, Role.find_all_givable + end + + def test_anonymous_should_return_the_anonymous_role + assert_no_difference('Role.count') do + role = Role.anonymous + assert role.builtin? + assert_equal Role::BUILTIN_ANONYMOUS, role.builtin + end + end + + def test_anonymous_with_a_missing_anonymous_role_should_return_the_anonymous_role + Role.where(:builtin => Role::BUILTIN_ANONYMOUS).delete_all + + assert_difference('Role.count') do + role = Role.anonymous + assert role.builtin? + assert_equal Role::BUILTIN_ANONYMOUS, role.builtin + end + end + + def test_non_member_should_return_the_non_member_role + assert_no_difference('Role.count') do + role = Role.non_member + assert role.builtin? + assert_equal Role::BUILTIN_NON_MEMBER, role.builtin + end + end + + def test_non_member_with_a_missing_non_member_role_should_return_the_non_member_role + Role.where(:builtin => Role::BUILTIN_NON_MEMBER).delete_all + + assert_difference('Role.count') do + role = Role.non_member + assert role.builtin? + assert_equal Role::BUILTIN_NON_MEMBER, role.builtin + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a3b49dd1167634bbf9c5460ad4e1ffc52389846f.svn-base --- a/.svn/pristine/a3/a3b49dd1167634bbf9c5460ad4e1ffc52389846f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -<%= form_tag({}) do -%> -<%= hidden_field_tag 'back_url', url_for(params) %> -
    - - - - -<%= sort_header_tag('spent_on', :caption => l(:label_date), :default_order => 'desc') %> -<%= sort_header_tag('user', :caption => l(:label_member)) %> -<%= sort_header_tag('activity', :caption => l(:label_activity)) %> -<%= sort_header_tag('project', :caption => l(:label_project)) %> -<%= sort_header_tag('issue', :caption => l(:label_issue), :default_order => 'desc') %> - -<%= sort_header_tag('hours', :caption => l(:field_hours)) %> - - - - -<% entries.each do |entry| -%> - hascontextmenu"> - - - - - - - - - - -<% end -%> - -
    - <%= link_to image_tag('toggle_check.png'), - {}, - :onclick => 'toggleIssuesSelection(this); return false;', - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> -<%= l(:field_comments) %>
    <%= check_box_tag("ids[]", entry.id, false, :id => nil) %><%= format_date(entry.spent_on) %><%= link_to_user(entry.user) %><%=h entry.activity %><%= link_to_project(entry.project) %> -<% if entry.issue -%> -<%= entry.issue.visible? ? link_to_issue(entry.issue, :truncate => 50) : "##{entry.issue.id}" -%> -<% end -%> -<%=h entry.comments %><%= html_hours("%.2f" % entry.hours) %> -<% if entry.editable_by?(User.current) -%> - <%= link_to image_tag('edit.png'), edit_time_entry_path(entry), - :title => l(:button_edit) %> - <%= link_to image_tag('delete.png'), time_entry_path(entry), - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:button_delete) %> -<% end -%> -
    -
    -<% end -%> - -<%= context_menu time_entries_context_menu_path %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a3b7c4372735e48e9ede0b15120e9d79060eed4d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a3/a3b7c4372735e48e9ede0b15120e9d79060eed4d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,12 @@ +class IssueMove < ActiveRecord::Migration + # model removed + class Permission < ActiveRecord::Base; end + + def self.up + Permission.create :controller => "projects", :action => "move_issues", :description => "button_move", :sort => 1061, :mail_option => 0, :mail_enabled => 0 + end + + def self.down + Permission.where("controller=? and action=?", 'projects', 'move_issues').first.destroy + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a3b9e1f5db2062f981afc156975b07fe4b88affe.svn-base --- a/.svn/pristine/a3/a3b9e1f5db2062f981afc156975b07fe4b88affe.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -class AddSettingsUpdatedOn < ActiveRecord::Migration - def self.up - add_column :settings, :updated_on, :timestamp - # set updated_on - Setting.find(:all).each(&:save) - end - - def self.down - remove_column :settings, :updated_on - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a3/a3be1d86be827009cae521df133b9bd67e4c3929.svn-base --- a/.svn/pristine/a3/a3be1d86be827009cae521df133b9bd67e4c3929.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -var NS4 = (navigator.appName == "Netscape" && parseInt(navigator.appVersion) < 5); - -function addOption(theSel, theText, theValue) -{ - var newOpt = new Option(theText, theValue); - var selLength = theSel.length; - theSel.options[selLength] = newOpt; -} - -function swapOptions(theSel, index1, index2) -{ - var text, value; - text = theSel.options[index1].text; - value = theSel.options[index1].value; - theSel.options[index1].text = theSel.options[index2].text; - theSel.options[index1].value = theSel.options[index2].value; - theSel.options[index2].text = text; - theSel.options[index2].value = value; -} - -function deleteOption(theSel, theIndex) -{ - var selLength = theSel.length; - if(selLength>0) - { - theSel.options[theIndex] = null; - } -} - -function moveOptions(theSelFrom, theSelTo) -{ - - var selLength = theSelFrom.length; - var selectedText = new Array(); - var selectedValues = new Array(); - var selectedCount = 0; - - var i; - - for(i=selLength-1; i>=0; i--) - { - if(theSelFrom.options[i].selected) - { - selectedText[selectedCount] = theSelFrom.options[i].text; - selectedValues[selectedCount] = theSelFrom.options[i].value; - deleteOption(theSelFrom, i); - selectedCount++; - } - } - - for(i=selectedCount-1; i>=0; i--) - { - addOption(theSelTo, selectedText[i], selectedValues[i]); - } - - if(NS4) history.go(0); -} - -function moveOptionUp(theSel) { - var index = theSel.selectedIndex; - if (index > 0) { - swapOptions(theSel, index-1, index); - theSel.selectedIndex = index-1; - } -} - -function moveOptionDown(theSel) { - var index = theSel.selectedIndex; - if (index < theSel.length - 1) { - swapOptions(theSel, index, index+1); - theSel.selectedIndex = index+1; - } -} - -// OK -function selectAllOptions(id) -{ - var select = $('#'+id);/* - for (var i=0; i + +<%= labelled_form_for @auth_source, :as => :auth_source, :url => auth_sources_path, :html => {:id => 'auth_source_form'} do |f| %> + <%= hidden_field_tag 'type', @auth_source.type %> + <%= render :partial => auth_source_partial_name(@auth_source), :locals => { :f => f } %> + <%= submit_tag l(:button_create) %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a4/a43737642d2d9f750bdaaad722e40b6f394f2d23.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a4/a43737642d2d9f750bdaaad722e40b6f394f2d23.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,13 @@ +

    <%= @issue.tracker %> #<%= @issue.id %>

    +

    <%= authoring @journal.created_on, @journal.user, :label => :label_updated_time_by %>

    + +
    +<%= simple_format_without_paragraph @diff.to_html %> +
    + +

    + <%= link_to(l(:button_back), issue_path(@issue), + :onclick => 'if (document.referrer != "") {history.back(); return false;}') %> +

    + +<% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a4/a43f7091da21bbe393f7564458e580e780857e6d.svn-base --- a/.svn/pristine/a4/a43f7091da21bbe393f7564458e580e780857e6d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -<%= form_tag({}, :id => "status_by_form") do -%> -
    - -<%= l(:label_issues_by, - select_tag('status_by', - status_by_options_for_select(criteria), - :id => 'status_by_select', - :data => {:remote => true, :method => 'post', :url => status_by_version_path(version)})).html_safe %> - -<% if counts.empty? %> -

    <%= l(:label_no_data) %>

    -<% else %> - - <% counts.each do |count| %> - - - - - <% end %> -
    - <% if count[:group] -%> - <%= link_to(h(count[:group]), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => count[:group])) %> - <% else -%> - <%= link_to(l(:label_none), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => "!*")) %> - <% end %> - - <%= progress_bar((count[:closed].to_f / count[:total])*100, - :legend => "#{count[:closed]}/#{count[:total]}", - :width => "#{(count[:total].to_f / max * 200).floor}px;") %> -
    -<% end %> -
    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a4/a4c45a7af3a5e670c765d740cec096dac5d5314d.svn-base --- a/.svn/pristine/a4/a4c45a7af3a5e670c765d740cec096dac5d5314d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class IssueStatus < ActiveRecord::Base - before_destroy :check_integrity - has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id" - acts_as_list - - before_destroy :delete_workflow_rules - after_save :update_default - - validates_presence_of :name - validates_uniqueness_of :name - validates_length_of :name, :maximum => 30 - validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true - - scope :sorted, order("#{table_name}.position ASC") - scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} - - def update_default - IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default? - end - - # Returns the default status for new issues - def self.default - where(:is_default => true).first - end - - # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+ - def self.update_issue_done_ratios - if Issue.use_status_for_done_ratio? - IssueStatus.where("default_done_ratio >= 0").all.each do |status| - Issue.update_all({:done_ratio => status.default_done_ratio}, {:status_id => status.id}) - end - end - - return Issue.use_status_for_done_ratio? - end - - # Returns an array of all statuses the given role can switch to - # Uses association cache when called more than one time - def new_statuses_allowed_to(roles, tracker, author=false, assignee=false) - if roles && tracker - role_ids = roles.collect(&:id) - transitions = workflows.select do |w| - role_ids.include?(w.role_id) && - w.tracker_id == tracker.id && - ((!w.author && !w.assignee) || (author && w.author) || (assignee && w.assignee)) - end - transitions.map(&:new_status).compact.sort - else - [] - end - end - - # Same thing as above but uses a database query - # More efficient than the previous method if called just once - def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false) - if roles.present? && tracker - conditions = "(author = :false AND assignee = :false)" - conditions << " OR author = :true" if author - conditions << " OR assignee = :true" if assignee - - workflows. - includes(:new_status). - where(["role_id IN (:role_ids) AND tracker_id = :tracker_id AND (#{conditions})", - {:role_ids => roles.collect(&:id), :tracker_id => tracker.id, :true => true, :false => false} - ]).all. - map(&:new_status).compact.sort - else - [] - end - end - - def <=>(status) - position <=> status.position - end - - def to_s; name end - - private - - def check_integrity - raise "Can't delete status" if Issue.where(:status_id => id).any? - end - - # Deletes associated workflows - def delete_workflow_rules - WorkflowRule.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}]) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a50c8a4fc61822f00307fa81cf3630d9ac86b4c0.svn-base --- a/.svn/pristine/a5/a50c8a4fc61822f00307fa81cf3630d9ac86b4c0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,208 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'redmine/scm/adapters/cvs_adapter' -require 'digest/sha1' - -class Repository::Cvs < Repository - validates_presence_of :url, :root_url, :log_encoding - - safe_attributes 'root_url', - :if => lambda {|repository, user| repository.new_record?} - - def self.human_attribute_name(attribute_key_name, *args) - attr_name = attribute_key_name.to_s - if attr_name == "root_url" - attr_name = "cvsroot" - elsif attr_name == "url" - attr_name = "cvs_module" - end - super(attr_name, *args) - end - - def self.scm_adapter_class - Redmine::Scm::Adapters::CvsAdapter - end - - def self.scm_name - 'CVS' - end - - def entry(path=nil, identifier=nil) - rev = identifier.nil? ? nil : changesets.find_by_revision(identifier) - scm.entry(path, rev.nil? ? nil : rev.committed_on) - end - - def entries(path=nil, identifier=nil) - rev = nil - if ! identifier.nil? - rev = changesets.find_by_revision(identifier) - return nil if rev.nil? - end - entries = scm.entries(path, rev.nil? ? nil : rev.committed_on) - if entries - entries.each() do |entry| - if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? ) - change = filechanges.find_by_revision_and_path( - entry.lastrev.revision, - scm.with_leading_slash(entry.path) ) - if change - entry.lastrev.identifier = change.changeset.revision - entry.lastrev.revision = change.changeset.revision - entry.lastrev.author = change.changeset.committer - # entry.lastrev.branch = change.branch - end - end - end - end - load_entries_changesets(entries) - entries - end - - def cat(path, identifier=nil) - rev = nil - if ! identifier.nil? - rev = changesets.find_by_revision(identifier) - return nil if rev.nil? - end - scm.cat(path, rev.nil? ? nil : rev.committed_on) - end - - def annotate(path, identifier=nil) - rev = nil - if ! identifier.nil? - rev = changesets.find_by_revision(identifier) - return nil if rev.nil? - end - scm.annotate(path, rev.nil? ? nil : rev.committed_on) - end - - def diff(path, rev, rev_to) - # convert rev to revision. CVS can't handle changesets here - diff=[] - changeset_from = changesets.find_by_revision(rev) - if rev_to.to_i > 0 - changeset_to = changesets.find_by_revision(rev_to) - end - changeset_from.filechanges.each() do |change_from| - revision_from = nil - revision_to = nil - if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path)) - revision_from = change_from.revision - end - if revision_from - if changeset_to - changeset_to.filechanges.each() do |change_to| - revision_to = change_to.revision if change_to.path == change_from.path - end - end - unless revision_to - revision_to = scm.get_previous_revision(revision_from) - end - file_diff = scm.diff(change_from.path, revision_from, revision_to) - diff = diff + file_diff unless file_diff.nil? - end - end - return diff - end - - def fetch_changesets - # some nifty bits to introduce a commit-id with cvs - # natively cvs doesn't provide any kind of changesets, - # there is only a revision per file. - # we now take a guess using the author, the commitlog and the commit-date. - - # last one is the next step to take. the commit-date is not equal for all - # commits in one changeset. cvs update the commit-date when the *,v file was touched. so - # we use a small delta here, to merge all changes belonging to _one_ changeset - time_delta = 10.seconds - fetch_since = latest_changeset ? latest_changeset.committed_on : nil - transaction do - tmp_rev_num = 1 - scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision| - # only add the change to the database, if it doen't exists. the cvs log - # is not exclusive at all. - tmp_time = revision.time.clone - unless filechanges.find_by_path_and_revision( - scm.with_leading_slash(revision.paths[0][:path]), - revision.paths[0][:revision] - ) - cmt = Changeset.normalize_comments(revision.message, repo_log_encoding) - author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding) - cs = changesets.find( - :first, - :conditions => { - :committed_on => tmp_time - time_delta .. tmp_time + time_delta, - :committer => author_utf8, - :comments => cmt - } - ) - # create a new changeset.... - unless cs - # we use a temporaray revision number here (just for inserting) - # later on, we calculate a continous positive number - tmp_time2 = tmp_time.clone.gmtime - branch = revision.paths[0][:branch] - scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S") - cs = Changeset.create(:repository => self, - :revision => "tmp#{tmp_rev_num}", - :scmid => scmid, - :committer => revision.author, - :committed_on => tmp_time, - :comments => revision.message) - tmp_rev_num += 1 - end - # convert CVS-File-States to internal Action-abbrevations - # default action is (M)odified - action = "M" - if revision.paths[0][:action] == "Exp" && revision.paths[0][:revision] == "1.1" - action = "A" # add-action always at first revision (= 1.1) - elsif revision.paths[0][:action] == "dead" - action = "D" # dead-state is similar to Delete - end - Change.create( - :changeset => cs, - :action => action, - :path => scm.with_leading_slash(revision.paths[0][:path]), - :revision => revision.paths[0][:revision], - :branch => revision.paths[0][:branch] - ) - end - end - - # Renumber new changesets in chronological order - Changeset.all( - :order => 'committed_on ASC, id ASC', - :conditions => ["repository_id = ? AND revision LIKE 'tmp%'", id] - ).each do |changeset| - changeset.update_attribute :revision, next_revision_number - end - end # transaction - @current_revision_number = nil - end - - private - - # Returns the next revision number to assign to a CVS changeset - def next_revision_number - # Need to retrieve existing revision numbers to sort them as integers - sql = "SELECT revision FROM #{Changeset.table_name} " - sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'" - @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0) - @current_revision_number += 1 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a54694ad491e752b7f87503a37270f988e6883b0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a54694ad491e752b7f87503a37270f988e6883b0.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,76 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class MenuManagerTest < ActionController::IntegrationTest + include Redmine::I18n + + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + def test_project_menu_with_specific_locale + get 'projects/ecookbook/issues', { }, 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' + + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => ll('fr', :label_activity), + :attributes => { :href => '/projects/ecookbook/activity', + :class => 'activity' } } } + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => ll('fr', :label_issue_plural), + :attributes => { :href => '/projects/ecookbook/issues', + :class => 'issues selected' } } } + end + + def test_project_menu_with_additional_menu_items + Setting.default_language = 'en' + assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do + Redmine::MenuManager.map :project_menu do |menu| + menu.push :foo, { :controller => 'projects', :action => 'show' }, :caption => 'Foo' + menu.push :bar, { :controller => 'projects', :action => 'show' }, :before => :activity + menu.push :hello, { :controller => 'projects', :action => 'show' }, :caption => Proc.new {|p| p.name.upcase }, :after => :bar + end + + get 'projects/ecookbook' + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo', + :attributes => { :class => 'foo' } } } + + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar', + :attributes => { :class => 'bar' } }, + :before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } } + + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK', + :attributes => { :class => 'hello' } }, + :before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } } + + # Remove the menu items + Redmine::MenuManager.map :project_menu do |menu| + menu.delete :foo + menu.delete :bar + menu.delete :hello + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a5718c7636f7768b70cb057f46ace176323e391e.svn-base --- a/.svn/pristine/a5/a5718c7636f7768b70cb057f46ace176323e391e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1100 +0,0 @@ -# Danish translation file for standard Ruby on Rails internationalization -# by Lars Hoeg (http://www.lenio.dk/) -# updated and upgraded to 0.9 by Morten Krogh Andersen (http://www.krogh.net) - -da: - direction: ltr - date: - formats: - default: "%d.%m.%Y" - short: "%e. %b %Y" - long: "%e. %B %Y" - - day_names: [søndag, mandag, tirsdag, onsdag, torsdag, fredag, lørdag] - abbr_day_names: [sø, ma, ti, 'on', to, fr, lø] - month_names: [~, januar, februar, marts, april, maj, juni, juli, august, september, oktober, november, december] - abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, aug, sep, okt, nov, dec] - order: - - :day - - :month - - :year - - time: - formats: - default: "%e. %B %Y, %H:%M" - time: "%H:%M" - short: "%e. %b %Y, %H:%M" - long: "%A, %e. %B %Y, %H:%M" - am: "" - pm: "" - - support: - array: - sentence_connector: "og" - skip_last_comma: true - - datetime: - distance_in_words: - half_a_minute: "et halvt minut" - less_than_x_seconds: - one: "mindre end et sekund" - other: "mindre end %{count} sekunder" - x_seconds: - one: "et sekund" - other: "%{count} sekunder" - less_than_x_minutes: - one: "mindre end et minut" - other: "mindre end %{count} minutter" - x_minutes: - one: "et minut" - other: "%{count} minutter" - about_x_hours: - one: "cirka en time" - other: "cirka %{count} timer" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "en dag" - other: "%{count} dage" - about_x_months: - one: "cirka en måned" - other: "cirka %{count} måneder" - x_months: - one: "en måned" - other: "%{count} måneder" - about_x_years: - one: "cirka et år" - other: "cirka %{count} år" - over_x_years: - one: "mere end et år" - other: "mere end %{count} år" - almost_x_years: - one: "næsten 1 år" - other: "næsten %{count} år" - - number: - format: - separator: "," - delimiter: "." - precision: 3 - currency: - format: - format: "%u %n" - unit: "DKK" - separator: "," - delimiter: "." - precision: 2 - precision: - format: - # separator: - delimiter: "" - # precision: - human: - format: - # separator: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - percentage: - format: - # separator: - delimiter: "" - # precision: - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "er ikke i listen" - exclusion: "er reserveret" - invalid: "er ikke gyldig" - confirmation: "stemmer ikke overens" - accepted: "skal accepteres" - empty: "må ikke udelades" - blank: "skal udfyldes" - too_long: "er for lang (højst %{count} tegn)" - too_short: "er for kort (mindst %{count} tegn)" - wrong_length: "har forkert længde (skulle være %{count} tegn)" - taken: "er allerede anvendt" - not_a_number: "er ikke et tal" - greater_than: "skal være større end %{count}" - greater_than_or_equal_to: "skal være større end eller lig med %{count}" - equal_to: "skal være lig med %{count}" - less_than: "skal være mindre end %{count}" - less_than_or_equal_to: "skal være mindre end eller lig med %{count}" - odd: "skal være ulige" - even: "skal være lige" - greater_than_start_date: "skal være senere end startdatoen" - not_same_project: "hører ikke til samme projekt" - circular_dependency: "Denne relation vil skabe et afhængighedsforhold" - cant_link_an_issue_with_a_descendant: "En sag kan ikke relateres til en af dens underopgaver" - - template: - header: - one: "En fejl forhindrede %{model} i at blive gemt" - other: "%{count} fejl forhindrede denne %{model} i at blive gemt" - body: "Der var problemer med følgende felter:" - - actionview_instancetag_blank_option: Vælg venligst - - general_text_No: 'Nej' - general_text_Yes: 'Ja' - general_text_no: 'nej' - general_text_yes: 'ja' - general_lang_name: 'Danish (Dansk)' - general_csv_separator: ',' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Kontoen er opdateret. - notice_account_invalid_creditentials: Ugyldig bruger og/eller kodeord - notice_account_password_updated: Kodeordet er opdateret. - notice_account_wrong_password: Forkert kodeord - notice_account_register_done: Kontoen er oprettet. For at aktivere kontoen skal du klikke på linket i den tilsendte email. - notice_account_unknown_email: Ukendt bruger. - notice_can_t_change_password: Denne konto benytter en ekstern sikkerhedsgodkendelse. Det er ikke muligt at skifte kodeord. - notice_account_lost_email_sent: En email med instruktioner til at vælge et nyt kodeord er afsendt til dig. - notice_account_activated: Din konto er aktiveret. Du kan nu logge ind. - notice_successful_create: Succesfuld oprettelse. - notice_successful_update: Succesfuld opdatering. - notice_successful_delete: Succesfuld sletning. - notice_successful_connection: Succesfuld forbindelse. - notice_file_not_found: Siden du forsøger at tilgå eksisterer ikke eller er blevet fjernet. - notice_locking_conflict: Data er opdateret af en anden bruger. - notice_not_authorized: Du har ikke adgang til denne side. - notice_email_sent: "En email er sendt til %{value}" - notice_email_error: "En fejl opstod under afsendelse af email (%{value})" - notice_feeds_access_key_reseted: Din adgangsnøgle til RSS er nulstillet. - notice_failed_to_save_issues: "Det mislykkedes at gemme %{count} sage(r) på %{total} valgt: %{ids}." - notice_no_issue_selected: "Ingen sag er valgt! Vælg venligst hvilke emner du vil rette." - notice_account_pending: "Din konto er oprettet, og afventer administrators godkendelse." - notice_default_data_loaded: Standardopsætningen er indlæst. - - error_can_t_load_default_data: "Standardopsætning kunne ikke indlæses: %{value}" - error_scm_not_found: "Adgang nægtet og/eller revision blev ikke fundet i det valgte repository." - error_scm_command_failed: "En fejl opstod under forbindelsen til det valgte repository: %{value}" - - mail_subject_lost_password: "Dit %{value} kodeord" - mail_body_lost_password: 'Klik på dette link for at ændre dit kodeord:' - mail_subject_register: "%{value} kontoaktivering" - mail_body_register: 'Klik på dette link for at aktivere din konto:' - mail_body_account_information_external: "Du kan bruge din %{value} konto til at logge ind." - mail_body_account_information: Din kontoinformation - mail_subject_account_activation_request: "%{value} kontoaktivering" - mail_body_account_activation_request: "En ny bruger (%{value}) er registreret. Godkend venligst kontoen:" - - gui_validation_error: 1 fejl - gui_validation_error_plural: "%{count} fejl" - - field_name: Navn - field_description: Beskrivelse - field_summary: Sammenfatning - field_is_required: Skal udfyldes - field_firstname: Fornavn - field_lastname: Efternavn - field_mail: Email - field_filename: Fil - field_filesize: Størrelse - field_downloads: Downloads - field_author: Forfatter - field_created_on: Oprettet - field_updated_on: Opdateret - field_field_format: Format - field_is_for_all: For alle projekter - field_possible_values: Mulige værdier - field_regexp: Regulære udtryk - field_min_length: Mindste længde - field_max_length: Største længde - field_value: Værdi - field_category: Kategori - field_title: Titel - field_project: Projekt - field_issue: Sag - field_status: Status - field_notes: Noter - field_is_closed: Sagen er lukket - field_is_default: Standardværdi - field_tracker: Type - field_subject: Emne - field_due_date: Deadline - field_assigned_to: Tildelt til - field_priority: Prioritet - field_fixed_version: Udgave - field_user: Bruger - field_role: Rolle - field_homepage: Hjemmeside - field_is_public: Offentlig - field_parent: Underprojekt af - field_is_in_roadmap: Sager vist i roadmap - field_login: Login - field_mail_notification: Email-påmindelser - field_admin: Administrator - field_last_login_on: Sidste forbindelse - field_language: Sprog - field_effective_date: Dato - field_password: Kodeord - field_new_password: Nyt kodeord - field_password_confirmation: Bekræft - field_version: Version - field_type: Type - field_host: Vært - field_port: Port - field_account: Kode - field_base_dn: Base DN - field_attr_login: Login attribut - field_attr_firstname: Fornavn attribut - field_attr_lastname: Efternavn attribut - field_attr_mail: Email attribut - field_onthefly: løbende brugeroprettelse - field_start_date: Start dato - field_done_ratio: "% færdig" - field_auth_source: Sikkerhedsmetode - field_hide_mail: Skjul min email - field_comments: Kommentar - field_url: URL - field_start_page: Startside - field_subproject: Underprojekt - field_hours: Timer - field_activity: Aktivitet - field_spent_on: Dato - field_identifier: Identifikator - field_is_filter: Brugt som et filter - field_issue_to: Beslægtede sag - field_delay: Udsættelse - field_assignable: Sager kan tildeles denne rolle - field_redirect_existing_links: Videresend eksisterende links - field_estimated_hours: Anslået tid - field_column_names: Kolonner - field_time_zone: Tidszone - field_searchable: Søgbar - field_default_value: Standardværdi - - setting_app_title: Applikationstitel - setting_app_subtitle: Applikationsundertekst - setting_welcome_text: Velkomsttekst - setting_default_language: Standardsporg - setting_login_required: Sikkerhed påkrævet - setting_self_registration: Brugeroprettelse - setting_attachment_max_size: Vedhæftede filers max størrelse - setting_issues_export_limit: Sagseksporteringsbegrænsning - setting_mail_from: Afsender-email - setting_bcc_recipients: Skjult modtager (bcc) - setting_host_name: Værtsnavn - setting_text_formatting: Tekstformatering - setting_wiki_compression: Komprimering af wiki-historik - setting_feeds_limit: Feed indholdsbegrænsning - setting_autofetch_changesets: Hent automatisk commits - setting_sys_api_enabled: Aktiver webservice for automatisk administration af repository - setting_commit_ref_keywords: Referencenøgleord - setting_commit_fix_keywords: Afslutningsnøgleord - setting_autologin: Automatisk login - setting_date_format: Datoformat - setting_time_format: Tidsformat - setting_cross_project_issue_relations: Tillad sagsrelationer på tværs af projekter - setting_issue_list_default_columns: Standardkolonner på sagslisten - setting_emails_footer: Email-fodnote - setting_protocol: Protokol - setting_user_format: Brugervisningsformat - - project_module_issue_tracking: Sagssøgning - project_module_time_tracking: Tidsstyring - project_module_news: Nyheder - project_module_documents: Dokumenter - project_module_files: Filer - project_module_wiki: Wiki - project_module_repository: Repository - project_module_boards: Fora - - label_user: Bruger - label_user_plural: Brugere - label_user_new: Ny bruger - label_project: Projekt - label_project_new: Nyt projekt - label_project_plural: Projekter - label_x_projects: - zero: Ingen projekter - one: 1 projekt - other: "%{count} projekter" - label_project_all: Alle projekter - label_project_latest: Seneste projekter - label_issue: Sag - label_issue_new: Opret sag - label_issue_plural: Sager - label_issue_view_all: Vis alle sager - label_issues_by: "Sager fra %{value}" - label_issue_added: Sagen er oprettet - label_issue_updated: Sagen er opdateret - label_document: Dokument - label_document_new: Nyt dokument - label_document_plural: Dokumenter - label_document_added: Dokument tilføjet - label_role: Rolle - label_role_plural: Roller - label_role_new: Ny rolle - label_role_and_permissions: Roller og rettigheder - label_member: Medlem - label_member_new: Nyt medlem - label_member_plural: Medlemmer - label_tracker: Type - label_tracker_plural: Typer - label_tracker_new: Ny type - label_workflow: Arbejdsgang - label_issue_status: Sagsstatus - label_issue_status_plural: Sagsstatusser - label_issue_status_new: Ny status - label_issue_category: Sagskategori - label_issue_category_plural: Sagskategorier - label_issue_category_new: Ny kategori - label_custom_field: Brugerdefineret felt - label_custom_field_plural: Brugerdefinerede felter - label_custom_field_new: Nyt brugerdefineret felt - label_enumerations: Værdier - label_enumeration_new: Ny værdi - label_information: Information - label_information_plural: Information - label_please_login: Login - label_register: Registrér - label_password_lost: Glemt kodeord - label_home: Forside - label_my_page: Min side - label_my_account: Min konto - label_my_projects: Mine projekter - label_administration: Administration - label_login: Log ind - label_logout: Log ud - label_help: Hjælp - label_reported_issues: Rapporterede sager - label_assigned_to_me_issues: Sager tildelt til mig - label_last_login: Sidste logintidspunkt - label_registered_on: Registreret den - label_activity: Aktivitet - label_new: Ny - label_logged_as: Registreret som - label_environment: Miljø - label_authentication: Sikkerhed - label_auth_source: Sikkerhedsmetode - label_auth_source_new: Ny sikkerhedsmetode - label_auth_source_plural: Sikkerhedsmetoder - label_subproject_plural: Underprojekter - label_min_max_length: Min - Max længde - label_list: Liste - label_date: Dato - label_integer: Heltal - label_float: Kommatal - label_boolean: Sand/falsk - label_string: Tekst - label_text: Lang tekst - label_attribute: Attribut - label_attribute_plural: Attributter - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - label_no_data: Ingen data at vise - label_change_status: Ændringsstatus - label_history: Historik - label_attachment: Fil - label_attachment_new: Ny fil - label_attachment_delete: Slet fil - label_attachment_plural: Filer - label_file_added: Fil tilføjet - label_report: Rapport - label_report_plural: Rapporter - label_news: Nyheder - label_news_new: Tilføj nyheder - label_news_plural: Nyheder - label_news_latest: Seneste nyheder - label_news_view_all: Vis alle nyheder - label_news_added: Nyhed tilføjet - label_settings: Indstillinger - label_overview: Oversigt - label_version: Udgave - label_version_new: Ny udgave - label_version_plural: Udgaver - label_confirmation: Bekræftelser - label_export_to: Eksporter til - label_read: Læs... - label_public_projects: Offentlige projekter - label_open_issues: åben - label_open_issues_plural: åbne - label_closed_issues: lukket - label_closed_issues_plural: lukkede - label_x_open_issues_abbr_on_total: - zero: 0 åbne / %{total} - one: 1 åben / %{total} - other: "%{count} åbne / %{total}" - label_x_open_issues_abbr: - zero: 0 åbne - one: 1 åben - other: "%{count} åbne" - label_x_closed_issues_abbr: - zero: 0 lukkede - one: 1 lukket - other: "%{count} lukkede" - label_total: Total - label_permissions: Rettigheder - label_current_status: Nuværende status - label_new_statuses_allowed: Ny status tilladt - label_all: alle - label_none: intet - label_nobody: ingen - label_next: Næste - label_previous: Forrige - label_used_by: Brugt af - label_details: Detaljer - label_add_note: Tilføj note - label_per_page: Pr. side - label_calendar: Kalender - label_months_from: måneder frem - label_gantt: Gantt - label_internal: Intern - label_last_changes: "sidste %{count} ændringer" - label_change_view_all: Vis alle ændringer - label_personalize_page: Tilret denne side - label_comment: Kommentar - label_comment_plural: Kommentarer - label_x_comments: - zero: ingen kommentarer - one: 1 kommentar - other: "%{count} kommentarer" - label_comment_add: Tilføj en kommentar - label_comment_added: Kommentaren er tilføjet - label_comment_delete: Slet kommentar - label_query: Brugerdefineret forespørgsel - label_query_plural: Brugerdefinerede forespørgsler - label_query_new: Ny forespørgsel - label_filter_add: Tilføj filter - label_filter_plural: Filtre - label_equals: er - label_not_equals: er ikke - label_in_less_than: er mindre end - label_in_more_than: er større end - label_in: indeholdt i - label_today: i dag - label_all_time: altid - label_yesterday: i går - label_this_week: denne uge - label_last_week: sidste uge - label_last_n_days: "sidste %{count} dage" - label_this_month: denne måned - label_last_month: sidste måned - label_this_year: dette år - label_date_range: Dato interval - label_less_than_ago: mindre end dage siden - label_more_than_ago: mere end dage siden - label_ago: dage siden - label_contains: indeholder - label_not_contains: ikke indeholder - label_day_plural: dage - label_repository: Repository - label_repository_plural: Repositories - label_browse: Gennemse - label_modification: "%{count} ændring" - label_modification_plural: "%{count} ændringer" - label_revision: Revision - label_revision_plural: Revisioner - label_associated_revisions: Tilknyttede revisioner - label_added: tilføjet - label_modified: ændret - label_deleted: slettet - label_latest_revision: Seneste revision - label_latest_revision_plural: Seneste revisioner - label_view_revisions: Se revisioner - label_max_size: Maksimal størrelse - label_sort_highest: Flyt til toppen - label_sort_higher: Flyt op - label_sort_lower: Flyt ned - label_sort_lowest: Flyt til bunden - label_roadmap: Roadmap - label_roadmap_due_in: Deadline - label_roadmap_overdue: "%{value} forsinket" - label_roadmap_no_issues: Ingen sager i denne version - label_search: Søg - label_result_plural: Resultater - label_all_words: Alle ord - label_wiki: Wiki - label_wiki_edit: Wiki ændring - label_wiki_edit_plural: Wiki ændringer - label_wiki_page: Wiki side - label_wiki_page_plural: Wiki sider - label_index_by_title: Indhold efter titel - label_index_by_date: Indhold efter dato - label_current_version: Nuværende version - label_preview: Forhåndsvisning - label_feed_plural: Feeds - label_changes_details: Detaljer for alle ændringer - label_issue_tracking: Sagssøgning - label_spent_time: Anvendt tid - label_f_hour: "%{value} time" - label_f_hour_plural: "%{value} timer" - label_time_tracking: Tidsstyring - label_change_plural: Ændringer - label_statistics: Statistik - label_commits_per_month: Commits pr. måned - label_commits_per_author: Commits pr. bruger - label_view_diff: Vis forskelle - label_diff_inline: inline - label_diff_side_by_side: side om side - label_options: Formatering - label_copy_workflow_from: Kopier arbejdsgang fra - label_permissions_report: Godkendelsesrapport - label_watched_issues: Overvågede sager - label_related_issues: Relaterede sager - label_applied_status: Anvendte statusser - label_loading: Indlæser... - label_relation_new: Ny relation - label_relation_delete: Slet relation - label_relates_to: relaterer til - label_duplicates: duplikater - label_blocks: blokerer - label_blocked_by: blokeret af - label_precedes: kommer før - label_follows: følger - label_end_to_start: slut til start - label_end_to_end: slut til slut - label_start_to_start: start til start - label_start_to_end: start til slut - label_stay_logged_in: Forbliv indlogget - label_disabled: deaktiveret - label_show_completed_versions: Vis færdige versioner - label_me: mig - label_board: Forum - label_board_new: Nyt forum - label_board_plural: Fora - label_topic_plural: Emner - label_message_plural: Beskeder - label_message_last: Sidste besked - label_message_new: Ny besked - label_message_posted: Besked tilføjet - label_reply_plural: Besvarer - label_send_information: Send konto information til bruger - label_year: År - label_month: Måned - label_week: Uge - label_date_from: Fra - label_date_to: Til - label_language_based: Baseret på brugerens sprog - label_sort_by: "Sortér efter %{value}" - label_send_test_email: Send en test email - label_feeds_access_key_created_on: "RSS adgangsnøgle dannet for %{value} siden" - label_module_plural: Moduler - label_added_time_by: "Tilføjet af %{author} for %{age} siden" - label_updated_time: "Opdateret for %{value} siden" - label_jump_to_a_project: Skift til projekt... - label_file_plural: Filer - label_changeset_plural: Ændringer - label_default_columns: Standardkolonner - label_no_change_option: (Ingen ændringer) - label_bulk_edit_selected_issues: Masse-ret de valgte sager - label_theme: Tema - label_default: standard - label_search_titles_only: Søg kun i titler - label_user_mail_option_all: "For alle hændelser på mine projekter" - label_user_mail_option_selected: "For alle hændelser på de valgte projekter..." - label_user_mail_no_self_notified: "Jeg ønsker ikke besked om ændring foretaget af mig selv" - label_registration_activation_by_email: kontoaktivering på email - label_registration_manual_activation: manuel kontoaktivering - label_registration_automatic_activation: automatisk kontoaktivering - label_display_per_page: "Per side: %{value}" - label_age: Alder - label_change_properties: Ændre indstillinger - label_general: Generelt - label_more: Mere - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: LDAP-godkendelse - label_downloads_abbr: D/L - - button_login: Login - button_submit: Send - button_save: Gem - button_check_all: Vælg alt - button_uncheck_all: Fravælg alt - button_delete: Slet - button_create: Opret - button_test: Test - button_edit: Ret - button_add: Tilføj - button_change: Ændre - button_apply: Anvend - button_clear: Nulstil - button_lock: Lås - button_unlock: Lås op - button_download: Download - button_list: List - button_view: Vis - button_move: Flyt - button_back: Tilbage - button_cancel: Annullér - button_activate: Aktivér - button_sort: Sortér - button_log_time: Log tid - button_rollback: Tilbagefør til denne version - button_watch: Overvåg - button_unwatch: Stop overvågning - button_reply: Besvar - button_archive: Arkivér - button_unarchive: Fjern fra arkiv - button_reset: Nulstil - button_rename: Omdøb - button_change_password: Skift kodeord - button_copy: Kopiér - button_annotate: Annotér - button_update: Opdatér - button_configure: Konfigurér - - status_active: aktiv - status_registered: registreret - status_locked: låst - - text_select_mail_notifications: Vælg handlinger der skal sendes email besked for. - text_regexp_info: f.eks. ^[A-ZÆØÅ0-9]+$ - text_min_max_length_info: 0 betyder ingen begrænsninger - text_project_destroy_confirmation: Er du sikker på at du vil slette dette projekt og alle relaterede data? - text_workflow_edit: Vælg en rolle samt en type, for at redigere arbejdsgangen - text_are_you_sure: Er du sikker? - text_tip_issue_begin_day: opgaven begynder denne dag - text_tip_issue_end_day: opaven slutter denne dag - text_tip_issue_begin_end_day: opgaven begynder og slutter denne dag - text_caracters_maximum: "max %{count} karakterer." - text_caracters_minimum: "Skal være mindst %{count} karakterer lang." - text_length_between: "Længde skal være mellem %{min} og %{max} karakterer." - text_tracker_no_workflow: Ingen arbejdsgang defineret for denne type - text_unallowed_characters: Ikke-tilladte karakterer - text_comma_separated: Adskillige værdier tilladt (adskilt med komma). - text_issues_ref_in_commit_messages: Referer og løser sager i commit-beskeder - text_issue_added: "Sag %{id} er rapporteret af %{author}." - text_issue_updated: "Sag %{id} er blevet opdateret af %{author}." - text_wiki_destroy_confirmation: Er du sikker på at du vil slette denne wiki og dens indhold? - text_issue_category_destroy_question: "Nogle sager (%{count}) er tildelt denne kategori. Hvad ønsker du at gøre?" - text_issue_category_destroy_assignments: Slet tildelinger af kategori - text_issue_category_reassign_to: Tildel sager til denne kategori - text_user_mail_option: "For ikke-valgte projekter vil du kun modtage beskeder omhandlende ting du er involveret i eller overvåger (f.eks. sager du har indberettet eller ejer)." - text_no_configuration_data: "Roller, typer, sagsstatusser og arbejdsgange er endnu ikke konfigureret.\nDet er anbefalet at indlæse standardopsætningen. Du vil kunne ændre denne når den er indlæst." - text_load_default_configuration: Indlæs standardopsætningen - text_status_changed_by_changeset: "Anvendt i ændring %{value}." - text_issues_destroy_confirmation: 'Er du sikker på du ønsker at slette den/de valgte sag(er)?' - text_select_project_modules: 'Vælg moduler er skal være aktiveret for dette projekt:' - text_default_administrator_account_changed: Standardadministratorkonto ændret - text_file_repository_writable: Filarkiv er skrivbar - text_rmagick_available: RMagick tilgængelig (valgfri) - - default_role_manager: Leder - default_role_developer: Udvikler - default_role_reporter: Rapportør - default_tracker_bug: Fejl - default_tracker_feature: Funktion - default_tracker_support: Support - default_issue_status_new: Ny - default_issue_status_in_progress: Igangværende - default_issue_status_resolved: Løst - default_issue_status_feedback: Feedback - default_issue_status_closed: Lukket - default_issue_status_rejected: Afvist - default_doc_category_user: Brugerdokumentation - default_doc_category_tech: Teknisk dokumentation - default_priority_low: Lav - default_priority_normal: Normal - default_priority_high: Høj - default_priority_urgent: Akut - default_priority_immediate: Omgående - default_activity_design: Design - default_activity_development: Udvikling - - enumeration_issue_priorities: Sagsprioriteter - enumeration_doc_categories: Dokumentkategorier - enumeration_activities: Aktiviteter (tidsstyring) - - label_add_another_file: Tilføj endnu en fil - label_chronological_order: I kronologisk rækkefølge - setting_activity_days_default: Antal dage der vises under projektaktivitet - text_destroy_time_entries_question: "%{hours} timer er registreret på denne sag som du er ved at slette. Hvad vil du gøre?" - error_issue_not_found_in_project: 'Sagen blev ikke fundet eller tilhører ikke dette projekt' - text_assign_time_entries_to_project: Tildel raporterede timer til projektet - setting_display_subprojects_issues: Vis sager for underprojekter på hovedprojektet som standard - label_optional_description: Valgfri beskrivelse - text_destroy_time_entries: Slet registrerede timer - field_comments_sorting: Vis kommentar - text_reassign_time_entries: 'Tildel registrerede timer til denne sag igen' - label_reverse_chronological_order: I omvendt kronologisk rækkefølge - label_preferences: Præferencer - label_overall_activity: Overordnet aktivitet - setting_default_projects_public: Nye projekter er offentlige som standard - error_scm_annotate: "Filen findes ikke, eller kunne ikke annoteres." - label_planning: Planlægning - text_subprojects_destroy_warning: "Dets underprojekter(er): %{value} vil også blive slettet." - permission_edit_issues: Redigér sager - setting_diff_max_lines_displayed: Højeste antal forskelle der vises - permission_edit_own_issue_notes: Redigér egne noter - setting_enabled_scm: Aktiveret SCM - button_quote: Citér - permission_view_files: Se filer - permission_add_issues: Tilføj sager - permission_edit_own_messages: Redigér egne beskeder - permission_delete_own_messages: Slet egne beskeder - permission_manage_public_queries: Administrér offentlig forespørgsler - permission_log_time: Registrér anvendt tid - label_renamed: omdøbt - label_incoming_emails: Indkommende emails - permission_view_changesets: Se ændringer - permission_manage_versions: Administrér versioner - permission_view_time_entries: Se anvendt tid - label_generate_key: Generér en nøglefil - permission_manage_categories: Administrér sagskategorier - permission_manage_wiki: Administrér wiki - setting_sequential_project_identifiers: Generér sekventielle projekt-identifikatorer - setting_plain_text_mail: Emails som almindelig tekst (ingen HTML) - field_parent_title: Siden over - text_email_delivery_not_configured: "Email-afsendelse er ikke indstillet og notifikationer er defor slået fra.\nKonfigurér din SMTP server i config/configuration.yml og genstart applikationen for at aktivere email-afsendelse." - permission_protect_wiki_pages: Beskyt wiki sider - permission_manage_documents: Administrér dokumenter - permission_add_issue_watchers: Tilføj overvågere - warning_attachments_not_saved: "der var %{count} fil(er), som ikke kunne gemmes." - permission_comment_news: Kommentér nyheder - text_enumeration_category_reassign_to: 'Flyt dem til denne værdi:' - permission_select_project_modules: Vælg projektmoduler - permission_view_gantt: Se Gantt diagram - permission_delete_messages: Slet beskeder - permission_move_issues: Flyt sager - permission_edit_wiki_pages: Redigér wiki sider - label_user_activity: "%{value}'s aktivitet" - permission_manage_issue_relations: Administrér sags-relationer - label_issue_watchers: Overvågere - permission_delete_wiki_pages: Slet wiki sider - notice_unable_delete_version: Kan ikke slette versionen. - permission_view_wiki_edits: Se wiki historik - field_editable: Redigérbar - label_duplicated_by: dubleret af - permission_manage_boards: Administrér fora - permission_delete_wiki_pages_attachments: Slet filer vedhæftet wiki sider - permission_view_messages: Se beskeder - text_enumeration_destroy_question: "%{count} objekter er tildelt denne værdi." - permission_manage_files: Administrér filer - permission_add_messages: Opret beskeder - permission_edit_issue_notes: Redigér noter - permission_manage_news: Administrér nyheder - text_plugin_assets_writable: Der er skriverettigheder til plugin assets folderen - label_display: Vis - label_and_its_subprojects: "%{value} og dets underprojekter" - permission_view_calendar: Se kalender - button_create_and_continue: Opret og fortsæt - setting_gravatar_enabled: Anvend Gravatar brugerikoner - label_updated_time_by: "Opdateret af %{author} for %{age} siden" - text_diff_truncated: '... Listen over forskelle er blevet afkortet da den overstiger den maksimale størrelse der kan vises.' - text_user_wrote: "%{value} skrev:" - setting_mail_handler_api_enabled: Aktiver webservice for indkomne emails - permission_delete_issues: Slet sager - permission_view_documents: Se dokumenter - permission_browse_repository: Gennemse repository - permission_manage_repository: Administrér repository - permission_manage_members: Administrér medlemmer - mail_subject_reminder: "%{count} sag(er) har deadline i de kommende dage (%{days})" - permission_add_issue_notes: Tilføj noter - permission_edit_messages: Redigér beskeder - permission_view_issue_watchers: Se liste over overvågere - permission_commit_access: Commit adgang - setting_mail_handler_api_key: API nøgle - label_example: Eksempel - permission_rename_wiki_pages: Omdøb wiki sider - text_custom_field_possible_values_info: 'En linje for hver værdi' - permission_view_wiki_pages: Se wiki - permission_edit_project: Redigér projekt - permission_save_queries: Gem forespørgsler - label_copied: kopieret - text_repository_usernames_mapping: "Vælg eller opdatér de Redmine brugere der svarer til de enkelte brugere fundet i repository loggen.\nBrugere med samme brugernavn eller email adresse i både Redmine og det valgte repository bliver automatisk koblet sammen." - permission_edit_time_entries: Redigér tidsregistreringer - general_csv_decimal_separator: ',' - permission_edit_own_time_entries: Redigér egne tidsregistreringer - setting_repository_log_display_limit: Højeste antal revisioner vist i fil-log - setting_file_max_size_displayed: Maksimale størrelse på tekstfiler vist inline - field_watcher: Overvåger - setting_openid: Tillad OpenID login og registrering - field_identity_url: OpenID URL - label_login_with_open_id_option: eller login med OpenID - setting_per_page_options: Enheder per side muligheder - mail_body_reminder: "%{count} sage(er) som er tildelt dig har deadline indenfor de næste %{days} dage:" - field_content: Indhold - label_descending: Aftagende - label_sort: Sortér - label_ascending: Tiltagende - label_date_from_to: Fra %{start} til %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Denne side har %{descendants} underside(r) og afledte. Hvad vil du gøre? - text_wiki_page_reassign_children: Flyt undersider til denne side - text_wiki_page_nullify_children: Behold undersider som rod-sider - text_wiki_page_destroy_children: Slet undersider ogalle deres afledte sider. - setting_password_min_length: Mindste længde på kodeord - field_group_by: Gruppér resultater efter - mail_subject_wiki_content_updated: "'%{id}' wikisiden er blevet opdateret" - label_wiki_content_added: Wiki side tilføjet - mail_subject_wiki_content_added: "'%{id}' wikisiden er blevet tilføjet" - mail_body_wiki_content_added: The '%{id}' wikiside er blevet tilføjet af %{author}. - label_wiki_content_updated: Wikiside opdateret - mail_body_wiki_content_updated: Wikisiden '%{id}' er blevet opdateret af %{author}. - permission_add_project: Opret projekt - setting_new_project_user_role_id: Denne rolle gives til en bruger, som ikke er administrator, og som opretter et projekt - label_view_all_revisions: Se alle revisioner - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: Der er ingen sagshåndtering for dette projekt. Kontrollér venligst projektindstillingerne. - error_no_default_issue_status: Der er ikke defineret en standardstatus. Kontrollér venligst indstillingerne (gå til "Administration -> Sagsstatusser"). - text_journal_changed: "%{label} ændret fra %{old} til %{new}" - text_journal_set_to: "%{label} sat til %{value}" - text_journal_deleted: "%{label} slettet (%{old})" - label_group_plural: Grupper - label_group: Grupper - label_group_new: Ny gruppe - label_time_entry_plural: Anvendt tid - text_journal_added: "%{label} %{value} tilføjet" - field_active: Aktiv - enumeration_system_activity: System Aktivitet - permission_delete_issue_watchers: Slet overvågere - version_status_closed: lukket - version_status_locked: låst - version_status_open: åben - error_can_not_reopen_issue_on_closed_version: En sag tildelt en lukket version kan ikke genåbnes - label_user_anonymous: Anonym - button_move_and_follow: Flyt og overvåg - setting_default_projects_modules: Standard moduler, aktiveret for nye projekter - setting_gravatar_default: Standard Gravatar billede - field_sharing: Delning - label_version_sharing_hierarchy: Med projekthierarki - label_version_sharing_system: Med alle projekter - label_version_sharing_descendants: Med underprojekter - label_version_sharing_tree: Med projekttræ - label_version_sharing_none: Ikke delt - error_can_not_archive_project: Dette projekt kan ikke arkiveres - button_duplicate: Duplikér - button_copy_and_follow: Kopiér og overvåg - label_copy_source: Kilde - setting_issue_done_ratio: Beregn sagsløsning ratio - setting_issue_done_ratio_issue_status: Benyt sagsstatus - error_issue_done_ratios_not_updated: Sagsløsnings ratio, ikke opdateret. - error_workflow_copy_target: Vælg venligst måltracker og rolle(r) - setting_issue_done_ratio_issue_field: Benyt sagsfelt - label_copy_same_as_target: Samme som mål - label_copy_target: Mål - notice_issue_done_ratios_updated: Sagsløsningsratio opdateret. - error_workflow_copy_source: Vælg venligst en kildetracker eller rolle - label_update_issue_done_ratios: Opdater sagsløsningsratio - setting_start_of_week: Start kalendre på - permission_view_issues: Vis sager - label_display_used_statuses_only: Vis kun statusser der er benyttet af denne tracker - label_revision_id: Revision %{value} - label_api_access_key: API nøgle - label_api_access_key_created_on: API nøgle genereret %{value} siden - label_feeds_access_key: RSS nøgle - notice_api_access_key_reseted: Din API nøgle er nulstillet. - setting_rest_api_enabled: Aktiver REST web service - label_missing_api_access_key: Mangler en API nøgle - label_missing_feeds_access_key: Mangler en RSS nøgle - button_show: Vis - text_line_separated: Flere væredier tilladt (en linje for hver værdi). - setting_mail_handler_body_delimiters: Trunkér emails efter en af disse linjer - permission_add_subprojects: Lav underprojekter - label_subproject_new: Nyt underprojekt - text_own_membership_delete_confirmation: |- - Du er ved at fjerne en eller flere af dine rettigheder, og kan muligvis ikke redigere projektet bagefter. - Er du sikker på du ønsker at fortsætte? - label_close_versions: Luk færdige versioner - label_board_sticky: Klistret - label_board_locked: Låst - permission_export_wiki_pages: Eksporter wiki sider - setting_cache_formatted_text: Cache formatteret tekst - permission_manage_project_activities: Administrer projektaktiviteter - error_unable_delete_issue_status: Det var ikke muligt at slette sagsstatus - label_profile: Profil - permission_manage_subtasks: Administrer underopgaver - field_parent_issue: Hovedopgave - label_subtask_plural: Underopgaver - label_project_copy_notifications: Send email notifikationer, mens projektet kopieres - error_can_not_delete_custom_field: Kan ikke slette brugerdefineret felt - error_unable_to_connect: Kan ikke forbinde (%{value}) - error_can_not_remove_role: Denne rolle er i brug og kan ikke slettes. - error_can_not_delete_tracker: Denne type indeholder sager og kan ikke slettes. - field_principal: Principal - label_my_page_block: blok - notice_failed_to_save_members: "Fejl under lagring af medlem(mer): %{errors}." - text_zoom_out: Zoom ud - text_zoom_in: Zoom ind - notice_unable_delete_time_entry: Kan ikke slette tidsregistrering. - label_overall_spent_time: Overordnet forbrug af tid - field_time_entries: Log tid - project_module_gantt: Gantt - project_module_calendar: Kalender - button_edit_associated_wikipage: "Redigér tilknyttet Wiki side: %{page_title}" - field_text: Tekstfelt - label_user_mail_option_only_owner: Kun for ting jeg er ejer af - setting_default_notification_option: Standardpåmindelsesmulighed - label_user_mail_option_only_my_events: Kun for ting jeg overvåger eller er involveret i - label_user_mail_option_only_assigned: Kun for ting jeg er tildelt - label_user_mail_option_none: Ingen hændelser - field_member_of_group: Medlem af gruppe - field_assigned_to_role: Medlem af rolle - notice_not_authorized_archived_project: Projektet du prøver at tilgå, er blevet arkiveret. - label_principal_search: "Søg efter bruger eller gruppe:" - label_user_search: "Søg efter bruger:" - field_visible: Synlig - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Aktivitet for registreret tid - text_time_logged_by_changeset: Anvendt i changeset %{value}. - setting_commit_logtime_enabled: Aktiver tidsregistrering - notice_gantt_chart_truncated: Kortet er blevet afkortet, fordi det overstiger det maksimale antal elementer, der kan vises (%{max}) - setting_gantt_items_limit: Maksimalt antal af elementer der kan vises på gantt kortet - - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Kodning af Commit beskeder - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 sag - one: 1 sag - other: "%{count} sager" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: alle - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Med underprojekter - label_cross_project_tree: Med projekttræ - label_cross_project_hierarchy: Med projekthierarki - label_cross_project_system: Med alle projekter - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a571b427a436ebec45f1319212126a1f8161df94.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a571b427a436ebec45f1319212126a1f8161df94.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class TimeEntryActivityCustomField < CustomField + def type_name + :enumeration_activities + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a57b60c6fc705f8d479ec2528b8b4f74bf1f817f.svn-base --- a/.svn/pristine/a5/a57b60c6fc705f8d479ec2528b8b4f74bf1f817f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class HookTest < ActionController::IntegrationTest - fixtures :users, :roles, :projects, :members, :member_roles - - # Hooks that are manually registered later - class ProjectBasedTemplate < Redmine::Hook::ViewListener - def view_layouts_base_html_head(context) - # Adds a project stylesheet - stylesheet_link_tag(context[:project].identifier) if context[:project] - end - end - - class SidebarContent < Redmine::Hook::ViewListener - def view_layouts_base_sidebar(context) - content_tag('p', 'Sidebar hook') - end - end - - class ContentForInsideHook < Redmine::Hook::ViewListener - render_on :view_welcome_index_left, :inline => <<-VIEW -<% content_for :header_tags do %> - <%= javascript_include_tag 'test_plugin.js', :plugin => 'test_plugin' %> - <%= stylesheet_link_tag 'test_plugin.css', :plugin => 'test_plugin' %> -<% end %> - -

    ContentForInsideHook content

    -VIEW - end - - def setup - Redmine::Hook.clear_listeners - end - - def teardown - Redmine::Hook.clear_listeners - end - - def test_html_head_hook_response - Redmine::Hook.add_listener(ProjectBasedTemplate) - - get '/projects/ecookbook' - assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'}, - :parent => {:tag => 'head'} - end - - def test_empty_sidebar_should_be_hidden - get '/' - assert_select 'div#main.nosidebar' - end - - def test_sidebar_with_hook_content_should_not_be_hidden - Redmine::Hook.add_listener(SidebarContent) - - get '/' - assert_select 'div#sidebar p', :text => 'Sidebar hook' - assert_select 'div#main' - assert_select 'div#main.nosidebar', 0 - end - - def test_hook_with_content_for_should_append_content - Redmine::Hook.add_listener(ContentForInsideHook) - - get '/' - assert_response :success - assert_select 'p', :text => 'ContentForInsideHook content' - assert_select 'head' do - assert_select 'script[src=/plugin_assets/test_plugin/javascripts/test_plugin.js]' - assert_select 'link[href=/plugin_assets/test_plugin/stylesheets/test_plugin.css]' - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a5815d0cddf1fb8e38d76e9186765b1d40c42e1c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a5815d0cddf1fb8e38d76e9186765b1d40c42e1c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,191 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../test_helper', __FILE__) + +class Redmine::MenuManager::MapperTest < ActiveSupport::TestCase + test "Mapper#initialize should define a root MenuNode if menu is not present in items" do + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + node = menu_mapper.menu_items + assert_not_nil node + assert_equal :root, node.name + end + + test "Mapper#initialize should use existing MenuNode if present" do + node = "foo" # just an arbitrary reference + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {:test_menu => node}) + assert_equal node, menu_mapper.menu_items + end + + def test_push_onto_root + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} + + menu_mapper.exists?(:test_overview) + end + + def test_push_onto_parent + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview} + + assert menu_mapper.exists?(:test_child) + assert_equal :test_child, menu_mapper.find(:test_child).name + end + + def test_push_onto_grandparent + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview} + menu_mapper.push :test_grandchild, { :controller => 'projects', :action => 'show'}, {:parent => :test_child} + + assert menu_mapper.exists?(:test_grandchild) + grandchild = menu_mapper.find(:test_grandchild) + assert_equal :test_grandchild, grandchild.name + assert_equal :test_child, grandchild.parent.name + end + + def test_push_first + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {:first => true} + + root = menu_mapper.find(:root) + assert_equal 5, root.children.size + {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name| + assert_not_nil root.children[position] + assert_equal name, root.children[position].name + end + + end + + def test_push_before + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {:before => :test_fourth} + + root = menu_mapper.find(:root) + assert_equal 5, root.children.size + {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name| + assert_not_nil root.children[position] + assert_equal name, root.children[position].name + end + + end + + def test_push_after + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {:after => :test_third} + + root = menu_mapper.find(:root) + assert_equal 5, root.children.size + {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name| + assert_not_nil root.children[position] + assert_equal name, root.children[position].name + end + + end + + def test_push_last + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {:last => true} + menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {} + + root = menu_mapper.find(:root) + assert_equal 5, root.children.size + {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name| + assert_not_nil root.children[position] + assert_equal name, root.children[position].name + end + + end + + def test_exists_for_child_node + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} + menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview } + + assert menu_mapper.exists?(:test_child) + end + + def test_exists_for_invalid_node + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} + + assert !menu_mapper.exists?(:nothing) + end + + def test_find + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} + + item = menu_mapper.find(:test_overview) + assert_equal :test_overview, item.name + assert_equal({:controller => 'projects', :action => 'show'}, item.url) + end + + def test_find_missing + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} + + item = menu_mapper.find(:nothing) + assert_equal nil, item + end + + def test_delete + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} + assert_not_nil menu_mapper.delete(:test_overview) + + assert_nil menu_mapper.find(:test_overview) + end + + def test_delete_missing + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + assert_nil menu_mapper.delete(:test_missing) + end + + test 'deleting all items' do + # Exposed by deleting :last items + Redmine::MenuManager.map :test_menu do |menu| + menu.push :not_last, Redmine::Info.help_url + menu.push :administration, { :controller => 'projects', :action => 'show'}, {:last => true} + menu.push :help, Redmine::Info.help_url, :last => true + end + + assert_nothing_raised do + Redmine::MenuManager.map :test_menu do |menu| + menu.delete(:administration) + menu.delete(:help) + menu.push :test_overview, { :controller => 'projects', :action => 'show'}, {} + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a5a34b2cada8528aefb674b60eab16486ab7bca6.svn-base --- a/.svn/pristine/a5/a5a34b2cada8528aefb674b60eab16486ab7bca6.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class FilesControllerTest < ActionController::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :journals, :journal_details, - :attachments, - :versions - - def setup - @controller = FilesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @request.session[:user_id] = nil - Setting.default_language = 'en' - end - - def test_index - get :index, :project_id => 1 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:containers) - - # file attached to the project - assert_tag :a, :content => 'project_file.zip', - :attributes => { :href => '/attachments/download/8/project_file.zip' } - - # file attached to a project's version - assert_tag :a, :content => 'version_file.zip', - :attributes => { :href => '/attachments/download/9/version_file.zip' } - end - - def test_new - @request.session[:user_id] = 2 - get :new, :project_id => 1 - assert_response :success - assert_template 'new' - - assert_tag 'select', :attributes => {:name => 'version_id'} - end - - def test_new_without_versions - Version.delete_all - @request.session[:user_id] = 2 - get :new, :project_id => 1 - assert_response :success - assert_template 'new' - - assert_no_tag 'select', :attributes => {:name => 'version_id'} - end - - def test_create_file - set_tmp_attachments_directory - @request.session[:user_id] = 2 - ActionMailer::Base.deliveries.clear - - with_settings :notified_events => %w(file_added) do - assert_difference 'Attachment.count' do - post :create, :project_id => 1, :version_id => '', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} - assert_response :redirect - end - end - assert_redirected_to '/projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') - assert_equal 'testfile.txt', a.filename - assert_equal Project.find(1), a.container - - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - assert_equal "[eCookbook] New file", mail.subject - assert_mail_body_match 'testfile.txt', mail - end - - def test_create_version_file - set_tmp_attachments_directory - @request.session[:user_id] = 2 - - assert_difference 'Attachment.count' do - post :create, :project_id => 1, :version_id => '2', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} - assert_response :redirect - end - assert_redirected_to '/projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') - assert_equal 'testfile.txt', a.filename - assert_equal Version.find(2), a.container - end - -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a5c1742116d103566daddf0042d6bf11b7196800.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a5c1742116d103566daddf0042d6bf11b7196800.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,601 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class RepositoryGitTest < ActiveSupport::TestCase + fixtures :projects, :repositories, :enabled_modules, :users, :roles + + include Redmine::I18n + + REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s + REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? + + NUM_REV = 28 + NUM_HEAD = 6 + + FELIX_HEX = "Felix Sch\xC3\xA4fer" + CHAR_1_HEX = "\xc3\x9c" + + ## Git, Mercurial and CVS path encodings are binary. + ## Subversion supports URL encoding for path. + ## Redmine Mercurial adapter and extension use URL encoding. + ## Git accepts only binary path in command line parameter. + ## So, there is no way to use binary command line parameter in JRuby. + JRUBY_SKIP = (RUBY_PLATFORM == 'java') + JRUBY_SKIP_STR = "TODO: This test fails in JRuby" + + def setup + @project = Project.find(3) + @repository = Repository::Git.create( + :project => @project, + :url => REPOSITORY_PATH, + :path_encoding => 'ISO-8859-1' + ) + assert @repository + @char_1 = CHAR_1_HEX.dup + if @char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + end + end + + def test_blank_path_to_repository_error_message + set_language_if_valid 'en' + repo = Repository::Git.new( + :project => @project, + :identifier => 'test' + ) + assert !repo.save + assert_include "Path to repository can't be blank", + repo.errors.full_messages + end + + def test_blank_path_to_repository_error_message_fr + set_language_if_valid 'fr' + str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + repo = Repository::Git.new( + :project => @project, + :url => "", + :identifier => 'test', + :path_encoding => '' + ) + assert !repo.save + assert_include str, repo.errors.full_messages + end + + if File.directory?(REPOSITORY_PATH) + ## Ruby uses ANSI api to fork a process on Windows. + ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem + ## and these are incompatible with ASCII. + ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10 + ## http://code.google.com/p/msysgit/issues/detail?id=80 + ## So, Latin-1 path tests fail on Japanese Windows + WINDOWS_PASS = (Redmine::Platform.mswin? && + Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10])) + WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10" + + def test_scm_available + klass = Repository::Git + assert_equal "Git", klass.scm_name + assert klass.scm_adapter_class + assert_not_equal "", klass.scm_command + assert_equal true, klass.scm_available + end + + def test_entries + entries = @repository.entries + assert_kind_of Redmine::Scm::Adapters::Entries, entries + end + + def test_fetch_changesets_from_scratch + assert_nil @repository.extra_info + + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + + assert_equal NUM_REV, @repository.changesets.count + assert_equal 39, @repository.filechanges.count + + commit = @repository.changesets.find_by_revision("7234cb2750b63f47bff735edc50a1c0a433c2518") + assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.scmid + assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments + assert_equal "jsmith ", commit.committer + assert_equal User.find_by_login('jsmith'), commit.user + # TODO: add a commit with commit time <> author time to the test repository + assert_equal "2007-12-14 09:22:52".to_time, commit.committed_on + assert_equal "2007-12-14".to_date, commit.commit_date + assert_equal 3, commit.filechanges.count + change = commit.filechanges.sort_by(&:path).first + assert_equal "README", change.path + assert_equal nil, change.from_path + assert_equal "A", change.action + + assert_equal NUM_HEAD, @repository.extra_info["heads"].size + end + + def test_fetch_changesets_incremental + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + extra_info_heads = @repository.extra_info["heads"].dup + assert_equal NUM_HEAD, extra_info_heads.size + extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" } + assert_equal 4, extra_info_heads.size + + del_revs = [ + "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c", + "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", + "4f26664364207fa8b1af9f8722647ab2d4ac5d43", + "deff712f05a90d96edbd70facc47d944be5897e3", + "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf", + "7e61ac704deecde634b51e59daa8110435dcb3da", + ] + @repository.changesets.each do |rev| + rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s } + end + @project.reload + cs1 = @repository.changesets + assert_equal NUM_REV - 6, cs1.count + extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8" + h = {} + h["heads"] = extra_info_heads + @repository.merge_extra_info(h) + @repository.save + @project.reload + assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8") + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + assert_equal NUM_HEAD, @repository.extra_info["heads"].size + assert @repository.extra_info["heads"].index("83ca5fd546063a3c7dc2e568ba3355661a9e2b2c") + end + + def test_fetch_changesets_history_editing + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + extra_info_heads = @repository.extra_info["heads"].dup + assert_equal NUM_HEAD, extra_info_heads.size + extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" } + assert_equal 4, extra_info_heads.size + + del_revs = [ + "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c", + "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", + "4f26664364207fa8b1af9f8722647ab2d4ac5d43", + "deff712f05a90d96edbd70facc47d944be5897e3", + "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf", + "7e61ac704deecde634b51e59daa8110435dcb3da", + ] + @repository.changesets.each do |rev| + rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s } + end + @project.reload + assert_equal NUM_REV - 6, @repository.changesets.count + + c = Changeset.new(:repository => @repository, + :committed_on => Time.now, + :revision => "abcd1234efgh", + :scmid => "abcd1234efgh", + :comments => 'test') + assert c.save + @project.reload + assert_equal NUM_REV - 5, @repository.changesets.count + + extra_info_heads << "1234abcd5678" + h = {} + h["heads"] = extra_info_heads + @repository.merge_extra_info(h) + @repository.save + @project.reload + h1 = @repository.extra_info["heads"].dup + assert h1.index("1234abcd5678") + assert_equal 5, h1.size + + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV - 5, @repository.changesets.count + h2 = @repository.extra_info["heads"].dup + assert_equal h1, h2 + end + + def test_keep_extra_report_last_commit_in_clear_changesets + assert_nil @repository.extra_info + h = {} + h["extra_report_last_commit"] = "1" + @repository.merge_extra_info(h) + @repository.save + @project.reload + + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + + assert_equal NUM_REV, @repository.changesets.count + @repository.send(:clear_changesets) + assert_equal 1, @repository.extra_info.size + assert_equal "1", @repository.extra_info["extra_report_last_commit"] + end + + def test_refetch_after_clear_changesets + assert_nil @repository.extra_info + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + @repository.send(:clear_changesets) + @project.reload + assert_equal 0, @repository.changesets.count + + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + end + + def test_parents + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + r1 = @repository.find_changeset_by_name("7234cb2750b63") + assert_equal [], r1.parents + r2 = @repository.find_changeset_by_name("899a15dba03a3") + assert_equal 1, r2.parents.length + assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", + r2.parents[0].identifier + r3 = @repository.find_changeset_by_name("32ae898b720c2") + assert_equal 2, r3.parents.length + r4 = [r3.parents[0].identifier, r3.parents[1].identifier].sort + assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", r4[0] + assert_equal "7e61ac704deecde634b51e59daa8110435dcb3da", r4[1] + end + + def test_db_consistent_ordering_init + assert_nil @repository.extra_info + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal 1, @repository.extra_info["db_consistent"]["ordering"] + end + + def test_db_consistent_ordering_before_1_2 + assert_nil @repository.extra_info + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + assert_not_nil @repository.extra_info + h = {} + h["heads"] = [] + h["branches"] = {} + h["db_consistent"] = {} + @repository.merge_extra_info(h) + @repository.save + assert_equal NUM_REV, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal 0, @repository.extra_info["db_consistent"]["ordering"] + + extra_info_heads = @repository.extra_info["heads"].dup + extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" } + del_revs = [ + "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c", + "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", + "4f26664364207fa8b1af9f8722647ab2d4ac5d43", + "deff712f05a90d96edbd70facc47d944be5897e3", + "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf", + "7e61ac704deecde634b51e59daa8110435dcb3da", + ] + @repository.changesets.each do |rev| + rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s } + end + @project.reload + cs1 = @repository.changesets + assert_equal NUM_REV - 6, cs1.count + assert_equal 0, @repository.extra_info["db_consistent"]["ordering"] + + extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8" + h = {} + h["heads"] = extra_info_heads + @repository.merge_extra_info(h) + @repository.save + @project.reload + assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8") + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + assert_equal NUM_HEAD, @repository.extra_info["heads"].size + + assert_equal 0, @repository.extra_info["db_consistent"]["ordering"] + end + + def test_heads_from_branches_hash + assert_nil @repository.extra_info + assert_equal 0, @repository.changesets.count + assert_equal [], @repository.heads_from_branches_hash + h = {} + h["branches"] = {} + h["branches"]["test1"] = {} + h["branches"]["test1"]["last_scmid"] = "1234abcd" + h["branches"]["test2"] = {} + h["branches"]["test2"]["last_scmid"] = "abcd1234" + @repository.merge_extra_info(h) + @repository.save + @project.reload + assert_equal ["1234abcd", "abcd1234"], @repository.heads_from_branches_hash.sort + end + + def test_latest_changesets + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + # with limit + changesets = @repository.latest_changesets('', 'master', 2) + assert_equal 2, changesets.size + + # with path + changesets = @repository.latest_changesets('images', 'master') + assert_equal [ + 'deff712f05a90d96edbd70facc47d944be5897e3', + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', nil) + assert_equal [ + '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf', + '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8', + '713f4944648826f558cf548222f813dabe7cbb04', + '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + # with path, revision and limit + changesets = @repository.latest_changesets('images', '899a15dba') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('images', '899a15dba', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', '899a15dba') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', '899a15dba', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + # with path, tag and limit + changesets = @repository.latest_changesets('images', 'tag01.annotated') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('images', 'tag01.annotated', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', 'tag01.annotated') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', 'tag01.annotated', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + # with path, branch and limit + changesets = @repository.latest_changesets('images', 'test_branch') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('images', 'test_branch', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', 'test_branch') + assert_equal [ + '713f4944648826f558cf548222f813dabe7cbb04', + '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', 'test_branch', 2) + assert_equal [ + '713f4944648826f558cf548222f813dabe7cbb04', + '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', + ], changesets.collect(&:revision) + + if WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + # latin-1 encoding path + changesets = @repository.latest_changesets( + "latin-1-dir/test-#{@char_1}-2.txt", '64f1f3e89') + assert_equal [ + '64f1f3e89ad1cb57976ff0ad99a107012ba3481d', + '4fc55c43bf3d3dc2efb66145365ddc17639ce81e', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets( + "latin-1-dir/test-#{@char_1}-2.txt", '64f1f3e89', 1) + assert_equal [ + '64f1f3e89ad1cb57976ff0ad99a107012ba3481d', + ], changesets.collect(&:revision) + end + end + + def test_latest_changesets_latin_1_dir + if WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changesets = @repository.latest_changesets( + "latin-1-dir/test-#{@char_1}-subdir", '1ca7f5ed') + assert_equal [ + '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', + ], changesets.collect(&:revision) + end + end + + def test_find_changeset_by_name + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['7234cb2750b63f47bff735edc50a1c0a433c2518', '7234cb2750b'].each do |r| + assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', + @repository.find_changeset_by_name(r).revision + end + end + + def test_find_changeset_by_empty_name + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['', ' ', nil].each do |r| + assert_nil @repository.find_changeset_by_name(r) + end + end + + def test_identifier + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + c = @repository.changesets.find_by_revision( + '7234cb2750b63f47bff735edc50a1c0a433c2518') + assert_equal c.scmid, c.identifier + end + + def test_format_identifier + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + c = @repository.changesets.find_by_revision( + '7234cb2750b63f47bff735edc50a1c0a433c2518') + assert_equal '7234cb27', c.format_identifier + end + + def test_activities + c = Changeset.new(:repository => @repository, + :committed_on => Time.now, + :revision => 'abc7234cb2750b63f47bff735edc50a1c0a433c2', + :scmid => 'abc7234cb2750b63f47bff735edc50a1c0a433c2', + :comments => 'test') + assert c.event_title.include?('abc7234c:') + assert_equal 'abc7234cb2750b63f47bff735edc50a1c0a433c2', c.event_url[:rev] + end + + def test_log_utf8 + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + str_felix_hex = FELIX_HEX.dup + if str_felix_hex.respond_to?(:force_encoding) + str_felix_hex.force_encoding('UTF-8') + end + c = @repository.changesets.find_by_revision( + 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b') + assert_equal "#{str_felix_hex} ", c.committer + end + + def test_previous + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1| + changeset = @repository.find_changeset_by_name(r1) + %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2| + assert_equal @repository.find_changeset_by_name(r2), changeset.previous + end + end + end + + def test_previous_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|7234cb2750b63f47bff735edc50a1c0a433c2518 7234cb275|.each do |r1| + changeset = @repository.find_changeset_by_name(r1) + assert_nil changeset.previous + end + end + + def test_next + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2| + changeset = @repository.find_changeset_by_name(r2) + %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1| + assert_equal @repository.find_changeset_by_name(r1), changeset.next + end + end + end + + def test_next_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|2a682156a3b6e77a8bf9cd4590e8db757f3c6c78 2a682156a3b6e77a|.each do |r1| + changeset = @repository.find_changeset_by_name(r1) + assert_nil changeset.next + end + end + else + puts "Git test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a5ccd83a86dbec61cffcefbeb376115743cf6510.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a5ccd83a86dbec61cffcefbeb376115743cf6510.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,89 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class HookTest < ActionController::IntegrationTest + fixtures :users, :roles, :projects, :members, :member_roles + + # Hooks that are manually registered later + class ProjectBasedTemplate < Redmine::Hook::ViewListener + def view_layouts_base_html_head(context) + # Adds a project stylesheet + stylesheet_link_tag(context[:project].identifier) if context[:project] + end + end + + class SidebarContent < Redmine::Hook::ViewListener + def view_layouts_base_sidebar(context) + content_tag('p', 'Sidebar hook') + end + end + + class ContentForInsideHook < Redmine::Hook::ViewListener + render_on :view_welcome_index_left, :inline => <<-VIEW +<% content_for :header_tags do %> + <%= javascript_include_tag 'test_plugin.js', :plugin => 'test_plugin' %> + <%= stylesheet_link_tag 'test_plugin.css', :plugin => 'test_plugin' %> +<% end %> + +

    ContentForInsideHook content

    +VIEW + end + + def setup + Redmine::Hook.clear_listeners + end + + def teardown + Redmine::Hook.clear_listeners + end + + def test_html_head_hook_response + Redmine::Hook.add_listener(ProjectBasedTemplate) + + get '/projects/ecookbook' + assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'}, + :parent => {:tag => 'head'} + end + + def test_empty_sidebar_should_be_hidden + get '/' + assert_select 'div#main.nosidebar' + end + + def test_sidebar_with_hook_content_should_not_be_hidden + Redmine::Hook.add_listener(SidebarContent) + + get '/' + assert_select 'div#sidebar p', :text => 'Sidebar hook' + assert_select 'div#main' + assert_select 'div#main.nosidebar', 0 + end + + def test_hook_with_content_for_should_append_content + Redmine::Hook.add_listener(ContentForInsideHook) + + get '/' + assert_response :success + assert_select 'p', :text => 'ContentForInsideHook content' + assert_select 'head' do + assert_select 'script[src=/plugin_assets/test_plugin/javascripts/test_plugin.js]' + assert_select 'link[href=/plugin_assets/test_plugin/stylesheets/test_plugin.css]' + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a5cebc8d799b0ca4dcb2be0c534ddb464d9bf52b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a5cebc8d799b0ca4dcb2be0c534ddb464d9bf52b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,39 @@ +
    +<%= link_to l(:label_issue_status_new), new_issue_status_path, :class => 'icon icon-add' %> +<%= link_to(l(:label_update_issue_done_ratios), update_issue_done_ratio_issue_statuses_path, :class => 'icon icon-multiple', :method => 'post', :data => {:confirm => l(:text_are_you_sure)}) if Issue.use_status_for_done_ratio? %> +
    + +

    <%=l(:label_issue_status_plural)%>

    + + + + + <% if Issue.use_status_for_done_ratio? %> + + <% end %> + + + + + + +<% for status in @issue_statuses %> + "> + + <% if Issue.use_status_for_done_ratio? %> + + <% end %> + + + + + +<% end %> + +
    <%=l(:field_status)%><%=l(:field_done_ratio)%><%=l(:field_is_default)%><%=l(:field_is_closed)%><%=l(:button_sort)%>
    <%= link_to h(status.name), edit_issue_status_path(status) %><%= h status.default_done_ratio %><%= checked_image status.is_default? %><%= checked_image status.is_closed? %><%= reorder_links('issue_status', {:action => 'update', :id => status}, :put) %> + <%= delete_link issue_status_path(status) %> +
    + +

    <%= pagination_links_full @issue_status_pages %>

    + +<% html_title(l(:label_issue_status_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a5de9cb99d68c8846c05c740505204c82d676643.svn-base --- a/.svn/pristine/a5/a5de9cb99d68c8846c05c740505204c82d676643.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -module ObjectHelpers - def User.generate!(attributes={}) - @generated_user_login ||= 'user0' - @generated_user_login.succ! - user = User.new(attributes) - user.login = @generated_user_login if user.login.blank? - user.mail = "#{@generated_user_login}@example.com" if user.mail.blank? - user.firstname = "Bob" if user.firstname.blank? - user.lastname = "Doe" if user.lastname.blank? - yield user if block_given? - user.save! - user - end - - def User.add_to_project(user, project, roles=nil) - roles = Role.find(1) if roles.nil? - roles = [roles] unless roles.is_a?(Array) - Member.create!(:principal => user, :project => project, :roles => roles) - end - - def Group.generate!(attributes={}) - @generated_group_name ||= 'Group 0' - @generated_group_name.succ! - group = Group.new(attributes) - group.name = @generated_group_name if group.name.blank? - yield group if block_given? - group.save! - group - end - - def Project.generate!(attributes={}) - @generated_project_identifier ||= 'project-0000' - @generated_project_identifier.succ! - project = Project.new(attributes) - project.name = @generated_project_identifier if project.name.blank? - project.identifier = @generated_project_identifier if project.identifier.blank? - yield project if block_given? - project.save! - project - end - - def Tracker.generate!(attributes={}) - @generated_tracker_name ||= 'Tracker 0' - @generated_tracker_name.succ! - tracker = Tracker.new(attributes) - tracker.name = @generated_tracker_name if tracker.name.blank? - yield tracker if block_given? - tracker.save! - tracker - end - - def Role.generate!(attributes={}) - @generated_role_name ||= 'Role 0' - @generated_role_name.succ! - role = Role.new(attributes) - role.name = @generated_role_name if role.name.blank? - yield role if block_given? - role.save! - role - end - - def Issue.generate!(attributes={}) - issue = Issue.new(attributes) - issue.project ||= Project.find(1) - issue.tracker ||= issue.project.trackers.first - issue.subject = 'Generated' if issue.subject.blank? - issue.author ||= User.find(2) - yield issue if block_given? - issue.save! - issue - end - - # Generates an issue with 2 children and a grandchild - def Issue.generate_with_descendants!(attributes={}) - issue = Issue.generate!(attributes) - child = Issue.generate!(:project => issue.project, :subject => 'Child1', :parent_issue_id => issue.id) - Issue.generate!(:project => issue.project, :subject => 'Child2', :parent_issue_id => issue.id) - Issue.generate!(:project => issue.project, :subject => 'Child11', :parent_issue_id => child.id) - issue.reload - end - - def Journal.generate!(attributes={}) - journal = Journal.new(attributes) - journal.user ||= User.first - journal.journalized ||= Issue.first - yield journal if block_given? - journal.save! - journal - end - - def Version.generate!(attributes={}) - @generated_version_name ||= 'Version 0' - @generated_version_name.succ! - version = Version.new(attributes) - version.name = @generated_version_name if version.name.blank? - yield version if block_given? - version.save! - version - end - - def AuthSource.generate!(attributes={}) - @generated_auth_source_name ||= 'Auth 0' - @generated_auth_source_name.succ! - source = AuthSource.new(attributes) - source.name = @generated_auth_source_name if source.name.blank? - yield source if block_given? - source.save! - source - end - - def Board.generate!(attributes={}) - @generated_board_name ||= 'Forum 0' - @generated_board_name.succ! - board = Board.new(attributes) - board.name = @generated_board_name if board.name.blank? - board.description = @generated_board_name if board.description.blank? - yield board if block_given? - board.save! - board - end - - def Attachment.generate!(attributes={}) - @generated_filename ||= 'testfile0' - @generated_filename.succ! - attributes = attributes.dup - attachment = Attachment.new(attributes) - attachment.container ||= Issue.find(1) - attachment.author ||= User.find(2) - attachment.filename = @generated_filename if attachment.filename.blank? - attachment.save! - attachment - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a5/a5eae59bbce6cca98bb354eb803d70ece1bc50d0.svn-base --- a/.svn/pristine/a5/a5eae59bbce6cca98bb354eb803d70ece1bc50d0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,798 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'iconv' -require 'tcpdf' -require 'fpdf/chinese' -require 'fpdf/japanese' -require 'fpdf/korean' - -module Redmine - module Export - module PDF - include ActionView::Helpers::TextHelper - include ActionView::Helpers::NumberHelper - include IssuesHelper - - class ITCPDF < TCPDF - include Redmine::I18n - attr_accessor :footer_date - - def initialize(lang, orientation='P') - @@k_path_cache = Rails.root.join('tmp', 'pdf') - FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache) - set_language_if_valid lang - pdf_encoding = l(:general_pdf_encoding).upcase - super(orientation, 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding) - case current_language.to_s.downcase - when 'vi' - @font_for_content = 'DejaVuSans' - @font_for_footer = 'DejaVuSans' - else - case pdf_encoding - when 'UTF-8' - @font_for_content = 'FreeSans' - @font_for_footer = 'FreeSans' - when 'CP949' - extend(PDF_Korean) - AddUHCFont() - @font_for_content = 'UHC' - @font_for_footer = 'UHC' - when 'CP932', 'SJIS', 'SHIFT_JIS' - extend(PDF_Japanese) - AddSJISFont() - @font_for_content = 'SJIS' - @font_for_footer = 'SJIS' - when 'GB18030' - extend(PDF_Chinese) - AddGBFont() - @font_for_content = 'GB' - @font_for_footer = 'GB' - when 'BIG5' - extend(PDF_Chinese) - AddBig5Font() - @font_for_content = 'Big5' - @font_for_footer = 'Big5' - else - @font_for_content = 'Arial' - @font_for_footer = 'Helvetica' - end - end - SetCreator(Redmine::Info.app_name) - SetFont(@font_for_content) - @outlines = [] - @outlineRoot = nil - end - - def SetFontStyle(style, size) - SetFont(@font_for_content, style, size) - end - - def SetTitle(txt) - txt = begin - utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) - hextxt = "" - rescue - txt - end || '' - super(txt) - end - - def textstring(s) - # Format a text string - if s =~ /^\{\{([<>]?)toc\}\}<\/p>/i, '') - html - end - - def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='') - Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link) - end - - def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1) - MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln) - end - - def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0) - @attachments = attachments - writeHTMLCell(w, h, x, y, - fix_text_encoding(formatted_text(txt)), - border, ln, fill) - end - - def getImageFilename(attrname) - # attrname: general_pdf_encoding string file/uri name - atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding)) - if atta - return atta.diskfile - else - return nil - end - end - - def Footer - SetFont(@font_for_footer, 'I', 8) - SetY(-15) - SetX(15) - RDMCell(0, 5, @footer_date, 0, 0, 'L') - SetY(-15) - SetX(-30) - RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') - end - - def Bookmark(txt, level=0, y=0) - if (y == -1) - y = GetY() - end - @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k} - end - - def bookmark_title(txt) - txt = begin - utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) - hextxt = "" - rescue - txt - end || '' - end - - def putbookmarks - nb=@outlines.size - return if (nb==0) - lru=[] - level=0 - @outlines.each_with_index do |o, i| - if(o[:l]>0) - parent=lru[o[:l]-1] - #Set parent and last pointers - @outlines[i][:parent]=parent - @outlines[parent][:last]=i - if (o[:l]>level) - #Level increasing: set first pointer - @outlines[parent][:first]=i - end - else - @outlines[i][:parent]=nb - end - if (o[:l]<=level && i>0) - #Set prev and next pointers - prev=lru[o[:l]] - @outlines[prev][:next]=i - @outlines[i][:prev]=prev - end - lru[o[:l]]=i - level=o[:l] - end - #Outline items - n=self.n+1 - @outlines.each_with_index do |o, i| - newobj() - out('<>') - out('endobj') - end - #Outline root - newobj() - @outlineRoot=self.n - out("<>"); - out('endobj'); - end - - def putresources() - super - putbookmarks() - end - - def putcatalog() - super - if(@outlines.size > 0) - out("/Outlines #{@outlineRoot} 0 R"); - out('/PageMode /UseOutlines'); - end - end - end - - # fetch row values - def fetch_row_values(issue, query, level) - query.inline_columns.collect do |column| - s = if column.is_a?(QueryCustomFieldColumn) - cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} - show_value(cv) - else - value = issue.send(column.name) - if column.name == :subject - value = " " * level + value - end - if value.is_a?(Date) - format_date(value) - elsif value.is_a?(Time) - format_time(value) - else - value - end - end - s.to_s - end - end - - # calculate columns width - def calc_col_width(issues, query, table_width, pdf) - # calculate statistics - # by captions - pdf.SetFontStyle('B',8) - col_padding = pdf.GetStringWidth('OO') - col_width_min = query.inline_columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding} - col_width_max = Array.new(col_width_min) - col_width_avg = Array.new(col_width_min) - word_width_max = query.inline_columns.map {|c| - n = 10 - c.caption.split.each {|w| - x = pdf.GetStringWidth(w) + col_padding - n = x if n < x - } - n - } - - # by properties of issues - pdf.SetFontStyle('',8) - col_padding = pdf.GetStringWidth('OO') - k = 1 - issue_list(issues) {|issue, level| - k += 1 - values = fetch_row_values(issue, query, level) - values.each_with_index {|v,i| - n = pdf.GetStringWidth(v) + col_padding - col_width_max[i] = n if col_width_max[i] < n - col_width_min[i] = n if col_width_min[i] > n - col_width_avg[i] += n - v.split.each {|w| - x = pdf.GetStringWidth(w) + col_padding - word_width_max[i] = x if word_width_max[i] < x - } - } - } - col_width_avg.map! {|x| x / k} - - # calculate columns width - ratio = table_width / col_width_avg.inject(0) {|s,w| s += w} - col_width = col_width_avg.map {|w| w * ratio} - - # correct max word width if too many columns - ratio = table_width / word_width_max.inject(0) {|s,w| s += w} - word_width_max.map! {|v| v * ratio} if ratio < 1 - - # correct and lock width of some columns - done = 1 - col_fix = [] - col_width.each_with_index do |w,i| - if w > col_width_max[i] - col_width[i] = col_width_max[i] - col_fix[i] = 1 - done = 0 - elsif w < word_width_max[i] - col_width[i] = word_width_max[i] - col_fix[i] = 1 - done = 0 - else - col_fix[i] = 0 - end - end - - # iterate while need to correct and lock coluns width - while done == 0 - # calculate free & locked columns width - done = 1 - fix_col_width = 0 - free_col_width = 0 - col_width.each_with_index do |w,i| - if col_fix[i] == 1 - fix_col_width += w - else - free_col_width += w - end - end - - # calculate column normalizing ratio - if free_col_width == 0 - ratio = table_width / col_width.inject(0) {|s,w| s += w} - else - ratio = (table_width - fix_col_width) / free_col_width - end - - # correct columns width - col_width.each_with_index do |w,i| - if col_fix[i] == 0 - col_width[i] = w * ratio - - # check if column width less then max word width - if col_width[i] < word_width_max[i] - col_width[i] = word_width_max[i] - col_fix[i] = 1 - done = 0 - elsif col_width[i] > col_width_max[i] - col_width[i] = col_width_max[i] - col_fix[i] = 1 - done = 0 - end - end - end - end - col_width - end - - def render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) - # headers - pdf.SetFontStyle('B',8) - pdf.SetFillColor(230, 230, 230) - - # render it background to find the max height used - base_x = pdf.GetX - base_y = pdf.GetY - max_height = issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) - pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD'); - pdf.SetXY(base_x, base_y); - - # write the cells on page - pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1) - issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) - issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) - pdf.SetY(base_y + max_height); - - # rows - pdf.SetFontStyle('',8) - pdf.SetFillColor(255, 255, 255) - end - - # Returns a PDF string of a list of issues - def issues_to_pdf(issues, project, query) - pdf = ITCPDF.new(current_language, "L") - title = query.new_record? ? l(:label_issue_plural) : query.name - title = "#{project} - #{title}" if project - pdf.SetTitle(title) - pdf.alias_nb_pages - pdf.footer_date = format_date(Date.today) - pdf.SetAutoPageBreak(false) - pdf.AddPage("L") - - # Landscape A4 = 210 x 297 mm - page_height = 210 - page_width = 297 - right_margin = 10 - bottom_margin = 20 - col_id_width = 10 - row_height = 4 - - # column widths - table_width = page_width - right_margin - 10 # fixed left margin - col_width = [] - unless query.inline_columns.empty? - col_width = calc_col_width(issues, query, table_width - col_id_width, pdf) - table_width = col_width.inject(0) {|s,v| s += v} - end - - # use full width if the description is displayed - if table_width > 0 && query.has_column?(:description) - col_width = col_width.map {|w| w = w * (page_width - right_margin - 10 - col_id_width) / table_width} - table_width = col_width.inject(0) {|s,v| s += v} - end - - # title - pdf.SetFontStyle('B',11) - pdf.RDMCell(190,10, title) - pdf.Ln - render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) - previous_group = false - issue_list(issues) do |issue, level| - if query.grouped? && - (group = query.group_by_column.value(issue)) != previous_group - pdf.SetFontStyle('B',10) - group_label = group.blank? ? 'None' : group.to_s.dup - group_label << " (#{query.issue_count_by_group[group]})" - pdf.Bookmark group_label, 0, -1 - pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L') - pdf.SetFontStyle('',8) - previous_group = group - end - - # fetch row values - col_values = fetch_row_values(issue, query, level) - - # render it off-page to find the max height used - base_x = pdf.GetX - base_y = pdf.GetY - pdf.SetY(2 * page_height) - max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) - pdf.SetXY(base_x, base_y) - - # make new page if it doesn't fit on the current one - space_left = page_height - base_y - bottom_margin - if max_height > space_left - pdf.AddPage("L") - render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) - base_x = pdf.GetX - base_y = pdf.GetY - end - - # write the cells on page - pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1) - issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) - issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) - pdf.SetY(base_y + max_height); - - if query.has_column?(:description) && issue.description? - pdf.SetX(10) - pdf.SetAutoPageBreak(true, 20) - pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT") - pdf.SetAutoPageBreak(false) - end - end - - if issues.size == Setting.issues_export_limit.to_i - pdf.SetFontStyle('B',10) - pdf.RDMCell(0, row_height, '...') - end - pdf.Output - end - - # Renders MultiCells and returns the maximum height used - def issues_to_pdf_write_cells(pdf, col_values, col_widths, - row_height, head=false) - base_y = pdf.GetY - max_height = row_height - col_values.each_with_index do |column, i| - col_x = pdf.GetX - if head == true - pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1) - else - pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1) - end - max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height - pdf.SetXY(col_x + col_widths[i], base_y); - end - return max_height - end - - # Draw lines to close the row (MultiCell border drawing in not uniform) - def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y, - id_width, col_widths) - col_x = top_x + id_width - pdf.Line(col_x, top_y, col_x, lower_y) # id right border - col_widths.each do |width| - col_x += width - pdf.Line(col_x, top_y, col_x, lower_y) # columns right border - end - pdf.Line(top_x, top_y, top_x, lower_y) # left border - pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border - end - - # Returns a PDF string of a single issue - def issue_to_pdf(issue, assoc={}) - pdf = ITCPDF.new(current_language) - pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}") - pdf.alias_nb_pages - pdf.footer_date = format_date(Date.today) - pdf.AddPage - pdf.SetFontStyle('B',11) - buf = "#{issue.project} - #{issue.tracker} ##{issue.id}" - pdf.RDMMultiCell(190, 5, buf) - pdf.SetFontStyle('',8) - base_x = pdf.GetX - i = 1 - issue.ancestors.visible.each do |ancestor| - pdf.SetX(base_x + i) - buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" - pdf.RDMMultiCell(190 - i, 5, buf) - i += 1 if i < 35 - end - pdf.SetFontStyle('B',11) - pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s) - pdf.SetFontStyle('',8) - pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}") - pdf.Ln - - left = [] - left << [l(:field_status), issue.status] - left << [l(:field_priority), issue.priority] - left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') - left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') - left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') - - right = [] - right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') - right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') - right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') - right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') - right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) - - rows = left.size > right.size ? left.size : right.size - while left.size < rows - left << nil - end - while right.size < rows - right << nil - end - - half = (issue.custom_field_values.size / 2.0).ceil - issue.custom_field_values.each_with_index do |custom_value, i| - (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)] - end - - rows = left.size > right.size ? left.size : right.size - rows.times do |i| - item = left[i] - pdf.SetFontStyle('B',9) - pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") - pdf.SetFontStyle('',9) - pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") - - item = right[i] - pdf.SetFontStyle('B',9) - pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") - pdf.SetFontStyle('',9) - pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") - pdf.Ln - end - - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) - pdf.SetFontStyle('',9) - - # Set resize image scale - pdf.SetImageScale(1.6) - pdf.RDMwriteHTMLCell(35+155, 5, 0, 0, - issue.description.to_s, issue.attachments, "LRB") - - unless issue.leaf? - # for CJK - truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 ) - - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") - pdf.Ln - issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| - buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}", - :length => truncate_length) - level = 10 if level >= 10 - pdf.SetFontStyle('',8) - pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L") - pdf.SetFontStyle('B',8) - pdf.RDMCell(20,5, child.status.to_s, "R") - pdf.Ln - end - end - - relations = issue.relations.select { |r| r.other_issue(issue).visible? } - unless relations.empty? - # for CJK - truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 ) - - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") - pdf.Ln - relations.each do |relation| - buf = "" - buf += "#{l(relation.label_for(issue))} " - if relation.delay && relation.delay != 0 - buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) " - end - if Setting.cross_project_issue_relations? - buf += "#{relation.other_issue(issue).project} - " - end - buf += "#{relation.other_issue(issue).tracker}" + - " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}" - buf = truncate(buf, :length => truncate_length) - pdf.SetFontStyle('', 8) - pdf.RDMCell(35+155-60, 5, buf, "L") - pdf.SetFontStyle('B',8) - pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "") - pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "") - pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R") - pdf.Ln - end - end - pdf.RDMCell(190,5, "", "T") - pdf.Ln - - if issue.changesets.any? && - User.current.allowed_to?(:view_changesets, issue.project) - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_associated_revisions), "B") - pdf.Ln - for changeset in issue.changesets - pdf.SetFontStyle('B',8) - csstr = "#{l(:label_revision)} #{changeset.format_identifier} - " - csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s - pdf.RDMCell(190, 5, csstr) - pdf.Ln - unless changeset.comments.blank? - pdf.SetFontStyle('',8) - pdf.RDMwriteHTMLCell(190,5,0,0, - changeset.comments.to_s, issue.attachments, "") - end - pdf.Ln - end - end - - if assoc[:journals].present? - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_history), "B") - pdf.Ln - assoc[:journals].each do |journal| - pdf.SetFontStyle('B',8) - title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}" - title << " (#{l(:field_private_notes)})" if journal.private_notes? - pdf.RDMCell(190,5, title) - pdf.Ln - pdf.SetFontStyle('I',8) - details_to_strings(journal.details, true).each do |string| - pdf.RDMMultiCell(190,5, "- " + string) - end - if journal.notes? - pdf.Ln unless journal.details.empty? - pdf.SetFontStyle('',8) - pdf.RDMwriteHTMLCell(190,5,0,0, - journal.notes.to_s, issue.attachments, "") - end - pdf.Ln - end - end - - if issue.attachments.any? - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_attachment_plural), "B") - pdf.Ln - for attachment in issue.attachments - pdf.SetFontStyle('',8) - pdf.RDMCell(80,5, attachment.filename) - pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") - pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") - pdf.RDMCell(65,5, attachment.author.name,0,0,"R") - pdf.Ln - end - end - pdf.Output - end - - # Returns a PDF string of a set of wiki pages - def wiki_pages_to_pdf(pages, project) - pdf = ITCPDF.new(current_language) - pdf.SetTitle(project.name) - pdf.alias_nb_pages - pdf.footer_date = format_date(Date.today) - pdf.AddPage - pdf.SetFontStyle('B',11) - pdf.RDMMultiCell(190,5, project.name) - pdf.Ln - # Set resize image scale - pdf.SetImageScale(1.6) - pdf.SetFontStyle('',9) - write_page_hierarchy(pdf, pages.group_by(&:parent_id)) - pdf.Output - end - - # Returns a PDF string of a single wiki page - def wiki_page_to_pdf(page, project) - pdf = ITCPDF.new(current_language) - pdf.SetTitle("#{project} - #{page.title}") - pdf.alias_nb_pages - pdf.footer_date = format_date(Date.today) - pdf.AddPage - pdf.SetFontStyle('B',11) - pdf.RDMMultiCell(190,5, - "#{project} - #{page.title} - # #{page.content.version}") - pdf.Ln - # Set resize image scale - pdf.SetImageScale(1.6) - pdf.SetFontStyle('',9) - write_wiki_page(pdf, page) - pdf.Output - end - - def write_page_hierarchy(pdf, pages, node=nil, level=0) - if pages[node] - pages[node].each do |page| - if @new_page - pdf.AddPage - else - @new_page = true - end - pdf.Bookmark page.title, level - write_wiki_page(pdf, page) - write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id] - end - end - end - - def write_wiki_page(pdf, page) - pdf.RDMwriteHTMLCell(190,5,0,0, - page.content.text.to_s, page.attachments, 0) - if page.attachments.any? - pdf.Ln - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_attachment_plural), "B") - pdf.Ln - for attachment in page.attachments - pdf.SetFontStyle('',8) - pdf.RDMCell(80,5, attachment.filename) - pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") - pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") - pdf.RDMCell(65,5, attachment.author.name,0,0,"R") - pdf.Ln - end - end - end - - class RDMPdfEncoding - def self.rdm_from_utf8(txt, encoding) - txt ||= '' - txt = Redmine::CodesetUtil.from_utf8(txt, encoding) - if txt.respond_to?(:force_encoding) - txt.force_encoding('ASCII-8BIT') - end - txt - end - - def self.attach(attachments, filename, encoding) - filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding) - atta = nil - if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i - atta = Attachment.latest_attach(attachments, filename_utf8) - end - if atta && atta.readable? && atta.visible? - return atta - else - return nil - end - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a60e93499b2f964cb84a1c5f5305a8701ced6961.svn-base Binary file .svn/pristine/a6/a60e93499b2f964cb84a1c5f5305a8701ced6961.svn-base has changed diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a630a39c4ba7dc7a2645fe3c5b9313ac357232d8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a6/a630a39c4ba7dc7a2645fe3c5b9313ac357232d8.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class Message < ActiveRecord::Base + include Redmine::SafeAttributes + belongs_to :board + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC" + acts_as_attachable + belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' + + acts_as_searchable :columns => ['subject', 'content'], + :include => {:board => :project}, + :project_key => "#{Board.table_name}.project_id", + :date_column => "#{table_name}.created_on" + acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, + :description => :content, + :group => :parent, + :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, + :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : + {:id => o.parent_id, :r => o.id, :anchor => "message-#{o.id}"})} + + acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]}, + :author_key => :author_id + acts_as_watchable + + validates_presence_of :board, :subject, :content + validates_length_of :subject, :maximum => 255 + validate :cannot_reply_to_locked_topic, :on => :create + + after_create :add_author_as_watcher, :reset_counters! + after_update :update_messages_board + after_destroy :reset_counters! + after_create :send_notification + + scope :visible, lambda {|*args| + includes(:board => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + } + + safe_attributes 'subject', 'content' + safe_attributes 'locked', 'sticky', 'board_id', + :if => lambda {|message, user| + user.allowed_to?(:edit_messages, message.project) + } + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_messages, project) + end + + def cannot_reply_to_locked_topic + # Can not reply to a locked topic + errors.add :base, 'Topic is locked' if root.locked? && self != root + end + + def update_messages_board + if board_id_changed? + Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id]) + Board.reset_counters!(board_id_was) + Board.reset_counters!(board_id) + end + end + + def reset_counters! + if parent && parent.id + Message.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id}) + end + board.reset_counters! + end + + def sticky=(arg) + write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0) + end + + def sticky? + sticky == 1 + end + + def project + board.project + end + + def editable_by?(usr) + usr && usr.logged? && (usr.allowed_to?(:edit_messages, project) || (self.author == usr && usr.allowed_to?(:edit_own_messages, project))) + end + + def destroyable_by?(usr) + usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project))) + end + + private + + def add_author_as_watcher + Watcher.create(:watchable => self.root, :user => author) + end + + def send_notification + if Setting.notified_events.include?('message_posted') + Mailer.message_posted(self).deliver + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a65be5ff4e4d817d915292a1086456273d988086.svn-base --- a/.svn/pristine/a6/a65be5ff4e4d817d915292a1086456273d988086.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -

    <%= link_to l(@enumeration.option_name), enumerations_path %> » <%=l(:label_enumeration_new)%>

    - -<%= labelled_form_for :enumeration, @enumeration, :url => enumerations_path do |f| %> - <%= f.hidden_field :type %> - <%= render :partial => 'form', :locals => {:f => f} %> - <%= submit_tag l(:button_create) %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a677b960a442cc4fdd4c022f1d5c5fd8938b0275.svn-base --- a/.svn/pristine/a6/a677b960a442cc4fdd4c022f1d5c5fd8938b0275.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -class CalendarAndActivity < ActiveRecord::Migration - # model removed - class Permission < ActiveRecord::Base; end - - def self.up - Permission.create :controller => "projects", :action => "activity", :description => "label_activity", :sort => 160, :is_public => true, :mail_option => 0, :mail_enabled => 0 - Permission.create :controller => "projects", :action => "calendar", :description => "label_calendar", :sort => 165, :is_public => true, :mail_option => 0, :mail_enabled => 0 - Permission.create :controller => "projects", :action => "gantt", :description => "label_gantt", :sort => 166, :is_public => true, :mail_option => 0, :mail_enabled => 0 - end - - def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'activity']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'calendar']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'gantt']).destroy - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a68ae98331ab8e425c64f338d2d4aae23bc594f6.svn-base --- a/.svn/pristine/a6/a68ae98331ab8e425c64f338d2d4aae23bc594f6.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'wikis_controller' - -# Re-raise errors caught by the controller. -class WikisController; def rescue_action(e) raise e end; end - -class WikisControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :wikis - - def setup - @controller = WikisController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_create - @request.session[:user_id] = 1 - assert_nil Project.find(3).wiki - - assert_difference 'Wiki.count' do - xhr :post, :edit, :id => 3, :wiki => { :start_page => 'Start page' } - assert_response :success - assert_template 'edit' - assert_equal 'text/javascript', response.content_type - end - - wiki = Project.find(3).wiki - assert_not_nil wiki - assert_equal 'Start page', wiki.start_page - end - - def test_create_with_failure - @request.session[:user_id] = 1 - - assert_no_difference 'Wiki.count' do - xhr :post, :edit, :id => 3, :wiki => { :start_page => '' } - assert_response :success - assert_template 'edit' - assert_equal 'text/javascript', response.content_type - end - - assert_include 'errorExplanation', response.body - assert_include 'Start page can't be blank', response.body - end - - def test_update - @request.session[:user_id] = 1 - - assert_no_difference 'Wiki.count' do - xhr :post, :edit, :id => 1, :wiki => { :start_page => 'Other start page' } - assert_response :success - assert_template 'edit' - assert_equal 'text/javascript', response.content_type - end - - wiki = Project.find(1).wiki - assert_equal 'Other start page', wiki.start_page - end - - def test_destroy - @request.session[:user_id] = 1 - post :destroy, :id => 1, :confirm => 1 - assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'ecookbook', :tab => 'wiki' - assert_nil Project.find(1).wiki - end - - def test_not_found - @request.session[:user_id] = 1 - post :destroy, :id => 999, :confirm => 1 - assert_response 404 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a6ae8d4e019a56b1c1694cce112ce20ab4d1bb3c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a6/a6ae8d4e019a56b1c1694cce112ce20ab4d1bb3c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,76 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::RolesTest < Redmine::ApiTest::Base + fixtures :roles + + def setup + Setting.rest_api_enabled = '1' + end + + test "GET /roles.xml should return the roles" do + get '/roles.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_equal 3, assigns(:roles).size + + assert_tag :tag => 'roles', + :attributes => {:type => 'array'}, + :child => { + :tag => 'role', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => 'Developer' + } + } + } + end + + test "GET /roles.json should return the roles" do + get '/roles.json' + + assert_response :success + assert_equal 'application/json', @response.content_type + assert_equal 3, assigns(:roles).size + + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Array, json['roles'] + assert_include({'id' => 2, 'name' => 'Developer'}, json['roles']) + end + + test "GET /roles/:id.xml should return the role" do + get '/roles/1.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + + assert_select 'role' do + assert_select 'name', :text => 'Manager' + assert_select 'role permissions[type=array]' do + assert_select 'permission', Role.find(1).permissions.size + assert_select 'permission', :text => 'view_issues' + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a6be7752b3e1c7a6a368129ebcfd669e88823a53.svn-base --- a/.svn/pristine/a6/a6be7752b3e1c7a6a368129ebcfd669e88823a53.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingFilesTest < ActionController::IntegrationTest - def test_files - assert_routing( - { :method => 'get', :path => "/projects/33/files" }, - { :controller => 'files', :action => 'index', :project_id => '33' } - ) - assert_routing( - { :method => 'get', :path => "/projects/33/files/new" }, - { :controller => 'files', :action => 'new', :project_id => '33' } - ) - assert_routing( - { :method => 'post', :path => "/projects/33/files" }, - { :controller => 'files', :action => 'create', :project_id => '33' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a6d8d977a7db837089d88e0eb10b4689db86305a.svn-base --- a/.svn/pristine/a6/a6d8d977a7db837089d88e0eb10b4689db86305a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,976 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Project < ActiveRecord::Base - include Redmine::SafeAttributes - - # Project statuses - STATUS_ACTIVE = 1 - STATUS_CLOSED = 5 - STATUS_ARCHIVED = 9 - - # Maximum length for project identifiers - IDENTIFIER_MAX_LENGTH = 100 - - # Specific overidden Activities - has_many :time_entry_activities - has_many :members, :include => [:principal, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}" - has_many :memberships, :class_name => 'Member' - has_many :member_principals, :class_name => 'Member', - :include => :principal, - :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})" - has_many :users, :through => :members - has_many :principals, :through => :member_principals, :source => :principal - - has_many :enabled_modules, :dependent => :delete_all - has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" - has_many :issues, :dependent => :destroy, :include => [:status, :tracker] - has_many :issue_changes, :through => :issues, :source => :journals - has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" - has_many :time_entries, :dependent => :delete_all - has_many :queries, :dependent => :delete_all - has_many :documents, :dependent => :destroy - has_many :news, :dependent => :destroy, :include => :author - has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" - has_many :boards, :dependent => :destroy, :order => "position ASC" - has_one :repository, :conditions => ["is_default = ?", true] - has_many :repositories, :dependent => :destroy - has_many :changesets, :through => :repository - has_one :wiki, :dependent => :destroy - # Custom field for the project issues - has_and_belongs_to_many :issue_custom_fields, - :class_name => 'IssueCustomField', - :order => "#{CustomField.table_name}.position", - :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", - :association_foreign_key => 'custom_field_id' - - acts_as_nested_set :order => 'name', :dependent => :destroy - acts_as_attachable :view_permission => :view_files, - :delete_permission => :manage_files - - acts_as_customizable - acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil - acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"}, - :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}}, - :author => nil - - attr_protected :status - - validates_presence_of :name, :identifier - validates_uniqueness_of :identifier - validates_associated :repository, :wiki - validates_length_of :name, :maximum => 255 - validates_length_of :homepage, :maximum => 255 - validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH - # donwcase letters, digits, dashes but not digits only - validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-_]*$/, :if => Proc.new { |p| p.identifier_changed? } - # reserved words - validates_exclusion_of :identifier, :in => %w( new ) - - after_save :update_position_under_parent, :if => Proc.new {|project| project.name_changed?} - before_destroy :delete_all_members - - scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } } - scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} - scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} } - scope :all_public, { :conditions => { :is_public => true } } - scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }} - scope :allowed_to, lambda {|*args| - user = User.current - permission = nil - if args.first.is_a?(Symbol) - permission = args.shift - else - user = args.shift - permission = args.shift - end - { :conditions => Project.allowed_to_condition(user, permission, *args) } - } - scope :like, lambda {|arg| - if arg.blank? - {} - else - pattern = "%#{arg.to_s.strip.downcase}%" - {:conditions => ["LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", {:p => pattern}]} - end - } - - def initialize(attributes=nil, *args) - super - - initialized = (attributes || {}).stringify_keys - if !initialized.key?('identifier') && Setting.sequential_project_identifiers? - self.identifier = Project.next_identifier - end - if !initialized.key?('is_public') - self.is_public = Setting.default_projects_public? - end - if !initialized.key?('enabled_module_names') - self.enabled_module_names = Setting.default_projects_modules - end - if !initialized.key?('trackers') && !initialized.key?('tracker_ids') - self.trackers = Tracker.sorted.all - end - end - - def identifier=(identifier) - super unless identifier_frozen? - end - - def identifier_frozen? - errors[:identifier].blank? && !(new_record? || identifier.blank?) - end - - # returns latest created projects - # non public projects will be returned only if user is a member of those - def self.latest(user=nil, count=5) - visible(user).find(:all, :limit => count, :order => "created_on DESC") - end - - # Returns true if the project is visible to +user+ or to the current user. - def visible?(user=User.current) - user.allowed_to?(:view_project, self) - end - - # Returns a SQL conditions string used to find all projects visible by the specified user. - # - # Examples: - # Project.visible_condition(admin) => "projects.status = 1" - # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))" - # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))" - def self.visible_condition(user, options={}) - allowed_to_condition(user, :view_project, options) - end - - # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+ - # - # Valid options: - # * :project => limit the condition to project - # * :with_subprojects => limit the condition to project and its subprojects - # * :member => limit the condition to the user projects - def self.allowed_to_condition(user, permission, options={}) - perm = Redmine::AccessControl.permission(permission) - base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}") - if perm && perm.project_module - # If the permission belongs to a project module, make sure the module is enabled - base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')" - end - if options[:project] - project_statement = "#{Project.table_name}.id = #{options[:project].id}" - project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects] - base_statement = "(#{project_statement}) AND (#{base_statement})" - end - - if user.admin? - base_statement - else - statement_by_role = {} - unless options[:member] - role = user.logged? ? Role.non_member : Role.anonymous - if role.allowed_to?(permission) - statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}" - end - end - if user.logged? - user.projects_by_role.each do |role, projects| - if role.allowed_to?(permission) && projects.any? - statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})" - end - end - end - if statement_by_role.empty? - "1=0" - else - if block_given? - statement_by_role.each do |role, statement| - if s = yield(role, user) - statement_by_role[role] = "(#{statement} AND (#{s}))" - end - end - end - "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))" - end - end - end - - # Returns the Systemwide and project specific activities - def activities(include_inactive=false) - if include_inactive - return all_activities - else - return active_activities - end - end - - # Will create a new Project specific Activity or update an existing one - # - # This will raise a ActiveRecord::Rollback if the TimeEntryActivity - # does not successfully save. - def update_or_create_time_entry_activity(id, activity_hash) - if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id') - self.create_time_entry_activity_if_needed(activity_hash) - else - activity = project.time_entry_activities.find_by_id(id.to_i) - activity.update_attributes(activity_hash) if activity - end - end - - # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity - # - # This will raise a ActiveRecord::Rollback if the TimeEntryActivity - # does not successfully save. - def create_time_entry_activity_if_needed(activity) - if activity['parent_id'] - - parent_activity = TimeEntryActivity.find(activity['parent_id']) - activity['name'] = parent_activity.name - activity['position'] = parent_activity.position - - if Enumeration.overridding_change?(activity, parent_activity) - project_activity = self.time_entry_activities.create(activity) - - if project_activity.new_record? - raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved" - else - self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id]) - end - end - end - end - - # Returns a :conditions SQL string that can be used to find the issues associated with this project. - # - # Examples: - # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))" - # project.project_condition(false) => "projects.id = 1" - def project_condition(with_subprojects) - cond = "#{Project.table_name}.id = #{id}" - cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects - cond - end - - def self.find(*args) - if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/) - project = find_by_identifier(*args) - raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil? - project - else - super - end - end - - def self.find_by_param(*args) - self.find(*args) - end - - def reload(*args) - @shared_versions = nil - @rolled_up_versions = nil - @rolled_up_trackers = nil - @all_issue_custom_fields = nil - @all_time_entry_custom_fields = nil - @to_param = nil - @allowed_parents = nil - @allowed_permissions = nil - @actions_allowed = nil - super - end - - def to_param - # id is used for projects with a numeric identifier (compatibility) - @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier) - end - - def active? - self.status == STATUS_ACTIVE - end - - def archived? - self.status == STATUS_ARCHIVED - end - - # Archives the project and its descendants - def archive - # Check that there is no issue of a non descendant project that is assigned - # to one of the project or descendant versions - v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten - if v_ids.any? && Issue.find(:first, :include => :project, - :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" + - " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids]) - return false - end - Project.transaction do - archive! - end - true - end - - # Unarchives the project - # All its ancestors must be active - def unarchive - return false if ancestors.detect {|a| !a.active?} - update_attribute :status, STATUS_ACTIVE - end - - def close - self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED - end - - def reopen - self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE - end - - # Returns an array of projects the project can be moved to - # by the current user - def allowed_parents - return @allowed_parents if @allowed_parents - @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects)) - @allowed_parents = @allowed_parents - self_and_descendants - if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?) - @allowed_parents << nil - end - unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent) - @allowed_parents << parent - end - @allowed_parents - end - - # Sets the parent of the project with authorization check - def set_allowed_parent!(p) - unless p.nil? || p.is_a?(Project) - if p.to_s.blank? - p = nil - else - p = Project.find_by_id(p) - return false unless p - end - end - if p.nil? - if !new_record? && allowed_parents.empty? - return false - end - elsif !allowed_parents.include?(p) - return false - end - set_parent!(p) - end - - # Sets the parent of the project - # Argument can be either a Project, a String, a Fixnum or nil - def set_parent!(p) - unless p.nil? || p.is_a?(Project) - if p.to_s.blank? - p = nil - else - p = Project.find_by_id(p) - return false unless p - end - end - if p == parent && !p.nil? - # Nothing to do - true - elsif p.nil? || (p.active? && move_possible?(p)) - set_or_update_position_under(p) - Issue.update_versions_from_hierarchy_change(self) - true - else - # Can not move to the given target - false - end - end - - # Recalculates all lft and rgt values based on project names - # Unlike Project.rebuild!, these values are recalculated even if the tree "looks" valid - # Used in BuildProjectsTree migration - def self.rebuild_tree! - transaction do - update_all "lft = NULL, rgt = NULL" - rebuild!(false) - end - end - - # Returns an array of the trackers used by the project and its active sub projects - def rolled_up_trackers - @rolled_up_trackers ||= - Tracker.find(:all, :joins => :projects, - :select => "DISTINCT #{Tracker.table_name}.*", - :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt], - :order => "#{Tracker.table_name}.position") - end - - # Closes open and locked project versions that are completed - def close_completed_versions - Version.transaction do - versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version| - if version.completed? - version.update_attribute(:status, 'closed') - end - end - end - end - - # Returns a scope of the Versions on subprojects - def rolled_up_versions - @rolled_up_versions ||= - Version.scoped(:include => :project, - :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt]) - end - - # Returns a scope of the Versions used by the project - def shared_versions - if new_record? - Version.scoped(:include => :project, - :conditions => "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND #{Version.table_name}.sharing = 'system'") - else - @shared_versions ||= begin - r = root? ? self : root - Version.scoped(:include => :project, - :conditions => "#{Project.table_name}.id = #{id}" + - " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" + - " #{Version.table_name}.sharing = 'system'" + - " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" + - " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + - " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + - "))") - end - end - end - - # Returns a hash of project users grouped by role - def users_by_role - members.find(:all, :include => [:user, :roles]).inject({}) do |h, m| - m.roles.each do |r| - h[r] ||= [] - h[r] << m.user - end - h - end - end - - # Deletes all project's members - def delete_all_members - me, mr = Member.table_name, MemberRole.table_name - connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})") - Member.delete_all(['project_id = ?', id]) - end - - # Users/groups issues can be assigned to - def assignable_users - assignable = Setting.issue_group_assignment? ? member_principals : members - assignable.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.principal}.sort - end - - # Returns the mail adresses of users that should be always notified on project events - def recipients - notified_users.collect {|user| user.mail} - end - - # Returns the users that should be notified on project events - def notified_users - # TODO: User part should be extracted to User#notify_about? - members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal} - end - - # Returns an array of all custom fields enabled for project issues - # (explictly associated custom fields and custom fields enabled for all projects) - def all_issue_custom_fields - @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort - end - - # Returns an array of all custom fields enabled for project time entries - # (explictly associated custom fields and custom fields enabled for all projects) - def all_time_entry_custom_fields - @all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort - end - - def project - self - end - - def <=>(project) - name.downcase <=> project.name.downcase - end - - def to_s - name - end - - # Returns a short description of the projects (first lines) - def short_description(length = 255) - description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description - end - - def css_classes - s = 'project' - s << ' root' if root? - s << ' child' if child? - s << (leaf? ? ' leaf' : ' parent') - unless active? - if archived? - s << ' archived' - else - s << ' closed' - end - end - s - end - - # The earliest start date of a project, based on it's issues and versions - def start_date - [ - issues.minimum('start_date'), - shared_versions.collect(&:effective_date), - shared_versions.collect(&:start_date) - ].flatten.compact.min - end - - # The latest due date of an issue or version - def due_date - [ - issues.maximum('due_date'), - shared_versions.collect(&:effective_date), - shared_versions.collect {|v| v.fixed_issues.maximum('due_date')} - ].flatten.compact.max - end - - def overdue? - active? && !due_date.nil? && (due_date < Date.today) - end - - # Returns the percent completed for this project, based on the - # progress on it's versions. - def completed_percent(options={:include_subprojects => false}) - if options.delete(:include_subprojects) - total = self_and_descendants.collect(&:completed_percent).sum - - total / self_and_descendants.count - else - if versions.count > 0 - total = versions.collect(&:completed_pourcent).sum - - total / versions.count - else - 100 - end - end - end - - # Return true if this project allows to do the specified action. - # action can be: - # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') - # * a permission Symbol (eg. :edit_project) - def allows_to?(action) - if archived? - # No action allowed on archived projects - return false - end - unless active? || Redmine::AccessControl.read_action?(action) - # No write action allowed on closed projects - return false - end - # No action allowed on disabled modules - if action.is_a? Hash - allowed_actions.include? "#{action[:controller]}/#{action[:action]}" - else - allowed_permissions.include? action - end - end - - def module_enabled?(module_name) - module_name = module_name.to_s - enabled_modules.detect {|m| m.name == module_name} - end - - def enabled_module_names=(module_names) - if module_names && module_names.is_a?(Array) - module_names = module_names.collect(&:to_s).reject(&:blank?) - self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)} - else - enabled_modules.clear - end - end - - # Returns an array of the enabled modules names - def enabled_module_names - enabled_modules.collect(&:name) - end - - # Enable a specific module - # - # Examples: - # project.enable_module!(:issue_tracking) - # project.enable_module!("issue_tracking") - def enable_module!(name) - enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name) - end - - # Disable a module if it exists - # - # Examples: - # project.disable_module!(:issue_tracking) - # project.disable_module!("issue_tracking") - # project.disable_module!(project.enabled_modules.first) - def disable_module!(target) - target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target) - target.destroy unless target.blank? - end - - safe_attributes 'name', - 'description', - 'homepage', - 'is_public', - 'identifier', - 'custom_field_values', - 'custom_fields', - 'tracker_ids', - 'issue_custom_field_ids' - - safe_attributes 'enabled_module_names', - :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) } - - # Returns an array of projects that are in this project's hierarchy - # - # Example: parents, children, siblings - def hierarchy - parents = project.self_and_ancestors || [] - descendants = project.descendants || [] - project_hierarchy = parents | descendants # Set union - end - - # Returns an auto-generated project identifier based on the last identifier used - def self.next_identifier - p = Project.find(:first, :order => 'created_on DESC') - p.nil? ? nil : p.identifier.to_s.succ - end - - # Copies and saves the Project instance based on the +project+. - # Duplicates the source project's: - # * Wiki - # * Versions - # * Categories - # * Issues - # * Members - # * Queries - # - # Accepts an +options+ argument to specify what to copy - # - # Examples: - # project.copy(1) # => copies everything - # project.copy(1, :only => 'members') # => copies members only - # project.copy(1, :only => ['members', 'versions']) # => copies members and versions - def copy(project, options={}) - project = project.is_a?(Project) ? project : Project.find(project) - - to_be_copied = %w(wiki versions issue_categories issues members queries boards) - to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil? - - Project.transaction do - if save - reload - to_be_copied.each do |name| - send "copy_#{name}", project - end - Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self) - save - end - end - end - - - # Copies +project+ and returns the new instance. This will not save - # the copy - def self.copy_from(project) - begin - project = project.is_a?(Project) ? project : Project.find(project) - if project - # clear unique attributes - attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt') - copy = Project.new(attributes) - copy.enabled_modules = project.enabled_modules - copy.trackers = project.trackers - copy.custom_values = project.custom_values.collect {|v| v.clone} - copy.issue_custom_fields = project.issue_custom_fields - return copy - else - return nil - end - rescue ActiveRecord::RecordNotFound - return nil - end - end - - # Yields the given block for each project with its level in the tree - def self.project_tree(projects, &block) - ancestors = [] - projects.sort_by(&:lft).each do |project| - while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) - ancestors.pop - end - yield project, ancestors.size - ancestors << project - end - end - - private - - # Copies wiki from +project+ - def copy_wiki(project) - # Check that the source project has a wiki first - unless project.wiki.nil? - wiki = self.wiki || Wiki.new - wiki.attributes = project.wiki.attributes.dup.except("id", "project_id") - wiki_pages_map = {} - project.wiki.pages.each do |page| - # Skip pages without content - next if page.content.nil? - new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on")) - new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id")) - new_wiki_page.content = new_wiki_content - wiki.pages << new_wiki_page - wiki_pages_map[page.id] = new_wiki_page - end - - self.wiki = wiki - wiki.save - # Reproduce page hierarchy - project.wiki.pages.each do |page| - if page.parent_id && wiki_pages_map[page.id] - wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id] - wiki_pages_map[page.id].save - end - end - end - end - - # Copies versions from +project+ - def copy_versions(project) - project.versions.each do |version| - new_version = Version.new - new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on") - self.versions << new_version - end - end - - # Copies issue categories from +project+ - def copy_issue_categories(project) - project.issue_categories.each do |issue_category| - new_issue_category = IssueCategory.new - new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id") - self.issue_categories << new_issue_category - end - end - - # Copies issues from +project+ - def copy_issues(project) - # Stores the source issue id as a key and the copied issues as the - # value. Used to map the two togeather for issue relations. - issues_map = {} - - # Store status and reopen locked/closed versions - version_statuses = versions.reject(&:open?).map {|version| [version, version.status]} - version_statuses.each do |version, status| - version.update_attribute :status, 'open' - end - - # Get issues sorted by root_id, lft so that parent issues - # get copied before their children - project.issues.find(:all, :order => 'root_id, lft').each do |issue| - new_issue = Issue.new - new_issue.copy_from(issue, :subtasks => false, :link => false) - new_issue.project = self - # Reassign fixed_versions by name, since names are unique per project - if issue.fixed_version && issue.fixed_version.project == project - new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name} - end - # Reassign the category by name, since names are unique per project - if issue.category - new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name} - end - # Parent issue - if issue.parent_id - if copied_parent = issues_map[issue.parent_id] - new_issue.parent_issue_id = copied_parent.id - end - end - - self.issues << new_issue - if new_issue.new_record? - logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info - else - issues_map[issue.id] = new_issue unless new_issue.new_record? - end - end - - # Restore locked/closed version statuses - version_statuses.each do |version, status| - version.update_attribute :status, status - end - - # Relations after in case issues related each other - project.issues.each do |issue| - new_issue = issues_map[issue.id] - unless new_issue - # Issue was not copied - next - end - - # Relations - issue.relations_from.each do |source_relation| - new_issue_relation = IssueRelation.new - new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id") - new_issue_relation.issue_to = issues_map[source_relation.issue_to_id] - if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations? - new_issue_relation.issue_to = source_relation.issue_to - end - new_issue.relations_from << new_issue_relation - end - - issue.relations_to.each do |source_relation| - new_issue_relation = IssueRelation.new - new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id") - new_issue_relation.issue_from = issues_map[source_relation.issue_from_id] - if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations? - new_issue_relation.issue_from = source_relation.issue_from - end - new_issue.relations_to << new_issue_relation - end - end - end - - # Copies members from +project+ - def copy_members(project) - # Copy users first, then groups to handle members with inherited and given roles - members_to_copy = [] - members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)} - members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)} - - members_to_copy.each do |member| - new_member = Member.new - new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on") - # only copy non inherited roles - # inherited roles will be added when copying the group membership - role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id) - next if role_ids.empty? - new_member.role_ids = role_ids - new_member.project = self - self.members << new_member - end - end - - # Copies queries from +project+ - def copy_queries(project) - project.queries.each do |query| - new_query = ::Query.new - new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria") - new_query.sort_criteria = query.sort_criteria if query.sort_criteria - new_query.project = self - new_query.user_id = query.user_id - self.queries << new_query - end - end - - # Copies boards from +project+ - def copy_boards(project) - project.boards.each do |board| - new_board = Board.new - new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id") - new_board.project = self - self.boards << new_board - end - end - - def allowed_permissions - @allowed_permissions ||= begin - module_names = enabled_modules.all(:select => :name).collect {|m| m.name} - Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name} - end - end - - def allowed_actions - @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten - end - - # Returns all the active Systemwide and project specific activities - def active_activities - overridden_activity_ids = self.time_entry_activities.collect(&:parent_id) - - if overridden_activity_ids.empty? - return TimeEntryActivity.shared.active - else - return system_activities_and_project_overrides - end - end - - # Returns all the Systemwide and project specific activities - # (inactive and active) - def all_activities - overridden_activity_ids = self.time_entry_activities.collect(&:parent_id) - - if overridden_activity_ids.empty? - return TimeEntryActivity.shared - else - return system_activities_and_project_overrides(true) - end - end - - # Returns the systemwide active activities merged with the project specific overrides - def system_activities_and_project_overrides(include_inactive=false) - if include_inactive - return TimeEntryActivity.shared. - find(:all, - :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) + - self.time_entry_activities - else - return TimeEntryActivity.shared.active. - find(:all, - :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) + - self.time_entry_activities.active - end - end - - # Archives subprojects recursively - def archive! - children.each do |subproject| - subproject.send :archive! - end - update_attribute :status, STATUS_ARCHIVED - end - - def update_position_under_parent - set_or_update_position_under(parent) - end - - # Inserts/moves the project so that target's children or root projects stay alphabetically sorted - def set_or_update_position_under(target_parent) - sibs = (target_parent.nil? ? self.class.roots : target_parent.children) - to_be_inserted_before = sibs.sort_by {|c| c.name.to_s.downcase}.detect {|c| c.name.to_s.downcase > name.to_s.downcase } - - if to_be_inserted_before - move_to_left_of(to_be_inserted_before) - elsif target_parent.nil? - if sibs.empty? - # move_to_root adds the project in first (ie. left) position - move_to_root - else - move_to_right_of(sibs.last) unless self == sibs.last - end - else - # move_to_child_of adds the project in last (ie.right) position - move_to_child_of(target_parent) - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a6de88a15e59e4df51a2d2faaac586d8b466d19f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a6/a6de88a15e59e4df51a2d2faaac586d8b466d19f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,12 @@ +class AddRoadmapPermission < ActiveRecord::Migration + # model removed + class Permission < ActiveRecord::Base; end + + def self.up + Permission.create :controller => "projects", :action => "roadmap", :description => "label_roadmap", :sort => 107, :is_public => true, :mail_option => 0, :mail_enabled => 0 + end + + def self.down + Permission.where("controller=? and action=?", 'projects', 'roadmap').first.destroy + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a6/a6ff77ff25dc544777092e624555824fdd530202.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a6/a6ff77ff25dc544777092e624555824fdd530202.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,15 @@ +

    + <%= link_to l(:label_reported_issues), + issues_path(:set_filter => 1, :status_id => '*', :author_id => 'me', :sort => 'updated_on:desc') %> + (<%= Issue.visible.where(:author_id => User.current.id).count %>) +

    + +<% reported_issues = issuesreportedbyme_items %> +<%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %> + +<% content_for :header_tags do %> +<%= auto_discovery_link_tag(:atom, + {:controller => 'issues', :action => 'index', :set_filter => 1, + :author_id => 'me', :format => 'atom', :key => User.current.rss_key}, + {:title => l(:label_reported_issues)}) %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a70d1149c6a4c4bb1eb4ec1a50adfed6edf09844.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a7/a70d1149c6a4c4bb1eb4ec1a50adfed6edf09844.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class VersionCustomField < CustomField + def type_name + :label_version_plural + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a71b998ee631f5527b99e3a0df2d62bbccac91fe.svn-base --- a/.svn/pristine/a7/a71b998ee631f5527b99e3a0df2d62bbccac91fe.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,269 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com -# -# 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. - -require 'redmine/scm/adapters/git_adapter' - -class Repository::Git < Repository - attr_protected :root_url - validates_presence_of :url - - def self.human_attribute_name(attribute_key_name, *args) - attr_name = attribute_key_name.to_s - if attr_name == "url" - attr_name = "path_to_repository" - end - super(attr_name, *args) - end - - def self.scm_adapter_class - Redmine::Scm::Adapters::GitAdapter - end - - def self.scm_name - 'Git' - end - - def report_last_commit - extra_report_last_commit - end - - def extra_report_last_commit - return false if extra_info.nil? - v = extra_info["extra_report_last_commit"] - return false if v.nil? - v.to_s != '0' - end - - def supports_directory_revisions? - true - end - - def supports_revision_graph? - true - end - - def repo_log_encoding - 'UTF-8' - end - - # Returns the identifier for the given git changeset - def self.changeset_identifier(changeset) - changeset.scmid - end - - # Returns the readable identifier for the given git changeset - def self.format_changeset_identifier(changeset) - changeset.revision[0, 8] - end - - def branches - scm.branches - end - - def tags - scm.tags - end - - def default_branch - scm.default_branch - rescue Exception => e - logger.error "git: error during get default branch: #{e.message}" - nil - end - - def find_changeset_by_name(name) - if name.present? - changesets.where(:revision => name.to_s).first || - changesets.where('scmid LIKE ?', "#{name}%").first - end - end - - def entries(path=nil, identifier=nil) - entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit) - load_entries_changesets(entries) - entries - end - - # With SCMs that have a sequential commit numbering, - # such as Subversion and Mercurial, - # Redmine is able to be clever and only fetch changesets - # going forward from the most recent one it knows about. - # - # However, Git does not have a sequential commit numbering. - # - # In order to fetch only new adding revisions, - # Redmine needs to save "heads". - # - # In Git and Mercurial, revisions are not in date order. - # Redmine Mercurial fixed issues. - # * Redmine Takes Too Long On Large Mercurial Repository - # http://www.redmine.org/issues/3449 - # * Sorting for changesets might go wrong on Mercurial repos - # http://www.redmine.org/issues/3567 - # - # Database revision column is text, so Redmine can not sort by revision. - # Mercurial has revision number, and revision number guarantees revision order. - # Redmine Mercurial model stored revisions ordered by database id to database. - # So, Redmine Mercurial model can use correct ordering revisions. - # - # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10" - # to get limited revisions from old to new. - # But, Git 1.7.3.4 does not support --reverse with -n or --skip. - # - # The repository can still be fully reloaded by calling #clear_changesets - # before fetching changesets (eg. for offline resync) - def fetch_changesets - scm_brs = branches - return if scm_brs.nil? || scm_brs.empty? - - h1 = extra_info || {} - h = h1.dup - repo_heads = scm_brs.map{ |br| br.scmid } - h["heads"] ||= [] - prev_db_heads = h["heads"].dup - if prev_db_heads.empty? - prev_db_heads += heads_from_branches_hash - end - return if prev_db_heads.sort == repo_heads.sort - - h["db_consistent"] ||= {} - if changesets.count == 0 - h["db_consistent"]["ordering"] = 1 - merge_extra_info(h) - self.save - elsif ! h["db_consistent"].has_key?("ordering") - h["db_consistent"]["ordering"] = 0 - merge_extra_info(h) - self.save - end - save_revisions(prev_db_heads, repo_heads) - end - - def save_revisions(prev_db_heads, repo_heads) - h = {} - opts = {} - opts[:reverse] = true - opts[:excludes] = prev_db_heads - opts[:includes] = repo_heads - - revisions = scm.revisions('', nil, nil, opts) - return if revisions.blank? - - # Make the search for existing revisions in the database in a more sufficient manner - # - # Git branch is the reference to the specific revision. - # Git can *delete* remote branch and *re-push* branch. - # - # $ git push remote :branch - # $ git push remote branch - # - # After deleting branch, revisions remain in repository until "git gc". - # On git 1.7.2.3, default pruning date is 2 weeks. - # So, "git log --not deleted_branch_head_revision" return code is 0. - # - # After re-pushing branch, "git log" returns revisions which are saved in database. - # So, Redmine needs to scan revisions and database every time. - # - # This is replacing the one-after-one queries. - # Find all revisions, that are in the database, and then remove them from the revision array. - # Then later we won't need any conditions for db existence. - # Query for several revisions at once, and remove them from the revisions array, if they are there. - # Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits). - # If there are no revisions (because the original code's algorithm filtered them), - # then this part will be stepped over. - # We make queries, just if there is any revision. - limit = 100 - offset = 0 - revisions_copy = revisions.clone # revisions will change - while offset < revisions_copy.size - recent_changesets_slice = changesets.find( - :all, - :conditions => [ - 'scmid IN (?)', - revisions_copy.slice(offset, limit).map{|x| x.scmid} - ] - ) - # Subtract revisions that redmine already knows about - recent_revisions = recent_changesets_slice.map{|c| c.scmid} - revisions.reject!{|r| recent_revisions.include?(r.scmid)} - offset += limit - end - - revisions.each do |rev| - transaction do - # There is no search in the db for this revision, because above we ensured, - # that it's not in the db. - save_revision(rev) - end - end - h["heads"] = repo_heads.dup - merge_extra_info(h) - self.save - end - private :save_revisions - - def save_revision(rev) - parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact - changeset = Changeset.create( - :repository => self, - :revision => rev.identifier, - :scmid => rev.scmid, - :committer => rev.author, - :committed_on => rev.time, - :comments => rev.message, - :parents => parents - ) - unless changeset.new_record? - rev.paths.each { |change| changeset.create_change(change) } - end - changeset - end - private :save_revision - - def heads_from_branches_hash - h1 = extra_info || {} - h = h1.dup - h["branches"] ||= {} - h['branches'].map{|br, hs| hs['last_scmid']} - end - - def latest_changesets(path,rev,limit=10) - revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) - return [] if revisions.nil? || revisions.empty? - - changesets.find( - :all, - :conditions => [ - "scmid IN (?)", - revisions.map!{|c| c.scmid} - ], - :order => 'committed_on DESC' - ) - end - - def clear_extra_info_of_changesets - return if extra_info.nil? - v = extra_info["extra_report_last_commit"] - write_attribute(:extra_info, nil) - h = {} - h["extra_report_last_commit"] = v - merge_extra_info(h) - self.save - end - private :clear_extra_info_of_changesets -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a721c0d1c7040afbf54df15fe3439fbbed48e0c9.svn-base --- a/.svn/pristine/a7/a721c0d1c7040afbf54df15fe3439fbbed48e0c9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingMyTest < ActionController::IntegrationTest - def test_my - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/my/account" }, - { :controller => 'my', :action => 'account' } - ) - end - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/my/account/destroy" }, - { :controller => 'my', :action => 'destroy' } - ) - end - assert_routing( - { :method => 'get', :path => "/my/page" }, - { :controller => 'my', :action => 'page' } - ) - assert_routing( - { :method => 'get', :path => "/my" }, - { :controller => 'my', :action => 'index' } - ) - assert_routing( - { :method => 'post', :path => "/my/reset_rss_key" }, - { :controller => 'my', :action => 'reset_rss_key' } - ) - assert_routing( - { :method => 'post', :path => "/my/reset_api_key" }, - { :controller => 'my', :action => 'reset_api_key' } - ) - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/my/password" }, - { :controller => 'my', :action => 'password' } - ) - end - assert_routing( - { :method => 'get', :path => "/my/page_layout" }, - { :controller => 'my', :action => 'page_layout' } - ) - assert_routing( - { :method => 'post', :path => "/my/add_block" }, - { :controller => 'my', :action => 'add_block' } - ) - assert_routing( - { :method => 'post', :path => "/my/remove_block" }, - { :controller => 'my', :action => 'remove_block' } - ) - assert_routing( - { :method => 'post', :path => "/my/order_blocks" }, - { :controller => 'my', :action => 'order_blocks' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a7307578244dc88b72a0f21959adfb5520767338.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a7/a7307578244dc88b72a0f21959adfb5520767338.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1096 @@ +en: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%m/%d/%Y" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%m/%d/%Y %I:%M %p" + time: "%I:%M %p" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "less than 1 second" + other: "less than %{count} seconds" + x_seconds: + one: "1 second" + other: "%{count} seconds" + less_than_x_minutes: + one: "less than a minute" + other: "less than %{count} minutes" + x_minutes: + one: "1 minute" + other: "%{count} minutes" + about_x_hours: + one: "about 1 hour" + other: "about %{count} hours" + x_hours: + one: "1 hour" + other: "%{count} hours" + x_days: + one: "1 day" + other: "%{count} days" + about_x_months: + one: "about 1 month" + other: "about %{count} months" + x_months: + one: "1 month" + other: "%{count} months" + about_x_years: + one: "about 1 year" + other: "about %{count} years" + over_x_years: + one: "over 1 year" + other: "over %{count} years" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "and" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "is not included in the list" + exclusion: "is reserved" + invalid: "is invalid" + confirmation: "doesn't match confirmation" + accepted: "must be accepted" + empty: "can't be empty" + blank: "can't be blank" + too_long: "is too long (maximum is %{count} characters)" + too_short: "is too short (minimum is %{count} characters)" + wrong_length: "is the wrong length (should be %{count} characters)" + taken: "has already been taken" + not_a_number: "is not a number" + not_a_date: "is not a valid date" + greater_than: "must be greater than %{count}" + greater_than_or_equal_to: "must be greater than or equal to %{count}" + equal_to: "must be equal to %{count}" + less_than: "must be less than %{count}" + less_than_or_equal_to: "must be less than or equal to %{count}" + odd: "must be odd" + even: "must be even" + greater_than_start_date: "must be greater than start date" + not_same_project: "doesn't belong to the same project" + circular_dependency: "This relation would create a circular dependency" + cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Please select + + general_text_No: 'No' + general_text_Yes: 'Yes' + general_text_no: 'no' + general_text_yes: 'yes' + general_lang_name: 'English' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_updated: Account was successfully updated. + notice_account_invalid_creditentials: Invalid user or password + notice_account_password_updated: Password was successfully updated. + notice_account_wrong_password: Wrong password + notice_account_register_done: Account was successfully created. An email containing the instructions to activate your account was sent to %{email}. + notice_account_unknown_email: Unknown user. + notice_account_not_activated_yet: You haven't activated your account yet. If you want to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. + notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. + notice_account_activated: Your account has been activated. You can now log in. + notice_successful_create: Successful creation. + notice_successful_update: Successful update. + notice_successful_delete: Successful deletion. + notice_successful_connection: Successful connection. + notice_file_not_found: The page you were trying to access doesn't exist or has been removed. + notice_locking_conflict: Data has been updated by another user. + notice_not_authorized: You are not authorized to access this page. + notice_not_authorized_archived_project: The project you're trying to access has been archived. + notice_email_sent: "An email was sent to %{value}" + notice_email_error: "An error occurred while sending mail (%{value})" + notice_feeds_access_key_reseted: Your Atom access key was reset. + notice_api_access_key_reseted: Your API access key was reset. + notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." + notice_account_pending: "Your account was created and is now pending administrator approval." + notice_default_data_loaded: Default configuration successfully loaded. + notice_unable_delete_version: Unable to delete version. + notice_unable_delete_time_entry: Unable to delete time log entry. + notice_issue_done_ratios_updated: Issue done ratios updated. + notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})" + notice_issue_successful_create: "Issue %{id} created." + notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it." + notice_account_deleted: "Your account has been permanently deleted." + notice_user_successful_create: "User %{id} created." + notice_new_password_must_be_different: The new password must be different from the current password + + error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" + error_scm_not_found: "The entry or revision was not found in the repository." + error_scm_command_failed: "An error occurred when trying to access the repository: %{value}" + error_scm_annotate: "The entry does not exist or cannot be annotated." + error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size." + error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' + error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' + error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' + error_can_not_delete_custom_field: Unable to delete custom field + error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted." + error_can_not_remove_role: "This role is in use and cannot be deleted." + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened' + error_can_not_archive_project: This project cannot be archived + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + error_unable_delete_issue_status: 'Unable to delete issue status' + error_unable_to_connect: "Unable to connect (%{value})" + error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})" + error_session_expired: "Your session has expired. Please login again." + warning_attachments_not_saved: "%{count} file(s) could not be saved." + + mail_subject_lost_password: "Your %{value} password" + mail_body_lost_password: 'To change your password, click on the following link:' + mail_subject_register: "Your %{value} account activation" + mail_body_register: 'To activate your account, click on the following link:' + mail_body_account_information_external: "You can use your %{value} account to log in." + mail_body_account_information: Your account information + mail_subject_account_activation_request: "%{value} account activation request" + mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:" + mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" + mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." + + field_name: Name + field_description: Description + field_summary: Summary + field_is_required: Required + field_firstname: First name + field_lastname: Last name + field_mail: Email + field_filename: File + field_filesize: Size + field_downloads: Downloads + field_author: Author + field_created_on: Created + field_updated_on: Updated + field_closed_on: Closed + field_field_format: Format + field_is_for_all: For all projects + field_possible_values: Possible values + field_regexp: Regular expression + field_min_length: Minimum length + field_max_length: Maximum length + field_value: Value + field_category: Category + field_title: Title + field_project: Project + field_issue: Issue + field_status: Status + field_notes: Notes + field_is_closed: Issue closed + field_is_default: Default value + field_tracker: Tracker + field_subject: Subject + field_due_date: Due date + field_assigned_to: Assignee + field_priority: Priority + field_fixed_version: Target version + field_user: User + field_principal: Principal + field_role: Role + field_homepage: Homepage + field_is_public: Public + field_parent: Subproject of + field_is_in_roadmap: Issues displayed in roadmap + field_login: Login + field_mail_notification: Email notifications + field_admin: Administrator + field_last_login_on: Last connection + field_language: Language + field_effective_date: Date + field_password: Password + field_new_password: New password + field_password_confirmation: Confirmation + field_version: Version + field_type: Type + field_host: Host + field_port: Port + field_account: Account + field_base_dn: Base DN + field_attr_login: Login attribute + field_attr_firstname: Firstname attribute + field_attr_lastname: Lastname attribute + field_attr_mail: Email attribute + field_onthefly: On-the-fly user creation + field_start_date: Start date + field_done_ratio: "% Done" + field_auth_source: Authentication mode + field_hide_mail: Hide my email address + field_comments: Comment + field_url: URL + field_start_page: Start page + field_subproject: Subproject + field_hours: Hours + field_activity: Activity + field_spent_on: Date + field_identifier: Identifier + field_is_filter: Used as a filter + field_issue_to: Related issue + field_delay: Delay + field_assignable: Issues can be assigned to this role + field_redirect_existing_links: Redirect existing links + field_estimated_hours: Estimated time + field_column_names: Columns + field_time_entries: Log time + field_time_zone: Time zone + field_searchable: Searchable + field_default_value: Default value + field_comments_sorting: Display comments + field_parent_title: Parent page + field_editable: Editable + field_watcher: Watcher + field_identity_url: OpenID URL + field_content: Content + field_group_by: Group results by + field_sharing: Sharing + field_parent_issue: Parent task + field_member_of_group: "Assignee's group" + field_assigned_to_role: "Assignee's role" + field_text: Text field + field_visible: Visible + field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" + field_issues_visibility: Issues visibility + field_is_private: Private + field_commit_logs_encoding: Commit messages encoding + field_scm_path_encoding: Path encoding + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvsroot: CVSROOT + field_cvs_module: Module + field_repository_is_default: Main repository + field_multiple: Multiple values + field_auth_source_ldap_filter: LDAP filter + field_core_fields: Standard fields + field_timeout: "Timeout (in seconds)" + field_board_parent: Parent forum + field_private_notes: Private notes + field_inherit_members: Inherit members + field_generate_password: Generate password + field_must_change_passwd: Must change password at next logon + + setting_app_title: Application title + setting_app_subtitle: Application subtitle + setting_welcome_text: Welcome text + setting_default_language: Default language + setting_login_required: Authentication required + setting_self_registration: Self-registration + setting_attachment_max_size: Maximum attachment size + setting_issues_export_limit: Issues export limit + setting_mail_from: Emission email address + setting_bcc_recipients: Blind carbon copy recipients (bcc) + setting_plain_text_mail: Plain text mail (no HTML) + setting_host_name: Host name and path + setting_text_formatting: Text formatting + setting_wiki_compression: Wiki history compression + setting_feeds_limit: Maximum number of items in Atom feeds + setting_default_projects_public: New projects are public by default + setting_autofetch_changesets: Fetch commits automatically + setting_sys_api_enabled: Enable WS for repository management + setting_commit_ref_keywords: Referencing keywords + setting_commit_fix_keywords: Fixing keywords + setting_autologin: Autologin + setting_date_format: Date format + setting_time_format: Time format + setting_cross_project_issue_relations: Allow cross-project issue relations + setting_cross_project_subtasks: Allow cross-project subtasks + setting_issue_list_default_columns: Default columns displayed on the issue list + setting_repositories_encodings: Attachments and repositories encodings + setting_emails_header: Email header + setting_emails_footer: Email footer + setting_protocol: Protocol + setting_per_page_options: Objects per page options + setting_user_format: Users display format + setting_activity_days_default: Days displayed on project activity + setting_display_subprojects_issues: Display subprojects issues on main projects by default + setting_enabled_scm: Enabled SCM + setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" + setting_mail_handler_api_enabled: Enable WS for incoming emails + setting_mail_handler_api_key: API key + setting_sequential_project_identifiers: Generate sequential project identifiers + setting_gravatar_enabled: Use Gravatar user icons + setting_gravatar_default: Default Gravatar image + setting_diff_max_lines_displayed: Maximum number of diff lines displayed + setting_file_max_size_displayed: Maximum size of text files displayed inline + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_openid: Allow OpenID login and registration + setting_password_min_length: Minimum password length + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + setting_default_projects_modules: Default enabled modules for new projects + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_field: Use the issue field + setting_issue_done_ratio_issue_status: Use the issue status + setting_start_of_week: Start calendars on + setting_rest_api_enabled: Enable REST web service + setting_cache_formatted_text: Cache formatted text + setting_default_notification_option: Default notification option + setting_commit_logtime_enabled: Enable time logging + setting_commit_logtime_activity_id: Activity for logged time + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + setting_issue_group_assignment: Allow issue assignment to groups + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + setting_unsubscribe: Allow users to delete their own account + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + setting_non_working_week_days: Non-working days + setting_jsonp_enabled: Enable JSONP support + setting_default_projects_tracker_ids: Default trackers for new projects + setting_mail_handler_excluded_filenames: Exclude attachments by name + + permission_add_project: Create project + permission_add_subprojects: Create subprojects + permission_edit_project: Edit project + permission_close_project: Close / reopen the project + permission_select_project_modules: Select project modules + permission_manage_members: Manage members + permission_manage_project_activities: Manage project activities + permission_manage_versions: Manage versions + permission_manage_categories: Manage issue categories + permission_view_issues: View Issues + permission_add_issues: Add issues + permission_edit_issues: Edit issues + permission_manage_issue_relations: Manage issue relations + permission_set_issues_private: Set issues public or private + permission_set_own_issues_private: Set own issues public or private + permission_add_issue_notes: Add notes + permission_edit_issue_notes: Edit notes + permission_edit_own_issue_notes: Edit own notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + permission_move_issues: Move issues + permission_delete_issues: Delete issues + permission_manage_public_queries: Manage public queries + permission_save_queries: Save queries + permission_view_gantt: View gantt chart + permission_view_calendar: View calendar + permission_view_issue_watchers: View watchers list + permission_add_issue_watchers: Add watchers + permission_delete_issue_watchers: Delete watchers + permission_log_time: Log spent time + permission_view_time_entries: View spent time + permission_edit_time_entries: Edit time logs + permission_edit_own_time_entries: Edit own time logs + permission_manage_news: Manage news + permission_comment_news: Comment news + permission_view_documents: View documents + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + permission_manage_files: Manage files + permission_view_files: View files + permission_manage_wiki: Manage wiki + permission_rename_wiki_pages: Rename wiki pages + permission_delete_wiki_pages: Delete wiki pages + permission_view_wiki_pages: View wiki + permission_view_wiki_edits: View wiki history + permission_edit_wiki_pages: Edit wiki pages + permission_delete_wiki_pages_attachments: Delete attachments + permission_protect_wiki_pages: Protect wiki pages + permission_manage_repository: Manage repository + permission_browse_repository: Browse repository + permission_view_changesets: View changesets + permission_commit_access: Commit access + permission_manage_boards: Manage forums + permission_view_messages: View messages + permission_add_messages: Post messages + permission_edit_messages: Edit messages + permission_edit_own_messages: Edit own messages + permission_delete_messages: Delete messages + permission_delete_own_messages: Delete own messages + permission_export_wiki_pages: Export wiki pages + permission_manage_subtasks: Manage subtasks + permission_manage_related_issues: Manage related issues + + project_module_issue_tracking: Issue tracking + project_module_time_tracking: Time tracking + project_module_news: News + project_module_documents: Documents + project_module_files: Files + project_module_wiki: Wiki + project_module_repository: Repository + project_module_boards: Forums + project_module_calendar: Calendar + project_module_gantt: Gantt + + label_user: User + label_user_plural: Users + label_user_new: New user + label_user_anonymous: Anonymous + label_project: Project + label_project_new: New project + label_project_plural: Projects + label_x_projects: + zero: no projects + one: 1 project + other: "%{count} projects" + label_project_all: All Projects + label_project_latest: Latest projects + label_issue: Issue + label_issue_new: New issue + label_issue_plural: Issues + label_issue_view_all: View all issues + label_issues_by: "Issues by %{value}" + label_issue_added: Issue added + label_issue_updated: Issue updated + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_document: Document + label_document_new: New document + label_document_plural: Documents + label_document_added: Document added + label_role: Role + label_role_plural: Roles + label_role_new: New role + label_role_and_permissions: Roles and permissions + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_member: Member + label_member_new: New member + label_member_plural: Members + label_tracker: Tracker + label_tracker_plural: Trackers + label_tracker_new: New tracker + label_workflow: Workflow + label_issue_status: Issue status + label_issue_status_plural: Issue statuses + label_issue_status_new: New status + label_issue_category: Issue category + label_issue_category_plural: Issue categories + label_issue_category_new: New category + label_custom_field: Custom field + label_custom_field_plural: Custom fields + label_custom_field_new: New custom field + label_enumerations: Enumerations + label_enumeration_new: New value + label_information: Information + label_information_plural: Information + label_please_login: Please log in + label_register: Register + label_login_with_open_id_option: or login with OpenID + label_password_lost: Lost password + label_home: Home + label_my_page: My page + label_my_account: My account + label_my_projects: My projects + label_my_page_block: My page block + label_administration: Administration + label_login: Sign in + label_logout: Sign out + label_help: Help + label_reported_issues: Reported issues + label_assigned_to_me_issues: Issues assigned to me + label_last_login: Last connection + label_registered_on: Registered on + label_activity: Activity + label_overall_activity: Overall activity + label_user_activity: "%{value}'s activity" + label_new: New + label_logged_as: Logged in as + label_environment: Environment + label_authentication: Authentication + label_auth_source: Authentication mode + label_auth_source_new: New authentication mode + label_auth_source_plural: Authentication modes + label_subproject_plural: Subprojects + label_subproject_new: New subproject + label_and_its_subprojects: "%{value} and its subprojects" + label_min_max_length: Min - Max length + label_list: List + label_date: Date + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: Text + label_text: Long text + label_attribute: Attribute + label_attribute_plural: Attributes + label_no_data: No data to display + label_change_status: Change status + label_history: History + label_attachment: File + label_attachment_new: New file + label_attachment_delete: Delete file + label_attachment_plural: Files + label_file_added: File added + label_report: Report + label_report_plural: Reports + label_news: News + label_news_new: Add news + label_news_plural: News + label_news_latest: Latest news + label_news_view_all: View all news + label_news_added: News added + label_news_comment_added: Comment added to a news + label_settings: Settings + label_overview: Overview + label_version: Version + label_version_new: New version + label_version_plural: Versions + label_close_versions: Close completed versions + label_confirmation: Confirmation + label_export_to: 'Also available in:' + label_read: Read... + label_public_projects: Public projects + label_open_issues: open + label_open_issues_plural: open + label_closed_issues: closed + label_closed_issues_plural: closed + label_x_open_issues_abbr_on_total: + zero: 0 open / %{total} + one: 1 open / %{total} + other: "%{count} open / %{total}" + label_x_open_issues_abbr: + zero: 0 open + one: 1 open + other: "%{count} open" + label_x_closed_issues_abbr: + zero: 0 closed + one: 1 closed + other: "%{count} closed" + label_x_issues: + zero: 0 issues + one: 1 issue + other: "%{count} issues" + label_total: Total + label_total_time: Total time + label_permissions: Permissions + label_current_status: Current status + label_new_statuses_allowed: New statuses allowed + label_all: all + label_any: any + label_none: none + label_nobody: nobody + label_next: Next + label_previous: Previous + label_used_by: Used by + label_details: Details + label_add_note: Add a note + label_per_page: Per page + label_calendar: Calendar + label_months_from: months from + label_gantt: Gantt + label_internal: Internal + label_last_changes: "last %{count} changes" + label_change_view_all: View all changes + label_personalize_page: Personalize this page + label_comment: Comment + label_comment_plural: Comments + label_x_comments: + zero: no comments + one: 1 comment + other: "%{count} comments" + label_comment_add: Add a comment + label_comment_added: Comment added + label_comment_delete: Delete comments + label_query: Custom query + label_query_plural: Custom queries + label_query_new: New query + label_my_queries: My custom queries + label_filter_add: Add filter + label_filter_plural: Filters + label_equals: is + label_not_equals: is not + label_in_less_than: in less than + label_in_more_than: in more than + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_between: between + label_in: in + label_today: today + label_all_time: all time + label_yesterday: yesterday + label_this_week: this week + label_last_week: last week + label_last_n_weeks: "last %{count} weeks" + label_last_n_days: "last %{count} days" + label_this_month: this month + label_last_month: last month + label_this_year: this year + label_date_range: Date range + label_less_than_ago: less than days ago + label_more_than_ago: more than days ago + label_ago: days ago + label_contains: contains + label_not_contains: doesn't contain + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + label_no_issues_in_project: no issues in project + label_day_plural: days + label_repository: Repository + label_repository_new: New repository + label_repository_plural: Repositories + label_browse: Browse + label_branch: Branch + label_tag: Tag + label_revision: Revision + label_revision_plural: Revisions + label_revision_id: "Revision %{value}" + label_associated_revisions: Associated revisions + label_added: added + label_modified: modified + label_copied: copied + label_renamed: renamed + label_deleted: deleted + label_latest_revision: Latest revision + label_latest_revision_plural: Latest revisions + label_view_revisions: View revisions + label_view_all_revisions: View all revisions + label_max_size: Maximum size + label_sort_highest: Move to top + label_sort_higher: Move up + label_sort_lower: Move down + label_sort_lowest: Move to bottom + label_roadmap: Roadmap + label_roadmap_due_in: "Due in %{value}" + label_roadmap_overdue: "%{value} late" + label_roadmap_no_issues: No issues for this version + label_search: Search + label_result_plural: Results + label_all_words: All words + label_wiki: Wiki + label_wiki_edit: Wiki edit + label_wiki_edit_plural: Wiki edits + label_wiki_page: Wiki page + label_wiki_page_plural: Wiki pages + label_index_by_title: Index by title + label_index_by_date: Index by date + label_current_version: Current version + label_preview: Preview + label_feed_plural: Feeds + label_changes_details: Details of all changes + label_issue_tracking: Issue tracking + label_spent_time: Spent time + label_overall_spent_time: Overall spent time + label_f_hour: "%{value} hour" + label_f_hour_plural: "%{value} hours" + label_time_tracking: Time tracking + label_change_plural: Changes + label_statistics: Statistics + label_commits_per_month: Commits per month + label_commits_per_author: Commits per author + label_diff: diff + label_view_diff: View differences + label_diff_inline: inline + label_diff_side_by_side: side by side + label_options: Options + label_copy_workflow_from: Copy workflow from + label_permissions_report: Permissions report + label_watched_issues: Watched issues + label_related_issues: Related issues + label_applied_status: Applied status + label_loading: Loading... + label_relation_new: New relation + label_relation_delete: Delete relation + label_relates_to: Related to + label_duplicates: Duplicates + label_duplicated_by: Duplicated by + label_blocks: Blocks + label_blocked_by: Blocked by + label_precedes: Precedes + label_follows: Follows + label_copied_to: Copied to + label_copied_from: Copied from + label_end_to_start: end to start + label_end_to_end: end to end + label_start_to_start: start to start + label_start_to_end: start to end + label_stay_logged_in: Stay logged in + label_disabled: disabled + label_show_completed_versions: Show completed versions + label_me: me + label_board: Forum + label_board_new: New forum + label_board_plural: Forums + label_board_locked: Locked + label_board_sticky: Sticky + label_topic_plural: Topics + label_message_plural: Messages + label_message_last: Last message + label_message_new: New message + label_message_posted: Message added + label_reply_plural: Replies + label_send_information: Send account information to the user + label_year: Year + label_month: Month + label_week: Week + label_date_from: From + label_date_to: To + label_language_based: Based on user's language + label_sort_by: "Sort by %{value}" + label_send_test_email: Send a test email + label_feeds_access_key: Atom access key + label_missing_feeds_access_key: Missing a Atom access key + label_feeds_access_key_created_on: "Atom access key created %{value} ago" + label_module_plural: Modules + label_added_time_by: "Added by %{author} %{age} ago" + label_updated_time_by: "Updated by %{author} %{age} ago" + label_updated_time: "Updated %{value} ago" + label_jump_to_a_project: Jump to a project... + label_file_plural: Files + label_changeset_plural: Changesets + label_default_columns: Default columns + label_no_change_option: (No change) + label_bulk_edit_selected_issues: Bulk edit selected issues + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + label_theme: Theme + label_default: Default + label_search_titles_only: Search titles only + label_user_mail_option_all: "For any event on all my projects" + label_user_mail_option_selected: "For any event on the selected projects only..." + label_user_mail_option_none: "No events" + label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in" + label_user_mail_option_only_assigned: "Only for things I am assigned to" + label_user_mail_option_only_owner: "Only for things I am the owner of" + label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" + label_registration_activation_by_email: account activation by email + label_registration_manual_activation: manual account activation + label_registration_automatic_activation: automatic account activation + label_display_per_page: "Per page: %{value}" + label_age: Age + label_change_properties: Change properties + label_general: General + label_more: More + label_scm: SCM + label_plugins: Plugins + label_ldap_authentication: LDAP authentication + label_downloads_abbr: D/L + label_optional_description: Optional description + label_add_another_file: Add another file + label_preferences: Preferences + label_chronological_order: In chronological order + label_reverse_chronological_order: In reverse chronological order + label_planning: Planning + label_incoming_emails: Incoming emails + label_generate_key: Generate a key + label_issue_watchers: Watchers + label_example: Example + label_display: Display + label_sort: Sort + label_ascending: Ascending + label_descending: Descending + label_date_from_to: From %{start} to %{end} + label_wiki_content_added: Wiki page added + label_wiki_content_updated: Wiki page updated + label_group: Group + label_group_plural: Groups + label_group_new: New group + label_time_entry_plural: Spent time + label_version_sharing_none: Not shared + label_version_sharing_descendants: With subprojects + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_tree: With project tree + label_version_sharing_system: With all projects + label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Source + label_copy_target: Target + label_copy_same_as_target: Same as target + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_api_access_key: API access key + label_missing_api_access_key: Missing an API access key + label_api_access_key_created_on: "API access key created %{value} ago" + label_profile: Profile + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_issues_visibility_all: All issues + label_issues_visibility_public: All non private issues + label_issues_visibility_own: Issues created by or assigned to the user + label_git_report_last_commit: Report last commit for files and directories + label_parent_revision: Parent + label_child_revision: Child + label_export_options: "%{export_format} export options" + label_copy_attachments: Copy attachments + label_copy_subtasks: Copy subtasks + label_item_position: "%{position} of %{count}" + label_completed_versions: Completed versions + label_search_for_watchers: Search for watchers to add + label_session_expiration: Session expiration + label_show_closed_projects: View closed projects + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + label_hidden: Hidden + label_attribute_of_project: "Project's %{name}" + label_attribute_of_issue: "Issue's %{name}" + label_attribute_of_author: "Author's %{name}" + label_attribute_of_assigned_to: "Assignee's %{name}" + label_attribute_of_user: "User's %{name}" + label_attribute_of_fixed_version: "Target version's %{name}" + label_cross_project_descendants: With subprojects + label_cross_project_tree: With project tree + label_cross_project_hierarchy: With project hierarchy + label_cross_project_system: With all projects + label_gantt_progress_line: Progress line + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + + button_login: Login + button_submit: Submit + button_save: Save + button_check_all: Check all + button_uncheck_all: Uncheck all + button_collapse_all: Collapse all + button_expand_all: Expand all + button_delete: Delete + button_create: Create + button_create_and_continue: Create and continue + button_test: Test + button_edit: Edit + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + button_add: Add + button_change: Change + button_apply: Apply + button_clear: Clear + button_lock: Lock + button_unlock: Unlock + button_download: Download + button_list: List + button_view: View + button_move: Move + button_move_and_follow: Move and follow + button_back: Back + button_cancel: Cancel + button_activate: Activate + button_sort: Sort + button_log_time: Log time + button_rollback: Rollback to this version + button_watch: Watch + button_unwatch: Unwatch + button_reply: Reply + button_archive: Archive + button_unarchive: Unarchive + button_reset: Reset + button_rename: Rename + button_change_password: Change password + button_copy: Copy + button_copy_and_follow: Copy and follow + button_annotate: Annotate + button_update: Update + button_configure: Configure + button_quote: Quote + button_duplicate: Duplicate + button_show: Show + button_hide: Hide + button_edit_section: Edit this section + button_export: Export + button_delete_my_account: Delete my account + button_close: Close + button_reopen: Reopen + + status_active: active + status_registered: registered + status_locked: locked + + project_status_active: active + project_status_closed: closed + project_status_archived: archived + + version_status_open: open + version_status_locked: locked + version_status_closed: closed + + field_active: Active + + text_select_mail_notifications: Select actions for which email notifications should be sent. + text_regexp_info: eg. ^[A-Z0-9]+$ + text_min_max_length_info: 0 means no restriction + text_project_destroy_confirmation: Are you sure you want to delete this project and related data? + text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." + text_workflow_edit: Select a role and a tracker to edit the workflow + text_are_you_sure: Are you sure? + text_journal_changed: "%{label} changed from %{old} to %{new}" + text_journal_changed_no_detail: "%{label} updated" + text_journal_set_to: "%{label} set to %{value}" + text_journal_deleted: "%{label} deleted (%{old})" + text_journal_added: "%{label} %{value} added" + text_tip_issue_begin_day: issue beginning this day + text_tip_issue_end_day: issue ending this day + text_tip_issue_begin_end_day: issue beginning and ending this day + text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed, must start with a lower case letter.
    Once saved, the identifier cannot be changed.' + text_caracters_maximum: "%{count} characters maximum." + text_caracters_minimum: "Must be at least %{count} characters long." + text_length_between: "Length between %{min} and %{max} characters." + text_tracker_no_workflow: No workflow defined for this tracker + text_unallowed_characters: Unallowed characters + text_comma_separated: Multiple values allowed (comma separated). + text_line_separated: Multiple values allowed (one line for each value). + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_issue_added: "Issue %{id} has been reported by %{author}." + text_issue_updated: "Issue %{id} has been updated by %{author}." + text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? + text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?" + text_issue_category_destroy_assignments: Remove category assignments + text_issue_category_reassign_to: Reassign issues to this category + text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." + text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." + text_load_default_configuration: Load the default configuration + text_status_changed_by_changeset: "Applied in changeset %{value}." + text_time_logged_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' + text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)." + text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?' + text_select_project_modules: 'Select modules to enable for this project:' + text_default_administrator_account_changed: Default administrator account changed + text_file_repository_writable: Attachments directory writable + text_plugin_assets_writable: Plugin assets directory writable + text_rmagick_available: RMagick available (optional) + text_convert_available: ImageMagick convert available (optional) + text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" + text_destroy_time_entries: Delete reported hours + text_assign_time_entries_to_project: Assign reported hours to the project + text_reassign_time_entries: 'Reassign reported hours to this issue:' + text_user_wrote: "%{value} wrote:" + text_enumeration_destroy_question: "%{count} objects are assigned to this value." + text_enumeration_category_reassign_to: 'Reassign them to this value:' + text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." + text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." + text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' + text_custom_field_possible_values_info: 'One line for each value' + text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" + text_wiki_page_nullify_children: "Keep child pages as root pages" + text_wiki_page_destroy_children: "Delete child pages and all their descendants" + text_wiki_page_reassign_children: "Reassign child pages to this parent page" + text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" + text_zoom_in: Zoom in + text_zoom_out: Zoom out + text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." + text_scm_path_encoding_note: "Default: UTF-8" + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" + text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" + text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" + text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it." + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + text_project_closed: This project is closed and read-only. + text_turning_multiple_off: "If you disable multiple values, multiple values will be removed in order to preserve only one value per item." + + default_role_manager: Manager + default_role_developer: Developer + default_role_reporter: Reporter + default_tracker_bug: Bug + default_tracker_feature: Feature + default_tracker_support: Support + default_issue_status_new: New + default_issue_status_in_progress: In Progress + default_issue_status_resolved: Resolved + default_issue_status_feedback: Feedback + default_issue_status_closed: Closed + default_issue_status_rejected: Rejected + default_doc_category_user: User documentation + default_doc_category_tech: Technical documentation + default_priority_low: Low + default_priority_normal: Normal + default_priority_high: High + default_priority_urgent: Urgent + default_priority_immediate: Immediate + default_activity_design: Design + default_activity_development: Development + + enumeration_issue_priorities: Issue priorities + enumeration_doc_categories: Document categories + enumeration_activities: Activities (time tracking) + enumeration_system_activity: System Activity + description_filter: Filter + description_search: Searchfield + description_choose_project: Projects + description_project_scope: Search scope + description_notes: Notes + description_message_content: Message content + description_query_sort_criteria_attribute: Sort attribute + description_query_sort_criteria_direction: Sort direction + description_user_mail_notification: Mail notification settings + description_available_columns: Available Columns + description_selected_columns: Selected Columns + description_all_columns: All Columns + description_issue_category_reassign: Choose issue category + description_wiki_subpages_reassign: Choose new parent page + description_date_range_list: Choose range from list + description_date_range_interval: Choose range by selecting start and end date + description_date_from: Enter start date + description_date_to: Enter end date + text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed.' diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a731233f0d7292a719e7077c91a8ab7c210b5a2a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a7/a731233f0d7292a719e7077c91a8ab7c210b5a2a.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,34 @@ +
    +<%= link_to l(:label_role_new), new_role_path, :class => 'icon icon-add' %> +<%= link_to l(:label_permissions_report), permissions_roles_path, :class => 'icon icon-summary' %> +
    + +

    <%=l(:label_role_plural)%>

    + + + + + + + + +<% for role in @roles %> + "> + + + + +<% end %> + +
    <%=l(:label_role)%><%=l(:button_sort)%>
    <%= content_tag(role.builtin? ? 'em' : 'span', link_to(h(role.name), edit_role_path(role))) %> + <% unless role.builtin? %> + <%= reorder_links('role', {:action => 'update', :id => role}, :put) %> + <% end %> + + <%= link_to l(:button_copy), new_role_path(:copy => role), :class => 'icon icon-copy' %> + <%= delete_link role_path(role) unless role.builtin? %> +
    + +

    <%= pagination_links_full @role_pages %>

    + +<% html_title(l(:label_role_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a781bd953d0066a0cb212d6ea0e21fdfc9eb41c7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a7/a781bd953d0066a0cb212d6ea0e21fdfc9eb41c7.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,846 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :issue_relations, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :attachments + + def setup + Setting.rest_api_enabled = '1' + end + + context "/issues" do + # Use a private project to make sure auth is really working and not just + # only showing public issues. + should_allow_api_authentication(:get, "/projects/private-child/issues.xml") + + should "contain metadata" do + get '/issues.xml' + + assert_tag :tag => 'issues', + :attributes => { + :type => 'array', + :total_count => assigns(:issue_count), + :limit => 25, + :offset => 0 + } + end + + context "with offset and limit" do + should "use the params" do + get '/issues.xml?offset=2&limit=3' + + assert_equal 3, assigns(:limit) + assert_equal 2, assigns(:offset) + assert_tag :tag => 'issues', :children => {:count => 3, :only => {:tag => 'issue'}} + end + end + + context "with nometa param" do + should "not contain metadata" do + get '/issues.xml?nometa=1' + + assert_tag :tag => 'issues', + :attributes => { + :type => 'array', + :total_count => nil, + :limit => nil, + :offset => nil + } + end + end + + context "with nometa header" do + should "not contain metadata" do + get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'} + + assert_tag :tag => 'issues', + :attributes => { + :type => 'array', + :total_count => nil, + :limit => nil, + :offset => nil + } + end + end + + context "with relations" do + should "display relations" do + get '/issues.xml?include=relations' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag 'relations', + :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '3'}}, + :children => {:count => 1}, + :child => { + :tag => 'relation', + :attributes => {:id => '2', :issue_id => '2', :issue_to_id => '3', + :relation_type => 'relates'} + } + assert_tag 'relations', + :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '1'}}, + :children => {:count => 0} + end + end + + context "with invalid query params" do + should "return errors" do + get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}} + + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => "Start date can't be blank"} + end + end + + context "with custom field filter" do + should "show only issues with the custom field value" do + get '/issues.xml', + {:set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, + :v => {:cf_1 => ['MySQL']}} + expected_ids = Issue.visible. + joins(:custom_values). + where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id) + assert_select 'issues > issue > id', :count => expected_ids.count do |ids| + ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } + end + end + end + + context "with custom field filter (shorthand method)" do + should "show only issues with the custom field value" do + get '/issues.xml', { :cf_1 => 'MySQL' } + + expected_ids = Issue.visible. + joins(:custom_values). + where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id) + + assert_select 'issues > issue > id', :count => expected_ids.count do |ids| + ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } + end + end + end + end + + context "/index.json" do + should_allow_api_authentication(:get, "/projects/private-child/issues.json") + end + + context "/index.xml with filter" do + should "show only issues with the status_id" do + get '/issues.xml?status_id=5' + + expected_ids = Issue.visible.where(:status_id => 5).map(&:id) + + assert_select 'issues > issue > id', :count => expected_ids.count do |ids| + ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } + end + end + end + + context "/index.json with filter" do + should "show only issues with the status_id" do + get '/issues.json?status_id=5' + + json = ActiveSupport::JSON.decode(response.body) + status_ids_used = json['issues'].collect {|j| j['status']['id'] } + assert_equal 3, status_ids_used.length + assert status_ids_used.all? {|id| id == 5 } + end + + end + + # Issue 6 is on a private project + context "/issues/6.xml" do + should_allow_api_authentication(:get, "/issues/6.xml") + end + + context "/issues/6.json" do + should_allow_api_authentication(:get, "/issues/6.json") + end + + context "GET /issues/:id" do + context "with journals" do + context ".xml" do + should "display journals" do + get '/issues/1.xml?include=journals' + + assert_tag :tag => 'issue', + :child => { + :tag => 'journals', + :attributes => { :type => 'array' }, + :child => { + :tag => 'journal', + :attributes => { :id => '1'}, + :child => { + :tag => 'details', + :attributes => { :type => 'array' }, + :child => { + :tag => 'detail', + :attributes => { :name => 'status_id' }, + :child => { + :tag => 'old_value', + :content => '1', + :sibling => { + :tag => 'new_value', + :content => '2' + } + } + } + } + } + } + end + end + end + + context "with custom fields" do + context ".xml" do + should "display custom fields" do + get '/issues/3.xml' + + assert_tag :tag => 'issue', + :child => { + :tag => 'custom_fields', + :attributes => { :type => 'array' }, + :child => { + :tag => 'custom_field', + :attributes => { :id => '1'}, + :child => { + :tag => 'value', + :content => 'MySQL' + } + } + } + + assert_nothing_raised do + Hash.from_xml(response.body).to_xml + end + end + end + end + + context "with multi custom fields" do + setup do + field = CustomField.find(1) + field.update_attribute :multiple, true + issue = Issue.find(3) + issue.custom_field_values = {1 => ['MySQL', 'Oracle']} + issue.save! + end + + context ".xml" do + should "display custom fields" do + get '/issues/3.xml' + assert_response :success + assert_tag :tag => 'issue', + :child => { + :tag => 'custom_fields', + :attributes => { :type => 'array' }, + :child => { + :tag => 'custom_field', + :attributes => { :id => '1'}, + :child => { + :tag => 'value', + :attributes => { :type => 'array' }, + :children => { :count => 2 } + } + } + } + + xml = Hash.from_xml(response.body) + custom_fields = xml['issue']['custom_fields'] + assert_kind_of Array, custom_fields + field = custom_fields.detect {|f| f['id'] == '1'} + assert_kind_of Hash, field + assert_equal ['MySQL', 'Oracle'], field['value'].sort + end + end + + context ".json" do + should "display custom fields" do + get '/issues/3.json' + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + custom_fields = json['issue']['custom_fields'] + assert_kind_of Array, custom_fields + field = custom_fields.detect {|f| f['id'] == 1} + assert_kind_of Hash, field + assert_equal ['MySQL', 'Oracle'], field['value'].sort + end + end + end + + context "with empty value for multi custom field" do + setup do + field = CustomField.find(1) + field.update_attribute :multiple, true + issue = Issue.find(3) + issue.custom_field_values = {1 => ['']} + issue.save! + end + + context ".xml" do + should "display custom fields" do + get '/issues/3.xml' + assert_response :success + assert_tag :tag => 'issue', + :child => { + :tag => 'custom_fields', + :attributes => { :type => 'array' }, + :child => { + :tag => 'custom_field', + :attributes => { :id => '1'}, + :child => { + :tag => 'value', + :attributes => { :type => 'array' }, + :children => { :count => 0 } + } + } + } + + xml = Hash.from_xml(response.body) + custom_fields = xml['issue']['custom_fields'] + assert_kind_of Array, custom_fields + field = custom_fields.detect {|f| f['id'] == '1'} + assert_kind_of Hash, field + assert_equal [], field['value'] + end + end + + context ".json" do + should "display custom fields" do + get '/issues/3.json' + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + custom_fields = json['issue']['custom_fields'] + assert_kind_of Array, custom_fields + field = custom_fields.detect {|f| f['id'] == 1} + assert_kind_of Hash, field + assert_equal [], field['value'].sort + end + end + end + + context "with attachments" do + context ".xml" do + should "display attachments" do + get '/issues/3.xml?include=attachments' + + assert_tag :tag => 'issue', + :child => { + :tag => 'attachments', + :children => {:count => 5}, + :child => { + :tag => 'attachment', + :child => { + :tag => 'filename', + :content => 'source.rb', + :sibling => { + :tag => 'content_url', + :content => 'http://www.example.com/attachments/download/4/source.rb' + } + } + } + } + end + end + end + + context "with subtasks" do + setup do + @c1 = Issue.create!( + :status_id => 1, :subject => "child c1", + :tracker_id => 1, :project_id => 1, :author_id => 1, + :parent_issue_id => 1 + ) + @c2 = Issue.create!( + :status_id => 1, :subject => "child c2", + :tracker_id => 1, :project_id => 1, :author_id => 1, + :parent_issue_id => 1 + ) + @c3 = Issue.create!( + :status_id => 1, :subject => "child c3", + :tracker_id => 1, :project_id => 1, :author_id => 1, + :parent_issue_id => @c1.id + ) + end + + context ".xml" do + should "display children" do + get '/issues/1.xml?include=children' + + assert_tag :tag => 'issue', + :child => { + :tag => 'children', + :children => {:count => 2}, + :child => { + :tag => 'issue', + :attributes => {:id => @c1.id.to_s}, + :child => { + :tag => 'subject', + :content => 'child c1', + :sibling => { + :tag => 'children', + :children => {:count => 1}, + :child => { + :tag => 'issue', + :attributes => {:id => @c3.id.to_s} + } + } + } + } + } + end + + context ".json" do + should "display children" do + get '/issues/1.json?include=children' + + json = ActiveSupport::JSON.decode(response.body) + assert_equal([ + { + 'id' => @c1.id, 'subject' => 'child c1', 'tracker' => {'id' => 1, 'name' => 'Bug'}, + 'children' => [{'id' => @c3.id, 'subject' => 'child c3', + 'tracker' => {'id' => 1, 'name' => 'Bug'} }] + }, + { 'id' => @c2.id, 'subject' => 'child c2', 'tracker' => {'id' => 1, 'name' => 'Bug'} } + ], + json['issue']['children']) + end + end + end + end + end + + test "GET /issues/:id.xml?include=watchers should include watchers" do + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + + get '/issues/1.xml?include=watchers', {}, credentials('jsmith') + + assert_response :ok + assert_equal 'application/xml', response.content_type + assert_select 'issue' do + assert_select 'watchers', Issue.find(1).watchers.count + assert_select 'watchers' do + assert_select 'user[id=3]' + end + end + end + + context "POST /issues.xml" do + should_allow_api_authentication( + :post, + '/issues.xml', + {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, + {:success_code => :created} + ) + should "create an issue with the attributes" do + assert_difference('Issue.count') do + post '/issues.xml', + {:issue => {:project_id => 1, :subject => 'API test', + :tracker_id => 2, :status_id => 3}}, credentials('jsmith') + end + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.project_id + assert_equal 2, issue.tracker_id + assert_equal 3, issue.status_id + assert_equal 'API test', issue.subject + + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s} + end + end + + test "POST /issues.xml with watcher_user_ids should create issue with watchers" do + assert_difference('Issue.count') do + post '/issues.xml', + {:issue => {:project_id => 1, :subject => 'Watchers', + :tracker_id => 2, :status_id => 3, :watcher_user_ids => [3, 1]}}, credentials('jsmith') + assert_response :created + end + issue = Issue.order('id desc').first + assert_equal 2, issue.watchers.size + assert_equal [1, 3], issue.watcher_user_ids.sort + end + + context "POST /issues.xml with failure" do + should "have an errors tag" do + assert_no_difference('Issue.count') do + post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith') + end + + assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} + end + end + + context "POST /issues.json" do + should_allow_api_authentication(:post, + '/issues.json', + {:issue => {:project_id => 1, :subject => 'API test', + :tracker_id => 2, :status_id => 3}}, + {:success_code => :created}) + + should "create an issue with the attributes" do + assert_difference('Issue.count') do + post '/issues.json', + {:issue => {:project_id => 1, :subject => 'API test', + :tracker_id => 2, :status_id => 3}}, + credentials('jsmith') + end + + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.project_id + assert_equal 2, issue.tracker_id + assert_equal 3, issue.status_id + assert_equal 'API test', issue.subject + end + + end + + context "POST /issues.json with failure" do + should "have an errors element" do + assert_no_difference('Issue.count') do + post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith') + end + + json = ActiveSupport::JSON.decode(response.body) + assert json['errors'].include?("Subject can't be blank") + end + end + + # Issue 6 is on a private project + context "PUT /issues/6.xml" do + setup do + @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}} + end + + should_allow_api_authentication(:put, + '/issues/6.xml', + {:issue => {:subject => 'API update', :notes => 'A new note'}}, + {:success_code => :ok}) + + should "not create a new issue" do + assert_no_difference('Issue.count') do + put '/issues/6.xml', @parameters, credentials('jsmith') + end + end + + should "create a new journal" do + assert_difference('Journal.count') do + put '/issues/6.xml', @parameters, credentials('jsmith') + end + end + + should "add the note to the journal" do + put '/issues/6.xml', @parameters, credentials('jsmith') + + journal = Journal.last + assert_equal "A new note", journal.notes + end + + should "update the issue" do + put '/issues/6.xml', @parameters, credentials('jsmith') + + issue = Issue.find(6) + assert_equal "API update", issue.subject + end + + end + + context "PUT /issues/3.xml with custom fields" do + setup do + @parameters = { + :issue => {:custom_fields => [{'id' => '1', 'value' => 'PostgreSQL' }, + {'id' => '2', 'value' => '150'}]} + } + end + + should "update custom fields" do + assert_no_difference('Issue.count') do + put '/issues/3.xml', @parameters, credentials('jsmith') + end + + issue = Issue.find(3) + assert_equal '150', issue.custom_value_for(2).value + assert_equal 'PostgreSQL', issue.custom_value_for(1).value + end + end + + context "PUT /issues/3.xml with multi custom fields" do + setup do + field = CustomField.find(1) + field.update_attribute :multiple, true + @parameters = { + :issue => {:custom_fields => [{'id' => '1', 'value' => ['MySQL', 'PostgreSQL'] }, + {'id' => '2', 'value' => '150'}]} + } + end + + should "update custom fields" do + assert_no_difference('Issue.count') do + put '/issues/3.xml', @parameters, credentials('jsmith') + end + + issue = Issue.find(3) + assert_equal '150', issue.custom_value_for(2).value + assert_equal ['MySQL', 'PostgreSQL'], issue.custom_field_value(1).sort + end + end + + context "PUT /issues/3.xml with project change" do + setup do + @parameters = {:issue => {:project_id => 2, :subject => 'Project changed'}} + end + + should "update project" do + assert_no_difference('Issue.count') do + put '/issues/3.xml', @parameters, credentials('jsmith') + end + + issue = Issue.find(3) + assert_equal 2, issue.project_id + assert_equal 'Project changed', issue.subject + end + end + + context "PUT /issues/6.xml with failed update" do + setup do + @parameters = {:issue => {:subject => ''}} + end + + should "not create a new issue" do + assert_no_difference('Issue.count') do + put '/issues/6.xml', @parameters, credentials('jsmith') + end + end + + should "not create a new journal" do + assert_no_difference('Journal.count') do + put '/issues/6.xml', @parameters, credentials('jsmith') + end + end + + should "have an errors tag" do + put '/issues/6.xml', @parameters, credentials('jsmith') + + assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} + end + end + + context "PUT /issues/6.json" do + setup do + @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}} + end + + should_allow_api_authentication(:put, + '/issues/6.json', + {:issue => {:subject => 'API update', :notes => 'A new note'}}, + {:success_code => :ok}) + + should "update the issue" do + assert_no_difference('Issue.count') do + assert_difference('Journal.count') do + put '/issues/6.json', @parameters, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + end + + issue = Issue.find(6) + assert_equal "API update", issue.subject + journal = Journal.last + assert_equal "A new note", journal.notes + end + end + + context "PUT /issues/6.json with failed update" do + should "return errors" do + assert_no_difference('Issue.count') do + assert_no_difference('Journal.count') do + put '/issues/6.json', {:issue => {:subject => ''}}, credentials('jsmith') + + assert_response :unprocessable_entity + end + end + + json = ActiveSupport::JSON.decode(response.body) + assert json['errors'].include?("Subject can't be blank") + end + end + + context "DELETE /issues/1.xml" do + should_allow_api_authentication(:delete, + '/issues/6.xml', + {}, + {:success_code => :ok}) + + should "delete the issue" do + assert_difference('Issue.count', -1) do + delete '/issues/6.xml', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + + assert_nil Issue.find_by_id(6) + end + end + + context "DELETE /issues/1.json" do + should_allow_api_authentication(:delete, + '/issues/6.json', + {}, + {:success_code => :ok}) + + should "delete the issue" do + assert_difference('Issue.count', -1) do + delete '/issues/6.json', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + + assert_nil Issue.find_by_id(6) + end + end + + test "POST /issues/:id/watchers.xml should add watcher" do + assert_difference 'Watcher.count' do + post '/issues/1/watchers.xml', {:user_id => 3}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + watcher = Watcher.order('id desc').first + assert_equal Issue.find(1), watcher.watchable + assert_equal User.find(3), watcher.user + end + + test "DELETE /issues/:id/watchers/:user_id.xml should remove watcher" do + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + + assert_difference 'Watcher.count', -1 do + delete '/issues/1/watchers/3.xml', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + assert_equal false, Issue.find(1).watched_by?(User.find(3)) + end + + def test_create_issue_with_uploaded_file + set_tmp_attachments_directory + # upload the file + assert_difference 'Attachment.count' do + post '/uploads.xml', 'test_create_with_upload', + {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + end + xml = Hash.from_xml(response.body) + token = xml['upload']['token'] + attachment = Attachment.first(:order => 'id DESC') + + # create the issue with the upload's token + assert_difference 'Issue.count' do + post '/issues.xml', + {:issue => {:project_id => 1, :subject => 'Uploaded file', + :uploads => [{:token => token, :filename => 'test.txt', + :content_type => 'text/plain'}]}}, + credentials('jsmith') + assert_response :created + end + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.attachments.count + assert_equal attachment, issue.attachments.first + + attachment.reload + assert_equal 'test.txt', attachment.filename + assert_equal 'text/plain', attachment.content_type + assert_equal 'test_create_with_upload'.size, attachment.filesize + assert_equal 2, attachment.author_id + + # get the issue with its attachments + get "/issues/#{issue.id}.xml", :include => 'attachments' + assert_response :success + xml = Hash.from_xml(response.body) + attachments = xml['issue']['attachments'] + assert_kind_of Array, attachments + assert_equal 1, attachments.size + url = attachments.first['content_url'] + assert_not_nil url + + # download the attachment + get url + assert_response :success + end + + def test_update_issue_with_uploaded_file + set_tmp_attachments_directory + # upload the file + assert_difference 'Attachment.count' do + post '/uploads.xml', 'test_upload_with_upload', + {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + end + xml = Hash.from_xml(response.body) + token = xml['upload']['token'] + attachment = Attachment.first(:order => 'id DESC') + + # update the issue with the upload's token + assert_difference 'Journal.count' do + put '/issues/1.xml', + {:issue => {:notes => 'Attachment added', + :uploads => [{:token => token, :filename => 'test.txt', + :content_type => 'text/plain'}]}}, + credentials('jsmith') + assert_response :ok + assert_equal '', @response.body + end + + issue = Issue.find(1) + assert_include attachment, issue.attachments + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a7836f4a070a9a0d96511cf7847d79f9fcfd69e4.svn-base --- a/.svn/pristine/a7/a7836f4a070a9a0d96511cf7847d79f9fcfd69e4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class MailHandlerController < ActionController::Base - before_filter :check_credential - - # Submits an incoming email to MailHandler - def index - options = params.dup - email = options.delete(:email) - if MailHandler.receive(email, options) - render :nothing => true, :status => :created - else - render :nothing => true, :status => :unprocessable_entity - end - end - - private - - def check_credential - User.current = nil - unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key - render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403 - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a78648685c99f33273975ef82cbc56723c1630db.svn-base --- a/.svn/pristine/a7/a78648685c99f33273975ef82cbc56723c1630db.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ ---- -custom_fields_trackers_001: - custom_field_id: 1 - tracker_id: 1 -custom_fields_trackers_002: - custom_field_id: 2 - tracker_id: 1 -custom_fields_trackers_003: - custom_field_id: 2 - tracker_id: 3 -custom_fields_trackers_004: - custom_field_id: 6 - tracker_id: 1 -custom_fields_trackers_005: - custom_field_id: 6 - tracker_id: 2 -custom_fields_trackers_006: - custom_field_id: 6 - tracker_id: 3 diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a7add1443588b3af2ef99c91219368b928dd34d0.svn-base --- a/.svn/pristine/a7/a7add1443588b3af2ef99c91219368b928dd34d0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Helpers - class TimeReport - attr_reader :criteria, :columns, :from, :to, :hours, :total_hours, :periods - - def initialize(project, issue, criteria, columns, from, to) - @project = project - @issue = issue - - @criteria = criteria || [] - @criteria = @criteria.select{|criteria| available_criteria.has_key? criteria} - @criteria.uniq! - @criteria = @criteria[0,3] - - @columns = (columns && %w(year month week day).include?(columns)) ? columns : 'month' - @from = from - @to = to - - run - end - - def available_criteria - @available_criteria || load_available_criteria - end - - private - - def run - unless @criteria.empty? - scope = TimeEntry.visible.spent_between(@from, @to) - if @issue - scope = scope.on_issue(@issue) - elsif @project - scope = scope.on_project(@project, Setting.display_subprojects_issues?) - end - time_columns = %w(tyear tmonth tweek spent_on) - @hours = [] - scope.sum(:hours, :include => :issue, :group => @criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns).each do |hash, hours| - h = {'hours' => hours} - (@criteria + time_columns).each_with_index do |name, i| - h[name] = hash[i] - end - @hours << h - end - - @hours.each do |row| - case @columns - when 'year' - row['year'] = row['tyear'] - when 'month' - row['month'] = "#{row['tyear']}-#{row['tmonth']}" - when 'week' - row['week'] = "#{row['tyear']}-#{row['tweek']}" - when 'day' - row['day'] = "#{row['spent_on']}" - end - end - - if @from.nil? - min = @hours.collect {|row| row['spent_on']}.min - @from = min ? min.to_date : Date.today - end - - if @to.nil? - max = @hours.collect {|row| row['spent_on']}.max - @to = max ? max.to_date : Date.today - end - - @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f} - - @periods = [] - # Date#at_beginning_of_ not supported in Rails 1.2.x - date_from = @from.to_time - # 100 columns max - while date_from <= @to.to_time && @periods.length < 100 - case @columns - when 'year' - @periods << "#{date_from.year}" - date_from = (date_from + 1.year).at_beginning_of_year - when 'month' - @periods << "#{date_from.year}-#{date_from.month}" - date_from = (date_from + 1.month).at_beginning_of_month - when 'week' - @periods << "#{date_from.year}-#{date_from.to_date.cweek}" - date_from = (date_from + 7.day).at_beginning_of_week - when 'day' - @periods << "#{date_from.to_date}" - date_from = date_from + 1.day - end - end - end - end - - def load_available_criteria - @available_criteria = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", - :klass => Project, - :label => :label_project}, - 'status' => {:sql => "#{Issue.table_name}.status_id", - :klass => IssueStatus, - :label => :field_status}, - 'version' => {:sql => "#{Issue.table_name}.fixed_version_id", - :klass => Version, - :label => :label_version}, - 'category' => {:sql => "#{Issue.table_name}.category_id", - :klass => IssueCategory, - :label => :field_category}, - 'member' => {:sql => "#{TimeEntry.table_name}.user_id", - :klass => User, - :label => :label_member}, - 'tracker' => {:sql => "#{Issue.table_name}.tracker_id", - :klass => Tracker, - :label => :label_tracker}, - 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id", - :klass => TimeEntryActivity, - :label => :label_activity}, - 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id", - :klass => Issue, - :label => :label_issue} - } - - # Add list and boolean custom fields as available criteria - custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields) - custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id ORDER BY c.value LIMIT 1)", - :format => cf.field_format, - :label => cf.name} - end if @project - - # Add list and boolean time entry custom fields - TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id ORDER BY c.value LIMIT 1)", - :format => cf.field_format, - :label => cf.name} - end - - # Add list and boolean time entry activity custom fields - TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id ORDER BY c.value LIMIT 1)", - :format => cf.field_format, - :label => cf.name} - end - - @available_criteria - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a7/a7f9a5a10472a6b42b368a1289f25463a8c4b622.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a7/a7f9a5a10472a6b42b368a1289f25463a8c4b622.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,10 @@ +class AddIssueStatusPosition < ActiveRecord::Migration + def self.up + add_column :issue_statuses, :position, :integer, :default => 1 + IssueStatus.all.each_with_index {|status, i| status.update_attribute(:position, i+1)} + end + + def self.down + remove_column :issue_statuses, :position + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a8/a81b13dc0401f67f1e9b5f7bed01d01362dcb1f6.svn-base --- a/.svn/pristine/a8/a81b13dc0401f67f1e9b5f7bed01d01362dcb1f6.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1120 +0,0 @@ -# Spanish translations for Rails -# by Francisco Fernando García Nieto (ffgarcianieto@gmail.com) -# Redmine spanish translation: -# by J. Cayetano Delgado (Cayetano _dot_ Delgado _at_ ioko _dot_ com) - -es: - number: - # Used in number_with_delimiter() - # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' - format: - # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) - separator: "," - # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) - delimiter: "." - # Number of decimals, behind the separator (1 with a precision of 2 gives: 1.00) - precision: 3 - - # Used in number_to_currency() - currency: - format: - # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) - format: "%n %u" - unit: "€" - # These three are to override number.format and are optional - separator: "," - delimiter: "." - precision: 2 - - # Used in number_to_percentage() - percentage: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_precision() - precision: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_human_size() - human: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() - datetime: - distance_in_words: - half_a_minute: "medio minuto" - less_than_x_seconds: - one: "menos de 1 segundo" - other: "menos de %{count} segundos" - x_seconds: - one: "1 segundo" - other: "%{count} segundos" - less_than_x_minutes: - one: "menos de 1 minuto" - other: "menos de %{count} minutos" - x_minutes: - one: "1 minuto" - other: "%{count} minutos" - about_x_hours: - one: "alrededor de 1 hora" - other: "alrededor de %{count} horas" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 día" - other: "%{count} días" - about_x_months: - one: "alrededor de 1 mes" - other: "alrededor de %{count} meses" - x_months: - one: "1 mes" - other: "%{count} meses" - about_x_years: - one: "alrededor de 1 año" - other: "alrededor de %{count} años" - over_x_years: - one: "más de 1 año" - other: "más de %{count} años" - almost_x_years: - one: "casi 1 año" - other: "casi %{count} años" - - activerecord: - errors: - template: - header: - one: "no se pudo guardar este %{model} porque se encontró 1 error" - other: "no se pudo guardar este %{model} porque se encontraron %{count} errores" - # The variable :count is also available - body: "Se encontraron problemas con los siguientes campos:" - - # The values :model, :attribute and :value are always available for interpolation - # The value :count is available when applicable. Can be used for pluralization. - messages: - inclusion: "no está incluido en la lista" - exclusion: "está reservado" - invalid: "no es válido" - confirmation: "no coincide con la confirmación" - accepted: "debe ser aceptado" - empty: "no puede estar vacío" - blank: "no puede estar en blanco" - too_long: "es demasiado largo (%{count} caracteres máximo)" - too_short: "es demasiado corto (%{count} caracteres mínimo)" - wrong_length: "no tiene la longitud correcta (%{count} caracteres exactos)" - taken: "ya está en uso" - not_a_number: "no es un número" - greater_than: "debe ser mayor que %{count}" - greater_than_or_equal_to: "debe ser mayor que o igual a %{count}" - equal_to: "debe ser igual a %{count}" - less_than: "debe ser menor que %{count}" - less_than_or_equal_to: "debe ser menor que o igual a %{count}" - odd: "debe ser impar" - even: "debe ser par" - greater_than_start_date: "debe ser posterior a la fecha de comienzo" - not_same_project: "no pertenece al mismo proyecto" - circular_dependency: "Esta relación podría crear una dependencia circular" - cant_link_an_issue_with_a_descendant: "Esta petición no puede ser ligada a una de estas tareas" - - # Append your own errors here or at the model/attributes scope. - - models: - # Overrides default messages - - attributes: - # Overrides model and default messages. - - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%d de %b" - long: "%d de %B de %Y" - - day_names: [Domingo, Lunes, Martes, Miércoles, Jueves, Viernes, Sábado] - abbr_day_names: [Dom, Lun, Mar, Mie, Jue, Vie, Sab] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio, Agosto, Septiembre, Octubre, Noviembre, Diciembre] - abbr_month_names: [~, Ene, Feb, Mar, Abr, May, Jun, Jul, Ago, Sep, Oct, Nov, Dic] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%A, %d de %B de %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d de %b %H:%M" - long: "%d de %B de %Y %H:%M" - am: "am" - pm: "pm" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "y" - - actionview_instancetag_blank_option: Por favor seleccione - - button_activate: Activar - button_add: Añadir - button_annotate: Anotar - button_apply: Aceptar - button_archive: Archivar - button_back: Atrás - button_cancel: Cancelar - button_change: Cambiar - button_change_password: Cambiar contraseña - button_check_all: Seleccionar todo - button_clear: Anular - button_configure: Configurar - button_copy: Copiar - button_create: Crear - button_delete: Borrar - button_download: Descargar - button_edit: Modificar - button_list: Listar - button_lock: Bloquear - button_log_time: Tiempo dedicado - button_login: Conexión - button_move: Mover - button_quote: Citar - button_rename: Renombrar - button_reply: Responder - button_reset: Reestablecer - button_rollback: Volver a esta versión - button_save: Guardar - button_sort: Ordenar - button_submit: Aceptar - button_test: Probar - button_unarchive: Desarchivar - button_uncheck_all: No seleccionar nada - button_unlock: Desbloquear - button_unwatch: No monitorizar - button_update: Actualizar - button_view: Ver - button_watch: Monitorizar - default_activity_design: Diseño - default_activity_development: Desarrollo - default_doc_category_tech: Documentación técnica - default_doc_category_user: Documentación de usuario - default_issue_status_in_progress: En curso - default_issue_status_closed: Cerrada - default_issue_status_feedback: Comentarios - default_issue_status_new: Nueva - default_issue_status_rejected: Rechazada - default_issue_status_resolved: Resuelta - default_priority_high: Alta - default_priority_immediate: Inmediata - default_priority_low: Baja - default_priority_normal: Normal - default_priority_urgent: Urgente - default_role_developer: Desarrollador - default_role_manager: Jefe de proyecto - default_role_reporter: Informador - default_tracker_bug: Errores - default_tracker_feature: Tareas - default_tracker_support: Soporte - enumeration_activities: Actividades (tiempo dedicado) - enumeration_doc_categories: Categorías del documento - enumeration_issue_priorities: Prioridad de las peticiones - error_can_t_load_default_data: "No se ha podido cargar la configuración por defecto: %{value}" - error_issue_not_found_in_project: 'La petición no se encuentra o no está asociada a este proyecto' - error_scm_annotate: "No existe la entrada o no ha podido ser anotada" - error_scm_annotate_big_text_file: "La entrada no puede anotarse, al superar el tamaño máximo para ficheros de texto." - error_scm_command_failed: "Se produjo un error al acceder al repositorio: %{value}" - error_scm_not_found: "La entrada y/o la revisión no existe en el repositorio." - field_account: Cuenta - field_activity: Actividad - field_admin: Administrador - field_assignable: Se pueden asignar peticiones a este perfil - field_assigned_to: Asignado a - field_attr_firstname: Cualidad del nombre - field_attr_lastname: Cualidad del apellido - field_attr_login: Cualidad del identificador - field_attr_mail: Cualidad del Email - field_auth_source: Modo de identificación - field_author: Autor - field_base_dn: DN base - field_category: Categoría - field_column_names: Columnas - field_comments: Comentario - field_comments_sorting: Mostrar comentarios - field_created_on: Creado - field_default_value: Estado por defecto - field_delay: Retraso - field_description: Descripción - field_done_ratio: "% Realizado" - field_downloads: Descargas - field_due_date: Fecha fin - field_effective_date: Fecha - field_estimated_hours: Tiempo estimado - field_field_format: Formato - field_filename: Fichero - field_filesize: Tamaño - field_firstname: Nombre - field_fixed_version: Versión prevista - field_hide_mail: Ocultar mi dirección de correo - field_homepage: Sitio web - field_host: Anfitrión - field_hours: Horas - field_identifier: Identificador - field_is_closed: Petición resuelta - field_is_default: Estado por defecto - field_is_filter: Usado como filtro - field_is_for_all: Para todos los proyectos - field_is_in_roadmap: Consultar las peticiones en la planificación - field_is_public: Público - field_is_required: Obligatorio - field_issue: Petición - field_issue_to: Petición relacionada - field_language: Idioma - field_last_login_on: Última conexión - field_lastname: Apellido - field_login: Identificador - field_mail: Correo electrónico - field_mail_notification: Notificaciones por correo - field_max_length: Longitud máxima - field_min_length: Longitud mínima - field_name: Nombre - field_new_password: Nueva contraseña - field_notes: Notas - field_onthefly: Creación del usuario "al vuelo" - field_parent: Proyecto padre - field_parent_title: Página padre - field_password: Contraseña - field_password_confirmation: Confirmación - field_port: Puerto - field_possible_values: Valores posibles - field_priority: Prioridad - field_project: Proyecto - field_redirect_existing_links: Redireccionar enlaces existentes - field_regexp: Expresión regular - field_role: Perfil - field_searchable: Incluir en las búsquedas - field_spent_on: Fecha - field_start_date: Fecha de inicio - field_start_page: Página principal - field_status: Estado - field_subject: Asunto - field_subproject: Proyecto secundario - field_summary: Resumen - field_time_zone: Zona horaria - field_title: Título - field_tracker: Tipo - field_type: Tipo - field_updated_on: Actualizado - field_url: URL - field_user: Usuario - field_value: Valor - field_version: Versión - general_csv_decimal_separator: ',' - general_csv_encoding: ISO-8859-15 - general_csv_separator: ';' - general_first_day_of_week: '1' - general_lang_name: 'Español' - general_pdf_encoding: UTF-8 - general_text_No: 'No' - general_text_Yes: 'Sí' - general_text_no: 'no' - general_text_yes: 'sí' - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errores" - label_activity: Actividad - label_add_another_file: Añadir otro fichero - label_add_note: Añadir una nota - label_added: añadido - label_added_time_by: "Añadido por %{author} hace %{age}" - label_administration: Administración - label_age: Edad - label_ago: hace - label_all: todos - label_all_time: todo el tiempo - label_all_words: Todas las palabras - label_and_its_subprojects: "%{value} y proyectos secundarios" - label_applied_status: Aplicar estado - label_assigned_to_me_issues: Peticiones que me están asignadas - label_associated_revisions: Revisiones asociadas - label_attachment: Fichero - label_attachment_delete: Borrar el fichero - label_attachment_new: Nuevo fichero - label_attachment_plural: Ficheros - label_attribute: Cualidad - label_attribute_plural: Cualidades - label_auth_source: Modo de autenticación - label_auth_source_new: Nuevo modo de autenticación - label_auth_source_plural: Modos de autenticación - label_authentication: Autenticación - label_blocked_by: bloqueado por - label_blocks: bloquea a - label_board: Foro - label_board_new: Nuevo foro - label_board_plural: Foros - label_boolean: Booleano - label_browse: Hojear - label_bulk_edit_selected_issues: Editar las peticiones seleccionadas - label_calendar: Calendario - label_change_plural: Cambios - label_change_properties: Cambiar propiedades - label_change_status: Cambiar el estado - label_change_view_all: Ver todos los cambios - label_changes_details: Detalles de todos los cambios - label_changeset_plural: Cambios - label_chronological_order: En orden cronológico - label_closed_issues: cerrada - label_closed_issues_plural: cerradas - label_x_open_issues_abbr_on_total: - zero: 0 abiertas / %{total} - one: 1 abierta / %{total} - other: "%{count} abiertas / %{total}" - label_x_open_issues_abbr: - zero: 0 abiertas - one: 1 abierta - other: "%{count} abiertas" - label_x_closed_issues_abbr: - zero: 0 cerradas - one: 1 cerrada - other: "%{count} cerradas" - label_comment: Comentario - label_comment_add: Añadir un comentario - label_comment_added: Comentario añadido - label_comment_delete: Borrar comentarios - label_comment_plural: Comentarios - label_x_comments: - zero: sin comentarios - one: 1 comentario - other: "%{count} comentarios" - label_commits_per_author: Commits por autor - label_commits_per_month: Commits por mes - label_confirmation: Confirmación - label_contains: contiene - label_copied: copiado - label_copy_workflow_from: Copiar flujo de trabajo desde - label_current_status: Estado actual - label_current_version: Versión actual - label_custom_field: Campo personalizado - label_custom_field_new: Nuevo campo personalizado - label_custom_field_plural: Campos personalizados - label_date: Fecha - label_date_from: Desde - label_date_range: Rango de fechas - label_date_to: Hasta - label_day_plural: días - label_default: Por defecto - label_default_columns: Columnas por defecto - label_deleted: suprimido - label_details: Detalles - label_diff_inline: en línea - label_diff_side_by_side: cara a cara - label_disabled: deshabilitado - label_display_per_page: "Por página: %{value}" - label_document: Documento - label_document_added: Documento añadido - label_document_new: Nuevo documento - label_document_plural: Documentos - label_download: "%{count} Descarga" - label_download_plural: "%{count} Descargas" - label_downloads_abbr: D/L - label_duplicated_by: duplicada por - label_duplicates: duplicada de - label_end_to_end: fin a fin - label_end_to_start: fin a principio - label_enumeration_new: Nuevo valor - label_enumerations: Listas de valores - label_environment: Entorno - label_equals: igual - label_example: Ejemplo - label_export_to: 'Exportar a:' - label_f_hour: "%{value} hora" - label_f_hour_plural: "%{value} horas" - label_feed_plural: Feeds - label_feeds_access_key_created_on: "Clave de acceso por RSS creada hace %{value}" - label_file_added: Fichero añadido - label_file_plural: Archivos - label_filter_add: Añadir el filtro - label_filter_plural: Filtros - label_float: Flotante - label_follows: posterior a - label_gantt: Gantt - label_general: General - label_generate_key: Generar clave - label_help: Ayuda - label_history: Histórico - label_home: Inicio - label_in: en - label_in_less_than: en menos que - label_in_more_than: en más que - label_incoming_emails: Correos entrantes - label_index_by_date: Ãndice por fecha - label_index_by_title: Ãndice por título - label_information: Información - label_information_plural: Información - label_integer: Número - label_internal: Interno - label_issue: Petición - label_issue_added: Petición añadida - label_issue_category: Categoría de las peticiones - label_issue_category_new: Nueva categoría - label_issue_category_plural: Categorías de las peticiones - label_issue_new: Nueva petición - label_issue_plural: Peticiones - label_issue_status: Estado de la petición - label_issue_status_new: Nuevo estado - label_issue_status_plural: Estados de las peticiones - label_issue_tracking: Peticiones - label_issue_updated: Petición actualizada - label_issue_view_all: Ver todas las peticiones - label_issue_watchers: Seguidores - label_issues_by: "Peticiones por %{value}" - label_jump_to_a_project: Ir al proyecto... - label_language_based: Basado en el idioma - label_last_changes: "últimos %{count} cambios" - label_last_login: Última conexión - label_last_month: último mes - label_last_n_days: "últimos %{count} días" - label_last_week: última semana - label_latest_revision: Última revisión - label_latest_revision_plural: Últimas revisiones - label_ldap_authentication: Autenticación LDAP - label_less_than_ago: hace menos de - label_list: Lista - label_loading: Cargando... - label_logged_as: Conectado como - label_login: Conexión - label_logout: Desconexión - label_max_size: Tamaño máximo - label_me: yo mismo - label_member: Miembro - label_member_new: Nuevo miembro - label_member_plural: Miembros - label_message_last: Último mensaje - label_message_new: Nuevo mensaje - label_message_plural: Mensajes - label_message_posted: Mensaje añadido - label_min_max_length: Longitud mín - máx - label_modification: "%{count} modificación" - label_modification_plural: "%{count} modificaciones" - label_modified: modificado - label_module_plural: Módulos - label_month: Mes - label_months_from: meses de - label_more: Más - label_more_than_ago: hace más de - label_my_account: Mi cuenta - label_my_page: Mi página - label_my_projects: Mis proyectos - label_new: Nuevo - label_new_statuses_allowed: Nuevos estados autorizados - label_news: Noticia - label_news_added: Noticia añadida - label_news_latest: Últimas noticias - label_news_new: Nueva noticia - label_news_plural: Noticias - label_news_view_all: Ver todas las noticias - label_next: Siguiente - label_no_change_option: (Sin cambios) - label_no_data: Ningún dato disponible - label_nobody: nadie - label_none: ninguno - label_not_contains: no contiene - label_not_equals: no igual - label_open_issues: abierta - label_open_issues_plural: abiertas - label_optional_description: Descripción opcional - label_options: Opciones - label_overall_activity: Actividad global - label_overview: Vistazo - label_password_lost: ¿Olvidaste la contraseña? - label_per_page: Por página - label_permissions: Permisos - label_permissions_report: Informe de permisos - label_personalize_page: Personalizar esta página - label_planning: Planificación - label_please_login: Conexión - label_plugins: Extensiones - label_precedes: anterior a - label_preferences: Preferencias - label_preview: Previsualizar - label_previous: Anterior - label_project: Proyecto - label_project_all: Todos los proyectos - label_project_latest: Últimos proyectos - label_project_new: Nuevo proyecto - label_project_plural: Proyectos - label_x_projects: - zero: sin proyectos - one: 1 proyecto - other: "%{count} proyectos" - label_public_projects: Proyectos públicos - label_query: Consulta personalizada - label_query_new: Nueva consulta - label_query_plural: Consultas personalizadas - label_read: Leer... - label_register: Registrar - label_registered_on: Inscrito el - label_registration_activation_by_email: activación de cuenta por correo - label_registration_automatic_activation: activación automática de cuenta - label_registration_manual_activation: activación manual de cuenta - label_related_issues: Peticiones relacionadas - label_relates_to: relacionada con - label_relation_delete: Eliminar relación - label_relation_new: Nueva relación - label_renamed: renombrado - label_reply_plural: Respuestas - label_report: Informe - label_report_plural: Informes - label_reported_issues: Peticiones registradas por mí - label_repository: Repositorio - label_repository_plural: Repositorios - label_result_plural: Resultados - label_reverse_chronological_order: En orden cronológico inverso - label_revision: Revisión - label_revision_plural: Revisiones - label_roadmap: Planificación - label_roadmap_due_in: "Finaliza en %{value}" - label_roadmap_no_issues: No hay peticiones para esta versión - label_roadmap_overdue: "%{value} tarde" - label_role: Perfil - label_role_and_permissions: Perfiles y permisos - label_role_new: Nuevo perfil - label_role_plural: Perfiles - label_scm: SCM - label_search: Búsqueda - label_search_titles_only: Buscar sólo en títulos - label_send_information: Enviar información de la cuenta al usuario - label_send_test_email: Enviar un correo de prueba - label_settings: Configuración - label_show_completed_versions: Muestra las versiones terminadas - label_sort_by: "Ordenar por %{value}" - label_sort_higher: Subir - label_sort_highest: Primero - label_sort_lower: Bajar - label_sort_lowest: Último - label_spent_time: Tiempo dedicado - label_start_to_end: principio a fin - label_start_to_start: principio a principio - label_statistics: Estadísticas - label_stay_logged_in: Recordar conexión - label_string: Texto - label_subproject_plural: Proyectos secundarios - label_text: Texto largo - label_theme: Tema - label_this_month: este mes - label_this_week: esta semana - label_this_year: este año - label_time_tracking: Control de tiempo - label_today: hoy - label_topic_plural: Temas - label_total: Total - label_tracker: Tipo - label_tracker_new: Nuevo tipo - label_tracker_plural: Tipos de peticiones - label_updated_time: "Actualizado hace %{value}" - label_updated_time_by: "Actualizado por %{author} hace %{age}" - label_used_by: Utilizado por - label_user: Usuario - label_user_activity: "Actividad de %{value}" - label_user_mail_no_self_notified: "No quiero ser avisado de cambios hechos por mí" - label_user_mail_option_all: "Para cualquier evento en todos mis proyectos" - label_user_mail_option_selected: "Para cualquier evento de los proyectos seleccionados..." - label_user_new: Nuevo usuario - label_user_plural: Usuarios - label_version: Versión - label_version_new: Nueva versión - label_version_plural: Versiones - label_view_diff: Ver diferencias - label_view_revisions: Ver las revisiones - label_watched_issues: Peticiones monitorizadas - label_week: Semana - label_wiki: Wiki - label_wiki_edit: Modificación Wiki - label_wiki_edit_plural: Modificaciones Wiki - label_wiki_page: Página Wiki - label_wiki_page_plural: Páginas Wiki - label_workflow: Flujo de trabajo - label_year: Año - label_yesterday: ayer - mail_body_account_activation_request: "Se ha inscrito un nuevo usuario (%{value}). La cuenta está pendiende de aprobación:" - mail_body_account_information: Información sobre su cuenta - mail_body_account_information_external: "Puede usar su cuenta %{value} para conectarse." - mail_body_lost_password: 'Para cambiar su contraseña, haga clic en el siguiente enlace:' - mail_body_register: 'Para activar su cuenta, haga clic en el siguiente enlace:' - mail_body_reminder: "%{count} peticion(es) asignadas a tí finalizan en los próximos %{days} días:" - mail_subject_account_activation_request: "Petición de activación de cuenta %{value}" - mail_subject_lost_password: "Tu contraseña del %{value}" - mail_subject_register: "Activación de la cuenta del %{value}" - mail_subject_reminder: "%{count} peticion(es) finalizan en los próximos %{days} días" - notice_account_activated: Su cuenta ha sido activada. Ya puede conectarse. - notice_account_invalid_creditentials: Usuario o contraseña inválido. - notice_account_lost_email_sent: Se le ha enviado un correo con instrucciones para elegir una nueva contraseña. - notice_account_password_updated: Contraseña modificada correctamente. - notice_account_pending: "Su cuenta ha sido creada y está pendiende de la aprobación por parte del administrador." - notice_account_register_done: Cuenta creada correctamente. Para activarla, haga clic sobre el enlace que le ha sido enviado por correo. - notice_account_unknown_email: Usuario desconocido. - notice_account_updated: Cuenta actualizada correctamente. - notice_account_wrong_password: Contraseña incorrecta. - notice_can_t_change_password: Esta cuenta utiliza una fuente de autenticación externa. No es posible cambiar la contraseña. - notice_default_data_loaded: Configuración por defecto cargada correctamente. - notice_email_error: "Ha ocurrido un error mientras enviando el correo (%{value})" - notice_email_sent: "Se ha enviado un correo a %{value}" - notice_failed_to_save_issues: "Imposible grabar %{count} peticion(es) de %{total} seleccionada(s): %{ids}." - notice_feeds_access_key_reseted: Su clave de acceso para RSS ha sido reiniciada. - notice_file_not_found: La página a la que intenta acceder no existe. - notice_locking_conflict: Los datos han sido modificados por otro usuario. - notice_no_issue_selected: "Ninguna petición seleccionada. Por favor, compruebe la petición que quiere modificar" - notice_not_authorized: No tiene autorización para acceder a esta página. - notice_successful_connection: Conexión correcta. - notice_successful_create: Creación correcta. - notice_successful_delete: Borrado correcto. - notice_successful_update: Modificación correcta. - notice_unable_delete_version: No se puede borrar la versión - permission_add_issue_notes: Añadir notas - permission_add_issue_watchers: Añadir seguidores - permission_add_issues: Añadir peticiones - permission_add_messages: Enviar mensajes - permission_browse_repository: Hojear repositiorio - permission_comment_news: Comentar noticias - permission_commit_access: Acceso de escritura - permission_delete_issues: Borrar peticiones - permission_delete_messages: Borrar mensajes - permission_delete_own_messages: Borrar mensajes propios - permission_delete_wiki_pages: Borrar páginas wiki - permission_delete_wiki_pages_attachments: Borrar ficheros - permission_edit_issue_notes: Modificar notas - permission_edit_issues: Modificar peticiones - permission_edit_messages: Modificar mensajes - permission_edit_own_issue_notes: Modificar notas propias - permission_edit_own_messages: Editar mensajes propios - permission_edit_own_time_entries: Modificar tiempos dedicados propios - permission_edit_project: Modificar proyecto - permission_edit_time_entries: Modificar tiempos dedicados - permission_edit_wiki_pages: Modificar páginas wiki - permission_log_time: Anotar tiempo dedicado - permission_manage_boards: Administrar foros - permission_manage_categories: Administrar categorías de peticiones - permission_manage_documents: Administrar documentos - permission_manage_files: Administrar ficheros - permission_manage_issue_relations: Administrar relación con otras peticiones - permission_manage_members: Administrar miembros - permission_manage_news: Administrar noticias - permission_manage_public_queries: Administrar consultas públicas - permission_manage_repository: Administrar repositorio - permission_manage_versions: Administrar versiones - permission_manage_wiki: Administrar wiki - permission_move_issues: Mover peticiones - permission_protect_wiki_pages: Proteger páginas wiki - permission_rename_wiki_pages: Renombrar páginas wiki - permission_save_queries: Grabar consultas - permission_select_project_modules: Seleccionar módulos del proyecto - permission_view_calendar: Ver calendario - permission_view_changesets: Ver cambios - permission_view_documents: Ver documentos - permission_view_files: Ver ficheros - permission_view_gantt: Ver diagrama de Gantt - permission_view_issue_watchers: Ver lista de seguidores - permission_view_messages: Ver mensajes - permission_view_time_entries: Ver tiempo dedicado - permission_view_wiki_edits: Ver histórico del wiki - permission_view_wiki_pages: Ver wiki - project_module_boards: Foros - project_module_documents: Documentos - project_module_files: Ficheros - project_module_issue_tracking: Peticiones - project_module_news: Noticias - project_module_repository: Repositorio - project_module_time_tracking: Control de tiempo - project_module_wiki: Wiki - setting_activity_days_default: Días a mostrar en la actividad de proyecto - setting_app_subtitle: Subtítulo de la aplicación - setting_app_title: Título de la aplicación - setting_attachment_max_size: Tamaño máximo del fichero - setting_autofetch_changesets: Autorellenar los commits del repositorio - setting_autologin: Conexión automática - setting_bcc_recipients: Ocultar las copias de carbón (bcc) - setting_commit_fix_keywords: Palabras clave para la corrección - setting_commit_ref_keywords: Palabras clave para la referencia - setting_cross_project_issue_relations: Permitir relacionar peticiones de distintos proyectos - setting_date_format: Formato de fecha - setting_default_language: Idioma por defecto - setting_default_projects_public: Los proyectos nuevos son públicos por defecto - setting_diff_max_lines_displayed: Número máximo de diferencias mostradas - setting_display_subprojects_issues: Mostrar por defecto peticiones de proy. secundarios en el principal - setting_emails_footer: Pie de mensajes - setting_enabled_scm: Activar SCM - setting_feeds_limit: Límite de contenido para sindicación - setting_gravatar_enabled: Usar iconos de usuario (Gravatar) - setting_host_name: Nombre y ruta del servidor - setting_issue_list_default_columns: Columnas por defecto para la lista de peticiones - setting_issues_export_limit: Límite de exportación de peticiones - setting_login_required: Se requiere identificación - setting_mail_from: Correo desde el que enviar mensajes - setting_mail_handler_api_enabled: Activar SW para mensajes entrantes - setting_mail_handler_api_key: Clave de la API - setting_per_page_options: Objetos por página - setting_plain_text_mail: sólo texto plano (no HTML) - setting_protocol: Protocolo - setting_self_registration: Registro permitido - setting_sequential_project_identifiers: Generar identificadores de proyecto - setting_sys_api_enabled: Habilitar SW para la gestión del repositorio - setting_text_formatting: Formato de texto - setting_time_format: Formato de hora - setting_user_format: Formato de nombre de usuario - setting_welcome_text: Texto de bienvenida - setting_wiki_compression: Compresión del historial del Wiki - status_active: activo - status_locked: bloqueado - status_registered: registrado - text_are_you_sure: ¿Está seguro? - text_assign_time_entries_to_project: Asignar las horas al proyecto - text_caracters_maximum: "%{count} caracteres como máximo." - text_caracters_minimum: "%{count} caracteres como mínimo." - text_comma_separated: Múltiples valores permitidos (separados por coma). - text_default_administrator_account_changed: Cuenta de administrador por defecto modificada - text_destroy_time_entries: Borrar las horas - text_destroy_time_entries_question: Existen %{hours} horas asignadas a la petición que quiere borrar. ¿Qué quiere hacer? - text_diff_truncated: '... Diferencia truncada por exceder el máximo tamaño visualizable.' - text_email_delivery_not_configured: "Las notificaciones están desactivadas porque el servidor de correo no está configurado.\nConfigure el servidor de SMTP en config/configuration.yml y reinicie la aplicación para activar los cambios." - text_enumeration_category_reassign_to: 'Reasignar al siguiente valor:' - text_enumeration_destroy_question: "%{count} objetos con este valor asignado." - text_file_repository_writable: Se puede escribir en el repositorio - text_issue_added: "Petición %{id} añadida por %{author}." - text_issue_category_destroy_assignments: Dejar las peticiones sin categoría - text_issue_category_destroy_question: "Algunas peticiones (%{count}) están asignadas a esta categoría. ¿Qué desea hacer?" - text_issue_category_reassign_to: Reasignar las peticiones a la categoría - text_issue_updated: "La petición %{id} ha sido actualizada por %{author}." - text_issues_destroy_confirmation: '¿Seguro que quiere borrar las peticiones seleccionadas?' - text_issues_ref_in_commit_messages: Referencia y petición de corrección en los mensajes - text_length_between: "Longitud entre %{min} y %{max} caracteres." - text_load_default_configuration: Cargar la configuración por defecto - text_min_max_length_info: 0 para ninguna restricción - text_no_configuration_data: "Todavía no se han configurado perfiles, ni tipos, estados y flujo de trabajo asociado a peticiones. Se recomiendo encarecidamente cargar la configuración por defecto. Una vez cargada, podrá modificarla." - text_project_destroy_confirmation: ¿Estás seguro de querer eliminar el proyecto? - text_reassign_time_entries: 'Reasignar las horas a esta petición:' - text_regexp_info: ej. ^[A-Z0-9]+$ - text_repository_usernames_mapping: "Establezca la correspondencia entre los usuarios de Redmine y los presentes en el log del repositorio.\nLos usuarios con el mismo nombre o correo en Redmine y en el repositorio serán asociados automáticamente." - text_rmagick_available: RMagick disponible (opcional) - text_select_mail_notifications: Seleccionar los eventos a notificar - text_select_project_modules: 'Seleccione los módulos a activar para este proyecto:' - text_status_changed_by_changeset: "Aplicado en los cambios %{value}" - text_subprojects_destroy_warning: "Los proyectos secundarios: %{value} también se eliminarán" - text_tip_issue_begin_day: tarea que comienza este día - text_tip_issue_begin_end_day: tarea que comienza y termina este día - text_tip_issue_end_day: tarea que termina este día - text_tracker_no_workflow: No hay ningún flujo de trabajo definido para este tipo de petición - text_unallowed_characters: Caracteres no permitidos - text_user_mail_option: "De los proyectos no seleccionados, sólo recibirá notificaciones sobre elementos monitorizados o elementos en los que esté involucrado (por ejemplo, peticiones de las que usted sea autor o asignadas a usted)." - text_user_wrote: "%{value} escribió:" - text_wiki_destroy_confirmation: ¿Seguro que quiere borrar el wiki y todo su contenido? - text_workflow_edit: Seleccionar un flujo de trabajo para actualizar - text_plugin_assets_writable: Se puede escribir en el directorio público de las extensiones - warning_attachments_not_saved: "No se han podido grabar %{count} ficheros." - button_create_and_continue: Crear y continuar - text_custom_field_possible_values_info: 'Un valor en cada línea' - label_display: Mostrar - field_editable: Modificable - setting_repository_log_display_limit: Número máximo de revisiones mostradas en el fichero de trazas - setting_file_max_size_displayed: Tamaño máximo de los ficheros de texto mostrados - field_watcher: Seguidor - setting_openid: Permitir identificación y registro por OpenID - field_identity_url: URL de OpenID - label_login_with_open_id_option: o identifíquese con OpenID - field_content: Contenido - label_descending: Descendente - label_sort: Ordenar - label_ascending: Ascendente - label_date_from_to: Desde %{start} hasta %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Esta página tiene %{descendants} página(s) hija(s) y descendiente(s). ¿Qué desea hacer? - text_wiki_page_reassign_children: Reasignar páginas hijas a esta página - text_wiki_page_nullify_children: Dejar páginas hijas como páginas raíz - text_wiki_page_destroy_children: Eliminar páginas hijas y todos sus descendientes - setting_password_min_length: Longitud mínima de la contraseña - field_group_by: Agrupar resultados por - mail_subject_wiki_content_updated: "La página wiki '%{id}' ha sido actualizada" - label_wiki_content_added: Página wiki añadida - mail_subject_wiki_content_added: "Se ha añadido la página wiki '%{id}'." - mail_body_wiki_content_added: "%{author} ha añadido la página wiki '%{id}'." - label_wiki_content_updated: Página wiki actualizada - mail_body_wiki_content_updated: La página wiki '%{id}' ha sido actualizada por %{author}. - permission_add_project: Crear proyecto - setting_new_project_user_role_id: Permiso asignado a un usuario no-administrador para crear proyectos - label_view_all_revisions: Ver todas las revisiones - label_tag: Etiqueta - label_branch: Rama - error_no_tracker_in_project: Este proyecto no tiene asociados tipos de peticiones. Por favor, revise la configuración. - error_no_default_issue_status: No se ha definido un estado de petición por defecto. Por favor, revise la configuración (en "Administración" -> "Estados de las peticiones"). - text_journal_changed: "%{label} cambiado %{old} por %{new}" - text_journal_set_to: "%{label} establecido a %{value}" - text_journal_deleted: "%{label} eliminado (%{old})" - label_group_plural: Grupos - label_group: Grupo - label_group_new: Nuevo grupo - label_time_entry_plural: Tiempo dedicado - text_journal_added: "Añadido %{label} %{value}" - field_active: Activo - enumeration_system_activity: Actividad del sistema - permission_delete_issue_watchers: Borrar seguidores - version_status_closed: cerrado - version_status_locked: bloqueado - version_status_open: abierto - error_can_not_reopen_issue_on_closed_version: No se puede reabrir una petición asignada a una versión cerrada - - label_user_anonymous: Anónimo - button_move_and_follow: Mover y seguir - setting_default_projects_modules: Módulos activados por defecto en proyectos nuevos - setting_gravatar_default: Imagen Gravatar por defecto - field_sharing: Compartir - button_copy_and_follow: Copiar y seguir - label_version_sharing_hierarchy: Con la jerarquía del proyecto - label_version_sharing_tree: Con el árbol del proyecto - label_version_sharing_descendants: Con proyectos hijo - label_version_sharing_system: Con todos los proyectos - label_version_sharing_none: No compartir - button_duplicate: Duplicar - error_can_not_archive_project: Este proyecto no puede ser archivado - label_copy_source: Fuente - setting_issue_done_ratio: Calcular el ratio de tareas realizadas con - setting_issue_done_ratio_issue_status: Usar el estado de tareas - error_issue_done_ratios_not_updated: Ratios de tareas realizadas no actualizado. - error_workflow_copy_target: Por favor, elija categoría(s) y perfil(es) destino - setting_issue_done_ratio_issue_field: Utilizar el campo de petición - label_copy_same_as_target: El mismo que el destino - label_copy_target: Destino - notice_issue_done_ratios_updated: Ratios de tareas realizadas actualizados. - error_workflow_copy_source: Por favor, elija una categoría o rol de origen - label_update_issue_done_ratios: Actualizar ratios de tareas realizadas - setting_start_of_week: Comenzar las semanas en - permission_view_issues: Ver peticiones - label_display_used_statuses_only: Sólo mostrar los estados usados por este tipo de petición - label_revision_id: Revisión %{value} - label_api_access_key: Clave de acceso de la API - label_api_access_key_created_on: Clave de acceso de la API creada hace %{value} - label_feeds_access_key: Clave de acceso RSS - notice_api_access_key_reseted: Clave de acceso a la API regenerada. - setting_rest_api_enabled: Activar servicio web REST - label_missing_api_access_key: Clave de acceso a la API ausente - label_missing_feeds_access_key: Clave de accesso RSS ausente - button_show: Mostrar - text_line_separated: Múltiples valores permitidos (un valor en cada línea). - setting_mail_handler_body_delimiters: Truncar correos tras una de estas líneas - permission_add_subprojects: Crear subproyectos - label_subproject_new: Nuevo subproyecto - text_own_membership_delete_confirmation: |- - Está a punto de eliminar algún o todos sus permisos y podría perder la posibilidad de modificar este proyecto tras hacerlo. - ¿Está seguro de querer continuar? - label_close_versions: Cerrar versiones completadas - label_board_sticky: Pegajoso - label_board_locked: Bloqueado - permission_export_wiki_pages: Exportar páginas wiki - setting_cache_formatted_text: Cachear texto formateado - permission_manage_project_activities: Gestionar actividades del proyecto - error_unable_delete_issue_status: Fue imposible eliminar el estado de la petición - label_profile: Perfil - permission_manage_subtasks: Gestionar subtareas - field_parent_issue: Tarea padre - label_subtask_plural: Subtareas - label_project_copy_notifications: Enviar notificaciones por correo electrónico durante la copia del proyecto - error_can_not_delete_custom_field: Fue imposible eliminar el campo personalizado - error_unable_to_connect: Fue imposible conectar con (%{value}) - error_can_not_remove_role: Este rol está en uso y no puede ser eliminado. - error_can_not_delete_tracker: Este tipo contiene peticiones y no puede ser eliminado. - field_principal: Principal - label_my_page_block: Bloque Mi página - notice_failed_to_save_members: "Fallo al guardar miembro(s): %{errors}." - text_zoom_out: Alejar - text_zoom_in: Acercar - notice_unable_delete_time_entry: Fue imposible eliminar la entrada de tiempo dedicado. - label_overall_spent_time: Tiempo total dedicado - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendario - button_edit_associated_wikipage: "Editar paginas Wiki asociadas: %{page_title}" - field_text: Campo de texto - label_user_mail_option_only_owner: Solo para objetos que soy propietario - setting_default_notification_option: Opcion de notificacion por defecto - label_user_mail_option_only_my_events: Solo para objetos que soy seguidor o estoy involucrado - label_user_mail_option_only_assigned: Solo para objetos que estoy asignado - label_user_mail_option_none: Sin eventos - field_member_of_group: Asignado al grupo - field_assigned_to_role: Asignado al perfil - notice_not_authorized_archived_project: El proyecto al que intenta acceder ha sido archivado. - label_principal_search: "Buscar por usuario o grupo:" - label_user_search: "Buscar por usuario:" - field_visible: Visible - setting_emails_header: Encabezado de Correos - - setting_commit_logtime_activity_id: Actividad de los tiempos registrados - text_time_logged_by_changeset: Aplicado en los cambios %{value}. - setting_commit_logtime_enabled: Habilitar registro de horas - notice_gantt_chart_truncated: Se recortó el diagrama porque excede el número máximo de elementos que pueden ser mostrados (%{max}) - setting_gantt_items_limit: Número máximo de elementos mostrados en el diagrama de Gantt - field_warn_on_leaving_unsaved: Avisarme cuando vaya a abandonar una página con texto no guardado - text_warn_on_leaving_unsaved: Esta página contiene texto no guardado y si la abandona sus cambios se perderán - label_my_queries: Mis consultas personalizadas - text_journal_changed_no_detail: "Se actualizó %{label}" - label_news_comment_added: Comentario añadido a noticia - button_expand_all: Expandir todo - button_collapse_all: Contraer todo - label_additional_workflow_transitions_for_assignee: Transiciones adicionales permitidas cuando la petición está asignada al usuario - label_additional_workflow_transitions_for_author: Transiciones adicionales permitidas cuando el usuario es autor de la petición - label_bulk_edit_selected_time_entries: Editar en bloque las horas seleccionadas - text_time_entries_destroy_confirmation: ¿Está seguro de querer eliminar (la hora seleccionada/las horas seleccionadas)? - label_role_anonymous: Anónimo - label_role_non_member: No miembro - label_issue_note_added: Nota añadida - label_issue_status_updated: Estado actualizado - label_issue_priority_updated: Prioridad actualizada - label_issues_visibility_own: Peticiones creadas por el usuario o asignadas a él - field_issues_visibility: Visibilidad de las peticiones - label_issues_visibility_all: Todas las peticiones - permission_set_own_issues_private: Poner las peticiones propias como públicas o privadas - field_is_private: Privada - permission_set_issues_private: Poner peticiones como públicas o privadas - label_issues_visibility_public: Todas las peticiones no privadas - text_issues_destroy_descendants_confirmation: Se procederá a borrar también %{count} subtarea(s). - field_commit_logs_encoding: Codificación de los mensajes de commit - field_scm_path_encoding: Codificación de las rutas - text_scm_path_encoding_note: "Por defecto: UTF-8" - field_path_to_repository: Ruta al repositorio - field_root_directory: Directorio raíz - field_cvs_module: Módulo - field_cvsroot: CVSROOT - text_mercurial_repository_note: Repositorio local (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Orden - text_scm_command_version: Versión - label_git_report_last_commit: Informar del último commit para ficheros y directorios - text_scm_config: Puede configurar las órdenes de cada scm en configuration/configuration.yml. Por favor, reinicie la aplicación después de editarlo - text_scm_command_not_available: La orden para el Scm no está disponible. Por favor, compruebe la configuración en el panel de administración. - notice_issue_successful_create: Petición %{id} creada. - label_between: entre - setting_issue_group_assignment: Permitir asignar peticiones a grupos - label_diff: diferencias - text_git_repository_note: El repositorio es básico y local (p.e. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Dirección de ordenación - description_project_scope: Ãmbito de búsqueda - description_filter: Filtro - description_user_mail_notification: Configuración de notificaciones por correo - description_date_from: Introduzca la fecha de inicio - description_message_content: Contenido del mensaje - description_available_columns: Columnas disponibles - description_date_range_interval: Elija el rango seleccionando la fecha de inicio y fin - description_issue_category_reassign: Elija la categoría de la petición - description_search: Campo de búsqueda - description_notes: Notas - description_date_range_list: Elija el rango en la lista - description_choose_project: Proyectos - description_date_to: Introduzca la fecha fin - description_query_sort_criteria_attribute: Atributo de ordenación - description_wiki_subpages_reassign: Elija la nueva página padre - description_selected_columns: Columnas seleccionadas - label_parent_revision: Padre - label_child_revision: Hijo - setting_default_issue_start_date_to_creation_date: Utilizar la fecha actual como fecha de inicio para nuevas peticiones - button_edit_section: Editar esta sección - setting_repositories_encodings: Codificación de adjuntos y repositorios - description_all_columns: Todas las columnas - button_export: Exportar - label_export_options: "%{export_format} opciones de exportación" - error_attachment_too_big: Este fichero no se puede adjuntar porque excede el tamaño máximo de fichero (%{max_size}) - notice_failed_to_save_time_entries: "Error al guardar %{count} entradas de tiempo de las %{total} seleccionadas: %{ids}." - label_x_issues: - zero: 0 petición - one: 1 petición - other: "%{count} peticiones" - label_repository_new: Nuevo repositorio - field_repository_is_default: Repositorio principal - label_copy_attachments: Copiar adjuntos - label_item_position: "%{position}/%{count}" - label_completed_versions: Versiones completadas - text_project_identifier_info: Solo se permiten letras en minúscula (a-z), números, guiones y barras bajas.
    Una vez guardado, el identificador no se puede cambiar. - field_multiple: Valores múltiples - setting_commit_cross_project_ref: Permitir referenciar y resolver peticiones de todos los demás proyectos - text_issue_conflict_resolution_add_notes: Añadir mis notas y descartar mis otros cambios - text_issue_conflict_resolution_overwrite: Aplicar mis campos de todas formas (las notas anteriores se mantendrán pero algunos cambios podrían ser sobreescritos) - notice_issue_update_conflict: La petición ha sido actualizada por otro usuario mientras la editaba. - text_issue_conflict_resolution_cancel: Descartar todos mis cambios y mostrar de nuevo %{link} - permission_manage_related_issues: Gestionar peticiones relacionadas - field_auth_source_ldap_filter: Filtro LDAP - label_search_for_watchers: Buscar seguidores para añadirlos - notice_account_deleted: Su cuenta ha sido eliminada - setting_unsubscribe: Permitir a los usuarios borrar sus propias cuentas - button_delete_my_account: Borrar mi cuenta - text_account_destroy_confirmation: |- - ¿Está seguro de querer proceder? - Su cuenta quedará borrada permanentemente, sin la posibilidad de reactivarla. - error_session_expired: Su sesión ha expirado. Por favor, vuelva a identificarse. - text_session_expiration_settings: "Advertencia: el cambio de estas opciones podría hacer expirar las sesiones activas, incluyendo la suya." - setting_session_lifetime: Tiempo de vida máximo de las sesiones - setting_session_timeout: Tiempo máximo de inactividad de las sesiones - label_session_expiration: Expiración de sesiones - permission_close_project: Cerrar / reabrir el proyecto - label_show_closed_projects: Ver proyectos cerrados - button_close: Cerrar - button_reopen: Reabrir - project_status_active: activo - project_status_closed: cerrado - project_status_archived: archivado - text_project_closed: Este proyecto está cerrado y es de sólo lectura - notice_user_successful_create: Usuario %{id} creado. - field_core_fields: Campos básicos - field_timeout: Tiempo de inactividad (en segundos) - setting_thumbnails_enabled: Mostrar miniaturas de los adjuntos - setting_thumbnails_size: Tamaño de las miniaturas (en píxeles) - label_status_transitions: Transiciones de estado - label_fields_permissions: Permisos sobre los campos - label_readonly: Sólo lectura - label_required: Requerido - text_repository_identifier_info: Solo se permiten letras en minúscula (a-z), números, guiones y barras bajas.
    Una vez guardado, el identificador no se puede cambiar. - field_board_parent: Foro padre - label_attribute_of_project: "%{name} del proyecto" - label_attribute_of_author: "%{name} del autor" - label_attribute_of_assigned_to: "%{name} de la persona asignada" - label_attribute_of_fixed_version: "%{name} de la versión indicada" - label_copy_subtasks: Copiar subtareas - label_copied_to: copiada a - label_copied_from: copiada desde - label_any_issues_in_project: cualquier petición del proyecto - label_any_issues_not_in_project: cualquier petición que no sea del proyecto - field_private_notes: Notas privadas - permission_view_private_notes: Ver notas privadas - permission_set_notes_private: Poner notas como privadas - label_no_issues_in_project: no hay peticiones en el proyecto - label_any: todos - label_last_n_weeks: en las últimas %{count} semanas - setting_cross_project_subtasks: Permitir subtareas cruzadas entre proyectos - label_cross_project_descendants: Con proyectos hijo - label_cross_project_tree: Con el árbol del proyecto - label_cross_project_hierarchy: Con la jerarquía del proyecto - label_cross_project_system: Con todos los proyectos - button_hide: Ocultar - setting_non_working_week_days: Días no laborables - label_in_the_next_days: en los próximos - label_in_the_past_days: en los anteriores diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a8/a8b00183ca19aefa144aaaa16c35a73416e33f9f.svn-base --- a/.svn/pristine/a8/a8b00183ca19aefa144aaaa16c35a73416e33f9f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -<% if @project.boards.any? %> - - - - - - - - -<% Board.board_tree(@project.boards) do |board, level| - next if board.new_record? %> - - - - - - -<% end %> - -
    <%= l(:label_board) %><%= l(:field_description) %>
    <%= link_to board.name, project_board_path(@project, board) %><%=h board.description %> - <% if authorize_for("boards", "edit") %> - <%= reorder_links('board', {:controller => 'boards', :action => 'update', :project_id => @project, :id => board}, :put) %> - <% end %> - - <% if User.current.allowed_to?(:manage_boards, @project) %> - <%= link_to l(:button_edit), edit_project_board_path(@project, board), :class => 'icon icon-edit' %> - <%= delete_link project_board_path(@project, board) %> - <% end %> -
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> - -<% if User.current.allowed_to?(:manage_boards, @project) %> -

    <%= link_to l(:label_board_new), new_project_board_path(@project), :class => 'icon icon-add' %>

    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a8/a8e5c6393341a001c62733de1b51c76eb2b10fe6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a8/a8e5c6393341a001c62733de1b51c76eb2b10fe6.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,23 @@ +class SplitDocumentsPermissions < ActiveRecord::Migration + def up + # :manage_documents permission split into 3 permissions: + # :add_documents, :edit_documents and :delete_documents + Role.all.each do |role| + if role.has_permission?(:manage_documents) + role.add_permission! :add_documents, :edit_documents, :delete_documents + role.remove_permission! :manage_documents + end + end + end + + def down + Role.all.each do |role| + if role.has_permission?(:add_documents) || + role.has_permission?(:edit_documents) || + role.has_permission?(:delete_documents) + role.remove_permission! :add_documents, :edit_documents, :delete_documents + role.add_permission! :manage_documents + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a9/a9200a514553078059a3a448009b624853a28547.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a9/a9200a514553078059a3a448009b624853a28547.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,176 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::PluginTest < ActiveSupport::TestCase + def setup + @klass = Redmine::Plugin + # In case some real plugins are installed + @klass.clear + end + + def teardown + @klass.clear + end + + def test_register + @klass.register :foo do + name 'Foo plugin' + url 'http://example.net/plugins/foo' + author 'John Smith' + author_url 'http://example.net/jsmith' + description 'This is a test plugin' + version '0.0.1' + settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings' + end + + assert_equal 1, @klass.all.size + + plugin = @klass.find('foo') + assert plugin.is_a?(Redmine::Plugin) + assert_equal :foo, plugin.id + assert_equal 'Foo plugin', plugin.name + assert_equal 'http://example.net/plugins/foo', plugin.url + assert_equal 'John Smith', plugin.author + assert_equal 'http://example.net/jsmith', plugin.author_url + assert_equal 'This is a test plugin', plugin.description + assert_equal '0.0.1', plugin.version + end + + def test_installed + @klass.register(:foo) {} + assert_equal true, @klass.installed?(:foo) + assert_equal false, @klass.installed?(:bar) + end + + def test_menu + assert_difference 'Redmine::MenuManager.items(:project_menu).size' do + @klass.register :foo do + menu :project_menu, :foo_menu_item, '/foo', :caption => 'Foo' + end + end + menu_item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name == :foo_menu_item} + assert_not_nil menu_item + assert_equal 'Foo', menu_item.caption + assert_equal '/foo', menu_item.url + end + + def test_delete_menu_item + Redmine::MenuManager.map(:project_menu).push(:foo_menu_item, '/foo', :caption => 'Foo') + assert_difference 'Redmine::MenuManager.items(:project_menu).size', -1 do + @klass.register :foo do + delete_menu_item :project_menu, :foo_menu_item + end + end + assert_nil Redmine::MenuManager.items(:project_menu).detect {|i| i.name == :foo_menu_item} + end + + def test_directory_with_override + @klass.register(:foo) do + directory '/path/to/foo' + end + assert_equal '/path/to/foo', @klass.find('foo').directory + end + + def test_directory_without_override + @klass.register(:foo) {} + assert_equal File.join(@klass.directory, 'foo'), @klass.find('foo').directory + end + + def test_requires_redmine + plugin = Redmine::Plugin.register(:foo) {} + Redmine::VERSION.stubs(:to_a).returns([2, 1, 3, "stable", 10817]) + # Specific version without hash + assert plugin.requires_redmine('2.1.3') + assert plugin.requires_redmine('2.1') + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine('2.1.4') + end + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine('2.2') + end + # Specific version + assert plugin.requires_redmine(:version => '2.1.3') + assert plugin.requires_redmine(:version => ['2.1.3', '2.2.0']) + assert plugin.requires_redmine(:version => '2.1') + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version => '2.2.0') + end + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version => ['2.1.4', '2.2.0']) + end + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version => '2.2') + end + # Version range + assert plugin.requires_redmine(:version => '2.0.0'..'2.2.4') + assert plugin.requires_redmine(:version => '2.1.3'..'2.2.4') + assert plugin.requires_redmine(:version => '2.0.0'..'2.1.3') + assert plugin.requires_redmine(:version => '2.0'..'2.2') + assert plugin.requires_redmine(:version => '2.1'..'2.2') + assert plugin.requires_redmine(:version => '2.0'..'2.1') + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version => '2.1.4'..'2.2.4') + end + # Version or higher + assert plugin.requires_redmine(:version_or_higher => '0.1.0') + assert plugin.requires_redmine(:version_or_higher => '2.1.3') + assert plugin.requires_redmine(:version_or_higher => '2.1') + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version_or_higher => '2.2.0') + end + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version_or_higher => '2.2') + end + end + + def test_requires_redmine_plugin + test = self + other_version = '0.5.0' + @klass.register :other do + name 'Other' + version other_version + end + @klass.register :foo do + test.assert requires_redmine_plugin(:other, :version_or_higher => '0.1.0') + test.assert requires_redmine_plugin(:other, :version_or_higher => other_version) + test.assert requires_redmine_plugin(:other, other_version) + test.assert_raise Redmine::PluginRequirementError do + requires_redmine_plugin(:other, :version_or_higher => '99.0.0') + end + test.assert requires_redmine_plugin(:other, :version => other_version) + test.assert requires_redmine_plugin(:other, :version => [other_version, '99.0.0']) + test.assert_raise Redmine::PluginRequirementError do + requires_redmine_plugin(:other, :version => '99.0.0') + end + test.assert_raise Redmine::PluginRequirementError do + requires_redmine_plugin(:other, :version => ['98.0.0', '99.0.0']) + end + # Missing plugin + test.assert_raise Redmine::PluginNotFound do + requires_redmine_plugin(:missing, :version_or_higher => '0.1.0') + end + test.assert_raise Redmine::PluginNotFound do + requires_redmine_plugin(:missing, '0.1.0') + end + test.assert_raise Redmine::PluginNotFound do + requires_redmine_plugin(:missing, :version => '0.1.0') + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a9/a9407e7175c72cbb7b95800538757dd1ce8a907c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a9/a9407e7175c72cbb7b95800538757dd1ce8a907c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,73 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class News < ActiveRecord::Base + include Redmine::SafeAttributes + belongs_to :project + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on" + + validates_presence_of :title, :description + validates_length_of :title, :maximum => 60 + validates_length_of :summary, :maximum => 255 + + acts_as_attachable :delete_permission => :manage_news + acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project + acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} + acts_as_activity_provider :find_options => {:include => [:project, :author]}, + :author_key => :author_id + acts_as_watchable + + after_create :add_author_as_watcher + after_create :send_notification + + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args)) + } + + safe_attributes 'title', 'summary', 'description' + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_news, project) + end + + # Returns true if the news can be commented by user + def commentable?(user=User.current) + user.allowed_to?(:comment_news, project) + end + + def recipients + project.users.select {|user| user.notify_about?(self)}.map(&:mail) + end + + # returns latest news for projects visible by user + def self.latest(user = User.current, count = 5) + visible(user).includes([:author, :project]).order("#{News.table_name}.created_on DESC").limit(count).all + end + + private + + def add_author_as_watcher + Watcher.create(:watchable => self, :user => author) + end + + def send_notification + if Setting.notified_events.include?('news_added') + Mailer.news_added(self).deliver + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a9/a9a37407adbe18522fea7abeb3093d6c557988f5.svn-base --- a/.svn/pristine/a9/a9a37407adbe18522fea7abeb3093d6c557988f5.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1104 +0,0 @@ -# Finnish translations for Ruby on Rails -# by Marko Seppä (marko.seppa@gmail.com) - -fi: - direction: ltr - date: - formats: - default: "%e. %Bta %Y" - long: "%A%e. %Bta %Y" - short: "%e.%m.%Y" - - day_names: [Sunnuntai, Maanantai, Tiistai, Keskiviikko, Torstai, Perjantai, Lauantai] - abbr_day_names: [Su, Ma, Ti, Ke, To, Pe, La] - month_names: [~, Tammikuu, Helmikuu, Maaliskuu, Huhtikuu, Toukokuu, Kesäkuu, Heinäkuu, Elokuu, Syyskuu, Lokakuu, Marraskuu, Joulukuu] - abbr_month_names: [~, Tammi, Helmi, Maalis, Huhti, Touko, Kesä, Heinä, Elo, Syys, Loka, Marras, Joulu] - order: - - :day - - :month - - :year - - time: - formats: - default: "%a, %e. %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%e. %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "aamupäivä" - pm: "iltapäivä" - - support: - array: - words_connector: ", " - two_words_connector: " ja " - last_word_connector: " ja " - - - - number: - format: - separator: "," - delimiter: "." - precision: 3 - - currency: - format: - format: "%n %u" - unit: "€" - separator: "," - delimiter: "." - precision: 2 - - percentage: - format: - # separator: - delimiter: "" - # precision: - - precision: - format: - # separator: - delimiter: "" - # precision: - - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Tavua" - other: "Tavua" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - datetime: - distance_in_words: - half_a_minute: "puoli minuuttia" - less_than_x_seconds: - one: "aiemmin kuin sekunti" - other: "aiemmin kuin %{count} sekuntia" - x_seconds: - one: "sekunti" - other: "%{count} sekuntia" - less_than_x_minutes: - one: "aiemmin kuin minuutti" - other: "aiemmin kuin %{count} minuuttia" - x_minutes: - one: "minuutti" - other: "%{count} minuuttia" - about_x_hours: - one: "noin tunti" - other: "noin %{count} tuntia" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "päivä" - other: "%{count} päivää" - about_x_months: - one: "noin kuukausi" - other: "noin %{count} kuukautta" - x_months: - one: "kuukausi" - other: "%{count} kuukautta" - about_x_years: - one: "vuosi" - other: "noin %{count} vuotta" - over_x_years: - one: "yli vuosi" - other: "yli %{count} vuotta" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - prompts: - year: "Vuosi" - month: "Kuukausi" - day: "Päivä" - hour: "Tunti" - minute: "Minuutti" - second: "Sekuntia" - - activerecord: - errors: - template: - header: - one: "1 virhe esti tämän %{model} mallinteen tallentamisen" - other: "%{count} virhettä esti tämän %{model} mallinteen tallentamisen" - body: "Seuraavat kentät aiheuttivat ongelmia:" - messages: - inclusion: "ei löydy listauksesta" - exclusion: "on jo varattu" - invalid: "on kelvoton" - confirmation: "ei vastaa varmennusta" - accepted: "täytyy olla hyväksytty" - empty: "ei voi olla tyhjä" - blank: "ei voi olla sisällötön" - too_long: "on liian pitkä (maksimi on %{count} merkkiä)" - too_short: "on liian lyhyt (minimi on %{count} merkkiä)" - wrong_length: "on väärän pituinen (täytyy olla täsmälleen %{count} merkkiä)" - taken: "on jo käytössä" - not_a_number: "ei ole numero" - greater_than: "täytyy olla suurempi kuin %{count}" - greater_than_or_equal_to: "täytyy olla suurempi tai yhtä suuri kuin%{count}" - equal_to: "täytyy olla yhtä suuri kuin %{count}" - less_than: "täytyy olla pienempi kuin %{count}" - less_than_or_equal_to: "täytyy olla pienempi tai yhtä suuri kuin %{count}" - odd: "täytyy olla pariton" - even: "täytyy olla parillinen" - greater_than_start_date: "tulee olla aloituspäivän jälkeinen" - not_same_project: "ei kuulu samaan projektiin" - circular_dependency: "Tämä suhde loisi kehän." - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Valitse, ole hyvä - - general_text_No: 'Ei' - general_text_Yes: 'Kyllä' - general_text_no: 'ei' - general_text_yes: 'kyllä' - general_lang_name: 'Finnish (Suomi)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-15 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Tilin päivitys onnistui. - notice_account_invalid_creditentials: Virheellinen käyttäjätunnus tai salasana - notice_account_password_updated: Salasanan päivitys onnistui. - notice_account_wrong_password: Väärä salasana - notice_account_register_done: Tilin luonti onnistui. Aktivoidaksesi tilin seuraa linkkiä joka välitettiin sähköpostiisi. - notice_account_unknown_email: Tuntematon käyttäjä. - notice_can_t_change_password: Tämä tili käyttää ulkoista tunnistautumisjärjestelmää. Salasanaa ei voi muuttaa. - notice_account_lost_email_sent: Sinulle on lähetetty sähköposti jossa on ohje kuinka vaihdat salasanasi. - notice_account_activated: Tilisi on nyt aktivoitu, voit kirjautua sisälle. - notice_successful_create: Luonti onnistui. - notice_successful_update: Päivitys onnistui. - notice_successful_delete: Poisto onnistui. - notice_successful_connection: Yhteyden muodostus onnistui. - notice_file_not_found: Hakemaasi sivua ei löytynyt tai se on poistettu. - notice_locking_conflict: Toinen käyttäjä on päivittänyt tiedot. - notice_not_authorized: Sinulla ei ole oikeutta näyttää tätä sivua. - notice_email_sent: "Sähköposti on lähetty osoitteeseen %{value}" - notice_email_error: "Sähköpostilähetyksessä tapahtui virhe (%{value})" - notice_feeds_access_key_reseted: RSS salasana on nollaantunut. - notice_failed_to_save_issues: "%{count} Tapahtum(an/ien) tallennus epäonnistui %{total} valitut: %{ids}." - notice_no_issue_selected: "Tapahtumia ei ole valittu! Valitse tapahtumat joita haluat muokata." - notice_account_pending: "Tilisi on luotu ja odottaa ylläpitäjän hyväksyntää." - notice_default_data_loaded: Vakioasetusten palautus onnistui. - - error_can_t_load_default_data: "Vakioasetuksia ei voitu ladata: %{value}" - error_scm_not_found: "Syötettä ja/tai versiota ei löydy tietovarastosta." - error_scm_command_failed: "Tietovarastoon pääsyssä tapahtui virhe: %{value}" - - mail_subject_lost_password: "Sinun %{value} salasanasi" - mail_body_lost_password: 'Vaihtaaksesi salasanasi, napsauta seuraavaa linkkiä:' - mail_subject_register: "%{value} tilin aktivointi" - mail_body_register: 'Aktivoidaksesi tilisi, napsauta seuraavaa linkkiä:' - mail_body_account_information_external: "Voit nyt käyttää %{value} tiliäsi kirjautuaksesi järjestelmään." - mail_body_account_information: Sinun tilin tiedot - mail_subject_account_activation_request: "%{value} tilin aktivointi pyyntö" - mail_body_account_activation_request: "Uusi käyttäjä (%{value}) on rekisteröitynyt. Hänen tili odottaa hyväksyntääsi:" - - gui_validation_error: 1 virhe - gui_validation_error_plural: "%{count} virhettä" - - field_name: Nimi - field_description: Kuvaus - field_summary: Yhteenveto - field_is_required: Vaaditaan - field_firstname: Etunimi - field_lastname: Sukunimi - field_mail: Sähköposti - field_filename: Tiedosto - field_filesize: Koko - field_downloads: Latausta - field_author: Tekijä - field_created_on: Luotu - field_updated_on: Päivitetty - field_field_format: Muoto - field_is_for_all: Kaikille projekteille - field_possible_values: Mahdolliset arvot - field_regexp: Säännöllinen lauseke (reg exp) - field_min_length: Minimipituus - field_max_length: Maksimipituus - field_value: Arvo - field_category: Luokka - field_title: Otsikko - field_project: Projekti - field_issue: Tapahtuma - field_status: Tila - field_notes: Muistiinpanot - field_is_closed: Tapahtuma suljettu - field_is_default: Vakioarvo - field_tracker: Tapahtuma - field_subject: Aihe - field_due_date: Määräaika - field_assigned_to: Nimetty - field_priority: Prioriteetti - field_fixed_version: Kohdeversio - field_user: Käyttäjä - field_role: Rooli - field_homepage: Kotisivu - field_is_public: Julkinen - field_parent: Aliprojekti - field_is_in_roadmap: Tapahtumat näytetään roadmap näkymässä - field_login: Kirjautuminen - field_mail_notification: Sähköposti muistutukset - field_admin: Ylläpitäjä - field_last_login_on: Viimeinen yhteys - field_language: Kieli - field_effective_date: Päivä - field_password: Salasana - field_new_password: Uusi salasana - field_password_confirmation: Vahvistus - field_version: Versio - field_type: Tyyppi - field_host: Verkko-osoite - field_port: Portti - field_account: Tili - field_base_dn: Base DN - field_attr_login: Kirjautumismääre - field_attr_firstname: Etuminenmääre - field_attr_lastname: Sukunimenmääre - field_attr_mail: Sähköpostinmääre - field_onthefly: Automaattinen käyttäjien luonti - field_start_date: Alku - field_done_ratio: "% Tehty" - field_auth_source: Varmennusmuoto - field_hide_mail: Piiloita sähköpostiosoitteeni - field_comments: Kommentti - field_url: URL - field_start_page: Aloitussivu - field_subproject: Aliprojekti - field_hours: Tuntia - field_activity: Historia - field_spent_on: Päivä - field_identifier: Tunniste - field_is_filter: Käytetään suodattimena - field_issue_to: Liittyvä tapahtuma - field_delay: Viive - field_assignable: Tapahtumia voidaan nimetä tälle roolille - field_redirect_existing_links: Uudelleenohjaa olemassa olevat linkit - field_estimated_hours: Arvioitu aika - field_column_names: Saraketta - field_time_zone: Aikavyöhyke - field_searchable: Haettava - field_default_value: Vakioarvo - - setting_app_title: Ohjelman otsikko - setting_app_subtitle: Ohjelman alaotsikko - setting_welcome_text: Tervehdysteksti - setting_default_language: Vakiokieli - setting_login_required: Pakollinen kirjautuminen - setting_self_registration: Itserekisteröinti - setting_attachment_max_size: Liitteen maksimikoko - setting_issues_export_limit: Tapahtumien vientirajoite - setting_mail_from: Lähettäjän sähköpostiosoite - setting_bcc_recipients: Vastaanottajat piilokopiona (bcc) - setting_host_name: Verkko-osoite - setting_text_formatting: Tekstin muotoilu - setting_wiki_compression: Wiki historian pakkaus - setting_feeds_limit: Syötteen sisällön raja - setting_autofetch_changesets: Automaattisten muutosjoukkojen haku - setting_sys_api_enabled: Salli WS tietovaraston hallintaan - setting_commit_ref_keywords: Viittaavat hakusanat - setting_commit_fix_keywords: Korjaavat hakusanat - setting_autologin: Automaatinen kirjautuminen - setting_date_format: Päivän muoto - setting_time_format: Ajan muoto - setting_cross_project_issue_relations: Salli projektien väliset tapahtuminen suhteet - setting_issue_list_default_columns: Vakiosarakkeiden näyttö tapahtumalistauksessa - setting_emails_footer: Sähköpostin alatunniste - setting_protocol: Protokolla - setting_per_page_options: Sivun objektien määrän asetukset - - label_user: Käyttäjä - label_user_plural: Käyttäjät - label_user_new: Uusi käyttäjä - label_project: Projekti - label_project_new: Uusi projekti - label_project_plural: Projektit - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: Kaikki projektit - label_project_latest: Uusimmat projektit - label_issue: Tapahtuma - label_issue_new: Uusi tapahtuma - label_issue_plural: Tapahtumat - label_issue_view_all: Näytä kaikki tapahtumat - label_issues_by: "Tapahtumat %{value}" - label_document: Dokumentti - label_document_new: Uusi dokumentti - label_document_plural: Dokumentit - label_role: Rooli - label_role_plural: Roolit - label_role_new: Uusi rooli - label_role_and_permissions: Roolit ja oikeudet - label_member: Jäsen - label_member_new: Uusi jäsen - label_member_plural: Jäsenet - label_tracker: Tapahtuma - label_tracker_plural: Tapahtumat - label_tracker_new: Uusi tapahtuma - label_workflow: Työnkulku - label_issue_status: Tapahtuman tila - label_issue_status_plural: Tapahtumien tilat - label_issue_status_new: Uusi tila - label_issue_category: Tapahtumaluokka - label_issue_category_plural: Tapahtumaluokat - label_issue_category_new: Uusi luokka - label_custom_field: Räätälöity kenttä - label_custom_field_plural: Räätälöidyt kentät - label_custom_field_new: Uusi räätälöity kenttä - label_enumerations: Lista - label_enumeration_new: Uusi arvo - label_information: Tieto - label_information_plural: Tiedot - label_please_login: Kirjaudu ole hyvä - label_register: Rekisteröidy - label_password_lost: Hukattu salasana - label_home: Koti - label_my_page: Omasivu - label_my_account: Oma tili - label_my_projects: Omat projektit - label_administration: Ylläpito - label_login: Kirjaudu sisään - label_logout: Kirjaudu ulos - label_help: Ohjeet - label_reported_issues: Raportoidut tapahtumat - label_assigned_to_me_issues: Minulle nimetyt tapahtumat - label_last_login: Viimeinen yhteys - label_registered_on: Rekisteröity - label_activity: Historia - label_new: Uusi - label_logged_as: Kirjauduttu nimellä - label_environment: Ympäristö - label_authentication: Varmennus - label_auth_source: Varmennustapa - label_auth_source_new: Uusi varmennustapa - label_auth_source_plural: Varmennustavat - label_subproject_plural: Aliprojektit - label_min_max_length: Min - Max pituudet - label_list: Lista - label_date: Päivä - label_integer: Kokonaisluku - label_float: Liukuluku - label_boolean: Totuusarvomuuttuja - label_string: Merkkijono - label_text: Pitkä merkkijono - label_attribute: Määre - label_attribute_plural: Määreet - label_download: "%{count} Lataus" - label_download_plural: "%{count} Lataukset" - label_no_data: Ei tietoa näytettäväksi - label_change_status: Muutos tila - label_history: Historia - label_attachment: Tiedosto - label_attachment_new: Uusi tiedosto - label_attachment_delete: Poista tiedosto - label_attachment_plural: Tiedostot - label_report: Raportti - label_report_plural: Raportit - label_news: Uutinen - label_news_new: Lisää uutinen - label_news_plural: Uutiset - label_news_latest: Viimeisimmät uutiset - label_news_view_all: Näytä kaikki uutiset - label_settings: Asetukset - label_overview: Yleiskatsaus - label_version: Versio - label_version_new: Uusi versio - label_version_plural: Versiot - label_confirmation: Vahvistus - label_export_to: Vie - label_read: Lukee... - label_public_projects: Julkiset projektit - label_open_issues: avoin, yhteensä - label_open_issues_plural: avointa, yhteensä - label_closed_issues: suljettu - label_closed_issues_plural: suljettua - label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_total: Yhteensä - label_permissions: Oikeudet - label_current_status: Nykyinen tila - label_new_statuses_allowed: Uudet tilat sallittu - label_all: kaikki - label_none: ei mitään - label_nobody: ei kukaan - label_next: Seuraava - label_previous: Edellinen - label_used_by: Käytetty - label_details: Yksityiskohdat - label_add_note: Lisää muistiinpano - label_per_page: Per sivu - label_calendar: Kalenteri - label_months_from: kuukauden päässä - label_gantt: Gantt - label_internal: Sisäinen - label_last_changes: "viimeiset %{count} muutokset" - label_change_view_all: Näytä kaikki muutokset - label_personalize_page: Personoi tämä sivu - label_comment: Kommentti - label_comment_plural: Kommentit - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: Lisää kommentti - label_comment_added: Kommentti lisätty - label_comment_delete: Poista kommentti - label_query: Räätälöity haku - label_query_plural: Räätälöidyt haut - label_query_new: Uusi haku - label_filter_add: Lisää suodatin - label_filter_plural: Suodattimet - label_equals: sama kuin - label_not_equals: eri kuin - label_in_less_than: pienempi kuin - label_in_more_than: suurempi kuin - label_today: tänään - label_this_week: tällä viikolla - label_less_than_ago: vähemmän kuin päivää sitten - label_more_than_ago: enemän kuin päivää sitten - label_ago: päiviä sitten - label_contains: sisältää - label_not_contains: ei sisällä - label_day_plural: päivää - label_repository: Tietovarasto - label_repository_plural: Tietovarastot - label_browse: Selaus - label_modification: "%{count} muutos" - label_modification_plural: "%{count} muutettu" - label_revision: Versio - label_revision_plural: Versiot - label_added: lisätty - label_modified: muokattu - label_deleted: poistettu - label_latest_revision: Viimeisin versio - label_latest_revision_plural: Viimeisimmät versiot - label_view_revisions: Näytä versiot - label_max_size: Suurin koko - label_sort_highest: Siirrä ylimmäiseksi - label_sort_higher: Siirrä ylös - label_sort_lower: Siirrä alas - label_sort_lowest: Siirrä alimmaiseksi - label_roadmap: Roadmap - label_roadmap_due_in: "Määräaika %{value}" - label_roadmap_overdue: "%{value} myöhässä" - label_roadmap_no_issues: Ei tapahtumia tälle versiolle - label_search: Haku - label_result_plural: Tulokset - label_all_words: kaikki sanat - label_wiki: Wiki - label_wiki_edit: Wiki muokkaus - label_wiki_edit_plural: Wiki muokkaukset - label_wiki_page: Wiki sivu - label_wiki_page_plural: Wiki sivut - label_index_by_title: Hakemisto otsikoittain - label_index_by_date: Hakemisto päivittäin - label_current_version: Nykyinen versio - label_preview: Esikatselu - label_feed_plural: Syötteet - label_changes_details: Kaikkien muutosten yksityiskohdat - label_issue_tracking: Tapahtumien seuranta - label_spent_time: Käytetty aika - label_f_hour: "%{value} tunti" - label_f_hour_plural: "%{value} tuntia" - label_time_tracking: Ajan seuranta - label_change_plural: Muutokset - label_statistics: Tilastot - label_commits_per_month: Tapahtumaa per kuukausi - label_commits_per_author: Tapahtumaa per tekijä - label_view_diff: Näytä erot - label_diff_inline: sisällössä - label_diff_side_by_side: vierekkäin - label_options: Valinnat - label_copy_workflow_from: Kopioi työnkulku - label_permissions_report: Oikeuksien raportti - label_watched_issues: Seurattavat tapahtumat - label_related_issues: Liittyvät tapahtumat - label_applied_status: Lisätty tila - label_loading: Lataa... - label_relation_new: Uusi suhde - label_relation_delete: Poista suhde - label_relates_to: liittyy - label_duplicates: kopio - label_blocks: estää - label_blocked_by: estetty - label_precedes: edeltää - label_follows: seuraa - label_end_to_start: lopusta alkuun - label_end_to_end: lopusta loppuun - label_start_to_start: alusta alkuun - label_start_to_end: alusta loppuun - label_stay_logged_in: Pysy kirjautuneena - label_disabled: poistettu käytöstä - label_show_completed_versions: Näytä valmiit versiot - label_me: minä - label_board: Keskustelupalsta - label_board_new: Uusi keskustelupalsta - label_board_plural: Keskustelupalstat - label_topic_plural: Aiheet - label_message_plural: Viestit - label_message_last: Viimeisin viesti - label_message_new: Uusi viesti - label_reply_plural: Vastaukset - label_send_information: Lähetä tilin tiedot käyttäjälle - label_year: Vuosi - label_month: Kuukausi - label_week: Viikko - label_language_based: Pohjautuen käyttäjän kieleen - label_sort_by: "Lajittele %{value}" - label_send_test_email: Lähetä testi sähköposti - label_feeds_access_key_created_on: "RSS salasana luotiin %{value} sitten" - label_module_plural: Moduulit - label_added_time_by: "Lisännyt %{author} %{age} sitten" - label_updated_time: "Päivitetty %{value} sitten" - label_jump_to_a_project: Siirry projektiin... - label_file_plural: Tiedostot - label_changeset_plural: Muutosryhmät - label_default_columns: Vakiosarakkeet - label_no_change_option: (Ei muutosta) - label_bulk_edit_selected_issues: Perusmuotoile valitut tapahtumat - label_theme: Teema - label_default: Vakio - label_search_titles_only: Hae vain otsikot - label_user_mail_option_all: "Kaikista tapahtumista kaikissa projekteistani" - label_user_mail_option_selected: "Kaikista tapahtumista vain valitsemistani projekteista..." - label_user_mail_no_self_notified: "En halua muistutusta muutoksista joita itse teen" - label_registration_activation_by_email: tilin aktivointi sähköpostitse - label_registration_manual_activation: tilin aktivointi käsin - label_registration_automatic_activation: tilin aktivointi automaattisesti - label_display_per_page: "Per sivu: %{value}" - label_age: Ikä - label_change_properties: Vaihda asetuksia - label_general: Yleinen - - button_login: Kirjaudu - button_submit: Lähetä - button_save: Tallenna - button_check_all: Valitse kaikki - button_uncheck_all: Poista valinnat - button_delete: Poista - button_create: Luo - button_test: Testaa - button_edit: Muokkaa - button_add: Lisää - button_change: Muuta - button_apply: Ota käyttöön - button_clear: Tyhjää - button_lock: Lukitse - button_unlock: Vapauta - button_download: Lataa - button_list: Lista - button_view: Näytä - button_move: Siirrä - button_back: Takaisin - button_cancel: Peruuta - button_activate: Aktivoi - button_sort: Järjestä - button_log_time: Seuraa aikaa - button_rollback: Siirry takaisin tähän versioon - button_watch: Seuraa - button_unwatch: Älä seuraa - button_reply: Vastaa - button_archive: Arkistoi - button_unarchive: Palauta - button_reset: Nollaus - button_rename: Uudelleen nimeä - button_change_password: Vaihda salasana - button_copy: Kopioi - button_annotate: Lisää selitys - button_update: Päivitä - - status_active: aktiivinen - status_registered: rekisteröity - status_locked: lukittu - - text_select_mail_notifications: Valitse tapahtumat joista tulisi lähettää sähköpostimuistutus. - text_regexp_info: esim. ^[A-Z0-9]+$ - text_min_max_length_info: 0 tarkoittaa, ei rajoitusta - text_project_destroy_confirmation: Oletko varma että haluat poistaa tämän projektin ja kaikki siihen kuuluvat tiedot? - text_workflow_edit: Valitse rooli ja tapahtuma muokataksesi työnkulkua - text_are_you_sure: Oletko varma? - text_tip_issue_begin_day: tehtävä joka alkaa tänä päivänä - text_tip_issue_end_day: tehtävä joka loppuu tänä päivänä - text_tip_issue_begin_end_day: tehtävä joka alkaa ja loppuu tänä päivänä - text_caracters_maximum: "%{count} merkkiä enintään." - text_caracters_minimum: "Täytyy olla vähintään %{count} merkkiä pitkä." - text_length_between: "Pituus välillä %{min} ja %{max} merkkiä." - text_tracker_no_workflow: Työnkulkua ei määritelty tälle tapahtumalle - text_unallowed_characters: Kiellettyjä merkkejä - text_comma_separated: Useat arvot sallittu (pilkku eroteltuna). - text_issues_ref_in_commit_messages: Liitän ja korjaan ongelmia syötetyssä viestissä - text_issue_added: "Issue %{id} has been reported by %{author}." - text_issue_updated: "Issue %{id} has been updated by %{author}." - text_wiki_destroy_confirmation: Oletko varma että haluat poistaa tämän wiki:n ja kaikki sen sisältämän tiedon? - text_issue_category_destroy_question: "Jotkut tapahtumat (%{count}) ovat nimetty tälle luokalle. Mitä haluat tehdä?" - text_issue_category_destroy_assignments: Poista luokan tehtävät - text_issue_category_reassign_to: Vaihda tapahtuma tähän luokkaan - text_user_mail_option: "Valitsemattomille projekteille, saat vain muistutuksen asioista joita seuraat tai olet mukana (esim. tapahtumat joissa olet tekijä tai nimettynä)." - text_no_configuration_data: "Rooleja, tapahtumien tiloja ja työnkulkua ei vielä olla määritelty.\nOn erittäin suotavaa ladata vakioasetukset. Voit muuttaa sitä latauksen jälkeen." - text_load_default_configuration: Lataa vakioasetukset - - default_role_manager: Päälikkö - default_role_developer: Kehittäjä - default_role_reporter: Tarkastelija - default_tracker_bug: Ohjelmointivirhe - default_tracker_feature: Ominaisuus - default_tracker_support: Tuki - default_issue_status_new: Uusi - default_issue_status_in_progress: In Progress - default_issue_status_resolved: Hyväksytty - default_issue_status_feedback: Palaute - default_issue_status_closed: Suljettu - default_issue_status_rejected: Hylätty - default_doc_category_user: Käyttäjä dokumentaatio - default_doc_category_tech: Tekninen dokumentaatio - default_priority_low: Matala - default_priority_normal: Normaali - default_priority_high: Korkea - default_priority_urgent: Kiireellinen - default_priority_immediate: Valitön - default_activity_design: Suunnittelu - default_activity_development: Kehitys - - enumeration_issue_priorities: Tapahtuman tärkeysjärjestys - enumeration_doc_categories: Dokumentin luokat - enumeration_activities: Historia (ajan seuranta) - label_associated_revisions: Liittyvät versiot - setting_user_format: Käyttäjien esitysmuoto - text_status_changed_by_changeset: "Päivitetty muutosversioon %{value}." - text_issues_destroy_confirmation: 'Oletko varma että haluat poistaa valitut tapahtumat ?' - label_more: Lisää - label_issue_added: Tapahtuma lisätty - label_issue_updated: Tapahtuma päivitetty - label_document_added: Dokumentti lisätty - label_message_posted: Viesti lisätty - label_file_added: Tiedosto lisätty - label_scm: SCM - text_select_project_modules: 'Valitse modulit jotka haluat käyttöön tähän projektiin:' - label_news_added: Uutinen lisätty - project_module_boards: Keskustelupalsta - project_module_issue_tracking: Tapahtuman seuranta - project_module_wiki: Wiki - project_module_files: Tiedostot - project_module_documents: Dokumentit - project_module_repository: Tietovarasto - project_module_news: Uutiset - project_module_time_tracking: Ajan seuranta - text_file_repository_writable: Kirjoitettava tiedostovarasto - text_default_administrator_account_changed: Vakio hallinoijan tunnus muutettu - text_rmagick_available: RMagick saatavilla (valinnainen) - button_configure: Asetukset - label_plugins: Lisäosat - label_ldap_authentication: LDAP tunnistautuminen - label_downloads_abbr: D/L - label_add_another_file: Lisää uusi tiedosto - label_this_month: tässä kuussa - text_destroy_time_entries_question: "%{hours} tuntia on raportoitu tapahtumasta jonka aiot poistaa. Mitä haluat tehdä ?" - label_last_n_days: "viimeiset %{count} päivää" - label_all_time: koko ajalta - error_issue_not_found_in_project: 'Tapahtumaa ei löytynyt tai se ei kuulu tähän projektiin' - label_this_year: tänä vuonna - text_assign_time_entries_to_project: Määritä tunnit projektille - label_date_range: Aikaväli - label_last_week: viime viikolla - label_yesterday: eilen - label_optional_description: Lisäkuvaus - label_last_month: viime kuussa - text_destroy_time_entries: Poista raportoidut tunnit - text_reassign_time_entries: 'Siirrä raportoidut tunnit tälle tapahtumalle:' - label_chronological_order: Aikajärjestyksessä - label_date_to: '' - setting_activity_days_default: Päivien esittäminen projektien historiassa - label_date_from: '' - label_in: '' - setting_display_subprojects_issues: Näytä aliprojektien tapahtumat pääprojektissa oletusarvoisesti - field_comments_sorting: Näytä kommentit - label_reverse_chronological_order: Käänteisessä aikajärjestyksessä - label_preferences: Asetukset - setting_default_projects_public: Uudet projektit ovat oletuksena julkisia - label_overall_activity: Kokonaishistoria - error_scm_annotate: "Merkintää ei ole tai siihen ei voi lisätä selityksiä." - label_planning: Suunnittelu - text_subprojects_destroy_warning: "Tämän aliprojekti(t): %{value} tullaan myös poistamaan." - label_and_its_subprojects: "%{value} ja aliprojektit" - mail_body_reminder: "%{count} sinulle nimettyä tapahtuma(a) erääntyy %{days} päivä sisään:" - mail_subject_reminder: "%{count} tapahtuma(a) erääntyy %{days} lähipäivinä" - text_user_wrote: "%{value} kirjoitti:" - label_duplicated_by: kopioinut - setting_enabled_scm: Versionhallinta käytettävissä - text_enumeration_category_reassign_to: 'Siirrä täksi arvoksi:' - text_enumeration_destroy_question: "%{count} kohdetta on sijoitettu tälle arvolle." - label_incoming_emails: Saapuvat sähköpostiviestit - label_generate_key: Luo avain - setting_mail_handler_api_enabled: Ota käyttöön WS saapuville sähköposteille - setting_mail_handler_api_key: API avain - text_email_delivery_not_configured: "Sähköpostin jakelu ei ole määritelty ja sähköpostimuistutukset eivät ole käytössä.\nKonfiguroi sähköpostipalvelinasetukset (SMTP) config/configuration.yml tiedostosta ja uudelleenkäynnistä sovellus jotta asetukset astuvat voimaan." - field_parent_title: Aloitussivu - label_issue_watchers: Tapahtuman seuraajat - button_quote: Vastaa - setting_sequential_project_identifiers: Luo peräkkäiset projektien tunnisteet - notice_unable_delete_version: Version poisto epäonnistui - label_renamed: uudelleennimetty - label_copied: kopioitu - setting_plain_text_mail: vain muotoilematonta tekstiä (ei HTML) - permission_view_files: Näytä tiedostot - permission_edit_issues: Muokkaa tapahtumia - permission_edit_own_time_entries: Muokka omia aikamerkintöjä - permission_manage_public_queries: Hallinnoi julkisia hakuja - permission_add_issues: Lisää tapahtumia - permission_log_time: Lokita käytettyä aikaa - permission_view_changesets: Näytä muutosryhmät - permission_view_time_entries: Näytä käytetty aika - permission_manage_versions: Hallinnoi versioita - permission_manage_wiki: Hallinnoi wikiä - permission_manage_categories: Hallinnoi tapahtumien luokkia - permission_protect_wiki_pages: Suojaa wiki sivut - permission_comment_news: Kommentoi uutisia - permission_delete_messages: Poista viestit - permission_select_project_modules: Valitse projektin modulit - permission_manage_documents: Hallinnoi dokumentteja - permission_edit_wiki_pages: Muokkaa wiki sivuja - permission_add_issue_watchers: Lisää seuraajia - permission_view_gantt: Näytä gantt kaavio - permission_move_issues: Siirrä tapahtuma - permission_manage_issue_relations: Hallinoi tapahtuman suhteita - permission_delete_wiki_pages: Poista wiki sivuja - permission_manage_boards: Hallinnoi keskustelupalstaa - permission_delete_wiki_pages_attachments: Poista liitteitä - permission_view_wiki_edits: Näytä wiki historia - permission_add_messages: Jätä viesti - permission_view_messages: Näytä viestejä - permission_manage_files: Hallinnoi tiedostoja - permission_edit_issue_notes: Muokkaa muistiinpanoja - permission_manage_news: Hallinnoi uutisia - permission_view_calendar: Näytä kalenteri - permission_manage_members: Hallinnoi jäseniä - permission_edit_messages: Muokkaa viestejä - permission_delete_issues: Poista tapahtumia - permission_view_issue_watchers: Näytä seuraaja lista - permission_manage_repository: Hallinnoi tietovarastoa - permission_commit_access: Tee pääsyoikeus - permission_browse_repository: Selaa tietovarastoa - permission_view_documents: Näytä dokumentit - permission_edit_project: Muokkaa projektia - permission_add_issue_notes: Lisää muistiinpanoja - permission_save_queries: Tallenna hakuja - permission_view_wiki_pages: Näytä wiki - permission_rename_wiki_pages: Uudelleennimeä wiki sivuja - permission_edit_time_entries: Muokkaa aika lokeja - permission_edit_own_issue_notes: Muokkaa omia muistiinpanoja - setting_gravatar_enabled: Käytä Gravatar käyttäjä ikoneita - label_example: Esimerkki - text_repository_usernames_mapping: "Valitse päivittääksesi Redmine käyttäjä jokaiseen käyttäjään joka löytyy tietovaraston lokista.\nKäyttäjät joilla on sama Redmine ja tietovaraston käyttäjänimi tai sähköpostiosoite, yhdistetään automaattisesti." - permission_edit_own_messages: Muokkaa omia viestejä - permission_delete_own_messages: Poista omia viestejä - label_user_activity: "Käyttäjän %{value} historia" - label_updated_time_by: "Updated by %{author} %{age} ago" - text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' - setting_diff_max_lines_displayed: Max number of diff lines displayed - text_plugin_assets_writable: Plugin assets directory writable - warning_attachments_not_saved: "%{count} file(s) could not be saved." - button_create_and_continue: Create and continue - text_custom_field_possible_values_info: 'One line for each value' - label_display: Display - field_editable: Editable - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_file_max_size_displayed: Max size of text files displayed inline - field_watcher: Watcher - setting_openid: Allow OpenID login and registration - field_identity_url: OpenID URL - label_login_with_open_id_option: or login with OpenID - field_content: Content - label_descending: Descending - label_sort: Sort - label_ascending: Ascending - label_date_from_to: From %{start} to %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? - text_wiki_page_reassign_children: Reassign child pages to this parent page - text_wiki_page_nullify_children: Keep child pages as root pages - text_wiki_page_destroy_children: Delete child pages and all their descendants - setting_password_min_length: Minimum password length - field_group_by: Group results by - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - label_wiki_content_added: Wiki page added - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: The '%{id}' wiki page has been added by %{author}. - label_wiki_content_updated: Wiki page updated - mail_body_wiki_content_updated: The '%{id}' wiki page has been updated by %{author}. - permission_add_project: Create project - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - label_view_all_revisions: View all revisions - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. - error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). - text_journal_changed: "%{label} changed from %{old} to %{new}" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} deleted (%{old})" - label_group_plural: Groups - label_group: Group - label_group_new: New group - label_time_entry_plural: Spent time - text_journal_added: "%{label} %{value} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Tee viestien koodaus - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 tapahtuma - one: 1 tapahtuma - other: "%{count} tapahtumat" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: kaikki - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: With subprojects - label_cross_project_tree: With project tree - label_cross_project_hierarchy: With project hierarchy - label_cross_project_system: With all projects - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/a9/a9d205d21aabcb1747a69fc0912d6918652b3b67.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a9/a9d205d21aabcb1747a69fc0912d6918652b3b67.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1270 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'forwardable' +require 'cgi' + +module ApplicationHelper + include Redmine::WikiFormatting::Macros::Definitions + include Redmine::I18n + include GravatarHelper::PublicMethods + include Redmine::Pagination::Helper + + extend Forwardable + def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter + + # Return true if user is authorized for controller/action, otherwise false + def authorize_for(controller, action) + User.current.allowed_to?({:controller => controller, :action => action}, @project) + end + + # Display a link if user is authorized + # + # @param [String] name Anchor text (passed to link_to) + # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized + # @param [optional, Hash] html_options Options passed to link_to + # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to + def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) + link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) + end + + # Displays a link to user's account page if active + def link_to_user(user, options={}) + if user.is_a?(User) + name = h(user.name(options[:format])) + if user.active? || (User.current.admin? && user.logged?) + link_to name, user_path(user), :class => user.css_classes + else + name + end + else + h(user.to_s) + end + end + + # Displays a link to +issue+ with its subject. + # Examples: + # + # link_to_issue(issue) # => Defect #6: This is the subject + # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... + # link_to_issue(issue, :subject => false) # => Defect #6 + # link_to_issue(issue, :project => true) # => Foo - Defect #6 + # link_to_issue(issue, :subject => false, :tracker => false) # => #6 + # + def link_to_issue(issue, options={}) + title = nil + subject = nil + text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" + if options[:subject] == false + title = truncate(issue.subject, :length => 60) + else + subject = issue.subject + if options[:truncate] + subject = truncate(subject, :length => options[:truncate]) + end + end + only_path = options[:only_path].nil? ? true : options[:only_path] + s = link_to text, issue_path(issue, :only_path => only_path), :class => issue.css_classes, :title => title + s << h(": #{subject}") if subject + s = h("#{issue.project} - ") + s if options[:project] + s + end + + # Generates a link to an attachment. + # Options: + # * :text - Link text (default to attachment filename) + # * :download - Force download (default: false) + def link_to_attachment(attachment, options={}) + text = options.delete(:text) || attachment.filename + route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path + html_options = options.slice!(:only_path) + url = send(route_method, attachment, attachment.filename, options) + link_to text, url, html_options + end + + # Generates a link to a SCM revision + # Options: + # * :text - Link text (default to the formatted revision) + def link_to_revision(revision, repository, options={}) + if repository.is_a?(Project) + repository = repository.repository + end + text = options.delete(:text) || format_revision(revision) + rev = revision.respond_to?(:identifier) ? revision.identifier : revision + link_to( + h(text), + {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, + :title => l(:label_revision_id, format_revision(revision)) + ) + end + + # Generates a link to a message + def link_to_message(message, options={}, html_options = nil) + link_to( + truncate(message.subject, :length => 60), + board_message_path(message.board_id, message.parent_id || message.id, { + :r => (message.parent_id && message.id), + :anchor => (message.parent_id ? "message-#{message.id}" : nil) + }.merge(options)), + html_options + ) + end + + # Generates a link to a project if active + # Examples: + # + # link_to_project(project) # => link to the specified project overview + # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options + # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) + # + def link_to_project(project, options={}, html_options = nil) + if project.archived? + h(project.name) + elsif options.key?(:action) + ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0." + url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) + link_to project.name, url, html_options + else + link_to project.name, project_path(project, options), html_options + end + end + + # Generates a link to a project settings if active + def link_to_project_settings(project, options={}, html_options=nil) + if project.active? + link_to project.name, settings_project_path(project, options), html_options + elsif project.archived? + h(project.name) + else + link_to project.name, project_path(project, options), html_options + end + end + + def wiki_page_path(page, options={}) + url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options)) + end + + def thumbnail_tag(attachment) + link_to image_tag(thumbnail_path(attachment)), + named_attachment_path(attachment, attachment.filename), + :title => attachment.filename + end + + def toggle_link(name, id, options={}) + onclick = "$('##{id}').toggle(); " + onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") + onclick << "return false;" + link_to(name, "#", :onclick => onclick) + end + + def image_to_function(name, function, html_options = {}) + html_options.symbolize_keys! + tag(:input, html_options.merge({ + :type => "image", :src => image_path(name), + :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" + })) + end + + def format_activity_title(text) + h(truncate_single_line(text, :length => 100)) + end + + def format_activity_day(date) + date == User.current.today ? l(:label_today).titleize : format_date(date) + end + + def format_activity_description(text) + h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') + ).gsub(/[\r\n]+/, "
    ").html_safe + end + + def format_version_name(version) + if version.project == @project + h(version) + else + h("#{version.project} - #{version}") + end + end + + def due_date_distance_in_words(date) + if date + l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) + end + end + + # 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_nested_lists(projects) + s = '' + if projects.any? + ancestors = [] + original_project = @project + projects.sort_by(&:lft).each do |project| + # set the project environment to please macros. + @project = project + if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) + s << "
      \n" + else + ancestors.pop + s << "" + while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) + ancestors.pop + s << "
    \n" + end + end + classes = (ancestors.empty? ? 'root' : 'child') + s << "
  • " + s << h(block_given? ? yield(project) : project.name) + s << "
    \n" + ancestors << project + end + s << ("
  • \n" * ancestors.size) + @project = original_project + end + s.html_safe + end + + def render_page_hierarchy(pages, node=nil, options={}) + content = '' + if pages[node] + content << "
      \n" + pages[node].each do |page| + content << "
    • " + content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil}, + :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) + content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] + content << "
    • \n" + end + content << "
    \n" + end + content.html_safe + end + + # Renders flash messages + def render_flash_messages + s = '' + flash.each do |k,v| + s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") + end + s.html_safe + end + + # Renders tabs and their content + def render_tabs(tabs) + if tabs.any? + render :partial => 'common/tabs', :locals => {:tabs => tabs} + else + content_tag 'p', l(:label_no_data), :class => "nodata" + end + end + + # Renders the project quick-jump box + def render_project_jump_box + return unless User.current.logged? + projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq + if projects.any? + options = + ("" + + '').html_safe + + options << project_tree_options_for_select(projects, :selected => @project) do |p| + { :value => project_path(:id => p, :jump => current_menu_item) } + end + + select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') + end + end + + def project_tree_options_for_select(projects, options = {}) + s = '' + project_tree(projects) do |project, level| + name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe + tag_options = {:value => project.id} + if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) + tag_options[:selected] = 'selected' + else + tag_options[:selected] = nil + end + tag_options.merge!(yield(project)) if block_given? + s << content_tag('option', name_prefix + h(project), tag_options) + end + s.html_safe + end + + # Yields the given block for each project with its level in the tree + # + # Wrapper for Project#project_tree + def project_tree(projects, &block) + Project.project_tree(projects, &block) + end + + def principals_check_box_tags(name, principals) + s = '' + principals.each do |principal| + s << "\n" + end + s.html_safe + end + + # Returns a string for users/groups option tags + def principals_options_for_select(collection, selected=nil) + s = '' + if collection.include?(User.current) + s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) + end + groups = '' + collection.sort.each do |element| + selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected + (element.is_a?(Group) ? groups : s) << %() + end + unless groups.empty? + s << %(#{groups}) + end + s.html_safe + end + + # Options for the new membership projects combo-box + def options_for_membership_project_select(principal, projects) + options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") + options << project_tree_options_for_select(projects) do |p| + {:disabled => principal.projects.to_a.include?(p)} + end + options + end + + def option_tag(name, text, value, selected=nil, options={}) + content_tag 'option', value, options.merge(:value => value, :selected => (value == selected)) + end + + # Truncates and returns the string as a single line + def truncate_single_line(string, *args) + truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') + end + + # Truncates at line break after 250 characters or options[:length] + def truncate_lines(string, options={}) + length = options[:length] || 250 + if string.to_s =~ /\A(.{#{length}}.*?)$/m + "#{$1}..." + else + string + end + end + + def anchor(text) + text.to_s.gsub(' ', '_') + end + + def html_hours(text) + text.gsub(%r{(\d+)\.(\d+)}, '\1.\2').html_safe + end + + def authoring(created, author, options={}) + l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe + end + + def time_tag(time) + text = distance_of_time_in_words(Time.now, time) + if @project + link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) + else + content_tag('abbr', text, :title => format_time(time)) + end + end + + def syntax_highlight_lines(name, content) + lines = [] + syntax_highlight(name, content).each_line { |line| lines << line } + lines + end + + def syntax_highlight(name, content) + Redmine::SyntaxHighlighting.highlight_by_filename(content, name) + end + + def to_path_param(path) + str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") + str.blank? ? nil : str + end + + def reorder_links(name, url, method = :post) + link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), + url.merge({"#{name}[move_to]" => 'highest'}), + :method => method, :title => l(:label_sort_highest)) + + link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), + url.merge({"#{name}[move_to]" => 'higher'}), + :method => method, :title => l(:label_sort_higher)) + + link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), + url.merge({"#{name}[move_to]" => 'lower'}), + :method => method, :title => l(:label_sort_lower)) + + link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), + url.merge({"#{name}[move_to]" => 'lowest'}), + :method => method, :title => l(:label_sort_lowest)) + end + + def breadcrumb(*args) + elements = args.flatten + elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil + end + + def other_formats_links(&block) + concat('

    '.html_safe + l(:label_export_to)) + yield Redmine::Views::OtherFormatsBuilder.new(self) + concat('

    '.html_safe) + end + + def page_header_title + if @project.nil? || @project.new_record? + h(Setting.app_title) + else + b = [] + ancestors = (@project.root? ? [] : @project.ancestors.visible.all) + if ancestors.any? + root = ancestors.shift + b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') + if ancestors.size > 2 + b << "\xe2\x80\xa6" + ancestors = ancestors[-2, 2] + end + b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } + end + b << h(@project) + b.join(" \xc2\xbb ").html_safe + end + end + + # Returns a h2 tag and sets the html title with the given arguments + def title(*args) + strings = args.map do |arg| + if arg.is_a?(Array) && arg.size >= 2 + link_to(*arg) + else + h(arg.to_s) + end + end + html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s} + content_tag('h2', strings.join(' » ').html_safe) + end + + # Sets the html title + # Returns the html title when called without arguments + # Current project name and app_title and automatically appended + # Exemples: + # html_title 'Foo', 'Bar' + # html_title # => 'Foo - Bar - My Project - Redmine' + def html_title(*args) + if args.empty? + title = @html_title || [] + title << @project.name if @project + title << Setting.app_title unless Setting.app_title == title.last + title.reject(&:blank?).join(' - ') + else + @html_title ||= [] + @html_title += args + end + end + + # Returns the theme, controller name, and action as css classes for the + # HTML body. + def body_css_classes + css = [] + if theme = Redmine::Themes.theme(Setting.ui_theme) + css << 'theme-' + theme.name + end + + css << 'project-' + @project.identifier if @project && @project.identifier.present? + css << 'controller-' + controller_name + css << 'action-' + action_name + css.join(' ') + end + + def accesskey(s) + @used_accesskeys ||= [] + key = Redmine::AccessKeys.key_for(s) + return nil if @used_accesskeys.include?(key) + @used_accesskeys << key + key + end + + # Formats text according to system settings. + # 2 ways to call this method: + # * with a String: textilizable(text, options) + # * with an object and one of its attribute: textilizable(issue, :description, options) + def textilizable(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + case args.size + when 1 + obj = options[:object] + text = args.shift + when 2 + obj = args.shift + attr = args.shift + text = obj.send(attr).to_s + else + raise ArgumentError, 'invalid arguments to textilizable' + end + return '' if text.blank? + project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) + only_path = options.delete(:only_path) == false ? false : true + + text = text.dup + macros = catch_macros(text) + text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) + + @parsed_headings = [] + @heading_anchors = {} + @current_section = 0 if options[:edit_section_links] + + parse_sections(text, project, obj, attr, only_path, options) + text = parse_non_pre_blocks(text, obj, macros) do |text| + [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| + send method_name, text, project, obj, attr, only_path, options + end + end + parse_headings(text, project, obj, attr, only_path, options) + + if @parsed_headings.any? + replace_toc(text, @parsed_headings) + end + + text.html_safe + end + + def parse_non_pre_blocks(text, obj, macros) + s = StringScanner.new(text) + tags = [] + parsed = '' + while !s.eos? + s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) + text, full_tag, closing, tag = s[1], s[2], s[3], s[4] + if tags.empty? + yield text + inject_macros(text, obj, macros) if macros.any? + else + inject_macros(text, obj, macros, false) if macros.any? + end + parsed << text + if tag + if closing + if tags.last == tag.downcase + tags.pop + end + else + tags << tag.downcase + end + parsed << full_tag + end + end + # Close any non closing tags + while tag = tags.pop + parsed << "" + end + parsed + end + + def parse_inline_attachments(text, project, obj, attr, only_path, options) + # when using an image link, try to use an attachment, if possible + attachments = options[:attachments] || [] + attachments += obj.attachments if obj.respond_to?(:attachments) + if attachments.present? + text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| + filename, ext, alt, alttext = $1.downcase, $2, $3, $4 + # search for the picture in attachments + if found = Attachment.latest_attach(attachments, filename) + image_url = download_named_attachment_path(found, found.filename, :only_path => only_path) + desc = found.description.to_s.gsub('"', '') + if !desc.blank? && alttext.blank? + alt = " title=\"#{desc}\" alt=\"#{desc}\"" + end + "src=\"#{image_url}\"#{alt}" + else + m + end + end + end + end + + # Wiki links + # + # Examples: + # [[mypage]] + # [[mypage|mytext]] + # wiki links can refer other project wikis, using project name or identifier: + # [[project:]] -> wiki starting page + # [[project:|mytext]] + # [[project:mypage]] + # [[project:mypage|mytext]] + def parse_wiki_links(text, project, obj, attr, only_path, options) + text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| + link_project = project + esc, all, page, title = $1, $2, $3, $5 + if esc.nil? + if page =~ /^([^\:]+)\:(.*)$/ + identifier, page = $1, $2 + link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier) + title ||= identifier if page.blank? + end + + if link_project && link_project.wiki + # extract anchor + anchor = nil + if page =~ /^(.+?)\#(.+)$/ + page, anchor = $1, $2 + end + anchor = sanitize_anchor_name(anchor) if anchor.present? + # check if page exists + wiki_page = link_project.wiki.find_page(page) + url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page + "##{anchor}" + else + case options[:wiki_links] + when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') + when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export + else + wiki_page_id = page.present? ? Wiki.titleize(page) : nil + parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil + url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, + :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) + end + end + link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) + else + # project or wiki doesn't exist + all + end + else + all + end + end + end + + # Redmine links + # + # Examples: + # Issues: + # #52 -> Link to issue #52 + # Changesets: + # r52 -> Link to revision 52 + # commit:a85130f -> Link to scmid starting with a85130f + # Documents: + # document#17 -> Link to document with id 17 + # document:Greetings -> Link to the document with title "Greetings" + # document:"Some document" -> Link to the document with title "Some document" + # Versions: + # version#3 -> Link to version with id 3 + # version:1.0.0 -> Link to version named "1.0.0" + # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" + # Attachments: + # attachment:file.zip -> Link to the attachment of the current object named file.zip + # Source files: + # source:some/file -> Link to the file located at /some/file in the project's repository + # source:some/file@52 -> Link to the file's revision 52 + # source:some/file#L120 -> Link to line 120 of the file + # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 + # export:some/file -> Force the download of the file + # Forum messages: + # message#1218 -> Link to message with id 1218 + # Projects: + # project:someproject -> Link to project named "someproject" + # project#3 -> Link to project with id 3 + # + # Links can refer other objects from other projects, using project identifier: + # identifier:r52 + # identifier:document:"Some document" + # identifier:version:1.0.0 + # identifier:source:some/file + def parse_redmine_links(text, default_project, obj, attr, only_path, options) + text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| + leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 + link = nil + project = default_project + if project_identifier + project = Project.visible.find_by_identifier(project_identifier) + end + if esc.nil? + if prefix.nil? && sep == 'r' + if project + repository = nil + if repo_identifier + repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} + else + repository = project.repository + end + # project.changesets.visible raises an SQL error because of a double join on repositories + if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) + link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, + :class => 'changeset', + :title => truncate_single_line(changeset.comments, :length => 100)) + end + end + elsif sep == '#' + oid = identifier.to_i + case prefix + when nil + if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) + anchor = comment_id ? "note-#{comment_id}" : nil + link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, + :class => issue.css_classes, + :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") + end + when 'document' + if document = Document.visible.find_by_id(oid) + link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, + :class => 'document' + end + when 'version' + if version = Version.visible.find_by_id(oid) + link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, + :class => 'version' + end + when 'message' + if message = Message.visible.find_by_id(oid, :include => :parent) + link = link_to_message(message, {:only_path => only_path}, :class => 'message') + end + when 'forum' + if board = Board.visible.find_by_id(oid) + link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, + :class => 'board' + end + when 'news' + if news = News.visible.find_by_id(oid) + link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, + :class => 'news' + end + when 'project' + if p = Project.visible.find_by_id(oid) + link = link_to_project(p, {:only_path => only_path}, :class => 'project') + end + end + elsif sep == ':' + # removes the double quotes if any + name = identifier.gsub(%r{^"(.*)"$}, "\\1") + case prefix + when 'document' + if project && document = project.documents.visible.find_by_title(name) + link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, + :class => 'document' + end + when 'version' + if project && version = project.versions.visible.find_by_name(name) + link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, + :class => 'version' + end + when 'forum' + if project && board = project.boards.visible.find_by_name(name) + link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, + :class => 'board' + end + when 'news' + if project && news = project.news.visible.find_by_title(name) + link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, + :class => 'news' + end + when 'commit', 'source', 'export' + if project + repository = nil + if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} + repo_prefix, repo_identifier, name = $1, $2, $3 + repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} + else + repository = project.repository + end + if prefix == 'commit' + if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first) + link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, + :class => 'changeset', + :title => truncate_single_line(changeset.comments, :length => 100) + end + else + if repository && User.current.allowed_to?(:browse_repository, project) + name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$} + path, rev, anchor = $1, $3, $5 + link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, + :path => to_path_param(path), + :rev => rev, + :anchor => anchor}, + :class => (prefix == 'export' ? 'source download' : 'source') + end + end + repo_prefix = nil + end + when 'attachment' + attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) + if attachments && attachment = Attachment.latest_attach(attachments, name) + link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') + end + when 'project' + if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first + link = link_to_project(p, {:only_path => only_path}, :class => 'project') + end + end + end + end + (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) + end + end + + HEADING_RE = /(]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) + + def parse_sections(text, project, obj, attr, only_path, options) + return unless options[:edit_section_links] + text.gsub!(HEADING_RE) do + heading = $1 + @current_section += 1 + if @current_section > 1 + content_tag('div', + link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), + :class => 'contextual', + :title => l(:button_edit_section), + :id => "section-#{@current_section}") + heading.html_safe + else + heading + end + end + end + + # Headings and TOC + # Adds ids and links to headings unless options[:headings] is set to false + def parse_headings(text, project, obj, attr, only_path, options) + return if options[:headings] == false + + text.gsub!(HEADING_RE) do + level, attrs, content = $2.to_i, $3, $4 + item = strip_tags(content).strip + anchor = sanitize_anchor_name(item) + # used for single-file wiki export + anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) + @heading_anchors[anchor] ||= 0 + idx = (@heading_anchors[anchor] += 1) + if idx > 1 + anchor = "#{anchor}-#{idx}" + end + @parsed_headings << [level, anchor, item] + "\n#{content}" + end + end + + MACROS_RE = /( + (!)? # escaping + ( + \{\{ # opening tag + ([\w]+) # macro name + (\(([^\n\r]*?)\))? # optional arguments + ([\n\r].*?[\n\r])? # optional block of text + \}\} # closing tag + ) + )/mx unless const_defined?(:MACROS_RE) + + MACRO_SUB_RE = /( + \{\{ + macro\((\d+)\) + \}\} + )/x unless const_defined?(:MACRO_SUB_RE) + + # Extracts macros from text + def catch_macros(text) + macros = {} + text.gsub!(MACROS_RE) do + all, macro = $1, $4.downcase + if macro_exists?(macro) || all =~ MACRO_SUB_RE + index = macros.size + macros[index] = all + "{{macro(#{index})}}" + else + all + end + end + macros + end + + # Executes and replaces macros in text + def inject_macros(text, obj, macros, execute=true) + text.gsub!(MACRO_SUB_RE) do + all, index = $1, $2.to_i + orig = macros.delete(index) + if execute && orig && orig =~ MACROS_RE + esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) + if esc.nil? + h(exec_macro(macro, obj, args, block) || all) + else + h(all) + end + elsif orig + h(orig) + else + h(all) + end + end + end + + TOC_RE = /

    \{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) + + # Renders the TOC with given headings + def replace_toc(text, headings) + text.gsub!(TOC_RE) do + # Keep only the 4 first levels + headings = headings.select{|level, anchor, item| level <= 4} + if headings.empty? + '' + else + div_class = 'toc' + div_class << ' right' if $1 == '>' + div_class << ' left' if $1 == '<' + out = "

    • " + root = headings.map(&:first).min + current = root + started = false + headings.each do |level, anchor, item| + if level > current + out << '
      • ' * (level - current) + elsif level < current + out << "
      \n" * (current - level) + "
    • " + elsif started + out << '
    • ' + end + out << "#{item}" + current = level + started = true + end + out << '
    ' * (current - root) + out << '' + end + end + end + + # Same as Rails' simple_format helper without using paragraphs + def simple_format_without_paragraph(text) + text.to_s. + gsub(/\r\n?/, "\n"). # \r\n and \r -> \n + gsub(/\n\n+/, "

    "). # 2+ newline -> 2 br + gsub(/([^\n]\n)(?=[^\n])/, '\1
    '). # 1 newline -> br + html_safe + end + + def lang_options_for_select(blank=true) + (blank ? [["(auto)", ""]] : []) + languages_options + end + + def label_tag_for(name, option_tags = nil, options = {}) + label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") + content_tag("label", label_text) + end + + def labelled_form_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + if args.first.is_a?(Symbol) + options.merge!(:as => args.shift) + end + options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) + form_for(*args, &proc) + end + + def labelled_fields_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) + fields_for(*args, &proc) + end + + def labelled_remote_form_for(*args, &proc) + ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true}) + form_for(*args, &proc) + end + + def error_messages_for(*objects) + html = "" + objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact + errors = objects.map {|o| o.errors.full_messages}.flatten + if errors.any? + html << "
      \n" + errors.each do |error| + html << "
    • #{h error}
    • \n" + end + html << "
    \n" + end + html.html_safe + end + + def delete_link(url, options={}) + options = { + :method => :delete, + :data => {:confirm => l(:text_are_you_sure)}, + :class => 'icon icon-del' + }.merge(options) + + link_to l(:button_delete), url, options + end + + def preview_link(url, form, target='preview', options={}) + content_tag 'a', l(:label_preview), { + :href => "#", + :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, + :accesskey => accesskey(:preview) + }.merge(options) + end + + def link_to_function(name, function, html_options={}) + content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) + end + + # Helper to render JSON in views + def raw_json(arg) + arg.to_json.to_s.gsub('/', '\/').html_safe + end + + def back_url + url = params[:back_url] + if url.nil? && referer = request.env['HTTP_REFERER'] + url = CGI.unescape(referer.to_s) + end + url + end + + def back_url_hidden_field_tag + url = back_url + hidden_field_tag('back_url', url, :id => nil) unless url.blank? + end + + def check_all_links(form_name) + link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + + " | ".html_safe + + link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") + end + + def progress_bar(pcts, options={}) + pcts = [pcts, pcts] unless pcts.is_a?(Array) + pcts = pcts.collect(&:round) + pcts[1] = pcts[1] - pcts[0] + pcts << (100 - pcts[1] - pcts[0]) + width = options[:width] || '100px;' + legend = options[:legend] || '' + content_tag('table', + content_tag('tr', + (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + + (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + + (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) + ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe + + content_tag('p', legend, :class => 'percent').html_safe + end + + def checked_image(checked=true) + if checked + image_tag 'toggle_check.png' + end + end + + def context_menu(url) + unless @context_menu_included + content_for :header_tags do + javascript_include_tag('context_menu') + + stylesheet_link_tag('context_menu') + end + if l(:direction) == 'rtl' + content_for :header_tags do + stylesheet_link_tag('context_menu_rtl') + end + end + @context_menu_included = true + end + javascript_tag "contextMenuInit('#{ url_for(url) }')" + end + + def calendar_for(field_id) + include_calendar_headers_tags + javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") + end + + def include_calendar_headers_tags + unless @calendar_headers_tags_included + tags = javascript_include_tag("datepicker") + @calendar_headers_tags_included = true + content_for :header_tags do + start_of_week = Setting.start_of_week + start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? + # Redmine uses 1..7 (monday..sunday) in settings and locales + # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 + start_of_week = start_of_week.to_i % 7 + tags << javascript_tag( + "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + + "showOn: 'button', buttonImageOnly: true, buttonImage: '" + + path_to_image('/images/calendar.png') + + "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " + + "selectOtherMonths: true, changeMonth: true, changeYear: true, " + + "beforeShow: beforeShowDatePicker};") + jquery_locale = l('jquery.locale', :default => current_language.to_s) + unless jquery_locale == 'en' + tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") + end + tags + end + end + end + + # Overrides Rails' stylesheet_link_tag with themes and plugins support. + # Examples: + # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults + # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets + # + def stylesheet_link_tag(*sources) + options = sources.last.is_a?(Hash) ? sources.pop : {} + plugin = options.delete(:plugin) + sources = sources.map do |source| + if plugin + "/plugin_assets/#{plugin}/stylesheets/#{source}" + elsif current_theme && current_theme.stylesheets.include?(source) + current_theme.stylesheet_path(source) + else + source + end + end + super sources, options + end + + # Overrides Rails' image_tag with themes and plugins support. + # Examples: + # image_tag('image.png') # => picks image.png from the current theme or defaults + # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets + # + def image_tag(source, options={}) + if plugin = options.delete(:plugin) + source = "/plugin_assets/#{plugin}/images/#{source}" + elsif current_theme && current_theme.images.include?(source) + source = current_theme.image_path(source) + end + super source, options + end + + # Overrides Rails' javascript_include_tag with plugins support + # Examples: + # javascript_include_tag('scripts') # => picks scripts.js from defaults + # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets + # + def javascript_include_tag(*sources) + options = sources.last.is_a?(Hash) ? sources.pop : {} + if plugin = options.delete(:plugin) + sources = sources.map do |source| + if plugin + "/plugin_assets/#{plugin}/javascripts/#{source}" + else + source + end + end + end + super sources, options + end + + # TODO: remove this in 2.5.0 + def has_content?(name) + content_for?(name) + end + + def sidebar_content? + content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present? + end + + def view_layouts_base_sidebar_hook_response + @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) + end + + def email_delivery_enabled? + !!ActionMailer::Base.perform_deliveries + end + + # Returns the avatar image tag for the given +user+ if avatars are enabled + # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe ') + def avatar(user, options = { }) + if Setting.gravatar_enabled? + options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) + email = nil + if user.respond_to?(:mail) + email = user.mail + elsif user.to_s =~ %r{<(.+?)>} + email = $1 + end + return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil + else + '' + end + end + + def sanitize_anchor_name(anchor) + if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' + anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-') + else + # TODO: remove when ruby1.8 is no longer supported + anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') + end + end + + # Returns the javascript tags that are included in the html layout head + def javascript_heads + tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application') + unless User.current.pref.warn_on_leaving_unsaved == '0' + tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") + end + tags + end + + def favicon + "".html_safe + end + + def robot_exclusion_tag + ''.html_safe + end + + # Returns true if arg is expected in the API response + def include_in_api_response?(arg) + unless @included_in_api_response + param = params[:include] + @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') + @included_in_api_response.collect!(&:strip) + end + @included_in_api_response.include?(arg.to_s) + end + + # Returns options or nil if nometa param or X-Redmine-Nometa header + # was set in the request + def api_meta(options) + if params[:nometa].present? || request.headers['X-Redmine-Nometa'] + # compatibility mode for activeresource clients that raise + # an error when unserializing an array with attributes + nil + else + options + end + end + + private + + def wiki_helper + helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) + extend helper + return self + end + + def link_to_content_update(text, url_params = {}, html_options = {}) + link_to(text, url_params, html_options) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/aa/aa0beb1e634d59699f850105fb8491055e09d5c3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/aa/aa0beb1e634d59699f850105fb8491055e09d5c3.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,784 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'active_record' +require 'iconv' if RUBY_VERSION < '1.9' +require 'pp' + +namespace :redmine do + desc 'Trac migration script' + task :migrate_from_trac => :environment do + + module TracMigrate + TICKET_MAP = [] + + DEFAULT_STATUS = IssueStatus.default + assigned_status = IssueStatus.find_by_position(2) + resolved_status = IssueStatus.find_by_position(3) + feedback_status = IssueStatus.find_by_position(4) + closed_status = IssueStatus.where(:is_closed => true).first + STATUS_MAPPING = {'new' => DEFAULT_STATUS, + 'reopened' => feedback_status, + 'assigned' => assigned_status, + 'closed' => closed_status + } + + priorities = IssuePriority.all + DEFAULT_PRIORITY = priorities[0] + PRIORITY_MAPPING = {'lowest' => priorities[0], + 'low' => priorities[0], + 'normal' => priorities[1], + 'high' => priorities[2], + 'highest' => priorities[3], + # --- + 'trivial' => priorities[0], + 'minor' => priorities[1], + 'major' => priorities[2], + 'critical' => priorities[3], + 'blocker' => priorities[4] + } + + TRACKER_BUG = Tracker.find_by_position(1) + TRACKER_FEATURE = Tracker.find_by_position(2) + DEFAULT_TRACKER = TRACKER_BUG + TRACKER_MAPPING = {'defect' => TRACKER_BUG, + 'enhancement' => TRACKER_FEATURE, + 'task' => TRACKER_FEATURE, + 'patch' =>TRACKER_FEATURE + } + + roles = Role.where(:builtin => 0).order('position ASC').all + manager_role = roles[0] + developer_role = roles[1] + DEFAULT_ROLE = roles.last + ROLE_MAPPING = {'admin' => manager_role, + 'developer' => developer_role + } + + class ::Time + class << self + alias :real_now :now + def now + real_now - @fake_diff.to_i + end + def fake(time) + @fake_diff = real_now - time + res = yield + @fake_diff = 0 + res + end + end + end + + class TracComponent < ActiveRecord::Base + self.table_name = :component + end + + class TracMilestone < ActiveRecord::Base + self.table_name = :milestone + # If this attribute is set a milestone has a defined target timepoint + def due + if read_attribute(:due) && read_attribute(:due) > 0 + Time.at(read_attribute(:due)).to_date + else + nil + end + end + # This is the real timepoint at which the milestone has finished. + def completed + if read_attribute(:completed) && read_attribute(:completed) > 0 + Time.at(read_attribute(:completed)).to_date + else + nil + end + end + + def description + # Attribute is named descr in Trac v0.8.x + has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description) + end + end + + class TracTicketCustom < ActiveRecord::Base + self.table_name = :ticket_custom + end + + class TracAttachment < ActiveRecord::Base + self.table_name = :attachment + set_inheritance_column :none + + def time; Time.at(read_attribute(:time)) end + + def original_filename + filename + end + + def content_type + '' + end + + def exist? + File.file? trac_fullpath + end + + def open + File.open("#{trac_fullpath}", 'rb') {|f| + @file = f + yield self + } + end + + def read(*args) + @file.read(*args) + end + + def description + read_attribute(:description).to_s.slice(0,255) + end + + private + def trac_fullpath + attachment_type = read_attribute(:type) + #replace exotic characters with their hex representation to avoid invalid filenames + trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) do |x| + codepoint = RUBY_VERSION < '1.9' ? x[0] : x.codepoints.to_a[0] + sprintf('%%%02x', codepoint) + end + "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}" + end + end + + class TracTicket < ActiveRecord::Base + self.table_name = :ticket + set_inheritance_column :none + + # ticket changes: only migrate status changes and comments + has_many :ticket_changes, :class_name => "TracTicketChange", :foreign_key => :ticket + has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket + + def attachments + TracMigrate::TracAttachment.all(:conditions => ["type = 'ticket' AND id = ?", self.id.to_s]) + end + + def ticket_type + read_attribute(:type) + end + + def summary + read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary) + end + + def description + read_attribute(:description).blank? ? summary : read_attribute(:description) + end + + def time; Time.at(read_attribute(:time)) end + def changetime; Time.at(read_attribute(:changetime)) end + end + + class TracTicketChange < ActiveRecord::Base + self.table_name = :ticket_change + + def self.columns + # Hides Trac field 'field' to prevent clash with AR field_changed? method (Rails 3.0) + super.select {|column| column.name.to_s != 'field'} + end + + def time; Time.at(read_attribute(:time)) end + end + + TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \ + TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \ + TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \ + TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \ + TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \ + WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \ + CamelCase TitleIndex) + + class TracWikiPage < ActiveRecord::Base + self.table_name = :wiki + set_primary_key :name + + def self.columns + # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0) + super.select {|column| column.name.to_s != 'readonly'} + end + + def attachments + TracMigrate::TracAttachment.all(:conditions => ["type = 'wiki' AND id = ?", self.id.to_s]) + end + + def time; Time.at(read_attribute(:time)) end + end + + class TracPermission < ActiveRecord::Base + self.table_name = :permission + end + + class TracSessionAttribute < ActiveRecord::Base + self.table_name = :session_attribute + end + + def self.find_or_create_user(username, project_member = false) + return User.anonymous if username.blank? + + u = User.find_by_login(username) + if !u + # Create a new user if not found + mail = username[0, User::MAIL_LENGTH_LIMIT] + if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email') + mail = mail_attr.value + end + mail = "#{mail}@foo.bar" unless mail.include?("@") + + name = username + if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name') + name = name_attr.value + end + name =~ (/(\w+)(\s+\w+)?/) + fn = ($1 || "-").strip + ln = ($2 || '-').strip + + u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), + :firstname => fn[0, limit_for(User, 'firstname')], + :lastname => ln[0, limit_for(User, 'lastname')] + + u.login = username[0, User::LOGIN_LENGTH_LIMIT].gsub(/[^a-z0-9_\-@\.]/i, '-') + u.password = 'trac' + u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') + # finally, a default user is used if the new user is not valid + u = User.first unless u.save + end + # Make sure user is a member of the project + if project_member && !u.member_of?(@target_project) + role = DEFAULT_ROLE + if u.admin + role = ROLE_MAPPING['admin'] + elsif TracPermission.find_by_username_and_action(username, 'developer') + role = ROLE_MAPPING['developer'] + end + Member.create(:user => u, :project => @target_project, :roles => [role]) + u.reload + end + u + end + + # Basic wiki syntax conversion + def self.convert_wiki_text(text) + # Titles + text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"} + # External Links + text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"} + # Ticket links: + # [ticket:234 Text],[ticket:234 This is a test] + text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1') + # ticket:1234 + # #1 is working cause Redmine uses the same syntax. + text = text.gsub(/ticket\:([^\ ]+)/, '#\1') + # Milestone links: + # [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)] + # The text "Milestone 0.1.0 (Mercury)" is not converted, + # cause Redmine's wiki does not support this. + text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"') + # [milestone:"0.1.0 Mercury"] + text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"') + text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"') + # milestone:0.1.0 + text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1') + text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1') + # Internal Links + text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below + text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} + text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} + text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} + text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} + text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"} + + # Links to pages UsingJustWikiCaps + text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]') + # Normalize things that were supposed to not be links + # like !NotALink + text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2') + # Revisions links + text = text.gsub(/\[(\d+)\]/, 'r\1') + # Ticket number re-writing + text = text.gsub(/#(\d+)/) do |s| + if $1.length < 10 +# TICKET_MAP[$1.to_i] ||= $1 + "\##{TICKET_MAP[$1.to_i] || $1}" + else + s + end + end + # We would like to convert the Code highlighting too + # This will go into the next line. + shebang_line = false + # Reguar expression for start of code + pre_re = /\{\{\{/ + # Code hightlighing... + shebang_re = /^\#\!([a-z]+)/ + # Regular expression for end of code + pre_end_re = /\}\}\}/ + + # Go through the whole text..extract it line by line + text = text.gsub(/^(.*)$/) do |line| + m_pre = pre_re.match(line) + if m_pre + line = '
    '
    +          else
    +            m_sl = shebang_re.match(line)
    +            if m_sl
    +              shebang_line = true
    +              line = ''
    +            end
    +            m_pre_end = pre_end_re.match(line)
    +            if m_pre_end
    +              line = '
    ' + if shebang_line + line = '' + line + end + end + end + line + end + + # Highlighting + text = text.gsub(/'''''([^\s])/, '_*\1') + text = text.gsub(/([^\s])'''''/, '\1*_') + text = text.gsub(/'''/, '*') + text = text.gsub(/''/, '_') + text = text.gsub(/__/, '+') + text = text.gsub(/~~/, '-') + text = text.gsub(/`/, '@') + text = text.gsub(/,,/, '~') + # Lists + text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "} + + text + end + + def self.migrate + establish_connection + + # Quick database test + TracComponent.count + + migrated_components = 0 + migrated_milestones = 0 + migrated_tickets = 0 + migrated_custom_values = 0 + migrated_ticket_attachments = 0 + migrated_wiki_edits = 0 + migrated_wiki_attachments = 0 + + #Wiki system initializing... + @target_project.wiki.destroy if @target_project.wiki + @target_project.reload + wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart') + wiki_edit_count = 0 + + # Components + print "Migrating components" + issues_category_map = {} + TracComponent.all.each do |component| + print '.' + STDOUT.flush + c = IssueCategory.new :project => @target_project, + :name => encode(component.name[0, limit_for(IssueCategory, 'name')]) + next unless c.save + issues_category_map[component.name] = c + migrated_components += 1 + end + puts + + # Milestones + print "Migrating milestones" + version_map = {} + TracMilestone.all.each do |milestone| + print '.' + STDOUT.flush + # First we try to find the wiki page... + p = wiki.find_or_new_page(milestone.name.to_s) + p.content = WikiContent.new(:page => p) if p.new_record? + p.content.text = milestone.description.to_s + p.content.author = find_or_create_user('trac') + p.content.comments = 'Milestone' + p.save + + v = Version.new :project => @target_project, + :name => encode(milestone.name[0, limit_for(Version, 'name')]), + :description => nil, + :wiki_page_title => milestone.name.to_s, + :effective_date => milestone.completed + + next unless v.save + version_map[milestone.name] = v + migrated_milestones += 1 + end + puts + + # Custom fields + # TODO: read trac.ini instead + print "Migrating custom fields" + custom_field_map = {} + TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field| + print '.' + STDOUT.flush + # Redmine custom field name + field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize + # Find if the custom already exists in Redmine + f = IssueCustomField.find_by_name(field_name) + # Or create a new one + f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize, + :field_format => 'string') + + next if f.new_record? + f.trackers = Tracker.all + f.projects << @target_project + custom_field_map[field.name] = f + end + puts + + # Trac 'resolution' field as a Redmine custom field + r = IssueCustomField.where(:name => "Resolution").first + r = IssueCustomField.new(:name => 'Resolution', + :field_format => 'list', + :is_filter => true) if r.nil? + r.trackers = Tracker.all + r.projects << @target_project + r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq + r.save! + custom_field_map['resolution'] = r + + # Tickets + print "Migrating tickets" + TracTicket.find_each(:batch_size => 200) do |ticket| + print '.' + STDOUT.flush + i = Issue.new :project => @target_project, + :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]), + :description => convert_wiki_text(encode(ticket.description)), + :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY, + :created_on => ticket.time + i.author = find_or_create_user(ticket.reporter) + i.category = issues_category_map[ticket.component] unless ticket.component.blank? + i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank? + i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS + i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER + i.id = ticket.id unless Issue.exists?(ticket.id) + next unless Time.fake(ticket.changetime) { i.save } + TICKET_MAP[ticket.id] = i.id + migrated_tickets += 1 + + # Owner + unless ticket.owner.blank? + i.assigned_to = find_or_create_user(ticket.owner, true) + Time.fake(ticket.changetime) { i.save } + end + + # Comments and status/resolution changes + ticket.ticket_changes.group_by(&:time).each do |time, changeset| + status_change = changeset.select {|change| change.field == 'status'}.first + resolution_change = changeset.select {|change| change.field == 'resolution'}.first + comment_change = changeset.select {|change| change.field == 'comment'}.first + + n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''), + :created_on => time + n.user = find_or_create_user(changeset.first.author) + n.journalized = i + if status_change && + STATUS_MAPPING[status_change.oldvalue] && + STATUS_MAPPING[status_change.newvalue] && + (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue]) + n.details << JournalDetail.new(:property => 'attr', + :prop_key => 'status_id', + :old_value => STATUS_MAPPING[status_change.oldvalue].id, + :value => STATUS_MAPPING[status_change.newvalue].id) + end + if resolution_change + n.details << JournalDetail.new(:property => 'cf', + :prop_key => custom_field_map['resolution'].id, + :old_value => resolution_change.oldvalue, + :value => resolution_change.newvalue) + end + n.save unless n.details.empty? && n.notes.blank? + end + + # Attachments + ticket.attachments.each do |attachment| + next unless attachment.exist? + attachment.open { + a = Attachment.new :created_on => attachment.time + a.file = attachment + a.author = find_or_create_user(attachment.author) + a.container = i + a.description = attachment.description + migrated_ticket_attachments += 1 if a.save + } + end + + # Custom fields + custom_values = ticket.customs.inject({}) do |h, custom| + if custom_field = custom_field_map[custom.name] + h[custom_field.id] = custom.value + migrated_custom_values += 1 + end + h + end + if custom_field_map['resolution'] && !ticket.resolution.blank? + custom_values[custom_field_map['resolution'].id] = ticket.resolution + end + i.custom_field_values = custom_values + i.save_custom_field_values + end + + # update issue id sequence if needed (postgresql) + Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!') + puts + + # Wiki + print "Migrating wiki" + if wiki.save + TracWikiPage.order('name, version').all.each do |page| + # Do not migrate Trac manual wiki pages + next if TRAC_WIKI_PAGES.include?(page.name) + wiki_edit_count += 1 + print '.' + STDOUT.flush + p = wiki.find_or_new_page(page.name) + p.content = WikiContent.new(:page => p) if p.new_record? + p.content.text = page.text + p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac' + p.content.comments = page.comment + Time.fake(page.time) { p.new_record? ? p.save : p.content.save } + + next if p.content.new_record? + migrated_wiki_edits += 1 + + # Attachments + page.attachments.each do |attachment| + next unless attachment.exist? + next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page + attachment.open { + a = Attachment.new :created_on => attachment.time + a.file = attachment + a.author = find_or_create_user(attachment.author) + a.description = attachment.description + a.container = p + migrated_wiki_attachments += 1 if a.save + } + end + end + + wiki.reload + wiki.pages.each do |page| + page.content.text = convert_wiki_text(page.content.text) + Time.fake(page.content.updated_on) { page.content.save } + end + end + puts + + puts + puts "Components: #{migrated_components}/#{TracComponent.count}" + puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}" + puts "Tickets: #{migrated_tickets}/#{TracTicket.count}" + puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s + puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}" + puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}" + puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s + end + + def self.limit_for(klass, attribute) + klass.columns_hash[attribute.to_s].limit + end + + def self.encoding(charset) + @charset = charset + end + + def self.set_trac_directory(path) + @@trac_directory = path + raise "This directory doesn't exist!" unless File.directory?(path) + raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory) + @@trac_directory + rescue Exception => e + puts e + return false + end + + def self.trac_directory + @@trac_directory + end + + def self.set_trac_adapter(adapter) + return false if adapter.blank? + raise "Unknown adapter: #{adapter}!" unless %w(sqlite3 mysql postgresql).include?(adapter) + # If adapter is sqlite or sqlite3, make sure that trac.db exists + raise "#{trac_db_path} doesn't exist!" if %w(sqlite3).include?(adapter) && !File.exist?(trac_db_path) + @@trac_adapter = adapter + rescue Exception => e + puts e + return false + end + + def self.set_trac_db_host(host) + return nil if host.blank? + @@trac_db_host = host + end + + def self.set_trac_db_port(port) + return nil if port.to_i == 0 + @@trac_db_port = port.to_i + end + + def self.set_trac_db_name(name) + return nil if name.blank? + @@trac_db_name = name + end + + def self.set_trac_db_username(username) + @@trac_db_username = username + end + + def self.set_trac_db_password(password) + @@trac_db_password = password + end + + def self.set_trac_db_schema(schema) + @@trac_db_schema = schema + end + + mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password + + def self.trac_db_path; "#{trac_directory}/db/trac.db" end + def self.trac_attachments_directory; "#{trac_directory}/attachments" end + + def self.target_project_identifier(identifier) + project = Project.find_by_identifier(identifier) + if !project + # create the target project + project = Project.new :name => identifier.humanize, + :description => '' + project.identifier = identifier + puts "Unable to create a project with identifier '#{identifier}'!" unless project.save + # enable issues and wiki for the created project + project.enabled_module_names = ['issue_tracking', 'wiki'] + else + puts + puts "This project already exists in your Redmine database." + print "Are you sure you want to append data to this project ? [Y/n] " + STDOUT.flush + exit if STDIN.gets.match(/^n$/i) + end + project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG) + project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE) + @target_project = project.new_record? ? nil : project + @target_project.reload + end + + def self.connection_params + if trac_adapter == 'sqlite3' + {:adapter => 'sqlite3', + :database => trac_db_path} + else + {:adapter => trac_adapter, + :database => trac_db_name, + :host => trac_db_host, + :port => trac_db_port, + :username => trac_db_username, + :password => trac_db_password, + :schema_search_path => trac_db_schema + } + end + end + + def self.establish_connection + constants.each do |const| + klass = const_get(const) + next unless klass.respond_to? 'establish_connection' + klass.establish_connection connection_params + end + end + + def self.encode(text) + if RUBY_VERSION < '1.9' + @ic ||= Iconv.new('UTF-8', @charset) + @ic.iconv text + else + text.to_s.force_encoding(@charset).encode('UTF-8') + end + end + end + + puts + if Redmine::DefaultData::Loader.no_data? + puts "Redmine configuration need to be loaded before importing data." + puts "Please, run this first:" + puts + puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" + exit + end + + puts "WARNING: a new project will be added to Redmine during this process." + print "Are you sure you want to continue ? [y/N] " + STDOUT.flush + break unless STDIN.gets.match(/^y$/i) + puts + + def prompt(text, options = {}, &block) + default = options[:default] || '' + while true + print "#{text} [#{default}]: " + STDOUT.flush + value = STDIN.gets.chomp! + value = default if value.blank? + break if yield value + end + end + + DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432} + + prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip} + prompt('Trac database adapter (sqlite3, mysql2, postgresql)', :default => 'sqlite3') {|adapter| TracMigrate.set_trac_adapter adapter} + unless %w(sqlite3).include?(TracMigrate.trac_adapter) + prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host} + prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port} + prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name} + prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema} + prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username} + prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password} + end + prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding} + prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier} + puts + + old_notified_events = Setting.notified_events + old_password_min_length = Setting.password_min_length + begin + # Turn off email notifications temporarily + Setting.notified_events = [] + Setting.password_min_length = 4 + # Run the migration + TracMigrate.migrate + ensure + # Restore previous settings + Setting.notified_events = old_notified_events + Setting.password_min_length = old_password_min_length + end + end +end + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/aa/aa1a7eb90b6f264114d03a211b1ad82b079870a8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/aa/aa1a7eb90b6f264114d03a211b1ad82b079870a8.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,31 @@ +<%= link_to(@repository.identifier.present? ? h(@repository.identifier) : 'root', + :action => 'show', :id => @project, + :repository_id => @repository.identifier_param, + :path => nil, :rev => @rev) %> +<% +dirs = path.split('/') +if 'file' == kind + filename = dirs.pop +end +link_path = '' +dirs.each do |dir| + next if dir.blank? + link_path << '/' unless link_path.empty? + link_path << "#{dir}" + %> + / <%= link_to h(dir), :action => 'show', :id => @project, :repository_id => @repository.identifier_param, + :path => to_path_param(link_path), :rev => @rev %> +<% end %> +<% if filename %> + / <%= link_to h(filename), + :action => 'changes', :id => @project, :repository_id => @repository.identifier_param, + :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> +<% end %> +<% + # @rev is revsion or Git and Mercurial branch or tag. + # For Mercurial *tip*, @rev and @changeset are nil. + rev_text = @changeset.nil? ? @rev : format_revision(@changeset) +%> +<%= "@ #{h rev_text}" unless rev_text.blank? %> + +<% html_title(with_leading_slash(path)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/aa/aaf905ee3518e0c52f64e7a528de1f2b7f2001eb.svn-base --- a/.svn/pristine/aa/aaf905ee3518e0c52f64e7a528de1f2b7f2001eb.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'settings_controller' - -# Re-raise errors caught by the controller. -class SettingsController; def rescue_action(e) raise e end; end - -class SettingsControllerTest < ActionController::TestCase - fixtures :users - - def setup - @controller = SettingsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_response :success - assert_template 'edit' - end - - def test_get_edit - get :edit - assert_response :success - assert_template 'edit' - - assert_tag 'input', :attributes => {:name => 'settings[enabled_scm][]', :value => ''} - end - - def test_get_edit_should_preselect_default_issue_list_columns - with_settings :issue_list_default_columns => %w(tracker subject status updated_on) do - get :edit - assert_response :success - end - - assert_select 'select[id=selected_columns][name=?]', 'settings[issue_list_default_columns][]' do - assert_select 'option', 4 - assert_select 'option[value=tracker]', :text => 'Tracker' - assert_select 'option[value=subject]', :text => 'Subject' - assert_select 'option[value=status]', :text => 'Status' - assert_select 'option[value=updated_on]', :text => 'Updated' - end - - assert_select 'select[id=available_columns]' do - assert_select 'option[value=tracker]', 0 - assert_select 'option[value=priority]', :text => 'Priority' - end - end - - def test_get_edit_without_trackers_should_succeed - Tracker.delete_all - - get :edit - assert_response :success - end - - def test_post_edit_notifications - post :edit, :settings => {:mail_from => 'functional@test.foo', - :bcc_recipients => '0', - :notified_events => %w(issue_added issue_updated news_added), - :emails_footer => 'Test footer' - } - assert_redirected_to '/settings/edit' - assert_equal 'functional@test.foo', Setting.mail_from - assert !Setting.bcc_recipients? - assert_equal %w(issue_added issue_updated news_added), Setting.notified_events - assert_equal 'Test footer', Setting.emails_footer - Setting.clear_cache - end - - def test_get_plugin_settings - Setting.stubs(:plugin_foo).returns({'sample_setting' => 'Plugin setting value'}) - ActionController::Base.append_view_path(File.join(Rails.root, "test/fixtures/plugins")) - Redmine::Plugin.register :foo do - settings :partial => "foo_plugin/foo_plugin_settings" - end - - get :plugin, :id => 'foo' - assert_response :success - assert_template 'plugin' - assert_tag 'form', :attributes => {:action => '/settings/plugin/foo'}, - :descendant => {:tag => 'input', :attributes => {:name => 'settings[sample_setting]', :value => 'Plugin setting value'}} - - Redmine::Plugin.clear - end - - def test_get_invalid_plugin_settings - get :plugin, :id => 'none' - assert_response 404 - end - - def test_post_plugin_settings - Setting.expects(:plugin_foo=).with({'sample_setting' => 'Value'}).returns(true) - Redmine::Plugin.register(:foo) {} - - post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'} - assert_redirected_to '/settings/plugin/foo' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ab/ab114f5adab85b76c8331bf71ea599dc558c656f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/ab114f5adab85b76c8331bf71ea599dc558c656f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,106 @@ +== Redmine installation + +Redmine - project management software +Copyright (C) 2006-2014 Jean-Philippe Lang +http://www.redmine.org/ + + +== Requirements + +* Ruby 1.8.7, 1.9.2, 1.9.3 or 2.0.0 +* RubyGems +* Bundler >= 1.0.21 + +* A database: + * MySQL (tested with MySQL 5.1) + * PostgreSQL (tested with PostgreSQL 9.1) + * SQLite3 (tested with SQLite 3.7) + * SQLServer (tested with SQLServer 2012) + +Optional: +* SCM binaries (e.g. svn, git...), for repository browsing (must be available in PATH) +* ImageMagick (to enable Gantt export to png images) + +== Installation + +1. Uncompress the program archive + +2. Create an empty utf8 encoded database: "redmine" for example + +3. Configure the database parameters in config/database.yml + for the "production" environment (default database is MySQL and ruby1.9) + + If you're running Redmine with MySQL and ruby1.8, replace the adapter name + with `mysql` + +4. Install the required gems by running: + bundle install --without development test + + If ImageMagick is not installed on your system, you should skip the installation + of the rmagick gem using: + bundle install --without development test rmagick + + Only the gems that are needed by the adapters you've specified in your database + configuration file are actually installed (eg. if your config/database.yml + uses the 'mysql2' adapter, then only the mysql2 gem will be installed). Don't + forget to re-run `bundle install` when you change config/database.yml for using + other database adapters. + + If you need to load some gems that are not required by Redmine core (eg. fcgi), + you can create a file named Gemfile.local at the root of your redmine directory. + It will be loaded automatically when running `bundle install`. + +5. Generate a session store secret + + Redmine stores session data in cookies by default, which requires + a secret to be generated. Under the application main directory run: + rake generate_secret_token + +6. Create the database structure + + Under the application main directory run: + rake db:migrate RAILS_ENV="production" + + It will create all the tables and an administrator account. + +7. Setting up permissions (Windows users have to skip this section) + + The user who runs Redmine must have write permission on the following + subdirectories: files, log, tmp & public/plugin_assets. + + Assuming you run Redmine with a user named "redmine": + sudo chown -R redmine:redmine files log tmp public/plugin_assets + sudo chmod -R 755 files log tmp public/plugin_assets + +8. Test the installation by running the WEBrick web server + + Under the main application directory run: + ruby script/rails server -e production + + Once WEBrick has started, point your browser to http://localhost:3000/ + You should now see the application welcome page. + +9. Use the default administrator account to log in: + login: admin + password: admin + + Go to "Administration" to load the default configuration data (roles, + trackers, statuses, workflow) and to adjust the application settings + +== SMTP server Configuration + +Copy config/configuration.yml.example to config/configuration.yml and +edit this file to adjust your SMTP settings. +Do not forget to restart the application after any change to this file. + +Please do not enter your SMTP settings in environment.rb. + +== References + +* http://www.redmine.org/wiki/redmine/RedmineInstall +* http://www.redmine.org/wiki/redmine/EmailConfiguration +* http://www.redmine.org/wiki/redmine/RedmineSettings +* http://www.redmine.org/wiki/redmine/RedmineRepositories +* http://www.redmine.org/wiki/redmine/RedmineReceivingEmails +* http://www.redmine.org/wiki/redmine/RedmineReminderEmails +* http://www.redmine.org/wiki/redmine/RedmineLDAP diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ab/ab1a5cb004277811c44708d7b2f380ff38057bbc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/ab1a5cb004277811c44708d7b2f380ff38057bbc.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,75 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'diff' + +module Redmine + module Helpers + class Diff + include ERB::Util + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + attr_reader :diff, :words + + def initialize(content_to, content_from) + @words = content_to.to_s.split(/(\s+)/) + @words = @words.select {|word| word != ' '} + words_from = content_from.to_s.split(/(\s+)/) + words_from = words_from.select {|word| word != ' '} + @diff = words_from.diff @words + end + + def to_html + words = self.words.collect{|word| h(word)} + words_add = 0 + words_del = 0 + dels = 0 + del_off = 0 + diff.diffs.each do |diff| + add_at = nil + add_to = nil + del_at = nil + deleted = "" + diff.each do |change| + pos = change[1] + if change[0] == "+" + add_at = pos + dels unless add_at + add_to = pos + dels + words_add += 1 + else + del_at = pos unless del_at + deleted << ' ' unless deleted.empty? + deleted << h(change[2]) + words_del += 1 + end + end + if add_at + words[add_at] = ''.html_safe + words[add_at] + words[add_to] = words[add_to] + ''.html_safe + end + if del_at + words.insert del_at - del_off + dels + words_add, ''.html_safe + deleted + ''.html_safe + dels += 1 + del_off += words_del + words_del = 0 + end + end + words.join(' ').html_safe + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ab/ab2ac870d79ffb5e7c22bd5bd53c01cd412b73f1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/ab2ac870d79ffb5e7c22bd5bd53c01cd412b73f1.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,173 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'digest/md5' + +module Redmine + module WikiFormatting + class StaleSectionError < Exception; end + + @@formatters = {} + + class << self + def map + yield self + end + + def register(name, formatter, helper) + raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name.to_s] + @@formatters[name.to_s] = {:formatter => formatter, :helper => helper} + end + + def formatter + formatter_for(Setting.text_formatting) + end + + def formatter_for(name) + entry = @@formatters[name.to_s] + (entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter + end + + def helper_for(name) + entry = @@formatters[name.to_s] + (entry && entry[:helper]) || Redmine::WikiFormatting::NullFormatter::Helper + end + + def format_names + @@formatters.keys.map + end + + def to_html(format, text, options = {}) + text = if Setting.cache_formatted_text? && text.size > 2.kilobyte && cache_store && cache_key = cache_key_for(format, text, options[:object], options[:attribute]) + # Text retrieved from the cache store may be frozen + # We need to dup it so we can do in-place substitutions with gsub! + cache_store.fetch cache_key do + formatter_for(format).new(text).to_html + end.dup + else + formatter_for(format).new(text).to_html + end + text + end + + # Returns true if the text formatter supports single section edit + def supports_section_edit? + (formatter.instance_methods & ['update_section', :update_section]).any? + end + + # Returns a cache key for the given text +format+, +text+, +object+ and +attribute+ or nil if no caching should be done + def cache_key_for(format, text, object, attribute) + if object && attribute && !object.new_record? && format.present? + "formatted_text/#{format}/#{object.class.model_name.cache_key}/#{object.id}-#{attribute}-#{Digest::MD5.hexdigest text}" + end + end + + # Returns the cache store used to cache HTML output + def cache_store + ActionController::Base.cache_store + end + end + + module LinksHelper + AUTO_LINK_RE = %r{ + ( # leading text + <\w+.*?>| # leading HTML tag, or + [\s\(\[,;]| # leading punctuation, or + ^ # beginning of line + ) + ( + (?:https?://)| # protocol spec, or + (?:s?ftps?://)| + (?:www\.) # www.* + ) + ( + ([^<]\S*?) # url + (\/)? # slash + ) + ((?:>)?|[^[:alnum:]_\=\/;\(\)]*?) # post + (?=<|\s|$) + }x unless const_defined?(:AUTO_LINK_RE) + + # Destructively remplaces urls into clickable links + def auto_link!(text) + text.gsub!(AUTO_LINK_RE) do + all, leading, proto, url, post = $&, $1, $2, $3, $6 + if leading =~ /=]?/ + # don't replace URL's that are already linked + # and URL's prefixed with ! !> !< != (textile images) + all + else + # Idea below : an URL with unbalanced parethesis and + # ending by ')' is put into external parenthesis + if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) ) + url=url[0..-2] # discard closing parenth from url + post = ")"+post # add closing parenth to post + end + content = proto + url + href = "#{proto=="www."?"http://www.":proto}#{url}" + %(#{leading}#{ERB::Util.html_escape content}#{post}).html_safe + end + end + end + + # Destructively remplaces email addresses into clickable links + def auto_mailto!(text) + text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do + mail = $1 + if text.match(/]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/) + mail + else + %().html_safe + end + end + end + end + + # Default formatter module + module NullFormatter + class Formatter + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::UrlHelper + include Redmine::WikiFormatting::LinksHelper + + def initialize(text) + @text = text + end + + def to_html(*args) + t = CGI::escapeHTML(@text) + auto_link!(t) + auto_mailto!(t) + simple_format(t, {}, :sanitize => false) + end + end + + module Helper + def wikitoolbar_for(field_id) + end + + def heads_for_wiki_formatter + end + + def initial_page_content(page) + page.pretty_title.to_s + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ab/ab489d78995ff1723e6c18bbc50248d8f1734a92.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/ab489d78995ff1723e6c18bbc50248d8f1734a92.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,78 @@ +// Automatic project identifier generation + +function generateProjectIdentifier(identifier, maxlength) { + var diacriticsMap = [ + {'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g}, + {'base':'aa','letters':/[\uA733\uA732]/g}, + {'base':'ae','letters':/[\u00E4\u00E6\u01FD\u01E3\u00C4\u00C6\u01FC\u01E2]/g}, + {'base':'ao','letters':/[\uA735\uA734]/g}, + {'base':'au','letters':/[\uA737\uA736]/g}, + {'base':'av','letters':/[\uA739\uA73B\uA738\uA73A]/g}, + {'base':'ay','letters':/[\uA73D\uA73C]/g}, + {'base':'b', 'letters':/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g}, + {'base':'c', 'letters':/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g}, + {'base':'d', 'letters':/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g}, + {'base':'dz','letters':/[\u01F3\u01C6\u01F1\u01C4\u01F2\u01C5]/g}, + {'base':'e', 'letters':/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g}, + {'base':'f', 'letters':/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g}, + {'base':'g', 'letters':/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g}, + {'base':'h', 'letters':/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g}, + {'base':'hv','letters':/[\u0195]/g}, + {'base':'i', 'letters':/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g}, + {'base':'j', 'letters':/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249\u004A\u24BF\uFF2A\u0134\u0248]/g}, + {'base':'k', 'letters':/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g}, + {'base':'l', 'letters':/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g}, + {'base':'lj','letters':/[\u01C9\u01C7\u01C8]/g}, + {'base':'m', 'letters':/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g}, + {'base':'n', 'letters':/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g}, + {'base':'nj','letters':/[\u01CC\u01CA\u01CB]/g}, + {'base':'o', 'letters':/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g}, + {'base':'oe','letters': /[\u00F6\u0153\u00D6\u0152]/g}, + {'base':'oi','letters':/[\u01A3\u01A2]/g}, + {'base':'ou','letters':/[\u0223\u0222]/g}, + {'base':'oo','letters':/[\uA74F\uA74E]/g}, + {'base':'p','letters':/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g}, + {'base':'q','letters':/[\u0071\u24E0\uFF51\u024B\uA757\uA759\u0051\u24C6\uFF31\uA756\uA758\u024A]/g}, + {'base':'r','letters':/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g}, + {'base':'s','letters':/[\u0073\u24E2\uFF53\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g}, + {'base':'ss','letters':/[\u00DF]/g}, + {'base':'t','letters':/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g}, + {'base':'tz','letters':/[\uA729\uA728]/g}, + {'base':'u','letters':/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g}, + {'base':'ue','letters':/[\u00FC\u00DC]/g}, + {'base':'v','letters':/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g}, + {'base':'vy','letters':/[\uA761\uA760]/g}, + {'base':'w','letters':/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g}, + {'base':'x','letters':/[\u0078\u24E7\uFF58\u1E8B\u1E8D\u0058\u24CD\uFF38\u1E8A\u1E8C]/g}, + {'base':'y','letters':/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g}, + {'base':'z','letters':/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g} + ]; + + for(var i=0; i hyphen + identifier = identifier.replace(/^[-_\d]*|[-_]*$/g, ''); // remove hyphens/underscores and numbers at beginning and hyphens/underscores at end + identifier = identifier.toLowerCase(); // to lower + identifier = identifier.substr(0, maxlength); // max characters + return identifier; +} + +function autoFillProjectIdentifier() { + var locked = ($('#project_identifier').val() != ''); + var maxlength = parseInt($('#project_identifier').attr('maxlength')); + + $('#project_name').keyup(function(){ + if(!locked) { + $('#project_identifier').val(generateProjectIdentifier($('#project_name').val(), maxlength)); + } + }); + + $('#project_identifier').keyup(function(){ + locked = ($('#project_identifier').val() != '' && $('#project_identifier').val() != generateProjectIdentifier($('#project_name').val(), maxlength)); + }); +} + +$(document).ready(function(){ + autoFillProjectIdentifier(); +}); diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ab/ab5241095b4d64374cabe95fece10b1972c8081e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/ab5241095b4d64374cabe95fece10b1972c8081e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,228 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module QueriesHelper + def filters_options_for_select(query) + options_for_select(filters_options(query)) + end + + def filters_options(query) + options = [[]] + options += query.available_filters.map do |field, field_options| + [field_options[:name], field] + end + end + + def query_filters_hidden_tags(query) + tags = ''.html_safe + query.filters.each do |field, options| + tags << hidden_field_tag("f[]", field, :id => nil) + tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil) + options[:values].each do |value| + tags << hidden_field_tag("v[#{field}][]", value, :id => nil) + end + end + tags + end + + def query_columns_hidden_tags(query) + tags = ''.html_safe + query.columns.each do |column| + tags << hidden_field_tag("c[]", column.name, :id => nil) + end + tags + end + + def query_hidden_tags(query) + query_filters_hidden_tags(query) + query_columns_hidden_tags(query) + end + + def available_block_columns_tags(query) + tags = ''.html_safe + query.available_block_columns.each do |column| + tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline') + end + tags + end + + def query_available_inline_columns_options(query) + (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} + end + + def query_selected_inline_columns_options(query) + (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} + end + + def render_query_columns_selection(query, options={}) + tag_name = (options[:name] || 'c') + '[]' + render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} + end + + def column_header(column) + column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, + :default_order => column.default_order) : + content_tag('th', h(column.caption)) + end + + def column_content(column, issue) + value = column.value(issue) + if value.is_a?(Array) + value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe + else + column_value(column, issue, value) + end + end + + def column_value(column, issue, value) + case value.class.name + when 'String' + if column.name == :subject + link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) + elsif column.name == :description + issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : '' + else + h(value) + end + when 'Time' + format_time(value) + when 'Date' + format_date(value) + when 'Fixnum' + if column.name == :id + link_to value, issue_path(issue) + elsif column.name == :done_ratio + progress_bar(value, :width => '80px') + else + value.to_s + end + when 'Float' + sprintf "%.2f", value + when 'User' + link_to_user value + when 'Project' + link_to_project value + when 'Version' + link_to(h(value), :controller => 'versions', :action => 'show', :id => value) + when 'TrueClass' + l(:general_text_Yes) + when 'FalseClass' + l(:general_text_No) + when 'Issue' + value.visible? ? link_to_issue(value) : "##{value.id}" + when 'IssueRelation' + other = value.other_issue(issue) + content_tag('span', + (l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe, + :class => value.css_classes_for(issue)) + else + h(value) + end + end + + def csv_content(column, issue) + value = column.value(issue) + if value.is_a?(Array) + value.collect {|v| csv_value(column, issue, v)}.compact.join(', ') + else + csv_value(column, issue, value) + end + end + + def csv_value(column, issue, value) + case value.class.name + when 'Time' + format_time(value) + when 'Date' + format_date(value) + when 'Float' + sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator)) + when 'IssueRelation' + other = value.other_issue(issue) + l(value.label_for(issue)) + " ##{other.id}" + when 'TrueClass' + l(:general_text_Yes) + when 'FalseClass' + l(:general_text_No) + else + value.to_s + end + end + + def query_to_csv(items, query, options={}) + encoding = l(:general_csv_encoding) + columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns) + query.available_block_columns.each do |column| + if options[column.name].present? + columns << column + end + end + + export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| + # csv header fields + csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } + # csv lines + items.each do |item| + csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) } + end + end + export + end + + # Retrieve query from session or build a new query + def retrieve_query + if !params[:query_id].blank? + cond = "project_id IS NULL" + cond << " OR project_id = #{@project.id}" if @project + @query = IssueQuery.where(cond).find(params[:query_id]) + raise ::Unauthorized unless @query.visible? + @query.project = @project + session[:query] = {:id => @query.id, :project_id => @query.project_id} + sort_clear + elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil) + # Give it a name, required to be valid + @query = IssueQuery.new(:name => "_") + @query.project = @project + @query.build_from_params(params) + session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} + else + # retrieve from session + @query = nil + @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] + @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) + @query.project = @project + end + end + + def retrieve_query_from_session + if session[:query] + if session[:query][:id] + @query = IssueQuery.find_by_id(session[:query][:id]) + return unless @query + else + @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) + end + if session[:query].has_key?(:project_id) + @query.project_id = session[:query][:project_id] + else + @query.project = @project + end + @query + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ab/ab6d26ef7a0d1797ec9850f4776a0b64ecc44d36.svn-base --- a/.svn/pristine/ab/ab6d26ef7a0d1797ec9850f4776a0b64ecc44d36.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -

    <%= link_to l(:label_tracker_plural), trackers_path %> » <%=h @tracker %>

    - -<%= labelled_form_for @tracker do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ab/ab765c6a0235f6d883edf2ecc8ab4daf55689a05.svn-base --- a/.svn/pristine/ab/ab765c6a0235f6d883edf2ecc8ab4daf55689a05.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class CustomFieldUserFormatTest < ActiveSupport::TestCase - fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues - - def setup - @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user') - end - - def test_possible_values_with_no_arguments - assert_equal [], @field.possible_values - assert_equal [], @field.possible_values(nil) - end - - def test_possible_values_with_project_resource - project = Project.find(1) - possible_values = @field.possible_values(project.issues.first) - assert possible_values.any? - assert_equal project.users.sort.collect(&:id).map(&:to_s), possible_values - end - - def test_possible_values_with_nil_project_resource - project = Project.find(1) - assert_equal [], @field.possible_values(Issue.new) - end - - def test_possible_values_options_with_no_arguments - assert_equal [], @field.possible_values_options - assert_equal [], @field.possible_values_options(nil) - end - - def test_possible_values_options_with_project_resource - project = Project.find(1) - possible_values_options = @field.possible_values_options(project.issues.first) - assert possible_values_options.any? - assert_equal project.users.sort.map {|u| [u.name, u.id.to_s]}, possible_values_options - end - - def test_possible_values_options_with_array - projects = Project.find([1, 2]) - possible_values_options = @field.possible_values_options(projects) - assert possible_values_options.any? - assert_equal (projects.first.users & projects.last.users).sort.map {|u| [u.name, u.id.to_s]}, possible_values_options - end - - def test_cast_blank_value - assert_equal nil, @field.cast_value(nil) - assert_equal nil, @field.cast_value("") - end - - def test_cast_valid_value - user = @field.cast_value("2") - assert_kind_of User, user - assert_equal User.find(2), user - end - - def test_cast_invalid_value - assert_equal nil, @field.cast_value("187") - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ab/ab96afdc85199bd90097700415e66f01fff19c6b.svn-base --- a/.svn/pristine/ab/ab96afdc85199bd90097700415e66f01fff19c6b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,474 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Mailer < ActionMailer::Base - layout 'mailer' - helper :application - helper :issues - helper :custom_fields - - include Redmine::I18n - - def self.default_url_options - { :host => Setting.host_name, :protocol => Setting.protocol } - end - - # Builds a Mail::Message object used to email recipients of the added issue. - # - # Example: - # issue_add(issue) => Mail::Message object - # Mailer.issue_add(issue).deliver => sends an email to issue recipients - def issue_add(issue) - redmine_headers 'Project' => issue.project.identifier, - 'Issue-Id' => issue.id, - 'Issue-Author' => issue.author.login - redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to - message_id issue - @author = issue.author - @issue = issue - @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue) - recipients = issue.recipients - cc = issue.watcher_recipients - recipients - mail :to => recipients, - :cc => cc, - :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" - end - - # Builds a Mail::Message object used to email recipients of the edited issue. - # - # Example: - # issue_edit(journal) => Mail::Message object - # Mailer.issue_edit(journal).deliver => sends an email to issue recipients - def issue_edit(journal) - issue = journal.journalized.reload - redmine_headers 'Project' => issue.project.identifier, - 'Issue-Id' => issue.id, - 'Issue-Author' => issue.author.login - redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to - message_id journal - references issue - @author = journal.user - recipients = journal.recipients - # Watchers in cc - cc = journal.watcher_recipients - recipients - s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " - s << "(#{issue.status.name}) " if journal.new_value_for('status_id') - s << issue.subject - @issue = issue - @journal = journal - @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}") - mail :to => recipients, - :cc => cc, - :subject => s - end - - def reminder(user, issues, days) - set_language_if_valid user.language - @issues = issues - @days = days - @issues_url = url_for(:controller => 'issues', :action => 'index', - :set_filter => 1, :assigned_to_id => user.id, - :sort => 'due_date:asc') - mail :to => user.mail, - :subject => l(:mail_subject_reminder, :count => issues.size, :days => days) - end - - # Builds a Mail::Message object used to email users belonging to the added document's project. - # - # Example: - # document_added(document) => Mail::Message object - # Mailer.document_added(document).deliver => sends an email to the document's project recipients - def document_added(document) - redmine_headers 'Project' => document.project.identifier - @author = User.current - @document = document - @document_url = url_for(:controller => 'documents', :action => 'show', :id => document) - mail :to => document.recipients, - :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" - end - - # Builds a Mail::Message object used to email recipients of a project when an attachements are added. - # - # Example: - # attachments_added(attachments) => Mail::Message object - # Mailer.attachments_added(attachments).deliver => sends an email to the project's recipients - def attachments_added(attachments) - container = attachments.first.container - added_to = '' - added_to_url = '' - @author = attachments.first.author - case container.class.name - when 'Project' - added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container) - added_to = "#{l(:label_project)}: #{container}" - recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail} - when 'Version' - added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project) - added_to = "#{l(:label_version)}: #{container.name}" - recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail} - when 'Document' - added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) - added_to = "#{l(:label_document)}: #{container.title}" - recipients = container.recipients - end - redmine_headers 'Project' => container.project.identifier - @attachments = attachments - @added_to = added_to - @added_to_url = added_to_url - mail :to => recipients, - :subject => "[#{container.project.name}] #{l(:label_attachment_new)}" - end - - # Builds a Mail::Message object used to email recipients of a news' project when a news item is added. - # - # Example: - # news_added(news) => Mail::Message object - # Mailer.news_added(news).deliver => sends an email to the news' project recipients - def news_added(news) - redmine_headers 'Project' => news.project.identifier - @author = news.author - message_id news - @news = news - @news_url = url_for(:controller => 'news', :action => 'show', :id => news) - mail :to => news.recipients, - :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}" - end - - # Builds a Mail::Message object used to email recipients of a news' project when a news comment is added. - # - # Example: - # news_comment_added(comment) => Mail::Message object - # Mailer.news_comment_added(comment) => sends an email to the news' project recipients - def news_comment_added(comment) - news = comment.commented - redmine_headers 'Project' => news.project.identifier - @author = comment.author - message_id comment - @news = news - @comment = comment - @news_url = url_for(:controller => 'news', :action => 'show', :id => news) - mail :to => news.recipients, - :cc => news.watcher_recipients, - :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}" - end - - # Builds a Mail::Message object used to email the recipients of the specified message that was posted. - # - # Example: - # message_posted(message) => Mail::Message object - # Mailer.message_posted(message).deliver => sends an email to the recipients - def message_posted(message) - redmine_headers 'Project' => message.project.identifier, - 'Topic-Id' => (message.parent_id || message.id) - @author = message.author - message_id message - references message.parent unless message.parent.nil? - recipients = message.recipients - cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients) - @message = message - @message_url = url_for(message.event_url) - mail :to => recipients, - :cc => cc, - :subject => "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}" - end - - # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was added. - # - # Example: - # wiki_content_added(wiki_content) => Mail::Message object - # Mailer.wiki_content_added(wiki_content).deliver => sends an email to the project's recipients - def wiki_content_added(wiki_content) - redmine_headers 'Project' => wiki_content.project.identifier, - 'Wiki-Page-Id' => wiki_content.page.id - @author = wiki_content.author - message_id wiki_content - recipients = wiki_content.recipients - cc = wiki_content.page.wiki.watcher_recipients - recipients - @wiki_content = wiki_content - @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', - :project_id => wiki_content.project, - :id => wiki_content.page.title) - mail :to => recipients, - :cc => cc, - :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}" - end - - # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was updated. - # - # Example: - # wiki_content_updated(wiki_content) => Mail::Message object - # Mailer.wiki_content_updated(wiki_content).deliver => sends an email to the project's recipients - def wiki_content_updated(wiki_content) - redmine_headers 'Project' => wiki_content.project.identifier, - 'Wiki-Page-Id' => wiki_content.page.id - @author = wiki_content.author - message_id wiki_content - recipients = wiki_content.recipients - cc = wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients - @wiki_content = wiki_content - @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', - :project_id => wiki_content.project, - :id => wiki_content.page.title) - @wiki_diff_url = url_for(:controller => 'wiki', :action => 'diff', - :project_id => wiki_content.project, :id => wiki_content.page.title, - :version => wiki_content.version) - mail :to => recipients, - :cc => cc, - :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}" - end - - # Builds a Mail::Message object used to email the specified user their account information. - # - # Example: - # account_information(user, password) => Mail::Message object - # Mailer.account_information(user, password).deliver => sends account information to the user - def account_information(user, password) - set_language_if_valid user.language - @user = user - @password = password - @login_url = url_for(:controller => 'account', :action => 'login') - mail :to => user.mail, - :subject => l(:mail_subject_register, Setting.app_title) - end - - # Builds a Mail::Message object used to email all active administrators of an account activation request. - # - # Example: - # account_activation_request(user) => Mail::Message object - # Mailer.account_activation_request(user).deliver => sends an email to all active administrators - def account_activation_request(user) - # Send the email to all active administrators - recipients = User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact - @user = user - @url = url_for(:controller => 'users', :action => 'index', - :status => User::STATUS_REGISTERED, - :sort_key => 'created_on', :sort_order => 'desc') - mail :to => recipients, - :subject => l(:mail_subject_account_activation_request, Setting.app_title) - end - - # Builds a Mail::Message object used to email the specified user that their account was activated by an administrator. - # - # Example: - # account_activated(user) => Mail::Message object - # Mailer.account_activated(user).deliver => sends an email to the registered user - def account_activated(user) - set_language_if_valid user.language - @user = user - @login_url = url_for(:controller => 'account', :action => 'login') - mail :to => user.mail, - :subject => l(:mail_subject_register, Setting.app_title) - end - - def lost_password(token) - set_language_if_valid(token.user.language) - @token = token - @url = url_for(:controller => 'account', :action => 'lost_password', :token => token.value) - mail :to => token.user.mail, - :subject => l(:mail_subject_lost_password, Setting.app_title) - end - - def register(token) - set_language_if_valid(token.user.language) - @token = token - @url = url_for(:controller => 'account', :action => 'activate', :token => token.value) - mail :to => token.user.mail, - :subject => l(:mail_subject_register, Setting.app_title) - end - - def test_email(user) - set_language_if_valid(user.language) - @url = url_for(:controller => 'welcome') - mail :to => user.mail, - :subject => 'Redmine test' - end - - # Overrides default deliver! method to prevent from sending an email - # with no recipient, cc or bcc - def deliver!(mail = @mail) - set_language_if_valid @initial_language - return false if (recipients.nil? || recipients.empty?) && - (cc.nil? || cc.empty?) && - (bcc.nil? || bcc.empty?) - - - # Log errors when raise_delivery_errors is set to false, Rails does not - raise_errors = self.class.raise_delivery_errors - self.class.raise_delivery_errors = true - begin - return super(mail) - rescue Exception => e - if raise_errors - raise e - elsif mylogger - mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml." - end - ensure - self.class.raise_delivery_errors = raise_errors - end - end - - # Sends reminders to issue assignees - # Available options: - # * :days => how many days in the future to remind about (defaults to 7) - # * :tracker => id of tracker for filtering issues (defaults to all trackers) - # * :project => id or identifier of project to process (defaults to all projects) - # * :users => array of user/group ids who should be reminded - def self.reminders(options={}) - days = options[:days] || 7 - project = options[:project] ? Project.find(options[:project]) : nil - tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil - user_ids = options[:users] - - scope = Issue.open.where("#{Issue.table_name}.assigned_to_id IS NOT NULL" + - " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" + - " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date - ) - scope = scope.where(:assigned_to_id => user_ids) if user_ids.present? - scope = scope.where(:project_id => project.id) if project - scope = scope.where(:tracker_id => tracker.id) if tracker - - issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).all.group_by(&:assigned_to) - issues_by_assignee.keys.each do |assignee| - if assignee.is_a?(Group) - assignee.users.each do |user| - issues_by_assignee[user] ||= [] - issues_by_assignee[user] += issues_by_assignee[assignee] - end - end - end - - issues_by_assignee.each do |assignee, issues| - reminder(assignee, issues, days).deliver if assignee.is_a?(User) && assignee.active? - end - end - - # Activates/desactivates email deliveries during +block+ - def self.with_deliveries(enabled = true, &block) - was_enabled = ActionMailer::Base.perform_deliveries - ActionMailer::Base.perform_deliveries = !!enabled - yield - ensure - ActionMailer::Base.perform_deliveries = was_enabled - end - - # Sends emails synchronously in the given block - def self.with_synched_deliveries(&block) - saved_method = ActionMailer::Base.delivery_method - if m = saved_method.to_s.match(%r{^async_(.+)$}) - synched_method = m[1] - ActionMailer::Base.delivery_method = synched_method.to_sym - ActionMailer::Base.send "#{synched_method}_settings=", ActionMailer::Base.send("async_#{synched_method}_settings") - end - yield - ensure - ActionMailer::Base.delivery_method = saved_method - end - - def mail(headers={}) - headers.merge! 'X-Mailer' => 'Redmine', - 'X-Redmine-Host' => Setting.host_name, - 'X-Redmine-Site' => Setting.app_title, - 'X-Auto-Response-Suppress' => 'OOF', - 'Auto-Submitted' => 'auto-generated', - 'From' => Setting.mail_from, - 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>" - - # Removes the author from the recipients and cc - # if he doesn't want to receive notifications about what he does - if @author && @author.logged? && @author.pref[:no_self_notified] - headers[:to].delete(@author.mail) if headers[:to].is_a?(Array) - headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array) - end - - if @author && @author.logged? - redmine_headers 'Sender' => @author.login - end - - # Blind carbon copy recipients - if Setting.bcc_recipients? - headers[:bcc] = [headers[:to], headers[:cc]].flatten.uniq.reject(&:blank?) - headers[:to] = nil - headers[:cc] = nil - end - - if @message_id_object - headers[:message_id] = "<#{self.class.message_id_for(@message_id_object)}>" - end - if @references_objects - headers[:references] = @references_objects.collect {|o| "<#{self.class.message_id_for(o)}>"}.join(' ') - end - - super headers do |format| - format.text - format.html unless Setting.plain_text_mail? - end - - set_language_if_valid @initial_language - end - - def initialize(*args) - @initial_language = current_language - set_language_if_valid Setting.default_language - super - end - - def self.deliver_mail(mail) - return false if mail.to.blank? && mail.cc.blank? && mail.bcc.blank? - super - end - - def self.method_missing(method, *args, &block) - if m = method.to_s.match(%r{^deliver_(.+)$}) - ActiveSupport::Deprecation.warn "Mailer.deliver_#{m[1]}(*args) is deprecated. Use Mailer.#{m[1]}(*args).deliver instead." - send(m[1], *args).deliver - else - super - end - end - - private - - # Appends a Redmine header field (name is prepended with 'X-Redmine-') - def redmine_headers(h) - h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s } - end - - # Returns a predictable Message-Id for the given object - def self.message_id_for(object) - # id + timestamp should reduce the odds of a collision - # as far as we don't send multiple emails for the same object - timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on) - hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}" - host = Setting.mail_from.to_s.gsub(%r{^.*@}, '') - host = "#{::Socket.gethostname}.redmine" if host.empty? - "#{hash}@#{host}" - end - - def message_id(object) - @message_id_object = object - end - - def references(object) - @references_objects ||= [] - @references_objects << object - end - - def mylogger - Rails.logger - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ab/abce92f16794eda42d844c3ac0c0ab72c8c6b0ec.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/abce92f16794eda42d844c3ac0c0ab72c8c6b0ec.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,362 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../test_helper', __FILE__) + +class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase + include ApplicationHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::SanitizeHelper + include ERB::Util + extend ActionView::Helpers::SanitizeHelper::ClassMethods + + fixtures :projects, :roles, :enabled_modules, :users, + :repositories, :changesets, + :trackers, :issue_statuses, :issues, + :versions, :documents, + :wikis, :wiki_pages, :wiki_contents, + :boards, :messages, + :attachments + + def setup + super + @project = nil + end + + def teardown + end + + def test_macro_registration + Redmine::WikiFormatting::Macros.register do + macro :foo do |obj, args| + "Foo: #{args.size} (#{args.join(',')}) (#{args.class.name})" + end + end + + assert_equal '

    Foo: 0 () (Array)

    ', textilizable("{{foo}}") + assert_equal '

    Foo: 0 () (Array)

    ', textilizable("{{foo()}}") + assert_equal '

    Foo: 1 (arg1) (Array)

    ', textilizable("{{foo(arg1)}}") + assert_equal '

    Foo: 2 (arg1,arg2) (Array)

    ', textilizable("{{foo(arg1, arg2)}}") + end + + def test_macro_registration_parse_args_set_to_false_should_disable_arguments_parsing + Redmine::WikiFormatting::Macros.register do + macro :bar, :parse_args => false do |obj, args| + "Bar: (#{args}) (#{args.class.name})" + end + end + + assert_equal '

    Bar: (args, more args) (String)

    ', textilizable("{{bar(args, more args)}}") + assert_equal '

    Bar: () (String)

    ', textilizable("{{bar}}") + assert_equal '

    Bar: () (String)

    ', textilizable("{{bar()}}") + end + + def test_macro_registration_with_3_args_should_receive_text_argument + Redmine::WikiFormatting::Macros.register do + macro :baz do |obj, args, text| + "Baz: (#{args.join(',')}) (#{text.class.name}) (#{text})" + end + end + + assert_equal "

    Baz: () (NilClass) ()

    ", textilizable("{{baz}}") + assert_equal "

    Baz: () (NilClass) ()

    ", textilizable("{{baz()}}") + assert_equal "

    Baz: () (String) (line1\nline2)

    ", textilizable("{{baz()\nline1\nline2\n}}") + assert_equal "

    Baz: (arg1,arg2) (String) (line1\nline2)

    ", textilizable("{{baz(arg1, arg2)\nline1\nline2\n}}") + end + + def test_macro_name_with_upper_case + Redmine::WikiFormatting::Macros.macro(:UpperCase) {|obj, args| "Upper"} + + assert_equal "

    Upper

    ", textilizable("{{UpperCase}}") + end + + def test_multiple_macros_on_the_same_line + Redmine::WikiFormatting::Macros.macro :foo do |obj, args| + args.any? ? "args: #{args.join(',')}" : "no args" + end + + assert_equal '

    no args no args

    ', textilizable("{{foo}} {{foo}}") + assert_equal '

    args: a,b no args

    ', textilizable("{{foo(a,b)}} {{foo}}") + assert_equal '

    args: a,b args: c,d

    ', textilizable("{{foo(a,b)}} {{foo(c,d)}}") + assert_equal '

    no args args: c,d

    ', textilizable("{{foo}} {{foo(c,d)}}") + end + + def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute + issue = Issue.find(1) + issue.description = "{{hello_world}}" + assert_equal '

    Hello world! Object: Issue, Called with no argument and no block of text.

    ', textilizable(issue, :description) + end + + def test_macro_should_receive_the_object_as_argument_when_called_with_object_option + text = "{{hello_world}}" + assert_equal '

    Hello world! Object: Issue, Called with no argument and no block of text.

    ', textilizable(text, :object => Issue.find(1)) + end + + def test_extract_macro_options_should_with_args + options = extract_macro_options(["arg1", "arg2"], :foo, :size) + assert_equal([["arg1", "arg2"], {}], options) + end + + def test_extract_macro_options_should_with_options + options = extract_macro_options(["foo=bar", "size=2"], :foo, :size) + assert_equal([[], {:foo => "bar", :size => "2"}], options) + end + + def test_extract_macro_options_should_with_args_and_options + options = extract_macro_options(["arg1", "arg2", "foo=bar", "size=2"], :foo, :size) + assert_equal([["arg1", "arg2"], {:foo => "bar", :size => "2"}], options) + end + + def test_extract_macro_options_should_parse_options_lazily + options = extract_macro_options(["params=x=1&y=2"], :params) + assert_equal([[], {:params => "x=1&y=2"}], options) + end + + def test_macro_exception_should_be_displayed + Redmine::WikiFormatting::Macros.macro :exception do |obj, args| + raise "My message" + end + + text = "{{exception}}" + assert_include '
    Error executing the exception macro (My message)
    ', textilizable(text) + end + + def test_macro_arguments_should_not_be_parsed_by_formatters + text = '{{hello_world(http://www.redmine.org, #1)}}' + assert_include 'Arguments: http://www.redmine.org, #1', textilizable(text) + end + + def test_exclamation_mark_should_not_run_macros + text = "!{{hello_world}}" + assert_equal '

    {{hello_world}}

    ', textilizable(text) + end + + def test_exclamation_mark_should_escape_macros + text = "!{{hello_world()}}" + assert_equal '

    {{hello_world(<tag>)}}

    ', textilizable(text) + end + + def test_unknown_macros_should_not_be_replaced + text = "{{unknown}}" + assert_equal '

    {{unknown}}

    ', textilizable(text) + end + + def test_unknown_macros_should_parsed_as_text + text = "{{unknown(*test*)}}" + assert_equal '

    {{unknown(test)}}

    ', textilizable(text) + end + + def test_unknown_macros_should_be_escaped + text = "{{unknown()}}" + assert_equal '

    {{unknown(<tag>)}}

    ', textilizable(text) + end + + def test_html_safe_macro_output_should_not_be_escaped + Redmine::WikiFormatting::Macros.macro :safe_macro do |obj, args| + "".html_safe + end + assert_equal '

    ', textilizable("{{safe_macro}}") + end + + def test_macro_hello_world + text = "{{hello_world}}" + assert textilizable(text).match(/Hello world!/) + end + + def test_macro_hello_world_should_escape_arguments + text = "{{hello_world()}}" + assert_include 'Arguments: <tag>', textilizable(text) + end + + def test_macro_macro_list + text = "{{macro_list}}" + assert_match %r{hello_world}, textilizable(text) + end + + def test_macro_include + @project = Project.find(1) + # include a page of the current project wiki + text = "{{include(Another page)}}" + assert_include 'This is a link to a ticket', textilizable(text) + + @project = nil + # include a page of a specific project wiki + text = "{{include(ecookbook:Another page)}}" + assert_include 'This is a link to a ticket', textilizable(text) + + text = "{{include(ecookbook:)}}" + assert_include 'CookBook documentation', textilizable(text) + + text = "{{include(unknowidentifier:somepage)}}" + assert_include 'Page not found', textilizable(text) + end + + def test_macro_collapse + text = "{{collapse\n*Collapsed* block of text\n}}" + result = textilizable(text) + + assert_select_in result, 'div.collapsed-text' + assert_select_in result, 'strong', :text => 'Collapsed' + assert_select_in result, 'a.collapsible.collapsed', :text => 'Show' + assert_select_in result, 'a.collapsible', :text => 'Hide' + end + + def test_macro_collapse_with_one_arg + text = "{{collapse(Example)\n*Collapsed* block of text\n}}" + result = textilizable(text) + + assert_select_in result, 'div.collapsed-text' + assert_select_in result, 'strong', :text => 'Collapsed' + assert_select_in result, 'a.collapsible.collapsed', :text => 'Example' + assert_select_in result, 'a.collapsible', :text => 'Example' + end + + def test_macro_collapse_with_two_args + text = "{{collapse(Show example, Hide example)\n*Collapsed* block of text\n}}" + result = textilizable(text) + + assert_select_in result, 'div.collapsed-text' + assert_select_in result, 'strong', :text => 'Collapsed' + assert_select_in result, 'a.collapsible.collapsed', :text => 'Show example' + assert_select_in result, 'a.collapsible', :text => 'Hide example' + end + + def test_macro_child_pages + expected = "

    \n

    " + + @project = Project.find(1) + # child pages of the current wiki page + assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content) + # child pages of another page + assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content) + + @project = Project.find(2) + assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content) + end + + def test_macro_child_pages_with_parent_option + expected = "

    \n

    " + + @project = Project.find(1) + # child pages of the current wiki page + assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content) + # child pages of another page + assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content) + + @project = Project.find(2) + assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content) + end + + def test_macro_child_pages_with_depth_option + expected = "

    \n

    " + + @project = Project.find(1) + assert_equal expected, textilizable("{{child_pages(depth=1)}}", :object => WikiPage.find(2).content) + end + + def test_macro_child_pages_without_wiki_page_should_fail + assert_match /can be called from wiki pages only/, textilizable("{{child_pages}}") + end + + def test_macro_thumbnail + assert_equal '

    testfile.PNG

    ', + textilizable("{{thumbnail(testfile.png)}}", :object => Issue.find(14)) + end + + def test_macro_thumbnail_with_size + assert_equal '

    testfile.PNG

    ', + textilizable("{{thumbnail(testfile.png, size=200)}}", :object => Issue.find(14)) + end + + def test_macro_thumbnail_with_title + assert_equal '

    testfile.PNG

    ', + textilizable("{{thumbnail(testfile.png, title=Cool image)}}", :object => Issue.find(14)) + end + + def test_macro_thumbnail_with_invalid_filename_should_fail + assert_include 'test.png not found', + textilizable("{{thumbnail(test.png)}}", :object => Issue.find(14)) + end + + def test_macros_should_not_be_executed_in_pre_tags + text = <<-RAW +{{hello_world(foo)}} + +
    +{{hello_world(pre)}}
    +!{{hello_world(pre)}}
    +
    + +{{hello_world(bar)}} +RAW + + expected = <<-EXPECTED +

    Hello world! Object: NilClass, Arguments: foo and no block of text.

    + +
    +{{hello_world(pre)}}
    +!{{hello_world(pre)}}
    +
    + +

    Hello world! Object: NilClass, Arguments: bar and no block of text.

    +EXPECTED + + assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '') + end + + def test_macros_should_be_escaped_in_pre_tags + text = "
    {{hello_world()}}
    " + assert_equal "
    {{hello_world(<tag>)}}
    ", textilizable(text) + end + + def test_macros_should_not_mangle_next_macros_outputs + text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}' + assert_equal '

    {{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo and no block of text.

    ', textilizable(text) + end + + def test_macros_with_text_should_not_mangle_following_macros + text = <<-RAW +{{hello_world +Line of text +}} + +{{hello_world +Another line of text +}} +RAW + + expected = <<-EXPECTED +

    Hello world! Object: NilClass, Called with no argument and a 12 bytes long block of text.

    +

    Hello world! Object: NilClass, Called with no argument and a 20 bytes long block of text.

    +EXPECTED + + assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '') + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ac/ac29d85a760bdd05295d465c5da481729f76a3a3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ac/ac29d85a760bdd05295d465c5da481729f76a3a3.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,216 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class IssueRelationTest < ActiveSupport::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :issue_relations, + :enabled_modules, + :enumerations, + :trackers, + :projects_trackers + + include Redmine::I18n + + def test_create + from = Issue.find(1) + to = Issue.find(2) + + relation = IssueRelation.new :issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_PRECEDES + assert relation.save + relation.reload + assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type + assert_equal from, relation.issue_from + assert_equal to, relation.issue_to + end + + def test_create_minimum + relation = IssueRelation.new :issue_from => Issue.find(1), :issue_to => Issue.find(2) + assert relation.save + assert_equal IssueRelation::TYPE_RELATES, relation.relation_type + end + + def test_follows_relation_should_be_reversed + from = Issue.find(1) + to = Issue.find(2) + + relation = IssueRelation.new :issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_FOLLOWS + assert relation.save + relation.reload + assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type + assert_equal to, relation.issue_from + assert_equal from, relation.issue_to + end + + def test_follows_relation_should_not_be_reversed_if_validation_fails + from = Issue.find(1) + to = Issue.find(2) + + relation = IssueRelation.new :issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_FOLLOWS, + :delay => 'xx' + assert !relation.save + assert_equal IssueRelation::TYPE_FOLLOWS, relation.relation_type + assert_equal from, relation.issue_from + assert_equal to, relation.issue_to + end + + def test_relation_type_for + from = Issue.find(1) + to = Issue.find(2) + + relation = IssueRelation.new :issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_PRECEDES + assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type_for(from) + assert_equal IssueRelation::TYPE_FOLLOWS, relation.relation_type_for(to) + end + + def test_set_issue_to_dates_without_issue_to + r = IssueRelation.new(:issue_from => Issue.new(:start_date => Date.today), + :relation_type => IssueRelation::TYPE_PRECEDES, + :delay => 1) + assert_nil r.set_issue_to_dates + end + + def test_set_issue_to_dates_without_issues + r = IssueRelation.new(:relation_type => IssueRelation::TYPE_PRECEDES, :delay => 1) + assert_nil r.set_issue_to_dates + end + + def test_validates_circular_dependency + IssueRelation.delete_all + assert IssueRelation.create!( + :issue_from => Issue.find(1), :issue_to => Issue.find(2), + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert IssueRelation.create!( + :issue_from => Issue.find(2), :issue_to => Issue.find(3), + :relation_type => IssueRelation::TYPE_PRECEDES + ) + r = IssueRelation.new( + :issue_from => Issue.find(3), :issue_to => Issue.find(1), + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert !r.save + assert_not_equal [], r.errors[:base] + end + + def test_validates_circular_dependency_of_subtask + set_language_if_valid 'en' + issue1 = Issue.generate! + issue2 = Issue.generate! + IssueRelation.create!( + :issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_PRECEDES + ) + child = Issue.generate!(:parent_issue_id => issue2.id) + issue1.reload + child.reload + + r = IssueRelation.new( + :issue_from => child, :issue_to => issue1, + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert !r.save + assert_include 'This relation would create a circular dependency', r.errors.full_messages + end + + def test_subtasks_should_allow_precedes_relation + parent = Issue.generate! + child1 = Issue.generate!(:parent_issue_id => parent.id) + child2 = Issue.generate!(:parent_issue_id => parent.id) + + r = IssueRelation.new( + :issue_from => child1, :issue_to => child2, + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert r.valid? + assert r.save + end + + def test_validates_circular_dependency_on_reverse_relations + IssueRelation.delete_all + assert IssueRelation.create!( + :issue_from => Issue.find(1), :issue_to => Issue.find(3), + :relation_type => IssueRelation::TYPE_BLOCKS + ) + assert IssueRelation.create!( + :issue_from => Issue.find(1), :issue_to => Issue.find(2), + :relation_type => IssueRelation::TYPE_BLOCKED + ) + r = IssueRelation.new( + :issue_from => Issue.find(2), :issue_to => Issue.find(1), + :relation_type => IssueRelation::TYPE_BLOCKED + ) + assert !r.save + assert_not_equal [], r.errors[:base] + end + + def test_create_should_make_journal_entry + from = Issue.find(1) + to = Issue.find(2) + from_journals = from.journals.size + to_journals = to.journals.size + relation = IssueRelation.new(:issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_PRECEDES) + assert relation.save + from.reload + to.reload + relation.reload + assert_equal from.journals.size, (from_journals + 1) + assert_equal to.journals.size, (to_journals + 1) + assert_equal 'relation', from.journals.last.details.last.property + assert_equal 'label_precedes', from.journals.last.details.last.prop_key + assert_equal '2', from.journals.last.details.last.value + assert_nil from.journals.last.details.last.old_value + assert_equal 'relation', to.journals.last.details.last.property + assert_equal 'label_follows', to.journals.last.details.last.prop_key + assert_equal '1', to.journals.last.details.last.value + assert_nil to.journals.last.details.last.old_value + end + + def test_delete_should_make_journal_entry + relation = IssueRelation.find(1) + id = relation.id + from = relation.issue_from + to = relation.issue_to + from_journals = from.journals.size + to_journals = to.journals.size + assert relation.destroy + from.reload + to.reload + assert_equal from.journals.size, (from_journals + 1) + assert_equal to.journals.size, (to_journals + 1) + assert_equal 'relation', from.journals.last.details.last.property + assert_equal 'label_blocks', from.journals.last.details.last.prop_key + assert_equal '9', from.journals.last.details.last.old_value + assert_nil from.journals.last.details.last.value + assert_equal 'relation', to.journals.last.details.last.property + assert_equal 'label_blocked_by', to.journals.last.details.last.prop_key + assert_equal '10', to.journals.last.details.last.old_value + assert_nil to.journals.last.details.last.value + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ac/ac29e4943c6b7f06073e396c00b1a9f4120db8e8.svn-base --- a/.svn/pristine/ac/ac29e4943c6b7f06073e396c00b1a9f4120db8e8.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -
    -<%= watcher_tag(@wiki, User.current) %> -
    - -

    <%= l(:label_index_by_title) %>

    - -<% if @pages.empty? %> -

    <%= l(:label_no_data) %>

    -<% end %> - -<%= render_page_hierarchy(@pages_by_parent_id, nil, :timestamp => true) %> - -<% content_for :sidebar do %> - <%= render :partial => 'sidebar' %> -<% end %> - -<% unless @pages.empty? %> -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', - :url => {:controller => 'activities', :action => 'index', - :id => @project, :show_wiki_edits => 1, - :key => User.current.rss_key} %> - <% if User.current.allowed_to?(:export_wiki_pages, @project) %> - <%= f.link_to('PDF', :url => {:action => 'export', :format => 'pdf'}) %> - <%= f.link_to('HTML', :url => {:action => 'export'}) %> - <% end %> -<% end %> -<% end %> - -<% content_for :header_tags do %> -<%= auto_discovery_link_tag( - :atom, :controller => 'activities', :action => 'index', - :id => @project, :show_wiki_edits => 1, :format => 'atom', - :key => User.current.rss_key) %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ac/ac2df139756a1b383082a659ee11216bfec62b21.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ac/ac2df139756a1b383082a659ee11216bfec62b21.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,477 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class IssuesController < ApplicationController + menu_item :new_issue, :only => [:new, :create] + default_search_scope :issues + + before_filter :find_issue, :only => [:show, :edit, :update] + before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] + before_filter :find_project, :only => [:new, :create, :update_form] + before_filter :authorize, :except => [:index] + before_filter :find_optional_project, :only => [:index] + before_filter :check_for_default_issue_status, :only => [:new, :create] + before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form] + accept_rss_auth :index, :show + accept_api_auth :index, :show, :create, :update, :destroy + + rescue_from Query::StatementInvalid, :with => :query_statement_invalid + + helper :journals + helper :projects + include ProjectsHelper + helper :custom_fields + include CustomFieldsHelper + helper :issue_relations + include IssueRelationsHelper + helper :watchers + include WatchersHelper + helper :attachments + include AttachmentsHelper + helper :queries + include QueriesHelper + helper :repositories + include RepositoriesHelper + helper :sort + include SortHelper + include IssuesHelper + helper :timelog + include Redmine::Export::PDF + + def index + retrieve_query + sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) + sort_update(@query.sortable_columns) + @query.sort_criteria = sort_criteria.to_a + + if @query.valid? + case params[:format] + when 'csv', 'pdf' + @limit = Setting.issues_export_limit.to_i + when 'atom' + @limit = Setting.feeds_limit.to_i + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = per_page_option + end + + @issue_count = @query.issue_count + @issue_pages = Paginator.new @issue_count, @limit, params['page'] + @offset ||= @issue_pages.offset + @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], + :order => sort_clause, + :offset => @offset, + :limit => @limit) + @issue_count_by_group = @query.issue_count_by_group + + respond_to do |format| + format.html { render :template => 'issues/index', :layout => !request.xhr? } + format.api { + Issue.load_visible_relations(@issues) if include_in_api_response?('relations') + } + format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } + format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') } + format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') } + end + else + respond_to do |format| + format.html { render(:template => 'issues/index', :layout => !request.xhr?) } + format.any(:atom, :csv, :pdf) { render(:nothing => true) } + format.api { render_validation_errors(@query) } + end + end + rescue ActiveRecord::RecordNotFound + render_404 + end + + def show + @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all + @journals.each_with_index {|j,i| j.indice = i+1} + @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project) + Journal.preload_journals_details_custom_fields(@journals) + # TODO: use #select! when ruby1.8 support is dropped + @journals.reject! {|journal| !journal.notes? && journal.visible_details.empty?} + @journals.reverse! if User.current.wants_comments_in_reverse_order? + + @changesets = @issue.changesets.visible.all + @changesets.reverse! if User.current.wants_comments_in_reverse_order? + + @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } + @allowed_statuses = @issue.new_statuses_allowed_to(User.current) + @edit_allowed = User.current.allowed_to?(:edit_issues, @project) + @priorities = IssuePriority.active + @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) + @relation = IssueRelation.new + + respond_to do |format| + format.html { + retrieve_previous_and_next_issue_ids + render :template => 'issues/show' + } + format.api + format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } + format.pdf { + pdf = issue_to_pdf(@issue, :journals => @journals) + send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") + } + end + end + + # Add a new issue + # The new issue will be created from an existing one if copy_from parameter is given + def new + respond_to do |format| + format.html { render :action => 'new', :layout => !request.xhr? } + end + end + + def create + call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue }) + @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads])) + if @issue.save + call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue}) + respond_to do |format| + format.html { + render_attachment_warning_if_needed(@issue) + flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject)) + if params[:continue] + attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} + redirect_to new_project_issue_path(@issue.project, :issue => attrs) + else + redirect_to issue_path(@issue) + end + } + format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } + end + return + else + respond_to do |format| + format.html { render :action => 'new' } + format.api { render_validation_errors(@issue) } + end + end + end + + def edit + return unless update_issue_from_params + + respond_to do |format| + format.html { } + format.xml { } + end + end + + def update + return unless update_issue_from_params + @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads])) + saved = false + begin + saved = save_issue_with_child_records + rescue ActiveRecord::StaleObjectError + @conflict = true + if params[:last_journal_id] + @conflict_journals = @issue.journals_after(params[:last_journal_id]).all + @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project) + end + end + + if saved + render_attachment_warning_if_needed(@issue) + flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? + + respond_to do |format| + format.html { redirect_back_or_default issue_path(@issue) } + format.api { render_api_ok } + end + else + respond_to do |format| + format.html { render :action => 'edit' } + format.api { render_validation_errors(@issue) } + end + end + end + + # Updates the issue form when changing the project, status or tracker + # on issue creation/update + def update_form + end + + # Bulk edit/copy a set of issues + def bulk_edit + @issues.sort! + @copy = params[:copy].present? + @notes = params[:notes] + + if User.current.allowed_to?(:move_issues, @projects) + @allowed_projects = Issue.allowed_target_projects_on_move + if params[:issue] + @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s} + if @target_project + target_projects = [@target_project] + end + end + end + target_projects ||= @projects + + if @copy + @available_statuses = [IssueStatus.default] + else + @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) + end + @custom_fields = target_projects.map{|p|p.all_issue_custom_fields.visible}.reduce(:&) + @assignables = target_projects.map(&:assignable_users).reduce(:&) + @trackers = target_projects.map(&:trackers).reduce(:&) + @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&) + @categories = target_projects.map {|p| p.issue_categories}.reduce(:&) + if @copy + @attachments_present = @issues.detect {|i| i.attachments.any?}.present? + @subtasks_present = @issues.detect {|i| !i.leaf?}.present? + end + + @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&) + + @issue_params = params[:issue] || {} + @issue_params[:custom_field_values] ||= {} + end + + def bulk_update + @issues.sort! + @copy = params[:copy].present? + attributes = parse_params_for_bulk_issue_attributes(params) + + unsaved_issues = [] + saved_issues = [] + + if @copy && params[:copy_subtasks].present? + # Descendant issues will be copied with the parent task + # Don't copy them twice + @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}} + end + + @issues.each do |orig_issue| + orig_issue.reload + if @copy + issue = orig_issue.copy({}, + :attachments => params[:copy_attachments].present?, + :subtasks => params[:copy_subtasks].present? + ) + else + issue = orig_issue + end + journal = issue.init_journal(User.current, params[:notes]) + issue.safe_attributes = attributes + call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) + if issue.save + saved_issues << issue + else + unsaved_issues << orig_issue + end + end + + if unsaved_issues.empty? + flash[:notice] = l(:notice_successful_update) unless saved_issues.empty? + if params[:follow] + if @issues.size == 1 && saved_issues.size == 1 + redirect_to issue_path(saved_issues.first) + elsif saved_issues.map(&:project).uniq.size == 1 + redirect_to project_issues_path(saved_issues.map(&:project).first) + end + else + redirect_back_or_default _project_issues_path(@project) + end + else + @saved_issues = @issues + @unsaved_issues = unsaved_issues + @issues = Issue.visible.find_all_by_id(@unsaved_issues.map(&:id)) + bulk_edit + render :action => 'bulk_edit' + end + end + + def destroy + @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f + if @hours > 0 + case params[:todo] + when 'destroy' + # nothing to do + when 'nullify' + TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues]) + when 'reassign' + reassign_to = @project.issues.find_by_id(params[:reassign_to_id]) + if reassign_to.nil? + flash.now[:error] = l(:error_issue_not_found_in_project) + return + else + TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues]) + end + else + # display the destroy form if it's a user request + return unless api_request? + end + end + @issues.each do |issue| + begin + issue.reload.destroy + rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists + # nothing to do, issue was already deleted (eg. by a parent) + end + end + respond_to do |format| + format.html { redirect_back_or_default _project_issues_path(@project) } + format.api { render_api_ok } + end + end + + private + + def find_project + project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id]) + @project = Project.find(project_id) + rescue ActiveRecord::RecordNotFound + render_404 + end + + def retrieve_previous_and_next_issue_ids + retrieve_query_from_session + if @query + sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) + sort_update(@query.sortable_columns, 'issues_index_sort') + limit = 500 + issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version]) + if (idx = issue_ids.index(@issue.id)) && idx < limit + if issue_ids.size < 500 + @issue_position = idx + 1 + @issue_count = issue_ids.size + end + @prev_issue_id = issue_ids[idx - 1] if idx > 0 + @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1) + end + end + end + + # Used by #edit and #update to set some common instance variables + # from the params + # TODO: Refactor, not everything in here is needed by #edit + def update_issue_from_params + @edit_allowed = User.current.allowed_to?(:edit_issues, @project) + @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) + @time_entry.attributes = params[:time_entry] + + @issue.init_journal(User.current) + + issue_attributes = params[:issue] + if issue_attributes && params[:conflict_resolution] + case params[:conflict_resolution] + when 'overwrite' + issue_attributes = issue_attributes.dup + issue_attributes.delete(:lock_version) + when 'add_notes' + issue_attributes = issue_attributes.slice(:notes) + when 'cancel' + redirect_to issue_path(@issue) + return false + end + end + @issue.safe_attributes = issue_attributes + @priorities = IssuePriority.active + @allowed_statuses = @issue.new_statuses_allowed_to(User.current) + true + end + + # TODO: Refactor, lots of extra code in here + # TODO: Changing tracker on an existing issue should not trigger this + def build_new_issue_from_params + if params[:id].blank? + @issue = Issue.new + if params[:copy_from] + begin + @copy_from = Issue.visible.find(params[:copy_from]) + @copy_attachments = params[:copy_attachments].present? || request.get? + @copy_subtasks = params[:copy_subtasks].present? || request.get? + @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks) + rescue ActiveRecord::RecordNotFound + render_404 + return + end + end + @issue.project = @project + else + @issue = @project.issues.visible.find(params[:id]) + end + + @issue.project = @project + @issue.author ||= User.current + # Tracker must be set before custom field values + @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) + if @issue.tracker.nil? + render_error l(:error_no_tracker_in_project) + return false + end + @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date? + @issue.safe_attributes = params[:issue] + + @priorities = IssuePriority.active + @allowed_statuses = @issue.new_statuses_allowed_to(User.current, @issue.new_record?) + @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq + end + + def check_for_default_issue_status + if IssueStatus.default.nil? + render_error l(:error_no_default_issue_status) + return false + end + end + + def parse_params_for_bulk_issue_attributes(params) + attributes = (params[:issue] || {}).reject {|k,v| v.blank?} + attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} + if custom = attributes[:custom_field_values] + custom.reject! {|k,v| v.blank?} + custom.keys.each do |k| + if custom[k].is_a?(Array) + custom[k] << '' if custom[k].delete('__none__') + else + custom[k] = '' if custom[k] == '__none__' + end + end + end + attributes + end + + # Saves @issue and a time_entry from the parameters + def save_issue_with_child_records + Issue.transaction do + if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project) + time_entry = @time_entry || TimeEntry.new + time_entry.project = @issue.project + time_entry.issue = @issue + time_entry.user = User.current + time_entry.spent_on = User.current.today + time_entry.attributes = params[:time_entry] + @issue.time_entries << time_entry + end + + call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal}) + if @issue.save + call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal}) + else + raise ActiveRecord::Rollback + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ac/ac3a2ee58099c23648eac79beac2eb4ee2a7c013.svn-base --- a/.svn/pristine/ac/ac3a2ee58099c23648eac79beac2eb4ee2a7c013.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class WikiPageTest < ActiveSupport::TestCase - fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions - - def setup - @wiki = Wiki.find(1) - @page = @wiki.pages.first - end - - def test_create - page = WikiPage.new(:wiki => @wiki) - assert !page.save - assert_equal 1, page.errors.count - - page.title = "Page" - assert page.save - page.reload - assert !page.protected? - - @wiki.reload - assert @wiki.pages.include?(page) - end - - def test_sidebar_should_be_protected_by_default - page = @wiki.find_or_new_page('sidebar') - assert page.new_record? - assert page.protected? - end - - def test_find_or_new_page - page = @wiki.find_or_new_page("CookBook documentation") - assert_kind_of WikiPage, page - assert !page.new_record? - - page = @wiki.find_or_new_page("Non existing page") - assert_kind_of WikiPage, page - assert page.new_record? - end - - def test_parent_title - page = WikiPage.find_by_title('Another_page') - assert_nil page.parent_title - - page = WikiPage.find_by_title('Page_with_an_inline_image') - assert_equal 'CookBook documentation', page.parent_title - end - - def test_assign_parent - page = WikiPage.find_by_title('Another_page') - page.parent_title = 'CookBook documentation' - assert page.save - page.reload - assert_equal WikiPage.find_by_title('CookBook_documentation'), page.parent - end - - def test_unassign_parent - page = WikiPage.find_by_title('Page_with_an_inline_image') - page.parent_title = '' - assert page.save - page.reload - assert_nil page.parent - end - - def test_parent_validation - page = WikiPage.find_by_title('CookBook_documentation') - - # A page that doesn't exist - page.parent_title = 'Unknown title' - assert !page.save - assert_include I18n.translate('activerecord.errors.messages.invalid'), - page.errors[:parent_title] - # A child page - page.parent_title = 'Page_with_an_inline_image' - assert !page.save - assert_include I18n.translate('activerecord.errors.messages.circular_dependency'), - page.errors[:parent_title] - # The page itself - page.parent_title = 'CookBook_documentation' - assert !page.save - assert_include I18n.translate('activerecord.errors.messages.circular_dependency'), - page.errors[:parent_title] - page.parent_title = 'Another_page' - assert page.save - end - - def test_destroy - page = WikiPage.find(1) - page.destroy - assert_nil WikiPage.find_by_id(1) - # make sure that page content and its history are deleted - assert WikiContent.find_all_by_page_id(1).empty? - assert WikiContent.versioned_class.find_all_by_page_id(1).empty? - end - - def test_destroy_should_not_nullify_children - page = WikiPage.find(2) - child_ids = page.child_ids - assert child_ids.any? - page.destroy - assert_nil WikiPage.find_by_id(2) - - children = WikiPage.find_all_by_id(child_ids) - assert_equal child_ids.size, children.size - children.each do |child| - assert_nil child.parent_id - end - end - - def test_updated_on_eager_load - page = WikiPage.with_updated_on.first(:order => 'id') - assert page.is_a?(WikiPage) - assert_not_nil page.read_attribute(:updated_on) - assert_equal Time.gm(2007, 3, 6, 23, 10, 51), page.content.updated_on - assert_equal page.content.updated_on, page.updated_on - assert_not_nil page.read_attribute(:version) - end - - def test_descendants - page = WikiPage.create!(:wiki => @wiki, :title => 'Parent') - child1 = WikiPage.create!(:wiki => @wiki, :title => 'Child1', :parent => page) - child11 = WikiPage.create!(:wiki => @wiki, :title => 'Child11', :parent => child1) - child111 = WikiPage.create!(:wiki => @wiki, :title => 'Child111', :parent => child11) - child2 = WikiPage.create!(:wiki => @wiki, :title => 'Child2', :parent => page) - - assert_equal %w(Child1 Child11 Child111 Child2), page.descendants.map(&:title).sort - assert_equal %w(Child1 Child11 Child111 Child2), page.descendants(nil).map(&:title).sort - assert_equal %w(Child1 Child11 Child2), page.descendants(2).map(&:title).sort - assert_equal %w(Child1 Child2), page.descendants(1).map(&:title).sort - - assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants.map(&:title).sort - assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants(nil).map(&:title).sort - assert_equal %w(Child1 Child11 Child2 Parent), page.self_and_descendants(2).map(&:title).sort - assert_equal %w(Child1 Child2 Parent), page.self_and_descendants(1).map(&:title).sort - end - - def test_diff_for_page_with_deleted_version_should_pick_the_previous_available_version - WikiContent::Version.find_by_page_id_and_version(1, 2).destroy - - page = WikiPage.find(1) - diff = page.diff(3) - assert_not_nil diff - assert_equal 3, diff.content_to.version - assert_equal 1, diff.content_from.version - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ac/ac3cb3eefdd851a5f9d663d9f434eeb017054b9d.svn-base --- a/.svn/pristine/ac/ac3cb3eefdd851a5f9d663d9f434eeb017054b9d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -# Default setup is given for MySQL with ruby1.8. If you're running Redmine -# with MySQL and ruby1.9, replace the adapter name with `mysql2`. -# Examples for PostgreSQL and SQLite3 can be found at the end. - -production: - adapter: mysql - database: redmine - host: localhost - username: root - password: "" - encoding: utf8 - -development: - adapter: mysql - database: redmine_development - host: localhost - username: root - password: "" - encoding: utf8 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - adapter: mysql - database: redmine_test - host: localhost - username: root - password: "" - encoding: utf8 - -test_pgsql: - adapter: postgresql - database: redmine_test - host: localhost - username: postgres - password: "postgres" - -test_sqlite3: - adapter: sqlite3 - database: db/test.sqlite3 diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ac/acb16fb7d1a43ca9b265d3fc139a9a8c3d61e7c6.svn-base --- a/.svn/pristine/ac/acb16fb7d1a43ca9b265d3fc139a9a8c3d61e7c6.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class SearchController < ApplicationController - before_filter :find_optional_project - - helper :messages - include MessagesHelper - - def index - @question = params[:q] || "" - @question.strip! - @all_words = params[:all_words] ? params[:all_words].present? : true - @titles_only = params[:titles_only] ? params[:titles_only].present? : false - - projects_to_search = - case params[:scope] - when 'all' - nil - when 'my_projects' - User.current.memberships.collect(&:project) - when 'subprojects' - @project ? (@project.self_and_descendants.active.all) : nil - else - @project - end - - offset = nil - begin; offset = params[:offset].to_time if params[:offset]; rescue; end - - # quick jump to an issue - if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i) - redirect_to :controller => "issues", :action => "show", :id => $1 - return - end - - @object_types = Redmine::Search.available_search_types.dup - if projects_to_search.is_a? Project - # don't search projects - @object_types.delete('projects') - # only show what the user is allowed to view - @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)} - end - - @scope = @object_types.select {|t| params[t]} - @scope = @object_types if @scope.empty? - - # extract tokens from the question - # eg. hello "bye bye" => ["hello", "bye bye"] - @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} - # tokens must be at least 2 characters long - @tokens = @tokens.uniq.select {|w| w.length > 1 } - - if !@tokens.empty? - # no more than 5 tokens to search for - @tokens.slice! 5..-1 if @tokens.size > 5 - - @results = [] - @results_by_type = Hash.new {|h,k| h[k] = 0} - - limit = 10 - @scope.each do |s| - r, c = s.singularize.camelcase.constantize.search(@tokens, projects_to_search, - :all_words => @all_words, - :titles_only => @titles_only, - :limit => (limit+1), - :offset => offset, - :before => params[:previous].nil?) - @results += r - @results_by_type[s] += c - end - @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime} - if params[:previous].nil? - @pagination_previous_date = @results[0].event_datetime if offset && @results[0] - if @results.size > limit - @pagination_next_date = @results[limit-1].event_datetime - @results = @results[0, limit] - end - else - @pagination_next_date = @results[-1].event_datetime if offset && @results[-1] - if @results.size > limit - @pagination_previous_date = @results[-(limit)].event_datetime - @results = @results[-(limit), limit] - end - end - else - @question = "" - end - render :layout => false if request.xhr? - end - -private - def find_optional_project - return true unless params[:id] - @project = Project.find(params[:id]) - check_project_privacy - rescue ActiveRecord::RecordNotFound - render_404 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/ad0238c786fdc47f0ed2113b9d3c0fe464953281.svn-base --- a/.svn/pristine/ad/ad0238c786fdc47f0ed2113b9d3c0fe464953281.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Views - module Builders - def self.for(format, request, response, &block) - builder = case format - when 'xml', :xml; Builders::Xml.new(request, response) - when 'json', :json; Builders::Json.new(request, response) - else; raise "No builder for format #{format}" - end - if block - block.call(builder) - else - builder - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/ad194258daac897f0c8e0281fc155c7b77d1ff1e.svn-base --- a/.svn/pristine/ad/ad194258daac897f0c8e0281fc155c7b77d1ff1e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class Redmine::NotifiableTest < ActiveSupport::TestCase - def setup - end - - def test_all - assert_equal 12, Redmine::Notifiable.all.length - - %w(issue_added issue_updated issue_note_added issue_status_updated issue_priority_updated news_added news_comment_added document_added file_added message_posted wiki_content_added wiki_content_updated).each do |notifiable| - assert Redmine::Notifiable.all.collect(&:name).include?(notifiable), "missing #{notifiable}" - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/ad198b4ca217e676200add6d2e30b0345b1e653c.svn-base --- a/.svn/pristine/ad/ad198b4ca217e676200add6d2e30b0345b1e653c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -david_action_controller: - developer_id: 1 - project_id: 2 - joined_on: 2004-10-10 - -david_active_record: - developer_id: 1 - project_id: 1 - joined_on: 2004-10-10 - -jamis_active_record: - developer_id: 2 - project_id: 1 \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/ad1b2e21afd9705f8ae7a1d13601f98473cd206b.svn-base --- a/.svn/pristine/ad/ad1b2e21afd9705f8ae7a1d13601f98473cd206b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class Reply < ActiveRecord::Base - belongs_to :topic, :include => [:replies] - - validates_presence_of :content -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/ad7462fa3902df8825690cb1e165b92bb8407c2c.svn-base --- a/.svn/pristine/ad/ad7462fa3902df8825690cb1e165b92bb8407c2c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -<%= form_tag({:action => 'edit', :tab => 'issues'}, :onsubmit => 'selectAllOptions("selected_columns");') do %> - -
    -

    <%= setting_check_box :cross_project_issue_relations %>

    - -

    <%= setting_select :cross_project_subtasks, cross_project_subtasks_options %>

    - -

    <%= setting_check_box :issue_group_assignment %>

    - -

    <%= setting_check_box :default_issue_start_date_to_creation_date %>

    - -

    <%= setting_check_box :display_subprojects_issues %>

    - -

    <%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %>

    - -

    <%= setting_multiselect :non_working_week_days, (1..7).map {|d| [day_name(d), d.to_s]}, :inline => true %>

    - -

    <%= setting_text_field :issues_export_limit, :size => 6 %>

    - -

    <%= setting_text_field :gantt_items_limit, :size => 6 %>

    -
    - -
    - <%= l(:setting_issue_list_default_columns) %> - <%= render :partial => 'queries/columns', - :locals => { - :query => Query.new(:column_names => Setting.issue_list_default_columns), - :tag_name => 'settings[issue_list_default_columns][]' - } %> -
    - -<%= submit_tag l(:button_save) %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/ad957ccce8d55237a463953923542a0cf137f251.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ad/ad957ccce8d55237a463953923542a0cf137f251.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,17 @@ +class SupportForMultipleCommitKeywords < ActiveRecord::Migration + def up + # Replaces commit_fix_keywords, commit_fix_status_id, commit_fix_done_ratio settings + # with commit_update_keywords setting + keywords = Setting.where(:name => 'commit_fix_keywords').limit(1).pluck(:value).first + status_id = Setting.where(:name => 'commit_fix_status_id').limit(1).pluck(:value).first + done_ratio = Setting.where(:name => 'commit_fix_done_ratio').limit(1).pluck(:value).first + if keywords.present? + Setting.commit_update_keywords = [{'keywords' => keywords, 'status_id' => status_id, 'done_ratio' => done_ratio}] + end + Setting.where(:name => %w(commit_fix_keywords commit_fix_status_id commit_fix_done_ratio)).delete_all + end + + def down + Setting.where(:name => 'commit_update_keywords').delete_all + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/ada11f96e495b87cafaee97c96fbbe8b5b160b08.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ad/ada11f96e495b87cafaee97c96fbbe8b5b160b08.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,67 @@ +<%= error_messages_for 'user' %> + +
    + +
    +
    + <%=l(:label_information_plural)%> +

    <%= f.text_field :login, :required => true, :size => 25 %>

    +

    <%= f.text_field :firstname, :required => true %>

    +

    <%= f.text_field :lastname, :required => true %>

    +

    <%= f.text_field :mail, :required => true %>

    +

    <%= f.select :language, lang_options_for_select %>

    + <% if Setting.openid? %> +

    <%= f.text_field :identity_url %>

    + <% end %> + + <% @user.custom_field_values.each do |value| %> +

    <%= custom_field_tag_with_label :user, value %>

    + <% end %> + +

    <%= f.check_box :admin, :disabled => (@user == User.current) %>

    + <%= call_hook(:view_users_form, :user => @user, :form => f) %> +
    + +
    + <%=l(:label_authentication)%> + <% unless @auth_sources.empty? %> +

    <%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }), {}, :onchange => "if (this.value=='') {$('#password_fields').show();} else {$('#password_fields').hide();}" %>

    + <% end %> +
    +

    <%= f.password_field :password, :required => true, :size => 25 %> + <%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

    +

    <%= f.password_field :password_confirmation, :required => true, :size => 25 %>

    +

    <%= f.check_box :generate_password %>

    +

    <%= f.check_box :must_change_passwd %>

    +
    +
    +
    + +
    +
    + <%=l(:field_mail_notification)%> + <%= render :partial => 'users/mail_notifications' %> +
    + +
    + <%=l(:label_preferences)%> + <%= render :partial => 'users/preferences' %> + <%= call_hook(:view_users_form_preferences, :user => @user, :form => f) %> +
    +
    +
    +
    + + +<%= javascript_tag do %> +$(document).ready(function(){ + $('#user_generate_password').change(function(){ + var passwd = $('#user_password, #user_password_confirmation'); + if ($(this).is(':checked')){ + passwd.val('').attr('disabled', true); + }else{ + passwd.removeAttr('disabled'); + } + }).trigger('change'); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/ada6049cb9e3f42d297d010fa449795702a9f12a.svn-base --- a/.svn/pristine/ad/ada6049cb9e3f42d297d010fa449795702a9f12a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -

    <%=l(:label_issue_new)%>

    - -<%= call_hook(:view_issues_new_top, {:issue => @issue}) %> - -<%= labelled_form_for @issue, :url => project_issues_path(@project), - :html => {:id => 'issue-form', :multipart => true} do |f| %> - <%= error_messages_for 'issue' %> - <%= hidden_field_tag 'copy_from', params[:copy_from] if params[:copy_from] %> -
    -
    - <%= render :partial => 'issues/form', :locals => {:f => f} %> -
    - - <% if @copy_from && @copy_from.attachments.any? %> -

    - - <%= check_box_tag 'copy_attachments', '1', @copy_attachments %> -

    - <% end %> - <% if @copy_from && !@copy_from.leaf? %> -

    - - <%= check_box_tag 'copy_subtasks', '1', @copy_subtasks %> -

    - <% end %> - -

    <%= render :partial => 'attachments/form', :locals => {:container => @issue} %>

    - - <% if @issue.safe_attribute? 'watcher_user_ids' -%> -

    - - <%= watchers_checkboxes(@issue, @available_watchers) %> - - - <%= link_to l(:label_search_for_watchers), - {:controller => 'watchers', :action => 'new', :project_id => @issue.project}, - :remote => true, - :method => 'get' %> - -

    - <% end %> -
    - - <%= submit_tag l(:button_create) %> - <%= submit_tag l(:button_create_and_continue), :name => 'continue' %> - <%= preview_link preview_new_issue_path(:project_id => @project), 'issue-form' %> - - <%= javascript_tag "$('#issue_subject').focus();" %> -<% end %> - -
    - -<% content_for :header_tags do %> - <%= robot_exclusion_tag %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/adb25c6fe998d19f312cf127b845819361f33fb5.svn-base --- a/.svn/pristine/ad/adb25c6fe998d19f312cf127b845819361f33fb5.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module RolesHelper -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/add3430324c137a983a618e2f764a7d937005ad5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ad/add3430324c137a983a618e2f764a7d937005ad5.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,67 @@ +<% roles = Role.find_all_givable %> +<% projects = Project.active.all %> + +
    +<% if @user.memberships.any? %> + + + + + + <%= call_hook(:view_users_memberships_table_header, :user => @user )%> + + + <% @user.memberships.each do |membership| %> + <% next if membership.new_record? %> + + + + + <%= call_hook(:view_users_memberships_table_row, :user => @user, :membership => membership, :roles => roles, :projects => projects )%> + + <% end; reset_cycle %> + +
    <%= l(:label_project) %><%= l(:label_role_plural) %>
    + <%= link_to_project membership.project %> + + <%=h membership.roles.sort.collect(&:to_s).join(', ') %> + <%= form_for(:membership, :remote => true, + :url => user_membership_path(@user, membership), :method => :put, + :html => {:id => "member-#{membership.id}-roles-form", + :style => 'display:none;'}) do %> +

    <% roles.each do |role| %> +
    + <% end %>

    + <%= hidden_field_tag 'membership[role_ids][]', '' %> +

    <%= submit_tag l(:button_change) %> + <%= link_to_function l(:button_cancel), + "$('#member-#{membership.id}-roles').show(); $('#member-#{membership.id}-roles-form').hide(); return false;" + %>

    + <% end %> +
    + <%= link_to_function l(:button_edit), + "$('#member-#{membership.id}-roles').hide(); $('#member-#{membership.id}-roles-form').show(); return false;", + :class => 'icon icon-edit' + %> + <%= delete_link user_membership_path(@user, membership), :remote => true if membership.deletable? %> +
    +<% else %> +

    <%= l(:label_no_data) %>

    +<% end %> +
    + +
    +<% if projects.any? %> +
    <%=l(:label_project_new)%> +<%= form_for(:membership, :remote => true, :url => user_memberships_path(@user)) do %> +<%= select_tag 'membership[project_id]', options_for_membership_project_select(@user, projects) %> +

    <%= l(:label_role_plural) %>: +<% roles.each do |role| %> + +<% end %>

    +

    <%= submit_tag l(:button_add) %>

    +<% end %> +
    +<% end %> +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/adf31b2936fe356e09b9ab737cd473578efad2f1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ad/adf31b2936fe356e09b9ab737cd473578efad2f1.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,18 @@ +
    + + +<% line_num = 1 %> +<% syntax_highlight_lines(filename, Redmine::CodesetUtil.to_utf8_by_setting(content)).each do |line| %> + + + + + <% line_num += 1 %> +<% end %> + +
    + <%= line_num %> + +
    <%= line.html_safe %>
    +
    +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ad/adf63bdb3d105b946c217c01392128a7d913bff0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ad/adf63bdb3d105b946c217c01392128a7d913bff0.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,26 @@ +

    <%=l(:button_change_password)%>

    + +<%= error_messages_for 'user' %> + +<%= form_tag({}, :class => "tabular") do %> +
    +

    +<%= password_field_tag 'password', nil, :size => 25 %>

    + +

    +<%= password_field_tag 'new_password', nil, :size => 25 %> +<%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

    + +

    +<%= password_field_tag 'new_password_confirmation', nil, :size => 25 %>

    +
    +<%= submit_tag l(:button_apply) %> +<% end %> + +<% unless @user.must_change_passwd? %> +<% content_for :sidebar do %> +<%= render :partial => 'sidebar' %> +<% end %> +<% end %> + +<%= javascript_tag "$('#password').focus();" %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ae/ae1f195964d1ff8b740bcbc54f3dee15904d207f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ae/ae1f195964d1ff8b740bcbc54f3dee15904d207f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,662 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class RepositoriesGitControllerTest < ActionController::TestCase + tests RepositoriesController + + fixtures :projects, :users, :roles, :members, :member_roles, + :repositories, :enabled_modules + + REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s + REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? + PRJ_ID = 3 + CHAR_1_HEX = "\xc3\x9c" + FELIX_HEX = "Felix Sch\xC3\xA4fer" + NUM_REV = 28 + + ## Git, Mercurial and CVS path encodings are binary. + ## Subversion supports URL encoding for path. + ## Redmine Mercurial adapter and extension use URL encoding. + ## Git accepts only binary path in command line parameter. + ## So, there is no way to use binary command line parameter in JRuby. + JRUBY_SKIP = (RUBY_PLATFORM == 'java') + JRUBY_SKIP_STR = "TODO: This test fails in JRuby" + + def setup + @ruby19_non_utf8_pass = + (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8') + + User.current = nil + @project = Project.find(PRJ_ID) + @repository = Repository::Git.create( + :project => @project, + :url => REPOSITORY_PATH, + :path_encoding => 'ISO-8859-1' + ) + assert @repository + @char_1 = CHAR_1_HEX.dup + @felix_utf8 = FELIX_HEX.dup + if @char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + @felix_utf8.force_encoding('UTF-8') + end + end + + def test_create_and_update + @request.session[:user_id] = 1 + assert_difference 'Repository.count' do + post :create, :project_id => 'subproject1', + :repository_scm => 'Git', + :repository => { + :url => '/test', + :is_default => '0', + :identifier => 'test-create', + :extra_report_last_commit => '1', + } + end + assert_response 302 + repository = Repository.first(:order => 'id DESC') + assert_kind_of Repository::Git, repository + assert_equal '/test', repository.url + assert_equal true, repository.extra_report_last_commit + + put :update, :id => repository.id, + :repository => { + :extra_report_last_commit => '0' + } + assert_response 302 + repo2 = Repository.find(repository.id) + assert_equal false, repo2.extra_report_last_commit + end + + if File.directory?(REPOSITORY_PATH) + ## Ruby uses ANSI api to fork a process on Windows. + ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem + ## and these are incompatible with ASCII. + ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10 + ## http://code.google.com/p/msysgit/issues/detail?id=80 + ## So, Latin-1 path tests fail on Japanese Windows + WINDOWS_PASS = (Redmine::Platform.mswin? && + Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10])) + WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10" + + def test_get_new + @request.session[:user_id] = 1 + @project.repository.destroy + get :new, :project_id => 'subproject1', :repository_scm => 'Git' + assert_response :success + assert_template 'new' + assert_kind_of Repository::Git, assigns(:repository) + assert assigns(:repository).new_record? + end + + def test_browse_root + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + get :show, :id => PRJ_ID + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal 9, assigns(:entries).size + assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'this_is_a_really_long_and_verbose_directory_name' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'copied_README' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'new_file.txt' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'renamed_test.txt' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'filemane with spaces.txt' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == ' filename with a leading space.txt ' && e.kind == 'file'} + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + + def test_browse_branch + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :rev => 'test_branch' + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal 4, assigns(:entries).size + assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'test.txt' && e.kind == 'file'} + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + + def test_browse_tag + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + [ + "tag00.lightweight", + "tag01.annotated", + ].each do |t1| + get :show, :id => PRJ_ID, :rev => t1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert assigns(:entries).size > 0 + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + end + + def test_browse_directory + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param] + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal ['edit.png'], assigns(:entries).collect(&:name) + entry = assigns(:entries).detect {|e| e.name == 'edit.png'} + assert_not_nil entry + assert_equal 'file', entry.kind + assert_equal 'images/edit.png', entry.path + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + + def test_browse_at_given_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param], + :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518' + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal ['delete.png'], assigns(:entries).collect(&:name) + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + + def test_changes + get :changes, :id => PRJ_ID, + :path => repository_path_hash(['images', 'edit.png'])[:param] + assert_response :success + assert_template 'changes' + assert_tag :tag => 'h2', :content => 'edit.png' + end + + def test_entry_show + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] + assert_response :success + assert_template 'entry' + # Line 19 + assert_tag :tag => 'th', + :content => '11', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ } + end + + def test_entry_show_latin_1 + if @ruby19_non_utf8_pass + puts_ruby19_non_utf8_pass() + elsif WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do + ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param], + :rev => r1 + assert_response :success + assert_template 'entry' + assert_tag :tag => 'th', + :content => '1', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', + :content => /test-#{@char_1}.txt/ } + end + end + end + end + + def test_entry_download + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], + :format => 'raw' + assert_response :success + # File content + assert @response.body.include?('WITHOUT ANY WARRANTY') + end + + def test_directory_entry + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['sources'])[:param] + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entry) + assert_equal 'sources', assigns(:entry).name + end + + def test_diff + assert_equal true, @repository.is_default + assert_nil @repository.identifier + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + # Full diff of changeset 2f9c0091 + ['inline', 'sbs'].each do |dt| + get :diff, + :id => PRJ_ID, + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', + :type => dt + assert_response :success + assert_template 'diff' + # Line 22 removed + assert_tag :tag => 'th', + :content => /22/, + :sibling => { :tag => 'td', + :attributes => { :class => /diff_out/ }, + :content => /def remove/ } + assert_tag :tag => 'h2', :content => /2f9c0091/ + end + end + + def test_diff_with_rev_and_path + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + with_settings :diff_max_lines_displayed => 1000 do + # Full diff of changeset 2f9c0091 + ['inline', 'sbs'].each do |dt| + get :diff, + :id => PRJ_ID, + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], + :type => dt + assert_response :success + assert_template 'diff' + # Line 22 removed + assert_tag :tag => 'th', + :content => '22', + :sibling => { :tag => 'td', + :attributes => { :class => /diff_out/ }, + :content => /def remove/ } + assert_tag :tag => 'h2', :content => /2f9c0091/ + end + end + end + + def test_diff_truncated + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + with_settings :diff_max_lines_displayed => 5 do + # Truncated diff of changeset 2f9c0091 + with_cache do + with_settings :default_language => 'en' do + get :diff, :id => PRJ_ID, :type => 'inline', + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' + assert_response :success + assert @response.body.include?("... This diff was truncated") + end + with_settings :default_language => 'fr' do + get :diff, :id => PRJ_ID, :type => 'inline', + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' + assert_response :success + assert ! @response.body.include?("... This diff was truncated") + assert @response.body.include?("... Ce diff") + end + end + end + end + + def test_diff_two_revs + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['inline', 'sbs'].each do |dt| + get :diff, + :id => PRJ_ID, + :rev => '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', + :rev_to => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', + :type => dt + assert_response :success + assert_template 'diff' + diff = assigns(:diff) + assert_not_nil diff + assert_tag :tag => 'h2', :content => /2f9c0091:61b685fb/ + assert_tag :tag => "form", + :attributes => { + :action => "/projects/subproject1/repository/revisions/" + + "61b685fbe55ab05b5ac68402d5720c1a6ac973d1/diff" + } + assert_tag :tag => 'input', + :attributes => { + :id => "rev_to", + :name => "rev_to", + :type => "hidden", + :value => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' + } + end + end + + def test_diff_path_in_subrepo + repo = Repository::Git.create( + :project => @project, + :url => REPOSITORY_PATH, + :identifier => 'test-diff-path', + :path_encoding => 'ISO-8859-1' + ); + assert repo + assert_equal false, repo.is_default + assert_equal 'test-diff-path', repo.identifier + get :diff, + :id => PRJ_ID, + :repository_id => 'test-diff-path', + :rev => '61b685fbe55ab05b', + :rev_to => '2f9c0091c754a91a', + :type => 'inline' + assert_response :success + assert_template 'diff' + diff = assigns(:diff) + assert_not_nil diff + assert_tag :tag => "form", + :attributes => { + :action => "/projects/subproject1/repository/test-diff-path/" + + "revisions/61b685fbe55ab05b/diff" + } + assert_tag :tag => 'input', + :attributes => { + :id => "rev_to", + :name => "rev_to", + :type => "hidden", + :value => '2f9c0091c754a91a' + } + end + + def test_diff_latin_1 + if @ruby19_non_utf8_pass + puts_ruby19_non_utf8_pass() + else + with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do + ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| + ['inline', 'sbs'].each do |dt| + get :diff, :id => PRJ_ID, :rev => r1, :type => dt + assert_response :success + assert_template 'diff' + assert_tag :tag => 'thead', + :descendant => { + :tag => 'th', + :attributes => { :class => 'filename' } , + :content => /latin-1-dir\/test-#{@char_1}.txt/ , + }, + :sibling => { + :tag => 'tbody', + :descendant => { + :tag => 'td', + :attributes => { :class => /diff_in/ }, + :content => /test-#{@char_1}.txt/ + } + } + end + end + end + end + end + + def test_diff_should_show_filenames + get :diff, :id => PRJ_ID, :rev => 'deff712f05a90d96edbd70facc47d944be5897e3', :type => 'inline' + assert_response :success + assert_template 'diff' + # modified file + assert_select 'th.filename', :text => 'sources/watchers_controller.rb' + # deleted file + assert_select 'th.filename', :text => 'test.txt' + end + + def test_save_diff_type + user1 = User.find(1) + user1.pref[:diff_type] = nil + user1.preference.save + user = User.find(1) + assert_nil user.pref[:diff_type] + + @request.session[:user_id] = 1 # admin + get :diff, + :id => PRJ_ID, + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' + assert_response :success + assert_template 'diff' + user.reload + assert_equal "inline", user.pref[:diff_type] + get :diff, + :id => PRJ_ID, + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', + :type => 'sbs' + assert_response :success + assert_template 'diff' + user.reload + assert_equal "sbs", user.pref[:diff_type] + end + + def test_annotate + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] + assert_response :success + assert_template 'annotate' + + # Line 23, changeset 2f9c0091 + assert_select 'tr' do + assert_select 'th.line-num', :text => '23' + assert_select 'td.revision', :text => /2f9c0091/ + assert_select 'td.author', :text => 'jsmith' + assert_select 'td', :text => /remove_watcher/ + end + end + + def test_annotate_at_given_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :annotate, :id => PRJ_ID, :rev => 'deff7', + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] + assert_response :success + assert_template 'annotate' + assert_tag :tag => 'h2', :content => /@ deff712f/ + end + + def test_annotate_binary_file + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['images', 'edit.png'])[:param] + assert_response 500 + assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, + :content => /cannot be annotated/ + end + + def test_annotate_error_when_too_big + with_settings :file_max_size_displayed => 1 do + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], + :rev => 'deff712f' + assert_response 500 + assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, + :content => /exceeds the maximum text file size/ + + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['README'])[:param], + :rev => '7234cb2' + assert_response :success + assert_template 'annotate' + end + end + + def test_annotate_latin_1 + if @ruby19_non_utf8_pass + puts_ruby19_non_utf8_pass() + elsif WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do + ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param], + :rev => r1 + assert_select "th.line-num", :text => '1' do + assert_select "+ td.revision" do + assert_select "a", :text => '57ca437c' + assert_select "+ td.author", :text => "jsmith" do + assert_select "+ td", + :text => "test-#{@char_1}.txt" + end + end + end + end + end + end + end + + def test_annotate_latin_1_author + ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', '83ca5fd546063a'].each do |r1| + get :annotate, :id => PRJ_ID, + :path => repository_path_hash([" filename with a leading space.txt "])[:param], + :rev => r1 + assert_select "th.line-num", :text => '1' do + assert_select "+ td.revision" do + assert_select "a", :text => '83ca5fd5' + assert_select "+ td.author", :text => @felix_utf8 do + assert_select "+ td", + :text => "And this is a file with a leading and trailing space..." + end + end + end + end + end + + def test_revisions + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :revisions, :id => PRJ_ID + assert_response :success + assert_template 'revisions' + assert_tag :tag => 'form', + :attributes => { + :method => 'get', + :action => '/projects/subproject1/repository/revision' + } + end + + def test_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['61b685fbe55ab05b5ac68402d5720c1a6ac973d1', '61b685f'].each do |r| + get :revision, :id => PRJ_ID, :rev => r + assert_response :success + assert_template 'revision' + end + end + + def test_empty_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['', ' ', nil].each do |r| + get :revision, :id => PRJ_ID, :rev => r + assert_response 404 + assert_error_tag :content => /was not found/ + end + end + + def test_destroy_valid_repository + @request.session[:user_id] = 1 # admin + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + assert_difference 'Repository.count', -1 do + delete :destroy, :id => @repository.id + end + assert_response 302 + @project.reload + assert_nil @project.repository + end + + def test_destroy_invalid_repository + @request.session[:user_id] = 1 # admin + @project.repository.destroy + @repository = Repository::Git.create!( + :project => @project, + :url => "/invalid", + :path_encoding => 'ISO-8859-1' + ) + @repository.fetch_changesets + @repository.reload + assert_equal 0, @repository.changesets.count + + assert_difference 'Repository.count', -1 do + delete :destroy, :id => @repository.id + end + assert_response 302 + @project.reload + assert_nil @project.repository + end + + private + + def puts_ruby19_non_utf8_pass + puts "TODO: This test fails in Ruby 1.9 " + + "and Encoding.default_external is not UTF-8. " + + "Current value is '#{Encoding.default_external.to_s}'" + end + else + puts "Git test repository NOT FOUND. Skipping functional tests !!!" + def test_fake; assert true end + end + + private + def with_cache(&block) + before = ActionController::Base.perform_caching + ActionController::Base.perform_caching = true + block.call + ActionController::Base.perform_caching = before + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ae/ae90afe2979961b1a71c92c215405e87e9e7ab30.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ae/ae90afe2979961b1a71c92c215405e87e9e7ab30.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module MailHandlerHelper +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ae/ae9b39171caeb6f8f6a13fdb73ccbb11ad433254.svn-base --- a/.svn/pristine/ae/ae9b39171caeb6f8f6a13fdb73ccbb11ad433254.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,297 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module RepositoriesHelper - def format_revision(revision) - if revision.respond_to? :format_identifier - revision.format_identifier - else - revision.to_s - end - end - - def truncate_at_line_break(text, length = 255) - if text - text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...') - end - end - - def render_properties(properties) - unless properties.nil? || properties.empty? - content = '' - properties.keys.sort.each do |property| - content << content_tag('li', "#{h property}: #{h properties[property]}".html_safe) - end - content_tag('ul', content.html_safe, :class => 'properties') - end - end - - def render_changeset_changes - changes = @changeset.filechanges.find(:all, :limit => 1000, :order => 'path').collect do |change| - case change.action - when 'A' - # Detects moved/copied files - if !change.from_path.blank? - change.action = - @changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C' - end - change - when 'D' - @changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change - else - change - end - end.compact - - tree = { } - changes.each do |change| - p = tree - dirs = change.path.to_s.split('/').select {|d| !d.blank?} - path = '' - dirs.each do |dir| - path += '/' + dir - p[:s] ||= {} - p = p[:s] - p[path] ||= {} - p = p[path] - end - p[:c] = change - end - render_changes_tree(tree[:s]) - end - - def render_changes_tree(tree) - return '' if tree.nil? - output = '' - output << '
      ' - tree.keys.sort.each do |file| - style = 'change' - text = File.basename(h(file)) - if s = tree[file][:s] - style << ' folder' - path_param = to_path_param(@repository.relative_path(file)) - text = link_to(h(text), :controller => 'repositories', - :action => 'show', - :id => @project, - :repository_id => @repository.identifier_param, - :path => path_param, - :rev => @changeset.identifier) - output << "
    • #{text}" - output << render_changes_tree(s) - output << "
    • " - elsif c = tree[file][:c] - style << " change-#{c.action}" - path_param = to_path_param(@repository.relative_path(c.path)) - text = link_to(h(text), :controller => 'repositories', - :action => 'entry', - :id => @project, - :repository_id => @repository.identifier_param, - :path => path_param, - :rev => @changeset.identifier) unless c.action == 'D' - text << " - #{h(c.revision)}" unless c.revision.blank? - text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories', - :action => 'diff', - :id => @project, - :repository_id => @repository.identifier_param, - :path => path_param, - :rev => @changeset.identifier) + ') '.html_safe if c.action == 'M' - text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank? - output << "
    • #{text}
    • " - end - end - output << '
    ' - output.html_safe - end - - def repository_field_tags(form, repository) - method = repository.class.name.demodulize.underscore + "_field_tags" - if repository.is_a?(Repository) && - respond_to?(method) && method != 'repository_field_tags' - send(method, form, repository) - end - end - - def scm_select_tag(repository) - scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']] - Redmine::Scm::Base.all.each do |scm| - if Setting.enabled_scm.include?(scm) || - (repository && repository.class.name.demodulize == scm) - scm_options << ["Repository::#{scm}".constantize.scm_name, scm] - end - end - select_tag('repository_scm', - options_for_select(scm_options, repository.class.name.demodulize), - :disabled => (repository && !repository.new_record?), - :data => {:remote => true, :method => 'get'}) - end - - def with_leading_slash(path) - path.to_s.starts_with?('/') ? path : "/#{path}" - end - - def without_leading_slash(path) - path.gsub(%r{^/+}, '') - end - - def subversion_field_tags(form, repository) - content_tag('p', form.text_field(:url, :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url')) + - '
    '.html_safe + - '(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') + - content_tag('p', form.text_field(:login, :size => 30)) + - content_tag('p', form.password_field( - :password, :size => 30, :name => 'ignore', - :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)), - :onfocus => "this.value=''; this.name='repository[password]';", - :onchange => "this.name='repository[password]';")) - end - - def darcs_field_tags(form, repository) - content_tag('p', form.text_field( - :url, :label => l(:field_path_to_repository), - :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url'))) + - content_tag('p', form.select( - :log_encoding, [nil] + Setting::ENCODINGS, - :label => l(:field_commit_logs_encoding), :required => true)) - end - - def mercurial_field_tags(form, repository) - content_tag('p', form.text_field( - :url, :label => l(:field_path_to_repository), - :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url') - ) + - '
    '.html_safe + l(:text_mercurial_repository_note)) + - content_tag('p', form.select( - :path_encoding, [nil] + Setting::ENCODINGS, - :label => l(:field_scm_path_encoding) - ) + - '
    '.html_safe + l(:text_scm_path_encoding_note)) - end - - def git_field_tags(form, repository) - content_tag('p', form.text_field( - :url, :label => l(:field_path_to_repository), - :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url') - ) + - '
    '.html_safe + - l(:text_git_repository_note)) + - content_tag('p', form.select( - :path_encoding, [nil] + Setting::ENCODINGS, - :label => l(:field_scm_path_encoding) - ) + - '
    '.html_safe + l(:text_scm_path_encoding_note)) + - content_tag('p', form.check_box( - :extra_report_last_commit, - :label => l(:label_git_report_last_commit) - )) - end - - def cvs_field_tags(form, repository) - content_tag('p', form.text_field( - :root_url, - :label => l(:field_cvsroot), - :size => 60, :required => true, - :disabled => !repository.safe_attribute?('root_url'))) + - content_tag('p', form.text_field( - :url, - :label => l(:field_cvs_module), - :size => 30, :required => true, - :disabled => !repository.safe_attribute?('url'))) + - content_tag('p', form.select( - :log_encoding, [nil] + Setting::ENCODINGS, - :label => l(:field_commit_logs_encoding), :required => true)) + - content_tag('p', form.select( - :path_encoding, [nil] + Setting::ENCODINGS, - :label => l(:field_scm_path_encoding) - ) + - '
    '.html_safe + l(:text_scm_path_encoding_note)) - end - - def bazaar_field_tags(form, repository) - content_tag('p', form.text_field( - :url, :label => l(:field_path_to_repository), - :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url'))) + - content_tag('p', form.select( - :log_encoding, [nil] + Setting::ENCODINGS, - :label => l(:field_commit_logs_encoding), :required => true)) - end - - def filesystem_field_tags(form, repository) - content_tag('p', form.text_field( - :url, :label => l(:field_root_directory), - :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url'))) + - content_tag('p', form.select( - :path_encoding, [nil] + Setting::ENCODINGS, - :label => l(:field_scm_path_encoding) - ) + - '
    '.html_safe + l(:text_scm_path_encoding_note)) - end - - def index_commits(commits, heads) - return nil if commits.nil? or commits.first.parents.nil? - refs_map = {} - heads.each do |head| - refs_map[head.scmid] ||= [] - refs_map[head.scmid] << head - end - commits_by_scmid = {} - commits.reverse.each_with_index do |commit, commit_index| - commits_by_scmid[commit.scmid] = { - :parent_scmids => commit.parents.collect { |parent| parent.scmid }, - :rdmid => commit_index, - :refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil, - :scmid => commit.scmid, - :href => block_given? ? yield(commit.scmid) : commit.scmid - } - end - heads.sort! { |head1, head2| head1.to_s <=> head2.to_s } - space = nil - heads.each do |head| - if commits_by_scmid.include? head.scmid - space = index_head((space || -1) + 1, head, commits_by_scmid) - end - end - # when no head matched anything use first commit - space ||= index_head(0, commits.first, commits_by_scmid) - return commits_by_scmid, space - end - - def index_head(space, commit, commits_by_scmid) - stack = [[space, commits_by_scmid[commit.scmid]]] - max_space = space - until stack.empty? - space, commit = stack.pop - commit[:space] = space if commit[:space].nil? - space -= 1 - commit[:parent_scmids].each_with_index do |parent_scmid, parent_index| - parent_commit = commits_by_scmid[parent_scmid] - if parent_commit and parent_commit[:space].nil? - stack.unshift [space += 1, parent_commit] - end - end - max_space = space if max_space < space - end - max_space - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ae/aeb7461845d55dcd77e6813155582781f1a0ad2a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ae/aeb7461845d55dcd77e6813155582781f1a0ad2a.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,129 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class ActivityTest < ActiveSupport::TestCase + fixtures :projects, :versions, :attachments, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, + :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :time_entries, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + + def setup + @project = Project.find(1) + end + + def test_activity_without_subprojects + events = find_events(User.anonymous, :project => @project) + assert_not_nil events + + assert events.include?(Issue.find(1)) + assert !events.include?(Issue.find(4)) + # subproject issue + assert !events.include?(Issue.find(5)) + end + + def test_activity_with_subprojects + events = find_events(User.anonymous, :project => @project, :with_subprojects => 1) + assert_not_nil events + + assert events.include?(Issue.find(1)) + # subproject issue + assert events.include?(Issue.find(5)) + end + + def test_global_activity_anonymous + events = find_events(User.anonymous) + assert_not_nil events + + assert events.include?(Issue.find(1)) + assert events.include?(Message.find(5)) + # Issue of a private project + assert !events.include?(Issue.find(4)) + # Private issue and comment + assert !events.include?(Issue.find(14)) + assert !events.include?(Journal.find(5)) + end + + def test_global_activity_logged_user + events = find_events(User.find(2)) # manager + assert_not_nil events + + assert events.include?(Issue.find(1)) + # Issue of a private project the user belongs to + assert events.include?(Issue.find(4)) + end + + def test_user_activity + user = User.find(2) + events = Redmine::Activity::Fetcher.new(User.anonymous, :author => user).events(nil, nil, :limit => 10) + + assert(events.size > 0) + assert(events.size <= 10) + assert_nil(events.detect {|e| e.event_author != user}) + end + + def test_files_activity + f = Redmine::Activity::Fetcher.new(User.anonymous, :project => Project.find(1)) + f.scope = ['files'] + events = f.events + + assert_kind_of Array, events + assert events.include?(Attachment.find_by_container_type_and_container_id('Project', 1)) + assert events.include?(Attachment.find_by_container_type_and_container_id('Version', 1)) + assert_equal [Attachment], events.collect(&:class).uniq + assert_equal %w(Project Version), events.collect(&:container_type).uniq.sort + end + + def test_event_group_for_issue + issue = Issue.find(1) + assert_equal issue, issue.event_group + end + + def test_event_group_for_journal + issue = Issue.find(1) + journal = issue.journals.first + assert_equal issue, journal.event_group + end + + def test_event_group_for_issue_time_entry + time = TimeEntry.where(:issue_id => 1).first + assert_equal time.issue, time.event_group + end + + def test_event_group_for_project_time_entry + time = TimeEntry.where(:issue_id => nil).first + assert_equal time, time.event_group + end + + def test_event_group_for_message + message = Message.find(1) + reply = message.children.first + assert_equal message, message.event_group + assert_equal message, reply.event_group + end + + def test_event_group_for_wiki_content_version + content = WikiContent::Version.find(1) + assert_equal content.page, content.event_group + end + + private + + def find_events(user, options={}) + Redmine::Activity::Fetcher.new(user, options).events(Date.today - 30, Date.today + 1) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ae/aeded9a36e99b80726b5792b98ad75a12b089d12.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ae/aeded9a36e99b80726b5792b98ad75a12b089d12.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,81 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +module RedminePmTest + class TestCase < ActiveSupport::TestCase + attr_reader :command, :response, :status, :username, :password + + # Cannot use transactional fixtures here: database + # will be accessed from Redmine.pm with its own connection + self.use_transactional_fixtures = false + + def test_dummy + end + + protected + + def assert_response(expected, msg=nil) + case expected + when :success + assert_equal 0, status, + (msg || "The command failed (exit: #{status}):\n #{command}\nOutput was:\n#{formatted_response}") + when :failure + assert_not_equal 0, status, + (msg || "The command succeed (exit: #{status}):\n #{command}\nOutput was:\n#{formatted_response}") + else + assert_equal expected, status, msg + end + end + + def assert_success(*args) + execute *args + assert_response :success + end + + def assert_failure(*args) + execute *args + assert_response :failure + end + + def with_credentials(username, password) + old_username, old_password = @username, @password + @username, @password = username, password + yield if block_given? + ensure + @username, @password = old_username, old_password + end + + def execute(*args) + @command = args.join(' ') + @status = nil + IO.popen("#{command} 2>&1") do |io| + @response = io.read + end + @status = $?.exitstatus + end + + def formatted_response + "#{'='*40}\n#{response}#{'='*40}" + end + + def random_filename + Redmine::Utils.random_hex(16) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ae/aee9019a693c0f591aa596c1d7a4423b6682e788.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ae/aee9019a693c0f591aa596c1d7a4423b6682e788.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1141 @@ +# Swedish translation for Ruby on Rails +# by Johan Lundström (johanlunds@gmail.com), +# with parts taken from http://github.com/daniel/swe_rails + +sv: + number: + # Used in number_with_delimiter() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "," + # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "." + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 2 + + # Used in number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + format: "%n %u" + unit: "kr" + # These three are to override number.format and are optional + # separator: "." + # delimiter: "," + # precision: 2 + + # Used in number_to_percentage() + percentage: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_precision() + precision: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_human_size() + human: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() + datetime: + distance_in_words: + half_a_minute: "en halv minut" + less_than_x_seconds: + one: "mindre än en sekund" + other: "mindre än %{count} sekunder" + x_seconds: + one: "en sekund" + other: "%{count} sekunder" + less_than_x_minutes: + one: "mindre än en minut" + other: "mindre än %{count} minuter" + x_minutes: + one: "en minut" + other: "%{count} minuter" + about_x_hours: + one: "ungefär en timme" + other: "ungefär %{count} timmar" + x_hours: + one: "1 timme" + other: "%{count} timmar" + x_days: + one: "en dag" + other: "%{count} dagar" + about_x_months: + one: "ungefär en månad" + other: "ungefär %{count} månader" + x_months: + one: "en månad" + other: "%{count} månader" + about_x_years: + one: "ungefär ett år" + other: "ungefär %{count} år" + over_x_years: + one: "mer än ett år" + other: "mer än %{count} år" + almost_x_years: + one: "nästan 1 år" + other: "nästan %{count} år" + + activerecord: + errors: + template: + header: + one: "Ett fel förhindrade denna %{model} från att sparas" + other: "%{count} fel förhindrade denna %{model} från att sparas" + # The variable :count is also available + body: "Det var problem med följande fält:" + # The values :model, :attribute and :value are always available for interpolation + # The value :count is available when applicable. Can be used for pluralization. + messages: + inclusion: "finns inte i listan" + exclusion: "är reserverat" + invalid: "är ogiltigt" + confirmation: "stämmer inte överens" + accepted : "måste vara accepterad" + empty: "får ej vara tom" + blank: "måste anges" + too_long: "är för lång (maximum är %{count} tecken)" + too_short: "är för kort (minimum är %{count} tecken)" + wrong_length: "har fel längd (ska vara %{count} tecken)" + taken: "har redan tagits" + not_a_number: "är inte ett nummer" + greater_than: "måste vara större än %{count}" + greater_than_or_equal_to: "måste vara större än eller lika med %{count}" + equal_to: "måste vara samma som" + less_than: "måste vara mindre än %{count}" + less_than_or_equal_to: "måste vara mindre än eller lika med %{count}" + odd: "måste vara udda" + even: "måste vara jämnt" + greater_than_start_date: "måste vara senare än startdatumet" + not_same_project: "tillhör inte samma projekt" + circular_dependency: "Denna relation skulle skapa ett cirkulärt beroende" + cant_link_an_issue_with_a_descendant: "Ett ärende kan inte länkas till ett av dess underärenden" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%e %b" + long: "%e %B, %Y" + + day_names: [söndag, måndag, tisdag, onsdag, torsdag, fredag, lördag] + abbr_day_names: [sön, mån, tis, ons, tor, fre, lör] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, januari, februari, mars, april, maj, juni, juli, augusti, september, oktober, november, december] + abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, aug, sep, okt, nov, dec] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%Y-%m-%d %H:%M" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%d %B, %Y %H:%M" + am: "" + pm: "" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "och" + skip_last_comma: true + + actionview_instancetag_blank_option: Var god välj + + general_text_No: 'Nej' + general_text_Yes: 'Ja' + general_text_no: 'nej' + general_text_yes: 'ja' + general_lang_name: 'Svenska' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Kontot har uppdaterats + notice_account_invalid_creditentials: Fel användarnamn eller lösenord + notice_account_password_updated: Lösenordet har uppdaterats + notice_account_wrong_password: Fel lösenord + notice_account_register_done: Kontot har skapats. För att aktivera kontot, klicka på länken i mailet som skickades till dig. + notice_account_unknown_email: Okänd användare. + notice_can_t_change_password: Detta konto använder en extern autentiseringskälla. Det går inte att byta lösenord. + notice_account_lost_email_sent: Ett mail med instruktioner om hur man väljer ett nytt lösenord har skickats till dig. + notice_account_activated: Ditt konto har blivit aktiverat. Du kan nu logga in. + notice_successful_create: Skapades korrekt. + notice_successful_update: Uppdatering lyckades. + notice_successful_delete: Borttagning lyckades. + notice_successful_connection: Uppkoppling lyckades. + notice_file_not_found: Sidan du försökte komma åt existerar inte eller är borttagen. + notice_locking_conflict: Data har uppdaterats av en annan användare. + notice_not_authorized: Du saknar behörighet att komma åt den här sidan. + notice_not_authorized_archived_project: Projektet du försöker komma åt har arkiverats. + notice_email_sent: "Ett mail skickades till %{value}" + notice_email_error: "Ett fel inträffade när mail skickades (%{value})" + notice_feeds_access_key_reseted: Din Atom-nyckel återställdes. + notice_api_access_key_reseted: Din API-nyckel återställdes. + notice_failed_to_save_issues: "Misslyckades med att spara %{count} ärende(n) på %{total} valda: %{ids}." + notice_failed_to_save_time_entries: "Misslyckades med att spara %{count} tidloggning(ar) på %{total} valda: %{ids}." + notice_failed_to_save_members: "Misslyckades med att spara medlem(mar): %{errors}." + notice_no_issue_selected: "Inget ärende är markerat! Var vänlig, markera de ärenden du vill ändra." + notice_account_pending: "Ditt konto skapades och avvaktar nu administratörens godkännande." + notice_default_data_loaded: Standardkonfiguration inläst. + notice_unable_delete_version: Denna version var inte möjlig att ta bort. + notice_unable_delete_time_entry: Tidloggning kunde inte tas bort. + notice_issue_done_ratios_updated: "% klart uppdaterade." + notice_gantt_chart_truncated: "Schemat förminskades eftersom det överskrider det maximala antalet aktiviteter som kan visas (%{max})" + notice_issue_successful_create: "Ärende %{id} skapades." + notice_issue_update_conflict: "Detta ärende har uppdaterats av en annan användare samtidigt som du redigerade det." + notice_account_deleted: "Ditt konto har avslutats permanent." + notice_user_successful_create: "Användare %{id} skapad." + + error_can_t_load_default_data: "Standardkonfiguration gick inte att läsa in: %{value}" + error_scm_not_found: "Inlägg och/eller revision finns inte i detta versionsarkiv." + error_scm_command_failed: "Ett fel inträffade vid försök att nå versionsarkivet: %{value}" + error_scm_annotate: "Inlägget existerar inte eller kan inte kommenteras." + error_scm_annotate_big_text_file: Inlägget kan inte annoteras eftersom det överskrider maximal storlek för textfiler. + error_issue_not_found_in_project: 'Ärendet hittades inte eller så tillhör det inte detta projekt' + error_no_tracker_in_project: 'Ingen ärendetyp är associerad med projektet. Vänligen kontrollera projektinställningarna.' + error_no_default_issue_status: 'Ingen status är definierad som standard för nya ärenden. Vänligen kontrollera din konfiguration (Gå till "Administration -> Ärendestatus").' + error_can_not_delete_custom_field: Kan inte ta bort användardefinerat fält + error_can_not_delete_tracker: "Det finns ärenden av denna typ och den är därför inte möjlig att ta bort." + error_can_not_remove_role: "Denna roll används och den är därför inte möjlig att ta bort." + error_can_not_reopen_issue_on_closed_version: 'Ett ärende tilldelat en stängd version kan inte öppnas på nytt' + error_can_not_archive_project: Detta projekt kan inte arkiveras + error_issue_done_ratios_not_updated: "% klart inte uppdaterade." + error_workflow_copy_source: 'Vänligen välj källans ärendetyp eller roll' + error_workflow_copy_target: 'Vänligen välj ärendetyp(er) och roll(er) för mål' + error_unable_delete_issue_status: 'Ärendestatus kunde inte tas bort' + error_unable_to_connect: "Kan inte ansluta (%{value})" + error_attachment_too_big: "Denna fil kan inte laddas upp eftersom den överstiger maximalt tillåten filstorlek (%{max_size})" + error_session_expired: "Din session har gått ut. Vänligen logga in på nytt." + warning_attachments_not_saved: "%{count} fil(er) kunde inte sparas." + + mail_subject_lost_password: "Ditt %{value} lösenord" + mail_body_lost_password: 'För att ändra ditt lösenord, klicka på följande länk:' + mail_subject_register: "Din %{value} kontoaktivering" + mail_body_register: 'För att aktivera ditt konto, klicka på följande länk:' + mail_body_account_information_external: "Du kan använda ditt %{value}-konto för att logga in." + mail_body_account_information: Din kontoinformation + mail_subject_account_activation_request: "%{value} begäran om kontoaktivering" + mail_body_account_activation_request: "En ny användare (%{value}) har registrerat sig och avvaktar ditt godkännande:" + mail_subject_reminder: "%{count} ärende(n) har deadline under de kommande %{days} dagarna" + mail_body_reminder: "%{count} ärende(n) som är tilldelat dig har deadline under de %{days} dagarna:" + mail_subject_wiki_content_added: "'%{id}' wikisida has lagts till" + mail_body_wiki_content_added: "The '%{id}' wikisida has lagts till av %{author}." + mail_subject_wiki_content_updated: "'%{id}' wikisida har uppdaterats" + mail_body_wiki_content_updated: "The '%{id}' wikisida har uppdaterats av %{author}." + + + field_name: Namn + field_description: Beskrivning + field_summary: Sammanfattning + field_is_required: Obligatorisk + field_firstname: Förnamn + field_lastname: Efternamn + field_mail: Mail + field_filename: Fil + field_filesize: Storlek + field_downloads: Nerladdningar + field_author: Författare + field_created_on: Skapad + field_updated_on: Uppdaterad + field_closed_on: Stängd + field_field_format: Format + field_is_for_all: För alla projekt + field_possible_values: Möjliga värden + field_regexp: Reguljärt uttryck + field_min_length: Minimilängd + field_max_length: Maxlängd + field_value: Värde + field_category: Kategori + field_title: Titel + field_project: Projekt + field_issue: Ärende + field_status: Status + field_notes: Anteckningar + field_is_closed: Ärendet är stängt + field_is_default: Standardvärde + field_tracker: Ärendetyp + field_subject: Ämne + field_due_date: Deadline + field_assigned_to: Tilldelad till + field_priority: Prioritet + field_fixed_version: Versionsmål + field_user: Användare + field_principal: Principal + field_role: Roll + field_homepage: Hemsida + field_is_public: Publik + field_parent: Underprojekt till + field_is_in_roadmap: Visa ärenden i roadmap + field_login: Användarnamn + field_mail_notification: Mailnotifieringar + field_admin: Administratör + field_last_login_on: Senaste inloggning + field_language: Språk + field_effective_date: Datum + field_password: Lösenord + field_new_password: Nytt lösenord + field_password_confirmation: Bekräfta lösenord + field_version: Version + field_type: Typ + field_host: Värddator + field_port: Port + field_account: Konto + field_base_dn: Bas-DN + field_attr_login: Inloggningsattribut + field_attr_firstname: Förnamnsattribut + field_attr_lastname: Efternamnsattribut + field_attr_mail: Mailattribut + field_onthefly: Skapa användare on-the-fly + field_start_date: Startdatum + field_done_ratio: "% Klart" + field_auth_source: Autentiseringsläge + field_hide_mail: Dölj min mailadress + field_comments: Kommentar + field_url: URL + field_start_page: Startsida + field_subproject: Underprojekt + field_hours: Timmar + field_activity: Aktivitet + field_spent_on: Datum + field_identifier: Identifierare + field_is_filter: Använd som filter + field_issue_to: Relaterade ärenden + field_delay: Fördröjning + field_assignable: Ärenden kan tilldelas denna roll + field_redirect_existing_links: Omdirigera existerande länkar + field_estimated_hours: Estimerad tid + field_column_names: Kolumner + field_time_entries: Spenderad tid + field_time_zone: Tidszon + field_searchable: Sökbar + field_default_value: Standardvärde + field_comments_sorting: Visa kommentarer + field_parent_title: Föräldersida + field_editable: Redigerbar + field_watcher: Bevakare + field_identity_url: OpenID URL + field_content: Innehåll + field_group_by: Gruppera resultat efter + field_sharing: Delning + field_parent_issue: Förälderaktivitet + field_member_of_group: "Tilldelad användares grupp" + field_assigned_to_role: "Tilldelad användares roll" + field_text: Textfält + field_visible: Synlig + field_warn_on_leaving_unsaved: Varna om jag lämnar en sida med osparad text + field_issues_visibility: Ärendesynlighet + field_is_private: Privat + field_commit_logs_encoding: Teckenuppsättning för commit-meddelanden + field_scm_path_encoding: Sökvägskodning + field_path_to_repository: Sökväg till versionsarkiv + field_root_directory: Rotmapp + field_cvsroot: CVSROOT + field_cvs_module: Modul + field_repository_is_default: Huvudarkiv + field_multiple: Flera värden + field_auth_source_ldap_filter: LDAP-filter + field_core_fields: Standardfält + field_timeout: "Timeout (i sekunder)" + field_board_parent: Förälderforum + field_private_notes: Privata anteckningar + field_inherit_members: Ärv medlemmar + field_generate_password: Generera lösenord + + setting_app_title: Applikationsrubrik + setting_app_subtitle: Applikationsunderrubrik + setting_welcome_text: Välkomsttext + setting_default_language: Standardspråk + setting_login_required: Kräver inloggning + setting_self_registration: Självregistrering + setting_attachment_max_size: Maxstorlek på bilaga + setting_issues_export_limit: Exportgräns för ärenden + setting_mail_from: Avsändaradress + setting_bcc_recipients: Hemlig kopia (bcc) till mottagare + setting_plain_text_mail: Oformaterad text i mail (ingen HTML) + setting_host_name: Värddatornamn + setting_text_formatting: Textformatering + setting_wiki_compression: Komprimering av wikihistorik + setting_feeds_limit: Innehållsgräns för Feed + setting_default_projects_public: Nya projekt är publika + setting_autofetch_changesets: Automatisk hämtning av commits + setting_sys_api_enabled: Aktivera WS för versionsarkivhantering + setting_commit_ref_keywords: Referens-nyckelord + setting_commit_fix_keywords: Fix-nyckelord + setting_autologin: Automatisk inloggning + setting_date_format: Datumformat + setting_time_format: Tidsformat + setting_cross_project_subtasks: Tillåt underaktiviteter mellan projekt + setting_cross_project_issue_relations: Tillåt ärenderelationer mellan projekt + setting_issue_list_default_columns: Standardkolumner i ärendelistan + setting_repositories_encodings: Encoding för bilagor och versionsarkiv + setting_emails_header: Mail-header + setting_emails_footer: Signatur + setting_protocol: Protokoll + setting_per_page_options: Alternativ, objekt per sida + setting_user_format: Visningsformat för användare + setting_activity_days_default: Dagar som visas på projektaktivitet + setting_display_subprojects_issues: Visa ärenden från underprojekt i huvudprojekt + setting_enabled_scm: Aktivera SCM + setting_mail_handler_body_delimiters: "Trunkera mail efter en av följande rader" + setting_mail_handler_api_enabled: Aktivera WS för inkommande mail + setting_mail_handler_api_key: API-nyckel + setting_sequential_project_identifiers: Generera projektidentifierare sekventiellt + setting_gravatar_enabled: Använd Gravatar-avatarer + setting_gravatar_default: Förvald Gravatar-bild + setting_diff_max_lines_displayed: Maximalt antal synliga rader i diff + setting_file_max_size_displayed: Maxstorlek på textfiler som visas inline + setting_repository_log_display_limit: Maximalt antal revisioner i filloggen + setting_openid: Tillåt inloggning och registrering med OpenID + setting_password_min_length: Minsta tillåtna lösenordslängd + setting_new_project_user_role_id: Tilldelad roll för en icke-administratör som skapar ett projekt + setting_default_projects_modules: Aktiverade moduler för nya projekt + setting_issue_done_ratio: Beräkna % klart med + setting_issue_done_ratio_issue_field: Använd ärendefältet + setting_issue_done_ratio_issue_status: Använd ärendestatus + setting_start_of_week: Första dagen i veckan + setting_rest_api_enabled: Aktivera REST webbtjänst + setting_cache_formatted_text: Cacha formaterad text + setting_default_notification_option: Standard notifieringsalternativ + setting_commit_logtime_enabled: Aktivera tidloggning + setting_commit_logtime_activity_id: Aktivitet för loggad tid + setting_gantt_items_limit: Maximalt antal aktiviteter som visas i gantt-schemat + setting_issue_group_assignment: Tillåt att ärenden tilldelas till grupper + setting_default_issue_start_date_to_creation_date: Använd dagens datum som startdatum för nya ärenden + setting_commit_cross_project_ref: Tillåt ärende i alla de andra projekten att bli refererade och fixade + setting_unsubscribe: Tillåt användare att avsluta prenumereration + setting_session_lifetime: Maximal sessionslivslängd + setting_session_timeout: Tidsgräns för sessionsinaktivitet + setting_thumbnails_enabled: Visa miniatyrbilder av bilagor + setting_thumbnails_size: Storlek på miniatyrbilder (i pixlar) + setting_non_working_week_days: Lediga dagar + setting_jsonp_enabled: Aktivera JSONP-stöd + setting_default_projects_tracker_ids: Standardärendetyper för nya projekt + + permission_add_project: Skapa projekt + permission_add_subprojects: Skapa underprojekt + permission_edit_project: Ändra projekt + permission_close_project: Stänga / återöppna projektet + permission_select_project_modules: Välja projektmoduler + permission_manage_members: Hantera medlemmar + permission_manage_project_activities: Hantera projektaktiviteter + permission_manage_versions: Hantera versioner + permission_manage_categories: Hantera ärendekategorier + permission_add_issues: Lägga till ärenden + permission_edit_issues: Ändra ärenden + permission_view_issues: Visa ärenden + permission_manage_issue_relations: Hantera ärenderelationer + permission_set_issues_private: Sätta ärenden publika eller privata + permission_set_own_issues_private: Sätta egna ärenden publika eller privata + permission_add_issue_notes: Lägga till ärendeanteckning + permission_edit_issue_notes: Ändra ärendeanteckningar + permission_edit_own_issue_notes: Ändra egna ärendeanteckningar + permission_view_private_notes: Visa privata anteckningar + permission_set_notes_private: Ställa in anteckningar som privata + permission_move_issues: Flytta ärenden + permission_delete_issues: Ta bort ärenden + permission_manage_public_queries: Hantera publika frågor + permission_save_queries: Spara frågor + permission_view_gantt: Visa Gantt-schema + permission_view_calendar: Visa kalender + permission_view_issue_watchers: Visa bevakarlista + permission_add_issue_watchers: Lägga till bevakare + permission_delete_issue_watchers: Ta bort bevakare + permission_log_time: Logga spenderad tid + permission_view_time_entries: Visa spenderad tid + permission_edit_time_entries: Ändra tidloggningar + permission_edit_own_time_entries: Ändra egna tidloggningar + permission_manage_news: Hantera nyheter + permission_comment_news: Kommentera nyheter + permission_view_documents: Visa dokument + permission_add_documents: Lägga till dokument + permission_edit_documents: Ändra dokument + permission_delete_documents: Ta bort dokument + permission_manage_files: Hantera filer + permission_view_files: Visa filer + permission_manage_wiki: Hantera wiki + permission_rename_wiki_pages: Byta namn på wikisidor + permission_delete_wiki_pages: Ta bort wikisidor + permission_view_wiki_pages: Visa wiki + permission_view_wiki_edits: Visa wikihistorik + permission_edit_wiki_pages: Ändra wikisidor + permission_delete_wiki_pages_attachments: Ta bort bilagor + permission_protect_wiki_pages: Skydda wikisidor + permission_manage_repository: Hantera versionsarkiv + permission_browse_repository: Bläddra i versionsarkiv + permission_view_changesets: Visa changesets + permission_commit_access: Commit-åtkomst + permission_manage_boards: Hantera forum + permission_view_messages: Visa meddelanden + permission_add_messages: Lägg till meddelanden + permission_edit_messages: Ändra meddelanden + permission_edit_own_messages: Ändra egna meddelanden + permission_delete_messages: Ta bort meddelanden + permission_delete_own_messages: Ta bort egna meddelanden + permission_export_wiki_pages: Exportera wikisidor + permission_manage_subtasks: Hantera underaktiviteter + permission_manage_related_issues: Hantera relaterade ärenden + + project_module_issue_tracking: Ärendeuppföljning + project_module_time_tracking: Tidsuppföljning + project_module_news: Nyheter + project_module_documents: Dokument + project_module_files: Filer + project_module_wiki: Wiki + project_module_repository: Versionsarkiv + project_module_boards: Forum + project_module_calendar: Kalender + project_module_gantt: Gantt + + label_user: Användare + label_user_plural: Användare + label_user_new: Ny användare + label_user_anonymous: Anonym + label_project: Projekt + label_project_new: Nytt projekt + label_project_plural: Projekt + label_x_projects: + zero: inga projekt + one: 1 projekt + other: "%{count} projekt" + label_project_all: Alla projekt + label_project_latest: Senaste projekt + label_issue: Ärende + label_issue_new: Nytt ärende + label_issue_plural: Ärenden + label_issue_view_all: Visa alla ärenden + label_issues_by: "Ärenden %{value}" + label_issue_added: Ärende tillagt + label_issue_updated: Ärende uppdaterat + label_issue_note_added: Anteckning tillagd + label_issue_status_updated: Status uppdaterad + label_issue_priority_updated: Prioritet uppdaterad + label_document: Dokument + label_document_new: Nytt dokument + label_document_plural: Dokument + label_document_added: Dokument tillagt + label_role: Roll + label_role_plural: Roller + label_role_new: Ny roll + label_role_and_permissions: Roller och behörigheter + label_role_anonymous: Anonym + label_role_non_member: Icke-medlem + label_member: Medlem + label_member_new: Ny medlem + label_member_plural: Medlemmar + label_tracker: Ärendetyp + label_tracker_plural: Ärendetyper + label_tracker_new: Ny ärendetyp + label_workflow: Arbetsflöde + label_issue_status: Ärendestatus + label_issue_status_plural: Ärendestatus + label_issue_status_new: Ny status + label_issue_category: Ärendekategori + label_issue_category_plural: Ärendekategorier + label_issue_category_new: Ny kategori + label_custom_field: Användardefinerat fält + label_custom_field_plural: Användardefinerade fält + label_custom_field_new: Nytt användardefinerat fält + label_enumerations: Uppräkningar + label_enumeration_new: Nytt värde + label_information: Information + label_information_plural: Information + label_please_login: Var god logga in + label_register: Registrera + label_login_with_open_id_option: eller logga in med OpenID + label_password_lost: Glömt lösenord + label_home: Hem + label_my_page: Min sida + label_my_account: Mitt konto + label_my_projects: Mina projekt + label_my_page_block: '"Min sida"-block' + label_administration: Administration + label_login: Logga in + label_logout: Logga ut + label_help: Hjälp + label_reported_issues: Rapporterade ärenden + label_assigned_to_me_issues: Ärenden tilldelade till mig + label_last_login: Senaste inloggning + label_registered_on: Registrerad + label_activity: Aktivitet + label_overall_activity: All aktivitet + label_user_activity: "Aktiviteter för %{value}" + label_new: Ny + label_logged_as: Inloggad som + label_environment: Miljö + label_authentication: Autentisering + label_auth_source: Autentiseringsläge + label_auth_source_new: Nytt autentiseringsläge + label_auth_source_plural: Autentiseringslägen + label_subproject_plural: Underprojekt + label_subproject_new: Nytt underprojekt + label_and_its_subprojects: "%{value} och dess underprojekt" + label_min_max_length: Min./Max.-längd + label_list: Lista + label_date: Datum + label_integer: Heltal + label_float: Flyttal + label_boolean: Boolean + label_string: Text + label_text: Lång text + label_attribute: Attribut + label_attribute_plural: Attribut + label_no_data: Ingen data att visa + label_change_status: Ändra status + label_history: Historia + label_attachment: Fil + label_attachment_new: Ny fil + label_attachment_delete: Ta bort fil + label_attachment_plural: Filer + label_file_added: Fil tillagd + label_report: Rapport + label_report_plural: Rapporter + label_news: Nyhet + label_news_new: Lägg till nyhet + label_news_plural: Nyheter + label_news_latest: Senaste nyheterna + label_news_view_all: Visa alla nyheter + label_news_added: Nyhet tillagd + label_news_comment_added: Kommentar tillagd till en nyhet + label_settings: Inställningar + label_overview: Översikt + label_version: Version + label_version_new: Ny version + label_version_plural: Versioner + label_close_versions: Stäng klara versioner + label_confirmation: Bekräftelse + label_export_to: 'Finns även som:' + label_read: Läs... + label_public_projects: Publika projekt + label_open_issues: öppen + label_open_issues_plural: öppna + label_closed_issues: stängd + label_closed_issues_plural: stängda + label_x_open_issues_abbr_on_total: + zero: 0 öppna av %{total} + one: 1 öppen av %{total} + other: "%{count} öppna av %{total}" + label_x_open_issues_abbr: + zero: 0 öppna + one: 1 öppen + other: "%{count} öppna" + label_x_closed_issues_abbr: + zero: 0 stängda + one: 1 stängd + other: "%{count} stängda" + label_x_issues: + zero: 0 ärenden + one: 1 ärende + other: "%{count} ärenden" + label_total: Total + label_total_time: Total tid + label_permissions: Behörigheter + label_current_status: Nuvarande status + label_new_statuses_allowed: Nya tillåtna statusvärden + label_all: alla + label_any: vad/vem som helst + label_none: inget/ingen + label_nobody: ingen + label_next: Nästa + label_previous: Föregående + label_used_by: Använd av + label_details: Detaljer + label_add_note: Lägg till anteckning + label_per_page: Per sida + label_calendar: Kalender + label_months_from: månader från + label_gantt: Gantt + label_internal: Intern + label_last_changes: "senaste %{count} ändringar" + label_change_view_all: Visa alla ändringar + label_personalize_page: Anpassa denna sida + label_comment: Kommentar + label_comment_plural: Kommentarer + label_x_comments: + zero: inga kommentarer + one: 1 kommentar + other: "%{count} kommentarer" + label_comment_add: Lägg till kommentar + label_comment_added: Kommentar tillagd + label_comment_delete: Ta bort kommentar + label_query: Användardefinerad fråga + label_query_plural: Användardefinerade frågor + label_query_new: Ny fråga + label_my_queries: Mina egna frågor + label_filter_add: Lägg till filter + label_filter_plural: Filter + label_equals: är + label_not_equals: är inte + label_in_less_than: om mindre än + label_in_more_than: om mer än + label_in_the_next_days: under kommande + label_in_the_past_days: under föregående + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_between: mellan + label_in: om + label_today: idag + label_all_time: närsom + label_yesterday: igår + label_this_week: denna vecka + label_last_week: senaste veckan + label_last_n_weeks: "senaste %{count} veckorna" + label_last_n_days: "senaste %{count} dagarna" + label_this_month: denna månad + label_last_month: senaste månaden + label_this_year: detta året + label_date_range: Datumintervall + label_less_than_ago: mindre än dagar sedan + label_more_than_ago: mer än dagar sedan + label_ago: dagar sedan + label_contains: innehåller + label_not_contains: innehåller inte + label_any_issues_in_project: några ärenden i projektet + label_any_issues_not_in_project: några ärenden utanför projektet + label_no_issues_in_project: inga ärenden i projektet + label_day_plural: dagar + label_repository: Versionsarkiv + label_repository_new: Nytt versionsarkiv + label_repository_plural: Versionsarkiv + label_browse: Bläddra + label_branch: Branch + label_tag: Tag + label_revision: Revision + label_revision_plural: Revisioner + label_revision_id: "Revision %{value}" + label_associated_revisions: Associerade revisioner + label_added: tillagd + label_modified: modifierad + label_copied: kopierad + label_renamed: omdöpt + label_deleted: borttagen + label_latest_revision: Senaste revisionen + label_latest_revision_plural: Senaste revisionerna + label_view_revisions: Visa revisioner + label_view_all_revisions: Visa alla revisioner + label_max_size: Maxstorlek + label_sort_highest: Flytta till toppen + label_sort_higher: Flytta upp + label_sort_lower: Flytta ner + label_sort_lowest: Flytta till botten + label_roadmap: Roadmap + label_roadmap_due_in: "Färdig om %{value}" + label_roadmap_overdue: "%{value} sen" + label_roadmap_no_issues: Inga ärenden för denna version + label_search: Sök + label_result_plural: Resultat + label_all_words: Alla ord + label_wiki: Wiki + label_wiki_edit: Wikiändring + label_wiki_edit_plural: Wikiändringar + label_wiki_page: Wikisida + label_wiki_page_plural: Wikisidor + label_index_by_title: Innehåll efter titel + label_index_by_date: Innehåll efter datum + label_current_version: Nuvarande version + label_preview: Förhandsgranska + label_feed_plural: Feeds + label_changes_details: Detaljer om alla ändringar + label_issue_tracking: Ärendeuppföljning + label_spent_time: Spenderad tid + label_overall_spent_time: Total tid spenderad + label_f_hour: "%{value} timme" + label_f_hour_plural: "%{value} timmar" + label_time_tracking: Tidsuppföljning + label_change_plural: Ändringar + label_statistics: Statistik + label_commits_per_month: Commits per månad + label_commits_per_author: Commits per författare + label_diff: diff + label_view_diff: Visa skillnader + label_diff_inline: i texten + label_diff_side_by_side: sida vid sida + label_options: Inställningar + label_copy_workflow_from: Kopiera arbetsflöde från + label_permissions_report: Behörighetsrapport + label_watched_issues: Bevakade ärenden + label_related_issues: Relaterade ärenden + label_applied_status: Tilldelad status + label_loading: Laddar... + label_relation_new: Ny relation + label_relation_delete: Ta bort relation + label_relates_to: Relaterar till + label_duplicates: Kopierar + label_duplicated_by: Kopierad av + label_blocks: Blockerar + label_blocked_by: Blockerad av + label_precedes: Kommer före + label_follows: Följer + label_copied_to: Kopierad till + label_copied_from: Kopierad från + label_end_to_start: slut till start + label_end_to_end: slut till slut + label_start_to_start: start till start + label_start_to_end: start till slut + label_stay_logged_in: Förbli inloggad + label_disabled: inaktiverad + label_show_completed_versions: Visa färdiga versioner + label_me: mig + label_board: Forum + label_board_new: Nytt forum + label_board_plural: Forum + label_board_locked: Låst + label_board_sticky: Sticky + label_topic_plural: Ämnen + label_message_plural: Meddelanden + label_message_last: Senaste meddelande + label_message_new: Nytt meddelande + label_message_posted: Meddelande tillagt + label_reply_plural: Svar + label_send_information: Skicka kontoinformation till användaren + label_year: År + label_month: Månad + label_week: Vecka + label_date_from: Från + label_date_to: Till + label_language_based: Språkbaserad + label_sort_by: "Sortera på %{value}" + label_send_test_email: Skicka testmail + label_feeds_access_key: Atom-nyckel + label_missing_feeds_access_key: Saknar en Atom-nyckel + label_feeds_access_key_created_on: "Atom-nyckel skapad för %{value} sedan" + label_module_plural: Moduler + label_added_time_by: "Tillagd av %{author} för %{age} sedan" + label_updated_time_by: "Uppdaterad av %{author} för %{age} sedan" + label_updated_time: "Uppdaterad för %{value} sedan" + label_jump_to_a_project: Gå till projekt... + label_file_plural: Filer + label_changeset_plural: Changesets + label_default_columns: Standardkolumner + label_no_change_option: (Ingen ändring) + label_bulk_edit_selected_issues: Gemensam ändring av markerade ärenden + label_bulk_edit_selected_time_entries: Gruppredigera valda tidloggningar + label_theme: Tema + label_default: Standard + label_search_titles_only: Sök endast i titlar + label_user_mail_option_all: "För alla händelser i mina projekt" + label_user_mail_option_selected: "För alla händelser i markerade projekt..." + label_user_mail_option_none: "Inga händelser" + label_user_mail_option_only_my_events: "Endast för saker jag bevakar eller är inblandad i" + label_user_mail_option_only_assigned: "Endast för saker jag är tilldelad" + label_user_mail_option_only_owner: "Endast för saker jag äger" + label_user_mail_no_self_notified: "Jag vill inte bli underrättad om ändringar som jag har gjort" + label_registration_activation_by_email: kontoaktivering med mail + label_registration_manual_activation: manuell kontoaktivering + label_registration_automatic_activation: automatisk kontoaktivering + label_display_per_page: "Per sida: %{value}" + label_age: Ålder + label_change_properties: Ändra inställningar + label_general: Allmänt + label_more: Mer + label_scm: SCM + label_plugins: Tillägg + label_ldap_authentication: LDAP-autentisering + label_downloads_abbr: Nerl. + label_optional_description: Valfri beskrivning + label_add_another_file: Lägg till ytterligare en fil + label_preferences: Användarinställningar + label_chronological_order: I kronologisk ordning + label_reverse_chronological_order: I omvänd kronologisk ordning + label_planning: Planering + label_incoming_emails: Inkommande mail + label_generate_key: Generera en nyckel + label_issue_watchers: Bevakare + label_example: Exempel + label_display: Visa + label_sort: Sortera + label_descending: Fallande + label_ascending: Stigande + label_date_from_to: Från %{start} till %{end} + label_wiki_content_added: Wikisida tillagd + label_wiki_content_updated: Wikisida uppdaterad + label_group: Grupp + label_group_plural: Grupper + label_group_new: Ny grupp + label_time_entry_plural: Spenderad tid + label_version_sharing_none: Inte delad + label_version_sharing_descendants: Med underprojekt + label_version_sharing_hierarchy: Med projekthierarki + label_version_sharing_tree: Med projektträd + label_version_sharing_system: Med alla projekt + label_update_issue_done_ratios: Uppdatera % klart + label_copy_source: Källa + label_copy_target: Mål + label_copy_same_as_target: Samma som mål + label_display_used_statuses_only: Visa endast status som används av denna ärendetyp + label_api_access_key: API-nyckel + label_missing_api_access_key: Saknar en API-nyckel + label_api_access_key_created_on: "API-nyckel skapad för %{value} sedan" + label_profile: Profil + label_subtask_plural: Underaktiviteter + label_project_copy_notifications: Skicka mailnotifieringar när projektet kopieras + label_principal_search: "Sök efter användare eller grupp:" + label_user_search: "Sök efter användare:" + label_additional_workflow_transitions_for_author: Ytterligare övergångar tillåtna när användaren är den som skapat ärendet + label_additional_workflow_transitions_for_assignee: Ytterligare övergångar tillåtna när användaren är den som tilldelats ärendet + label_issues_visibility_all: Alla ärenden + label_issues_visibility_public: Alla icke-privata ärenden + label_issues_visibility_own: Ärenden skapade av eller tilldelade till användaren + label_git_report_last_commit: Rapportera senaste commit av filer och mappar + label_parent_revision: Förälder + label_child_revision: Barn + label_export_options: "%{export_format} exportalternativ" + label_copy_attachments: Kopiera bilagor + label_copy_subtasks: Kopiera underaktiviteter + label_item_position: "%{position}/%{count}" + label_completed_versions: Klara versioner + label_search_for_watchers: Sök efter bevakare att lägga till + label_session_expiration: Sessionsutgång + label_show_closed_projects: Visa stängda projekt + label_status_transitions: Statusövergångar + label_fields_permissions: Fältbehörigheter + label_readonly: Skrivskyddad + label_required: Nödvändig + label_attribute_of_project: Projektets %{name} + label_attribute_of_issue: Ärendets %{name} + label_attribute_of_author: Författarens %{name} + label_attribute_of_assigned_to: Tilldelad användares %{name} + label_attribute_of_user: Användarens %{name} + label_attribute_of_fixed_version: Målversionens %{name} + label_cross_project_descendants: Med underprojekt + label_cross_project_tree: Med projektträd + label_cross_project_hierarchy: Med projekthierarki + label_cross_project_system: Med alla projekt + label_gantt_progress_line: Framstegslinje + + button_login: Logga in + button_submit: Skicka + button_save: Spara + button_check_all: Markera alla + button_uncheck_all: Avmarkera alla + button_collapse_all: Kollapsa alla + button_expand_all: Expandera alla + button_delete: Ta bort + button_create: Skapa + button_create_and_continue: Skapa och fortsätt + button_test: Testa + button_edit: Ändra + button_edit_associated_wikipage: "Ändra associerad Wikisida: %{page_title}" + button_add: Lägg till + button_change: Ändra + button_apply: Verkställ + button_clear: Återställ + button_lock: Lås + button_unlock: Lås upp + button_download: Ladda ner + button_list: Lista + button_view: Visa + button_move: Flytta + button_move_and_follow: Flytta och följ efter + button_back: Tillbaka + button_cancel: Avbryt + button_activate: Aktivera + button_sort: Sortera + button_log_time: Logga tid + button_rollback: Återställ till denna version + button_watch: Bevaka + button_unwatch: Stoppa bevakning + button_reply: Svara + button_archive: Arkivera + button_unarchive: Ta bort från arkiv + button_reset: Återställ + button_rename: Byt namn + button_change_password: Ändra lösenord + button_copy: Kopiera + button_copy_and_follow: Kopiera och följ efter + button_annotate: Kommentera + button_update: Uppdatera + button_configure: Konfigurera + button_quote: Citera + button_duplicate: Duplicera + button_show: Visa + button_hide: Göm + button_edit_section: Redigera denna sektion + button_export: Exportera + button_delete_my_account: Ta bort mitt konto + button_close: Stäng + button_reopen: Återöppna + + status_active: aktiv + status_registered: registrerad + status_locked: låst + + project_status_active: aktiv + project_status_closed: stängd + project_status_archived: arkiverad + + version_status_open: öppen + version_status_locked: låst + version_status_closed: stängd + + field_active: Aktiv + + text_select_mail_notifications: Välj för vilka händelser mail ska skickas. + text_regexp_info: eg. ^[A-Z0-9]+$ + text_min_max_length_info: 0 betyder ingen gräns + text_project_destroy_confirmation: Är du säker på att du vill ta bort detta projekt och all relaterad data? + text_subprojects_destroy_warning: "Alla underprojekt: %{value} kommer också tas bort." + text_workflow_edit: Välj en roll och en ärendetyp för att ändra arbetsflöde + text_are_you_sure: Är du säker ? + text_journal_changed: "%{label} ändrad från %{old} till %{new}" + text_journal_changed_no_detail: "%{label} uppdaterad" + text_journal_set_to: "%{label} satt till %{value}" + text_journal_deleted: "%{label} borttagen (%{old})" + text_journal_added: "%{label} %{value} tillagd" + text_tip_issue_begin_day: ärende som börjar denna dag + text_tip_issue_end_day: ärende som slutar denna dag + text_tip_issue_begin_end_day: ärende som börjar och slutar denna dag + text_project_identifier_info: 'Endast gemener (a-z), siffror, streck och understreck är tillåtna, måste börja med en bokstav.
    När identifieraren sparats kan den inte ändras.' + text_caracters_maximum: "max %{count} tecken." + text_caracters_minimum: "Måste vara minst %{count} tecken lång." + text_length_between: "Längd mellan %{min} och %{max} tecken." + text_tracker_no_workflow: Inget arbetsflöde definerat för denna ärendetyp + text_unallowed_characters: Otillåtna tecken + text_comma_separated: Flera värden tillåtna (kommaseparerade). + text_line_separated: Flera värden tillåtna (ett värde per rad). + text_issues_ref_in_commit_messages: Referera och fixa ärenden i commit-meddelanden + text_issue_added: "Ärende %{id} har rapporterats (av %{author})." + text_issue_updated: "Ärende %{id} har uppdaterats (av %{author})." + text_wiki_destroy_confirmation: Är du säker på att du vill ta bort denna wiki och allt dess innehåll ? + text_issue_category_destroy_question: "Några ärenden (%{count}) är tilldelade till denna kategori. Vad vill du göra ?" + text_issue_category_destroy_assignments: Ta bort kategoritilldelningar + text_issue_category_reassign_to: Återtilldela ärenden till denna kategori + text_user_mail_option: "För omarkerade projekt kommer du bara bli underrättad om saker du bevakar eller är inblandad i (T.ex. ärenden du skapat eller tilldelats)." + text_no_configuration_data: "Roller, ärendetyper, ärendestatus och arbetsflöden har inte konfigurerats ännu.\nDet rekommenderas att läsa in standardkonfigurationen. Du kommer att kunna göra ändringar efter att den blivit inläst." + text_load_default_configuration: Läs in standardkonfiguration + text_status_changed_by_changeset: "Tilldelad i changeset %{value}." + text_time_logged_by_changeset: "Tilldelad i changeset %{value}." + text_issues_destroy_confirmation: 'Är du säker på att du vill radera markerade ärende(n) ?' + text_issues_destroy_descendants_confirmation: Detta kommer även ta bort %{count} underaktivitet(er). + text_time_entries_destroy_confirmation: Är du säker på att du vill ta bort valda tidloggningar? + text_select_project_modules: 'Välj vilka moduler som ska vara aktiva för projektet:' + text_default_administrator_account_changed: Standardadministratörens konto ändrat + text_file_repository_writable: Arkivet för bifogade filer är skrivbart + text_plugin_assets_writable: Arkivet för plug-ins är skrivbart + text_rmagick_available: RMagick tillgängligt (ej obligatoriskt) + text_destroy_time_entries_question: "%{hours} timmar har rapporterats på ärendena du är på väg att ta bort. Vad vill du göra ?" + text_destroy_time_entries: Ta bort rapporterade timmar + text_assign_time_entries_to_project: Tilldela rapporterade timmar till projektet + text_reassign_time_entries: 'Återtilldela rapporterade timmar till detta ärende:' + text_user_wrote: "%{value} skrev:" + text_enumeration_destroy_question: "%{count} objekt är tilldelade till detta värde." + text_enumeration_category_reassign_to: 'Återtilldela till detta värde:' + text_email_delivery_not_configured: "Mailfunktionen har inte konfigurerats, och notifieringar via mail kan därför inte skickas.\nKonfigurera din SMTP-server i config/configuration.yml och starta om applikationen för att aktivera dem." + text_repository_usernames_mapping: "Välj eller uppdatera den Redmine-användare som är mappad till varje användarnamn i versionarkivloggen.\nAnvändare med samma användarnamn eller mailadress i både Redmine och versionsarkivet mappas automatiskt." + text_diff_truncated: '... Denna diff har förminskats eftersom den överskrider den maximala storlek som kan visas.' + text_custom_field_possible_values_info: 'Ett värde per rad' + text_wiki_page_destroy_question: "Denna sida har %{descendants} underliggande sidor. Vad vill du göra?" + text_wiki_page_nullify_children: "Behåll undersidor som rotsidor" + text_wiki_page_destroy_children: "Ta bort alla underliggande sidor" + text_wiki_page_reassign_children: "Flytta undersidor till denna föräldersida" + text_own_membership_delete_confirmation: "Några av, eller alla, dina behörigheter kommer att tas bort och du kanske inte längre kommer kunna göra ändringar i det här projektet.\nVill du verkligen fortsätta?" + text_zoom_out: Zooma ut + text_zoom_in: Zooma in + text_warn_on_leaving_unsaved: "Nuvarande sida innehåller osparad text som kommer försvinna om du lämnar sidan." + text_scm_path_encoding_note: "Standard: UTF-8" + text_git_repository_note: Versionsarkiv är tomt och lokalt (t.ex. /gitrepo, c:\gitrepo) + text_mercurial_repository_note: Lokalt versionsarkiv (t.ex. /hgrepo, c:\hgrepo) + text_scm_command: Kommando + text_scm_command_version: Version + text_scm_config: Du kan konfigurera dina scm-kommando i config/configuration.yml. Vänligen starta om applikationen när ändringar gjorts. + text_scm_command_not_available: Scm-kommando är inte tillgängligt. Vänligen kontrollera inställningarna i administratörspanelen. + text_issue_conflict_resolution_overwrite: "Använd mina ändringar i alla fall (tidigare anteckningar kommer behållas men några ändringar kan bli överskrivna)" + text_issue_conflict_resolution_add_notes: "Lägg till mina anteckningar och kasta mina andra ändringar" + text_issue_conflict_resolution_cancel: "Kasta alla mina ändringar och visa igen %{link}" + text_account_destroy_confirmation: "Är du säker på att du vill fortsätta?\nDitt konto kommer tas bort permanent, utan möjlighet att återaktivera det." + text_session_expiration_settings: "Varning: ändring av dessa inställningar kan få alla nuvarande sessioner, inklusive din egen, att gå ut." + text_project_closed: Detta projekt är stängt och skrivskyddat. + text_turning_multiple_off: "Om du inaktiverar möjligheten till flera värden kommer endast ett värde per objekt behållas." + + default_role_manager: Projektledare + default_role_developer: Utvecklare + default_role_reporter: Rapportör + default_tracker_bug: Bugg + default_tracker_feature: Funktionalitet + default_tracker_support: Support + default_issue_status_new: Ny + default_issue_status_in_progress: Pågår + default_issue_status_resolved: Löst + default_issue_status_feedback: Återkoppling + default_issue_status_closed: Stängd + default_issue_status_rejected: Avslagen + default_doc_category_user: Användardokumentation + default_doc_category_tech: Teknisk dokumentation + default_priority_low: Låg + default_priority_normal: Normal + default_priority_high: Hög + default_priority_urgent: Brådskande + default_priority_immediate: Omedelbar + default_activity_design: Design + default_activity_development: Utveckling + + enumeration_issue_priorities: Ärendeprioriteter + enumeration_doc_categories: Dokumentkategorier + enumeration_activities: Aktiviteter (tidsuppföljning) + enumeration_system_activity: Systemaktivitet + description_filter: Filter + description_search: Sökfält + description_choose_project: Projekt + description_project_scope: Sökomfång + description_notes: Anteckningar + description_message_content: Meddelandeinnehåll + description_query_sort_criteria_attribute: Sorteringsattribut + description_query_sort_criteria_direction: Sorteringsriktning + description_user_mail_notification: Mailnotifieringsinställningar + description_available_columns: Tillgängliga Kolumner + description_selected_columns: Valda Kolumner + description_all_columns: Alla kolumner + description_issue_category_reassign: Välj ärendekategori + description_wiki_subpages_reassign: Välj ny föräldersida + description_date_range_list: Välj intervall från listan + description_date_range_interval: Ange intervall genom att välja start- och slutdatum + description_date_from: Ange startdatum + description_date_to: Ange slutdatum + text_repository_identifier_info: 'Endast gemener (a-z), siffror, streck och understreck är tillåtna.
    När identifieraren sparats kan den inte ändras.' + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/af/af4ee39953b41daae78d74937664bfbeedf5ef0a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/af/af4ee39953b41daae78d74937664bfbeedf5ef0a.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,41 @@ + + + + + + + + + <% for new_status in @statuses %> + + <% end %> + + + + <% for old_status in @statuses %> + "> + + <% for new_status in @statuses -%> + <% checked = workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id} %> + + <% end -%> + + <% end %> + +
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + <%=l(:label_current_status)%> + <%=l(:label_new_statuses_allowed)%>
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.new-status-#{new_status.id}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + <%=h new_status.name %> +
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.old-status-#{old_status.id}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + + <%=h old_status.name %> + + <%= check_box_tag "issue_status[#{ old_status.id }][#{new_status.id}][]", name, checked, + :class => "old-status-#{old_status.id} new-status-#{new_status.id}" %> +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/af/af84f3f5537a5370b6f58104816b1886cf576cfc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/af/af84f3f5537a5370b6f58104816b1886cf576cfc.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,294 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../test_case', __FILE__) +require 'tmpdir' + +class RedminePmTest::RepositorySubversionTest < RedminePmTest::TestCase + fixtures :projects, :users, :members, :roles, :member_roles, :auth_sources + + SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn" + + def test_anonymous_read_on_public_repo_with_permission_should_succeed + assert_success "ls", svn_url + end + + def test_anonymous_read_on_public_repo_without_permission_should_fail + Role.anonymous.remove_permission! :browse_repository + assert_failure "ls", svn_url + end + + def test_anonymous_read_on_private_repo_should_fail + Project.find(1).update_attribute :is_public, false + assert_failure "ls", svn_url + end + + def test_anonymous_commit_on_public_repo_should_fail + Role.anonymous.add_permission! :commit_access + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + + def test_anonymous_commit_on_private_repo_should_fail + Role.anonymous.add_permission! :commit_access + Project.find(1).update_attribute :is_public, false + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + + def test_non_member_read_on_public_repo_with_permission_should_succeed + Role.anonymous.remove_permission! :browse_repository + with_credentials "miscuser8", "foo" do + assert_success "ls", svn_url + end + end + + def test_non_member_read_on_public_repo_without_permission_should_fail + Role.anonymous.remove_permission! :browse_repository + Role.non_member.remove_permission! :browse_repository + with_credentials "miscuser8", "foo" do + assert_failure "ls", svn_url + end + end + + def test_non_member_read_on_private_repo_should_fail + Project.find(1).update_attribute :is_public, false + with_credentials "miscuser8", "foo" do + assert_failure "ls", svn_url + end + end + + def test_non_member_commit_on_public_repo_should_fail + Role.non_member.add_permission! :commit_access + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + + def test_non_member_commit_on_private_repo_should_fail + Role.non_member.add_permission! :commit_access + Project.find(1).update_attribute :is_public, false + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + + def test_member_read_on_public_repo_with_permission_should_succeed + Role.anonymous.remove_permission! :browse_repository + Role.non_member.remove_permission! :browse_repository + with_credentials "dlopper", "foo" do + assert_success "ls", svn_url + end + end + + def test_member_read_on_public_repo_without_permission_should_fail + Role.anonymous.remove_permission! :browse_repository + Role.non_member.remove_permission! :browse_repository + Role.find(2).remove_permission! :browse_repository + with_credentials "dlopper", "foo" do + assert_failure "ls", svn_url + end + end + + def test_member_read_on_private_repo_with_permission_should_succeed + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "ls", svn_url + end + end + + def test_member_read_on_private_repo_without_permission_should_fail + Role.find(2).remove_permission! :browse_repository + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_failure "ls", svn_url + end + end + + def test_member_commit_on_public_repo_with_permission_should_succeed + Role.find(2).add_permission! :commit_access + with_credentials "dlopper", "foo" do + assert_success "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_member_commit_on_public_repo_without_permission_should_fail + Role.find(2).remove_permission! :commit_access + with_credentials "dlopper", "foo" do + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_member_commit_on_private_repo_with_permission_should_succeed + Role.find(2).add_permission! :commit_access + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_member_commit_on_private_repo_without_permission_should_fail + Role.find(2).remove_permission! :commit_access + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_invalid_credentials_should_fail + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "ls", svn_url + end + with_credentials "dlopper", "wrong" do + assert_failure "ls", svn_url + end + end + + def test_anonymous_read_should_fail_with_login_required + assert_success "ls", svn_url + with_settings :login_required => '1' do + assert_failure "ls", svn_url + end + end + + def test_authenticated_read_should_succeed_with_login_required + with_settings :login_required => '1' do + with_credentials "miscuser8", "foo" do + assert_success "ls", svn_url + end + end + end + + def test_read_on_archived_projects_should_fail + Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED + assert_failure "ls", svn_url + end + + def test_read_on_archived_private_projects_should_fail + Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_failure "ls", svn_url + end + end + + def test_read_on_closed_projects_should_succeed + Project.find(1).update_attribute :status, Project::STATUS_CLOSED + assert_success "ls", svn_url + end + + def test_read_on_closed_private_projects_should_succeed + Project.find(1).update_attribute :status, Project::STATUS_CLOSED + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "ls", svn_url + end + end + + def test_commit_on_closed_projects_should_fail + Project.find(1).update_attribute :status, Project::STATUS_CLOSED + Role.find(2).add_permission! :commit_access + with_credentials "dlopper", "foo" do + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_commit_on_closed_private_projects_should_fail + Project.find(1).update_attribute :status, Project::STATUS_CLOSED + Project.find(1).update_attribute :is_public, false + Role.find(2).add_permission! :commit_access + with_credentials "dlopper", "foo" do + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + if ldap_configured? + def test_user_with_ldap_auth_source_should_authenticate_with_ldap_credentials + ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1) + ldap_user.login = 'example1' + ldap_user.save! + + with_settings :login_required => '1' do + with_credentials "example1", "123456" do + assert_success "ls", svn_url + end + end + + with_settings :login_required => '1' do + with_credentials "example1", "wrong" do + assert_failure "ls", svn_url + end + end + end + end + + def test_checkout + Dir.mktmpdir do |dir| + assert_success "checkout", svn_url, dir + end + end + + def test_read_commands + assert_success "info", svn_url + assert_success "ls", svn_url + assert_success "log", svn_url + end + + def test_write_commands + Role.find(2).add_permission! :commit_access + filename = random_filename + + Dir.mktmpdir do |dir| + assert_success "checkout", svn_url, dir + Dir.chdir(dir) do + # creates a file in the working copy + f = File.new(File.join(dir, filename), "w") + f.write "test file content" + f.close + + assert_success "add", filename + with_credentials "dlopper", "foo" do + assert_success "commit --message Committing_a_file" + assert_success "copy --message Copying_a_file", svn_url(filename), svn_url("#{filename}_copy") + assert_success "delete --message Deleting_a_file", svn_url(filename) + assert_success "mkdir --message Creating_a_directory", svn_url("#{filename}_dir") + end + assert_success "update" + + # checks that the working copy was updated + assert File.exists?(File.join(dir, "#{filename}_copy")) + assert File.directory?(File.join(dir, "#{filename}_dir")) + end + end + end + + def test_read_invalid_repo_should_fail + assert_failure "ls", svn_url("invalid") + end + + protected + + def execute(*args) + a = [SVN_BIN, "--no-auth-cache --non-interactive"] + a << "--username #{username}" if username + a << "--password #{password}" if password + + super a, *args + end + + def svn_url(path=nil) + host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1' + url = "http://#{host}/svn/ecookbook" + url << "/#{path}" if path + url + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/af/af96c0777d172b6c3630823b529d0bf5cada5ec3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/af/af96c0777d172b6c3630823b529d0bf5cada5ec3.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,77 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class CustomFieldUserFormatTest < ActiveSupport::TestCase + fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues + + def setup + @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user') + end + + def test_possible_values_with_no_arguments + assert_equal [], @field.possible_values + assert_equal [], @field.possible_values(nil) + end + + def test_possible_values_with_project_resource + project = Project.find(1) + possible_values = @field.possible_values(project.issues.first) + assert possible_values.any? + assert_equal project.users.sort.collect(&:id).map(&:to_s), possible_values + end + + def test_possible_values_with_nil_project_resource + project = Project.find(1) + assert_equal [], @field.possible_values(Issue.new) + end + + def test_possible_values_options_with_no_arguments + assert_equal [], @field.possible_values_options + assert_equal [], @field.possible_values_options(nil) + end + + def test_possible_values_options_with_project_resource + project = Project.find(1) + possible_values_options = @field.possible_values_options(project.issues.first) + assert possible_values_options.any? + assert_equal project.users.sort.map {|u| [u.name, u.id.to_s]}, possible_values_options + end + + def test_possible_values_options_with_array + projects = Project.find([1, 2]) + possible_values_options = @field.possible_values_options(projects) + assert possible_values_options.any? + assert_equal (projects.first.users & projects.last.users).sort.map {|u| [u.name, u.id.to_s]}, possible_values_options + end + + def test_cast_blank_value + assert_equal nil, @field.cast_value(nil) + assert_equal nil, @field.cast_value("") + end + + def test_cast_valid_value + user = @field.cast_value("2") + assert_kind_of User, user + assert_equal User.find(2), user + end + + def test_cast_invalid_value + assert_equal nil, @field.cast_value("187") + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b0017ec26c9070575c2b102bc7db593493d5910c.svn-base --- a/.svn/pristine/b0/b0017ec26c9070575c2b102bc7db593493d5910c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class Board < ActiveRecord::Base - include Redmine::SafeAttributes - belongs_to :project - has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC" - has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC" - belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id - acts_as_tree :dependent => :nullify - acts_as_list :scope => '(project_id = #{project_id} AND parent_id #{parent_id ? "= #{parent_id}" : "IS NULL"})' - acts_as_watchable - - validates_presence_of :name, :description - validates_length_of :name, :maximum => 30 - validates_length_of :description, :maximum => 255 - validate :validate_board - - scope :visible, lambda {|*args| { :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } } - - safe_attributes 'name', 'description', 'parent_id', 'move_to' - - def visible?(user=User.current) - !user.nil? && user.allowed_to?(:view_messages, project) - end - - def reload(*args) - @valid_parents = nil - super - end - - def to_s - name - end - - def valid_parents - @valid_parents ||= project.boards - self_and_descendants - end - - def reset_counters! - self.class.reset_counters!(id) - end - - # Updates topics_count, messages_count and last_message_id attributes for +board_id+ - def self.reset_counters!(board_id) - board_id = board_id.to_i - update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," + - " messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id})," + - " last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})", - ["id = ?", board_id]) - end - - def self.board_tree(boards, parent_id=nil, level=0) - tree = [] - boards.select {|board| board.parent_id == parent_id}.sort_by(&:position).each do |board| - tree << [board, level] - tree += board_tree(boards, board.id, level+1) - end - if block_given? - tree.each do |board, level| - yield board, level - end - end - tree - end - - protected - - def validate_board - if parent_id && parent_id_changed? - errors.add(:parent_id, :invalid) unless valid_parents.include?(parent) - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b00a202757b193101a54d08689fd79809fe86c1b.svn-base --- a/.svn/pristine/b0/b00a202757b193101a54d08689fd79809fe86c1b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -<%= error_messages_for 'auth_source' %> - -
    - -

    -<%= text_field 'auth_source', 'name' %>

    - -

    -<%= check_box 'auth_source', 'onthefly_register' %>

    -
    - - - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b01b1d9455c0cc103023931e45cd6cdc1d9c80a4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b01b1d9455c0cc103023931e45cd6cdc1d9c80a4.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,210 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class UsersController < ApplicationController + layout 'admin' + + before_filter :require_admin, :except => :show + before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership] + accept_api_auth :index, :show, :create, :update, :destroy + + helper :sort + include SortHelper + helper :custom_fields + include CustomFieldsHelper + + def index + sort_init 'login', 'asc' + sort_update %w(login firstname lastname mail admin created_on last_login_on) + + case params[:format] + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = per_page_option + end + + @status = params[:status] || 1 + + scope = User.logged.status(@status) + scope = scope.like(params[:name]) if params[:name].present? + scope = scope.in_group(params[:group_id]) if params[:group_id].present? + + @user_count = scope.count + @user_pages = Paginator.new @user_count, @limit, params['page'] + @offset ||= @user_pages.offset + @users = scope.order(sort_clause).limit(@limit).offset(@offset).all + + respond_to do |format| + format.html { + @groups = Group.all.sort + render :layout => !request.xhr? + } + format.api + end + end + + def show + # show projects based on current user visibility + @memberships = @user.memberships.where(Project.visible_condition(User.current)).all + + events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10) + @events_by_day = events.group_by(&:event_date) + + unless User.current.admin? + if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?) + render_404 + return + end + end + + respond_to do |format| + format.html { render :layout => 'base' } + format.api + end + end + + def new + @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) + @user.safe_attributes = params[:user] + @auth_sources = AuthSource.all + end + + def create + @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) + @user.safe_attributes = params[:user] + @user.admin = params[:user][:admin] || false + @user.login = params[:user][:login] + @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id + + if @user.save + @user.pref.attributes = params[:pref] + @user.pref.save + + Mailer.account_information(@user, @user.password).deliver if params[:send_information] + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user))) + if params[:continue] + attrs = params[:user].slice(:generate_password) + redirect_to new_user_path(:user => attrs) + else + redirect_to edit_user_path(@user) + end + } + format.api { render :action => 'show', :status => :created, :location => user_url(@user) } + end + else + @auth_sources = AuthSource.all + # Clear password input + @user.password = @user.password_confirmation = nil + + respond_to do |format| + format.html { render :action => 'new' } + format.api { render_validation_errors(@user) } + end + end + end + + def edit + @auth_sources = AuthSource.all + @membership ||= Member.new + end + + def update + @user.admin = params[:user][:admin] if params[:user][:admin] + @user.login = params[:user][:login] if params[:user][:login] + if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?) + @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] + end + @user.safe_attributes = params[:user] + # Was the account actived ? (do it before User#save clears the change) + was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) + # TODO: Similar to My#account + @user.pref.attributes = params[:pref] + + if @user.save + @user.pref.save + + if was_activated + Mailer.account_activated(@user).deliver + elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? + Mailer.account_information(@user, @user.password).deliver + end + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_to_referer_or edit_user_path(@user) + } + format.api { render_api_ok } + end + else + @auth_sources = AuthSource.all + @membership ||= Member.new + # Clear password input + @user.password = @user.password_confirmation = nil + + respond_to do |format| + format.html { render :action => :edit } + format.api { render_validation_errors(@user) } + end + end + end + + def destroy + @user.destroy + respond_to do |format| + format.html { redirect_back_or_default(users_path) } + format.api { render_api_ok } + end + end + + def edit_membership + @membership = Member.edit_membership(params[:membership_id], params[:membership], @user) + @membership.save + respond_to do |format| + format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } + format.js + end + end + + def destroy_membership + @membership = Member.find(params[:membership_id]) + if @membership.deletable? + @membership.destroy + end + respond_to do |format| + format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } + format.js + end + end + + private + + def find_user + if params[:id] == 'current' + require_login || return + @user = User.current + else + @user = User.find(params[:id]) + end + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b02c60eec1517017e4be344103ca5e9fef268321.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b02c60eec1517017e4be344103ca5e9fef268321.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,129 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class RepositoryDarcsTest < ActiveSupport::TestCase + fixtures :projects + + include Redmine::I18n + + REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s + NUM_REV = 6 + + def setup + @project = Project.find(3) + @repository = Repository::Darcs.create( + :project => @project, + :url => REPOSITORY_PATH, + :log_encoding => 'UTF-8' + ) + assert @repository + end + + def test_blank_path_to_repository_error_message + set_language_if_valid 'en' + repo = Repository::Darcs.new( + :project => @project, + :identifier => 'test', + :log_encoding => 'UTF-8' + ) + assert !repo.save + assert_include "Path to repository can't be blank", + repo.errors.full_messages + end + + def test_blank_path_to_repository_error_message_fr + set_language_if_valid 'fr' + str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + repo = Repository::Darcs.new( + :project => @project, + :url => "", + :identifier => 'test', + :log_encoding => 'UTF-8' + ) + assert !repo.save + assert_include str, repo.errors.full_messages + end + + if File.directory?(REPOSITORY_PATH) + def test_fetch_changesets_from_scratch + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + + assert_equal NUM_REV, @repository.changesets.count + assert_equal 13, @repository.filechanges.count + assert_equal "Initial commit.", @repository.changesets.find_by_revision('1').comments + end + + def test_fetch_changesets_incremental + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + # Remove changesets with revision > 3 + @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 3} + @project.reload + assert_equal 3, @repository.changesets.count + + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + end + + def test_entries + entries = @repository.entries + assert_kind_of Redmine::Scm::Adapters::Entries, entries + end + + def test_entries_invalid_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + assert_nil @repository.entries('', '123') + end + + def test_deleted_files_should_not_be_listed + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + entries = @repository.entries('sources') + assert entries.detect {|e| e.name == 'watchers_controller.rb'} + assert_nil entries.detect {|e| e.name == 'welcome_controller.rb'} + end + + def test_cat + if @repository.scm.supports_cat? + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + cat = @repository.cat("sources/welcome_controller.rb", 2) + assert_not_nil cat + assert cat.include?('class WelcomeController < ApplicationController') + end + end + else + puts "Darcs test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b068921bb34f750331c44877f44180d7da10bc0f.svn-base --- a/.svn/pristine/b0/b068921bb34f750331c44877f44180d7da10bc0f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -<%= form_tag({:action => 'edit', :tab => 'authentication'}) do %> - -
    -

    <%= setting_check_box :login_required %>

    - -

    <%= setting_select :autologin, [[l(:label_disabled), 0]] + [1, 7, 30, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), days.to_s]} %>

    - -

    <%= setting_select :self_registration, [[l(:label_disabled), "0"], - [l(:label_registration_activation_by_email), "1"], - [l(:label_registration_manual_activation), "2"], - [l(:label_registration_automatic_activation), "3"]] %>

    - -

    <%= setting_check_box :unsubscribe %>

    - -

    <%= setting_text_field :password_min_length, :size => 6 %>

    - -

    <%= setting_check_box :lost_password, :label => :label_password_lost %>

    - -

    <%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %>

    - -

    <%= setting_check_box :rest_api_enabled %>

    -
    - -
    - <%= l(:label_session_expiration) %> - -
    -

    <%= setting_select :session_lifetime, [[l(:label_disabled), 0]] + [1, 7, 30, 60, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), (days * 60 * 24).to_s]} %>

    -

    <%= setting_select :session_timeout, [[l(:label_disabled), 0]] + [1, 2, 4, 8, 12, 24, 48].collect{|hours| [l('datetime.distance_in_words.x_hours', :count => hours), (hours * 60).to_s]} %>

    -
    - -

    <%= l(:text_session_expiration_settings) %>

    -
    - -<%= submit_tag l(:button_save) %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b06985145680863895954e4da4419cc45ad4d7ad.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b06985145680863895954e4da4419cc45ad4d7ad.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,32 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingReportsTest < ActionController::IntegrationTest + def test_reports + assert_routing( + { :method => 'get', :path => "/projects/567/issues/report" }, + { :controller => 'reports', :action => 'issue_report', :id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/issues/report/assigned_to" }, + { :controller => 'reports', :action => 'issue_report_details', + :id => '567', :detail => 'assigned_to' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b080561727eb14f1a6a2cf717727d6923d2488b6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b080561727eb14f1a6a2cf717727d6923d2488b6.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../../test_helper', __FILE__) +begin + require 'mocha/setup' + + class CvsAdapterTest < ActiveSupport::TestCase + REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s + REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? + MODULE_NAME = 'test' + + if File.directory?(REPOSITORY_PATH) + def setup + @adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH) + end + + def test_scm_version + to_test = { "\nConcurrent Versions System (CVS) 1.12.13 (client/server)\n" => [1,12,13], + "\r\n1.12.12\r\n1.12.11" => [1,12,12], + "1.12.11\r\n1.12.10\r\n" => [1,12,11]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end + end + + def test_revisions_all + cnt = 0 + @adapter.revisions('', nil, nil, :log_encoding => 'UTF-8') do |revision| + cnt += 1 + end + assert_equal 16, cnt + end + + def test_revisions_from_rev3 + rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) + cnt = 0 + @adapter.revisions('', rev3_committed_on, nil, :log_encoding => 'UTF-8') do |revision| + cnt += 1 + end + assert_equal 4, cnt + end + + def test_entries_rev3 + rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) + entries = @adapter.entries('sources', rev3_committed_on) + assert_equal 2, entries.size + assert_equal entries[0].name, "watchers_controller.rb" + assert_equal entries[0].lastrev.time, Time.gm(2007, 12, 13, 16, 27, 22) + end + + def test_path_encoding_default_utf8 + adpt1 = Redmine::Scm::Adapters::CvsAdapter.new( + MODULE_NAME, + REPOSITORY_PATH + ) + assert_equal "UTF-8", adpt1.path_encoding + adpt2 = Redmine::Scm::Adapters::CvsAdapter.new( + MODULE_NAME, + REPOSITORY_PATH, + nil, + nil, + "" + ) + assert_equal "UTF-8", adpt2.path_encoding + end + + def test_root_url_path + to_test = { + ':pserver:cvs_user:cvs_password@123.456.789.123:9876/repo' => '/repo', + ':pserver:cvs_user:cvs_password@123.456.789.123/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server:/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server:9876/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server/path/repo' => '/path/repo', + ':ext:cvsservername:/path' => '/path' + } + + to_test.each do |string, expected| + assert_equal expected, Redmine::Scm::Adapters::CvsAdapter.new('foo', string).send(:root_url_path), "#{string} failed" + end + end + + private + + def test_scm_version_for(scm_command_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) + assert_equal version, @adapter.class.scm_command_version + end + else + puts "Cvs test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end + end + +rescue LoadError + class CvsMochaFake < ActiveSupport::TestCase + def test_fake; assert(false, "Requires mocha to run those tests") end + end +end + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b08b8359ba61a19a3531e1766fc73fb2077273a2.svn-base --- a/.svn/pristine/b0/b08b8359ba61a19a3531e1766fc73fb2077273a2.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Acts - module Customizable - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - def acts_as_customizable(options = {}) - return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods) - cattr_accessor :customizable_options - self.customizable_options = options - has_many :custom_values, :as => :customized, - :include => :custom_field, - :order => "#{CustomField.table_name}.position", - :dependent => :delete_all, - :validate => false - send :include, Redmine::Acts::Customizable::InstanceMethods - validate :validate_custom_field_values - after_save :save_custom_field_values - end - end - - module InstanceMethods - def self.included(base) - base.extend ClassMethods - end - - def available_custom_fields - CustomField.find(:all, :conditions => "type = '#{self.class.name}CustomField'", - :order => 'position') - end - - # Sets the values of the object's custom fields - # values is an array like [{'id' => 1, 'value' => 'foo'}, {'id' => 2, 'value' => 'bar'}] - def custom_fields=(values) - values_to_hash = values.inject({}) do |hash, v| - v = v.stringify_keys - if v['id'] && v.has_key?('value') - hash[v['id']] = v['value'] - end - hash - end - self.custom_field_values = values_to_hash - end - - # Sets the values of the object's custom fields - # values is a hash like {'1' => 'foo', 2 => 'bar'} - def custom_field_values=(values) - values = values.stringify_keys - - custom_field_values.each do |custom_field_value| - key = custom_field_value.custom_field_id.to_s - if values.has_key?(key) - value = values[key] - if value.is_a?(Array) - value = value.reject(&:blank?).uniq - if value.empty? - value << '' - end - end - custom_field_value.value = value - end - end - @custom_field_values_changed = true - end - - def custom_field_values - @custom_field_values ||= available_custom_fields.collect do |field| - x = CustomFieldValue.new - x.custom_field = field - x.customized = self - if field.multiple? - values = custom_values.select { |v| v.custom_field == field } - if values.empty? - values << custom_values.build(:customized => self, :custom_field => field, :value => nil) - end - x.value = values.map(&:value) - else - cv = custom_values.detect { |v| v.custom_field == field } - cv ||= custom_values.build(:customized => self, :custom_field => field, :value => nil) - x.value = cv.value - end - x - end - end - - def visible_custom_field_values - custom_field_values.select(&:visible?) - end - - def custom_field_values_changed? - @custom_field_values_changed == true - end - - def custom_value_for(c) - field_id = (c.is_a?(CustomField) ? c.id : c.to_i) - custom_values.detect {|v| v.custom_field_id == field_id } - end - - def custom_field_value(c) - field_id = (c.is_a?(CustomField) ? c.id : c.to_i) - custom_field_values.detect {|v| v.custom_field_id == field_id }.try(:value) - end - - def validate_custom_field_values - if new_record? || custom_field_values_changed? - custom_field_values.each(&:validate_value) - end - end - - def save_custom_field_values - target_custom_values = [] - custom_field_values.each do |custom_field_value| - if custom_field_value.value.is_a?(Array) - custom_field_value.value.each do |v| - target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field && cv.value == v} - target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field, :value => v) - target_custom_values << target - end - else - target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field} - target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field) - target.value = custom_field_value.value - target_custom_values << target - end - end - self.custom_values = target_custom_values - custom_values.each(&:save) - @custom_field_values_changed = false - true - end - - def reset_custom_values! - @custom_field_values = nil - @custom_field_values_changed = true - end - - module ClassMethods - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b0bccc208d7807b84f7eb5ca0db0c75765ec033a.svn-base --- a/.svn/pristine/b0/b0bccc208d7807b84f7eb5ca0db0c75765ec033a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class CommentTest < ActiveSupport::TestCase - fixtures :users, :news, :comments, :projects, :enabled_modules - - def setup - @jsmith = User.find(2) - @news = News.find(1) - end - - def test_create - comment = Comment.new(:commented => @news, :author => @jsmith, :comments => "my comment") - assert comment.save - @news.reload - assert_equal 2, @news.comments_count - end - - def test_create_should_send_notification - Watcher.create!(:watchable => @news, :user => @jsmith) - - with_settings :notified_events => %w(news_comment_added) do - assert_difference 'ActionMailer::Base.deliveries.size' do - Comment.create!(:commented => @news, :author => @jsmith, :comments => "my comment") - end - end - end - - def test_validate - comment = Comment.new(:commented => @news) - assert !comment.save - assert_equal 2, comment.errors.count - end - - def test_destroy - comment = Comment.find(1) - assert comment.destroy - @news.reload - assert_equal 0, @news.comments_count - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b0dd7f2e6e380d76ce0d76b315917633345e67d4.svn-base --- a/.svn/pristine/b0/b0dd7f2e6e380d76ce0d76b315917633345e67d4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -
    -<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'new', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %> -
    - -<%= render_timelog_breadcrumb %> - -

    <%= l(:label_spent_time) %>

    - -<%= form_tag({:controller => 'timelog', :action => 'report', - :project_id => @project, :issue_id => @issue}, - :method => :get, :id => 'query_form') do %> - <% @report.criteria.each do |criterion| %> - <%= hidden_field_tag 'criteria[]', criterion, :id => nil %> - <% end %> - <%= render :partial => 'timelog/date_range' %> - -

    : <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'], - [l(:label_month), 'month'], - [l(:label_week), 'week'], - [l(:label_day_plural).titleize, 'day']], @report.columns), - :onchange => "this.form.submit();" %> - - : <%= select_tag('criteria[]', options_for_select([[]] + (@report.available_criteria.keys - @report.criteria).collect{|k| [l_or_humanize(@report.available_criteria[k][:label]), k]}), - :onchange => "this.form.submit();", - :style => 'width: 200px', - :id => nil, - :disabled => (@report.criteria.length >= 3), :id => "criterias") %> - <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @report.columns}, :class => 'icon icon-reload' %>

    -<% end %> - -<% unless @report.criteria.empty? %> -
    -

    <%= l(:label_total) %>: <%= html_hours(l_hours(@report.total_hours)) %>

    -
    - -<% unless @report.hours.empty? %> -
    - - - -<% @report.criteria.each do |criteria| %> - -<% end %> -<% columns_width = (40 / (@report.periods.length+1)).to_i %> -<% @report.periods.each do |period| %> - -<% end %> - - - - -<%= render :partial => 'report_criteria', :locals => {:criterias => @report.criteria, :hours => @report.hours, :level => 0} %> - - - <%= ('' * (@report.criteria.size - 1)).html_safe %> - <% total = 0 -%> - <% @report.periods.each do |period| -%> - <% sum = sum_hours(select_hours(@report.hours, @report.columns, period.to_s)); total += sum -%> - - <% end -%> - - - -
    <%= l_or_humanize(@report.available_criteria[criteria][:label]) %><%= period %><%= l(:label_total) %>
    <%= l(:label_total) %><%= html_hours("%.2f" % sum) if sum > 0 %><%= html_hours("%.2f" % total) if total > 0 %>
    -
    - -<% other_formats_links do |f| %> - <%= f.link_to 'CSV', :url => params %> -<% end %> -<% end %> -<% end %> - -<% html_title l(:label_spent_time), l(:label_report) %> - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b0/b0e82d7d19b1e5c31f1fcecc02dfe717ce20175c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b0e82d7d19b1e5c31f1fcecc02dfe717ce20175c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,119 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingVersionsTest < ActionController::IntegrationTest + def test_roadmap + # /projects/foo/versions is /projects/foo/roadmap + assert_routing( + { :method => 'get', :path => "/projects/33/roadmap" }, + { :controller => 'versions', :action => 'index', :project_id => '33' } + ) + end + + def test_versions_scoped_under_project + assert_routing( + { :method => 'put', :path => "/projects/foo/versions/close_completed" }, + { :controller => 'versions', :action => 'close_completed', + :project_id => 'foo' } + ) + assert_routing( + { :method => 'get', :path => "/projects/foo/versions.xml" }, + { :controller => 'versions', :action => 'index', + :project_id => 'foo', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/projects/foo/versions.json" }, + { :controller => 'versions', :action => 'index', + :project_id => 'foo', :format => 'json' } + ) + assert_routing( + { :method => 'get', :path => "/projects/foo/versions/new" }, + { :controller => 'versions', :action => 'new', + :project_id => 'foo' } + ) + assert_routing( + { :method => 'post', :path => "/projects/foo/versions" }, + { :controller => 'versions', :action => 'create', + :project_id => 'foo' } + ) + assert_routing( + { :method => 'post', :path => "/projects/foo/versions.xml" }, + { :controller => 'versions', :action => 'create', + :project_id => 'foo', :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/projects/foo/versions.json" }, + { :controller => 'versions', :action => 'create', + :project_id => 'foo', :format => 'json' } + ) + end + + def test_versions + assert_routing( + { :method => 'get', :path => "/versions/1" }, + { :controller => 'versions', :action => 'show', :id => '1' } + ) + assert_routing( + { :method => 'get', :path => "/versions/1.xml" }, + { :controller => 'versions', :action => 'show', :id => '1', + :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/versions/1.json" }, + { :controller => 'versions', :action => 'show', :id => '1', + :format => 'json' } + ) + assert_routing( + { :method => 'get', :path => "/versions/1/edit" }, + { :controller => 'versions', :action => 'edit', :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/versions/1" }, + { :controller => 'versions', :action => 'update', :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/versions/1.xml" }, + { :controller => 'versions', :action => 'update', :id => '1', + :format => 'xml' } + ) + assert_routing( + { :method => 'put', :path => "/versions/1.json" }, + { :controller => 'versions', :action => 'update', :id => '1', + :format => 'json' } + ) + assert_routing( + { :method => 'delete', :path => "/versions/1" }, + { :controller => 'versions', :action => 'destroy', :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/versions/1.xml" }, + { :controller => 'versions', :action => 'destroy', :id => '1', + :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/versions/1.json" }, + { :controller => 'versions', :action => 'destroy', :id => '1', + :format => 'json' } + ) + assert_routing( + { :method => 'post', :path => "/versions/1/status_by" }, + { :controller => 'versions', :action => 'status_by', :id => '1' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b1/b10cf0a6565381d9c11e7e85aa348b209e42548a.svn-base --- a/.svn/pristine/b1/b10cf0a6565381d9c11e7e85aa348b209e42548a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ ---- -users_004: - created_on: 2006-07-19 19:34:07 +02:00 - status: 1 - last_login_on: - language: en - # password = foo - salt: 3126f764c3c5ac61cbfc103f25f934cf - hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b - updated_on: 2006-07-19 19:34:07 +02:00 - admin: false - mail: rhill@somenet.foo - lastname: Hill - firstname: Robert - id: 4 - auth_source_id: - mail_notification: all - login: rhill - type: User -users_001: - created_on: 2006-07-19 19:12:21 +02:00 - status: 1 - last_login_on: 2006-07-19 22:57:52 +02:00 - language: en - # password = admin - salt: 82090c953c4a0000a7db253b0691a6b4 - hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150 - updated_on: 2006-07-19 22:57:52 +02:00 - admin: true - mail: admin@somenet.foo - lastname: Admin - firstname: redMine - id: 1 - auth_source_id: - mail_notification: all - login: admin - type: User -users_002: - created_on: 2006-07-19 19:32:09 +02:00 - status: 1 - last_login_on: 2006-07-19 22:42:15 +02:00 - language: en - # password = jsmith - salt: 67eb4732624d5a7753dcea7ce0bb7d7d - hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc - updated_on: 2006-07-19 22:42:15 +02:00 - admin: false - mail: jsmith@somenet.foo - lastname: Smith - firstname: John - id: 2 - auth_source_id: - mail_notification: all - login: jsmith - type: User -users_003: - created_on: 2006-07-19 19:33:19 +02:00 - status: 1 - last_login_on: - language: en - # password = foo - salt: 7599f9963ec07b5a3b55b354407120c0 - hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: dlopper@somenet.foo - lastname: Lopper - firstname: Dave - id: 3 - auth_source_id: - mail_notification: all - login: dlopper - type: User -users_005: - id: 5 - created_on: 2006-07-19 19:33:19 +02:00 - # Locked - status: 3 - last_login_on: - language: en - hashed_password: 1 - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: dlopper2@somenet.foo - lastname: Lopper2 - firstname: Dave2 - auth_source_id: - mail_notification: all - login: dlopper2 - type: User -users_006: - id: 6 - created_on: 2006-07-19 19:33:19 +02:00 - status: 0 - last_login_on: - language: '' - hashed_password: 1 - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: '' - lastname: Anonymous - firstname: '' - auth_source_id: - mail_notification: only_my_events - login: '' - type: AnonymousUser -users_007: - id: 7 - created_on: 2006-07-19 19:33:19 +02:00 - status: 1 - last_login_on: - language: '' - hashed_password: 1 - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: someone@foo.bar - lastname: One - firstname: Some - auth_source_id: - mail_notification: only_my_events - login: someone - type: User -users_008: - id: 8 - created_on: 2006-07-19 19:33:19 +02:00 - status: 1 - last_login_on: - language: 'it' - # password = foo - salt: 7599f9963ec07b5a3b55b354407120c0 - hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: miscuser8@foo.bar - lastname: Misc - firstname: User - auth_source_id: - mail_notification: only_my_events - login: miscuser8 - type: User -users_009: - id: 9 - created_on: 2006-07-19 19:33:19 +02:00 - status: 1 - last_login_on: - language: 'it' - hashed_password: 1 - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: miscuser9@foo.bar - lastname: Misc - firstname: User - auth_source_id: - mail_notification: only_my_events - login: miscuser9 - type: User -groups_010: - id: 10 - lastname: A Team - type: Group -groups_011: - id: 11 - lastname: B Team - type: Group - - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b1/b10fb2a7d79af038e911673a9e805ba72e9f4421.svn-base --- a/.svn/pristine/b1/b10fb2a7d79af038e911673a9e805ba72e9f4421.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -<% roles = Role.find_all_givable %> -<% projects = Project.active.find(:all, :order => 'lft') %> - -
    -<% if @user.memberships.any? %> - - - - - - <%= call_hook(:view_users_memberships_table_header, :user => @user )%> - - - <% @user.memberships.each do |membership| %> - <% next if membership.new_record? %> - - - - - <%= call_hook(:view_users_memberships_table_row, :user => @user, :membership => membership, :roles => roles, :projects => projects )%> - - <% end; reset_cycle %> - -
    <%= l(:label_project) %><%= l(:label_role_plural) %>
    - <%= link_to_project membership.project %> - - <%=h membership.roles.sort.collect(&:to_s).join(', ') %> - <%= form_for(:membership, :remote => true, - :url => user_membership_path(@user, membership), :method => :put, - :html => {:id => "member-#{membership.id}-roles-form", - :style => 'display:none;'}) do %> -

    <% roles.each do |role| %> -
    - <% end %>

    - <%= hidden_field_tag 'membership[role_ids][]', '' %> -

    <%= submit_tag l(:button_change) %> - <%= link_to_function l(:button_cancel), - "$('#member-#{membership.id}-roles').show(); $('#member-#{membership.id}-roles-form').hide(); return false;" - %>

    - <% end %> -
    - <%= link_to_function l(:button_edit), - "$('#member-#{membership.id}-roles').hide(); $('#member-#{membership.id}-roles-form').show(); return false;", - :class => 'icon icon-edit' - %> - <%= delete_link user_membership_path(@user, membership), :remote => true if membership.deletable? %> -
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> -
    - -
    -<% if projects.any? %> -
    <%=l(:label_project_new)%> -<%= form_for(:membership, :remote => true, :url => user_memberships_path(@user)) do %> -<%= select_tag 'membership[project_id]', options_for_membership_project_select(@user, projects) %> -

    <%= l(:label_role_plural) %>: -<% roles.each do |role| %> - -<% end %>

    -

    <%= submit_tag l(:button_add) %>

    -<% end %> -
    -<% end %> -
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b1/b18eaa15a5db0d11bdab1de8144265ea1093fcca.svn-base --- a/.svn/pristine/b1/b18eaa15a5db0d11bdab1de8144265ea1093fcca.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../../../test_helper', __FILE__) - -class Redmine::Views::Builders::JsonTest < ActiveSupport::TestCase - - def test_hash - assert_json_output({'person' => {'name' => 'Ryan', 'age' => 32}}) do |b| - b.person do - b.name 'Ryan' - b.age 32 - end - end - end - - def test_hash_hash - assert_json_output({'person' => {'name' => 'Ryan', 'birth' => {'city' => 'London', 'country' => 'UK'}}}) do |b| - b.person do - b.name 'Ryan' - b.birth :city => 'London', :country => 'UK' - end - end - - assert_json_output({'person' => {'id' => 1, 'name' => 'Ryan', 'birth' => {'city' => 'London', 'country' => 'UK'}}}) do |b| - b.person :id => 1 do - b.name 'Ryan' - b.birth :city => 'London', :country => 'UK' - end - end - end - - def test_array - assert_json_output({'books' => [{'title' => 'Book 1', 'author' => 'B. Smith'}, {'title' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b| - b.array :books do |b| - b.book :title => 'Book 1', :author => 'B. Smith' - b.book :title => 'Book 2', :author => 'G. Cooper' - end - end - - assert_json_output({'books' => [{'title' => 'Book 1', 'author' => 'B. Smith'}, {'title' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b| - b.array :books do |b| - b.book :title => 'Book 1' do - b.author 'B. Smith' - end - b.book :title => 'Book 2' do - b.author 'G. Cooper' - end - end - end - end - - def test_array_with_content_tags - assert_json_output({'books' => [{'value' => 'Book 1', 'author' => 'B. Smith'}, {'value' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b| - b.array :books do |b| - b.book 'Book 1', :author => 'B. Smith' - b.book 'Book 2', :author => 'G. Cooper' - end - end - end - - def test_nested_arrays - assert_json_output({'books' => [{'authors' => ['B. Smith', 'G. Cooper']}]}) do |b| - b.array :books do |books| - books.book do |book| - book.array :authors do |authors| - authors.author 'B. Smith' - authors.author 'G. Cooper' - end - end - end - end - end - - def assert_json_output(expected, &block) - builder = Redmine::Views::Builders::Json.new(ActionDispatch::TestRequest.new, ActionDispatch::TestResponse.new) - block.call(builder) - assert_equal(expected, ActiveSupport::JSON.decode(builder.output)) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b1/b1e2d95840db59650e1901526ee33ceb0d395bd8.svn-base --- a/.svn/pristine/b1/b1e2d95840db59650e1901526ee33ceb0d395bd8.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1084 +0,0 @@ -mn: - direction: ltr - jquery: - locale: "en" - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y/%m/%d" - short: "%b %d" - long: "%Y, %B %d" - - day_names: [Даваа, МÑгмар, Лхагва, ПүрÑв, БааÑан, БÑмба, ÐÑм] - abbr_day_names: [Дав, МÑг, Лха, Пүр, БÑн, БÑм, ÐÑм] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, 1-Ñ€ Ñар, 2-Ñ€ Ñар, 3-Ñ€ Ñар, 4-Ñ€ Ñар, 5-Ñ€ Ñар, 6-Ñ€ Ñар, 7-Ñ€ Ñар, 8-Ñ€ Ñар, 9-Ñ€ Ñар, 10-Ñ€ Ñар, 11-Ñ€ Ñар, 12-Ñ€ Ñар] - abbr_month_names: [~, 1Ñар, 2Ñар, 3Ñар, 4Ñар, 5Ñар, 6Ñар, 7Ñар, 8Ñар, 9Ñар, 10Ñар, 11Ñар, 12Ñар] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%Y/%m/%d %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%Y, %B %d %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "Ñ…Ð°Ð³Ð°Ñ Ð¼Ð¸Ð½ÑƒÑ‚" - less_than_x_seconds: - one: "Ñекунд орчим" - other: "%{count} ÑекундÑÑÑ Ð±Ð°Ð³Ð° хугацаа" - x_seconds: - one: "1 Ñекунд" - other: "%{count} Ñекунд" - less_than_x_minutes: - one: "Ð¼Ð¸Ð½ÑƒÑ‚Ð°Ð°Ñ Ð±Ð°Ð³Ð° хугацаа" - other: "%{count} Ð¼Ð¸Ð½ÑƒÑ‚Ð°Ð°Ñ Ð±Ð°Ð³Ð° хугацаа" - x_minutes: - one: "1 минут" - other: "%{count} минут" - about_x_hours: - one: "1 цаг орчим" - other: "ойролцоогоор %{count} цаг" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 өдөр" - other: "%{count} өдөр" - about_x_months: - one: "1 Ñар орчим" - other: "ойролцоогоор %{count} Ñар" - x_months: - one: "1 Ñар" - other: "%{count} Ñар" - about_x_years: - one: "ойролцоогоор 1 жил" - other: "ойролцоогоор %{count} жил" - over_x_years: - one: "1 жилÑÑÑ Ð¸Ñ…" - other: "%{count} жилÑÑÑ Ð¸Ñ…" - almost_x_years: - one: "бараг 1 жил" - other: "бараг %{count} жил" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Байт" - other: "Байт" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "баÑ" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "жагÑаалтад заагдаагүй байна" - exclusion: "нөөцлөгдÑөн" - invalid: "буруу" - confirmation: "баталгаажÑан өгөгдөлтÑй таарахгүй байна" - accepted: "хүлÑÑж авах Ñ‘Ñтой" - empty: "хооÑон байж болохгүй" - blank: "бланк байж болохгүй" - too_long: "дÑндүү урт байна (хамгийн ихдÑÑ %{count} Ñ‚ÑмдÑгт)" - too_short: "дÑндүү богино байна (хамгийн багадаа %{count} Ñ‚ÑмдÑгт)" - wrong_length: "буруу урттай байна (заавал %{count} Ñ‚ÑмдÑгт)" - taken: "аль Ñ…ÑÐ´Ð¸Ð¹Ð½Ñ Ð°Ð²Ñан байна" - not_a_number: "тоо биш байна" - not_a_date: "зөв огноо биш байна" - greater_than: "%{count} их байх Ñ‘Ñтой" - greater_than_or_equal_to: "must be greater than or equal to %{count}" - equal_to: "must be equal to %{count}" - less_than: "must be less than %{count}" - less_than_or_equal_to: "must be less than or equal to %{count}" - odd: "заавал Ñондгой" - even: "заавал Ñ‚Ñгш" - greater_than_start_date: "must be greater than start date" - not_same_project: "нÑг ижил төÑөлд хамаарахгүй байна" - circular_dependency: "Ð­Ð½Ñ Ñ…Ð°Ñ€ÑŒÑ†Ð°Ð° нь гинжин(рекурÑив) харьцаа Ò¯Ò¯ÑгÑÑ… юм байна" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Сонгоно уу - - general_text_No: 'Үгүй' - general_text_Yes: 'Тийм' - general_text_no: 'үгүй' - general_text_yes: 'тийм' - general_lang_name: 'Mongolian (Монгол)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '7' - - notice_account_updated: ДанÑыг амжилттай өөрчиллөө. - notice_account_invalid_creditentials: Ð¥ÑÑ€ÑглÑгчийн нÑÑ€ ÑÑвÑл нууц үг буруу байна - notice_account_password_updated: Ðууц үгийг амжилттай өөрчиллөө. - notice_account_wrong_password: Буруу нууц үг - notice_account_register_done: Ð¨Ð¸Ð½Ñ Ñ…ÑÑ€ÑглÑгч амжилттай Ò¯Ò¯ÑгÑлÑÑ. ИдÑвхжүүлÑхийн тулд, бидний тань луу илгÑÑÑÑн мÑйл дотор байгаа Ñ…Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð´ÑÑÑ€ дараарай. - notice_account_unknown_email: Үл мÑдÑгдÑÑ… Ñ…ÑÑ€ÑглÑгч. - notice_can_t_change_password: Ð­Ð½Ñ Ñрх гадаад нÑвтрÑлтÑд ашигладаг ÑƒÑ‡Ñ€Ð°Ð°Ñ Ð½ÑƒÑƒÑ† үгийг өөрчлөх боломжгүй. - notice_account_lost_email_sent: Бид таньд мÑйлÑÑÑ€ нууц үгÑÑ Ó©Ó©Ñ€Ñ‡Ð»Ó©Ñ… зааврыг илгÑÑÑÑн байгаа. - notice_account_activated: Таны Ð´Ð°Ð½Ñ Ð¸Ð´ÑвхжлÑÑ. Одоо нÑвтÑрч орж болно. - notice_successful_create: Ðмжилттай Ò¯Ò¯ÑгÑлÑÑ. - notice_successful_update: Ðмжилттай өөрчиллөө. - notice_successful_delete: Ðмжилттай уÑтгалаа. - notice_successful_connection: Ðмжилттай холбогдлоо. - notice_file_not_found: Таны үзÑÑ… гÑÑÑн Ñ…ÑƒÑƒÐ´Ð°Ñ Ð±Ð°Ð¹Ñ…Ð³Ò¯Ð¹ юмуу уÑтгагдÑан байна. - notice_locking_conflict: Өгөгдлийг Ó©Ó©Ñ€ хүн өөрчилÑөн байна. - notice_not_authorized: Танд ÑÐ½Ñ Ñ…ÑƒÑƒÐ´Ñыг үзÑÑ… Ñрх байхгүй байна. - notice_email_sent: "%{value} - руу мÑйл илгÑÑлÑÑ" - notice_email_error: "МÑйл илгÑÑÑ…Ñд алдаа гарлаа (%{value})" - notice_feeds_access_key_reseted: Таны RSS хандалтын түлхүүрийг дахин ÑхлүүллÑÑ. - notice_api_access_key_reseted: Your API access key was reset. - notice_failed_to_save_issues: "%{total} аÑуудал ÑонгогдÑÐ¾Ð½Ð¾Ð¾Ñ %{count} аÑуудлыг нь хадгалахад алдаа гарлаа: %{ids}." - notice_no_issue_selected: "Ямар ч аÑуудал Ñонгогдоогүй байна! ЗаÑварлах аÑуудлуудаа Ñонгоно уу." - notice_account_pending: "Таны данÑыг Ò¯Ò¯ÑгÑж дууÑлаа, админиÑтратор баталгаажуулах хүртÑл хүлÑÑÐ½Ñ Ò¯Ò¯." - notice_default_data_loaded: Стандарт тохиргоог амжилттай ачааллаа. - notice_unable_delete_version: Хувилбарыг уÑтгах боломжгүй. - notice_issue_done_ratios_updated: Issue done ratios updated. - - error_can_t_load_default_data: "Стандарт тохиргоог ачаалж чадÑангүй: %{value}" - error_scm_not_found: "Repository дотор тухайн бичлÑг ÑÑвÑл хувилбарыг олÑонгүй." - error_scm_command_failed: "Repository-д хандахад алдаа гарлаа: %{value}" - error_scm_annotate: "БичлÑг байхгүй байна, ÑÑвÑл бичлÑгт тайлбар хавÑаргаж болохгүй." - error_issue_not_found_in_project: 'СонгоÑон аÑуудал ÑÐ½Ñ Ñ‚Ó©Ñөлд хамаардаггүй юм уу ÑÑвÑл ÑиÑтемд байхгүй байна.' - error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' - error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' - error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' - error_can_not_archive_project: This project can not be archived - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' - - warning_attachments_not_saved: "%{count} file(s) файлыг хадгалж чадÑангүй." - - mail_subject_lost_password: "Таны %{value} нууц үг" - mail_body_lost_password: 'Ðууц үгÑÑ Ó©Ó©Ñ€Ñ‡Ð»Ó©Ñ…Ð¸Ð¹Ð½ тулд доорх Ñ…Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð´ÑÑÑ€ дарна уу:' - mail_subject_register: "Таны %{value} данÑыг идÑвхжүүлÑÑ…" - mail_body_register: 'ДанÑаа идÑвхжүүлÑхийн тулд доорх Ñ…Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð´ÑÑÑ€ дарна уу:' - mail_body_account_information_external: "Та өөрийнхөө %{value} данÑыг ашиглаж холбогдож болно." - mail_body_account_information: Таны данÑны тухай мÑдÑÑлÑл - mail_subject_account_activation_request: "%{value} данÑыг идÑвхжүүлÑÑ… Ñ…Ò¯ÑÑлт" - mail_body_account_activation_request: "Ð¨Ð¸Ð½Ñ Ñ…ÑÑ€ÑглÑгч (%{value}) бүртгүүлÑÑн байна. Таны баталгаажуулахыг хүлÑÑж байна:" - mail_subject_reminder: "Дараагийн өдрүүдÑд %{count} аÑуудлыг шийдÑÑ… Ñ…ÑÑ€ÑгтÑй (%{days})" - mail_body_reminder: "Танд оноогдÑон %{count} аÑуудлуудыг дараагийн %{days} өдрүүдÑд шийдÑÑ… Ñ…ÑÑ€ÑгтÑй:" - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - - gui_validation_error: 1 алдаа - gui_validation_error_plural: "%{count} алдаа" - - field_name: ÐÑÑ€ - field_description: Тайлбар - field_summary: ДүгнÑлт - field_is_required: Зайлшгүй - field_firstname: Таны нÑÑ€ - field_lastname: Овог - field_mail: ИмÑйл - field_filename: Файл - field_filesize: Ð¥ÑмжÑÑ - field_downloads: Татаж авах Ð·Ò¯Ð¹Ð»Ñ - field_author: Зохиогч - field_created_on: Ò®Ò¯ÑÑÑн - field_updated_on: ӨөрчилÑөн - field_field_format: Формат - field_is_for_all: Бүх төÑлийн хувьд - field_possible_values: Боломжтой утгууд - field_regexp: Энгийн илÑрхийлÑл - field_min_length: Минимум урт - field_max_length: МакÑимум урт - field_value: Утга - field_category: Төрөл - field_title: Гарчиг - field_project: ТөÑөл - field_issue: ÐÑуудал - field_status: Төлөв - field_notes: ТÑмдÑглÑлүүд - field_is_closed: ÐÑуудал хаагдÑан - field_is_default: Стандарт утга - field_tracker: ЧиглÑл - field_subject: Гарчиг - field_due_date: ДууÑах огноо - field_assigned_to: ОноогдÑон - field_priority: ЗÑÑ€ÑглÑл - field_fixed_version: Хувилбар - field_user: Ð¥ÑÑ€ÑглÑгч - field_role: Хандалтын Ñрх - field_homepage: Ðүүр Ñ…ÑƒÑƒÐ´Ð°Ñ - field_is_public: Олон нийтийн - field_parent: ЭцÑг төÑөл нь - field_is_in_roadmap: ÐÑуудлуудыг Ñвцын зураг дÑÑÑ€ харуулах - field_login: ÐÑвтрÑÑ… нÑÑ€ - field_mail_notification: ИмÑйл мÑдÑгдлүүд - field_admin: ÐдминиÑтратор - field_last_login_on: Сүүлийн холбоо - field_language: Ð¥Ñл - field_effective_date: Огноо - field_password: Ðууц үг - field_new_password: Ð¨Ð½Ð½Ñ Ð½ÑƒÑƒÑ† үг - field_password_confirmation: Баталгаажуулах - field_version: Хувилбар - field_type: Төрөл - field_host: ХоÑÑ‚ - field_port: Порт - field_account: Ð”Ð°Ð½Ñ - field_base_dn: ҮндÑÑн ДР- field_attr_login: ÐÑвтрÑÑ… аттрибут - field_attr_firstname: Таны нÑÑ€ аттрибут - field_attr_lastname: Овог аттрибут - field_attr_mail: ИмÑйл аттрибут - field_onthefly: Ð¥Ò¯ÑÑÑн үедÑÑ Ñ…ÑÑ€ÑглÑгч Ò¯Ò¯ÑгÑÑ… - field_start_date: ЭхлÑл - field_done_ratio: "%% ГүйцÑтгÑÑÑн" - field_auth_source: ÐÑвтрÑÑ… арга - field_hide_mail: Миний имÑйл хаÑгийг нуу - field_comments: Тайлбар - field_url: URL ХаÑг - field_start_page: ТÑргүүн Ñ…ÑƒÑƒÐ´Ð°Ñ - field_subproject: ДÑд төÑөл - field_hours: Цаг - field_activity: Үйл ажиллагаа - field_spent_on: Огноо - field_identifier: ТөÑлийн глобал нÑÑ€ - field_is_filter: Шүүлтүүр болгон Ñ…ÑÑ€ÑглÑгддÑг - field_issue_to: Хамаатай аÑуудал - field_delay: Хоцролт - field_assignable: Ð­Ð½Ñ Ñ…Ð°Ð½Ð´Ð°Ð»Ñ‚Ñ‹Ð½ ÑрхÑд аÑуудлуудыг оноож өгч болно - field_redirect_existing_links: Байгаа холбооÑуудыг дахин чиглүүлÑÑ… - field_estimated_hours: БарагцаалÑан цаг - field_column_names: Баганууд - field_time_zone: Цагын Ð±Ò¯Ñ - field_searchable: Хайж болох - field_default_value: Стандарт утга - field_comments_sorting: Тайлбаруудыг харуул - field_parent_title: ЭцÑг Ñ…ÑƒÑƒÐ´Ð°Ñ - field_editable: ЗаÑварлагдана - field_watcher: Харна - field_identity_url: OpenID URL - field_content: Ðгуулга - field_group_by: Үр дүнгÑÑÑ€ бүлÑглÑÑ… - field_sharing: Sharing - - setting_app_title: Программын гарчиг - setting_app_subtitle: Программын дÑд гарчиг - setting_welcome_text: МÑндчилгÑÑ - setting_default_language: Стандарт Ñ…Ñл - setting_login_required: ÐÑвтрÑÑ… шаардлагатай - setting_self_registration: Өөрийгөө бүртгүүлÑÑ… - setting_attachment_max_size: ХавÑралт файлын дÑÑд Ñ…ÑмжÑÑ - setting_issues_export_limit: ÐÑуудал ÑкÑпортлох Ñ…Ñзгаар - setting_mail_from: Ямар имÑйл хаÑг Ò¯Ò¯ÑгÑÑ… - setting_bcc_recipients: BCC талбарын хаÑгууд (bcc) - setting_plain_text_mail: дан текÑÑ‚ мÑйл (HTML биш) - setting_host_name: ХоÑтын нÑÑ€ болон зам - setting_text_formatting: ТекÑÑ‚ Ñ…ÑлбÑржүүлÑлт - setting_wiki_compression: Вики хуудÑуудын түүх дÑÑÑ€ шахалт хийх - setting_feeds_limit: Фийд агуулгын Ñ…Ñзгаар - setting_default_projects_public: Ð¨Ð¸Ð½Ñ Ñ‚Ó©Ñлүүд автоматаар олон нийтийнх байна - setting_autofetch_changesets: Комитуудыг автоматаар татаж авах - setting_sys_api_enabled: Репозитори менежментÑд зориулан WS-ийг идÑвхжүүлÑÑ… - setting_commit_ref_keywords: Хамааруулах түлхүүр Ò¯Ð³Ñ - setting_commit_fix_keywords: Зоолттой түлхүүр Ò¯Ð³Ñ - setting_autologin: Компьютер дÑÑÑ€ Ñанах - setting_date_format: Огнооны формат - setting_time_format: Цагийн формат - setting_cross_project_issue_relations: ТөÑөл хооронд аÑуудал хамааруулахыг зөвшөөрөх - setting_issue_list_default_columns: ÐÑуудлуудыг харуулах Ñтандарт баганууд - setting_emails_footer: ИмÑйлүүдийн хөл Ñ…ÑÑÑг - setting_protocol: Протокол - setting_per_page_options: ÐÑг хуудÑанд байх обьектуудын тохиргоо - setting_user_format: Ð¥ÑÑ€ÑглÑгчдийг харуулах формат - setting_activity_days_default: ТөÑлийн үйл ажиллагаа Ñ…ÑÑÑгт үзүүлÑÑ… өдрийн тоо - setting_display_subprojects_issues: ДÑд төÑлүүдийн аÑуудлуудыг автоматаар гол төÑөл дÑÑÑ€ харуулах - setting_enabled_scm: SCM - ийг идÑвхжүүлÑÑ… - setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" - setting_mail_handler_api_enabled: ИрÑÑн мÑйлүүдийн хувьд WS-ийг идÑвхжүүлÑÑ… - setting_mail_handler_api_key: API түлхүүр - setting_sequential_project_identifiers: ДÑÑ Ð´Ð°Ñ€Ð°Ð°Ð»Ñан төÑлийн глобал нÑÑ€ Ò¯Ò¯ÑгÑж байх - setting_gravatar_enabled: Gravatar дүрÑүүдийг Ñ…ÑÑ€ÑглÑгчдÑд Ñ…ÑÑ€ÑглÑж байх - setting_gravatar_default: Default Gravatar image - setting_diff_max_lines_displayed: Ялгаатай мөрүүдийн тоо (дÑÑд тал нь) - setting_file_max_size_displayed: Max size of text files displayed inline - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_openid: Allow OpenID login and registration - setting_password_min_length: Minimum password length - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - setting_default_projects_modules: Default enabled modules for new projects - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_field: Use the issue field - setting_issue_done_ratio_issue_status: Use the issue status - setting_start_of_week: Start calendars on - setting_rest_api_enabled: Enable REST web service - setting_cache_formatted_text: Cache formatted text - - permission_add_project: Create project - permission_add_subprojects: Create subprojects - permission_edit_project: ТөÑлийг заÑварлах - permission_select_project_modules: ТөÑлийн модулуудийг Ñонгоно уу - permission_manage_members: СиÑтемийн Ñ…ÑÑ€ÑглÑгчид - permission_manage_project_activities: Manage project activities - permission_manage_versions: Хувилбарууд - permission_manage_categories: ÐÑуудлын ангиллууд - permission_view_issues: ÐÑуудлуудыг харах - permission_add_issues: ÐÑуудлууд нÑмÑÑ… - permission_edit_issues: ÐÑуудлуудыг заÑварлах - permission_manage_issue_relations: ÐÑуудлын хамаарлыг зохицуулах - permission_add_issue_notes: ТÑмдÑглÑл нÑмÑÑ… - permission_edit_issue_notes: ТÑмдÑглÑлүүд заÑварлах - permission_edit_own_issue_notes: Өөрийн үлдÑÑÑÑн Ñ‚ÑмдÑглÑлүүдийг заÑварлах - permission_move_issues: ÐÑуудлуудыг зөөх - permission_delete_issues: ÐÑуудлуудыг уÑтгах - permission_manage_public_queries: Олон нийтийн аÑуултууд - permission_save_queries: ÐÑуултуудыг хадгалах - permission_view_gantt: Гант диаграмыг үзÑÑ… - permission_view_calendar: Календарь үзÑÑ… - permission_view_issue_watchers: Ðжиглагчдын жагÑаалтыг харах - permission_add_issue_watchers: Ðжиглагчид нÑмÑÑ… - permission_delete_issue_watchers: Ðжиглагчдыг уÑтгах - permission_log_time: ЗарцуулÑан хугацааг лог хийх - permission_view_time_entries: ЗарцуулÑан хугацааг харах - permission_edit_time_entries: Хугацааны логуудыг заÑварлах - permission_edit_own_time_entries: Өөрийн хугацааны логуудыг заÑварлах - permission_manage_news: МÑдÑÑ Ð¼ÑдÑÑллүүд - permission_comment_news: МÑдÑÑнд тайлбар үлдÑÑÑ… - permission_manage_documents: Бичиг баримтууд - permission_view_documents: Бичиг баримтуудыг харах - permission_manage_files: Файлууд - permission_view_files: Файлуудыг харах - permission_manage_wiki: Вики удирдах - permission_rename_wiki_pages: Вики хуудÑуудыг дахиж нÑрлÑÑ… - permission_delete_wiki_pages: Вики хуудÑуудыг уÑтгах - permission_view_wiki_pages: Вики үзÑÑ… - permission_view_wiki_edits: Вики түүх үзÑÑ… - permission_edit_wiki_pages: Вики хуудÑуудыг заÑварлах - permission_delete_wiki_pages_attachments: ХавÑралтуудыг уÑтгах - permission_protect_wiki_pages: Вики хуудÑуудыг хамгаалах - permission_manage_repository: Репозитори - permission_browse_repository: Репозиторийг үзÑÑ… - permission_view_changesets: Өөрчлөлтүүдийг харах - permission_commit_access: Коммит хандалт - permission_manage_boards: Самбарууд - permission_view_messages: ЗурваÑуудыг харах - permission_add_messages: Ð—ÑƒÑ€Ð²Ð°Ñ Ð¸Ð»Ð³ÑÑÑ… - permission_edit_messages: ЗурваÑуудыг заÑварлах - permission_edit_own_messages: Өөрийн зурваÑуудыг заÑварлах - permission_delete_messages: ЗурваÑуудыг уÑтгах - permission_delete_own_messages: Өөрийн зурваÑуудыг уÑтгах - permission_export_wiki_pages: Вики хуудÑуудыг ÑкÑпорт хийх - - project_module_issue_tracking: ÐÑуудал Ñ…Ñнах - project_module_time_tracking: Хугацаа Ñ…Ñнах - project_module_news: МÑдÑÑ Ð¼ÑдÑÑллүүд - project_module_documents: Бичиг баримтууд - project_module_files: Файлууд - project_module_wiki: Вики - project_module_repository: Репозитори - project_module_boards: Самбарууд - - label_user: Ð¥ÑÑ€ÑглÑгч - label_user_plural: Ð¥ÑÑ€ÑглÑгчид - label_user_new: Ð¨Ð¸Ð½Ñ Ñ…ÑÑ€ÑглÑгч - label_user_anonymous: Хамаагүй Ñ…ÑÑ€ÑглÑгч - label_project: ТөÑөл - label_project_new: Ð¨Ð¸Ð½Ñ Ñ‚Ó©Ñөл - label_project_plural: ТөÑлүүд - label_x_projects: - zero: төÑөл байхгүй - one: 1 төÑөл - other: "%{count} төÑлүүд" - label_project_all: Бүх ТөÑлүүд - label_project_latest: Сүүлийн үеийн төÑлүүд - label_issue: ÐÑуудал - label_issue_new: Ð¨Ð¸Ð½Ñ Ð°Ñуудал - label_issue_plural: ÐÑуудлууд - label_issue_view_all: Бүх аÑуудлуудыг харах - label_issues_by: "%{value} - н аÑуудлууд" - label_issue_added: ÐÑуудал нÑмÑгдлÑÑ - label_issue_updated: ÐÑуудал өөрчлөгдлөө - label_document: Бичиг баримт - label_document_new: Ð¨Ð¸Ð½Ñ Ð±Ð¸Ñ‡Ð¸Ð³ баримт - label_document_plural: Бичиг баримтууд - label_document_added: Бичиг баримт нÑмÑгдлÑÑ - label_role: Хандалтын Ñрх - label_role_plural: Хандалтын Ñрхүүд - label_role_new: Ð¨Ð¸Ð½Ñ Ñ…Ð°Ð½Ð´Ð°Ð»Ñ‚Ñ‹Ð½ Ñрх - label_role_and_permissions: Хандалтын Ñрхүүд болон зөвшөөрлүүд - label_member: Гишүүн - label_member_new: Ð¨Ð¸Ð½Ñ Ð³Ð¸ÑˆÒ¯Ò¯Ð½ - label_member_plural: Гишүүд - label_tracker: ЧиглÑл - label_tracker_plural: ЧиглÑлүүд - label_tracker_new: Ð¨Ð¸Ð½Ñ Ñ‡Ð¸Ð³Ð»Ñл - label_workflow: Ðжлын дараалал - label_issue_status: ÐÑуудлын төлөв - label_issue_status_plural: ÐÑуудлын төлвүүд - label_issue_status_new: Ð¨Ð¸Ð½Ñ Ñ‚Ó©Ð»Ó©Ð² - label_issue_category: ÐÑуудлын ангилал - label_issue_category_plural: ÐÑуудлын ангиллууд - label_issue_category_new: Ð¨Ð¸Ð½Ñ Ð°Ð½Ð³Ð¸Ð»Ð°Ð» - label_custom_field: Ð¥ÑÑ€ÑглÑгчийн тодорхойлÑон талбар - label_custom_field_plural: Ð¥ÑÑ€ÑглÑгчийн тодорхойлÑон талбарууд - label_custom_field_new: ШинÑÑÑ€ Ñ…ÑÑ€ÑглÑгчийн тодорхойлÑон талбар Ò¯Ò¯ÑгÑÑ… - label_enumerations: Ðнгиллууд - label_enumeration_new: Ð¨Ð¸Ð½Ñ ÑƒÑ‚Ð³Ð° - label_information: МÑдÑÑлÑл - label_information_plural: МÑдÑÑллүүд - label_please_login: ÐÑвтÑрч орно уу - label_register: БүртгүүлÑÑ… - label_login_with_open_id_option: or login with OpenID - label_password_lost: Ðууц үгÑÑ Ð°Ð»Ð´Ñан - label_home: Ðүүр - label_my_page: Миний Ñ…ÑƒÑƒÐ´Ð°Ñ - label_my_account: Миний Ð´Ð°Ð½Ñ - label_my_projects: Миний төÑлүүд - label_administration: Ðдмин Ñ…ÑÑÑг - label_login: ÐÑвтрÑÑ… - label_logout: Гарах - label_help: ТуÑламж - label_reported_issues: МÑдÑгдÑÑн аÑуудлууд - label_assigned_to_me_issues: Ðадад оноогдÑон аÑуудлууд - label_last_login: Сүүлийн холболт - label_registered_on: БүртгүүлÑÑн огноо - label_activity: Үйл ажиллагаа - label_overall_activity: Ерөнхий үйл ажиллагаа - label_user_activity: "%{value}-ийн үйл ажиллагаа" - label_new: Ð¨Ð¸Ð½Ñ - label_logged_as: ХолбогдÑон нÑÑ€ - label_environment: Орчин - label_authentication: ÐÑвтрÑÑ… - label_auth_source: ÐÑвтрÑÑ… арга - label_auth_source_new: Ð¨Ð¸Ð½Ñ Ð½ÑвтрÑÑ… арга - label_auth_source_plural: ÐÑвтрÑÑ… аргууд - label_subproject_plural: ДÑд төÑлүүд - label_subproject_new: Ð¨Ð¸Ð½Ñ Ð´Ñд төÑөл - label_and_its_subprojects: "%{value} болон холбогдох дÑд төÑлүүд" - label_min_max_length: ДÑÑд - Доод урт - label_list: ЖагÑаалт - label_date: Огноо - label_integer: БүхÑл тоо - label_float: Бутархай тоо - label_boolean: ҮнÑн худал утга - label_string: ТекÑÑ‚ - label_text: Урт текÑÑ‚ - label_attribute: Ðттрибут - label_attribute_plural: Ðттрибутууд - label_download: "%{count} Татаж авÑан зүйл" - label_download_plural: "%{count} Татаж авÑан зүйлÑ" - label_no_data: ҮзүүлÑÑ… өгөгдөл байхгүй байна - label_change_status: Төлвийг өөрчлөх - label_history: Түүх - label_attachment: Файл - label_attachment_new: Ð¨Ð¸Ð½Ñ Ñ„Ð°Ð¹Ð» - label_attachment_delete: Файл уÑтгах - label_attachment_plural: Файлууд - label_file_added: Файл нÑмÑгдлÑÑ - label_report: Тайлан - label_report_plural: Тайлангууд - label_news: МÑдÑÑ - label_news_new: Ð¨Ð¸Ð½Ñ Ð¼ÑдÑÑ - label_news_plural: МÑдÑÑ - label_news_latest: Сүүлийн үеийн мÑдÑÑнүүд - label_news_view_all: Бүх мÑдÑÑг харах - label_news_added: МÑдÑÑ Ð½ÑмÑгдлÑÑ - label_settings: Тохиргоо - label_overview: ЭхлÑл - label_version: Хувилбар - label_version_new: Ð¨Ð¸Ð½Ñ Ñ…ÑƒÐ²Ð¸Ð»Ð±Ð°Ñ€ - label_version_plural: Хувилбарууд - label_close_versions: ГүйцÑÑ‚ хувилбаруудыг хаалаа - label_confirmation: Баталгаажуулах - label_export_to: 'Ó¨Ó©Ñ€ авч болох формат:' - label_read: Унших... - label_public_projects: Олон нийтийн төÑлүүд - label_open_issues: нÑÑлттÑй - label_open_issues_plural: нÑÑлттÑй - label_closed_issues: хаалттай - label_closed_issues_plural: хаалттай - label_x_open_issues_abbr_on_total: - zero: 0 нÑÑлттÑй / %{total} - one: 1 нÑÑлттÑй / %{total} - other: "%{count} нÑÑлттÑй / %{total}" - label_x_open_issues_abbr: - zero: 0 нÑÑлттÑй - one: 1 нÑÑлттÑй - other: "%{count} нÑÑлттÑй" - label_x_closed_issues_abbr: - zero: 0 хаалттай - one: 1 хаалттай - other: "%{count} хаалттай" - label_total: Ðийт - label_permissions: Зөвшөөрлүүд - label_current_status: Одоогийн төлөв - label_new_statuses_allowed: ШинÑÑÑ€ олгож болох төлвүүд - label_all: бүгд - label_none: хооÑон - label_nobody: Ñ…Ñн ч биш - label_next: Дараагийн - label_previous: Өмнөх - label_used_by: Ð¥ÑÑ€ÑглÑгддÑг - label_details: ДÑлгÑÑ€Ñнгүй - label_add_note: ТÑмдÑглÑл нÑмÑÑ… - label_per_page: ÐÑг хуудÑанд - label_calendar: Календарь - label_months_from: Саруудыг Ñ…Ð°Ð°Ð½Ð°Ð°Ñ - label_gantt: Гант диаграм - label_internal: Дотоод - label_last_changes: "Ñүүлийн %{count} өөрчлөлтүүд" - label_change_view_all: Бүх өөрчлөлтүүдийг харах - label_personalize_page: Ð­Ð½Ñ Ñ…ÑƒÑƒÐ´Ñыг өөрт зориулан өөрчлөх - label_comment: Тайлбар - label_comment_plural: Тайлбарууд - label_x_comments: - zero: ÑÑтгÑгдÑл байхгүй - one: 1 ÑÑтгÑгдÑлтÑй - other: "%{count} ÑÑтгÑгдÑлтÑй" - label_comment_add: Тайлбар нÑмÑÑ… - label_comment_added: Тайлбар нÑмÑгдлÑÑ - label_comment_delete: Тайлбарууд уÑтгах - label_query: Ð¥ÑÑ€ÑглÑгчийн тодорхойлÑон аÑуулт - label_query_plural: Ð¥ÑÑ€ÑглÑгчийн тодорхойлÑон аÑуултууд - label_query_new: ШинÑÑÑ€ Ñ…ÑÑ€ÑглÑгчийн тодорхойлÑон аÑуулт Ò¯Ò¯ÑгÑÑ… - label_filter_add: Шүүлтүүр нÑмÑÑ… - label_filter_plural: Шүүлтүүрүүд - label_equals: бол - label_not_equals: биш - label_in_less_than: Ð°Ð°Ñ Ð±Ð°Ð³Ð° - label_in_more_than: Ð°Ð°Ñ Ð¸Ñ… - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: дотор - label_today: өнөөдөр - label_all_time: бүх хугацаа - label_yesterday: өчигдөр - label_this_week: ÑÐ½Ñ Ð´Ð¾Ð»Ð¾Ð¾ хоног - label_last_week: өнгөрÑөн долоо хоног - label_last_n_days: "Ñүүлийн %{count} өдрүүд" - label_this_month: ÑÐ½Ñ Ñар - label_last_month: Ñүүлийн Ñар - label_this_year: ÑÐ½Ñ Ð¶Ð¸Ð» - label_date_range: Ð¥Ñзгаар огноо - label_less_than_ago: бага өдрийн дотор - label_more_than_ago: их өдрийн дотор - label_ago: өдрийн өмнө - label_contains: агуулж байгаа - label_not_contains: агуулаагүй - label_day_plural: өдрүүд - label_repository: Репозитори - label_repository_plural: Репозиторууд - label_browse: ҮзÑÑ… - label_modification: "%{count} өөрчлөлт" - label_modification_plural: "%{count} өөрчлөлтүүд" - label_branch: Салбар - label_tag: Шошго - label_revision: Хувилбар - label_revision_plural: Хувилбарууд - label_revision_id: "%{value} Хувилбар" - label_associated_revisions: Хамааралтай хувилбарууд - label_added: нÑмÑгдÑÑн - label_modified: өөрчлөгдÑөн - label_copied: хуулÑан - label_renamed: нÑрийг нь өөрчилÑөн - label_deleted: уÑтгаÑан - label_latest_revision: Сүүлийн үеийн хувилбар - label_latest_revision_plural: Сүүлийн үеийн хувилбарууд - label_view_revisions: Хувилбаруудыг харах - label_view_all_revisions: Бүх хувилбаруудыг харах - label_max_size: Maximum size - label_sort_highest: Хамгийн дÑÑÑ€ - label_sort_higher: ДÑÑш нь - label_sort_lower: Доош нь - label_sort_lowest: Хамгийн доор - label_roadmap: Хөтөч - label_roadmap_due_in: "%{value} дотор дууÑгах" - label_roadmap_overdue: "%{value} оройтÑон" - label_roadmap_no_issues: Ð­Ð½Ñ Ñ…ÑƒÐ²Ð¸Ð»Ð±Ð°Ñ€Ñ‚ аÑуудал байхгүй байна - label_search: Хайх - label_result_plural: Үр дүн - label_all_words: Бүх Ò¯Ð³Ñ - label_wiki: Вики - label_wiki_edit: Вики заÑвар - label_wiki_edit_plural: Вики заÑварууд - label_wiki_page: Вики Ñ…ÑƒÑƒÐ´Ð°Ñ - label_wiki_page_plural: Вики Ñ…ÑƒÑƒÐ´Ð°Ñ - label_index_by_title: Гарчгаар ÑÑ€ÑмбÑлÑÑ… - label_index_by_date: Огноогоор ÑÑ€ÑмбÑлÑÑ… - label_current_version: Одоогийн хувилбар - label_preview: Ямар харагдахыг шалгах - label_feed_plural: Feeds - label_changes_details: Бүх өөрчлөлтүүдийн дÑлгÑÑ€Ñнгүй - label_issue_tracking: ÐÑуудал Ñ…Ñнах - label_spent_time: ЗарцуулÑан хугацаа - label_f_hour: "%{value} цаг" - label_f_hour_plural: "%{value} цаг" - label_time_tracking: Хугацааг Ñ…Ñнах - label_change_plural: Өөрчлөлтүүд - label_statistics: СтатиÑтик - label_commits_per_month: Сард хийÑÑн коммитын тоо - label_commits_per_author: Зохиогч бүрийн хувьд коммитын тоо - label_view_diff: Ялгаануудыг харах - label_diff_inline: дотор нь - label_diff_side_by_side: зÑÑ€Ñгцүүлж - label_options: Тохиргоо - label_copy_workflow_from: Ðжлын дарааллыг хуулах - label_permissions_report: Зөвшөөрлүүдийн таблиц - label_watched_issues: Ðжиглагдаж байгаа аÑуудлууд - label_related_issues: Хамааралтай аÑуудлууд - label_applied_status: ОлгоÑон төлөв - label_loading: Ðчаалж байна... - label_relation_new: Ð¨Ð¸Ð½Ñ Ñ…Ð°Ð¼Ð°Ð°Ñ€Ð°Ð» - label_relation_delete: Хамаарлыг уÑтгах - label_relates_to: Ñнгийн хамааралтай - label_duplicates: Ñ…Ð¾Ñ Ñ…Ð°Ð¼Ð°Ð°Ñ€Ð°Ð»Ñ‚Ð°Ð¹ - label_duplicated_by: давхардуулÑан ÑзÑн - label_blocks: шаардах хамааралтай - label_blocked_by: блоколÑон ÑзÑн - label_precedes: урьдчилах хамааралтай - label_follows: дагаж - label_end_to_start: Ñ…Ð¾Ð¹Ð½Ð¾Ð¾Ñ Ð½ÑŒ урагшаа - label_end_to_end: Ñ…Ð¾Ð¹Ð½Ð¾Ð¾Ñ Ð½ÑŒ хойшоо - label_start_to_start: ÑƒÑ€Ð´Ð°Ð°Ñ Ð½ÑŒ урагаа - label_start_to_end: ÑƒÑ€Ð´Ð°Ð°Ñ Ð½ÑŒ хойшоо - label_stay_logged_in: Ð­Ð½Ñ ÐºÐ¾Ð¼ÑŒÑŽÑ‚ÐµÑ€ дÑÑÑ€ Ñанах - label_disabled: идÑвхгүй болÑон - label_show_completed_versions: ГүйцÑд хувилбаруудыг харуулах - label_me: би - label_board: Форум - label_board_new: Ð¨Ð¸Ð½Ñ Ñ„Ð¾Ñ€ÑƒÐ¼ - label_board_plural: Форумууд - label_board_locked: ТүгжÑÑÑ‚Ñй - label_board_sticky: Sticky - label_topic_plural: СÑдвүүд - label_message_plural: ЗурваÑууд - label_message_last: Сүүлийн Ð·ÑƒÑ€Ð²Ð°Ñ - label_message_new: Ð¨Ð¸Ð½Ñ Ð·ÑƒÑ€Ð²Ð°Ñ - label_message_posted: Ð—ÑƒÑ€Ð²Ð°Ñ Ð½ÑмÑгдлÑÑ - label_reply_plural: Хариултууд - label_send_information: ДанÑны мÑдÑÑллийг Ñ…ÑÑ€ÑглÑгчид илгÑÑÑ… - label_year: Жил - label_month: Сар - label_week: Долоо хоног - label_date_from: Ð¥ÑзÑÑнÑÑÑ - label_date_to: Ð¥Ñдий хүртÑл - label_language_based: Ð¥ÑÑ€ÑглÑгчийн Ñ…ÑÐ»Ð½Ð°Ñ ÑˆÐ°Ð»Ñ‚Ð³Ð°Ð°Ð»Ð°Ð½ - label_sort_by: "%{value} талбараар нь ÑÑ€ÑмбÑлÑÑ…" - label_send_test_email: Турших мÑйл илгÑÑÑ… - label_feeds_access_key: RSS хандах түлхүүр - label_missing_feeds_access_key: RSS хандах түлхүүр алга - label_feeds_access_key_created_on: "RSS хандалтын түлхүүр %{value}-ийн өмнө Ò¯Ò¯ÑÑÑн" - label_module_plural: Модулууд - label_added_time_by: "%{author} %{age}-ийн өмнө нÑмÑÑн" - label_updated_time_by: "%{author} %{age}-ийн өмнө өөрчилÑөн" - label_updated_time: "%{value} -ийн өмнө өөрчлөгдÑөн" - label_jump_to_a_project: ТөÑөл Ñ€Ò¯Ò¯ очих... - label_file_plural: Файлууд - label_changeset_plural: Өөрчлөлтүүд - label_default_columns: Стандарт баганууд - label_no_change_option: (Өөрчлөлт байхгүй) - label_bulk_edit_selected_issues: СонгогдÑон аÑуудлуудыг бөөнөөр заÑварлах - label_theme: СиÑтемийн Дизайн - label_default: Стандарт - label_search_titles_only: Зөвхөн гарчиг хайх - label_user_mail_option_all: "Миний бүх төÑөл дÑÑрх бүх үзÑгдлүүдийн хувьд" - label_user_mail_option_selected: "СонгогдÑон төÑлүүдийн хувьд бүх үзÑгдÑл дÑÑÑ€..." - label_user_mail_no_self_notified: "Миний өөрийн хийÑÑн өөрчлөлтүүдийн тухай надад мÑдÑгдÑÑ… Ñ…ÑÑ€Ñггүй" - label_registration_activation_by_email: данÑыг имÑйлÑÑÑ€ идÑвхжүүлÑÑ… - label_registration_manual_activation: данÑыг гараар идÑвхжүүлÑÑ… - label_registration_automatic_activation: данÑыг автоматаар идÑвхжүүлÑÑ… - label_display_per_page: 'ÐÑг хуудÑанд: %{value}' - label_age: ÐÐ°Ñ - label_change_properties: Тохиргоог өөрчлөх - label_general: Ерөнхий - label_more: Цааш нь - label_scm: SCM - label_plugins: Модулууд - label_ldap_authentication: LDAP нÑвтрÑÑ… горим - label_downloads_abbr: D/L - label_optional_description: Дурын тайлбар - label_add_another_file: Дахин файл нÑмÑÑ… - label_preferences: Тохиргоо - label_chronological_order: Цагаан толгойн Ò¯Ñгийн дарааллаар - label_reverse_chronological_order: Урвуу цагаан толгойн Ò¯Ñгийн дарааллаар - label_planning: Төлөвлөлт - label_incoming_emails: ИрÑÑн мÑйлүүд - label_generate_key: Түлхүүр Ò¯Ò¯ÑгÑÑ… - label_issue_watchers: Ðжиглагчид - label_example: ЖишÑÑ - label_display: Display - label_sort: Sort - label_ascending: Ascending - label_descending: Descending - label_date_from_to: From %{start} to %{end} - label_wiki_content_added: Wiki page added - label_wiki_content_updated: Wiki page updated - label_group: Group - label_group_plural: Groups - label_group_new: New group - label_time_entry_plural: Spent time - label_version_sharing_none: Not shared - label_version_sharing_descendants: With subprojects - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_tree: With project tree - label_version_sharing_system: With all projects - label_update_issue_done_ratios: Update issue done ratios - label_copy_source: Source - label_copy_target: Target - label_copy_same_as_target: Same as target - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_api_access_key: API access key - label_missing_api_access_key: Missing an API access key - label_api_access_key_created_on: "API access key created %{value} ago" - - button_login: ÐÑвтрÑÑ… - button_submit: ИлгÑÑÑ… - button_save: Хадгалах - button_check_all: Бүгдийг Ñонго - button_uncheck_all: Бүгдийг үл Ñонго - button_delete: УÑтгах - button_create: Ò®Ò¯ÑгÑÑ… - button_create_and_continue: Ò®Ò¯ÑгÑÑд цааш үргÑлжлүүлÑÑ… - button_test: Турших - button_edit: ЗаÑварлах - button_add: ÐÑмÑÑ… - button_change: Өөрчлөх - button_apply: Өөрчлөлтийг хадгалах - button_clear: ЦÑвÑрлÑÑ… - button_lock: Түгжих - button_unlock: ТүгжÑÑг тайлах - button_download: Татах - button_list: ЖагÑаалт - button_view: Харах - button_move: Зөөх - button_move_and_follow: Зөө Ð±Ð°Ñ Ð´Ð°Ð³Ð° - button_back: Буцах - button_cancel: Болих - button_activate: ИдÑвхжүүлÑÑ… - button_sort: ЭрÑмбÑлÑÑ… - button_log_time: Лог хийÑÑн хугацаа - button_rollback: Ð­Ð½Ñ Ñ…ÑƒÐ²Ð¸Ð»Ð±Ð°Ñ€ руу буцах - button_watch: Ðжиглах - button_unwatch: Ðжиглахаа болих - button_reply: Хариулах - button_archive: Ðрхивлах - button_unarchive: Ðрхивыг задлах - button_reset: Ðнхны утгууд - button_rename: ÐÑрийг нь Ñолих - button_change_password: Ðууц үгÑÑ Ó©Ó©Ñ€Ñ‡Ð»Ó©Ñ… - button_copy: Хуулах - button_copy_and_follow: Зөө Ð±Ð°Ñ Ð´Ð°Ð³Ð° - button_annotate: Тайлбар хавÑаргах - button_update: ШинÑчлÑÑ… - button_configure: Тохируулах - button_quote: ИшлÑл - button_duplicate: Хуулбар - button_show: ҮзÑÑ… - - status_active: идÑвхтÑй - status_registered: бүртгүүлÑÑн - status_locked: түгжÑÑÑ‚Ñй - - version_status_open: нÑÑлттÑй - version_status_locked: түгжÑÑÑ‚Ñй - version_status_closed: хаалттай - - field_active: идÑвхтÑй - - text_select_mail_notifications: Ямар үед имÑйлÑÑÑ€ мÑдÑгдÑл илгÑÑхийг Ñонгоно уу. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 гÑвÑл Ñмар ч Ñ…Ñзгааргүй гÑÑÑн үг - text_project_destroy_confirmation: Та ÑÐ½Ñ Ñ‚Ó©Ñөл болоод буÑад мÑдÑÑллийг нь үнÑÑ…ÑÑÑ€ уÑтгамаар байна уу ? - text_subprojects_destroy_warning: "Уг төÑлийн дÑд төÑлүүд : %{value} нь Ð±Ð°Ñ ÑƒÑтгагдах болно." - text_workflow_edit: Ðжлын дарааллыг өөрчлөхийн тулд хандалтын Ñрх болон аÑуудлын чиглÑлийг Ñонгоно уу - text_are_you_sure: Та итгÑлтÑй байна уу ? - text_journal_changed: "%{label} %{old} байÑан нь %{new} болов" - text_journal_set_to: "%{label} %{value} болгож өөрчиллөө" - text_journal_deleted: "%{label} уÑÑ‚Ñан (%{old})" - text_journal_added: "%{label} %{value} нÑмÑгдÑÑн" - text_tip_issue_begin_day: ÑÐ½Ñ Ó©Ð´Ó©Ñ€ ÑхлÑÑ… ажил - text_tip_issue_end_day: ÑÐ½Ñ Ó©Ð´Ó©Ñ€ дууÑах ажил - text_tip_issue_begin_end_day: ÑÐ½Ñ Ó©Ð´Ó©Ñ€ ÑхлÑÑд мөн дууÑч байгаа ажил - text_caracters_maximum: "дÑÑд тал нь %{count} Ò¯ÑÑг." - text_caracters_minimum: "Хамгийн багадаа Ñдаж %{count} Ñ‚ÑмдÑгт байх." - text_length_between: "Урт нь багадаа %{min}, ихдÑÑ %{max} Ñ‚ÑмдÑгт." - text_tracker_no_workflow: ЭнÑÑ…Ò¯Ò¯ аÑуудлын чиглÑлд Ñмар ч ажлын дараалал тодорхойлогдоогүй байна - text_unallowed_characters: Ð¥ÑÑ€ÑглÑж болохгүй Ñ‚ÑмдÑгтүүд - text_comma_separated: ТаÑлалаар зааглан олон утга оруулж болно. - text_line_separated: Multiple values allowed (one line for each value). - text_issues_ref_in_commit_messages: Коммитийн зурваÑуудад хамааруулÑан болон байнгын аÑуудлууд - text_issue_added: "ÐÑуудал %{id} - ийг Ñ…ÑÑ€ÑглÑгч %{author} мÑдÑгдÑÑн байна." - text_issue_updated: "ÐÑуудал %{id} - ийг Ñ…ÑÑ€ÑглÑгч %{author} өөрчилÑөн байна." - text_wiki_destroy_confirmation: Та ÑÐ½Ñ Ð²Ð¸ÐºÐ¸ болон холбогдох бүх мÑдÑÑллийг үнÑÑ…ÑÑÑ€ уÑтгамаар байна уу ? - text_issue_category_destroy_question: "Ð­Ð½Ñ Ð°Ð½Ð³Ð¸Ð»Ð°Ð»Ð´ зарим аÑуудлууд (%{count}) орÑон байна. Та Ñах Ð²Ñ ?" - text_issue_category_destroy_assignments: ÐÑуудлуудыг ÑÐ½Ñ Ð°Ð½Ð³Ð¸Ð»Ð»Ð°Ð°Ñ Ð°Ð²Ð°Ñ… - text_issue_category_reassign_to: ÐÑуудлуудыг ÑÐ½Ñ Ð°Ð½Ð³Ð¸Ð»Ð°Ð»Ð´ дахин оноох - text_user_mail_option: "Сонгогдоогүй төÑлүүдийн хувьд, та зөвхөн өөрийнхөө ажиглаж байгаа Ð·Ò¯Ð¹Ð»Ñ ÑŽÐ¼ÑƒÑƒ танд хамаатай зүйлÑийн талаар мÑдÑгдÑл авах болно (Таны оруулÑан аÑуудал, ÑÑвÑл танд онооÑон гÑÑ… мÑÑ‚)." - text_no_configuration_data: "Хандалтын Ñрхүүд, чиглÑлүүд, аÑуудлын төлвүүд болон ажлын дарааллын тухай мÑдÑÑллийг хараахан оруулаагүй байна.\nТа Ñтандарт өгөгдлүүдийг даруйхан оруулахыг зөвлөж байна, оруулÑан хойно та заÑварлаж болно." - text_load_default_configuration: Стандарт өгөгдлийг ачаалах - text_status_changed_by_changeset: "%{value} өөрчлөлтөд хийгдÑÑн." - text_issues_destroy_confirmation: 'Та ÑонгогдÑон аÑуудлуудыг үнÑÑ…ÑÑÑ€ уÑтгамаар байна уу ?' - text_select_project_modules: 'Ð­Ð½Ñ Ñ‚Ó©Ñлийн хувьд идÑвхжүүлÑÑ… модулуудаа Ñонгоно уу:' - text_default_administrator_account_changed: Стандарт админиÑтраторын бүртгÑл өөрчлөгдлөө - text_file_repository_writable: ХавÑралт файл хадгалах Ñ…Ð°Ð²Ñ‚Ð°Ñ Ñ€ÑƒÑƒ бичих ÑрхтÑй - text_plugin_assets_writable: Плагин модулийн аÑÑет Ñ…Ð°Ð²Ñ‚Ð°Ñ Ñ€ÑƒÑƒ бичих ÑрхтÑй - text_rmagick_available: RMagick ÑуулгагдÑан (заавал биш) - text_destroy_time_entries_question: "Таны уÑтгах гÑж байгаа аÑуудлууд дÑÑÑ€ нийт %{hours} цаг зарцуулÑан юм байна, та Ñах Ð²Ñ ?" - text_destroy_time_entries: МÑдÑгдÑÑн цагуудыг уÑтгах - text_assign_time_entries_to_project: МÑдÑгдÑÑн аÑуудлуудыг төÑөлд оноох - text_reassign_time_entries: 'МÑдÑгдÑÑн аÑуудлуудыг ÑÐ½Ñ Ð°Ñуудалд дахин оноо:' - text_user_wrote: "%{value} бичихдÑÑ:" - text_enumeration_destroy_question: "Ð­Ð½Ñ ÑƒÑ‚Ð³Ð°Ð´ %{count} обьект оноогдÑон байна." - text_enumeration_category_reassign_to: 'ТÑдгÑÑрийг ÑÐ½Ñ ÑƒÑ‚Ð³Ð°Ð´ дахин оноо:' - text_email_delivery_not_configured: "ИмÑйлийн тохиргоог хараахан тохируулаагүй байна, тиймÑÑÑ Ð¸Ð¼Ñйл мÑдÑгдÑл Ñвуулах боломжгүй байна.\nSMTP ÑервÑÑ€ÑÑ config/configuration.yml файл дотор тохируулаад төÑлийн менежерÑÑ Ð´Ð°Ñ…Ð¸Ð°Ð´ ÑхлүүлÑÑÑ€Ñй." - text_repository_usernames_mapping: "Репозиторийн логд байгаа бүх Ñ…ÑÑ€ÑглÑгчийн нÑрүүдÑд харгалзÑан ТөÑлийн Менежер ÑиÑтемд бүртгÑлтÑй Ñ…ÑÑ€ÑглÑгчдийг Сонгох юмуу шинÑÑ‡Ð¸Ð»Ð½Ñ Ò¯Ò¯.\nТөÑлийн менежер болон репозиторид байгаа ижилхÑн нÑÑ€ юмуу имÑйлтÑй Ñ…ÑÑ€ÑглÑгчид харилцан харгалзна." - text_diff_truncated: '... Файлын Ñлгаврын Ñ…ÑмжÑÑ Ò¯Ð·Ò¯Ò¯Ð»ÑÑ…Ñд дÑндүү урт байгаа ÑƒÑ‡Ñ€Ð°Ð°Ñ Ñ‚Ó©Ð³ÑÐ³Ó©Ð»Ó©Ó©Ñ Ð½ÑŒ хаÑч үзүүлÑв.' - text_custom_field_possible_values_info: 'One line for each value' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" - text_wiki_page_nullify_children: "Keep child pages as root pages" - text_wiki_page_destroy_children: "Delete child pages and all their descendants" - text_wiki_page_reassign_children: "Reassign child pages to this parent page" - text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" - - default_role_manager: Менежер - default_role_developer: ХөгжүүлÑгч - default_role_reporter: МÑдÑгдÑгч - default_tracker_bug: Ðлдаа - default_tracker_feature: Онцлог - default_tracker_support: ТуÑламж - default_issue_status_new: Ð¨Ð¸Ð½Ñ - default_issue_status_in_progress: Ðхицтай - default_issue_status_assigned: ОноогдÑон - default_issue_status_resolved: ШийдвÑрлÑгдÑÑн - default_issue_status_feedback: Feedback - default_issue_status_closed: ХаагдÑан - default_issue_status_rejected: ХүлÑÑж аваагүй - default_doc_category_user: Ð¥ÑÑ€ÑглÑгчийн бичиг баримт - default_doc_category_tech: Техникийн бичиг баримт - default_priority_low: Бага - default_priority_normal: Ð¥Ñвийн - default_priority_high: Өндөр - default_priority_urgent: ÐÑн Ñаралтай - default_priority_immediate: ÐÑн даруй - default_activity_design: Дизайн - default_activity_development: ХөгжүүлÑлт - - enumeration_issue_priorities: ÐÑуудлын зÑÑ€ÑглÑлүүд - enumeration_doc_categories: Бичиг баримтын ангиллууд - enumeration_activities: Үйл ажиллагаанууд (хугацааг Ñ…Ñнах) - enumeration_system_activity: СиÑтемийн үйл ажиллагаа - - permission_manage_subtasks: Manage subtasks - label_profile: Profile - field_parent_issue: Parent task - error_unable_delete_issue_status: Unable to delete issue status - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Коммит хийх үед харуулах текÑтүүдийн Ñнкодинг - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 ÐÑуудал - one: 1 ÐÑуудал - other: "%{count} ÐÑуудлууд" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: бүгд - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: With subprojects - label_cross_project_tree: With project tree - label_cross_project_hierarchy: With project hierarchy - label_cross_project_system: With all projects - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b1/b1ef4066928eb38f392c116365a81367439453cd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b1/b1ef4066928eb38f392c116365a81367439453cd.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1093 @@ +# Norwegian, norsk bokmål, by irb.no +"no": + support: + array: + sentence_connector: "og" + direction: ltr + date: + formats: + default: "%d.%m.%Y" + short: "%e. %b" + long: "%e. %B %Y" + day_names: [søndag, mandag, tirsdag, onsdag, torsdag, fredag, lørdag] + abbr_day_names: [søn, man, tir, ons, tor, fre, lør] + month_names: [~, januar, februar, mars, april, mai, juni, juli, august, september, oktober, november, desember] + abbr_month_names: [~, jan, feb, mar, apr, mai, jun, jul, aug, sep, okt, nov, des] + order: + - :day + - :month + - :year + time: + formats: + default: "%A, %e. %B %Y, %H:%M" + time: "%H:%M" + short: "%e. %B, %H:%M" + long: "%A, %e. %B %Y, %H:%M" + am: "" + pm: "" + datetime: + distance_in_words: + half_a_minute: "et halvt minutt" + less_than_x_seconds: + one: "mindre enn 1 sekund" + other: "mindre enn %{count} sekunder" + x_seconds: + one: "1 sekund" + other: "%{count} sekunder" + less_than_x_minutes: + one: "mindre enn 1 minutt" + other: "mindre enn %{count} minutter" + x_minutes: + one: "1 minutt" + other: "%{count} minutter" + about_x_hours: + one: "rundt 1 time" + other: "rundt %{count} timer" + x_hours: + one: "1 time" + other: "%{count} timer" + x_days: + one: "1 dag" + other: "%{count} dager" + about_x_months: + one: "rundt 1 måned" + other: "rundt %{count} måneder" + x_months: + one: "1 måned" + other: "%{count} måneder" + about_x_years: + one: "rundt 1 år" + other: "rundt %{count} år" + over_x_years: + one: "over 1 år" + other: "over %{count} år" + almost_x_years: + one: "nesten 1 år" + other: "nesten %{count} år" + number: + format: + precision: 3 + separator: "." + delimiter: "," + currency: + format: + unit: "kr" + format: "%n %u" + precision: + format: + delimiter: "" + precision: 4 + human: + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + activerecord: + errors: + template: + header: "kunne ikke lagre %{model} på grunn av %{count} feil." + body: "det oppstod problemer i følgende felt:" + messages: + inclusion: "er ikke inkludert i listen" + exclusion: "er reservert" + invalid: "er ugyldig" + confirmation: "passer ikke bekreftelsen" + accepted: "må være akseptert" + empty: "kan ikke være tom" + blank: "kan ikke være blank" + too_long: "er for lang (maksimum %{count} tegn)" + too_short: "er for kort (minimum %{count} tegn)" + wrong_length: "er av feil lengde (maksimum %{count} tegn)" + taken: "er allerede i bruk" + not_a_number: "er ikke et tall" + greater_than: "må være større enn %{count}" + greater_than_or_equal_to: "må være større enn eller lik %{count}" + equal_to: "må være lik %{count}" + less_than: "må være mindre enn %{count}" + less_than_or_equal_to: "må være mindre enn eller lik %{count}" + odd: "må være oddetall" + even: "må være partall" + greater_than_start_date: "må være større enn startdato" + not_same_project: "hører ikke til samme prosjekt" + circular_dependency: "Denne relasjonen ville lagd en sirkulær avhengighet" + cant_link_an_issue_with_a_descendant: "En sak kan ikke kobles mot en av sine undersaker" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + + actionview_instancetag_blank_option: Vennligst velg + + general_text_No: 'Nei' + general_text_Yes: 'Ja' + general_text_no: 'nei' + general_text_yes: 'ja' + general_lang_name: 'Norwegian (Norsk bokmål)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Kontoen er oppdatert. + notice_account_invalid_creditentials: Feil brukernavn eller passord + notice_account_password_updated: Passordet er oppdatert. + notice_account_wrong_password: Feil passord + notice_account_register_done: Kontoen er opprettet. Klikk lenken som er sendt deg i e-post for å aktivere kontoen. + notice_account_unknown_email: Ukjent bruker. + notice_can_t_change_password: Denne kontoen bruker ekstern godkjenning. Passordet kan ikke endres. + notice_account_lost_email_sent: En e-post med instruksjoner for å velge et nytt passord er sendt til deg. + notice_account_activated: Din konto er aktivert. Du kan nå logge inn. + notice_successful_create: Opprettet. + notice_successful_update: Oppdatert. + notice_successful_delete: Slettet. + notice_successful_connection: Koblet opp. + notice_file_not_found: Siden du forsøkte å vise eksisterer ikke, eller er slettet. + notice_locking_conflict: Data har blitt oppdatert av en annen bruker. + notice_not_authorized: Du har ikke adgang til denne siden. + notice_email_sent: "En e-post er sendt til %{value}" + notice_email_error: "En feil oppstod under sending av e-post (%{value})" + notice_feeds_access_key_reseted: Din Atom-tilgangsnøkkel er nullstilt. + notice_failed_to_save_issues: "Lykkes ikke å lagre %{count} sak(er) på %{total} valgt: %{ids}." + notice_no_issue_selected: "Ingen sak valgt! Vennligst merk sakene du vil endre." + notice_account_pending: "Din konto ble opprettet og avventer nå administrativ godkjenning." + notice_default_data_loaded: Standardkonfigurasjonen lastet inn. + + error_can_t_load_default_data: "Standardkonfigurasjonen kunne ikke lastes inn: %{value}" + error_scm_not_found: "Elementet og/eller revisjonen eksisterer ikke i depoet." + error_scm_command_failed: "En feil oppstod under tilkobling til depoet: %{value}" + error_scm_annotate: "Elementet eksisterer ikke, eller kan ikke noteres." + error_issue_not_found_in_project: 'Saken eksisterer ikke, eller hører ikke til dette prosjektet' + + mail_subject_lost_password: "Ditt %{value} passord" + mail_body_lost_password: 'Klikk følgende lenke for å endre ditt passord:' + mail_subject_register: "%{value} kontoaktivering" + mail_body_register: 'Klikk følgende lenke for å aktivere din konto:' + mail_body_account_information_external: "Du kan bruke din %{value}-konto for å logge inn." + mail_body_account_information: Informasjon om din konto + mail_subject_account_activation_request: "%{value} kontoaktivering" + mail_body_account_activation_request: "En ny bruker (%{value}) er registrert, og avventer din godkjenning:" + mail_subject_reminder: "%{count} sak(er) har frist de kommende %{days} dagene" + mail_body_reminder: "%{count} sak(er) som er tildelt deg har frist de kommende %{days} dager:" + + + field_name: Navn + field_description: Beskrivelse + field_summary: Oppsummering + field_is_required: Kreves + field_firstname: Fornavn + field_lastname: Etternavn + field_mail: E-post + field_filename: Fil + field_filesize: Størrelse + field_downloads: Nedlastinger + field_author: Forfatter + field_created_on: Opprettet + field_updated_on: Oppdatert + field_field_format: Format + field_is_for_all: For alle prosjekter + field_possible_values: Lovlige verdier + field_regexp: Regular expression + field_min_length: Minimum lengde + field_max_length: Maksimum lengde + field_value: Verdi + field_category: Kategori + field_title: Tittel + field_project: Prosjekt + field_issue: Sak + field_status: Status + field_notes: Notater + field_is_closed: Lukker saken + field_is_default: Standardverdi + field_tracker: Sakstype + field_subject: Emne + field_due_date: Frist + field_assigned_to: Tildelt til + field_priority: Prioritet + field_fixed_version: Mål-versjon + field_user: Bruker + field_role: Rolle + field_homepage: Hjemmeside + field_is_public: Offentlig + field_parent: Underprosjekt av + field_is_in_roadmap: Vises i veikart + field_login: Brukernavn + field_mail_notification: E-post-varsling + field_admin: Administrator + field_last_login_on: Sist innlogget + field_language: Språk + field_effective_date: Dato + field_password: Passord + field_new_password: Nytt passord + field_password_confirmation: Bekreft passord + field_version: Versjon + field_type: Type + field_host: Vert + field_port: Port + field_account: Konto + field_base_dn: Base DN + field_attr_login: Brukernavnsattributt + field_attr_firstname: Fornavnsattributt + field_attr_lastname: Etternavnsattributt + field_attr_mail: E-post-attributt + field_onthefly: On-the-fly brukeropprettelse + field_start_date: Start + field_done_ratio: "% Ferdig" + field_auth_source: Autentiseringskilde + field_hide_mail: Skjul min epost-adresse + field_comments: Kommentarer + field_url: URL + field_start_page: Startside + field_subproject: Underprosjekt + field_hours: Timer + field_activity: Aktivitet + field_spent_on: Dato + field_identifier: Identifikasjon + field_is_filter: Brukes som filter + field_issue_to: Relaterte saker + field_delay: Forsinkelse + field_assignable: Saker kan tildeles denne rollen + field_redirect_existing_links: Viderekoble eksisterende lenker + field_estimated_hours: Estimert tid + field_column_names: Kolonner + field_time_zone: Tidssone + field_searchable: Søkbar + field_default_value: Standardverdi + field_comments_sorting: Vis kommentarer + + setting_app_title: Applikasjonstittel + setting_app_subtitle: Applikasjonens undertittel + setting_welcome_text: Velkomsttekst + setting_default_language: Standardspråk + setting_login_required: Krever innlogging + setting_self_registration: Selvregistrering + setting_attachment_max_size: Maks. størrelse vedlegg + setting_issues_export_limit: Eksportgrense for saker + setting_mail_from: Avsenders epost + setting_bcc_recipients: Blindkopi (bcc) til mottakere + setting_host_name: Vertsnavn + setting_text_formatting: Tekstformattering + setting_wiki_compression: Komprimering av Wiki-historikk + setting_feeds_limit: Innholdsgrense for Feed + setting_default_projects_public: Nye prosjekter er offentlige som standard + setting_autofetch_changesets: Autohenting av endringssett + setting_sys_api_enabled: Aktiver webservice for depot-administrasjon + setting_commit_ref_keywords: Nøkkelord for referanse + setting_commit_fix_keywords: Nøkkelord for retting + setting_autologin: Autoinnlogging + setting_date_format: Datoformat + setting_time_format: Tidsformat + setting_cross_project_issue_relations: Tillat saksrelasjoner på kryss av prosjekter + setting_issue_list_default_columns: Standardkolonner vist i sakslisten + setting_emails_footer: Epost-signatur + setting_protocol: Protokoll + setting_per_page_options: Alternativer, objekter pr. side + setting_user_format: Visningsformat, brukere + setting_activity_days_default: Dager vist på prosjektaktivitet + setting_display_subprojects_issues: Vis saker fra underprosjekter på hovedprosjekt som standard + setting_enabled_scm: Aktiviserte SCM + + project_module_issue_tracking: Sakshåndtering + project_module_time_tracking: Tidsregistrering + project_module_news: Nyheter + project_module_documents: Dokumenter + project_module_files: Filer + project_module_wiki: Wiki + project_module_repository: Depot + project_module_boards: Forumer + + label_user: Bruker + label_user_plural: Brukere + label_user_new: Ny bruker + label_project: Prosjekt + label_project_new: Nytt prosjekt + label_project_plural: Prosjekter + label_x_projects: + zero: ingen prosjekter + one: 1 prosjekt + other: "%{count} prosjekter" + label_project_all: Alle prosjekter + label_project_latest: Siste prosjekter + label_issue: Sak + label_issue_new: Ny sak + label_issue_plural: Saker + label_issue_view_all: Vis alle saker + label_issues_by: "Saker etter %{value}" + label_issue_added: Sak lagt til + label_issue_updated: Sak oppdatert + label_document: Dokument + label_document_new: Nytt dokument + label_document_plural: Dokumenter + label_document_added: Dokument lagt til + label_role: Rolle + label_role_plural: Roller + label_role_new: Ny rolle + label_role_and_permissions: Roller og rettigheter + label_member: Medlem + label_member_new: Nytt medlem + label_member_plural: Medlemmer + label_tracker: Sakstype + label_tracker_plural: Sakstyper + label_tracker_new: Ny sakstype + label_workflow: Arbeidsflyt + label_issue_status: Saksstatus + label_issue_status_plural: Saksstatuser + label_issue_status_new: Ny status + label_issue_category: Sakskategori + label_issue_category_plural: Sakskategorier + label_issue_category_new: Ny kategori + label_custom_field: Eget felt + label_custom_field_plural: Egne felt + label_custom_field_new: Nytt eget felt + label_enumerations: Listeverdier + label_enumeration_new: Ny verdi + label_information: Informasjon + label_information_plural: Informasjon + label_please_login: Vennlist logg inn + label_register: Registrer + label_password_lost: Mistet passord + label_home: Hjem + label_my_page: Min side + label_my_account: Min konto + label_my_projects: Mine prosjekter + label_administration: Administrasjon + label_login: Logg inn + label_logout: Logg ut + label_help: Hjelp + label_reported_issues: Rapporterte saker + label_assigned_to_me_issues: Saker tildelt meg + label_last_login: Sist innlogget + label_registered_on: Registrert + label_activity: Aktivitet + label_overall_activity: All aktivitet + label_new: Ny + label_logged_as: Innlogget som + label_environment: Miljø + label_authentication: Autentisering + label_auth_source: Autentiseringskilde + label_auth_source_new: Ny autentiseringskilde + label_auth_source_plural: Autentiseringskilder + label_subproject_plural: Underprosjekter + label_and_its_subprojects: "%{value} og dets underprosjekter" + label_min_max_length: Min.-maks. lengde + label_list: Liste + label_date: Dato + label_integer: Heltall + label_float: Kommatall + label_boolean: Sann/usann + label_string: Tekst + label_text: Lang tekst + label_attribute: Attributt + label_attribute_plural: Attributter + label_no_data: Ingen data å vise + label_change_status: Endre status + label_history: Historikk + label_attachment: Fil + label_attachment_new: Ny fil + label_attachment_delete: Slett fil + label_attachment_plural: Filer + label_file_added: Fil lagt til + label_report: Rapport + label_report_plural: Rapporter + label_news: Nyheter + label_news_new: Legg til nyhet + label_news_plural: Nyheter + label_news_latest: Siste nyheter + label_news_view_all: Vis alle nyheter + label_news_added: Nyhet lagt til + label_settings: Innstillinger + label_overview: Oversikt + label_version: Versjon + label_version_new: Ny versjon + label_version_plural: Versjoner + label_confirmation: Bekreftelse + label_export_to: Eksporter til + label_read: Leser... + label_public_projects: Offentlige prosjekt + label_open_issues: åpen + label_open_issues_plural: åpne + label_closed_issues: lukket + label_closed_issues_plural: lukkede + label_x_open_issues_abbr_on_total: + zero: 0 åpne / %{total} + one: 1 åpen / %{total} + other: "%{count} åpne / %{total}" + label_x_open_issues_abbr: + zero: 0 åpne + one: 1 åpen + other: "%{count} åpne" + label_x_closed_issues_abbr: + zero: 0 lukket + one: 1 lukket + other: "%{count} lukket" + label_total: Totalt + label_permissions: Rettigheter + label_current_status: Nåværende status + label_new_statuses_allowed: Tillate nye statuser + label_all: alle + label_none: ingen + label_nobody: ingen + label_next: Neste + label_previous: Forrige + label_used_by: Brukt av + label_details: Detaljer + label_add_note: Legg til notat + label_per_page: Pr. side + label_calendar: Kalender + label_months_from: måneder fra + label_gantt: Gantt + label_internal: Intern + label_last_changes: "siste %{count} endringer" + label_change_view_all: Vis alle endringer + label_personalize_page: Tilpass denne siden + label_comment: Kommentar + label_comment_plural: Kommentarer + label_x_comments: + zero: ingen kommentarer + one: 1 kommentar + other: "%{count} kommentarer" + label_comment_add: Legg til kommentar + label_comment_added: Kommentar lagt til + label_comment_delete: Slett kommentar + label_query: Egen spørring + label_query_plural: Egne spørringer + label_query_new: Ny spørring + label_filter_add: Legg til filter + label_filter_plural: Filtre + label_equals: er + label_not_equals: er ikke + label_in_less_than: er mindre enn + label_in_more_than: in mer enn + label_in: i + label_today: idag + label_all_time: all tid + label_yesterday: i går + label_this_week: denne uken + label_last_week: sist uke + label_last_n_days: "siste %{count} dager" + label_this_month: denne måneden + label_last_month: siste måned + label_this_year: dette året + label_date_range: Dato-spenn + label_less_than_ago: mindre enn dager siden + label_more_than_ago: mer enn dager siden + label_ago: dager siden + label_contains: inneholder + label_not_contains: ikke inneholder + label_day_plural: dager + label_repository: Depot + label_repository_plural: Depoter + label_browse: Utforsk + label_revision: Revisjon + label_revision_plural: Revisjoner + label_associated_revisions: Assosierte revisjoner + label_added: lagt til + label_modified: endret + label_deleted: slettet + label_latest_revision: Siste revisjon + label_latest_revision_plural: Siste revisjoner + label_view_revisions: Vis revisjoner + label_max_size: Maksimum størrelse + label_sort_highest: Flytt til toppen + label_sort_higher: Flytt opp + label_sort_lower: Flytt ned + label_sort_lowest: Flytt til bunnen + label_roadmap: Veikart + label_roadmap_due_in: "Frist om %{value}" + label_roadmap_overdue: "%{value} over fristen" + label_roadmap_no_issues: Ingen saker for denne versjonen + label_search: Søk + label_result_plural: Resultater + label_all_words: Alle ord + label_wiki: Wiki + label_wiki_edit: Wiki endring + label_wiki_edit_plural: Wiki endringer + label_wiki_page: Wiki-side + label_wiki_page_plural: Wiki-sider + label_index_by_title: Indekser etter tittel + label_index_by_date: Indekser etter dato + label_current_version: Gjeldende versjon + label_preview: Forhåndsvis + label_feed_plural: Feeder + label_changes_details: Detaljer om alle endringer + label_issue_tracking: Sakshåndtering + label_spent_time: Brukt tid + label_f_hour: "%{value} time" + label_f_hour_plural: "%{value} timer" + label_time_tracking: Tidsregistrering + label_change_plural: Endringer + label_statistics: Statistikk + label_commits_per_month: Innsendinger pr. måned + label_commits_per_author: Innsendinger pr. forfatter + label_view_diff: Vis forskjeller + label_diff_inline: i teksten + label_diff_side_by_side: side ved side + label_options: Alternativer + label_copy_workflow_from: Kopier arbeidsflyt fra + label_permissions_report: Rettighetsrapport + label_watched_issues: Overvåkede saker + label_related_issues: Relaterte saker + label_applied_status: Gitt status + label_loading: Laster... + label_relation_new: Ny relasjon + label_relation_delete: Slett relasjon + label_relates_to: relatert til + label_duplicates: dupliserer + label_duplicated_by: duplisert av + label_blocks: blokkerer + label_blocked_by: blokkert av + label_precedes: kommer før + label_follows: følger + label_end_to_start: slutt til start + label_end_to_end: slutt til slutt + label_start_to_start: start til start + label_start_to_end: start til slutt + label_stay_logged_in: Hold meg innlogget + label_disabled: avslått + label_show_completed_versions: Vis ferdige versjoner + label_me: meg + label_board: Forum + label_board_new: Nytt forum + label_board_plural: Forumer + label_topic_plural: Emner + label_message_plural: Meldinger + label_message_last: Siste melding + label_message_new: Ny melding + label_message_posted: Melding lagt til + label_reply_plural: Svar + label_send_information: Send kontoinformasjon til brukeren + label_year: År + label_month: Måned + label_week: Uke + label_date_from: Fra + label_date_to: Til + label_language_based: Basert på brukerens språk + label_sort_by: "Sorter etter %{value}" + label_send_test_email: Send en epost-test + label_feeds_access_key_created_on: "Atom tilgangsnøkkel opprettet for %{value} siden" + label_module_plural: Moduler + label_added_time_by: "Lagt til av %{author} for %{age} siden" + label_updated_time: "Oppdatert for %{value} siden" + label_jump_to_a_project: Gå til et prosjekt... + label_file_plural: Filer + label_changeset_plural: Endringssett + label_default_columns: Standardkolonner + label_no_change_option: (Ingen endring) + label_bulk_edit_selected_issues: Samlet endring av valgte saker + label_theme: Tema + label_default: Standard + label_search_titles_only: Søk bare i titler + label_user_mail_option_all: "For alle hendelser på mine prosjekter" + label_user_mail_option_selected: "For alle hendelser på valgte prosjekt..." + label_user_mail_no_self_notified: "Jeg vil ikke bli varslet om endringer jeg selv gjør" + label_registration_activation_by_email: kontoaktivering pr. e-post + label_registration_manual_activation: manuell kontoaktivering + label_registration_automatic_activation: automatisk kontoaktivering + label_display_per_page: "Pr. side: %{value}" + label_age: Alder + label_change_properties: Endre egenskaper + label_general: Generell + label_more: Mer + label_scm: SCM + label_plugins: Tillegg + label_ldap_authentication: LDAP-autentisering + label_downloads_abbr: Nedl. + label_optional_description: Valgfri beskrivelse + label_add_another_file: Legg til en fil til + label_preferences: Brukerinnstillinger + label_chronological_order: I kronologisk rekkefølge + label_reverse_chronological_order: I omvendt kronologisk rekkefølge + label_planning: Planlegging + + button_login: Logg inn + button_submit: Send + button_save: Lagre + button_check_all: Merk alle + button_uncheck_all: Avmerk alle + button_delete: Slett + button_create: Opprett + button_test: Test + button_edit: Endre + button_add: Legg til + button_change: Endre + button_apply: Bruk + button_clear: Nullstill + button_lock: Lås + button_unlock: Lås opp + button_download: Last ned + button_list: Liste + button_view: Vis + button_move: Flytt + button_back: Tilbake + button_cancel: Avbryt + button_activate: Aktiver + button_sort: Sorter + button_log_time: Logg tid + button_rollback: Rull tilbake til denne versjonen + button_watch: Overvåk + button_unwatch: Stopp overvåkning + button_reply: Svar + button_archive: Arkiver + button_unarchive: Gjør om arkivering + button_reset: Nullstill + button_rename: Endre navn + button_change_password: Endre passord + button_copy: Kopier + button_annotate: Notér + button_update: Oppdater + button_configure: Konfigurer + + status_active: aktiv + status_registered: registrert + status_locked: låst + + text_select_mail_notifications: Velg hendelser som skal varsles med e-post. + text_regexp_info: f.eks. ^[A-Z0-9]+$ + text_min_max_length_info: 0 betyr ingen begrensning + text_project_destroy_confirmation: Er du sikker på at du vil slette dette prosjekter og alle relatert data ? + text_subprojects_destroy_warning: "Underprojekt(ene): %{value} vil også bli slettet." + text_workflow_edit: Velg en rolle og en sakstype for å endre arbeidsflyten + text_are_you_sure: Er du sikker ? + text_tip_issue_begin_day: oppgaven starter denne dagen + text_tip_issue_end_day: oppgaven avsluttes denne dagen + text_tip_issue_begin_end_day: oppgaven starter og avsluttes denne dagen + text_caracters_maximum: "%{count} tegn maksimum." + text_caracters_minimum: "Må være minst %{count} tegn langt." + text_length_between: "Lengde mellom %{min} og %{max} tegn." + text_tracker_no_workflow: Ingen arbeidsflyt definert for denne sakstypen + text_unallowed_characters: Ugyldige tegn + text_comma_separated: Flere verdier tillat (kommaseparert). + text_issues_ref_in_commit_messages: Referering og retting av saker i innsendingsmelding + text_issue_added: "Sak %{id} er innrapportert av %{author}." + text_issue_updated: "Sak %{id} er oppdatert av %{author}." + text_wiki_destroy_confirmation: Er du sikker på at du vil slette denne wikien og alt innholdet ? + text_issue_category_destroy_question: "Noen saker (%{count}) er lagt til i denne kategorien. Hva vil du gjøre ?" + text_issue_category_destroy_assignments: Fjern bruk av kategorier + text_issue_category_reassign_to: Overfør sakene til denne kategorien + text_user_mail_option: "For ikke-valgte prosjekter vil du bare motta varsling om ting du overvåker eller er involveret i (eks. saker du er forfatter av eller er tildelt)." + text_no_configuration_data: "Roller, arbeidsflyt, sakstyper og -statuser er ikke konfigurert enda.\nDet anbefales sterkt å laste inn standardkonfigurasjonen. Du vil kunne endre denne etter den er innlastet." + text_load_default_configuration: Last inn standardkonfigurasjonen + text_status_changed_by_changeset: "Brukt i endringssett %{value}." + text_issues_destroy_confirmation: 'Er du sikker på at du vil slette valgte sak(er) ?' + text_select_project_modules: 'Velg moduler du vil aktivere for dette prosjektet:' + text_default_administrator_account_changed: Standard administrator-konto er endret + text_file_repository_writable: Fil-arkivet er skrivbart + text_rmagick_available: RMagick er tilgjengelig (valgfritt) + text_destroy_time_entries_question: "%{hours} timer er ført på sakene du er i ferd med å slette. Hva vil du gjøre ?" + text_destroy_time_entries: Slett førte timer + text_assign_time_entries_to_project: Overfør førte timer til prosjektet + text_reassign_time_entries: 'Overfør førte timer til denne saken:' + text_user_wrote: "%{value} skrev:" + + default_role_manager: Leder + default_role_developer: Utvikler + default_role_reporter: Rapportør + default_tracker_bug: Feil + default_tracker_feature: Funksjon + default_tracker_support: Support + default_issue_status_new: Ny + default_issue_status_in_progress: Pågår + default_issue_status_resolved: Avklart + default_issue_status_feedback: Tilbakemelding + default_issue_status_closed: Lukket + default_issue_status_rejected: Avvist + default_doc_category_user: Brukerdokumentasjon + default_doc_category_tech: Teknisk dokumentasjon + default_priority_low: Lav + default_priority_normal: Normal + default_priority_high: Høy + default_priority_urgent: Haster + default_priority_immediate: Omgående + default_activity_design: Design + default_activity_development: Utvikling + + enumeration_issue_priorities: Sakssprioriteringer + enumeration_doc_categories: Dokumentkategorier + enumeration_activities: Aktiviteter (tidsregistrering) + text_enumeration_category_reassign_to: 'Endre dem til denne verdien:' + text_enumeration_destroy_question: "%{count} objekter er endret til denne verdien." + label_incoming_emails: Innkommende e-post + label_generate_key: Generer en nøkkel + setting_mail_handler_api_enabled: Skru på WS for innkommende epost + setting_mail_handler_api_key: API-nøkkel + text_email_delivery_not_configured: "Levering av epost er ikke satt opp, og varsler er skrudd av.\nStill inn din SMTP-tjener i config/configuration.yml og start programmet på nytt for å skru det på." + field_parent_title: Overordnet side + label_issue_watchers: Overvåkere + button_quote: Sitat + setting_sequential_project_identifiers: Generer sekvensielle prosjekt-IDer + notice_unable_delete_version: Kan ikke slette versjonen + label_renamed: gitt nytt navn + label_copied: kopiert + setting_plain_text_mail: kun ren tekst (ikke HTML) + permission_view_files: Vise filer + permission_edit_issues: Redigere saker + permission_edit_own_time_entries: Redigere egne timelister + permission_manage_public_queries: Administrere delte søk + permission_add_issues: Legge inn saker + permission_log_time: Loggføre timer + permission_view_changesets: Vise endringssett + permission_view_time_entries: Vise brukte timer + permission_manage_versions: Administrere versjoner + permission_manage_wiki: Administrere wiki + permission_manage_categories: Administrere kategorier for saker + permission_protect_wiki_pages: Beskytte wiki-sider + permission_comment_news: Kommentere nyheter + permission_delete_messages: Slette meldinger + permission_select_project_modules: Velge prosjektmoduler + permission_edit_wiki_pages: Redigere wiki-sider + permission_add_issue_watchers: Legge til overvåkere + permission_view_gantt: Vise gantt-diagram + permission_move_issues: Flytte saker + permission_manage_issue_relations: Administrere saksrelasjoner + permission_delete_wiki_pages: Slette wiki-sider + permission_manage_boards: Administrere forum + permission_delete_wiki_pages_attachments: Slette vedlegg + permission_view_wiki_edits: Vise wiki-historie + permission_add_messages: Sende meldinger + permission_view_messages: Vise meldinger + permission_manage_files: Administrere filer + permission_edit_issue_notes: Redigere notater + permission_manage_news: Administrere nyheter + permission_view_calendar: Vise kalender + permission_manage_members: Administrere medlemmer + permission_edit_messages: Redigere meldinger + permission_delete_issues: Slette saker + permission_view_issue_watchers: Vise liste over overvåkere + permission_manage_repository: Administrere depot + permission_commit_access: Tilgang til innsending + permission_browse_repository: Bla gjennom depot + permission_view_documents: Vise dokumenter + permission_edit_project: Redigere prosjekt + permission_add_issue_notes: Legge til notater + permission_save_queries: Lagre søk + permission_view_wiki_pages: Vise wiki + permission_rename_wiki_pages: Gi wiki-sider nytt navn + permission_edit_time_entries: Redigere timelister + permission_edit_own_issue_notes: Redigere egne notater + setting_gravatar_enabled: Bruk Gravatar-brukerikoner + label_example: Eksempel + text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." + permission_edit_own_messages: Rediger egne meldinger + permission_delete_own_messages: Slett egne meldinger + label_user_activity: "%{value}s aktivitet" + label_updated_time_by: "Oppdatert av %{author} for %{age} siden" + text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' + setting_diff_max_lines_displayed: Max number of diff lines displayed + text_plugin_assets_writable: Plugin assets directory writable + warning_attachments_not_saved: "%{count} fil(er) kunne ikke lagres." + button_create_and_continue: Opprett og fortsett + text_custom_field_possible_values_info: 'En linje for hver verdi' + label_display: Visning + field_editable: Redigerbar + setting_repository_log_display_limit: Maks antall revisjoner vist i fil-loggen + setting_file_max_size_displayed: Max size of text files displayed inline + field_watcher: Overvåker + setting_openid: Tillat OpenID innlogging og registrering + field_identity_url: OpenID URL + label_login_with_open_id_option: eller logg inn med OpenID + field_content: Innhold + label_descending: Synkende + label_sort: Sorter + label_ascending: Stigende + label_date_from_to: Fra %{start} til %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Denne siden har %{descendants} underside(r). Hva ønsker du å gjøre? + text_wiki_page_reassign_children: Tilknytt undersider til denne overordnede siden + text_wiki_page_nullify_children: Behold undersider som rotsider + text_wiki_page_destroy_children: Slett undersider og alle deres underliggende sider + setting_password_min_length: Minimum passordlengde + field_group_by: Grupper resultater etter + mail_subject_wiki_content_updated: "Wiki-side '%{id}' er oppdatert" + label_wiki_content_added: Wiki-side opprettet + mail_subject_wiki_content_added: "Wiki-side '%{id}' er opprettet" + mail_body_wiki_content_added: Wiki-siden '%{id}' ble opprettet av %{author}. + label_wiki_content_updated: Wiki-side oppdatert + mail_body_wiki_content_updated: Wiki-siden '%{id}' ble oppdatert av %{author}. + permission_add_project: Opprett prosjekt + setting_new_project_user_role_id: Rolle gitt en ikke-administratorbruker som oppretter et prosjekt + label_view_all_revisions: Se alle revisjoner + label_tag: Tag + label_branch: Gren + error_no_tracker_in_project: Ingen sakstyper er tilknyttet dette prosjektet. Vennligst kontroller prosjektets innstillinger. + error_no_default_issue_status: Ingen standard saksstatus er angitt. Vennligst kontroller konfigurasjonen (Gå til "Administrasjon -> Saksstatuser"). + text_journal_changed: "%{label} endret fra %{old} til %{new}" + text_journal_set_to: "%{label} satt til %{value}" + text_journal_deleted: "%{label} slettet (%{old})" + label_group_plural: Grupper + label_group: Gruppe + label_group_new: Ny gruppe + label_time_entry_plural: Brukt tid + text_journal_added: "%{label} %{value} lagt til" + field_active: Aktiv + enumeration_system_activity: Systemaktivitet + permission_delete_issue_watchers: Slett overvåkere + version_status_closed: stengt + version_status_locked: låst + version_status_open: åpen + error_can_not_reopen_issue_on_closed_version: En sak tilknyttet en stengt versjon kan ikke gjenåpnes. + label_user_anonymous: Anonym + button_move_and_follow: Flytt og følg etter + setting_default_projects_modules: Standard aktiverte moduler for nye prosjekter + setting_gravatar_default: Standard Gravatar-bilde + field_sharing: Deling + label_version_sharing_hierarchy: Med prosjekt-hierarki + label_version_sharing_system: Med alle prosjekter + label_version_sharing_descendants: Med underprosjekter + label_version_sharing_tree: Med prosjekt-tre + label_version_sharing_none: Ikke delt + error_can_not_archive_project: Dette prosjektet kan ikke arkiveres + button_duplicate: Duplikat + button_copy_and_follow: Kopier og følg etter + label_copy_source: Kilde + setting_issue_done_ratio: Kalkuler ferdigstillingsprosent ut i fra + setting_issue_done_ratio_issue_status: Bruk saksstatuser + error_issue_done_ratios_not_updated: Ferdigstillingsprosent oppdateres ikke. + error_workflow_copy_target: Vennligst velg sakstype(r) og rolle(r) + setting_issue_done_ratio_issue_field: Bruk felt fra saker + label_copy_same_as_target: Samme som mål + label_copy_target: Mål + notice_issue_done_ratios_updated: Ferdigstillingsprosent oppdatert. + error_workflow_copy_source: Vennligst velg en kilde-sakstype eller rolle. + label_update_issue_done_ratios: Oppdatert ferdigstillingsprosent + setting_start_of_week: Start kalender på + permission_view_issues: Se på saker + label_display_used_statuses_only: Vis kun statuser som brukes av denne sakstypen + label_revision_id: Revision %{value} + label_api_access_key: API tilgangsnøkkel + label_api_access_key_created_on: API tilgangsnøkkel opprettet for %{value} siden + label_feeds_access_key: Atom tilgangsnøkkel + notice_api_access_key_reseted: Din API tilgangsnøkkel ble resatt. + setting_rest_api_enabled: Aktiver REST webservice + label_missing_api_access_key: Mangler en API tilgangsnøkkel + label_missing_feeds_access_key: Mangler en Atom tilgangsnøkkel + button_show: Vis + text_line_separated: Flere verdier er tillatt (en linje per verdi). + setting_mail_handler_body_delimiters: Avkort epost etter en av disse linjene + permission_add_subprojects: Opprett underprosjekt + label_subproject_new: Nytt underprosjekt + text_own_membership_delete_confirmation: |- + Du er i ferd med å fjerne noen eller alle rettigheter og vil kanskje ikke være i stand til å redigere dette prosjektet etterpå. + Er du sikker på at du vil fortsette? + label_close_versions: Steng fullførte versjoner + label_board_sticky: Fast + label_board_locked: Låst + permission_export_wiki_pages: Eksporter wiki-sider + setting_cache_formatted_text: Mellomlagre formattert tekst + permission_manage_project_activities: Administrere prosjektaktiviteter + error_unable_delete_issue_status: Kan ikke slette saksstatus + label_profile: Profil + permission_manage_subtasks: Administrere undersaker + field_parent_issue: Overordnet sak + label_subtask_plural: Undersaker + label_project_copy_notifications: Send epost-varslinger under prosjektkopiering + error_can_not_delete_custom_field: Kan ikke slette eget felt + error_unable_to_connect: Kunne ikke koble til (%{value}) + error_can_not_remove_role: Denne rollen er i bruk og kan ikke slettes. + error_can_not_delete_tracker: Denne sakstypen inneholder saker og kan ikke slettes. + field_principal: Principal + label_my_page_block: Min side felt + notice_failed_to_save_members: "Feil ved lagring av medlem(mer): %{errors}." + text_zoom_out: Zoom ut + text_zoom_in: Zoom inn + notice_unable_delete_time_entry: Kan ikke slette oppføring fra timeliste. + label_overall_spent_time: All tidsbruk + field_time_entries: Loggfør tid + project_module_gantt: Gantt + project_module_calendar: Kalender + button_edit_associated_wikipage: "Rediger tilhørende Wiki-side: %{page_title}" + field_text: Tekstfelt + label_user_mail_option_only_owner: Kun for ting jeg eier + setting_default_notification_option: Standardvalg for varslinger + label_user_mail_option_only_my_events: Kun for ting jeg overvåker eller er involvert i + label_user_mail_option_only_assigned: Kun for ting jeg er tildelt + label_user_mail_option_none: Ingen hendelser + field_member_of_group: Den tildeltes gruppe + field_assigned_to_role: Den tildeltes rolle + notice_not_authorized_archived_project: Prosjektet du forsøker å åpne er blitt arkivert. + label_principal_search: "Søk etter bruker eller gruppe:" + label_user_search: "Søk etter bruker:" + field_visible: Synlig + setting_emails_header: Eposthode + setting_commit_logtime_activity_id: Aktivitet for logget tid. + text_time_logged_by_changeset: Lagt til i endringssett %{value}. + setting_commit_logtime_enabled: Muliggjør loggføring av tid + notice_gantt_chart_truncated: Diagrammet ble avkortet fordi det overstiger det maksimale antall elementer som kan vises (%{max}) + setting_gantt_items_limit: Maksimalt antall elementer vist på gantt-diagrammet + field_warn_on_leaving_unsaved: Vis meg en advarsel når jeg forlater en side med ikke lagret tekst + text_warn_on_leaving_unsaved: Siden inneholder tekst som ikke er lagret og som vil bli tapt om du forlater denne siden. + label_my_queries: Mine egne spørringer + text_journal_changed_no_detail: "%{label} oppdatert" + label_news_comment_added: Kommentar lagt til en nyhet + button_expand_all: Utvid alle + button_collapse_all: Kollaps alle + label_additional_workflow_transitions_for_assignee: Ytterligere overganger tillatt når brukeren er den som er tildelt saken + label_additional_workflow_transitions_for_author: Ytterligere overganger tillatt når brukeren er den som har opprettet saken + label_bulk_edit_selected_time_entries: Masserediger valgte timeliste-oppføringer + text_time_entries_destroy_confirmation: Er du sikker på du vil slette de(n) valgte timeliste-oppføringen(e)? + label_role_anonymous: Anonym + label_role_non_member: Ikke medlem + label_issue_note_added: Notat lagt til + label_issue_status_updated: Status oppdatert + label_issue_priority_updated: Prioritet oppdatert + label_issues_visibility_own: Saker opprettet av eller tildelt brukeren + field_issues_visibility: Synlighet på saker + label_issues_visibility_all: Alle saker + permission_set_own_issues_private: Gjør egne saker offentlige eller private + field_is_private: Privat + permission_set_issues_private: Gjør saker offentlige eller private + label_issues_visibility_public: Alle ikke-private saker + text_issues_destroy_descendants_confirmation: Dette vil også slette %{count} undersak(er). + field_commit_logs_encoding: Tegnkoding for innsendingsmeldinger + field_scm_path_encoding: Koding av sti + text_scm_path_encoding_note: "Standard: UTF-8" + field_path_to_repository: Sti til depot + field_root_directory: Rotkatalog + field_cvs_module: Modul + field_cvsroot: CVSROOT + text_mercurial_repository_note: Lokalt depot (f.eks. /hgrepo, c:\hgrepo) + text_scm_command: Kommando + text_scm_command_version: Versjon + label_git_report_last_commit: Rapporter siste innsending for filer og kataloger + text_scm_config: Du kan konfigurere scm kommandoer i config/configuration.yml. Vennligst restart applikasjonen etter å ha redigert filen. + text_scm_command_not_available: Scm kommando er ikke tilgjengelig. Vennligst kontroller innstillingene i administrasjonspanelet. + + text_git_repository_note: Depot er bart og lokalt (f.eks. /gitrepo, c:\gitrepo) + + notice_issue_successful_create: Sak %{id} opprettet. + label_between: mellom + setting_issue_group_assignment: Tillat tildeling av saker til grupper + label_diff: diff + + description_query_sort_criteria_direction: Sorteringsretning + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Oppgi startdato + description_message_content: Meldingsinnhold + description_available_columns: Tilgjengelige kolonner + description_date_range_interval: Velg datointervall ved å spesifisere start- og sluttdato + description_issue_category_reassign: Choose issue category + description_search: Søkefelt + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Prosjekter + description_date_to: Oppgi sluttdato + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Velg ny overordnet side + description_selected_columns: Valgte kolonner + label_parent_revision: Overordnet + label_child_revision: Underordnet + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Bruk dagens dato som startdato for nye saker + button_edit_section: Rediger denne seksjonen + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: Alle kolonnene + button_export: Eksporter + label_export_options: "%{export_format} eksportvalg" + error_attachment_too_big: Filen overstiger maksimum filstørrelse (%{max_size}) og kan derfor ikke lastes opp + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 saker + one: 1 sak + other: "%{count} saker" + label_repository_new: Nytt depot + field_repository_is_default: Hoveddepot + label_copy_attachments: Kopier vedlegg + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Kun små bokstaver (a-z), tall, bindestrek (-) og "underscore" (_) er tillatt.
    Etter lagring er det ikke mulig å gjøre endringer. + field_multiple: Flere verdier + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: Saken ble oppdatert av en annen bruker mens du redigerte den. + text_issue_conflict_resolution_cancel: Forkast alle endringen mine og vis %{link} på nytt + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Din konto er ugjenkallelig slettet. + setting_unsubscribe: Tillat brukere å slette sin egen konto + button_delete_my_account: Slett kontoen min + text_account_destroy_confirmation: |- + Er du sikker på at du ønsker å fortsette? + Kontoen din vil bli ugjenkallelig slettet uten mulighet for å reaktiveres igjen. + error_session_expired: Økten har gått ut på tid. Vennligst logg på igjen. + text_session_expiration_settings: "Advarsel: ved å endre disse innstillingene kan aktive økter gå ut på tid, inkludert din egen." + setting_session_lifetime: Øktenes makslengde + setting_session_timeout: Økten er avsluttet på grunn av inaktivitet + label_session_expiration: Økten er avsluttet + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: kopiert til + label_copied_from: kopiert fra + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: alle + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Med underprosjekter + label_cross_project_tree: Med prosjekt-tre + label_cross_project_hierarchy: Med prosjekt-hierarki + label_cross_project_system: Med alle prosjekter + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Totalt + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b1/b1f1c9a5937a6d164b84ffb6897cf36e2e131495.svn-base --- a/.svn/pristine/b1/b1f1c9a5937a6d164b84ffb6897cf36e2e131495.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -

    <%= l(:label_bulk_edit_selected_time_entries) %>

    - -
      -<%= @time_entries.collect {|i| content_tag('li', - link_to(h("#{i.spent_on.strftime("%Y-%m-%d")} - #{i.project}: #{l(:label_f_hour_plural, :value => i.hours)}"), - { :action => 'edit', :id => i }) - )}.join("\n").html_safe %> -
    - -<%= form_tag(:action => 'bulk_update') do %> -<%= @time_entries.collect {|i| hidden_field_tag('ids[]', i.id)}.join.html_safe %> -
    -
    -

    - - <%= text_field :time_entry, :issue_id, :size => 6 %> -

    - -

    - - <%= text_field :time_entry, :spent_on, :size => 10 %><%= calendar_for('time_entry_spent_on') %> -

    - -

    - - <%= text_field :time_entry, :hours, :size => 6 %> -

    - - <% if @available_activities.any? %> -

    - - <%= select_tag('time_entry[activity_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@available_activities, :id, :name)) %> -

    - <% end %> - -

    - - <%= text_field(:time_entry, :comments, :size => 100) %> -

    - - <% @custom_fields.each do |custom_field| %> -

    <%= custom_field_tag_for_bulk_edit('time_entry', custom_field, @projects) %>

    - <% end %> - - <%= call_hook(:view_time_entries_bulk_edit_details_bottom, { :time_entries => @time_entries }) %> -
    -
    - -

    <%= submit_tag l(:button_submit) %>

    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b2/b26e895abc1075fe545ee5bc24c16e5071122bf7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b2/b26e895abc1075fe545ee5bc24c16e5071122bf7.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,80 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingIssueStatusesTest < ActionController::IntegrationTest + def test_issue_statuses + assert_routing( + { :method => 'get', :path => "/issue_statuses" }, + { :controller => 'issue_statuses', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/issue_statuses.xml" }, + { :controller => 'issue_statuses', :action => 'index', :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/issue_statuses" }, + { :controller => 'issue_statuses', :action => 'create' } + ) + assert_routing( + { :method => 'post', :path => "/issue_statuses.xml" }, + { :controller => 'issue_statuses', :action => 'create', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/issue_statuses/new" }, + { :controller => 'issue_statuses', :action => 'new' } + ) + assert_routing( + { :method => 'get', :path => "/issue_statuses/new.xml" }, + { :controller => 'issue_statuses', :action => 'new', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/issue_statuses/1/edit" }, + { :controller => 'issue_statuses', :action => 'edit', :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/issue_statuses/1" }, + { :controller => 'issue_statuses', :action => 'update', + :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/issue_statuses/1.xml" }, + { :controller => 'issue_statuses', :action => 'update', + :format => 'xml', :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/issue_statuses/1" }, + { :controller => 'issue_statuses', :action => 'destroy', + :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/issue_statuses/1.xml" }, + { :controller => 'issue_statuses', :action => 'destroy', + :format => 'xml', :id => '1' } + ) + assert_routing( + { :method => 'post', :path => "/issue_statuses/update_issue_done_ratio" }, + { :controller => 'issue_statuses', :action => 'update_issue_done_ratio' } + ) + assert_routing( + { :method => 'post', :path => "/issue_statuses/update_issue_done_ratio.xml" }, + { :controller => 'issue_statuses', :action => 'update_issue_done_ratio', + :format => 'xml' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b2/b276250e954ac8b398b0737a04404d7c1ed1df94.svn-base --- a/.svn/pristine/b2/b276250e954ac8b398b0737a04404d7c1ed1df94.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -<%= error_messages_for 'custom_field' %> - -
    -

    <%= f.text_field :name, :required => true %>

    -

    <%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %>

    - -<% if @custom_field.format_in? 'list', 'user', 'version' %> -

    <%= f.check_box :multiple, :disabled => @custom_field.multiple && !@custom_field.new_record? %>

    -<% end %> - -<% unless @custom_field.format_in? 'list', 'bool', 'date', 'user', 'version' %> -

    - <%= f.text_field :min_length, :size => 5, :no_label => true %> - - <%= f.text_field :max_length, :size => 5, :no_label => true %>
    (<%=l(:text_min_max_length_info)%>)

    -

    <%= f.text_field :regexp, :size => 50 %>
    (<%=l(:text_regexp_info)%>)

    -<% end %> - -<% if @custom_field.format_in? 'list' %> -

    - <%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %> - <%= l(:text_custom_field_possible_values_info) %> -

    -<% end %> - -<% unless @custom_field.format_in? 'user', 'version' %> -

    <%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : f.text_field(:default_value) %>

    -<% end %> - -<%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %> -
    - -
    -<% case @custom_field.class.name -when "IssueCustomField" %> - -
    <%=l(:label_tracker_plural)%> - <% Tracker.sorted.all.each do |tracker| %> - <%= check_box_tag "custom_field[tracker_ids][]", - tracker.id, - (@custom_field.trackers.include? tracker), - :id => "custom_field_tracker_ids_#{tracker.id}" %> - - <% end %> - <%= hidden_field_tag "custom_field[tracker_ids][]", '' %> -
    -   -

    <%= f.check_box :is_required %>

    -

    <%= f.check_box :is_for_all %>

    -

    <%= f.check_box :is_filter %>

    -

    <%= f.check_box :searchable %>

    - -<% when "UserCustomField" %> -

    <%= f.check_box :is_required %>

    -

    <%= f.check_box :visible %>

    -

    <%= f.check_box :editable %>

    -

    <%= f.check_box :is_filter %>

    - -<% when "ProjectCustomField" %> -

    <%= f.check_box :is_required %>

    -

    <%= f.check_box :visible %>

    -

    <%= f.check_box :searchable %>

    -

    <%= f.check_box :is_filter %>

    - -<% when "VersionCustomField" %> -

    <%= f.check_box :is_required %>

    -

    <%= f.check_box :is_filter %>

    - -<% when "GroupCustomField" %> -

    <%= f.check_box :is_required %>

    -

    <%= f.check_box :is_filter %>

    - -<% when "TimeEntryCustomField" %> -

    <%= f.check_box :is_required %>

    - -<% else %> -

    <%= f.check_box :is_required %>

    - -<% end %> -<%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %> -
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b2/b279683e40e793804fbad932586afda247f91b94.svn-base --- a/.svn/pristine/b2/b279683e40e793804fbad932586afda247f91b94.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -<%= error_messages_for 'user' %> - -
    - -
    -
    - <%=l(:label_information_plural)%> -

    <%= f.text_field :login, :required => true, :size => 25 %>

    -

    <%= f.text_field :firstname, :required => true %>

    -

    <%= f.text_field :lastname, :required => true %>

    -

    <%= f.text_field :mail, :required => true %>

    -

    <%= f.select :language, lang_options_for_select %>

    - <% if Setting.openid? %> -

    <%= f.text_field :identity_url %>

    - <% end %> - - <% @user.custom_field_values.each do |value| %> -

    <%= custom_field_tag_with_label :user, value %>

    - <% end %> - -

    <%= f.check_box :admin, :disabled => (@user == User.current) %>

    - <%= call_hook(:view_users_form, :user => @user, :form => f) %> -
    - -
    - <%=l(:label_authentication)%> - <% unless @auth_sources.empty? %> -

    <%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }), {}, :onchange => "if (this.value=='') {$('#password_fields').show();} else {$('#password_fields').hide();}" %>

    - <% end %> -
    -

    <%= f.password_field :password, :required => true, :size => 25 %> - <%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

    -

    <%= f.password_field :password_confirmation, :required => true, :size => 25 %>

    -
    -
    -
    - -
    -
    - <%=l(:field_mail_notification)%> - <%= render :partial => 'users/mail_notifications' %> -
    - -
    - <%=l(:label_preferences)%> - <%= render :partial => 'users/preferences' %> -
    -
    -
    -
    - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b2/b2d23f95d6214e578deb83fcc382ea31f387d199.svn-base --- a/.svn/pristine/b2/b2d23f95d6214e578deb83fcc382ea31f387d199.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -

    <%=l(:label_report_plural)%>

    - -

    <%=@report_title%>

    -<%= render :partial => 'details', :locals => { :data => @data, :field_name => @field, :rows => @rows } %> -
    -<%= link_to l(:button_back), :action => 'issue_report' %> - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b2/b2e850115d38f72b8bb02be2b155cf948264198f.svn-base --- a/.svn/pristine/b2/b2e850115d38f72b8bb02be2b155cf948264198f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingNewsTest < ActionController::IntegrationTest - def test_news_index - assert_routing( - { :method => 'get', :path => "/news" }, - { :controller => 'news', :action => 'index' } - ) - assert_routing( - { :method => 'get', :path => "/news.atom" }, - { :controller => 'news', :action => 'index', :format => 'atom' } - ) - assert_routing( - { :method => 'get', :path => "/news.xml" }, - { :controller => 'news', :action => 'index', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/news.json" }, - { :controller => 'news', :action => 'index', :format => 'json' } - ) - end - - def test_news - assert_routing( - { :method => 'get', :path => "/news/2" }, - { :controller => 'news', :action => 'show', :id => '2' } - ) - assert_routing( - { :method => 'get', :path => "/news/234" }, - { :controller => 'news', :action => 'show', :id => '234' } - ) - assert_routing( - { :method => 'get', :path => "/news/567/edit" }, - { :controller => 'news', :action => 'edit', :id => '567' } - ) - assert_routing( - { :method => 'put', :path => "/news/567" }, - { :controller => 'news', :action => 'update', :id => '567' } - ) - assert_routing( - { :method => 'delete', :path => "/news/567" }, - { :controller => 'news', :action => 'destroy', :id => '567' } - ) - end - - def test_news_scoped_under_project - assert_routing( - { :method => 'get', :path => "/projects/567/news" }, - { :controller => 'news', :action => 'index', :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/news.atom" }, - { :controller => 'news', :action => 'index', :format => 'atom', - :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/news.xml" }, - { :controller => 'news', :action => 'index', :format => 'xml', - :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/news.json" }, - { :controller => 'news', :action => 'index', :format => 'json', - :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/news/new" }, - { :controller => 'news', :action => 'new', :project_id => '567' } - ) - assert_routing( - { :method => 'post', :path => "/projects/567/news" }, - { :controller => 'news', :action => 'create', :project_id => '567' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b2/b2f284009205068a659d0ac9822336c00dcda837.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b2/b2f284009205068a659d0ac9822336c00dcda837.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,2 @@ +function jsToolBar(e){if(!document.createElement){return}if(!e){return}if(typeof document["selection"]=="undefined"&&typeof e["setSelectionRange"]=="undefined"){return}this.textarea=e;this.editor=document.createElement("div");this.editor.className="jstEditor";this.textarea.parentNode.insertBefore(this.editor,this.textarea);this.editor.appendChild(this.textarea);this.toolbar=document.createElement("div");this.toolbar.className="jstElements";this.editor.parentNode.insertBefore(this.toolbar,this.editor);if(this.editor.addEventListener&&navigator.appVersion.match(/\bMSIE\b/)){this.handle=document.createElement("div");this.handle.className="jstHandle";var t=this.resizeDragStart;var n=this;this.handle.addEventListener("mousedown",function(e){t.call(n,e)},false);window.addEventListener("unload",function(){var e=n.handle.parentNode.removeChild(n.handle);delete n.handle},false);this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling)}this.context=null;this.toolNodes={}}function jsButton(e,t,n,r){if(typeof jsToolBar.strings=="undefined"){this.title=e||null}else{this.title=jsToolBar.strings[e]||e||null}this.fn=t||function(){};this.scope=n||null;this.className=r||null}function jsSpace(e){this.id=e||null;this.width=null}function jsCombo(e,t,n,r,i){this.title=e||null;this.options=t||null;this.scope=n||null;this.fn=r||function(){};this.className=i||null}jsButton.prototype.draw=function(){if(!this.scope)return null;var e=document.createElement("button");e.setAttribute("type","button");e.tabIndex=200;if(this.className)e.className=this.className;e.title=this.title;var t=document.createElement("span");t.appendChild(document.createTextNode(this.title));e.appendChild(t);if(this.icon!=undefined){e.style.backgroundImage="url("+this.icon+")"}if(typeof this.fn=="function"){var n=this;e.onclick=function(){try{n.fn.apply(n.scope,arguments)}catch(e){}return false}}return e};jsSpace.prototype.draw=function(){var e=document.createElement("span");if(this.id)e.id=this.id;e.appendChild(document.createTextNode(String.fromCharCode(160)));e.className="jstSpacer";if(this.width)e.style.marginRight=this.width+"px";return e};jsCombo.prototype.draw=function(){if(!this.scope||!this.options)return null;var e=document.createElement("select");if(this.className)e.className=className;e.title=this.title;for(var t in this.options){var n=document.createElement("option");n.value=t;n.appendChild(document.createTextNode(this.options[t]));e.appendChild(n)}var r=this;e.onchange=function(){try{r.fn.call(r.scope,this.value)}catch(e){alert(e)}return false};return e};jsToolBar.prototype={base_url:"",mode:"wiki",elements:{},help_link:"",getMode:function(){return this.mode},setMode:function(e){this.mode=e||"wiki"},switchMode:function(e){e=e||"wiki";this.draw(e)},setHelpLink:function(e){this.help_link=e},button:function(e){var t=this.elements[e];if(typeof t.fn[this.mode]!="function")return null;var n=new jsButton(t.title,t.fn[this.mode],this,"jstb_"+e);if(t.icon!=undefined)n.icon=t.icon;return n},space:function(e){var t=new jsSpace(e);if(this.elements[e].width!==undefined)t.width=this.elements[e].width;return t},combo:function(e){var t=this.elements[e];var n=t[this.mode].list.length;if(typeof t[this.mode].fn!="function"||n==0){return null}else{var r={};for(var i=0;i $2")})}}};jsToolBar.prototype.elements.unbq={type:"button",title:"Unquote",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2")})}}};jsToolBar.prototype.elements.pre={type:"button",title:"Preformatted text",fn:{wiki:function(){this.encloseLineSelection("
    \n","\n
    ")}}};jsToolBar.prototype.elements.space4={type:"space"};jsToolBar.prototype.elements.link={type:"button",title:"Wiki link",fn:{wiki:function(){this.encloseSelection("[[","]]")}}};jsToolBar.prototype.elements.img={type:"button",title:"Image",fn:{wiki:function(){this.encloseSelection("!","!")}}};jsToolBar.prototype.elements.space5={type:"space"};jsToolBar.prototype.elements.help={type:"button",title:"Help",fn:{wiki:function(){window.open(this.help_link,"","resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes")}}} diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b3/b313db558eec0f33b4606021ff7dbfce33600948.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b3/b313db558eec0f33b4606021ff7dbfce33600948.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,186 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingWikiTest < ActionController::IntegrationTest + def test_wiki_matching + assert_routing( + { :method => 'get', :path => "/projects/567/wiki" }, + { :controller => 'wiki', :action => 'show', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/lalala" }, + { :controller => 'wiki', :action => 'show', :project_id => '567', + :id => 'lalala' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/lalala.pdf" }, + { :controller => 'wiki', :action => 'show', :project_id => '567', + :id => 'lalala', :format => 'pdf' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff" }, + { :controller => 'wiki', :action => 'diff', :project_id => '1', + :id => 'CookBook_documentation' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2" }, + { :controller => 'wiki', :action => 'show', :project_id => '1', + :id => 'CookBook_documentation', :version => '2' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/diff" }, + { :controller => 'wiki', :action => 'diff', :project_id => '1', + :id => 'CookBook_documentation', :version => '2' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/annotate" }, + { :controller => 'wiki', :action => 'annotate', :project_id => '1', + :id => 'CookBook_documentation', :version => '2' } + ) + # Make sure we don't route wiki page sub-uris to let plugins handle them + assert_raise(ActionController::RoutingError) do + assert_recognizes({}, {:method => 'get', :path => "/projects/1/wiki/CookBook_documentation/whatever"}) + end + end + + def test_wiki_misc + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/date_index" }, + { :controller => 'wiki', :action => 'date_index', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/export" }, + { :controller => 'wiki', :action => 'export', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/export.pdf" }, + { :controller => 'wiki', :action => 'export', :project_id => '567', :format => 'pdf' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/index" }, + { :controller => 'wiki', :action => 'index', :project_id => '567' } + ) + end + + def test_wiki_resources + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/my_page/edit" }, + { :controller => 'wiki', :action => 'edit', :project_id => '567', + :id => 'my_page' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/history" }, + { :controller => 'wiki', :action => 'history', :project_id => '1', + :id => 'CookBook_documentation' } + ) + assert_routing( + { :method => 'get', :path => "/projects/22/wiki/ladida/rename" }, + { :controller => 'wiki', :action => 'rename', :project_id => '22', + :id => 'ladida' } + ) + ["post", "put"].each do |method| + assert_routing( + { :method => method, :path => "/projects/567/wiki/CookBook_documentation/preview" }, + { :controller => 'wiki', :action => 'preview', :project_id => '567', + :id => 'CookBook_documentation' } + ) + end + assert_routing( + { :method => 'post', :path => "/projects/22/wiki/ladida/rename" }, + { :controller => 'wiki', :action => 'rename', :project_id => '22', + :id => 'ladida' } + ) + assert_routing( + { :method => 'post', :path => "/projects/22/wiki/ladida/protect" }, + { :controller => 'wiki', :action => 'protect', :project_id => '22', + :id => 'ladida' } + ) + assert_routing( + { :method => 'post', :path => "/projects/22/wiki/ladida/add_attachment" }, + { :controller => 'wiki', :action => 'add_attachment', :project_id => '22', + :id => 'ladida' } + ) + assert_routing( + { :method => 'put', :path => "/projects/567/wiki/my_page" }, + { :controller => 'wiki', :action => 'update', :project_id => '567', + :id => 'my_page' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/22/wiki/ladida" }, + { :controller => 'wiki', :action => 'destroy', :project_id => '22', + :id => 'ladida' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/22/wiki/ladida/3" }, + { :controller => 'wiki', :action => 'destroy_version', :project_id => '22', + :id => 'ladida', :version => '3' } + ) + end + + def test_api + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/my_page.xml" }, + { :controller => 'wiki', :action => 'show', :project_id => '567', + :id => 'my_page', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/my_page.json" }, + { :controller => 'wiki', :action => 'show', :project_id => '567', + :id => 'my_page', :format => 'json' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2.xml" }, + { :controller => 'wiki', :action => 'show', :project_id => '1', + :id => 'CookBook_documentation', :version => '2', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2.json" }, + { :controller => 'wiki', :action => 'show', :project_id => '1', + :id => 'CookBook_documentation', :version => '2', :format => 'json' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/index.xml" }, + { :controller => 'wiki', :action => 'index', :project_id => '567', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/index.json" }, + { :controller => 'wiki', :action => 'index', :project_id => '567', :format => 'json' } + ) + assert_routing( + { :method => 'put', :path => "/projects/567/wiki/my_page.xml" }, + { :controller => 'wiki', :action => 'update', :project_id => '567', + :id => 'my_page', :format => 'xml' } + ) + assert_routing( + { :method => 'put', :path => "/projects/567/wiki/my_page.json" }, + { :controller => 'wiki', :action => 'update', :project_id => '567', + :id => 'my_page', :format => 'json' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/567/wiki/my_page.xml" }, + { :controller => 'wiki', :action => 'destroy', :project_id => '567', + :id => 'my_page', :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/567/wiki/my_page.json" }, + { :controller => 'wiki', :action => 'destroy', :project_id => '567', + :id => 'my_page', :format => 'json' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b3/b314740f953a31b89fda7f481071584ad0011ffb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b3/b314740f953a31b89fda7f481071584ad0011ffb.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,52 @@ +# Default setup is given for MySQL with ruby1.9. If you're running Redmine +# with MySQL and ruby1.8, replace the adapter name with `mysql`. +# Examples for PostgreSQL, SQLite3 and SQL Server can be found at the end. +# Line indentation must be 2 spaces (no tabs). + +production: + adapter: mysql2 + database: redmine + host: localhost + username: root + password: "" + encoding: utf8 + +development: + adapter: mysql2 + database: redmine_development + host: localhost + username: root + password: "" + encoding: utf8 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: mysql2 + database: redmine_test + host: localhost + username: root + password: "" + encoding: utf8 + +# PostgreSQL configuration example +#production: +# adapter: postgresql +# database: redmine +# host: localhost +# username: postgres +# password: "postgres" + +# SQLite3 configuration example +#production: +# adapter: sqlite3 +# database: db/redmine.sqlite3 + +# SQL Server configuration example +#production: +# adapter: sqlserver +# database: redmine +# host: localhost +# username: jenkins +# password: jenkins diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b3/b316d8599200244900100c9290ba9b6619bf99d4.svn-base --- a/.svn/pristine/b3/b316d8599200244900100c9290ba9b6619bf99d4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine #:nodoc: - module CoreExtensions #:nodoc: - module Date #:nodoc: - # Custom date calculations - module Calculations - # Returns difference with specified date in months - def months_ago(date = self.class.today) - (date.year - self.year)*12 + (date.month - self.month) - end - - # Returns difference with specified date in weeks - def weeks_ago(date = self.class.today) - (date.year - self.year)*52 + (date.cweek - self.cweek) - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b3/b3902028311a1823b90c19ebd8dcafd42f37a54f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b3/b3902028311a1823b90c19ebd8dcafd42f37a54f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,29 @@ +<% if @project.issue_categories.any? %> + + + + + + + +<% for category in @project.issue_categories %> + <% unless category.new_record? %> + + + + + + <% end %> +<% end %> + +
    <%= l(:label_issue_category) %><%= l(:field_assigned_to) %>
    <%=h(category.name) %><%=h(category.assigned_to.name) if category.assigned_to %> + <% if User.current.allowed_to?(:manage_categories, @project) %> + <%= link_to l(:button_edit), edit_issue_category_path(category), :class => 'icon icon-edit' %> + <%= delete_link issue_category_path(category) %> + <% end %> +
    +<% else %> +

    <%= l(:label_no_data) %>

    +<% end %> + +

    <%= link_to l(:label_issue_category_new), new_project_issue_category_path(@project), :class => 'icon icon-add' if User.current.allowed_to?(:manage_categories, @project) %>

    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b4/b40d32ac3abc929736d90872f228037593cb276b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b4/b40d32ac3abc929736d90872f228037593cb276b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,12 @@ +<%= title [l(@enumeration.option_name), enumerations_path], @enumeration.name %> + +<%= form_tag({}, :method => :delete) do %> +
    +

    <%= l(:text_enumeration_destroy_question, @enumeration.objects_count) %>

    +

    +<%= select_tag 'reassign_to_id', (content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '') + options_from_collection_for_select(@enumerations, 'id', 'name')) %>

    +
    + +<%= submit_tag l(:button_apply) %> +<%= link_to l(:button_cancel), enumerations_path %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b4/b4511bc93db767ac95296554618bacc3be7a52cf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b4/b4511bc93db767ac95296554618bacc3be7a52cf.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,21 @@ +--- +repositories_001: + project_id: 1 + url: file:///<%= Rails.root %>/tmp/test/subversion_repository + id: 10 + root_url: file:///<%= Rails.root %>/tmp/test/subversion_repository + password: "" + login: "" + type: Repository::Subversion + is_default: true + created_on: 2006-07-19 19:04:21 +02:00 +repositories_002: + project_id: 2 + url: svn://localhost/test + id: 11 + root_url: svn://localhost + password: "" + login: "" + type: Repository::Subversion + is_default: true + created_on: 2006-07-19 19:04:21 +02:00 diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b4/b47c06d46b32769546485279d1a354df12670495.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b4/b47c06d46b32769546485279d1a354df12670495.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,32 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingCommentsTest < ActionController::IntegrationTest + def test_comments + assert_routing( + { :method => 'post', :path => "/news/567/comments" }, + { :controller => 'comments', :action => 'create', :id => '567' } + ) + assert_routing( + { :method => 'delete', :path => "/news/567/comments/15" }, + { :controller => 'comments', :action => 'destroy', :id => '567', + :comment_id => '15' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b4/b4b1f198c8e086edfc312f99671cb5c7dd8f466b.svn-base --- a/.svn/pristine/b4/b4b1f198c8e086edfc312f99671cb5c7dd8f466b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,377 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class RepositoryMercurialTest < ActiveSupport::TestCase - fixtures :projects - - include Redmine::I18n - - REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s - NUM_REV = 32 - CHAR_1_HEX = "\xc3\x9c" - - def setup - @project = Project.find(3) - @repository = Repository::Mercurial.create( - :project => @project, - :url => REPOSITORY_PATH, - :path_encoding => 'ISO-8859-1' - ) - assert @repository - @char_1 = CHAR_1_HEX.dup - @tag_char_1 = "tag-#{CHAR_1_HEX}-00" - @branch_char_0 = "branch-#{CHAR_1_HEX}-00" - @branch_char_1 = "branch-#{CHAR_1_HEX}-01" - if @char_1.respond_to?(:force_encoding) - @char_1.force_encoding('UTF-8') - @tag_char_1.force_encoding('UTF-8') - @branch_char_0.force_encoding('UTF-8') - @branch_char_1.force_encoding('UTF-8') - end - end - - - def test_blank_path_to_repository_error_message - set_language_if_valid 'en' - repo = Repository::Mercurial.new( - :project => @project, - :identifier => 'test' - ) - assert !repo.save - assert_include "Path to repository can't be blank", - repo.errors.full_messages - end - - def test_blank_path_to_repository_error_message_fr - set_language_if_valid 'fr' - str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)" - str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) - repo = Repository::Mercurial.new( - :project => @project, - :url => "", - :identifier => 'test', - :path_encoding => '' - ) - assert !repo.save - assert_include str, repo.errors.full_messages - end - - if File.directory?(REPOSITORY_PATH) - def test_scm_available - klass = Repository::Mercurial - assert_equal "Mercurial", klass.scm_name - assert klass.scm_adapter_class - assert_not_equal "", klass.scm_command - assert_equal true, klass.scm_available - end - - def test_entries - entries = @repository.entries - assert_kind_of Redmine::Scm::Adapters::Entries, entries - end - - def test_fetch_changesets_from_scratch - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - assert_equal 46, @repository.filechanges.count - assert_equal "Initial import.\nThe repository contains 3 files.", - @repository.changesets.find_by_revision('0').comments - end - - def test_fetch_changesets_incremental - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - # Remove changesets with revision > 2 - @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 2} - @project.reload - assert_equal 3, @repository.changesets.count - - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - end - - def test_isodatesec - # Template keyword 'isodatesec' supported in Mercurial 1.0 and higher - if @repository.scm.class.client_version_above?([1, 0]) - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - rev0_committed_on = Time.gm(2007, 12, 14, 9, 22, 52) - assert_equal @repository.changesets.find_by_revision('0').committed_on, rev0_committed_on - end - end - - def test_changeset_order_by_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - c0 = @repository.latest_changeset - c1 = @repository.changesets.find_by_revision('0') - # sorted by revision (id), not by date - assert c0.revision.to_i > c1.revision.to_i - assert c0.committed_on < c1.committed_on - end - - def test_latest_changesets - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - # with_limit - changesets = @repository.latest_changesets('', nil, 2) - assert_equal %w|31 30|, changesets.collect(&:revision) - - # with_filepath - changesets = @repository.latest_changesets( - '/sql_escape/percent%dir/percent%file1.txt', nil) - assert_equal %w|30 11 10 9|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets( - '/sql_escape/underscore_dir/understrike_file.txt', nil) - assert_equal %w|30 12 9|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets('README', nil) - assert_equal %w|31 30 28 17 8 6 1 0|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets('README','8') - assert_equal %w|8 6 1 0|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets('README','8', 2) - assert_equal %w|8 6|, changesets.collect(&:revision) - - # with_dirpath - changesets = @repository.latest_changesets('images', nil) - assert_equal %w|1 0|, changesets.collect(&:revision) - - path = 'sql_escape/percent%dir' - changesets = @repository.latest_changesets(path, nil) - assert_equal %w|30 13 11 10 9|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets(path, '11') - assert_equal %w|11 10 9|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets(path, '11', 2) - assert_equal %w|11 10|, changesets.collect(&:revision) - - path = 'sql_escape/underscore_dir' - changesets = @repository.latest_changesets(path, nil) - assert_equal %w|30 13 12 9|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets(path, '12') - assert_equal %w|12 9|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets(path, '12', 1) - assert_equal %w|12|, changesets.collect(&:revision) - - # tag - changesets = @repository.latest_changesets('', 'tag_test.00') - assert_equal %w|5 4 3 2 1 0|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets('', 'tag_test.00', 2) - assert_equal %w|5 4|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets('sources', 'tag_test.00') - assert_equal %w|4 3 2 1 0|, changesets.collect(&:revision) - - changesets = @repository.latest_changesets('sources', 'tag_test.00', 2) - assert_equal %w|4 3|, changesets.collect(&:revision) - - # named branch - if @repository.scm.class.client_version_above?([1, 6]) - changesets = @repository.latest_changesets('', @branch_char_1) - assert_equal %w|27 26|, changesets.collect(&:revision) - end - - changesets = @repository.latest_changesets("latin-1-dir/test-#{@char_1}-subdir", @branch_char_1) - assert_equal %w|27|, changesets.collect(&:revision) - end - - def test_copied_files - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - cs1 = @repository.changesets.find_by_revision('13') - assert_not_nil cs1 - c1 = cs1.filechanges.sort_by(&:path) - assert_equal 2, c1.size - - assert_equal 'A', c1[0].action - assert_equal '/sql_escape/percent%dir/percentfile1.txt', c1[0].path - assert_equal '/sql_escape/percent%dir/percent%file1.txt', c1[0].from_path - assert_equal '3a330eb32958', c1[0].from_revision - - assert_equal 'A', c1[1].action - assert_equal '/sql_escape/underscore_dir/understrike-file.txt', c1[1].path - assert_equal '/sql_escape/underscore_dir/understrike_file.txt', c1[1].from_path - - cs2 = @repository.changesets.find_by_revision('15') - c2 = cs2.filechanges - assert_equal 1, c2.size - - assert_equal 'A', c2[0].action - assert_equal '/README (1)[2]&,%.-3_4', c2[0].path - assert_equal '/README', c2[0].from_path - assert_equal '933ca60293d7', c2[0].from_revision - - cs3 = @repository.changesets.find_by_revision('19') - c3 = cs3.filechanges - assert_equal 1, c3.size - assert_equal 'A', c3[0].action - assert_equal "/latin-1-dir/test-#{@char_1}-1.txt", c3[0].path - assert_equal "/latin-1-dir/test-#{@char_1}.txt", c3[0].from_path - assert_equal '5d9891a1b425', c3[0].from_revision - end - - def test_find_changeset_by_name - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - %w|2 400bb8672109 400|.each do |r| - assert_equal '2', @repository.find_changeset_by_name(r).revision - end - end - - def test_find_changeset_by_invalid_name - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - assert_nil @repository.find_changeset_by_name('100000') - end - - def test_identifier - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - c = @repository.changesets.find_by_revision('2') - assert_equal c.scmid, c.identifier - end - - def test_format_identifier - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - c = @repository.changesets.find_by_revision('2') - assert_equal '2:400bb8672109', c.format_identifier - end - - def test_find_changeset_by_empty_name - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['', ' ', nil].each do |r| - assert_nil @repository.find_changeset_by_name(r) - end - end - - def test_parents - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - r1 = @repository.changesets.find_by_revision('0') - assert_equal [], r1.parents - r2 = @repository.changesets.find_by_revision('1') - assert_equal 1, r2.parents.length - assert_equal "0885933ad4f6", - r2.parents[0].identifier - r3 = @repository.changesets.find_by_revision('30') - assert_equal 2, r3.parents.length - r4 = [r3.parents[0].identifier, r3.parents[1].identifier].sort - assert_equal "3a330eb32958", r4[0] - assert_equal "a94b0528f24f", r4[1] - end - - def test_activities - c = Changeset.new(:repository => @repository, - :committed_on => Time.now, - :revision => '123', - :scmid => 'abc400bb8672', - :comments => 'test') - assert c.event_title.include?('123:abc400bb8672:') - assert_equal 'abc400bb8672', c.event_url[:rev] - end - - def test_previous - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - %w|28 3ae45e2d177d 3ae45|.each do |r1| - changeset = @repository.find_changeset_by_name(r1) - %w|27 7bbf4c738e71 7bbf|.each do |r2| - assert_equal @repository.find_changeset_by_name(r2), changeset.previous - end - end - end - - def test_previous_nil - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - %w|0 0885933ad4f6 0885|.each do |r1| - changeset = @repository.find_changeset_by_name(r1) - assert_nil changeset.previous - end - end - - def test_next - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - %w|27 7bbf4c738e71 7bbf|.each do |r2| - changeset = @repository.find_changeset_by_name(r2) - %w|28 3ae45e2d177d 3ae45|.each do |r1| - assert_equal @repository.find_changeset_by_name(r1), changeset.next - end - end - end - - def test_next_nil - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - %w|31 31eeee7395c8 31eee|.each do |r1| - changeset = @repository.find_changeset_by_name(r1) - assert_nil changeset.next - end - end - else - puts "Mercurial test repository NOT FOUND. Skipping unit tests !!!" - def test_fake; assert true end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b4/b4cbb97bf1cb3ceea1add1a6dc6bf753b13c3827.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b4/b4cbb97bf1cb3ceea1add1a6dc6bf753b13c3827.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,75 @@ +<%= title [l(:label_tracker_plural), trackers_path], l(:field_summary) %> + +<% if @trackers.any? %> + <%= form_tag fields_trackers_path do %> +
    + + + + + <% @trackers.each do |tracker| %> + + <% end %> + + + + + + + <% Tracker::CORE_FIELDS.each do |field| %> + "> + + <% @trackers.each do |tracker| %> + + <% end %> + + <% end %> + <% if @custom_fields.any? %> + + + + <% @custom_fields.each do |field| %> + "> + + <% @trackers.each do |tracker| %> + + <% end %> + + <% end %> + <% end %> + +
    + <%= tracker.name %> + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.tracker-#{tracker.id}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> +
    +   + <%= l(:field_core_fields) %> +
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.core-field-#{field}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + <%= l("field_#{field}".sub(/_id$/, '')) %> + + <%= check_box_tag "trackers[#{tracker.id}][core_fields][]", field, tracker.core_fields.include?(field), + :class => "tracker-#{tracker.id} core-field-#{field}" %> +
    +   + <%= l(:label_custom_field_plural) %> +
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.custom-field-#{field.id}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + <%= field.name %> + + <%= check_box_tag "trackers[#{tracker.id}][custom_field_ids][]", field.id, tracker.custom_fields.include?(field), + :class => "tracker-#{tracker.id} custom-field-#{field.id}" %> +
    +
    +

    <%= submit_tag l(:button_save) %>

    + <% @trackers.each do |tracker| %> + <%= hidden_field_tag "trackers[#{tracker.id}][core_fields][]", '' %> + <%= hidden_field_tag "trackers[#{tracker.id}][custom_field_ids][]", '' %> + <% end %> + <% end %> +<% else %> +

    <%= l(:label_no_data) %>

    +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b4/b4e6ed08b51fdf56d73a3e5d3b099dd3bd0e68ad.svn-base --- a/.svn/pristine/b4/b4e6ed08b51fdf56d73a3e5d3b099dd3bd0e68ad.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,197 +0,0 @@ ---- -roles_001: - name: Manager - id: 1 - builtin: 0 - issues_visibility: all - permissions: | - --- - - :add_project - - :edit_project - - :close_project - - :select_project_modules - - :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 - - :set_issues_private - - :set_notes_private - - :view_private_notes - - :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_related_issues - - :manage_project_activities - - position: 1 -roles_002: - name: Developer - id: 2 - builtin: 0 - issues_visibility: default - 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 - issues_visibility: default - 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 - issues_visibility: default - permissions: | - --- - - :view_issues - - :add_issues - - :edit_issues - - :manage_issue_relations - - :add_issue_notes - - :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 - issues_visibility: default - 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 - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b5/b50241919b7b81279d644e95a3326c2189de1da8.svn-base --- a/.svn/pristine/b5/b50241919b7b81279d644e95a3326c2189de1da8.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,425 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class RepositoriesSubversionControllerTest < ActionController::TestCase - tests RepositoriesController - - fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, - :repositories, :issues, :issue_statuses, :changesets, :changes, - :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers - - PRJ_ID = 3 - NUM_REV = 11 - - def setup - Setting.default_language = 'en' - User.current = nil - - @project = Project.find(PRJ_ID) - @repository = Repository::Subversion.create(:project => @project, - :url => self.class.subversion_repository_url) - assert @repository - end - - if repository_configured?('subversion') - def test_new - @request.session[:user_id] = 1 - @project.repository.destroy - get :new, :project_id => 'subproject1', :repository_scm => 'Subversion' - assert_response :success - assert_template 'new' - assert_kind_of Repository::Subversion, assigns(:repository) - assert assigns(:repository).new_record? - end - - def test_show - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_not_nil assigns(:changesets) - - entry = assigns(:entries).detect {|e| e.name == 'subversion_test'} - assert_not_nil entry - assert_equal 'dir', entry.kind - assert_select 'tr.dir a[href=/projects/subproject1/repository/show/subversion_test]' - - assert_tag 'input', :attributes => {:name => 'rev'} - assert_tag 'a', :content => 'Statistics' - assert_tag 'a', :content => 'Atom' - assert_tag :tag => 'a', - :attributes => {:href => '/projects/subproject1/repository'}, - :content => 'root' - end - - def test_show_non_default - Repository::Subversion.create(:project => @project, - :url => self.class.subversion_repository_url, - :is_default => false, :identifier => 'svn') - - get :show, :id => PRJ_ID, :repository_id => 'svn' - assert_response :success - assert_template 'show' - assert_select 'tr.dir a[href=/projects/subproject1/repository/svn/show/subversion_test]' - # Repository menu should link to the main repo - assert_select '#main-menu a[href=/projects/subproject1/repository]' - end - - def test_browse_directory - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :path => repository_path_hash(['subversion_test'])[:param] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal [ - '[folder_with_brackets]', 'folder', '.project', - 'helloworld.c', 'textfile.txt' - ], - assigns(:entries).collect(&:name) - entry = assigns(:entries).detect {|e| e.name == 'helloworld.c'} - assert_equal 'file', entry.kind - assert_equal 'subversion_test/helloworld.c', entry.path - assert_tag :a, :content => 'helloworld.c', :attributes => { :class => /text\-x\-c/ } - end - - def test_browse_at_given_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :path => repository_path_hash(['subversion_test'])[:param], - :rev => 4 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], - assigns(:entries).collect(&:name) - end - - def test_file_changes - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :changes, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'folder', 'helloworld.rb'])[:param] - assert_response :success - assert_template 'changes' - - changesets = assigns(:changesets) - assert_not_nil changesets - assert_equal %w(6 3 2), changesets.collect(&:revision) - - # svn properties displayed with svn >= 1.5 only - if Redmine::Scm::Adapters::SubversionAdapter.client_version_above?([1, 5, 0]) - assert_not_nil assigns(:properties) - assert_equal 'native', assigns(:properties)['svn:eol-style'] - assert_tag :ul, - :child => { :tag => 'li', - :child => { :tag => 'b', :content => 'svn:eol-style' }, - :child => { :tag => 'span', :content => 'native' } } - end - end - - def test_directory_changes - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :changes, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'folder'])[:param] - assert_response :success - assert_template 'changes' - - changesets = assigns(:changesets) - assert_not_nil changesets - assert_equal %w(10 9 7 6 5 2), changesets.collect(&:revision) - end - - def test_entry - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] - assert_response :success - assert_template 'entry' - end - - def test_entry_should_send_if_too_big - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - # no files in the test repo is larger than 1KB... - with_settings :file_max_size_displayed => 0 do - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] - assert_response :success - assert_equal 'attachment; filename="helloworld.c"', - @response.headers['Content-Disposition'] - end - end - - def test_entry_should_send_images_inline - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'folder', 'subfolder', 'rubylogo.gif'])[:param] - assert_response :success - assert_equal 'inline; filename="rubylogo.gif"', response.headers['Content-Disposition'] - end - - def test_entry_at_given_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'helloworld.rb'])[:param], - :rev => 2 - assert_response :success - assert_template 'entry' - # this line was removed in r3 and file was moved in r6 - assert_tag :tag => 'td', :attributes => { :class => /line-code/}, - :content => /Here's the code/ - end - - def test_entry_not_found - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'zzz.c'])[:param] - assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, - :content => /The entry or revision was not found in the repository/ - end - - def test_entry_download - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :raw, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] - assert_response :success - assert_equal 'attachment; filename="helloworld.c"', @response.headers['Content-Disposition'] - end - - def test_directory_entry - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'folder'])[:param] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entry) - assert_equal 'folder', assigns(:entry).name - end - - # TODO: this test needs fixtures. - def test_revision - get :revision, :id => 1, :rev => 2 - assert_response :success - assert_template 'revision' - - assert_select 'ul' do - assert_select 'li' do - # link to the entry at rev 2 - assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/2/entry/test/some/path/in/the/repo', :text => 'repo' - # link to partial diff - assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/2/diff/test/some/path/in/the/repo' - end - end - end - - def test_invalid_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :revision, :id => PRJ_ID, :rev => 'something_weird' - assert_response 404 - assert_error_tag :content => /was not found/ - end - - def test_invalid_revision_diff - get :diff, :id => PRJ_ID, :rev => '1', :rev_to => 'something_weird' - assert_response 404 - assert_error_tag :content => /was not found/ - end - - def test_empty_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['', ' ', nil].each do |r| - get :revision, :id => PRJ_ID, :rev => r - assert_response 404 - assert_error_tag :content => /was not found/ - end - end - - # TODO: this test needs fixtures. - def test_revision_with_repository_pointing_to_a_subdirectory - r = Project.find(1).repository - # Changes repository url to a subdirectory - r.update_attribute :url, (r.url + '/test/some') - - get :revision, :id => 1, :rev => 2 - assert_response :success - assert_template 'revision' - - assert_select 'ul' do - assert_select 'li' do - # link to the entry at rev 2 - assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/2/entry/path/in/the/repo', :text => 'repo' - # link to partial diff - assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/2/diff/path/in/the/repo' - end - end - end - - def test_revision_diff - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => 3, :type => dt - assert_response :success - assert_template 'diff' - assert_select 'h2', :text => /Revision 3/ - assert_select 'th.filename', :text => 'subversion_test/textfile.txt' - end - end - - def test_revision_diff_raw_format - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - get :diff, :id => PRJ_ID, :rev => 3, :format => 'diff' - assert_response :success - assert_equal 'text/x-patch', @response.content_type - assert_equal 'Index: subversion_test/textfile.txt', @response.body.split(/\r?\n/).first - end - - def test_directory_diff - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => 6, :rev_to => 2, - :path => repository_path_hash(['subversion_test', 'folder'])[:param], - :type => dt - assert_response :success - assert_template 'diff' - - diff = assigns(:diff) - assert_not_nil diff - # 2 files modified - assert_equal 2, Redmine::UnifiedDiff.new(diff).size - assert_tag :tag => 'h2', :content => /2:6/ - end - end - - def test_annotate - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :annotate, :id => PRJ_ID, - :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] - assert_response :success - assert_template 'annotate' - - assert_select 'tr' do - assert_select 'th.line-num', :text => '1' - assert_select 'td.revision', :text => '4' - assert_select 'td.author', :text => 'jp' - assert_select 'td', :text => /stdio.h/ - end - # Same revision - assert_select 'tr' do - assert_select 'th.line-num', :text => '2' - assert_select 'td.revision', :text => '' - assert_select 'td.author', :text => '' - end - end - - def test_annotate_at_given_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :annotate, :id => PRJ_ID, :rev => 8, - :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] - assert_response :success - assert_template 'annotate' - assert_tag :tag => 'h2', :content => /@ 8/ - end - - def test_destroy_valid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - assert_equal NUM_REV, @repository.changesets.count - - assert_difference 'Repository.count', -1 do - delete :destroy, :id => @repository.id - end - assert_response 302 - @project.reload - assert_nil @project.repository - end - - def test_destroy_invalid_repository - @request.session[:user_id] = 1 # admin - @project.repository.destroy - @repository = Repository::Subversion.create!( - :project => @project, - :url => "file:///invalid") - @repository.fetch_changesets - assert_equal 0, @repository.changesets.count - - assert_difference 'Repository.count', -1 do - delete :destroy, :id => @repository.id - end - assert_response 302 - @project.reload - assert_nil @project.repository - end - else - puts "Subversion test repository NOT FOUND. Skipping functional tests !!!" - def test_fake; assert true end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b5/b55f3d7a207d1ee11754ddcc8223772b20e5796a.svn-base --- a/.svn/pristine/b5/b55f3d7a207d1ee11754ddcc8223772b20e5796a.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -

    <%=l(:label_custom_field_plural)%>

    - -<%= render_tabs custom_fields_tabs %> - -<% html_title(l(:label_custom_field_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b5/b5727556fb665fb8b69db76bff48bd4ca73fc261.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b5/b5727556fb665fb8b69db76bff48bd4ca73fc261.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1122 @@ +pt-BR: + direction: ltr + date: + formats: + default: "%d/%m/%Y" + short: "%d de %B" + long: "%d de %B de %Y" + only_day: "%d" + + day_names: [Domingo, Segunda, Terça, Quarta, Quinta, Sexta, Sábado] + abbr_day_names: [Dom, Seg, Ter, Qua, Qui, Sex, Sáb] + month_names: [~, Janeiro, Fevereiro, Março, Abril, Maio, Junho, Julho, Agosto, Setembro, Outubro, Novembro, Dezembro] + abbr_month_names: [~, Jan, Fev, Mar, Abr, Mai, Jun, Jul, Ago, Set, Out, Nov, Dez] + order: + - :day + - :month + - :year + + time: + formats: + default: "%A, %d de %B de %Y, %H:%M h" + time: "%H:%M h" + short: "%d/%m, %H:%M h" + long: "%A, %d de %B de %Y, %H:%M h" + only_second: "%S" + datetime: + formats: + default: "%Y-%m-%dT%H:%M:%S%Z" + am: '' + pm: '' + + # date helper distancia em palavras + datetime: + distance_in_words: + half_a_minute: 'meio minuto' + less_than_x_seconds: + one: 'menos de 1 segundo' + other: 'menos de %{count} segundos' + + x_seconds: + one: '1 segundo' + other: '%{count} segundos' + + less_than_x_minutes: + one: 'menos de um minuto' + other: 'menos de %{count} minutos' + + x_minutes: + one: '1 minuto' + other: '%{count} minutos' + + about_x_hours: + one: 'aproximadamente 1 hora' + other: 'aproximadamente %{count} horas' + x_hours: + one: "1 hora" + other: "%{count} horas" + + x_days: + one: '1 dia' + other: '%{count} dias' + + about_x_months: + one: 'aproximadamente 1 mês' + other: 'aproximadamente %{count} meses' + + x_months: + one: '1 mês' + other: '%{count} meses' + + about_x_years: + one: 'aproximadamente 1 ano' + other: 'aproximadamente %{count} anos' + + over_x_years: + one: 'mais de 1 ano' + other: 'mais de %{count} anos' + almost_x_years: + one: "quase 1 ano" + other: "quase %{count} anos" + + # numeros + number: + format: + precision: 3 + separator: ',' + delimiter: '.' + currency: + format: + unit: 'R$' + precision: 2 + format: '%u %n' + separator: ',' + delimiter: '.' + percentage: + format: + delimiter: '.' + precision: + format: + delimiter: '.' + human: + format: + precision: 3 + delimiter: '.' + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + support: + array: + sentence_connector: "e" + skip_last_comma: true + + # Active Record + activerecord: + errors: + template: + header: + one: "modelo não pode ser salvo: 1 erro" + other: "modelo não pode ser salvo: %{count} erros." + body: "Por favor, verifique os seguintes campos:" + messages: + inclusion: "não está incluso na lista" + exclusion: "não está disponível" + invalid: "não é válido" + confirmation: "não está de acordo com a confirmação" + accepted: "precisa ser aceito" + empty: "não pode ficar vazio" + blank: "não pode ficar vazio" + too_long: "é muito longo (máximo: %{count} caracteres)" + too_short: "é muito curto (mínimo: %{count} caracteres)" + wrong_length: "deve ter %{count} caracteres" + taken: "não está disponível" + not_a_number: "não é um número" + greater_than: "precisa ser maior do que %{count}" + greater_than_or_equal_to: "precisa ser maior ou igual a %{count}" + equal_to: "precisa ser igual a %{count}" + less_than: "precisa ser menor do que %{count}" + less_than_or_equal_to: "precisa ser menor ou igual a %{count}" + odd: "precisa ser ímpar" + even: "precisa ser par" + greater_than_start_date: "deve ser maior que a data inicial" + not_same_project: "não pertence ao mesmo projeto" + circular_dependency: "Esta relação geraria uma dependência circular" + cant_link_an_issue_with_a_descendant: "Uma tarefa não pode ser relaciona a uma de suas subtarefas" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Selecione + + general_text_No: 'Não' + general_text_Yes: 'Sim' + general_text_no: 'não' + general_text_yes: 'sim' + general_lang_name: 'Português(Brasil)' + general_csv_separator: ';' + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Conta atualizada com sucesso. + notice_account_invalid_creditentials: Usuário ou senha inválido. + notice_account_password_updated: Senha alterada com sucesso. + notice_account_wrong_password: Senha inválida. + notice_account_register_done: Conta criada com sucesso. Para ativar sua conta, clique no link que lhe foi enviado por e-mail. + notice_account_unknown_email: Usuário desconhecido. + notice_can_t_change_password: Esta conta utiliza autenticação externa. Não é possível alterar a senha. + notice_account_lost_email_sent: Um e-mail com instruções para escolher uma nova senha foi enviado para você. + notice_account_activated: Sua conta foi ativada. Você pode acessá-la agora. + notice_successful_create: Criado com sucesso. + notice_successful_update: Alterado com sucesso. + notice_successful_delete: Excluído com sucesso. + notice_successful_connection: Conectado com sucesso. + notice_file_not_found: A página que você está tentando acessar não existe ou foi excluída. + notice_locking_conflict: Os dados foram atualizados por outro usuário. + notice_not_authorized: Você não está autorizado a acessar esta página. + notice_email_sent: "Um e-mail foi enviado para %{value}" + notice_email_error: "Ocorreu um erro ao enviar o e-mail (%{value})" + notice_feeds_access_key_reseted: Sua chave Atom foi reconfigurada. + notice_failed_to_save_issues: "Problema ao salvar %{count} tarefa(s) de %{total} selecionadas: %{ids}." + notice_no_issue_selected: "Nenhuma tarefa selecionada! Por favor, marque as tarefas que você deseja editar." + notice_account_pending: "Sua conta foi criada e está aguardando aprovação do administrador." + notice_default_data_loaded: Configuração padrão carregada com sucesso. + + error_can_t_load_default_data: "A configuração padrão não pode ser carregada: %{value}" + error_scm_not_found: "A entrada e/ou a revisão não existe no repositório." + error_scm_command_failed: "Ocorreu um erro ao tentar acessar o repositório: %{value}" + error_scm_annotate: "Esta entrada não existe ou não pode ser anotada." + error_issue_not_found_in_project: 'A tarefa não foi encontrada ou não pertence a este projeto' + error_no_tracker_in_project: 'Não há um tipo de tarefa associado a este projeto. Favor verificar as configurações do projeto.' + error_no_default_issue_status: 'A situação padrão para tarefa não está definida. Favor verificar sua configuração (Vá em "Administração -> Situação da tarefa").' + + mail_subject_lost_password: "Sua senha do %{value}." + mail_body_lost_password: 'Para mudar sua senha, clique no link abaixo:' + mail_subject_register: "Ativação de conta do %{value}." + mail_body_register: 'Para ativar sua conta, clique no link abaixo:' + mail_body_account_information_external: "Você pode usar sua conta do %{value} para entrar." + mail_body_account_information: Informações sobre sua conta + mail_subject_account_activation_request: "%{value} - Requisição de ativação de conta" + mail_body_account_activation_request: "Um novo usuário (%{value}) se registrou. A conta está aguardando sua aprovação:" + mail_subject_reminder: "%{count} tarefa(s) com data prevista para os próximos %{days} dias" + mail_body_reminder: "%{count} tarefa(s) para você com data prevista para os próximos %{days} dias:" + + + field_name: Nome + field_description: Descrição + field_summary: Resumo + field_is_required: Obrigatório + field_firstname: Nome + field_lastname: Sobrenome + field_mail: E-mail + field_filename: Arquivo + field_filesize: Tamanho + field_downloads: Downloads + field_author: Autor + field_created_on: Criado em + field_updated_on: Alterado em + field_field_format: Formato + field_is_for_all: Para todos os projetos + field_possible_values: Possíveis valores + field_regexp: Expressão regular + field_min_length: Tamanho mínimo + field_max_length: Tamanho máximo + field_value: Valor + field_category: Categoria + field_title: Título + field_project: Projeto + field_issue: Tarefa + field_status: Situação + field_notes: Notas + field_is_closed: Tarefa fechada + field_is_default: Situação padrão + field_tracker: Tipo + field_subject: Título + field_due_date: Data prevista + field_assigned_to: Atribuído para + field_priority: Prioridade + field_fixed_version: Versão + field_user: Usuário + field_role: Cargo + field_homepage: Página do projeto + field_is_public: Público + field_parent: Sub-projeto de + field_is_in_roadmap: Exibir no planejamento + field_login: Usuário + field_mail_notification: Notificações por e-mail + field_admin: Administrador + field_last_login_on: Última conexão + field_language: Idioma + field_effective_date: Data + field_password: Senha + field_new_password: Nova senha + field_password_confirmation: Confirmação + field_version: Versão + field_type: Tipo + field_host: Servidor + field_port: Porta + field_account: Conta + field_base_dn: DN Base + field_attr_login: Atributo para nome de usuário + field_attr_firstname: Atributo para nome + field_attr_lastname: Atributo para sobrenome + field_attr_mail: Atributo para e-mail + field_onthefly: Criar usuários dinamicamente ("on-the-fly") + field_start_date: Início + field_done_ratio: "% Terminado" + field_auth_source: Modo de autenticação + field_hide_mail: Ocultar meu e-mail + field_comments: Comentário + field_url: URL + field_start_page: Página inicial + field_subproject: Subprojeto + field_hours: Horas + field_activity: Atividade + field_spent_on: Data + field_identifier: Identificador + field_is_filter: É um filtro + field_issue_to: Tarefa relacionada + field_delay: Atraso + field_assignable: Tarefas podem ser atribuídas a este papel + field_redirect_existing_links: Redirecionar links existentes + field_estimated_hours: Tempo estimado + field_column_names: Colunas + field_time_zone: Fuso-horário + field_searchable: Pesquisável + field_default_value: Padrão + field_comments_sorting: Visualizar comentários + field_parent_title: Página pai + + setting_app_title: Título da aplicação + setting_app_subtitle: Subtítulo da aplicação + setting_welcome_text: Texto de boas-vindas + setting_default_language: Idioma padrão + setting_login_required: Exigir autenticação + setting_self_registration: Permitido Auto-registro + setting_attachment_max_size: Tamanho máximo do anexo + setting_issues_export_limit: Limite de exportação das tarefas + setting_mail_from: E-mail enviado de + setting_bcc_recipients: Enviar com cópia oculta (cco) + setting_host_name: Nome do Servidor e subdomínio + setting_text_formatting: Formatação do texto + setting_wiki_compression: Compactação de histórico do Wiki + setting_feeds_limit: Número de registros por Feed + setting_default_projects_public: Novos projetos são públicos por padrão + setting_autofetch_changesets: Obter commits automaticamente + setting_sys_api_enabled: Ativa WS para gerenciamento do repositório (SVN) + setting_commit_ref_keywords: Palavras-chave de referência + setting_commit_fix_keywords: Definição de palavras-chave + setting_autologin: Auto-login + setting_date_format: Formato da data + setting_time_format: Formato de hora + setting_cross_project_issue_relations: Permitir relacionar tarefas entre projetos + setting_issue_list_default_columns: Colunas padrão visíveis na lista de tarefas + setting_emails_footer: Rodapé do e-mail + setting_protocol: Protocolo + setting_per_page_options: Número de itens exibidos por página + setting_user_format: Formato de exibição de nome de usuário + setting_activity_days_default: Dias visualizados na atividade do projeto + setting_display_subprojects_issues: Visualizar tarefas dos subprojetos nos projetos principais por padrão + setting_enabled_scm: SCM habilitados + setting_mail_handler_api_enabled: Habilitar WS para e-mails de entrada + setting_mail_handler_api_key: Chave de API + setting_sequential_project_identifiers: Gerar identificadores sequenciais de projeto + + project_module_issue_tracking: Gerenciamento de Tarefas + project_module_time_tracking: Gerenciamento de tempo + project_module_news: Notícias + project_module_documents: Documentos + project_module_files: Arquivos + project_module_wiki: Wiki + project_module_repository: Repositório + project_module_boards: Fóruns + + label_user: Usuário + label_user_plural: Usuários + label_user_new: Novo usuário + label_project: Projeto + label_project_new: Novo projeto + label_project_plural: Projetos + label_x_projects: + zero: nenhum projeto + one: 1 projeto + other: "%{count} projetos" + label_project_all: Todos os projetos + label_project_latest: Últimos projetos + label_issue: Tarefa + label_issue_new: Nova tarefa + label_issue_plural: Tarefas + label_issue_view_all: Ver todas as tarefas + label_issues_by: "Tarefas por %{value}" + label_issue_added: Tarefa adicionada + label_issue_updated: Tarefa atualizada + label_issue_note_added: Nota adicionada + label_issue_status_updated: Situação atualizada + label_issue_priority_updated: Prioridade atualizada + label_document: Documento + label_document_new: Novo documento + label_document_plural: Documentos + label_document_added: Documento adicionado + label_role: Papel + label_role_plural: Papéis + label_role_new: Novo papel + label_role_and_permissions: Papéis e permissões + label_member: Membro + label_member_new: Novo membro + label_member_plural: Membros + label_tracker: Tipo de tarefa + label_tracker_plural: Tipos de tarefas + label_tracker_new: Novo tipo + label_workflow: Fluxo de trabalho + label_issue_status: Situação da tarefa + label_issue_status_plural: Situação das tarefas + label_issue_status_new: Nova situação + label_issue_category: Categoria da tarefa + label_issue_category_plural: Categorias das tarefas + label_issue_category_new: Nova categoria + label_custom_field: Campo personalizado + label_custom_field_plural: Campos personalizados + label_custom_field_new: Novo campo personalizado + label_enumerations: 'Tipos & Categorias' + label_enumeration_new: Novo + label_information: Informação + label_information_plural: Informações + label_please_login: Efetue o login + label_register: Cadastre-se + label_password_lost: Perdi minha senha + label_home: Página inicial + label_my_page: Minha página + label_my_account: Minha conta + label_my_projects: Meus projetos + label_administration: Administração + label_login: Entrar + label_logout: Sair + label_help: Ajuda + label_reported_issues: Tarefas reportadas + label_assigned_to_me_issues: Minhas tarefas + label_last_login: Última conexão + label_registered_on: Registrado em + label_activity: Atividade + label_overall_activity: Atividades gerais + label_new: Novo + label_logged_as: "Acessando como:" + label_environment: Ambiente + label_authentication: Autenticação + label_auth_source: Modo de autenticação + label_auth_source_new: Novo modo de autenticação + label_auth_source_plural: Modos de autenticação + label_subproject_plural: Subprojetos + label_and_its_subprojects: "%{value} e seus subprojetos" + label_min_max_length: Tamanho mín-máx + label_list: Lista + label_date: Data + label_integer: Inteiro + label_float: Decimal + label_boolean: Boleano + label_string: Texto + label_text: Texto longo + label_attribute: Atributo + label_attribute_plural: Atributos + label_no_data: Nenhuma informação disponível + label_change_status: Alterar situação + label_history: Histórico + label_attachment: Arquivo + label_attachment_new: Novo arquivo + label_attachment_delete: Excluir arquivo + label_attachment_plural: Arquivos + label_file_added: Arquivo adicionado + label_report: Relatório + label_report_plural: Relatório + label_news: Notícia + label_news_new: Adicionar notícia + label_news_plural: Notícias + label_news_latest: Últimas notícias + label_news_view_all: Ver todas as notícias + label_news_added: Notícia adicionada + label_settings: Configurações + label_overview: Visão geral + label_version: Versão + label_version_new: Nova versão + label_version_plural: Versões + label_confirmation: Confirmação + label_export_to: Exportar para + label_read: Ler... + label_public_projects: Projetos públicos + label_open_issues: Aberta + label_open_issues_plural: Abertas + label_closed_issues: Fechada + label_closed_issues_plural: Fechadas + label_x_open_issues_abbr_on_total: + zero: 0 aberta / %{total} + one: 1 aberta / %{total} + other: "%{count} abertas / %{total}" + label_x_open_issues_abbr: + zero: 0 aberta + one: 1 aberta + other: "%{count} abertas" + label_x_closed_issues_abbr: + zero: 0 fechada + one: 1 fechada + other: "%{count} fechadas" + label_total: Total + label_permissions: Permissões + label_current_status: Situação atual + label_new_statuses_allowed: Nova situação permitida + label_all: todos + label_none: nenhum + label_nobody: ninguém + label_next: Próximo + label_previous: Anterior + label_used_by: Usado por + label_details: Detalhes + label_add_note: Adicionar nota + label_per_page: Por página + label_calendar: Calendário + label_months_from: meses a partir de + label_gantt: Gantt + label_internal: Interno + label_last_changes: "últimas %{count} alterações" + label_change_view_all: Mostrar todas as alterações + label_personalize_page: Personalizar esta página + label_comment: Comentário + label_comment_plural: Comentários + label_x_comments: + zero: nenhum comentário + one: 1 comentário + other: "%{count} comentários" + label_comment_add: Adicionar comentário + label_comment_added: Comentário adicionado + label_comment_delete: Excluir comentário + label_query: Consulta personalizada + label_query_plural: Consultas personalizadas + label_query_new: Nova consulta + label_filter_add: Adicionar filtro + label_filter_plural: Filtros + label_equals: igual a + label_not_equals: diferente de + label_in_less_than: maior que + label_in_more_than: menor que + label_in: em + label_today: hoje + label_all_time: tudo + label_yesterday: ontem + label_this_week: esta semana + label_last_week: última semana + label_last_n_days: "últimos %{count} dias" + label_this_month: este mês + label_last_month: último mês + label_this_year: este ano + label_date_range: Período + label_less_than_ago: menos de + label_more_than_ago: mais de + label_ago: dias atrás + label_contains: contém + label_not_contains: não contém + label_day_plural: dias + label_repository: Repositório + label_repository_plural: Repositórios + label_browse: Procurar + label_revision: Revisão + label_revision_plural: Revisões + label_associated_revisions: Revisões associadas + label_added: adicionada + label_modified: alterada + label_deleted: excluída + label_latest_revision: Última revisão + label_latest_revision_plural: Últimas revisões + label_view_revisions: Ver revisões + label_max_size: Tamanho máximo + label_sort_highest: Mover para o início + label_sort_higher: Mover para cima + label_sort_lower: Mover para baixo + label_sort_lowest: Mover para o fim + label_roadmap: Planejamento + label_roadmap_due_in: "Previsto para %{value}" + label_roadmap_overdue: "%{value} atrasado" + label_roadmap_no_issues: Sem tarefas para esta versão + label_search: Busca + label_result_plural: Resultados + label_all_words: Todas as palavras + label_wiki: Wiki + label_wiki_edit: Editar Wiki + label_wiki_edit_plural: Edições Wiki + label_wiki_page: Página Wiki + label_wiki_page_plural: páginas Wiki + label_index_by_title: Ãndice por título + label_index_by_date: Ãndice por data + label_current_version: Versão atual + label_preview: Pré-visualizar + label_feed_plural: Feeds + label_changes_details: Detalhes de todas as alterações + label_issue_tracking: Tarefas + label_spent_time: Tempo gasto + label_f_hour: "%{value} hora" + label_f_hour_plural: "%{value} horas" + label_time_tracking: Registro de horas + label_change_plural: Alterações + label_statistics: Estatísticas + label_commits_per_month: Commits por mês + label_commits_per_author: Commits por autor + label_view_diff: Ver diferenças + label_diff_inline: em linha + label_diff_side_by_side: lado a lado + label_options: Opções + label_copy_workflow_from: Copiar fluxo de trabalho de + label_permissions_report: Relatório de permissões + label_watched_issues: Tarefas observadas + label_related_issues: Tarefas relacionadas + label_applied_status: Situação alterada + label_loading: Carregando... + label_relation_new: Nova relação + label_relation_delete: Excluir relação + label_relates_to: relacionado a + label_duplicates: duplica + label_duplicated_by: duplicado por + label_blocks: bloqueia + label_blocked_by: bloqueado por + label_precedes: precede + label_follows: segue + label_end_to_start: fim para o início + label_end_to_end: fim para fim + label_start_to_start: início para início + label_start_to_end: início para fim + label_stay_logged_in: Permanecer logado + label_disabled: desabilitado + label_show_completed_versions: Exibir versões completas + label_me: mim + label_board: Fórum + label_board_new: Novo fórum + label_board_plural: Fóruns + label_topic_plural: Tópicos + label_message_plural: Mensagens + label_message_last: Última mensagem + label_message_new: Nova mensagem + label_message_posted: Mensagem enviada + label_reply_plural: Respostas + label_send_information: Enviar informação da nova conta para o usuário + label_year: Ano + label_month: Mês + label_week: Semana + label_date_from: De + label_date_to: Para + label_language_based: Com base no idioma do usuário + label_sort_by: "Ordenar por %{value}" + label_send_test_email: Enviar um e-mail de teste + label_feeds_access_key_created_on: "chave de acesso Atom criada %{value} atrás" + label_module_plural: Módulos + label_added_time_by: "Adicionado por %{author} %{age} atrás" + label_updated_time: "Atualizado %{value} atrás" + label_jump_to_a_project: Ir para o projeto... + label_file_plural: Arquivos + label_changeset_plural: Conjunto de alterações + label_default_columns: Colunas padrão + label_no_change_option: (Sem alteração) + label_bulk_edit_selected_issues: Edição em massa das tarefas selecionadas. + label_theme: Tema + label_default: Padrão + label_search_titles_only: Pesquisar somente títulos + label_user_mail_option_all: "Para qualquer evento em todos os meus projetos" + label_user_mail_option_selected: "Para qualquer evento somente no(s) projeto(s) selecionado(s)..." + label_user_mail_no_self_notified: "Eu não quero ser notificado de minhas próprias modificações" + label_registration_activation_by_email: ativação de conta por e-mail + label_registration_manual_activation: ativação manual de conta + label_registration_automatic_activation: ativação automática de conta + label_display_per_page: "Por página: %{value}" + label_age: Idade + label_change_properties: Alterar propriedades + label_general: Geral + label_more: Mais + label_scm: 'Controle de versão:' + label_plugins: Plugins + label_ldap_authentication: Autenticação LDAP + label_downloads_abbr: D/L + label_optional_description: Descrição opcional + label_add_another_file: Adicionar outro arquivo + label_preferences: Preferências + label_chronological_order: Em ordem cronológica + label_reverse_chronological_order: Em ordem cronológica inversa + label_planning: Planejamento + label_incoming_emails: E-mails recebidos + label_generate_key: Gerar uma chave + label_issue_watchers: Observadores + + button_login: Entrar + button_submit: Enviar + button_save: Salvar + button_check_all: Marcar todos + button_uncheck_all: Desmarcar todos + button_delete: Excluir + button_create: Criar + button_test: Testar + button_edit: Editar + button_add: Adicionar + button_change: Alterar + button_apply: Aplicar + button_clear: Limpar + button_lock: Bloquear + button_unlock: Desbloquear + button_download: Baixar + button_list: Listar + button_view: Ver + button_move: Mover + button_back: Voltar + button_cancel: Cancelar + button_activate: Ativar + button_sort: Ordenar + button_log_time: Tempo de trabalho + button_rollback: Voltar para esta versão + button_watch: Observar + button_unwatch: Parar de observar + button_reply: Responder + button_archive: Arquivar + button_unarchive: Desarquivar + button_reset: Redefinir + button_rename: Renomear + button_change_password: Alterar senha + button_copy: Copiar + button_annotate: Anotar + button_update: Atualizar + button_configure: Configurar + button_quote: Responder + + status_active: ativo + status_registered: registrado + status_locked: bloqueado + + text_select_mail_notifications: Ações a serem notificadas por e-mail + text_regexp_info: ex. ^[A-Z0-9]+$ + text_min_max_length_info: 0 = sem restrição + text_project_destroy_confirmation: Você tem certeza que deseja excluir este projeto e todos os dados relacionados? + text_subprojects_destroy_warning: "Seu(s) subprojeto(s): %{value} também serão excluídos." + text_workflow_edit: Selecione um papel e um tipo de tarefa para editar o fluxo de trabalho + text_are_you_sure: Você tem certeza? + text_tip_issue_begin_day: tarefa inicia neste dia + text_tip_issue_end_day: tarefa termina neste dia + text_tip_issue_begin_end_day: tarefa inicia e termina neste dia + text_caracters_maximum: "máximo %{count} caracteres" + text_caracters_minimum: "deve ter ao menos %{count} caracteres." + text_length_between: "deve ter entre %{min} e %{max} caracteres." + text_tracker_no_workflow: Sem fluxo de trabalho definido para este tipo. + text_unallowed_characters: Caracteres não permitidos + text_comma_separated: Múltiplos valores são permitidos (separados por vírgula). + text_issues_ref_in_commit_messages: Referenciando tarefas nas mensagens de commit + text_issue_added: "Tarefa %{id} incluída (por %{author})." + text_issue_updated: "Tarefa %{id} alterada (por %{author})." + text_wiki_destroy_confirmation: Você tem certeza que deseja excluir este wiki e TODO o seu conteúdo? + text_issue_category_destroy_question: "Algumas tarefas (%{count}) estão atribuídas a esta categoria. O que você deseja fazer?" + text_issue_category_destroy_assignments: Remover atribuições da categoria + text_issue_category_reassign_to: Redefinir tarefas para esta categoria + text_user_mail_option: "Para projetos (não selecionados), você somente receberá notificações sobre o que você está observando ou está envolvido (ex. tarefas das quais você é o autor ou que estão atribuídas a você)" + text_no_configuration_data: "Os Papéis, tipos de tarefas, situação de tarefas e fluxos de trabalho não foram configurados ainda.\nÉ altamente recomendado carregar as configurações padrão. Você poderá modificar estas configurações assim que carregadas." + text_load_default_configuration: Carregar a configuração padrão + text_status_changed_by_changeset: "Aplicado no conjunto de alterações %{value}." + text_issues_destroy_confirmation: 'Você tem certeza que deseja excluir a(s) tarefa(s) selecionada(s)?' + text_select_project_modules: 'Selecione módulos para habilitar para este projeto:' + text_default_administrator_account_changed: Conta padrão do administrador alterada + text_file_repository_writable: Repositório com permissão de escrita + text_rmagick_available: RMagick disponível (opcional) + text_destroy_time_entries_question: "%{hours} horas de trabalho foram registradas nas tarefas que você está excluindo. O que você deseja fazer?" + text_destroy_time_entries: Excluir horas de trabalho + text_assign_time_entries_to_project: Atribuir estas horas de trabalho para outro projeto + text_reassign_time_entries: 'Atribuir horas reportadas para esta tarefa:' + text_user_wrote: "%{value} escreveu:" + text_enumeration_destroy_question: "%{count} objetos estão atribuídos a este valor." + text_enumeration_category_reassign_to: 'Reatribuí-los ao valor:' + text_email_delivery_not_configured: "O envio de e-mail não está configurado, e as notificações estão inativas.\nConfigure seu servidor SMTP no arquivo config/configuration.yml e reinicie a aplicação para ativá-las." + + default_role_manager: Gerente + default_role_developer: Desenvolvedor + default_role_reporter: Informante + default_tracker_bug: Defeito + default_tracker_feature: Funcionalidade + default_tracker_support: Suporte + default_issue_status_new: Nova + default_issue_status_in_progress: Em andamento + default_issue_status_resolved: Resolvida + default_issue_status_feedback: Feedback + default_issue_status_closed: Fechada + default_issue_status_rejected: Rejeitada + default_doc_category_user: Documentação do usuário + default_doc_category_tech: Documentação técnica + default_priority_low: Baixa + default_priority_normal: Normal + default_priority_high: Alta + default_priority_urgent: Urgente + default_priority_immediate: Imediata + default_activity_design: Design + default_activity_development: Desenvolvimento + + enumeration_issue_priorities: Prioridade das tarefas + enumeration_doc_categories: Categorias de documento + enumeration_activities: Atividades (registro de horas) + notice_unable_delete_version: Não foi possível excluir a versão + label_renamed: renomeado + label_copied: copiado + setting_plain_text_mail: Usar mensagem sem formatação HTML + permission_view_files: Ver arquivos + permission_edit_issues: Editar tarefas + permission_edit_own_time_entries: Editar o próprio tempo de trabalho + permission_manage_public_queries: Gerenciar consultas públicas + permission_add_issues: Adicionar tarefas + permission_log_time: Adicionar tempo gasto + permission_view_changesets: Ver conjunto de alterações + permission_view_time_entries: Ver tempo gasto + permission_manage_versions: Gerenciar versões + permission_manage_wiki: Gerenciar wiki + permission_manage_categories: Gerenciar categorias de tarefas + permission_protect_wiki_pages: Proteger páginas wiki + permission_comment_news: Comentar notícias + permission_delete_messages: Excluir mensagens + permission_select_project_modules: Selecionar módulos de projeto + permission_edit_wiki_pages: Editar páginas wiki + permission_add_issue_watchers: Adicionar observadores + permission_view_gantt: Ver gráfico gantt + permission_move_issues: Mover tarefas + permission_manage_issue_relations: Gerenciar relacionamentos de tarefas + permission_delete_wiki_pages: Excluir páginas wiki + permission_manage_boards: Gerenciar fóruns + permission_delete_wiki_pages_attachments: Excluir anexos + permission_view_wiki_edits: Ver histórico do wiki + permission_add_messages: Postar mensagens + permission_view_messages: Ver mensagens + permission_manage_files: Gerenciar arquivos + permission_edit_issue_notes: Editar notas + permission_manage_news: Gerenciar notícias + permission_view_calendar: Ver calendário + permission_manage_members: Gerenciar membros + permission_edit_messages: Editar mensagens + permission_delete_issues: Excluir tarefas + permission_view_issue_watchers: Ver lista de observadores + permission_manage_repository: Gerenciar repositório + permission_commit_access: Acesso do commit + permission_browse_repository: Pesquisar repositório + permission_view_documents: Ver documentos + permission_edit_project: Editar projeto + permission_add_issue_notes: Adicionar notas + permission_save_queries: Salvar consultas + permission_view_wiki_pages: Ver wiki + permission_rename_wiki_pages: Renomear páginas wiki + permission_edit_time_entries: Editar tempo gasto + permission_edit_own_issue_notes: Editar suas próprias notas + setting_gravatar_enabled: Usar ícones do Gravatar + label_example: Exemplo + text_repository_usernames_mapping: "Seleciona ou atualiza os usuários do Redmine mapeando para cada usuário encontrado no log do repositório.\nUsuários com o mesmo login ou e-mail no Redmine e no repositório serão mapeados automaticamente." + permission_edit_own_messages: Editar próprias mensagens + permission_delete_own_messages: Excluir próprias mensagens + label_user_activity: "Atividade de %{value}" + label_updated_time_by: "Atualizado por %{author} há %{age}" + text_diff_truncated: '... Este diff foi truncado porque excede o tamanho máximo que pode ser exibido.' + setting_diff_max_lines_displayed: Número máximo de linhas exibidas no diff + text_plugin_assets_writable: Diretório de plugins gravável + warning_attachments_not_saved: "%{count} arquivo(s) não puderam ser salvo(s)." + button_create_and_continue: Criar e continuar + text_custom_field_possible_values_info: 'Uma linha para cada valor' + label_display: Exibição + field_editable: Editável + setting_repository_log_display_limit: Número máximo de revisões exibidas no arquivo de log + setting_file_max_size_displayed: Tamanho máximo dos arquivos textos exibidos em linha + field_identity_urler: Observador + setting_openid: Permitir Login e Registro via OpenID + field_identity_url: OpenID URL + label_login_with_open_id_option: ou use o OpenID + field_content: Conteúdo + label_descending: Descendente + label_sort: Ordenar + label_ascending: Ascendente + label_date_from_to: De %{start} até %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Esta página tem %{descendants} página(s) filha(s) e descendente(s). O que você quer fazer? + text_wiki_page_reassign_children: Reatribuir páginas filhas para esta página pai + text_wiki_page_nullify_children: Manter as páginas filhas como páginas raízes + text_wiki_page_destroy_children: Excluir páginas filhas e todas suas descendentes + setting_password_min_length: Comprimento mínimo para senhas + field_group_by: Agrupar por + mail_subject_wiki_content_updated: "A página wiki '%{id}' foi atualizada" + label_wiki_content_added: Página wiki adicionada + mail_subject_wiki_content_added: "A página wiki '%{id}' foi adicionada" + mail_body_wiki_content_added: A página wiki '%{id}' foi adicionada por %{author}. + label_wiki_content_updated: Página wiki atualizada + mail_body_wiki_content_updated: A página wiki '%{id}' foi atualizada por %{author}. + permission_add_project: Criar projeto + setting_new_project_user_role_id: Papel atribuído a um usuário não-administrador que cria um projeto + label_view_all_revisions: Ver todas as revisões + label_tag: Tag + label_branch: Branch + text_journal_changed: "%{label} alterado de %{old} para %{new}" + text_journal_set_to: "%{label} ajustado para %{value}" + text_journal_deleted: "%{label} excluído (%{old})" + label_group_plural: Grupos + label_group: Grupo + label_group_new: Novo grupo + label_time_entry_plural: Tempos gastos + text_journal_added: "%{label} %{value} adicionado" + field_active: Ativo + enumeration_system_activity: Atividade do sistema + permission_delete_issue_watchers: Excluir observadores + version_status_closed: fechado + version_status_locked: bloqueado + version_status_open: aberto + error_can_not_reopen_issue_on_closed_version: Uma tarefa atribuída a uma versão fechada não pode ser reaberta + label_user_anonymous: Anônimo + button_move_and_follow: Mover e seguir + setting_default_projects_modules: Módulos habilitados por padrão para novos projetos + setting_gravatar_default: Imagem-padrão do Gravatar + field_sharing: Compartilhamento + label_version_sharing_hierarchy: Com a hierarquia do projeto + label_version_sharing_system: Com todos os projetos + label_version_sharing_descendants: Com sub-projetos + label_version_sharing_tree: Com a árvore do projeto + label_version_sharing_none: Sem compartilhamento + error_can_not_archive_project: Este projeto não pode ser arquivado + button_duplicate: Duplicar + button_copy_and_follow: Copiar e seguir + label_copy_source: Origem + setting_issue_done_ratio: Calcular o percentual de conclusão da tarefa + setting_issue_done_ratio_issue_status: Usar a situação da tarefa + error_issue_done_ratios_not_updated: O percentual de conclusão das tarefas não foi atualizado. + error_workflow_copy_target: Por favor, selecione os tipos de tarefa e os papéis alvo + setting_issue_done_ratio_issue_field: Use o campo da tarefa + label_copy_same_as_target: Mesmo alvo + label_copy_target: Alvo + notice_issue_done_ratios_updated: Percentual de conclusão atualizados. + error_workflow_copy_source: Por favor, selecione um tipo de tarefa e papel de origem + label_update_issue_done_ratios: Atualizar percentual de conclusão das tarefas + setting_start_of_week: Início da semana + field_watcher: Observador + permission_view_issues: Ver tarefas + label_display_used_statuses_only: Somente exibir situações que são usadas por este tipo de tarefa + label_revision_id: Revisão %{value} + label_api_access_key: Chave de acesso a API + button_show: Exibir + label_api_access_key_created_on: Chave de acesso a API criado a %{value} atrás + label_feeds_access_key: Chave de acesso ao Atom + notice_api_access_key_reseted: Sua chave de acesso a API foi redefinida. + setting_rest_api_enabled: Habilitar a api REST + label_missing_api_access_key: Chave de acesso a API faltando + label_missing_feeds_access_key: Chave de acesso ao Atom faltando + text_line_separated: Múltiplos valores permitidos (uma linha para cada valor). + setting_mail_handler_body_delimiters: Truncar e-mails após uma destas linhas + permission_add_subprojects: Criar subprojetos + label_subproject_new: Novo subprojeto + text_own_membership_delete_confirmation: |- + Você irá excluir algumas de suas próprias permissões e não estará mais apto a editar este projeto após esta operação. + Você tem certeza que deseja continuar? + label_close_versions: Fechar versões concluídas + label_board_sticky: Marcado + label_board_locked: Bloqueado + permission_export_wiki_pages: Exportar páginas wiki + setting_cache_formatted_text: Realizar cache de texto formatado + permission_manage_project_activities: Gerenciar atividades do projeto + error_unable_delete_issue_status: Não foi possível excluir situação da tarefa + label_profile: Perfil + permission_manage_subtasks: Gerenciar subtarefas + field_parent_issue: Tarefa pai + label_subtask_plural: Subtarefas + label_project_copy_notifications: Enviar notificações por e-mail ao copiar projeto + error_can_not_delete_custom_field: Não foi possível excluir o campo personalizado + error_unable_to_connect: Não foi possível conectar (%{value}) + error_can_not_remove_role: Este papel está em uso e não pode ser excluído. + error_can_not_delete_tracker: Este tipo de tarefa está atribuído a alguma(s) tarefa(s) e não pode ser excluído. + field_principal: Principal + label_my_page_block: Meu bloco de página + notice_failed_to_save_members: "Falha ao salvar membro(s): %{errors}." + text_zoom_out: Afastar zoom + text_zoom_in: Aproximar zoom + notice_unable_delete_time_entry: Não foi possível excluir a entrada no registro de horas trabalhadas. + label_overall_spent_time: Tempo gasto geral + field_time_entries: Registro de horas + project_module_gantt: Gantt + project_module_calendar: Calendário + button_edit_associated_wikipage: "Editar página wiki relacionada: %{page_title}" + field_text: Campo de texto + label_user_mail_option_only_owner: Somente para as coisas que eu criei + setting_default_notification_option: Opção padrão de notificação + label_user_mail_option_only_my_events: Somente para as coisas que eu esteja observando ou esteja envolvido + label_user_mail_option_only_assigned: Somente para as coisas que estejam atribuídas a mim + label_user_mail_option_none: Sem eventos + field_member_of_group: Responsável pelo grupo + field_assigned_to_role: Papel do responsável + notice_not_authorized_archived_project: O projeto que você está tentando acessar foi arquivado. + label_principal_search: "Pesquisar por usuários ou grupos:" + label_user_search: "Pesquisar por usuário:" + field_visible: Visível + setting_emails_header: Cabeçalho do e-mail + setting_commit_logtime_activity_id: Atividade para registrar horas + text_time_logged_by_changeset: Aplicado no conjunto de alterações %{value}. + setting_commit_logtime_enabled: Habilitar registro de horas + notice_gantt_chart_truncated: O gráfico foi cortado por exceder o tamanho máximo de linhas que podem ser exibidas (%{max}) + setting_gantt_items_limit: Número máximo de itens exibidos no gráfico gantt + field_warn_on_leaving_unsaved: Alertar-me ao sair de uma página sem salvar o texto + text_warn_on_leaving_unsaved: A página atual contém texto que não foi salvo e será perdido se você sair desta página. + label_my_queries: Minhas consultas personalizadas + text_journal_changed_no_detail: "%{label} atualizado(a)" + label_news_comment_added: Notícia recebeu um comentário + button_expand_all: Expandir tudo + button_collapse_all: Recolher tudo + label_additional_workflow_transitions_for_assignee: Transições adicionais permitidas quando o usuário é o responsável pela tarefa + label_additional_workflow_transitions_for_author: Transições adicionais permitidas quando o usuário é o autor + + label_bulk_edit_selected_time_entries: Alteração em massa do registro de horas + text_time_entries_destroy_confirmation: Tem certeza que quer excluir o(s) registro(s) de horas selecionado(s)? + label_role_anonymous: Anônimo + label_role_non_member: Não Membro + label_issues_visibility_own: Tarefas criadas ou atribuídas ao usuário + field_issues_visibility: Visibilidade das tarefas + label_issues_visibility_all: Todas as tarefas + permission_set_own_issues_private: Alterar as próprias tarefas para públicas ou privadas + field_is_private: Privado + permission_set_issues_private: Alterar tarefas para públicas ou privadas + label_issues_visibility_public: Todas as tarefas não privadas + text_issues_destroy_descendants_confirmation: Isto também irá excluir %{count} subtarefa(s). + field_commit_logs_encoding: Codificação das mensagens de commit + field_scm_path_encoding: Codificação do caminho + text_scm_path_encoding_note: "Padrão: UTF-8" + field_path_to_repository: Caminho para o repositório + field_root_directory: Diretório raiz + field_cvs_module: Módulo + field_cvsroot: CVSROOT + text_mercurial_repository_note: "Repositório local (ex.: /hgrepo, c:\\hgrepo)" + text_scm_command: Comando + text_scm_command_version: Versão + label_git_report_last_commit: Relatar última alteração para arquivos e diretórios + text_scm_config: Você pode configurar seus comandos de versionamento em config/configurations.yml. Por favor reinicie a aplicação após alterá-lo. + text_scm_command_not_available: Comando de versionamento não disponível. Por favor verifique as configurações no painel de administração. + notice_issue_successful_create: Tarefa %{id} criada. + label_between: entre + setting_issue_group_assignment: Permitir atribuições de tarefas a grupos + label_diff: diff + text_git_repository_note: "Repositório esta vazio e é local (ex: /gitrepo, c:\\gitrepo)" + + description_query_sort_criteria_direction: Escolher ordenação + description_project_scope: Escopo da pesquisa + description_filter: Filtro + description_user_mail_notification: Configuração de notificações por e-mail + description_date_from: Digite a data inicial + description_message_content: Conteúdo da mensagem + description_available_columns: Colunas disponíveis + description_date_range_interval: Escolha um período selecionando a data de início e fim + description_issue_category_reassign: Escolha uma categoria de tarefas + description_search: Campo de busca + description_notes: Notas + description_date_range_list: Escolha um período a partir da lista + description_choose_project: Projetos + description_date_to: Digite a data final + description_query_sort_criteria_attribute: Atributo de ordenação + description_wiki_subpages_reassign: Escolha uma nova página pai + description_selected_columns: Colunas selecionadas + + label_parent_revision: Pai + label_child_revision: Filho + error_scm_annotate_big_text_file: A entrada não pode ser anotada, pois excede o tamanho máximo do arquivo de texto. + setting_default_issue_start_date_to_creation_date: Usar data corrente como data inicial para novas tarefas + button_edit_section: Editar esta seção + setting_repositories_encodings: Codificação dos repositórios e anexos + description_all_columns: Todas as colunas + button_export: Exportar + label_export_options: "Opções de exportação %{export_format}" + error_attachment_too_big: Este arquivo não pode ser enviado porque excede o tamanho máximo permitido (%{max_size}) + notice_failed_to_save_time_entries: "Falha ao salvar %{count} de %{total} horas trabalhadas: %{ids}." + label_x_issues: + zero: 0 tarefa + one: 1 tarefa + other: "%{count} tarefas" + label_repository_new: Novo repositório + field_repository_is_default: Repositório principal + label_copy_attachments: Copiar anexos + label_item_position: "%{position}/%{count}" + label_completed_versions: Versões concluídas + text_project_identifier_info: Somente letras minúsculas (a-z), números, traços e sublinhados são permitidos.
    Uma vez salvo, o identificador não pode ser alterado. + field_multiple: Múltiplos valores + setting_commit_cross_project_ref: Permitir que tarefas de todos os outros projetos sejam refenciadas e resolvidas + text_issue_conflict_resolution_add_notes: Adicionar minhas anotações e descartar minhas outras mudanças + text_issue_conflict_resolution_overwrite: Aplicar as minhas alterações de qualquer maneira (notas anteriores serão mantidas, mas algumas mudanças podem ser substituídas) + notice_issue_update_conflict: A tarefa foi atualizada por um outro usuário, enquanto você estava editando. + text_issue_conflict_resolution_cancel: Descartar todas as minhas mudanças e reexibir %{link} + permission_manage_related_issues: Gerenciar tarefas relacionadas + field_auth_source_ldap_filter: Filtro LDAP + label_search_for_watchers: Procurar por outros observadores para adiconar + notice_account_deleted: Sua conta foi excluída permanentemente. + setting_unsubscribe: Permitir aos usuários excluir sua própria conta + button_delete_my_account: Excluir minha conta + text_account_destroy_confirmation: |- + Tem certeza que quer continuar? + Sua conta será excluída permanentemente, sem qualquer forma de reativá-la. + error_session_expired: A sua sessão expirou. Por favor, faça login novamente. + text_session_expiration_settings: "Aviso: a alteração dessas configurações pode expirar as sessões atuais, incluindo a sua." + setting_session_lifetime: duração máxima da sessão + setting_session_timeout: tempo limite de inatividade da sessão + label_session_expiration: "Expiração da sessão" + permission_close_project: Fechar / reabrir o projeto + label_show_closed_projects: Visualizar projetos fechados + button_close: Fechar + button_reopen: Reabrir + project_status_active: ativo + project_status_closed: fechado + project_status_archived: arquivado + text_project_closed: Este projeto está fechado e somente leitura. + notice_user_successful_create: Usuário %{id} criado. + field_core_fields: campos padrão + field_timeout: Tempo de espera (em segundos) + setting_thumbnails_enabled: Exibir miniaturas de anexos + setting_thumbnails_size: Tamanho das miniaturas (em pixels) + label_status_transitions: Estados das transições + label_fields_permissions: Permissões de campos + label_readonly: somente leitura + label_required: Obrigatório + text_repository_identifier_info: Somente letras minúsculas (az), números, traços e sublinhados são permitidos
    Uma vez salvo, o identificador não pode ser alterado. + field_board_parent: Fórum Pai + label_attribute_of_project: "Projeto %{name}" + label_attribute_of_author: "autor %{name}" + label_attribute_of_assigned_to: "atribuído a %{name}" + label_attribute_of_fixed_version: "versão %{name}" + label_copy_subtasks: Copiar subtarefas + label_copied_to: copiada + label_copied_from: copiado + label_any_issues_in_project: qualquer tarefa do projeto + label_any_issues_not_in_project: qualquer tarefa que não está no projeto + field_private_notes: notas privadas + permission_view_private_notes: Ver notas privadas + permission_set_notes_private: Permitir alterar notas para privada + label_no_issues_in_project: sem tarefas no projeto + label_any: todos + label_last_n_weeks: "últimas %{count} semanas" + setting_cross_project_subtasks: Permitir subtarefas entre projetos + label_cross_project_descendants: com subprojetos + label_cross_project_tree: Com a árvore do Projeto + label_cross_project_hierarchy: Com uma hierarquia do Projeto + label_cross_project_system: Com todos os Projetos + button_hide: Omitir + setting_non_working_week_days: dias não úteis + label_in_the_next_days: nos próximos dias + label_in_the_past_days: nos dias anteriores + label_attribute_of_user: Usuário %{name} + text_turning_multiple_off: Se você desativar vários valores, eles serão removidos, a fim de preservar somente um valor por item. + label_attribute_of_issue: Tarefa %{name} + permission_add_documents: Adicionar documentos + permission_edit_documents: Editar documentos + permission_delete_documents: Excluir documentos + label_gantt_progress_line: Linha de progresso + setting_jsonp_enabled: Ativar suporte JSONP + field_inherit_members: Herdar membros + field_closed_on: Concluído + field_generate_password: Gerar senha + setting_default_projects_tracker_ids: Tipos padrões para novos projeto + label_total_time: Total + notice_account_not_activated_yet: Sua conta ainda não foi ativada. Se você deseja receber + um novo email de ativação, por favor clique aqui. + notice_account_locked: Sua conta está bloqueada. + label_hidden: Visibilidade + label_visibility_private: para mim + label_visibility_roles: para os papéis + label_visibility_public: para qualquer usuário + field_must_change_passwd: É necessário alterar sua senha na próxima vez que tentar acessar sua conta + notice_new_password_must_be_different: A nova senha deve ser diferente da senha atual + setting_mail_handler_excluded_filenames: Exclui anexos por nome + text_convert_available: Conversor ImageMagick disponível (opcional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b5/b5853b637c67cfc0aed37f236eb8fa380a6714c9.svn-base --- a/.svn/pristine/b5/b5853b637c67cfc0aed37f236eb8fa380a6714c9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module WatchersHelper - - def watcher_tag(object, user, options={}) - content_tag("span", watcher_link(object, user), :class => watcher_css(object)) - end - - def watcher_link(object, user) - return '' unless user && user.logged? && object.respond_to?('watched_by?') - watched = object.watched_by?(user) - url = {:controller => 'watchers', - :action => (watched ? 'unwatch' : 'watch'), - :object_type => object.class.to_s.underscore, - :object_id => object.id} - link_to((watched ? l(:button_unwatch) : l(:button_watch)), url, - :remote => true, :method => 'post', :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off')) - - end - - # Returns the css class used to identify watch links for a given +object+ - def watcher_css(object) - "#{object.class.to_s.underscore}-#{object.id}-watcher" - end - - # Returns a comma separated list of users watching the given object - def watchers_list(object) - remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project) - content = ''.html_safe - lis = object.watcher_users.collect do |user| - s = ''.html_safe - s << avatar(user, :size => "16").to_s - s << link_to_user(user, :class => 'user') - if remove_allowed - url = {:controller => 'watchers', - :action => 'destroy', - :object_type => object.class.to_s.underscore, - :object_id => object.id, - :user_id => user} - s << ' ' - s << link_to(image_tag('delete.png'), url, - :remote => true, :method => 'post', :style => "vertical-align: middle", :class => "delete") - end - content << content_tag('li', s) - end - content.present? ? content_tag('ul', content) : content - end - - def watchers_checkboxes(object, users, checked=nil) - users.map do |user| - c = checked.nil? ? object.watched_by?(user) : checked - tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil - content_tag 'label', "#{tag} #{h(user)}".html_safe, - :id => "issue_watcher_user_ids_#{user.id}", - :class => "floating" - end.join.html_safe - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b5/b5927def2130703fcd89c61c9d2aa9928f429ac5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b5/b5927def2130703fcd89c61c9d2aa9928f429ac5.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,38 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingContextMenusTest < ActionController::IntegrationTest + def test_context_menus_time_entries + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/time_entries/context_menu" }, + { :controller => 'context_menus', :action => 'time_entries' } + ) + end + end + + def test_context_menus_issues + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/issues/context_menu" }, + { :controller => 'context_menus', :action => 'issues' } + ) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b5/b5c65a26ab892d9f8cee94e484ee432c6e022830.svn-base --- a/.svn/pristine/b5/b5c65a26ab892d9f8cee94e484ee432c6e022830.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module AccessKeys - ACCESSKEYS = {:edit => 'e', - :preview => 'r', - :quick_search => 'f', - :search => '4', - :new_issue => '7' - }.freeze unless const_defined?(:ACCESSKEYS) - - def self.key_for(action) - ACCESSKEYS[action] - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b5/b5ff9d7cff11555edaa362cc7b4aa84c35458cdf.svn-base --- a/.svn/pristine/b5/b5ff9d7cff11555edaa362cc7b4aa84c35458cdf.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -

    <%= link_to l(:label_custom_field_plural), :controller => 'custom_fields', :action => 'index' %> - » <%= link_to l(@custom_field.type_name), :controller => 'custom_fields', :action => 'index', :tab => @custom_field.class.name %> - » <%=h @custom_field.name %>

    - -<%= labelled_form_for :custom_field, @custom_field, :url => custom_field_path(@custom_field), :html => {:method => :put, :id => 'custom_field_form'} do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<%= submit_tag l(:button_save) %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b6/b636c6cb1a8d4d87d0a0604e334ec3ebc8d155af.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b6/b636c6cb1a8d4d87d0a0604e334ec3ebc8d155af.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,38 @@ +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {:action => 'index', :format => 'atom', :key => User.current.rss_key}) %> +<% end %> + +
    + <%= 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), issues_path) + ' |' if User.current.allowed_to?(:view_issues, nil, :global => true) %> + <%= link_to(l(:label_overall_spent_time), time_entries_path) + ' |' if User.current.allowed_to?(:view_time_entries, nil, :global => true) %> + <%= link_to l(:label_overall_activity), + { :controller => 'activities', :action => 'index', + :id => nil } %> +
    + +

    <%= l(:label_project_plural) %>

    + +
    +<%= render_project_hierarchy(@projects) %> +
    + +<% if User.current.logged? %> +

    +<%= l(:label_my_projects) %> +

    +<% end %> + +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> +<% end %> + +<% content_for :sidebar do %> + <%= form_tag({}, :method => :get) do %> +

    <%= l(:label_project_plural) %>

    + +

    <%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %>

    + <% end %> +<% end %> + +<% html_title(l(:label_project_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b6/b655ad2d788904a0652e611b77d64e13681af213.svn-base --- a/.svn/pristine/b6/b655ad2d788904a0652e611b77d64e13681af213.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'fileutils' - -module Redmine - module Thumbnail - extend Redmine::Utils::Shell - - CONVERT_BIN = (Redmine::Configuration['imagemagick_convert_command'] || 'convert').freeze - - # Generates a thumbnail for the source image to target - def self.generate(source, target, size) - return nil unless convert_available? - unless File.exists?(target) - directory = File.dirname(target) - unless File.exists?(directory) - FileUtils.mkdir_p directory - end - size_option = "#{size}x#{size}>" - cmd = "#{shell_quote CONVERT_BIN} #{shell_quote source} -thumbnail #{shell_quote size_option} #{shell_quote target}" - unless system(cmd) - logger.error("Creating thumbnail failed (#{$?}):\nCommand: #{cmd}") - return nil - end - end - target - end - - def self.convert_available? - return @convert_available if defined?(@convert_available) - @convert_available = system("#{shell_quote CONVERT_BIN} -version") rescue false - logger.warn("Imagemagick's convert binary (#{CONVERT_BIN}) not available") unless @convert_available - @convert_available - end - - def self.logger - Rails.logger - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b6/b660ee83289e164ea0413ddff0a433dfd70ad1fb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b6/b660ee83289e164ea0413ddff0a433dfd70ad1fb.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,41 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingJournalsTest < ActionController::IntegrationTest + def test_journals + assert_routing( + { :method => 'post', :path => "/issues/1/quoted" }, + { :controller => 'journals', :action => 'new', :id => '1' } + ) + assert_routing( + { :method => 'get', :path => "/issues/changes" }, + { :controller => 'journals', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/journals/diff/1" }, + { :controller => 'journals', :action => 'diff', :id => '1' } + ) + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/journals/edit/1" }, + { :controller => 'journals', :action => 'edit', :id => '1' } + ) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b6/b67ea0ec77b4b276a5e977476ff6fc94a0544b66.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b6/b67ea0ec77b4b276a5e977476ff6fc94a0544b66.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,40 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingActivitiesTest < ActionController::IntegrationTest + def test_activities + assert_routing( + { :method => 'get', :path => "/activity" }, + { :controller => 'activities', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/activity.atom" }, + { :controller => 'activities', :action => 'index', :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/projects/33/activity" }, + { :controller => 'activities', :action => 'index', :id => '33' } + ) + assert_routing( + { :method => 'get', :path => "/projects/33/activity.atom" }, + { :controller => 'activities', :action => 'index', :id => '33', + :format => 'atom' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b6/b69919be9b30cca58da48f5eb6fb2fab0f0c471f.svn-base --- a/.svn/pristine/b6/b69919be9b30cca58da48f5eb6fb2fab0f0c471f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1164 +0,0 @@ -# Chinese (Taiwan) translations for Ruby on Rails -# by tsechingho (http://github.com/tsechingho) -# See http://github.com/svenfuchs/rails-i18n/ for details. - -"zh-TW": - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%b%dæ—¥" - long: "%Yå¹´%b%dæ—¥" - - day_names: [星期日, 星期一, 星期二, 星期三, 星期四, 星期五, 星期六] - abbr_day_names: [æ—¥, 一, 二, 三, å››, 五, å…­] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, 一月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 乿œˆ, åæœˆ, å一月, å二月] - abbr_month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月] - # 使用於 date_select 與 datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%Yå¹´%b%dæ—¥ %A %H:%M:%S %Z" - time: "%H:%M" - short: "%b%dæ—¥ %H:%M" - long: "%Yå¹´%b%dæ—¥ %H:%M" - am: "AM" - pm: "PM" - -# 使用於 array.to_sentence. - support: - array: - words_connector: ", " - two_words_connector: " å’Œ " - last_word_connector: ", å’Œ " - sentence_connector: "且" - skip_last_comma: false - - number: - # 使用於 number_with_delimiter() - # åŒæ™‚也是 'currency', 'percentage', 'precision', 與 'human' çš„é è¨­å€¼ - format: - # è¨­å®šå°æ•¸é»žåˆ†éš”字元,以使用更高的準確度 (例如: 1.0 / 2.0 == 0.5) - separator: "." - # åƒåˆ†ä½ç¬¦è™Ÿ (ä¾‹å¦‚ï¼šä¸€ç™¾è¬æ˜¯ 1,000,000) (å‡ä»¥ä¸‰å€‹ä½æ•¸ä¾†åˆ†çµ„) - delimiter: "," - # å°æ•¸é»žåˆ†éš”å­—å…ƒå¾Œä¹‹ç²¾ç¢ºä½æ•¸ (數字 1 æ­é… 2 ä½ç²¾ç¢ºä½æ•¸ç‚ºï¼š 1.00) - precision: 3 - - # 使用於 number_to_currency() - currency: - format: - # 貨幣符號的ä½ç½®? %u 是貨幣符號, %n 是數值 (é è¨­å€¼ï¼š $5.00) - format: "%u%n" - unit: "NT$" - # 下列三個é¸é …設定, 若有設定值將會å–代 number.format æˆç‚ºé è¨­å€¼ - separator: "." - delimiter: "," - precision: 2 - - # 使用於 number_to_percentage() - percentage: - format: - # 下列三個é¸é …設定, 若有設定值將會å–代 number.format æˆç‚ºé è¨­å€¼ - # separator: - delimiter: "" - # precision: - - # 使用於 number_to_precision() - precision: - format: - # 下列三個é¸é …設定, 若有設定值將會å–代 number.format æˆç‚ºé è¨­å€¼ - # separator: - delimiter: "" - # precision: - - # 使用於 number_to_human_size() - human: - format: - # 下列三個é¸é …設定, 若有設定值將會å–代 number.format æˆç‚ºé è¨­å€¼ - # separator: - delimiter: "" - precision: 3 - # 儲存單ä½è¼¸å‡ºæ ¼å¼. - # %u 是儲存單ä½, %n 是數值 (é è¨­å€¼: 2 MB) - storage_units: - format: "%n %u" - units: - byte: - one: "ä½å…ƒçµ„ (B)" - other: "ä½å…ƒçµ„ (B)" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - # 使用於 distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() - datetime: - distance_in_words: - half_a_minute: "åŠåˆ†é˜" - less_than_x_seconds: - one: "å°æ–¼ 1 ç§’" - other: "å°æ–¼ %{count} ç§’" - x_seconds: - one: "1 ç§’" - other: "%{count} ç§’" - less_than_x_minutes: - one: "å°æ–¼ 1 分é˜" - other: "å°æ–¼ %{count} 分é˜" - x_minutes: - one: "1 分é˜" - other: "%{count} 分é˜" - about_x_hours: - one: "ç´„ 1 å°æ™‚" - other: "ç´„ %{count} å°æ™‚" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 天" - other: "%{count} 天" - about_x_months: - one: "ç´„ 1 個月" - other: "ç´„ %{count} 個月" - x_months: - one: "1 個月" - other: "%{count} 個月" - about_x_years: - one: "ç´„ 1 å¹´" - other: "ç´„ %{count} å¹´" - over_x_years: - one: "è¶…éŽ 1 å¹´" - other: "è¶…éŽ %{count} å¹´" - almost_x_years: - one: "將近 1 å¹´" - other: "將近 %{count} å¹´" - prompts: - year: "å¹´" - month: "月" - day: "æ—¥" - hour: "時" - minute: "分" - second: "ç§’" - - activerecord: - errors: - template: - header: - one: "有 1 個錯誤發生使得「%{model}ã€ç„¡æ³•被儲存。" - other: "有 %{count} 個錯誤發生使得「%{model}ã€ç„¡æ³•被儲存。" - # The variable :count is also available - body: "䏋颿‰€åˆ—æ¬„ä½æœ‰å•題:" - # The values :model, :attribute and :value are always available for interpolation - # The value :count is available when applicable. Can be used for pluralization. - messages: - inclusion: "沒有包å«åœ¨åˆ—表中" - exclusion: "是被ä¿ç•™çš„" - invalid: "是無效的" - confirmation: "ä¸ç¬¦åˆç¢ºèªå€¼" - accepted: "必须是å¯è¢«æŽ¥å—çš„" - empty: "ä¸èƒ½ç•™ç©º" - blank: "ä¸èƒ½æ˜¯ç©ºç™½å­—å…ƒ" - too_long: "éŽé•·ï¼ˆæœ€é•·æ˜¯ %{count} 個字)" - too_short: "éŽçŸ­ï¼ˆæœ€çŸ­æ˜¯ %{count} 個字)" - wrong_length: "字數錯誤(必須是 %{count} 個字)" - taken: "已經被使用" - not_a_number: "䏿˜¯æ•¸å­—" - greater_than: "必須大於 %{count}" - greater_than_or_equal_to: "必須大於或等於 %{count}" - equal_to: "必須等於 %{count}" - less_than: "å¿…é ˆå°æ–¼ %{count}" - less_than_or_equal_to: "å¿…é ˆå°æ–¼æˆ–等於 %{count}" - odd: "必須是奇數" - even: "å¿…é ˆæ˜¯å¶æ•¸" - # Append your own errors here or at the model/attributes scope. - greater_than_start_date: "必須在開始日期之後" - not_same_project: "ä¸å±¬æ–¼åŒä¸€å€‹å°ˆæ¡ˆ" - circular_dependency: "é€™å€‹é—œè¯æœƒå°Žè‡´ç’°ç‹€ç›¸ä¾" - cant_link_an_issue_with_a_descendant: "å•題無法被連çµè‡³è‡ªå·±çš„å­ä»»å‹™" - - # You can define own errors for models or model attributes. - # The values :model, :attribute and :value are always available for interpolation. - # - # For example, - # models: - # user: - # blank: "This is a custom blank message for %{model}: %{attribute}" - # attributes: - # login: - # blank: "This is a custom blank message for User login" - # Will define custom blank validation message for User model and - # custom blank validation message for login attribute of User model. - #models: - - # Translate model names. Used in Model.human_name(). - #models: - # For example, - # user: "Dude" - # will translate User model name to "Dude" - - # Translate model attribute names. Used in Model.human_attribute_name(attribute). - #attributes: - # For example, - # user: - # login: "Handle" - # will translate User attribute "login" as "Handle" - - actionview_instancetag_blank_option: è«‹é¸æ“‡ - - general_text_No: 'å¦' - general_text_Yes: '是' - general_text_no: 'å¦' - general_text_yes: '是' - general_lang_name: 'Traditional Chinese (ç¹é«”中文)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: Big5 - general_pdf_encoding: Big5 - general_first_day_of_week: '7' - - notice_account_updated: 帳戶更新資訊已儲存 - notice_account_invalid_creditentials: å¸³æˆ¶æˆ–å¯†ç¢¼ä¸æ­£ç¢º - notice_account_password_updated: 帳戶新密碼已儲存 - notice_account_wrong_password: å¯†ç¢¼ä¸æ­£ç¢º - notice_account_register_done: 帳號已建立æˆåŠŸã€‚æ¬²å•Ÿç”¨æ‚¨çš„å¸³è™Ÿï¼Œè«‹é»žæ“Šç³»çµ±ç¢ºèªä¿¡å‡½ä¸­çš„啟用連çµã€‚ - notice_account_unknown_email: 未知的使用者 - notice_can_t_change_password: 這個帳號使用外部èªè­‰æ–¹å¼ï¼Œç„¡æ³•變更其密碼。 - notice_account_lost_email_sent: 包å«é¸æ“‡æ–°å¯†ç¢¼æŒ‡ç¤ºçš„é›»å­éƒµä»¶ï¼Œå·²ç¶“寄出給您。 - notice_account_activated: 您的帳號已經啟用,å¯ç”¨å®ƒç™»å…¥ç³»çµ±ã€‚ - notice_successful_create: 建立æˆåŠŸ - notice_successful_update: æ›´æ–°æˆåŠŸ - notice_successful_delete: 刪除æˆåŠŸ - notice_successful_connection: 連線æˆåŠŸ - notice_file_not_found: 您想è¦å­˜å–çš„é é¢å·²ç¶“ä¸å­˜åœ¨æˆ–被æ¬ç§»è‡³å…¶ä»–ä½ç½®ã€‚ - notice_locking_conflict: 資料已被其他使用者更新。 - notice_not_authorized: ä½ æœªè¢«æŽˆæ¬Šå­˜å–æ­¤é é¢ã€‚ - notice_not_authorized_archived_project: 您欲存å–的專案已經被å°å­˜ã€‚ - notice_email_sent: "郵件已經æˆåŠŸå¯„é€è‡³ä»¥ä¸‹æ”¶ä»¶è€…: %{value}" - notice_email_error: "寄é€éƒµä»¶çš„éŽç¨‹ä¸­ç™¼ç”ŸéŒ¯èª¤ (%{value})" - notice_feeds_access_key_reseted: 您的 RSS å­˜å–é‡‘é‘°å·²è¢«é‡æ–°è¨­å®šã€‚ - notice_api_access_key_reseted: 您的 API å­˜å–é‡‘é‘°å·²è¢«é‡æ–°è¨­å®šã€‚ - notice_failed_to_save_issues: "無法儲存 %{count} å•題到下列所é¸å–çš„ %{total} 個項目中: %{ids}。" - notice_failed_to_save_time_entries: "無法儲存 %{count} 個工時到下列所é¸å–çš„ %{total} 個項目中: %{ids}。" - notice_failed_to_save_members: "æˆå“¡å„²å­˜å¤±æ•—: %{errors}." - notice_no_issue_selected: "æœªé¸æ“‡ä»»ä½•å•題ï¼è«‹å‹¾é¸æ‚¨æƒ³è¦ç·¨è¼¯çš„å•題。" - notice_account_pending: "您的帳號已經建立,正在等待管ç†å“¡çš„審核。" - notice_default_data_loaded: é è¨­çµ„態已載入æˆåŠŸã€‚ - notice_unable_delete_version: 無法刪除版本。 - notice_unable_delete_time_entry: 無法刪除工時記錄項目。 - notice_issue_done_ratios_updated: å•題完æˆç™¾åˆ†æ¯”已更新。 - notice_gantt_chart_truncated: "由於項目數é‡è¶…éŽå¯é¡¯ç¤ºæ•¸é‡çš„æœ€å¤§å€¼ (%{max}),故此甘特圖尾部已被截斷" - notice_issue_successful_create: "å•題 %{id} 已建立。" - notice_issue_update_conflict: "當您正在編輯這個å•題的時候,它已經被其他人æ¶å…ˆä¸€æ­¥æ›´æ–°éŽã€‚" - notice_account_deleted: "您的帳戶已被永久刪除。" - notice_user_successful_create: "已建立用戶 %{id}。" - - error_can_t_load_default_data: "無法載入é è¨­çµ„態: %{value}" - error_scm_not_found: "在儲存機制中找ä¸åˆ°é€™å€‹é …目或修訂版。" - error_scm_command_failed: "嘗試存å–儲存機制時發生錯誤: %{value}" - error_scm_annotate: "é …ç›®ä¸å­˜åœ¨æˆ–項目無法被加上附註。" - error_scm_annotate_big_text_file: æ­¤é …ç›®ç„¡æ³•è¢«æ¨™è¨»ï¼Œå› ç‚ºå®ƒå·²ç¶“è¶…éŽæœ€å¤§çš„æ–‡å­—檔大å°ã€‚ - error_issue_not_found_in_project: '該å•題ä¸å­˜åœ¨æˆ–ä¸å±¬æ–¼æ­¤å°ˆæ¡ˆ' - error_no_tracker_in_project: '此專案尚未指定追蹤標籤。請檢查專案的設定資訊。' - error_no_default_issue_status: '尚未定義å•題狀態的é è¨­å€¼ã€‚請您å‰å¾€ã€Œç¶²ç«™ç®¡ç†ã€->「å•題狀態清單ã€é é¢ï¼Œæª¢æŸ¥ç›¸é—œçµ„態設定。' - error_can_not_delete_custom_field: ç„¡æ³•åˆªé™¤è‡ªè¨‚æ¬„ä½ - error_can_not_delete_tracker: "此追蹤標籤已包å«å•題,無法被刪除。" - error_can_not_remove_role: "此角色已被使用,無法將其刪除。" - error_can_not_reopen_issue_on_closed_version: 'æŒ‡æ´¾çµ¦ã€Œå·²çµæŸã€ç‰ˆæœ¬çš„å•題,無法å†å°‡å…¶ç‹€æ…‹è®Šæ›´ç‚ºã€Œé€²è¡Œä¸­ã€' - error_can_not_archive_project: 此專案無法被å°å­˜ - error_issue_done_ratios_not_updated: "å•題完æˆç™¾åˆ†æ¯”未更新。" - error_workflow_copy_source: 'è«‹é¸æ“‡ä¸€å€‹ä¾†æºå•題追蹤標籤或角色' - error_workflow_copy_target: 'è«‹é¸æ“‡ä¸€å€‹ï¼ˆæˆ–多個)目的å•題追蹤標籤或角色' - error_unable_delete_issue_status: '無法刪除å•題狀態' - error_unable_to_connect: "無法連線至(%{value})" - error_attachment_too_big: "é€™å€‹æª”æ¡ˆç„¡æ³•è¢«ä¸Šå‚³ï¼Œå› ç‚ºå®ƒå·²ç¶“è¶…éŽæœ€å¤§çš„æª”æ¡ˆå¤§å° (%{max_size})" - error_session_expired: "æ‚¨çš„å·¥ä½œéšŽæ®µå·²ç¶“éŽæœŸã€‚è«‹é‡æ–°ç™»å…¥ã€‚" - warning_attachments_not_saved: "%{count} 個附加檔案無法被儲存。" - - mail_subject_lost_password: 您的 Redmine 網站密碼 - mail_body_lost_password: '欲變更您的 Redmine 網站密碼, 請點é¸ä»¥ä¸‹éˆçµ:' - mail_subject_register: 啟用您的 Redmine 帳號 - mail_body_register: '欲啟用您的 Redmine 帳號, 請點é¸ä»¥ä¸‹éˆçµ:' - mail_body_account_information_external: "您å¯ä»¥ä½¿ç”¨ %{value} 帳號登入 Redmine 網站。" - mail_body_account_information: 您的 Redmine 帳號資訊 - mail_subject_account_activation_request: Redmine 帳號啟用需求通知 - mail_body_account_activation_request: "æœ‰ä½æ–°ç”¨æˆ¶ (%{value}) 已經完æˆè¨»å†Šï¼Œæ­£ç­‰å€™æ‚¨çš„審核:" - mail_subject_reminder: "您有 %{count} 個å•題å³å°‡åˆ°æœŸ (%{days})" - mail_body_reminder: "%{count} 個指派給您的å•題,將於 %{days} 天之內到期:" - mail_subject_wiki_content_added: "'%{id}' wiki é é¢å·²è¢«æ–°å¢ž" - mail_body_wiki_content_added: "æ­¤ '%{id}' wiki é é¢å·²è¢« %{author} 新增。" - mail_subject_wiki_content_updated: "'%{id}' wiki é é¢å·²è¢«æ›´æ–°" - mail_body_wiki_content_updated: "æ­¤ '%{id}' wiki é é¢å·²è¢« %{author} 更新。" - - gui_validation_error: 1 個錯誤 - gui_validation_error_plural: "%{count} 個錯誤" - - field_name: å稱 - field_description: 概述 - field_summary: æ‘˜è¦ - field_is_required: å¿…å¡« - field_firstname: åå­— - field_lastname: å§“æ° - field_mail: é›»å­éƒµä»¶ - field_filename: 檔案å稱 - field_filesize: å¤§å° - field_downloads: 下載次數 - field_author: 作者 - field_created_on: 建立日期 - field_updated_on: æ›´æ–° - field_field_format: æ ¼å¼ - field_is_for_all: 給全部的專案 - field_possible_values: å¯èƒ½å€¼ - field_regexp: æ­£è¦è¡¨ç¤ºå¼ - field_min_length: 最å°é•·åº¦ - field_max_length: 最大長度 - field_value: 值 - field_category: 分類 - field_title: 標題 - field_project: 專案 - field_issue: å•題 - field_status: 狀態 - field_notes: 筆記 - field_is_closed: å•é¡Œå·²çµæŸ - field_is_default: é è¨­å€¼ - field_tracker: 追蹤標籤 - field_subject: 主旨 - field_due_date: å®Œæˆæ—¥æœŸ - field_assigned_to: 分派給 - field_priority: 優先權 - field_fixed_version: 版本 - field_user: 用戶 - field_principal: 原則 - field_role: 角色 - field_homepage: ç¶²ç«™é¦–é  - field_is_public: 公開 - field_parent: 父專案 - field_is_in_roadmap: å•題顯示於版本è—圖中 - field_login: 帳戶å稱 - field_mail_notification: é›»å­éƒµä»¶æé†’é¸é … - field_admin: 管ç†è€… - field_last_login_on: 最近連線日期 - field_language: 語系 - field_effective_date: 日期 - field_password: ç›®å‰å¯†ç¢¼ - field_new_password: 新密碼 - field_password_confirmation: ç¢ºèªæ–°å¯†ç¢¼ - field_version: 版本 - field_type: Type - field_host: Host - field_port: 連接埠 - field_account: 帳戶 - field_base_dn: Base DN - field_attr_login: 登入屬性 - field_attr_firstname: å字屬性 - field_attr_lastname: å§“æ°å±¬æ€§ - field_attr_mail: é›»å­éƒµä»¶ä¿¡ç®±å±¬æ€§ - field_onthefly: 峿™‚建立使用者 - field_start_date: 開始日期 - field_done_ratio: 完æˆç™¾åˆ†æ¯” - field_auth_source: èªè­‰æ¨¡å¼ - field_hide_mail: éš±è—æˆ‘的電å­éƒµä»¶ - field_comments: 回應 - field_url: ç¶²å€ - field_start_page: é¦–é  - field_subproject: å­å°ˆæ¡ˆ - field_hours: å°æ™‚ - field_activity: 活動 - field_spent_on: 日期 - field_identifier: 代碼 - field_is_filter: ç”¨ä¾†ä½œç‚ºéŽæ¿¾å™¨ - field_issue_to: 相關å•題 - field_delay: 逾期 - field_assignable: å•題å¯è¢«åˆ†æ´¾è‡³æ­¤è§’色 - field_redirect_existing_links: 釿–°å°Žå‘ç¾æœ‰é€£çµ - field_estimated_hours: é ä¼°å·¥æ™‚ - field_column_names: æ¬„ä½ - field_time_entries: 耗用工時 - field_time_zone: æ™‚å€ - field_searchable: å¯ç”¨åšæœå°‹æ¢ä»¶ - field_default_value: é è¨­å€¼ - field_comments_sorting: å›žæ‡‰æŽ’åº - field_parent_title: 父é é¢ - field_editable: å¯ç·¨è¼¯ - field_watcher: 觀察者 - field_identity_url: OpenID ç¶²å€ - field_content: 內容 - field_group_by: çµæžœåˆ†çµ„æ–¹å¼ - field_sharing: 共用 - field_parent_issue: 父å•題 - field_member_of_group: "被指派者的群組" - field_assigned_to_role: "被指派者的角色" - field_text: 內容文字 - field_visible: å¯è¢«çœ‹è¦‹ - field_warn_on_leaving_unsaved: "æé†’我將è¦é›¢é–‹çš„é é¢ä¸­å°šæœ‰æœªå„²å­˜çš„資料" - field_issues_visibility: å•題å¯è¦‹åº¦ - field_is_private: ç§äºº - field_commit_logs_encoding: èªå¯è¨Šæ¯ç·¨ç¢¼ - field_scm_path_encoding: 路徑編碼 - field_path_to_repository: 儲存機制路徑 - field_root_directory: 根資料夾 - field_cvsroot: CVSROOT - field_cvs_module: 模組 - field_repository_is_default: 主è¦å„²å­˜æ©Ÿåˆ¶ - field_multiple: 多é‡å€¼ - field_auth_source_ldap_filter: LDAP 篩é¸å™¨ - field_core_fields: æ¨™æº–æ¬„ä½ - field_timeout: "逾時 (å–®ä½: ç§’)" - field_board_parent: 父論壇 - field_private_notes: ç§äººç­†è¨˜ - - setting_app_title: 標題 - setting_app_subtitle: 副標題 - setting_welcome_text: 歡迎詞 - setting_default_language: é è¨­èªžç³» - setting_login_required: 需è¦é©—è­‰ - setting_self_registration: 註冊é¸é … - setting_attachment_max_size: 附件大å°é™åˆ¶ - setting_issues_export_limit: å•題匯出é™åˆ¶ - setting_mail_from: 寄件者電å­éƒµä»¶ - setting_bcc_recipients: 使用密件副本 (BCC) - setting_plain_text_mail: 純文字郵件 (ä¸å« HTML) - setting_host_name: 主機å稱 - setting_text_formatting: æ–‡å­—æ ¼å¼ - setting_wiki_compression: 壓縮 Wiki æ­·å²æ–‡ç«  - setting_feeds_limit: RSS æ–°èžé™åˆ¶ - setting_autofetch_changesets: 自動擷å–èªå¯ - setting_default_projects_public: 新建立之專案é è¨­ç‚ºã€Œå…¬é–‹ã€ - setting_sys_api_enabled: 啟用管ç†å„²å­˜æ©Ÿåˆ¶çš„ç¶²é æœå‹™ (Web Service) - setting_commit_ref_keywords: èªå¯ç”¨æ–¼åƒç…§ä¹‹é—œéµå­— - setting_commit_fix_keywords: èªå¯ç”¨æ–¼ä¿®æ­£ä¹‹é—œéµå­— - setting_autologin: 自動登入 - setting_date_format: æ—¥æœŸæ ¼å¼ - setting_time_format: æ™‚é–“æ ¼å¼ - setting_cross_project_issue_relations: å…許關è¯è‡³å…¶å®ƒå°ˆæ¡ˆçš„å•題 - setting_cross_project_subtasks: å…許跨專案的å­ä»»å‹™ - setting_issue_list_default_columns: é è¨­é¡¯ç¤ºæ–¼å•é¡Œæ¸…å–®çš„æ¬„ä½ - setting_repositories_encodings: 附加檔案與儲存機制的編碼 - setting_emails_header: é›»å­éƒµä»¶å‰é ­èªªæ˜Ž - setting_emails_footer: é›»å­éƒµä»¶é™„帶說明 - setting_protocol: å”定 - setting_per_page_options: æ¯é é¡¯ç¤ºå€‹æ•¸é¸é … - setting_user_format: ä½¿ç”¨è€…é¡¯ç¤ºæ ¼å¼ - setting_activity_days_default: 專案活動顯示天數 - setting_display_subprojects_issues: é è¨­æ–¼çˆ¶å°ˆæ¡ˆä¸­é¡¯ç¤ºå­å°ˆæ¡ˆçš„å•題 - setting_enabled_scm: 啟用的 SCM - setting_mail_handler_body_delimiters: "截去郵件中包å«ä¸‹åˆ—值之後的內容" - setting_mail_handler_api_enabled: 啟用處ç†å‚³å…¥é›»å­éƒµä»¶çš„æœå‹™ - setting_mail_handler_api_key: API 金鑰 - setting_sequential_project_identifiers: 循åºç”¢ç”Ÿå°ˆæ¡ˆè­˜åˆ¥ç¢¼ - setting_gravatar_enabled: 啟用 Gravatar å…¨çƒèªè­‰å¤§é ­åƒ - setting_gravatar_default: é è¨­å…¨çƒèªè­‰å¤§é ­åƒåœ–片 - setting_diff_max_lines_displayed: 差異顯示行數之最大值 - setting_file_max_size_displayed: 檔案內容顯示大å°ä¹‹æœ€å¤§å€¼ - setting_repository_log_display_limit: 修訂版顯示數目之最大值 - setting_openid: å…許使用 OpenID 登入與註冊 - setting_password_min_length: 密碼最å°é•·åº¦ - setting_new_project_user_role_id: 管ç†è€…以外之用戶建立新專案時,將被指派的角色 - setting_default_projects_modules: 新專案é è¨­å•Ÿç”¨çš„æ¨¡çµ„ - setting_issue_done_ratio: 計算å•題完æˆç™¾åˆ†æ¯”ä¹‹æ–¹å¼ - setting_issue_done_ratio_issue_field: 便“šå•題完æˆç™¾åˆ†æ¯”æ¬„ä½ - setting_issue_done_ratio_issue_status: 便“šå•題狀態 - setting_start_of_week: 週的第一天 - setting_rest_api_enabled: 啟用 REST 網路æœå‹™æŠ€è¡“(Web Service) - setting_cache_formatted_text: å¿«å–已格å¼åŒ–文字 - setting_default_notification_option: é è¨­é€šçŸ¥é¸é … - setting_commit_logtime_enabled: 啟用èªå¯ä¸­çš„æ™‚間記錄 - setting_commit_logtime_activity_id: æ™‚é–“è¨˜éŒ„å°æ‡‰çš„æ´»å‹• - setting_gantt_items_limit: 甘特圖中項目顯示數é‡çš„æœ€å¤§å€¼ - setting_issue_group_assignment: å…許å•題被指派至群組 - setting_default_issue_start_date_to_creation_date: 設定新å•題的起始日期為今天的日期 - setting_commit_cross_project_ref: å…許關è¯ä¸¦ä¿®æ­£å…¶ä»–專案的å•題 - setting_unsubscribe: å…è¨±ç”¨æˆ¶å–æ¶ˆè¨»å†Šï¼ˆåˆªé™¤å¸³æˆ¶ï¼‰ - setting_session_lifetime: 工作階段存留時間最大值 - setting_session_timeout: 工作階段無活動逾時時間 - setting_thumbnails_enabled: 顯示附加檔案的縮圖 - setting_thumbnails_size: "ç¸®åœ–å¤§å° (å–®ä½: åƒç´  pixels)" - setting_non_working_week_days: éžå·¥ä½œæ—¥ - - permission_add_project: 建立專案 - permission_add_subprojects: 建立å­å°ˆæ¡ˆ - permission_edit_project: 編輯專案 - permission_close_project: 關閉 / 釿–°é–‹å•Ÿå°ˆæ¡ˆ - permission_select_project_modules: 鏿“‡å°ˆæ¡ˆæ¨¡çµ„ - permission_manage_members: ç®¡ç†æˆå“¡ - permission_manage_project_activities: 管ç†å°ˆæ¡ˆæ´»å‹• - permission_manage_versions: 管ç†ç‰ˆæœ¬ - permission_manage_categories: 管ç†å•題分類 - permission_view_issues: 檢視å•題 - permission_add_issues: 新增å•題 - permission_edit_issues: 編輯å•題 - permission_manage_issue_relations: 管ç†å•é¡Œé—œè¯ - permission_set_issues_private: 設定å•題為公開或ç§äºº - permission_set_own_issues_private: 設定自己的å•題為公開或ç§äºº - permission_add_issue_notes: 新增筆記 - permission_edit_issue_notes: 編輯筆記 - permission_edit_own_issue_notes: 編輯自己的筆記 - permission_view_private_notes: 檢視ç§äººç­†è¨˜ - permission_set_notes_private: 設定筆記為ç§äººç­†è¨˜ - permission_move_issues: æ¬ç§»å•題 - permission_delete_issues: 刪除å•題 - permission_manage_public_queries: 管ç†å…¬é–‹æŸ¥è©¢ - permission_save_queries: 儲存查詢 - permission_view_gantt: 檢視甘特圖 - permission_view_calendar: 檢視日曆 - permission_view_issue_watchers: 檢視監看者清單 - permission_add_issue_watchers: 新增監看者 - permission_delete_issue_watchers: 刪除監看者 - permission_log_time: 紀錄耗用工時 - permission_view_time_entries: 檢視耗用工時 - permission_edit_time_entries: 編輯工時紀錄 - permission_edit_own_time_entries: 編輯自己的工時記錄 - permission_manage_news: ç®¡ç†æ–°èž - permission_comment_news: å›žæ‡‰æ–°èž - permission_manage_documents: ç®¡ç†æ–‡ä»¶ - permission_view_documents: 檢視文件 - permission_manage_files: ç®¡ç†æª”案 - permission_view_files: 檢視檔案 - permission_manage_wiki: ç®¡ç† wiki - permission_rename_wiki_pages: 釿–°å‘½å wiki é é¢ - permission_delete_wiki_pages: 刪除 wiki é é¢ - permission_view_wiki_pages: 檢視 wiki - permission_view_wiki_edits: 檢視 wiki æ­·å² - permission_edit_wiki_pages: 編輯 wiki é é¢ - permission_delete_wiki_pages_attachments: 刪除附件 - permission_protect_wiki_pages: 專案 wiki é é¢ - permission_manage_repository: 管ç†å„²å­˜æ©Ÿåˆ¶ - permission_browse_repository: ç€è¦½å„²å­˜æ©Ÿåˆ¶ - permission_view_changesets: 檢視變更集 - permission_commit_access: å­˜å–èªå¯ - permission_manage_boards: 管ç†è¨Žè«–版 - permission_view_messages: æª¢è¦–è¨Šæ¯ - permission_add_messages: æ–°å¢žè¨Šæ¯ - permission_edit_messages: ç·¨è¼¯è¨Šæ¯ - permission_edit_own_messages: ç·¨è¼¯è‡ªå·±çš„è¨Šæ¯ - permission_delete_messages: åˆªé™¤è¨Šæ¯ - permission_delete_own_messages: åˆªé™¤è‡ªå·±çš„è¨Šæ¯ - permission_export_wiki_pages: 匯出 wiki é é¢ - permission_manage_subtasks: 管ç†å­ä»»å‹™ - permission_manage_related_issues: 管ç†ç›¸é—œå•題 - - project_module_issue_tracking: å•題追蹤 - project_module_time_tracking: 工時追蹤 - project_module_news: æ–°èž - project_module_documents: 文件 - project_module_files: 檔案 - project_module_wiki: Wiki - project_module_repository: 版本控管 - project_module_boards: è¨Žè«–å€ - project_module_calendar: 日曆 - project_module_gantt: 甘特圖 - - label_user: 用戶 - label_user_plural: 用戶清單 - label_user_new: 建立新用戶 - label_user_anonymous: 匿å用戶 - label_project: 專案 - label_project_new: 建立新專案 - label_project_plural: 專案清單 - label_x_projects: - zero: 無專案 - one: 1 個專案 - other: "%{count} 個專案" - label_project_all: 全部的專案 - label_project_latest: 最近的專案 - label_issue: å•題 - label_issue_new: 建立新å•題 - label_issue_plural: å•題清單 - label_issue_view_all: 檢視所有å•題 - label_issues_by: "å•題按 %{value} 分組顯示" - label_issue_added: å•題已新增 - label_issue_updated: å•題已更新 - label_issue_note_added: 筆記已新增 - label_issue_status_updated: 狀態已更新 - label_issue_priority_updated: 優先權已更新 - label_document: 文件 - label_document_new: 建立新文件 - label_document_plural: 文件 - label_document_added: 文件已新增 - label_role: 角色 - label_role_plural: 角色 - label_role_new: 建立新角色 - label_role_and_permissions: è§’è‰²èˆ‡æ¬Šé™ - label_role_anonymous: 匿å者 - label_role_non_member: éžæœƒå“¡ - label_member: æˆå“¡ - label_member_new: 建立新æˆå“¡ - label_member_plural: æˆå“¡ - label_tracker: 追蹤標籤 - label_tracker_plural: 追蹤標籤清單 - label_tracker_new: 建立新的追蹤標籤 - label_workflow: æµç¨‹ - label_issue_status: å•題狀態 - label_issue_status_plural: å•題狀態清單 - label_issue_status_new: 建立新狀態 - label_issue_category: å•題分類 - label_issue_category_plural: å•題分類清單 - label_issue_category_new: 建立新分類 - label_custom_field: è‡ªè¨‚æ¬„ä½ - label_custom_field_plural: è‡ªè¨‚æ¬„ä½æ¸…å–® - label_custom_field_new: å»ºç«‹æ–°è‡ªè¨‚æ¬„ä½ - label_enumerations: 列舉值清單 - label_enumeration_new: 建立新列舉值 - label_information: 資訊 - label_information_plural: 資訊 - label_please_login: 請先登入 - label_register: 註冊 - label_login_with_open_id_option: 或使用 OpenID 登入 - label_password_lost: éºå¤±å¯†ç¢¼ - label_home: ç¶²ç«™é¦–é  - label_my_page: å¸³æˆ¶é¦–é  - label_my_account: 我的帳戶 - label_my_projects: 我的專案 - label_my_page_block: 帳戶首é å€å¡Š - label_administration: ç¶²ç«™ç®¡ç† - label_login: 登入 - label_logout: 登出 - label_help: 說明 - label_reported_issues: 我通報的å•題 - label_assigned_to_me_issues: 分派給我的å•題 - label_last_login: 最近一次連線 - label_registered_on: 註冊於 - label_activity: 活動 - label_overall_activity: 整體活動 - label_user_activity: "%{value} 的活動" - label_new: 建立新的... - label_logged_as: ç›®å‰ç™»å…¥ - label_environment: 環境 - label_authentication: èªè­‰ - label_auth_source: èªè­‰æ¨¡å¼ - label_auth_source_new: 建立新èªè­‰æ¨¡å¼ - label_auth_source_plural: èªè­‰æ¨¡å¼æ¸…å–® - label_subproject_plural: å­å°ˆæ¡ˆ - label_subproject_new: 建立å­å°ˆæ¡ˆ - label_and_its_subprojects: "%{value} 與其å­å°ˆæ¡ˆ" - label_min_max_length: æœ€å° - 最大 長度 - label_list: 清單 - label_date: 日期 - label_integer: 整數 - label_float: 浮點數 - label_boolean: 布林 - label_string: 文字 - label_text: 長文字 - label_attribute: 屬性 - label_attribute_plural: 屬性 - label_download: "%{count} 個下載" - label_download_plural: "%{count} 個下載" - label_no_data: 沒有任何資料å¯ä¾›é¡¯ç¤º - label_change_status: 變更狀態 - label_history: æ­·å² - label_attachment: 檔案 - label_attachment_new: 建立新檔案 - label_attachment_delete: 刪除檔案 - label_attachment_plural: 檔案 - label_file_added: 檔案已新增 - label_report: 報告 - label_report_plural: 報告 - label_news: æ–°èž - label_news_new: å»ºç«‹æ–°èž - label_news_plural: æ–°èž - label_news_latest: æœ€è¿‘æ–°èž - label_news_view_all: æª¢è¦–å…¨éƒ¨çš„æ–°èž - label_news_added: æ–°èžå·²æ–°å¢ž - label_news_comment_added: å›žæ‡‰å·²åŠ å…¥æ–°èž - label_settings: 設定 - label_overview: 概觀 - label_version: 版本 - label_version_new: 建立新版本 - label_version_plural: 版本 - label_close_versions: çµæŸå·²å®Œæˆçš„版本 - label_confirmation: ç¢ºèª - label_export_to: 匯出至 - label_read: 讀å–... - label_public_projects: 公開專案 - label_open_issues: 進行中 - label_open_issues_plural: 進行中 - label_closed_issues: å·²çµæŸ - label_closed_issues_plural: å·²çµæŸ - label_x_open_issues_abbr_on_total: - zero: 0 進行中 / å…± %{total} - one: 1 進行中 / å…± %{total} - other: "%{count} 進行中 / å…± %{total}" - label_x_open_issues_abbr: - zero: 0 進行中 - one: 1 進行中 - other: "%{count} 進行中" - label_x_closed_issues_abbr: - zero: 0 å·²çµæŸ - one: 1 å·²çµæŸ - other: "%{count} å·²çµæŸ" - label_x_issues: - zero: 0 個å•題 - one: 1 個å•題 - other: "%{count} 個å•題" - label_total: 總計 - label_permissions: æ¬Šé™ - label_current_status: ç›®å‰ç‹€æ…‹ - label_new_statuses_allowed: å¯è®Šæ›´è‡³ä»¥ä¸‹ç‹€æ…‹ - label_all: 全部 - label_any: ä»»æ„一個 - label_none: 空值 - label_nobody: ç„¡å - label_next: ä¸‹ä¸€é  - label_previous: ä¸Šä¸€é  - label_used_by: Used by - label_details: 明細 - label_add_note: 加入一個新筆記 - label_per_page: æ¯é  - label_calendar: 日曆 - label_months_from: 個月, 開始月份 - label_gantt: 甘特圖 - label_internal: 內部 - label_last_changes: "最近 %{count} 個變更" - label_change_view_all: 檢視全部的變更 - label_personalize_page: è‡ªè¨‚ç‰ˆé¢ - label_comment: 回應 - label_comment_plural: 回應 - label_x_comments: - zero: 無回應 - one: 1 個回應 - other: "%{count} 個回應" - label_comment_add: 加入新回應 - label_comment_added: 新回應已加入 - label_comment_delete: 刪除回應 - label_query: 自訂查詢 - label_query_plural: 自訂查詢 - label_query_new: 建立新查詢 - label_my_queries: 我的自訂查詢 - label_filter_add: åŠ å…¥æ–°ç¯©é¸æ¢ä»¶ - label_filter_plural: ç¯©é¸æ¢ä»¶ - label_equals: 等於 - label_not_equals: ä¸ç­‰æ–¼ - label_in_less_than: åœ¨å°æ–¼ - label_in_more_than: 在大於 - label_in_the_next_days: 在未來幾天之內 - label_in_the_past_days: 在éŽå޻幾天之內 - label_greater_or_equal: "大於等於 (>=)" - label_less_or_equal: "å°æ–¼ç­‰æ–¼ (<=)" - label_between: å€é–“ - label_in: 在 - label_today: 今天 - label_all_time: 全部 - label_yesterday: 昨天 - label_this_week: 本週 - label_last_week: 上週 - label_last_n_weeks: "éŽåŽ» %{count} 週" - label_last_n_days: "éŽåŽ» %{count} 天" - label_this_month: 這個月 - label_last_month: 上個月 - label_this_year: 今年 - label_date_range: 日期å€é–“ - label_less_than_ago: å°æ–¼å¹¾å¤©ä¹‹å‰ - label_more_than_ago: å¤§æ–¼å¹¾å¤©ä¹‹å‰ - label_ago: å¤©ä»¥å‰ - label_contains: åŒ…å« - label_not_contains: ä¸åŒ…å« - label_any_issues_in_project: 在專案中的任æ„å•題 - label_any_issues_not_in_project: ä¸åœ¨å°ˆæ¡ˆä¸­çš„ä»»æ„å•題 - label_no_issues_in_project: 沒有å•題在專案中 - label_day_plural: 天 - label_repository: 儲存機制 - label_repository_new: 建立新儲存機制 - label_repository_plural: 儲存機制清單 - label_browse: ç€è¦½ - label_modification: "%{count} 變更" - label_modification_plural: "%{count} 變更" - label_branch: 分支 - label_tag: 標籤 - label_revision: 修訂版 - label_revision_plural: 修訂版清單 - label_revision_id: "修訂版 %{value}" - label_associated_revisions: é—œè¯çš„修訂版 - label_added: 已新增 - label_modified: 已修改 - label_copied: 已複製 - label_renamed: 已釿–°å‘½å - label_deleted: 已刪除 - label_latest_revision: 最新的修訂版 - label_latest_revision_plural: 最新的修訂版清單 - label_view_revisions: 檢視修訂版清單 - label_view_all_revisions: 檢視所有的的修訂版清單 - label_max_size: 最大長度 - label_sort_highest: 移動至開頭 - label_sort_higher: 往上移動 - label_sort_lower: 往下移動 - label_sort_lowest: 移動至çµå°¾ - label_roadmap: 版本è—圖 - label_roadmap_due_in: "剩餘 %{value}" - label_roadmap_overdue: "逾期 %{value}" - label_roadmap_no_issues: 此版本尚未包å«ä»»ä½•å•題 - label_search: æœå°‹ - label_result_plural: çµæžœ - label_all_words: 包å«å…¨éƒ¨çš„字詞 - label_wiki: Wiki - label_wiki_edit: Wiki 編輯 - label_wiki_edit_plural: Wiki 編輯 - label_wiki_page: Wiki ç¶²é  - label_wiki_page_plural: Wiki ç¶²é  - label_index_by_title: 便¨™é¡Œç´¢å¼• - label_index_by_date: 便—¥æœŸç´¢å¼• - label_current_version: ç¾è¡Œç‰ˆæœ¬ - label_preview: é è¦½ - label_feed_plural: Feeds - label_changes_details: 所有變更的明細 - label_issue_tracking: å•題追蹤 - label_spent_time: 耗用工時 - label_overall_spent_time: 整體耗用工時 - label_f_hour: "%{value} å°æ™‚" - label_f_hour_plural: "%{value} å°æ™‚" - label_time_tracking: 工時追蹤 - label_change_plural: 變更 - label_statistics: 統計資訊 - label_commits_per_month: 便œˆä»½çµ±è¨ˆèªå¯ - label_commits_per_author: ä¾ä½œè€…統計èªå¯ - label_view_diff: 檢視差異 - label_diff: 差異 - label_diff_inline: 直列 - label_diff_side_by_side: 並排 - label_options: é¸é …清單 - label_copy_workflow_from: 從以下追蹤標籤複製工作æµç¨‹ - label_permissions_report: 權é™å ±è¡¨ - label_watched_issues: 監看中的å•題清單 - label_related_issues: 相關的å•題清單 - label_applied_status: 已套用狀態 - label_loading: 載入中... - label_relation_new: å»ºç«‹æ–°é—œè¯ - label_relation_delete: åˆªé™¤é—œè¯ - label_relates_to: é—œè¯è‡³ - label_duplicates: å·²é‡è¤‡ - label_duplicated_by: èˆ‡å¾Œé¢æ‰€åˆ—å•題é‡è¤‡ - label_blocks: 阻擋 - label_blocked_by: 被阻擋 - label_precedes: 優先於 - label_follows: 跟隨於 - label_copied_to: 複製到 - label_copied_from: 複製於 - label_end_to_start: çµæŸâ”€é–‹å§‹ - label_end_to_end: çµæŸâ”€çµæŸ - label_start_to_start: 開始─開始 - label_start_to_end: é–‹å§‹â”€çµæŸ - label_stay_logged_in: ç¶­æŒå·²ç™»å…¥ç‹€æ…‹ - label_disabled: 關閉 - label_show_completed_versions: 顯示已完æˆçš„版本 - label_me: 我自己 - label_board: 論壇 - label_board_new: 建立新論壇 - label_board_plural: 論壇 - label_board_locked: 鎖定 - label_board_sticky: 置頂 - label_topic_plural: 討論主題 - label_message_plural: è¨Šæ¯ - label_message_last: 上一å°è¨Šæ¯ - label_message_new: å»ºç«‹æ–°è¨Šæ¯ - label_message_posted: 訊æ¯å·²æ–°å¢ž - label_reply_plural: 回應 - label_send_information: 寄é€å¸³æˆ¶è³‡è¨Šé›»å­éƒµä»¶çµ¦ç”¨æˆ¶ - label_year: å¹´ - label_month: 月 - label_week: 週 - label_date_from: é–‹å§‹ - label_date_to: çµæŸ - label_language_based: ä¾ç”¨æˆ¶ä¹‹èªžç³»æ±ºå®š - label_sort_by: "按 %{value} 排åº" - label_send_test_email: 坄逿¸¬è©¦éƒµä»¶ - label_feeds_access_key: RSS å­˜å–金鑰 - label_missing_feeds_access_key: 找ä¸åˆ° RSS å­˜å–金鑰 - label_feeds_access_key_created_on: "RSS å­˜å–éµå»ºç«‹æ–¼ %{value} 之å‰" - label_module_plural: 模組 - label_added_time_by: "是由 %{author} æ–¼ %{age} å‰åŠ å…¥" - label_updated_time_by: "是由 %{author} æ–¼ %{age} 剿›´æ–°" - label_updated_time: "æ–¼ %{value} 剿›´æ–°" - label_jump_to_a_project: 鏿“‡æ¬²å‰å¾€çš„專案... - label_file_plural: 檔案清單 - label_changeset_plural: 變更集清單 - label_default_columns: é è¨­æ¬„使¸…å–® - label_no_change_option: (ç¶­æŒä¸è®Š) - label_bulk_edit_selected_issues: 大é‡ç·¨è¼¯é¸å–çš„å•題 - label_bulk_edit_selected_time_entries: 大é‡ç·¨è¼¯é¸å–的工時項目 - label_theme: ç•«é¢ä¸»é¡Œ - label_default: é è¨­ - label_search_titles_only: 僅æœå°‹æ¨™é¡Œ - label_user_mail_option_all: "æé†’與我的專案有關的全部事件" - label_user_mail_option_selected: "åªæé†’æˆ‘æ‰€é¸æ“‡å°ˆæ¡ˆä¸­çš„事件..." - label_user_mail_option_none: "å–æ¶ˆæé†’" - label_user_mail_option_only_my_events: "åªæé†’æˆ‘è§€å¯Ÿä¸­æˆ–åƒèˆ‡ä¸­çš„事物" - label_user_mail_option_only_assigned: "åªæé†’æˆ‘è¢«æŒ‡æ´¾çš„äº‹ç‰©" - label_user_mail_option_only_owner: "åªæé†’æˆ‘ä½œç‚ºæ“æœ‰è€…的事物" - label_user_mail_no_self_notified: "ä¸æé†’æˆ‘è‡ªå·±æ‰€åšçš„變更" - label_registration_activation_by_email: é€éŽé›»å­éƒµä»¶å•Ÿç”¨å¸³æˆ¶ - label_registration_manual_activation: 手動啟用帳戶 - label_registration_automatic_activation: 自動啟用帳戶 - label_display_per_page: "æ¯é é¡¯ç¤º: %{value} 個" - label_age: 年齡 - label_change_properties: 變更屬性 - label_general: 一般 - label_more: 更多 » - label_scm: 版本控管 - label_plugins: 附加元件 - label_ldap_authentication: LDAP èªè­‰ - label_downloads_abbr: 下載 - label_optional_description: é¡å¤–的說明 - label_add_another_file: 增加其他檔案 - label_preferences: å好é¸é … - label_chronological_order: 以時間由é è‡³è¿‘æŽ’åº - label_reverse_chronological_order: ä»¥æ™‚é–“ç”±è¿‘è‡³é æŽ’åº - label_planning: 計劃表 - label_incoming_emails: 傳入的電å­éƒµä»¶ - label_generate_key: 產生金鑰 - label_issue_watchers: 監看者 - label_example: 範例 - label_display: 顯示 - label_sort: æŽ’åº - label_ascending: éžå¢žæŽ’åº - label_descending: éžæ¸›æŽ’åº - label_date_from_to: èµ· %{start} è¿„ %{end} - label_wiki_content_added: Wiki é é¢å·²æ–°å¢ž - label_wiki_content_updated: Wiki é é¢å·²æ›´æ–° - label_group: 群組 - label_group_plural: 群組清單 - label_group_new: 建立新群組 - label_time_entry_plural: 耗用工時 - label_version_sharing_none: ä¸å…±ç”¨ - label_version_sharing_descendants: 與å­å°ˆæ¡ˆå…±ç”¨ - label_version_sharing_hierarchy: 與專案階層架構共用 - label_version_sharing_tree: 與專案樹共用 - label_version_sharing_system: 與全部的專案共用 - label_update_issue_done_ratios: æ›´æ–°å•題完æˆç™¾åˆ†æ¯” - label_copy_source: ä¾†æº - label_copy_target: 目的地 - label_copy_same_as_target: èˆ‡ç›®çš„åœ°ç›¸åŒ - label_display_used_statuses_only: 僅顯示此追蹤標籤所使用之狀態 - label_api_access_key: API å­˜å–金鑰 - label_missing_api_access_key: 找ä¸åˆ° API å­˜å–金鑰 - label_api_access_key_created_on: "API å­˜å–金鑰建立於 %{value} 之å‰" - label_profile: é…ç½®æ¦‚æ³ - label_subtask_plural: å­ä»»å‹™ - label_project_copy_notifications: 在複製專案的éŽç¨‹ä¸­ï¼Œå‚³é€é€šçŸ¥éƒµä»¶ - label_principal_search: "æœå°‹ç”¨æˆ¶æˆ–群組:" - label_user_search: "æœå°‹ç”¨æˆ¶ï¼š" - label_additional_workflow_transitions_for_author: 用戶為作者時é¡å¤–å…許的æµç¨‹è½‰æ› - label_additional_workflow_transitions_for_assignee: 用戶為被指定者時é¡å¤–å…許的æµç¨‹è½‰æ› - label_issues_visibility_all: 所有å•題 - label_issues_visibility_public: 所有éžç§äººå•題 - label_issues_visibility_own: 使用者所建立的或被指派的å•題 - label_git_report_last_commit: 報告最後èªå¯çš„æ–‡ä»¶å’Œç›®éŒ„ - label_parent_revision: 父項 - label_child_revision: å­é … - label_export_options: "%{export_format} 匯出é¸é …" - label_copy_attachments: 複製附件 - label_copy_subtasks: 複製å­ä»»å‹™ - label_item_position: "%{position} / %{count}" - label_completed_versions: 已完æˆç‰ˆæœ¬ - label_search_for_watchers: æœå°‹å¯ä¾›åŠ å…¥çš„ç›£çœ‹è€… - label_session_expiration: 工作階段逾期 - label_show_closed_projects: 檢視已關閉的專案 - label_status_transitions: ç‹€æ…‹è½‰æ› - label_fields_permissions: æ¬„ä½æ¬Šé™ - label_readonly: 唯讀 - label_required: å¿…å¡« - label_attribute_of_project: "專案是 %{name}" - label_attribute_of_author: "作者是 %{name}" - label_attribute_of_assigned_to: "被指派者是 %{name}" - label_attribute_of_fixed_version: "版本是 %{name}" - label_cross_project_descendants: 與å­å°ˆæ¡ˆå…±ç”¨ - label_cross_project_tree: 與專案樹共用 - label_cross_project_hierarchy: 與專案階層架構共用 - label_cross_project_system: 與全部的專案共用 - - button_login: 登入 - button_submit: é€å‡º - button_save: 儲存 - button_check_all: å…¨é¸ - button_uncheck_all: å…¨ä¸é¸ - button_collapse_all: 全部摺疊 - button_expand_all: 全部展開 - button_delete: 刪除 - button_create: 建立 - button_create_and_continue: 繼續建立 - button_test: 測試 - button_edit: 編輯 - button_edit_associated_wikipage: "編輯相關 Wiki é é¢: %{page_title}" - button_add: 新增 - button_change: 修改 - button_apply: 套用 - button_clear: 清除 - button_lock: 鎖定 - button_unlock: 解除鎖定 - button_download: 下載 - button_list: 清單 - button_view: 檢視 - button_move: 移動 - button_move_and_follow: 移動後跟隨 - button_back: 返回 - button_cancel: å–æ¶ˆ - button_activate: 啟用 - button_sort: æŽ’åº - button_log_time: 記錄時間 - button_rollback: 還原至此版本 - button_watch: 觀察 - button_unwatch: å–æ¶ˆè§€å¯Ÿ - button_reply: 回應 - button_archive: å°å­˜ - button_unarchive: å–æ¶ˆå°å­˜ - button_reset: 回復 - button_rename: 釿–°å‘½å - button_change_password: 變更密碼 - button_copy: 複製 - button_copy_and_follow: 複製後跟隨 - button_annotate: 註解 - button_update: æ›´æ–° - button_configure: 設定 - button_quote: 引用 - button_duplicate: é‡è£½ - button_show: 顯示 - button_hide: éš±è— - button_edit_section: 編輯此å€å¡Š - button_export: 匯出 - button_delete_my_account: 刪除我的帳戶 - button_close: 關閉 - button_reopen: 釿–°é–‹å•Ÿ - - status_active: 活動中 - status_registered: è¨»å†Šå®Œæˆ - status_locked: 鎖定中 - - project_status_active: 使用中 - project_status_closed: 已關閉 - project_status_archived: å·²å°å­˜ - - version_status_open: 進行中 - version_status_locked: 已鎖定 - version_status_closed: å·²çµæŸ - - field_active: 活動中 - - text_select_mail_notifications: 鏿“‡æ¬²å¯„é€æé†’é€šçŸ¥éƒµä»¶ä¹‹å‹•ä½œ - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 代表「ä¸é™åˆ¶ã€ - text_project_destroy_confirmation: 您確定è¦åˆªé™¤é€™å€‹å°ˆæ¡ˆå’Œå…¶ä»–相關資料? - text_subprojects_destroy_warning: "下列å­å°ˆæ¡ˆï¼š %{value} 將一併被刪除。" - text_workflow_edit: 鏿“‡è§’色與追蹤標籤以設定其工作æµç¨‹ - text_are_you_sure: 確定執行? - text_journal_changed: "%{label} 從 %{old} 變更為 %{new}" - text_journal_changed_no_detail: "%{label} 已更新" - text_journal_set_to: "%{label} 設定為 %{value}" - text_journal_deleted: "%{label} 已刪除 (%{old})" - text_journal_added: "%{label} %{value} 已新增" - text_tip_issue_begin_day: 今天起始的å•題 - text_tip_issue_end_day: 今天截止的的å•題 - text_tip_issue_begin_end_day: 今天起始與截止的å•題 - text_project_identifier_info: '僅å…許使用å°å¯«è‹±æ–‡å­—æ¯ (a-z), 阿拉伯數字, 虛線與底線。
    一旦儲存之後, ä»£ç¢¼ä¾¿ç„¡æ³•å†æ¬¡è¢«æ›´æ”¹ã€‚' - text_caracters_maximum: "最多 %{count} 個字元." - text_caracters_minimum: "長度必須大於 %{count} 個字元." - text_length_between: "長度必須介於 %{min} 至 %{max} 個字元之間." - text_tracker_no_workflow: 此追蹤標籤尚未定義工作æµç¨‹ - text_unallowed_characters: ä¸å…許的字元 - text_comma_separated: å¯è¼¸å…¥å¤šå€‹å€¼ï¼ˆé ˆä»¥é€—號分隔)。 - text_line_separated: å¯è¼¸å…¥å¤šå€‹å€¼ï¼ˆé ˆä»¥æ›è¡Œç¬¦è™Ÿåˆ†éš”ï¼Œå³æ¯åˆ—åªèƒ½è¼¸å…¥ä¸€å€‹å€¼ï¼‰ã€‚ - text_issues_ref_in_commit_messages: èªå¯è¨Šæ¯ä¸­åƒç…§(或修正)å•題之關éµå­— - text_issue_added: "å•題 %{id} 已被 %{author} 通報。" - text_issue_updated: "å•題 %{id} 已被 %{author} 更新。" - text_wiki_destroy_confirmation: 您確定è¦åˆªé™¤é€™å€‹ wiki 和其中的所有內容? - text_issue_category_destroy_question: "有 (%{count}) 個å•題被指派到此分類. è«‹é¸æ“‡æ‚¨æƒ³è¦çš„動作?" - text_issue_category_destroy_assignments: 移除這些å•題的分類 - text_issue_category_reassign_to: 釿–°æŒ‡æ´¾é€™äº›å•題至其它分類 - text_user_mail_option: "å°æ–¼é‚£äº›æœªè¢«é¸æ“‡çš„å°ˆæ¡ˆï¼Œå°‡åªæœƒæŽ¥æ”¶åˆ°æ‚¨æ­£åœ¨è§€å¯Ÿä¸­ï¼Œæˆ–是åƒèˆ‡ä¸­çš„å•題通知。(「åƒèˆ‡ä¸­çš„å•題ã€åŒ…嫿‚¨å»ºç«‹çš„æˆ–是指派給您的å•題)" - text_no_configuration_data: "角色ã€è¿½è¹¤æ¨™ç±¤ã€å•題狀態與æµç¨‹å°šæœªè¢«è¨­å®šå®Œæˆã€‚\n強烈建議您先載入é è¨­çš„組態。將é è¨­çµ„態載入之後,您å¯å†è®Šæ›´å…¶ä¸­ä¹‹å€¼ã€‚" - text_load_default_configuration: 載入é è¨­çµ„æ…‹ - text_status_changed_by_changeset: "已套用至變更集 %{value}." - text_time_logged_by_changeset: "紀錄於變更集 %{value}." - text_issues_destroy_confirmation: 'ç¢ºå®šåˆªé™¤å·²é¸æ“‡çš„å•題?' - text_issues_destroy_descendants_confirmation: "這麼åšå°‡æœƒä¸€ä½µåˆªé™¤ %{count} å­ä»»å‹™ã€‚" - text_time_entries_destroy_confirmation: 您確定è¦åˆªé™¤æ‰€é¸æ“‡çš„工時紀錄? - text_select_project_modules: '鏿“‡æ­¤å°ˆæ¡ˆå¯ä½¿ç”¨ä¹‹æ¨¡çµ„:' - text_default_administrator_account_changed: 已變更é è¨­ç®¡ç†å“¡å¸³è™Ÿå…§å®¹ - text_file_repository_writable: å¯å¯«å…¥é™„加檔案目錄 - text_plugin_assets_writable: å¯å¯«å…¥é™„加元件目錄 - text_rmagick_available: å¯ä½¿ç”¨ RMagick (é¸é…) - text_destroy_time_entries_question: 您å³å°‡åˆªé™¤çš„å•題已報工 %{hours} å°æ™‚. æ‚¨çš„é¸æ“‡æ˜¯ï¼Ÿ - text_destroy_time_entries: 刪除已報工的時數 - text_assign_time_entries_to_project: 指定已報工的時數至專案中 - text_reassign_time_entries: '釿–°æŒ‡å®šå·²å ±å·¥çš„æ™‚數至此å•題:' - text_user_wrote: "%{value} å…ˆå‰æåˆ°:" - text_enumeration_destroy_question: "ç›®å‰æœ‰ %{count} 個物件使用此列舉值。" - text_enumeration_category_reassign_to: '釿–°è¨­å®šå…¶åˆ—舉值為:' - text_email_delivery_not_configured: "您尚未設定電å­éƒµä»¶å‚³é€æ–¹å¼ï¼Œå› æ­¤æé†’é¸é …已被åœç”¨ã€‚\n請在 config/configuration.yml 中設定 SMTP ä¹‹å¾Œï¼Œé‡æ–°å•Ÿå‹• Redmine,以啟用電å­éƒµä»¶æé†’é¸é …。" - text_repository_usernames_mapping: "鏿“‡æˆ–æ›´æ–° Redmine ä½¿ç”¨è€…èˆ‡å„²å­˜æ©Ÿåˆ¶ç´€éŒ„ä½¿ç”¨è€…ä¹‹å°æ‡‰é—œä¿‚。\n儲存機制中之使用者帳號或電å­éƒµä»¶ä¿¡ç®±ï¼Œèˆ‡ Redmine 設定相åŒè€…ï¼Œå°‡è‡ªå‹•ç”¢ç”Ÿå°æ‡‰é—œä¿‚。" - text_diff_truncated: '... 這份差異已被截短以符åˆé¡¯ç¤ºè¡Œæ•¸ä¹‹æœ€å¤§å€¼' - text_custom_field_possible_values_info: '一列輸入一個值' - text_wiki_page_destroy_question: "æ­¤é é¢åŒ…å« %{descendants} 個å­é é¢åŠå»¶ä¼¸é é¢ã€‚ è«‹é¸æ“‡æ‚¨æƒ³è¦çš„動作?" - text_wiki_page_nullify_children: "ä¿ç•™æ‰€æœ‰å­é é¢ç•¶ä½œæ ¹é é¢" - text_wiki_page_destroy_children: "刪除所有å­é é¢åŠå…¶å»¶ä¼¸é é¢" - text_wiki_page_reassign_children: "釿–°æŒ‡å®šæ‰€æœ‰çš„å­é é¢ä¹‹çˆ¶é é¢è‡³æ­¤é é¢" - text_own_membership_delete_confirmation: "æ‚¨åœ¨å°ˆæ¡ˆä¸­ï¼Œæ‰€æ“æœ‰çš„部分或全部權é™å³å°‡è¢«ç§»é™¤ï¼Œåœ¨é€™ä¹‹å¾Œå¯èƒ½ç„¡æ³•冿¬¡ç·¨è¼¯æ­¤å°ˆæ¡ˆã€‚\n您確定è¦ç¹¼çºŒåŸ·è¡Œé€™å€‹å‹•作?" - text_zoom_in: 放大 - text_zoom_out: ç¸®å° - text_warn_on_leaving_unsaved: "若您離開這個é é¢ï¼Œæ­¤é é¢æ‰€åŒ…å«çš„æœªå„²å­˜è³‡æ–™å°‡æœƒéºå¤±ã€‚" - text_scm_path_encoding_note: "é è¨­: UTF-8" - text_git_repository_note: 儲存機制是本機的空(bare)目錄 (å³ï¼š /gitrepo, c:\gitrepo) - text_mercurial_repository_note: 本機儲存機制 (å³ï¼š /hgrepo, c:\hgrepo) - text_scm_command: 命令 - text_scm_command_version: 版本 - text_scm_config: 您å¯ä»¥åœ¨ config/configuration.yml 中設定 SCM å‘½ä»¤ã€‚è«‹åœ¨ç·¨è¼¯è©²æª”æ¡ˆä¹‹å¾Œé‡æ–°å•Ÿå‹• Redmine 應用程å¼ã€‚ - text_scm_command_not_available: SCM 命令無法使用。請檢查管ç†é¢æ¿ä¸­çš„設定。 - text_issue_conflict_resolution_overwrite: "直接套用我的變更 (å…ˆå‰çš„筆記將會被ä¿ç•™ï¼Œä½†æ˜¯æŸäº›è®Šæ›´å¯èƒ½æœƒè¢«è¤‡å¯«)" - text_issue_conflict_resolution_add_notes: "æ–°å¢žæˆ‘çš„ç­†è¨˜ä¸¦æ¨æ£„我其他的變更" - text_issue_conflict_resolution_cancel: "æ¨æ£„æˆ‘å…¨éƒ¨çš„è®Šæ›´ä¸¦é‡æ–°é¡¯ç¤º %{link}" - text_account_destroy_confirmation: |- - 您確定è¦ç¹¼çºŒé€™å€‹å‹•作嗎? - æ‚¨çš„å¸³æˆ¶å°‡æœƒè¢«æ°¸ä¹…åˆªé™¤ï¼Œä¸”ç„¡æ³•è¢«é‡æ–°å•Ÿç”¨ã€‚ - text_session_expiration_settings: "è­¦å‘Šï¼šè®Šæ›´é€™äº›è¨­å®šå°‡æœƒå°Žè‡´åŒ…å«æ‚¨åœ¨å…§çš„æ‰€æœ‰å·¥ä½œéšŽæ®µéŽæœŸã€‚" - text_project_closed: 此專案已被關閉,僅供唯讀使用。 - - default_role_manager: 管ç†äººå“¡ - default_role_developer: 開發人員 - default_role_reporter: 報告人員 - default_tracker_bug: 臭蟲 - default_tracker_feature: 功能 - default_tracker_support: æ”¯æ´ - default_issue_status_new: 新建立 - default_issue_status_in_progress: 實作中 - default_issue_status_resolved: 已解決 - default_issue_status_feedback: 已回應 - default_issue_status_closed: å·²çµæŸ - default_issue_status_rejected: 已拒絕 - default_doc_category_user: 使用手冊 - default_doc_category_tech: 技術文件 - default_priority_low: 低 - default_priority_normal: 正常 - default_priority_high: 高 - default_priority_urgent: 速 - default_priority_immediate: 急 - default_activity_design: 設計 - default_activity_development: 開發 - - enumeration_issue_priorities: å•題優先權 - enumeration_doc_categories: 文件分類 - enumeration_activities: 活動 (時間追蹤) - enumeration_system_activity: 系統活動 - description_filter: ç¯©é¸æ¢ä»¶ - description_search: æœå°‹æ¬„ä½ - description_choose_project: 專案清單 - description_project_scope: æœå°‹ç¯„åœ - description_notes: 筆記 - description_message_content: 訊æ¯å…§å®¹ - description_query_sort_criteria_attribute: 排åºå±¬æ€§ - description_query_sort_criteria_direction: æŽ’åˆ—é †åº - description_user_mail_notification: 郵件通知設定 - description_available_columns: å¯ç”¨æ¬„ä½ - description_selected_columns: å·²é¸å–çš„æ¬„ä½ - description_all_columns: æ‰€æœ‰æ¬„ä½ - description_issue_category_reassign: 鏿“‡å•題分類 - description_wiki_subpages_reassign: 鏿“‡æ–°çš„父é é¢ - description_date_range_list: 從清單中é¸å–ç¯„åœ - description_date_range_interval: 鏿“‡èµ·å§‹èˆ‡çµæŸæ—¥æœŸä»¥è¨­å®šç¯„åœå€é–“ - description_date_from: 輸入起始日期 - description_date_to: è¼¸å…¥çµæŸæ—¥æœŸ - text_repository_identifier_info: '僅å…許使用å°å¯«è‹±æ–‡å­—æ¯ (a-z), 阿拉伯數字, 虛線與底線。
    一旦儲存之後, ä»£ç¢¼ä¾¿ç„¡æ³•å†æ¬¡è¢«æ›´æ”¹ã€‚' diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b6/b6a2a69382c779cf0cb5b9cf78c2974d34b88329.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b6/b6a2a69382c779cf0cb5b9cf78c2974d34b88329.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,46 @@ +
    +<%= link_to(l(:label_attachment_new), new_project_file_path(@project), :class => 'icon icon-add') if User.current.allowed_to?(:manage_files, @project) %> +
    + +

    <%=l(:label_attachment_plural)%>

    + +<% delete_allowed = User.current.allowed_to?(:manage_files, @project) %> + + + + <%= sort_header_tag('filename', :caption => l(:field_filename)) %> + <%= sort_header_tag('created_on', :caption => l(:label_date), :default_order => 'desc') %> + <%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %> + <%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %> + + + + +<% @containers.each do |container| %> + <% next if container.attachments.empty? -%> + <% if container.is_a?(Version) -%> + + + + <% end -%> + <% container.attachments.each do |file| %> + "> + + + + + + + + <% end + reset_cycle %> +<% end %> + +
    MD5
    + <%= link_to(h(container), {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %> +
    <%= link_to_attachment file, :download => true, :title => file.description %><%= format_time(file.created_on) %><%= number_to_human_size(file.filesize) %><%= file.downloads %><%= file.digest %> + <%= link_to(image_tag('delete.png'), attachment_path(file), + :data => {:confirm => l(:text_are_you_sure)}, :method => :delete) if delete_allowed %> +
    + +<% html_title(l(:label_attachment_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b6/b6ae26217d2d8b94a4209ce1a20c88a27342bb5b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b6/b6ae26217d2d8b94a4209ce1a20c88a27342bb5b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,171 @@ + + + + + +Форматирование Wiki + + + + +

    СинтакÑÐ¸Ñ Wiki ÐšÑ€Ð°Ñ‚ÐºÐ°Ñ Ð¡Ð¿Ñ€Ð°Ð²ÐºÐ°

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Стили Шрифтов
    Выделенный*Выделенный*Выделенный
    Ðаклонный_Ðаклонный_Ðаклонный
    Подчёркнутый+Подчёркнутый+ + Подчёркнутый +
    Зачёркнутый-Зачёркнутый- + Зачёркнутый +
    ??Цитата??Цитата
    Ð’Ñтавка Кода@Ð’Ñтавка Кода@Ð’Ñтавка Кода
    Отформатированный текÑÑ‚<pre>
     Ñтроки
     ÐºÐ¾Ð´Ð°
    </pre>
    +
    + Ñтроки
    + кода
    +            
    +
    СпиÑки
    ÐеÑортированный ÑпиÑок* Элемент 1
    * Элемент 2
    +
      +
    • Элемент 1
    • +
    • Элемент 2
    • +
    +
    Сортированный ÑпиÑок# Элемент 1
    # Элемент 2
    +
      +
    1. Элемент 1
    2. +
    3. Элемент 2
    4. +
    +
    Заголовки
    Заголовок 1h1. Ðазвание 1

    Ðазвание 1

    Заголовок 2h2. Ðазвание 2

    Ðазвание 2

    Заголовок 3h3. Ðазвание 3

    Ðазвание 3

    СÑылки
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    СÑылки Redmine
    СÑылка на Wiki Ñтраницу[[Wiki Ñтраница]]Wiki Ñтраница
    Задача #12Задача #12
    ФикÑÐ°Ñ†Ð¸Ñ r43ФикÑÐ°Ñ†Ð¸Ñ r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Ð’Ñтавка изображений
    Изображение!url_картинки!
    !вложенный_файл!
    + +

    Больше информации

    + + + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b6/b6af6ff709a2c46123e5e47c3c80bd99fd0c8e17.svn-base --- a/.svn/pristine/b6/b6af6ff709a2c46123e5e47c3c80bd99fd0c8e17.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module ReportsHelper - - def aggregate(data, criteria) - a = 0 - data.each { |row| - match = 1 - criteria.each { |k, v| - match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t")) - } unless criteria.nil? - a = a + row["total"].to_i if match == 1 - } unless data.nil? - a - end - - def aggregate_link(data, criteria, *args) - a = aggregate data, criteria - a > 0 ? link_to(h(a), *args) : '-' - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b7/b75d5f0ff60be5f80b39e65c645dcf825ec9984b.svn-base --- a/.svn/pristine/b7/b75d5f0ff60be5f80b39e65c645dcf825ec9984b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,462 +0,0 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang -# -# 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. - -require 'redmine/scm/adapters/abstract_adapter' - -module Redmine - module Scm - module Adapters - class CvsAdapter < AbstractAdapter - - # CVS executable name - CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs" - - class << self - def client_command - @@bin ||= CVS_BIN - end - - def sq_bin - @@sq_bin ||= shell_quote_command - end - - def client_version - @@client_version ||= (scm_command_version || []) - end - - def client_available - client_version_above?([1, 12]) - end - - def scm_command_version - scm_version = scm_version_from_command_line.dup - if scm_version.respond_to?(:force_encoding) - scm_version.force_encoding('ASCII-8BIT') - end - if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}m) - m[2].scan(%r{\d+}).collect(&:to_i) - end - end - - def scm_version_from_command_line - shellout("#{sq_bin} --version") { |io| io.read }.to_s - end - end - - # Guidelines for the input: - # url -> the project-path, relative to the cvsroot (eg. module name) - # root_url -> the good old, sometimes damned, CVSROOT - # login -> unnecessary - # password -> unnecessary too - def initialize(url, root_url=nil, login=nil, password=nil, - path_encoding=nil) - @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding - @url = url - # TODO: better Exception here (IllegalArgumentException) - raise CommandFailed if root_url.blank? - @root_url = root_url - - # These are unused. - @login = login if login && !login.empty? - @password = (password || "") if @login - end - - def path_encoding - @path_encoding - end - - def info - logger.debug " info" - Info.new({:root_url => @root_url, :lastrev => nil}) - end - - def get_previous_revision(revision) - CvsRevisionHelper.new(revision).prevRev - end - - # Returns an Entries collection - # or nil if the given path doesn't exist in the repository - # this method is used by the repository-browser (aka LIST) - def entries(path=nil, identifier=nil, options={}) - logger.debug " entries '#{path}' with identifier '#{identifier}'" - path_locale = scm_iconv(@path_encoding, 'UTF-8', path) - path_locale.force_encoding("ASCII-8BIT") if path_locale.respond_to?(:force_encoding) - entries = Entries.new - cmd_args = %w|-q rls -e| - cmd_args << "-D" << time_to_cvstime_rlog(identifier) if identifier - cmd_args << path_with_proj(path) - scm_cmd(*cmd_args) do |io| - io.each_line() do |line| - fields = line.chop.split('/',-1) - logger.debug(">>InspectLine #{fields.inspect}") - if fields[0]!="D" - time = nil - # Thu Dec 13 16:27:22 2007 - time_l = fields[-3].split(' ') - if time_l.size == 5 && time_l[4].length == 4 - begin - time = Time.parse( - "#{time_l[1]} #{time_l[2]} #{time_l[3]} GMT #{time_l[4]}") - rescue - end - end - entries << Entry.new( - { - :name => scm_iconv('UTF-8', @path_encoding, fields[-5]), - #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]), - :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[-5]}"), - :kind => 'file', - :size => nil, - :lastrev => Revision.new( - { - :revision => fields[-4], - :name => scm_iconv('UTF-8', @path_encoding, fields[-4]), - :time => time, - :author => '' - }) - }) - else - entries << Entry.new( - { - :name => scm_iconv('UTF-8', @path_encoding, fields[1]), - :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[1]}"), - :kind => 'dir', - :size => nil, - :lastrev => nil - }) - end - end - end - entries.sort_by_name - rescue ScmCommandAborted - nil - end - - STARTLOG="----------------------------" - ENDLOG ="=============================================================================" - - # Returns all revisions found between identifier_from and identifier_to - # in the repository. both identifier have to be dates or nil. - # these method returns nothing but yield every result in block - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block) - path_with_project_utf8 = path_with_proj(path) - path_with_project_locale = scm_iconv(@path_encoding, 'UTF-8', path_with_project_utf8) - logger.debug " revisions path:" + - "'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" - cmd_args = %w|-q rlog| - cmd_args << "-d" << ">#{time_to_cvstime_rlog(identifier_from)}" if identifier_from - cmd_args << path_with_project_utf8 - scm_cmd(*cmd_args) do |io| - state = "entry_start" - commit_log = String.new - revision = nil - date = nil - author = nil - entry_path = nil - entry_name = nil - file_state = nil - branch_map = nil - io.each_line() do |line| - if state != "revision" && /^#{ENDLOG}/ =~ line - commit_log = String.new - revision = nil - state = "entry_start" - end - if state == "entry_start" - branch_map = Hash.new - if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project_locale)}(.+),v$/ =~ line - entry_path = normalize_cvs_path($1) - entry_name = normalize_path(File.basename($1)) - logger.debug("Path #{entry_path} <=> Name #{entry_name}") - elsif /^head: (.+)$/ =~ line - entry_headRev = $1 #unless entry.nil? - elsif /^symbolic names:/ =~ line - state = "symbolic" #unless entry.nil? - elsif /^#{STARTLOG}/ =~ line - commit_log = String.new - state = "revision" - end - next - elsif state == "symbolic" - if /^(.*):\s(.*)/ =~ (line.strip) - branch_map[$1] = $2 - else - state = "tags" - next - end - elsif state == "tags" - if /^#{STARTLOG}/ =~ line - commit_log = "" - state = "revision" - elsif /^#{ENDLOG}/ =~ line - state = "head" - end - next - elsif state == "revision" - if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line - if revision - revHelper = CvsRevisionHelper.new(revision) - revBranch = "HEAD" - branch_map.each() do |branch_name, branch_point| - if revHelper.is_in_branch_with_symbol(branch_point) - revBranch = branch_name - end - end - logger.debug("********** YIELD Revision #{revision}::#{revBranch}") - yield Revision.new({ - :time => date, - :author => author, - :message => commit_log.chomp, - :paths => [{ - :revision => revision.dup, - :branch => revBranch.dup, - :path => scm_iconv('UTF-8', @path_encoding, entry_path), - :name => scm_iconv('UTF-8', @path_encoding, entry_name), - :kind => 'file', - :action => file_state - }] - }) - end - commit_log = String.new - revision = nil - if /^#{ENDLOG}/ =~ line - state = "entry_start" - end - next - end - - if /^branches: (.+)$/ =~ line - # TODO: version.branch = $1 - elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line - revision = $1 - elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line - date = Time.parse($1) - line_utf8 = scm_iconv('UTF-8', options[:log_encoding], line) - author_utf8 = /author: ([^;]+)/.match(line_utf8)[1] - author = scm_iconv(options[:log_encoding], 'UTF-8', author_utf8) - file_state = /state: ([^;]+)/.match(line)[1] - # TODO: - # linechanges only available in CVS.... - # maybe a feature our SVN implementation. - # I'm sure, they are useful for stats or something else - # linechanges =/lines: \+(\d+) -(\d+)/.match(line) - # unless linechanges.nil? - # version.line_plus = linechanges[1] - # version.line_minus = linechanges[2] - # else - # version.line_plus = 0 - # version.line_minus = 0 - # end - else - commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/ - end - end - end - end - rescue ScmCommandAborted - Revisions.new - end - - def diff(path, identifier_from, identifier_to=nil) - logger.debug " diff path:'#{path}'" + - ",identifier_from #{identifier_from}, identifier_to #{identifier_to}" - cmd_args = %w|rdiff -u| - cmd_args << "-r#{identifier_to}" - cmd_args << "-r#{identifier_from}" - cmd_args << path_with_proj(path) - diff = [] - scm_cmd(*cmd_args) do |io| - io.each_line do |line| - diff << line - end - end - diff - rescue ScmCommandAborted - nil - end - - def cat(path, identifier=nil) - identifier = (identifier) ? identifier : "HEAD" - logger.debug " cat path:'#{path}',identifier #{identifier}" - cmd_args = %w|-q co| - cmd_args << "-D" << time_to_cvstime(identifier) if identifier - cmd_args << "-p" << path_with_proj(path) - cat = nil - scm_cmd(*cmd_args) do |io| - io.binmode - cat = io.read - end - cat - rescue ScmCommandAborted - nil - end - - def annotate(path, identifier=nil) - identifier = (identifier) ? identifier : "HEAD" - logger.debug " annotate path:'#{path}',identifier #{identifier}" - cmd_args = %w|rannotate| - cmd_args << "-D" << time_to_cvstime(identifier) if identifier - cmd_args << path_with_proj(path) - blame = Annotate.new - scm_cmd(*cmd_args) do |io| - io.each_line do |line| - next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$} - blame.add_line( - $3.rstrip, - Revision.new( - :revision => $1, - :identifier => nil, - :author => $2.strip - )) - end - end - blame - rescue ScmCommandAborted - Annotate.new - end - - private - - # Returns the root url without the connexion string - # :pserver:anonymous@foo.bar:/path => /path - # :ext:cvsservername:/path => /path - def root_url_path - root_url.to_s.gsub(/^:.+:\d*/, '') - end - - # convert a date/time into the CVS-format - def time_to_cvstime(time) - return nil if time.nil? - time = Time.now if time == 'HEAD' - - unless time.kind_of? Time - time = Time.parse(time) - end - return time_to_cvstime_rlog(time) - end - - def time_to_cvstime_rlog(time) - return nil if time.nil? - t1 = time.clone.localtime - return t1.strftime("%Y-%m-%d %H:%M:%S") - end - - def normalize_cvs_path(path) - normalize_path(path.gsub(/Attic\//,'')) - end - - def normalize_path(path) - path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1') - end - - def path_with_proj(path) - "#{url}#{with_leading_slash(path)}" - end - private :path_with_proj - - class Revision < Redmine::Scm::Adapters::Revision - # Returns the readable identifier - def format_identifier - revision.to_s - end - end - - def scm_cmd(*args, &block) - full_args = ['-d', root_url] - full_args += args - full_args_locale = [] - full_args.map do |e| - full_args_locale << scm_iconv(@path_encoding, 'UTF-8', e) - end - ret = shellout( - self.class.sq_bin + ' ' + full_args_locale.map { |e| shell_quote e.to_s }.join(' '), - &block - ) - if $? && $?.exitstatus != 0 - raise ScmCommandAborted, "cvs exited with non-zero status: #{$?.exitstatus}" - end - ret - end - private :scm_cmd - end - - class CvsRevisionHelper - attr_accessor :complete_rev, :revision, :base, :branchid - - def initialize(complete_rev) - @complete_rev = complete_rev - parseRevision() - end - - def branchPoint - return @base - end - - def branchVersion - if isBranchRevision - return @base+"."+@branchid - end - return @base - end - - def isBranchRevision - !@branchid.nil? - end - - def prevRev - unless @revision == 0 - return buildRevision( @revision - 1 ) - end - return buildRevision( @revision ) - end - - def is_in_branch_with_symbol(branch_symbol) - bpieces = branch_symbol.split(".") - branch_start = "#{bpieces[0..-3].join(".")}.#{bpieces[-1]}" - return ( branchVersion == branch_start ) - end - - private - def buildRevision(rev) - if rev == 0 - if @branchid.nil? - @base + ".0" - else - @base - end - elsif @branchid.nil? - @base + "." + rev.to_s - else - @base + "." + @branchid + "." + rev.to_s - end - end - - # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15 - def parseRevision() - pieces = @complete_rev.split(".") - @revision = pieces.last.to_i - baseSize = 1 - baseSize += (pieces.size / 2) - @base = pieces[0..-baseSize].join(".") - if baseSize > 2 - @branchid = pieces[-2] - end - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b7/b7c7443654af68cf9c522afbacac1ebc7839e6c2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b7/b7c7443654af68cf9c522afbacac1ebc7839e6c2.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class ChangeUsersLastnameLengthTo255 < ActiveRecord::Migration + def self.up + change_column :users, :lastname, :string, :limit => 255, :default => '', :null => false + end + + def self.down + change_column :users, :lastname, :string, :limit => 30, :default => '', :null => false + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b7/b7e57087e2b7452a3c04ea867d9d27d95784c19d.svn-base --- a/.svn/pristine/b7/b7e57087e2b7452a3c04ea867d9d27d95784c19d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - class CustomFieldFormat - include Redmine::I18n - - cattr_accessor :available - @@available = {} - - attr_accessor :name, :order, :label, :edit_as, :class_names - - def initialize(name, options={}) - self.name = name - self.label = options[:label] || "label_#{name}".to_sym - self.order = options[:order] || self.class.available_formats.size - self.edit_as = options[:edit_as] || name - self.class_names = options[:only] - end - - def format(value) - send "format_as_#{name}", value - end - - def format_as_date(value) - begin; format_date(value.to_date); rescue; value end - end - - def format_as_bool(value) - l(value == "1" ? :general_text_Yes : :general_text_No) - end - - ['string','text','int','float','list'].each do |name| - define_method("format_as_#{name}") {|value| - return value - } - end - - ['user', 'version'].each do |name| - define_method("format_as_#{name}") {|value| - return value.blank? ? "" : name.classify.constantize.find_by_id(value.to_i).to_s - } - end - - class << self - def map(&block) - yield self - end - - # Registers a custom field format - def register(*args) - custom_field_format = args.first - unless custom_field_format.is_a?(Redmine::CustomFieldFormat) - custom_field_format = Redmine::CustomFieldFormat.new(*args) - end - @@available[custom_field_format.name] = custom_field_format unless @@available.keys.include?(custom_field_format.name) - end - - def available_formats - @@available.keys - end - - def find_by_name(name) - @@available[name.to_s] - end - - def label_for(name) - format = @@available[name.to_s] - format.label if format - end - - # Return an array of custom field formats which can be used in select_tag - def as_select(class_name=nil) - fields = @@available.values - fields = fields.select {|field| field.class_names.nil? || field.class_names.include?(class_name)} - fields.sort {|a,b| - a.order <=> b.order - }.collect {|custom_field_format| - [ l(custom_field_format.label), custom_field_format.name ] - } - end - - def format_value(value, field_format) - return "" unless value && !value.empty? - - if format_type = find_by_name(field_format) - format_type.format(value) - else - value - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b8/b8657f7d2e5a1ade105ba1c1a91e64ff816547b4.svn-base --- a/.svn/pristine/b8/b8657f7d2e5a1ade105ba1c1a91e64ff816547b4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,566 +0,0 @@ -# Copyright (c) 2005 Rick Olson -# -# 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. - -module ActiveRecord #:nodoc: - module Acts #:nodoc: - # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a - # versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version - # column is present as well. - # - # The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart - # your container for the changes to be reflected. In development mode this usually means restarting WEBrick. - # - # class Page < ActiveRecord::Base - # # assumes pages_versions table - # acts_as_versioned - # end - # - # Example: - # - # page = Page.create(:title => 'hello world!') - # page.version # => 1 - # - # page.title = 'hello world' - # page.save - # page.version # => 2 - # page.versions.size # => 2 - # - # page.revert_to(1) # using version number - # page.title # => 'hello world!' - # - # page.revert_to(page.versions.last) # using versioned instance - # page.title # => 'hello world' - # - # page.versions.earliest # efficient query to find the first version - # page.versions.latest # efficient query to find the most recently created version - # - # - # Simple Queries to page between versions - # - # page.versions.before(version) - # page.versions.after(version) - # - # Access the previous/next versions from the versioned model itself - # - # version = page.versions.latest - # version.previous # go back one version - # version.next # go forward one version - # - # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options - module Versioned - CALLBACKS = [:set_new_version, :save_version_on_create, :save_version?, :clear_altered_attributes] - def self.included(base) # :nodoc: - base.extend ClassMethods - end - - module ClassMethods - # == Configuration options - # - # * class_name - versioned model class name (default: PageVersion in the above example) - # * table_name - versioned model table name (default: page_versions in the above example) - # * foreign_key - foreign key used to relate the versioned model to the original model (default: page_id in the above example) - # * inheritance_column - name of the column to save the model's inheritance_column value for STI. (default: versioned_type) - # * version_column - name of the column in the model that keeps the version number (default: version) - # * sequence_name - name of the custom sequence to be used by the versioned model. - # * limit - number of revisions to keep, defaults to unlimited - # * if - symbol of method to check before saving a new version. If this method returns false, a new version is not saved. - # For finer control, pass either a Proc or modify Model#version_condition_met? - # - # acts_as_versioned :if => Proc.new { |auction| !auction.expired? } - # - # or... - # - # class Auction - # def version_condition_met? # totally bypasses the :if option - # !expired? - # end - # end - # - # * if_changed - Simple way of specifying attributes that are required to be changed before saving a model. This takes - # either a symbol or array of symbols. WARNING - This will attempt to overwrite any attribute setters you may have. - # Use this instead if you want to write your own attribute setters (and ignore if_changed): - # - # def name=(new_name) - # write_changed_attribute :name, new_name - # end - # - # * extend - Lets you specify a module to be mixed in both the original and versioned models. You can also just pass a block - # to create an anonymous mixin: - # - # class Auction - # acts_as_versioned do - # def started? - # !started_at.nil? - # end - # end - # end - # - # or... - # - # module AuctionExtension - # def started? - # !started_at.nil? - # end - # end - # class Auction - # acts_as_versioned :extend => AuctionExtension - # end - # - # Example code: - # - # @auction = Auction.find(1) - # @auction.started? - # @auction.versions.first.started? - # - # == Database Schema - # - # The model that you're versioning needs to have a 'version' attribute. The model is versioned - # into a table called #{model}_versions where the model name is singlular. The _versions table should - # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field. - # - # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance, - # then that field is reflected in the versioned model as 'versioned_type' by default. - # - # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table - # method, perfect for a migration. It will also create the version column if the main model does not already have it. - # - # class AddVersions < ActiveRecord::Migration - # def self.up - # # create_versioned_table takes the same options hash - # # that create_table does - # Post.create_versioned_table - # end - # - # def self.down - # Post.drop_versioned_table - # end - # end - # - # == Changing What Fields Are Versioned - # - # By default, acts_as_versioned will version all but these fields: - # - # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column] - # - # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols. - # - # class Post < ActiveRecord::Base - # acts_as_versioned - # self.non_versioned_columns << 'comments_count' - # end - # - def acts_as_versioned(options = {}, &extension) - # don't allow multiple calls - return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods) - - send :include, ActiveRecord::Acts::Versioned::ActMethods - - cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column, - :version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns, - :version_association_options - - # legacy - alias_method :non_versioned_fields, :non_versioned_columns - alias_method :non_versioned_fields=, :non_versioned_columns= - - class << self - alias_method :non_versioned_fields, :non_versioned_columns - alias_method :non_versioned_fields=, :non_versioned_columns= - end - - send :attr_accessor, :altered_attributes - - self.versioned_class_name = options[:class_name] || "Version" - self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key - self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}" - self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}" - self.version_column = options[:version_column] || 'version' - self.version_sequence_name = options[:sequence_name] - self.max_version_limit = options[:limit].to_i - self.version_condition = options[:if] || true - self.non_versioned_columns = [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column] - self.version_association_options = { - :class_name => "#{self.to_s}::#{versioned_class_name}", - :foreign_key => versioned_foreign_key, - :dependent => :delete_all - }.merge(options[:association_options] || {}) - - if block_given? - extension_module_name = "#{versioned_class_name}Extension" - silence_warnings do - self.const_set(extension_module_name, Module.new(&extension)) - end - - options[:extend] = self.const_get(extension_module_name) - end - - class_eval do - has_many :versions, version_association_options do - # finds earliest version of this record - def earliest - @earliest ||= find(:first, :order => 'version') - end - - # find latest version of this record - def latest - @latest ||= find(:first, :order => 'version desc') - end - end - before_save :set_new_version - after_create :save_version_on_create - after_update :save_version - after_save :clear_old_versions - after_save :clear_altered_attributes - - unless options[:if_changed].nil? - self.track_altered_attributes = true - options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array) - options[:if_changed].each do |attr_name| - define_method("#{attr_name}=") do |value| - write_changed_attribute attr_name, value - end - end - end - - include options[:extend] if options[:extend].is_a?(Module) - end - - # create the dynamic versioned model - const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do - def self.reloadable? ; false ; end - # find first version before the given version - def self.before(version) - find :first, :order => 'version desc', - :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version] - end - - # find first version after the given version. - def self.after(version) - find :first, :order => 'version', - :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version] - end - - def previous - self.class.before(self) - end - - def next - self.class.after(self) - end - - def versions_count - page.version - end - end - - versioned_class.cattr_accessor :original_class - versioned_class.original_class = self - versioned_class.table_name = versioned_table_name - versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym, - :class_name => "::#{self.to_s}", - :foreign_key => versioned_foreign_key - versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module) - versioned_class.set_sequence_name version_sequence_name if version_sequence_name - end - end - - module ActMethods - def self.included(base) # :nodoc: - base.extend ClassMethods - end - - # Finds a specific version of this record - def find_version(version = nil) - self.class.find_version(id, version) - end - - # Saves a version of the model if applicable - def save_version - save_version_on_create if save_version? - end - - # Saves a version of the model in the versioned table. This is called in the after_save callback by default - def save_version_on_create - rev = self.class.versioned_class.new - self.clone_versioned_model(self, rev) - rev.version = send(self.class.version_column) - rev.send("#{self.class.versioned_foreign_key}=", self.id) - rev.save - end - - # Clears old revisions if a limit is set with the :limit option in acts_as_versioned. - # Override this method to set your own criteria for clearing old versions. - def clear_old_versions - return if self.class.max_version_limit == 0 - excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit - if excess_baggage > 0 - sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version <= #{excess_baggage} AND #{self.class.versioned_foreign_key} = #{self.id}" - self.class.versioned_class.connection.execute sql - end - end - - def versions_count - version - end - - # Reverts a model to a given version. Takes either a version number or an instance of the versioned model - def revert_to(version) - if version.is_a?(self.class.versioned_class) - return false unless version.send(self.class.versioned_foreign_key) == self.id and !version.new_record? - else - return false unless version = versions.find_by_version(version) - end - self.clone_versioned_model(version, self) - self.send("#{self.class.version_column}=", version.version) - true - end - - # Reverts a model to a given version and saves the model. - # Takes either a version number or an instance of the versioned model - def revert_to!(version) - revert_to(version) ? save_without_revision : false - end - - # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created. - def save_without_revision - save_without_revision! - true - rescue - false - end - - def save_without_revision! - without_locking do - without_revision do - save! - end - end - end - - # Returns an array of attribute keys that are versioned. See non_versioned_columns - def versioned_attributes - self.attributes.keys.select { |k| !self.class.non_versioned_columns.include?(k) } - end - - # If called with no parameters, gets whether the current model has changed and needs to be versioned. - # If called with a single parameter, gets whether the parameter has changed. - def changed?(attr_name = nil) - attr_name.nil? ? - (!self.class.track_altered_attributes || (altered_attributes && altered_attributes.length > 0)) : - (altered_attributes && altered_attributes.include?(attr_name.to_s)) - end - - # keep old dirty? method - alias_method :dirty?, :changed? - - # Clones a model. Used when saving a new version or reverting a model's version. - def clone_versioned_model(orig_model, new_model) - self.versioned_attributes.each do |key| - new_model.send("#{key}=", orig_model.send(key)) if orig_model.respond_to?(key) - end - - if self.class.columns_hash.include?(self.class.inheritance_column) - if orig_model.is_a?(self.class.versioned_class) - new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column] - elsif new_model.is_a?(self.class.versioned_class) - new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column] - end - end - end - - # Checks whether a new version shall be saved or not. Calls version_condition_met? and changed?. - def save_version? - version_condition_met? && changed? - end - - # Checks condition set in the :if option to check whether a revision should be created or not. Override this for - # custom version condition checking. - def version_condition_met? - case - when version_condition.is_a?(Symbol) - send(version_condition) - when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1) - version_condition.call(self) - else - version_condition - end - end - - # Executes the block with the versioning callbacks disabled. - # - # @foo.without_revision do - # @foo.save - # end - # - def without_revision(&block) - self.class.without_revision(&block) - end - - # Turns off optimistic locking for the duration of the block - # - # @foo.without_locking do - # @foo.save - # end - # - def without_locking(&block) - self.class.without_locking(&block) - end - - def empty_callback() end #:nodoc: - - protected - # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version. - def set_new_version - self.send("#{self.class.version_column}=", self.next_version) if new_record? || (!locking_enabled? && save_version?) - end - - # Gets the next available version for the current record, or 1 for a new record - def next_version - return 1 if new_record? - (versions.calculate(:max, :version) || 0) + 1 - end - - # clears current changed attributes. Called after save. - def clear_altered_attributes - self.altered_attributes = [] - end - - def write_changed_attribute(attr_name, attr_value) - # Convert to db type for comparison. Avoids failing Float<=>String comparisons. - attr_value_for_db = self.class.columns_hash[attr_name.to_s].type_cast(attr_value) - (self.altered_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) || self.send(attr_name) == attr_value_for_db - write_attribute(attr_name, attr_value_for_db) - end - - module ClassMethods - # Finds a specific version of a specific row of this model - def find_version(id, version = nil) - return find(id) unless version - - conditions = ["#{versioned_foreign_key} = ? AND version = ?", id, version] - options = { :conditions => conditions, :limit => 1 } - - if result = find_versions(id, options).first - result - else - raise RecordNotFound, "Couldn't find #{name} with ID=#{id} and VERSION=#{version}" - end - end - - # Finds versions of a specific model. Takes an options hash like find - def find_versions(id, options = {}) - versioned_class.find :all, { - :conditions => ["#{versioned_foreign_key} = ?", id], - :order => 'version' }.merge(options) - end - - # Returns an array of columns that are versioned. See non_versioned_columns - def versioned_columns - self.columns.select { |c| !non_versioned_columns.include?(c.name) } - end - - # Returns an instance of the dynamic versioned model - def versioned_class - const_get versioned_class_name - end - - # Rake migration task to create the versioned table using options passed to acts_as_versioned - def create_versioned_table(create_table_options = {}) - # create version column in main table if it does not exist - if !self.content_columns.find { |c| %w(version lock_version).include? c.name } - self.connection.add_column table_name, :version, :integer - end - - self.connection.create_table(versioned_table_name, create_table_options) do |t| - t.column versioned_foreign_key, :integer - t.column :version, :integer - end - - updated_col = nil - self.versioned_columns.each do |col| - updated_col = col if !updated_col && %(updated_at updated_on).include?(col.name) - self.connection.add_column versioned_table_name, col.name, col.type, - :limit => col.limit, - :default => col.default, - :scale => col.scale, - :precision => col.precision - end - - if type_col = self.columns_hash[inheritance_column] - self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type, - :limit => type_col.limit, - :default => type_col.default, - :scale => type_col.scale, - :precision => type_col.precision - end - - if updated_col.nil? - self.connection.add_column versioned_table_name, :updated_at, :timestamp - end - end - - # Rake migration task to drop the versioned table - def drop_versioned_table - self.connection.drop_table versioned_table_name - end - - # Executes the block with the versioning callbacks disabled. - # - # Foo.without_revision do - # @foo.save - # end - # - def without_revision(&block) - class_eval do - CALLBACKS.each do |attr_name| - alias_method "orig_#{attr_name}".to_sym, attr_name - alias_method attr_name, :empty_callback - end - end - block.call - ensure - class_eval do - CALLBACKS.each do |attr_name| - alias_method attr_name, "orig_#{attr_name}".to_sym - end - end - end - - # Turns off optimistic locking for the duration of the block - # - # Foo.without_locking do - # @foo.save - # end - # - def without_locking(&block) - current = ActiveRecord::Base.lock_optimistically - ActiveRecord::Base.lock_optimistically = false if current - result = block.call - ActiveRecord::Base.lock_optimistically = true if current - result - end - end - end - end - end -end - -ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b8/b89495372bc55506424effd6c3e5df5c0cb09a12.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b8/b89495372bc55506424effd6c3e5df5c0cb09a12.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,84 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class AdminController < ApplicationController + layout 'admin' + menu_item :projects, :only => :projects + menu_item :plugins, :only => :plugins + menu_item :info, :only => :info + + before_filter :require_admin + helper :sort + include SortHelper + + def index + @no_configuration_data = Redmine::DefaultData::Loader::no_data? + end + + def projects + @status = params[:status] || 1 + + scope = Project.status(@status).order('lft') + scope = scope.like(params[:name]) if params[:name].present? + @projects = scope.all + + render :action => "projects", :layout => false if request.xhr? + end + + def plugins + @plugins = Redmine::Plugin.all + end + + # Loads the default configuration + # (roles, trackers, statuses, workflow, enumerations) + def default_configuration + if request.post? + begin + Redmine::DefaultData::Loader::load(params[:lang]) + flash[:notice] = l(:notice_default_data_loaded) + rescue Exception => e + flash[:error] = l(:error_can_t_load_default_data, e.message) + end + end + redirect_to admin_path + end + + def test_email + raise_delivery_errors = ActionMailer::Base.raise_delivery_errors + # Force ActionMailer to raise delivery errors so we can catch it + ActionMailer::Base.raise_delivery_errors = true + begin + @test = Mailer.test_email(User.current).deliver + flash[:notice] = l(:notice_email_sent, User.current.mail) + rescue Exception => e + flash[:error] = l(:notice_email_error, Redmine::CodesetUtil.replace_invalid_utf8(e.message.dup)) + end + ActionMailer::Base.raise_delivery_errors = raise_delivery_errors + redirect_to settings_path(:tab => 'notifications') + end + + def info + @db_adapter_name = ActiveRecord::Base.connection.adapter_name + @checklist = [ + [:text_default_administrator_account_changed, User.default_admin_account_changed?], + [:text_file_repository_writable, File.writable?(Attachment.storage_path)], + [:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)], + [:text_rmagick_available, Object.const_defined?(:Magick)], + [:text_convert_available, Redmine::Thumbnail.convert_available?] + ] + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b8/b8adc96bc75bc39796f4d27ac3d2335820b193bb.svn-base --- a/.svn/pristine/b8/b8adc96bc75bc39796f4d27ac3d2335820b193bb.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> - -
    - <%= render :partial => 'navigation' %> -
    - -

    <%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %>

    - -<%= render :partial => 'link_to_functions' %> - -<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %> - -
    - - - <% line_num = 1; previous_revision = nil %> - <% syntax_highlight_lines(@path, Redmine::CodesetUtil.to_utf8_by_setting(@annotate.content)).each do |line| %> - <% revision = @annotate.revisions[line_num - 1] %> - - - - - - - <% line_num += 1; previous_revision = revision %> - <% end %> - -
    <%= line_num %> - <%= (revision.identifier ? link_to_revision(revision, @repository) : format_revision(revision)) if revision && revision != previous_revision %><%= h(revision.author.to_s.split('<').first) if revision && revision != previous_revision %>
    <%= line.html_safe %>
    -
    - -<% html_title(l(:button_annotate)) -%> - -<% content_for :header_tags do %> -<%= stylesheet_link_tag 'scm' %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b9/b97a924cfd29125d9c328b566de6a9a5dd6ba62d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b9/b97a924cfd29125d9c328b566de6a9a5dd6ba62d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,72 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::JsonpTest < Redmine::ApiTest::Base + fixtures :trackers + + def test_should_ignore_jsonp_callback_with_jsonp_disabled + with_settings :jsonp_enabled => '0' do + get '/trackers.json?jsonp=handler' + end + + assert_response :success + assert_match %r{^\{"trackers":.+\}$}, response.body + assert_equal 'application/json; charset=utf-8', response.headers['Content-Type'] + end + + def test_jsonp_should_accept_callback_param + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=handler' + end + + assert_response :success + assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body + assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] + end + + def test_jsonp_should_accept_jsonp_param + with_settings :jsonp_enabled => '1' do + get '/trackers.json?jsonp=handler' + end + + assert_response :success + assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body + assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] + end + + def test_jsonp_should_strip_invalid_characters_from_callback + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=+-aA$1_' + end + + assert_response :success + assert_match %r{^aA1_\(\{"trackers":.+\}\)$}, response.body + assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] + end + + def test_jsonp_without_callback_should_return_json + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=' + end + + assert_response :success + assert_match %r{^\{"trackers":.+\}$}, response.body + assert_equal 'application/json; charset=utf-8', response.headers['Content-Type'] + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/b9/b9a8a43ee4546a78753a5fba634e4d8d0d32059d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b9/b9a8a43ee4546a78753a5fba634e4d8d0d32059d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,49 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class DefaultDataTest < ActiveSupport::TestCase + include Redmine::I18n + fixtures :roles + + def test_no_data + assert !Redmine::DefaultData::Loader::no_data? + Role.delete_all("builtin = 0") + Tracker.delete_all + IssueStatus.delete_all + Enumeration.delete_all + assert Redmine::DefaultData::Loader::no_data? + end + + def test_load + valid_languages.each do |lang| + begin + Role.delete_all("builtin = 0") + Tracker.delete_all + IssueStatus.delete_all + Enumeration.delete_all + assert Redmine::DefaultData::Loader::load(lang) + assert_not_nil DocumentCategory.first + assert_not_nil IssuePriority.first + assert_not_nil TimeEntryActivity.first + rescue ActiveRecord::RecordInvalid => e + assert false, ":#{lang} default data is invalid (#{e.message})." + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ba/ba17318dbb463f926062c32d7de0e2c9351f38c4.svn-base --- a/.svn/pristine/ba/ba17318dbb463f926062c32d7de0e2c9351f38c4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,281 +0,0 @@ - - - -RedmineWikiFormatting - - - - - -

    Wiki formatting

    - -

    Links

    - -

    Redmine links

    - -

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    -
      -
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • -
    • Link to an issue note: #124-6, or #124#note-6
    • -
    - -

    Wiki links:

    - -
      -
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • -
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • -
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • -
    - -

    You can also link to pages of an other project wiki:

    - -
      -
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • -
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • -
    - -

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    - -

    Links to other resources:

    - -
      -
    • Documents: -
        -
      • document#17 (link to document with id 17)
      • -
      • document:Greetings (link to the document with title "Greetings")
      • -
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • -
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • -
    • -
    - -
      -
    • Versions: -
        -
      • version#3 (link to version with id 3)
      • -
      • version:1.0.0 (link to version named "1.0.0")
      • -
      • version:"1.0 beta 2"
      • -
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • -
    • -
    - -
      -
    • Attachments: -
        -
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • -
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • -
    • -
    - -
      -
    • Changesets: -
        -
      • r758 (link to a changeset)
      • -
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • -
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • -
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • -
      • sandbox:r758 (link to a changeset of another project)
      • -
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • -
    • -
    - -
      -
    • Repository files: -
        -
      • source:some/file (link to the file located at /some/file in the project's repository)
      • -
      • source:some/file@52 (link to the file's revision 52)
      • -
      • source:some/file#L120 (link to line 120 of the file)
      • -
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • -
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • -
      • export:some/file (force the download of the file)
      • -
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • -
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • -
      • sandbox:export:some/file (force the download of the file)
      • -
    • -
    - -
      -
    • Forum messages: -
        -
      • message#1218 (link to message with id 1218)
      • -
    • -
    - -
      -
    • Projects: -
        -
      • project#3 (link to project with id 3)
      • -
      • project:someproject (link to project named "someproject")
      • -
    • -
    - - -

    Escaping:

    - -
      -
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • -
    - - -

    External links

    - -

    HTTP URLs and email addresses are automatically turned into clickable links:

    - -
    -http://www.redmine.org, someone@foo.bar
    -
    - -

    displays: http://www.redmine.org,

    - -

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    - -
    -"Redmine web site":http://www.redmine.org
    -
    - -

    displays: Redmine web site

    - - -

    Text formatting

    - - -

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    - -

    Font style

    - -
    -* *bold*
    -* _italic_
    -* _*bold italic*_
    -* +underline+
    -* -strike-through-
    -
    - -

    Display:

    - -
      -
    • bold
    • -
    • italic
    • -
    • *bold italic*
    • -
    • underline
    • -
    • strike-through
    • -
    - -

    Inline images

    - -
      -
    • !image_url! displays an image located at image_url (textile syntax)
    • -
    • !>image_url! right floating image
    • -
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • -
    - -

    Headings

    - -
    -h1. Heading
    -h2. Subheading
    -h3. Subsubheading
    -
    - -

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    - - -

    Paragraphs

    - -
    -p>. right aligned
    -p=. centered
    -
    - -

    This is a centered paragraph.

    - - -

    Blockquotes

    - -

    Start the paragraph with bq.

    - -
    -bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    -To go live, all you need to add is a database and a web server.
    -
    - -

    Display:

    - -
    -

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    -
    - - -

    Table of content

    - -
    -{{toc}} => left aligned toc
    -{{>toc}} => right aligned toc
    -
    - -

    Macros

    - -

    Redmine has the following builtin macros:

    - -

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    - -
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    - - -

    Code highlighting

    - -

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    - -

    You can highlight code in your wiki page using this syntax:

    - -
    -<pre><code class="ruby">
    -  Place you code here.
    -</code></pre>
    -
    - -

    Example:

    - -
     1 # The Greeter class
    - 2 class Greeter
    - 3   def initialize(name)
    - 4     @name = name.capitalize
    - 5   end
    - 6 
    - 7   def salute
    - 8     puts "Hello #{@name}!" 
    - 9   end
    -10 end
    -
    - - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ba/ba1b6670bb0d5502171f46487cf403f4e5667038.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ba/ba1b6670bb0d5502171f46487cf403f4e5667038.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,32 @@ + + + + + + <% if tab[:name] == 'IssueCustomField' %> + + + <% end %> + + + + + <% (@custom_fields_by_type[tab[:name]] || []).sort.each do |custom_field| -%> + "> + + + + <% if tab[:name] == 'IssueCustomField' %> + + + <% end %> + + + + <% end; reset_cycle %> + +
    <%=l(:field_name)%><%=l(:field_field_format)%><%=l(:field_is_required)%><%=l(:field_is_for_all)%><%=l(:label_used_by)%><%=l(:button_sort)%>
    <%= link_to h(custom_field.name), edit_custom_field_path(custom_field) %><%= l(Redmine::CustomFieldFormat.label_for(custom_field.field_format)) %><%= checked_image custom_field.is_required? %><%= checked_image custom_field.is_for_all? %><%= l(:label_x_projects, :count => custom_field.projects.count) if custom_field.is_a? IssueCustomField and !custom_field.is_for_all? %><%= reorder_links('custom_field', {:action => 'update', :id => custom_field}, :put) %> + <%= delete_link custom_field_path(custom_field) %> +
    + +

    <%= link_to l(:label_custom_field_new), new_custom_field_path(:type => tab[:name]), :class => 'icon icon-add' %>

    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ba/ba5d4789df17479bc2d584f1eb52a1d2b9bef079.svn-base --- a/.svn/pristine/ba/ba5d4789df17479bc2d584f1eb52a1d2b9bef079.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -<% if defined?(container) && container && container.saved_attachments %> - <% container.saved_attachments.each_with_index do |attachment, i| %> - - <%= h(attachment.filename) %> (<%= number_to_human_size(attachment.filesize) %>) - <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.id}.#{attachment.digest}" %> - - <% end %> -<% end %> - - - <%= file_field_tag 'attachments[1][file]', :id => nil, :class => 'file', - :onchange => "checkFileSize(this, #{Setting.attachment_max_size.to_i.kilobytes}, '#{escape_javascript(l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)))}');" -%> - <%= text_field_tag 'attachments[1][description]', '', :id => nil, :class => 'description', :maxlength => 255, :placeholder => l(:label_optional_description) %> - <%= link_to_function(image_tag('delete.png'), 'removeFileField(this)', :title => (l(:button_delete))) %> - - -<%= link_to l(:label_add_another_file), '#', :onclick => 'addFileField(); return false;', :class => 'add_attachment' %> -(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ba/ba61511e045eca96ea5a08facf6c796046d39c61.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ba/ba61511e045eca96ea5a08facf6c796046d39c61.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,18 @@ +<%= title [l(:label_custom_field_plural), custom_fields_path], + [l(@custom_field.type_name), custom_fields_path(:tab => @custom_field.class.name)], + l(:label_custom_field_new) %> + +<%= labelled_form_for :custom_field, @custom_field, :url => custom_fields_path, :html => {:id => 'custom_field_form'} do |f| %> +<%= render :partial => 'form', :locals => { :f => f } %> +<%= hidden_field_tag 'type', @custom_field.type %> +<% end %> + +<%= javascript_tag do %> +$('#custom_field_field_format').change(function(){ + $.ajax({ + url: '<%= new_custom_field_path(:format => 'js') %>', + type: 'get', + data: $('#custom_field_form').serialize() + }); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ba/badebf4fa69fcda626a5e7a05f07c66ca17188a7.svn-base --- a/.svn/pristine/ba/badebf4fa69fcda626a5e7a05f07c66ca17188a7.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -# This file was generated by the "jdbc" generator, which is provided -# by the activerecord-jdbc-adapter gem. -# -# This file allows you to use Rails' various db:* tasks with JDBC. -if defined?(JRUBY_VERSION) - require 'jdbc_adapter' - require 'jdbc_adapter/rake_tasks' -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ba/bae5f43e87eacd9d199692aa578d1d741f75b33e.svn-base --- a/.svn/pristine/ba/bae5f43e87eacd9d199692aa578d1d741f75b33e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,218 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) -require 'trackers_controller' - -# Re-raise errors caught by the controller. -class TrackersController; def rescue_action(e) raise e end; end - -class TrackersControllerTest < ActionController::TestCase - fixtures :trackers, :projects, :projects_trackers, :users, :issues, :custom_fields - - def setup - @controller = TrackersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_response :success - assert_template 'index' - end - - def test_index_by_anonymous_should_redirect_to_login_form - @request.session[:user_id] = nil - get :index - assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftrackers' - end - - def test_index_by_user_should_respond_with_406 - @request.session[:user_id] = 2 - get :index - assert_response 406 - end - - def test_new - get :new - assert_response :success - assert_template 'new' - end - - def test_create - assert_difference 'Tracker.count' do - post :create, :tracker => { :name => 'New tracker', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] } - end - assert_redirected_to :action => 'index' - tracker = Tracker.first(:order => 'id DESC') - assert_equal 'New tracker', tracker.name - assert_equal [1], tracker.project_ids.sort - assert_equal Tracker::CORE_FIELDS, tracker.core_fields - assert_equal [1, 6], tracker.custom_field_ids.sort - assert_equal 0, tracker.workflow_rules.count - end - - def create_with_disabled_core_fields - assert_difference 'Tracker.count' do - post :create, :tracker => { :name => 'New tracker', :core_fields => ['assigned_to_id', 'fixed_version_id', ''] } - end - assert_redirected_to :action => 'index' - tracker = Tracker.first(:order => 'id DESC') - assert_equal 'New tracker', tracker.name - assert_equal %w(assigned_to_id fixed_version_id), tracker.core_fields - end - - def test_create_new_with_workflow_copy - assert_difference 'Tracker.count' do - post :create, :tracker => { :name => 'New tracker' }, :copy_workflow_from => 1 - end - assert_redirected_to :action => 'index' - tracker = Tracker.find_by_name('New tracker') - assert_equal 0, tracker.projects.count - assert_equal Tracker.find(1).workflow_rules.count, tracker.workflow_rules.count - end - - def test_create_with_failure - assert_no_difference 'Tracker.count' do - post :create, :tracker => { :name => '', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] } - end - assert_response :success - assert_template 'new' - assert_error_tag :content => /name can't be blank/i - end - - def test_edit - Tracker.find(1).project_ids = [1, 3] - - get :edit, :id => 1 - assert_response :success - assert_template 'edit' - - assert_tag :input, :attributes => { :name => 'tracker[project_ids][]', - :value => '1', - :checked => 'checked' } - - assert_tag :input, :attributes => { :name => 'tracker[project_ids][]', - :value => '2', - :checked => nil } - - assert_tag :input, :attributes => { :name => 'tracker[project_ids][]', - :value => '', - :type => 'hidden'} - end - - def test_edit_should_check_core_fields - tracker = Tracker.find(1) - tracker.core_fields = %w(assigned_to_id fixed_version_id) - tracker.save! - - get :edit, :id => 1 - assert_response :success - assert_template 'edit' - - assert_select 'input[name=?][value=assigned_to_id][checked=checked]', 'tracker[core_fields][]' - assert_select 'input[name=?][value=fixed_version_id][checked=checked]', 'tracker[core_fields][]' - - assert_select 'input[name=?][value=category_id]', 'tracker[core_fields][]' - assert_select 'input[name=?][value=category_id][checked=checked]', 'tracker[core_fields][]', 0 - - assert_select 'input[name=?][value=][type=hidden]', 'tracker[core_fields][]' - end - - def test_update - put :update, :id => 1, :tracker => { :name => 'Renamed', - :project_ids => ['1', '2', ''] } - assert_redirected_to :action => 'index' - assert_equal [1, 2], Tracker.find(1).project_ids.sort - end - - def test_update_without_projects - put :update, :id => 1, :tracker => { :name => 'Renamed', - :project_ids => [''] } - assert_redirected_to :action => 'index' - assert Tracker.find(1).project_ids.empty? - end - - def test_update_without_core_fields - put :update, :id => 1, :tracker => { :name => 'Renamed', :core_fields => [''] } - assert_redirected_to :action => 'index' - assert Tracker.find(1).core_fields.empty? - end - - def test_update_with_failure - put :update, :id => 1, :tracker => { :name => '' } - assert_response :success - assert_template 'edit' - assert_error_tag :content => /name can't be blank/i - end - - def test_move_lower - tracker = Tracker.find_by_position(1) - put :update, :id => 1, :tracker => { :move_to => 'lower' } - assert_equal 2, tracker.reload.position - end - - def test_destroy - tracker = Tracker.create!(:name => 'Destroyable') - assert_difference 'Tracker.count', -1 do - delete :destroy, :id => tracker.id - end - assert_redirected_to :action => 'index' - assert_nil flash[:error] - end - - def test_destroy_tracker_in_use - assert_no_difference 'Tracker.count' do - delete :destroy, :id => 1 - end - assert_redirected_to :action => 'index' - assert_not_nil flash[:error] - end - - def test_get_fields - get :fields - assert_response :success - assert_template 'fields' - - assert_select 'form' do - assert_select 'input[type=checkbox][name=?][value=assigned_to_id]', 'trackers[1][core_fields][]' - assert_select 'input[type=checkbox][name=?][value=2]', 'trackers[1][custom_field_ids][]' - - assert_select 'input[type=hidden][name=?][value=]', 'trackers[1][core_fields][]' - assert_select 'input[type=hidden][name=?][value=]', 'trackers[1][custom_field_ids][]' - end - end - - def test_post_fields - post :fields, :trackers => { - '1' => {'core_fields' => ['assigned_to_id', 'due_date', ''], 'custom_field_ids' => ['1', '2']}, - '2' => {'core_fields' => [''], 'custom_field_ids' => ['']} - } - assert_redirected_to '/trackers/fields' - - tracker = Tracker.find(1) - assert_equal %w(assigned_to_id due_date), tracker.core_fields - assert_equal [1, 2], tracker.custom_field_ids.sort - - tracker = Tracker.find(2) - assert_equal [], tracker.core_fields - assert_equal [], tracker.custom_field_ids.sort - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bb/bb2db291d2956eb483816457a1bb37aed25066e3.svn-base --- a/.svn/pristine/bb/bb2db291d2956eb483816457a1bb37aed25066e3.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1077 +0,0 @@ -# translated by Dzintars Bergs (dzintars.bergs@gmail.com) - -lv: - direction: ltr - date: - formats: - default: "%d.%m.%Y" - short: "%d %b" - long: "%d %B %Y" - - day_names: [SvÄ“tdiena, Pirmdiena, Otrdiena, TreÅ¡diena, Ceturtdiena, Piektdiena, Sestdiena] - abbr_day_names: [Sv, Pr, Ot, Tr, Ct, Pk, St] - - month_names: [~, JanvÄris, FebruÄris, Marts, AprÄ«lis , Maijs, JÅ«nijs, JÅ«lijs, Augusts, Septembris, Oktobris, Novembris, Decembris] - abbr_month_names: [~, Jan, Feb, Mar, Apr, Mai, JÅ«n, JÅ«l, Aug, Sep, Okt, Nov, Dec] - order: - - :day - - :month - - :year - - time: - formats: - default: "%a, %d %b %Y, %H:%M:%S %z" - time: "%H:%M" - short: "%d %b, %H:%M" - long: "%B %d, %Y %H:%M" - am: "rÄ«tÄ" - pm: "vakarÄ" - - datetime: - distance_in_words: - half_a_minute: "pus minÅ«te" - less_than_x_seconds: - one: "mazÄk kÄ 1 sekunde" - other: "mazÄk kÄ %{count} sekundes" - x_seconds: - one: "1 sekunde" - other: "%{count} sekundes" - less_than_x_minutes: - one: "mazÄk kÄ minÅ«te" - other: "mazÄk kÄ %{count} minÅ«tes" - x_minutes: - one: "1 minÅ«te" - other: "%{count} minÅ«tes" - about_x_hours: - one: "aptuveni 1 stunda" - other: "aptuveni %{count} stundas" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 diena" - other: "%{count} dienas" - about_x_months: - one: "aptuveni 1 mÄ“nesis" - other: "aptuveni %{count} mÄ“neÅ¡i" - x_months: - one: "1 mÄ“nesis" - other: "%{count} mÄ“neÅ¡i" - about_x_years: - one: "aptuveni 1 gads" - other: "aptuveni %{count} gadi" - over_x_years: - one: "ilgÄk par 1 gadu" - other: "ilgÄk par %{count} gadiem" - almost_x_years: - one: "gandrÄ«z 1 gadu" - other: "gandrÄ«z %{count} gadus" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: " " - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Baits" - other: "Baiti" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - support: - array: - sentence_connector: "un" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "nav iekļauts sarakstÄ" - exclusion: "ir rezervÄ“ts" - invalid: "nederÄ«gs" - confirmation: "apstiprinÄjums nesakrÄ«t" - accepted: "jÄbÅ«t akceptÄ“tam" - empty: "nevar bÅ«t tukÅ¡s" - blank: "nevar bÅ«t neaizpildÄ«ts" - too_long: "ir pÄrÄk gara(Å¡) (maksimÄlais garums ir %{count} simboli)" - too_short: "ir pÄrÄk Ä«sa(s) (minimÄlais garums ir %{count} simboli)" - wrong_length: "ir nepareiza garuma (vajadzÄ“tu bÅ«t %{count} simboli)" - taken: "eksistÄ“" - not_a_number: "nav skaitlis" - not_a_date: "nav derÄ«gs datums" - greater_than: "jÄbÅ«t lielÄkam par %{count}" - greater_than_or_equal_to: "jÄbÅ«t lielÄkam vai vienÄdam ar %{count}" - equal_to: "jÄbÅ«t vienÄdam ar %{count}" - less_than: "jÄbÅ«t mazÄkam kÄ %{count}" - less_than_or_equal_to: "jÄbÅ«t mazÄkam vai vienÄdam ar %{count}" - odd: "jÄatšķirÄs" - even: "jÄsakrÄ«t" - greater_than_start_date: "jÄbÅ«t vÄ“lÄkam par sÄkuma datumu" - not_same_project: "nepieder pie tÄ paÅ¡a projekta" - circular_dependency: "Å Ä« relÄcija radÄ«tu ciklisku atkarÄ«bu" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: IzvÄ“lieties - - general_text_No: 'NÄ“' - general_text_Yes: 'JÄ' - general_text_no: 'nÄ“' - general_text_yes: 'jÄ' - general_lang_name: 'Latvian (LatvieÅ¡u)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Konts tika atjaunots veiksmÄ«gi. - notice_account_invalid_creditentials: Nepareizs lietotÄja vÄrds vai parole. - notice_account_password_updated: Parole tika veiksmÄ«gi atjaunota. - notice_account_wrong_password: Nepareiza parole - notice_account_register_done: Konts veiksmÄ«gi izveidots. Lai aktivizÄ“tu kontu, spiediet uz saites, kas Jums tika nosÅ«tÄ«ta. - notice_account_unknown_email: NezinÄms lietotÄjs - notice_can_t_change_password: Å is konts izmanto ÄrÄ“ju pilnvaroÅ¡anas avotu. Nav iespÄ“jams nomainÄ«t paroli. - notice_account_lost_email_sent: Jums tika nosÅ«tÄ«ts e-pasts ar instrukcijÄm, kÄ izveidot jaunu paroli. - notice_account_activated: JÅ«su konts ir aktivizÄ“ts. Varat pieslÄ“gties sistÄ“mai. - notice_successful_create: VeiksmÄ«ga izveide. - notice_successful_update: VeiksmÄ«ga atjaunoÅ¡ana. - notice_successful_delete: VeiksmÄ«ga dzēšana. - notice_successful_connection: VeiksmÄ«gs savienojums. - notice_file_not_found: Lapa, ko JÅ«s mēģinÄt atvÄ“rt, neeksistÄ“ vai ir pÄrvietota. - notice_locking_conflict: Datus ir atjaunojis cits lietotÄjs. - notice_not_authorized: Jums nav tiesÄ«bu piekļūt Å¡ai lapai. - notice_email_sent: "E-pasts tika nosÅ«tÄ«ts uz %{value}" - notice_email_error: "Kļūda sÅ«tot e-pastu (%{value})" - notice_feeds_access_key_reseted: JÅ«su RSS pieejas atslÄ“ga tika iestatÄ«ta sÄkuma stÄvoklÄ«. - notice_api_access_key_reseted: JÅ«su API pieejas atslÄ“ga tika iestatÄ«ta sÄkuma stÄvoklÄ«. - notice_failed_to_save_issues: "NeizdevÄs saglabÄt %{count} uzdevumu(us) no %{total} izvÄ“lÄ“ti: %{ids}." - notice_no_issue_selected: "Nav izvÄ“lÄ“ts uzdevums! LÅ«dzu, atzÄ«mÄ“jiet uzdevumus, kurus vÄ“laties rediģēt!" - notice_account_pending: "JÅ«su konts tika izveidots un Å¡obrÄ«d gaida administratora apstiprinÄjumu." - notice_default_data_loaded: NoklusÄ“tÄ konfigurÄcija tika veiksmÄ«gi ielÄdÄ“ta. - notice_unable_delete_version: NeizdevÄs dzÄ“st versiju. - notice_issue_done_ratios_updated: Uzdevuma izpildes koeficients atjaunots. - - error_can_t_load_default_data: "Nevar ielÄdÄ“t noklusÄ“tos konfigurÄcijas datus: %{value}" - error_scm_not_found: "Ieraksts vai versija nebija repozitorijÄ." - error_scm_command_failed: "Mēģinot piekļūt repozitorijam, notika kļūda: %{value}" - error_scm_annotate: "Ieraksts neeksistÄ“ vai tam nevar tikt pievienots paskaidrojums." - error_issue_not_found_in_project: 'Uzdevums netika atrasts vai nepieder Å¡im projektam.' - error_no_tracker_in_project: 'Neviens trakeris nav saistÄ«ts ar Å¡o projektu. PÄrbaudiet projekta iestatÄ«jumus.' - error_no_default_issue_status: 'Nav definÄ“ts uzdevuma noklusÄ“tais statuss. PÄrbaudiet konfigurÄciju (Ejat uz: "AdministrÄcija -> Uzdevumu statusi")!' - error_can_not_reopen_issue_on_closed_version: 'Nevar pievienot atsauksmi uzdevumam, kas saistÄ«ts ar slÄ“gtu versiju.' - error_can_not_archive_project: Å is projekts nevar tikt arhivÄ“ts - error_issue_done_ratios_not_updated: "Uzdevuma izpildes koeficients nav atjaunots." - error_workflow_copy_source: 'LÅ«dzu izvÄ“lieties avota trakeri vai lomu' - error_workflow_copy_target: 'LÅ«dzu izvÄ“lÄ“ties mÄ“rÄ·a trakeri(us) un lomu(as)' - - warning_attachments_not_saved: "%{count} datnes netika saglabÄtas." - - mail_subject_lost_password: "JÅ«su %{value} parole" - mail_body_lost_password: 'Lai mainÄ«tu paroli, spiediet uz šīs saites:' - mail_subject_register: "JÅ«su %{value} konta aktivizÄcija" - mail_body_register: 'Lai izveidotu kontu, spiediet uz šīs saites:' - mail_body_account_information_external: "Varat izmantot JÅ«su %{value} kontu, lai pieslÄ“gtos." - mail_body_account_information: JÅ«su konta informÄcija - mail_subject_account_activation_request: "%{value} konta aktivizÄcijas pieprasÄ«jums" - mail_body_account_activation_request: "Jauns lietotÄjs (%{value}) ir reÄ£istrÄ“ts. LietotÄja konts gaida JÅ«su apstiprinÄjumu:" - mail_subject_reminder: "%{count} uzdevums(i) sagaidÄms(i) tuvÄkajÄs %{days} dienÄs" - mail_body_reminder: "%{count} uzdevums(i), kurÅ¡(i) ir nozÄ«mÄ“ts(i) Jums, sagaidÄms(i) tuvÄkajÄs %{days} dienÄs:" - mail_subject_wiki_content_added: "'%{id}' Wiki lapa pievienota" - mail_body_wiki_content_added: "The '%{id}' Wiki lapu pievienojis %{author}." - mail_subject_wiki_content_updated: "'%{id}' Wiki lapa atjaunota" - mail_body_wiki_content_updated: "The '%{id}' Wiki lapu atjaunojis %{author}." - - gui_validation_error: 1 kļūda - gui_validation_error_plural: "%{count} kļūdas" - - field_name: Nosaukums - field_description: Apraksts - field_summary: Kopsavilkums - field_is_required: NepiecieÅ¡ams - field_firstname: VÄrds - field_lastname: UzvÄrds - field_mail: "E-pasts" - field_filename: Datne - field_filesize: IzmÄ“rs - field_downloads: LejupielÄdes - field_author: Autors - field_created_on: Izveidots - field_updated_on: Atjaunots - field_field_format: FormÄts - field_is_for_all: Visiem projektiem - field_possible_values: IespÄ“jamÄs vÄ“rtÄ«bas - field_regexp: RegulÄrÄ izteiksme - field_min_length: MinimÄlais garums - field_max_length: MaksimÄlais garums - field_value: VÄ“rtÄ«ba - field_category: Kategorija - field_title: Nosaukums - field_project: Projekts - field_issue: Uzdevums - field_status: Statuss - field_notes: PiezÄ«mes - field_is_closed: Uzdevums slÄ“gts - field_is_default: NoklusÄ“tÄ vÄ“rtÄ«ba - field_tracker: Trakeris - field_subject: Temats - field_due_date: SagaidÄmais datums - field_assigned_to: Piešķirts - field_priority: PrioritÄte - field_fixed_version: MÄ“rÄ·a versija - field_user: LietotÄjs - field_role: Loma - field_homepage: Vietne - field_is_public: Publisks - field_parent: ApakÅ¡projekts projektam - field_is_in_roadmap: CeļvedÄ« parÄdÄ«tie uzdevumi - field_login: PieslÄ“gties - field_mail_notification: "E-pasta paziņojumi" - field_admin: Administrators - field_last_login_on: PÄ“dÄ“jo reizi pieslÄ“dzies - field_language: Valoda - field_effective_date: Datums - field_password: Parole - field_new_password: JanÄ parole - field_password_confirmation: Paroles apstiprinÄjums - field_version: Versija - field_type: Tips - field_host: Hosts - field_port: Ports - field_account: Konts - field_base_dn: Base DN - field_attr_login: PieslÄ“gÅ¡anÄs atribÅ«ts - field_attr_firstname: VÄrda atribÅ«ts - field_attr_lastname: UzvÄrda atribÅ«ts - field_attr_mail: "E-pasta atribÅ«ts" - field_onthefly: "LietotÄja izveidoÅ¡ana on-the-fly" - field_start_date: SÄkuma datums - field_done_ratio: "% padarÄ«ti" - field_auth_source: PilnvaroÅ¡anas režīms - field_hide_mail: "PaslÄ“pt manu e-pasta adresi" - field_comments: KomentÄrs - field_url: URL - field_start_page: SÄkuma lapa - field_subproject: ApakÅ¡projekts - field_hours: Stundas - field_activity: AktivitÄte - field_spent_on: Datums - field_identifier: Identifikators - field_is_filter: Izmantots kÄ filtrs - field_issue_to: SaistÄ«ts uzdevums - field_delay: KavÄ“jums - field_assignable: Uzdevums var tikt piesaistÄ«ts Å¡ai lomai - field_redirect_existing_links: PÄradresÄ“t eksistÄ“joÅ¡Äs saites - field_estimated_hours: ParedzÄ“tais laiks - field_column_names: Kolonnas - field_time_zone: Laika zona - field_searchable: MeklÄ“jams - field_default_value: NoklusÄ“tÄ vÄ“rtÄ«ba - field_comments_sorting: RÄdÄ«t komentÄrus - field_parent_title: VecÄka lapa - field_editable: Rediģējams - field_watcher: VÄ“rotÄjs - field_identity_url: OpenID URL - field_content: Saturs - field_group_by: GrupÄ“t rezultÄtus pÄ“c - field_sharing: KoplietoÅ¡ana - - setting_app_title: Programmas nosaukums - setting_app_subtitle: Programmas apakÅ¡-nosaukums - setting_welcome_text: Sveiciena teksts - setting_default_language: NoklusÄ“tÄ valoda - setting_login_required: NepiecieÅ¡ama pilnvaroÅ¡ana - setting_self_registration: PaÅ¡reÄ£istrēšanÄs - setting_attachment_max_size: Pielikuma maksimÄlais izmÄ“rs - setting_issues_export_limit: Uzdevumu eksporta ierobežojums - setting_mail_from: "E-pasta adrese informÄcijas nosÅ«tīšanai" - setting_bcc_recipients: "SaņēmÄ“ju adreses neparÄdÄ«sies citu saņēmÄ“ju vÄ“stulÄ“s (bcc)" - setting_plain_text_mail: "VÄ“stule brÄ«vÄ tekstÄ (bez HTML)" - setting_host_name: Hosta nosaukums un piekļuves ceļš - setting_text_formatting: Teksta formatēšana - setting_wiki_compression: Wiki vÄ“stures saspieÅ¡ana - setting_feeds_limit: Barotnes satura ierobežojums - setting_default_projects_public: Jaunie projekti noklusÄ“ti ir publiski pieejami - setting_autofetch_changesets: "AutomÄtiski lietot jaunÄko versiju, pieslÄ“dzoties repozitorijam (Autofetch)" - setting_sys_api_enabled: IeslÄ“gt WS repozitoriju menedžmentam - setting_commit_ref_keywords: NorÄdes atslÄ“gvÄrdi - setting_commit_fix_keywords: FiksÄ“joÅ¡ie atslÄ“gvÄrdi - setting_autologin: AutomÄtiskÄ pieslÄ“gÅ¡anÄs - setting_date_format: Datuma formÄts - setting_time_format: Laika formÄts - setting_cross_project_issue_relations: "Atļaut starp-projektu uzdevumu relÄcijas" - setting_issue_list_default_columns: NoklusÄ“ti rÄdÄ«tÄs kolonnas uzdevumu sarakstÄ - setting_emails_footer: "E-pastu kÄjene" - setting_protocol: Protokols - setting_per_page_options: Objekti vienÄ lapÄ - setting_user_format: LietotÄju rÄdīšanas formÄts - setting_activity_days_default: Dienus skaits aktivitÄÅ¡u rÄdīšanai aktivitÄÅ¡u sadaÄ¼Ä - setting_display_subprojects_issues: RÄdÄ«t apakÅ¡projekta uzdevumus galvenajÄ projektÄ pÄ“c noklusÄ“juma - setting_enabled_scm: Lietot SCM - setting_mail_handler_body_delimiters: "SaÄ«sinÄt pÄ“c vienas no Å¡im rindÄm" - setting_mail_handler_api_enabled: "Lietot WS ienÄkoÅ¡ajiem e-pastiem" - setting_mail_handler_api_key: API atslÄ“ga - setting_sequential_project_identifiers: Ä¢enerÄ“t secÄ«gus projektu identifikatorus - setting_gravatar_enabled: Izmantot Gravatar lietotÄju ikonas - setting_gravatar_default: NoklusÄ“tais Gravatar attÄ“ls - setting_diff_max_lines_displayed: MaksimÄlais rÄdÄ«to diff rindu skaits - setting_file_max_size_displayed: MaksimÄlais izmÄ“rs iekļautajiem teksta failiem - setting_repository_log_display_limit: MaksimÄlais žurnÄla datnÄ“ rÄdÄ«to revÄ«ziju skaits - setting_openid: Atļaut OpenID pieslÄ“gÅ¡anos un reÄ£istrēšanos - setting_password_min_length: MinimÄlais paroles garums - setting_new_project_user_role_id: Loma, kura tiek piešķirta ne-administratora lietotÄjam, kurÅ¡ izveido projektu - setting_default_projects_modules: NoklusÄ“tie lietotie moduļi jaunam projektam - setting_issue_done_ratio: AprēķinÄt uzdevuma izpildes koeficientu ar - setting_issue_done_ratio_issue_field: uzdevuma lauku - setting_issue_done_ratio_issue_status: uzdevuma statusu - setting_start_of_week: SÄkt kalendÄru ar - setting_rest_api_enabled: Lietot REST web-servisu - setting_cache_formatted_text: KeÅ¡ot formatÄ“tu tekstu - - permission_add_project: Izveidot projektu - permission_add_subprojects: Izveidot apakÅ¡projektu - permission_edit_project: Rediģēt projektu - permission_select_project_modules: IzvÄ“lÄ“ties projekta moduļus - permission_manage_members: PÄrvaldÄ«t dalÄ«bniekus - permission_manage_project_activities: PÄrvaldÄ«t projekta aktivitÄtes - permission_manage_versions: PÄrvaldÄ«t versijas - permission_manage_categories: PÄrvaldÄ«t uzdevumu kategorijas - permission_view_issues: ApskatÄ«t uzdevumus - permission_add_issues: Pievienot uzdevumus - permission_edit_issues: Rediģēt uzdevumus - permission_manage_issue_relations: PÄrvaldÄ«t uzdevumu relÄcijas - permission_add_issue_notes: Pievienot piezÄ«mes - permission_edit_issue_notes: Rediģēt piezÄ«mes - permission_edit_own_issue_notes: Rediģēt paÅ¡a piezÄ«mes - permission_move_issues: PÄrvietot uzdevumus - permission_delete_issues: DzÄ“st uzdevumus - permission_manage_public_queries: PÄrvaldÄ«t publiskos pieprasÄ«jumus - permission_save_queries: SaglabÄt pieprasÄ«jumus - permission_view_gantt: SkatÄ«t Ganta diagrammu - permission_view_calendar: SkatÄ«t kalendÄru - permission_view_issue_watchers: SkatÄ«t vÄ“rotÄju sarakstu - permission_add_issue_watchers: Pievienot vÄ“rotÄjus - permission_delete_issue_watchers: DzÄ“st vÄ“rotÄjus - permission_log_time: PiereÄ£istrÄ“t pavadÄ«to laiku - permission_view_time_entries: SkatÄ«t pavadÄ«to laiku - permission_edit_time_entries: Rdiģēt laika reÄ£istrus - permission_edit_own_time_entries: Rediģēt savus laika reÄ£istrus - permission_manage_news: PÄrvaldÄ«t jaunumus - permission_comment_news: KomentÄ“t jaunumus - permission_manage_documents: PÄrvaldÄ«t dokumentus - permission_view_documents: SkatÄ«t dokumentus - permission_manage_files: PÄrvaldÄ«t failus - permission_view_files: SkatÄ«t failus - permission_manage_wiki: PÄrvaldÄ«t wiki - permission_rename_wiki_pages: PÄrsaukt wiki lapas - permission_delete_wiki_pages: DzÄ“st wiki lapas - permission_view_wiki_pages: SkatÄ«t wiki - permission_view_wiki_edits: SkatÄ«t wiki vÄ“sturi - permission_edit_wiki_pages: Rdiģēt wiki lapas - permission_delete_wiki_pages_attachments: DzÄ“st pielikumus - permission_protect_wiki_pages: Projekta wiki lapas - permission_manage_repository: PÄrvaldÄ«t repozitoriju - permission_browse_repository: PÄrlÅ«kot repozitoriju - permission_view_changesets: SkatÄ«t izmaiņu kopumus - permission_commit_access: Atļaut piekļuvi - permission_manage_boards: PÄrvaldÄ«t ziņojumu dēļus - permission_view_messages: SkatÄ«t ziņas - permission_add_messages: PublicÄ“t ziņas - permission_edit_messages: Rediģēt ziņas - permission_edit_own_messages: Rediģēt savas ziņas - permission_delete_messages: DzÄ“st ziņas - permission_delete_own_messages: DzÄ“st savas ziņas - permission_export_wiki_pages: EksportÄ“t Wiki lapas - - project_module_issue_tracking: Uzdevumu uzskaite - project_module_time_tracking: Laika uzskaite - project_module_news: Jaunumi - project_module_documents: Dokumenti - project_module_files: Datnes - project_module_wiki: Wiki - project_module_repository: Repozitorijs - project_module_boards: Ziņojumu dēļi - - label_user: LietotÄjs - label_user_plural: LietotÄji - label_user_new: Jauns lietotÄjs - label_user_anonymous: AnonÄ«ms - label_project: Projekts - label_project_new: Jauns projekts - label_project_plural: Projekti - label_x_projects: - zero: nav projektu - one: 1 projekts - other: "%{count} projekti" - label_project_all: Visi projekti - label_project_latest: JaunÄkie projekti - label_issue: Uzdevums - label_issue_new: Jauns uzdevums - label_issue_plural: Uzdevumi - label_issue_view_all: SkatÄ«t visus uzdevumus - label_issues_by: "KÄrtot pÄ“c %{value}" - label_issue_added: Uzdevums pievienots - label_issue_updated: Uzdevums atjaunots - label_document: Dokuments - label_document_new: Jauns dokuments - label_document_plural: Dokumenti - label_document_added: Dokuments pievienots - label_role: Loma - label_role_plural: Lomas - label_role_new: Jauna loma - label_role_and_permissions: Lomas un atļaujas - label_member: DalÄ«bnieks - label_member_new: Jauns dalÄ«bnieks - label_member_plural: DalÄ«bnieki - label_tracker: Trakeris - label_tracker_plural: Trakeri - label_tracker_new: Jauns trakeris - label_workflow: Darba gaita - label_issue_status: Uzdevuma statuss - label_issue_status_plural: Uzdevumu statusi - label_issue_status_new: Jauns statuss - label_issue_category: Uzdevuma kategorija - label_issue_category_plural: Uzdevumu kategorijas - label_issue_category_new: Jauna kategorija - label_custom_field: PielÄgojams lauks - label_custom_field_plural: PielÄgojami lauki - label_custom_field_new: Jauns pielÄgojams lauks - label_enumerations: UzskaitÄ«jumi - label_enumeration_new: Jauna vÄ“rtÄ«ba - label_information: InformÄcija - label_information_plural: InformÄcija - label_please_login: LÅ«dzu pieslÄ“dzieties - label_register: ReÄ£istrÄ“ties - label_login_with_open_id_option: vai pieslÄ“gties ar OpenID - label_password_lost: NozaudÄ“ta parole - label_home: SÄkums - label_my_page: Mana lapa - label_my_account: Mans konts - label_my_projects: Mani projekti - label_administration: AdministrÄcija - label_login: PieslÄ“gties - label_logout: AtslÄ“gties - label_help: PalÄ«dzÄ«ba - label_reported_issues: Ziņotie uzdevumi - label_assigned_to_me_issues: Man piesaistÄ«tie uzdevumi - label_last_login: PÄ“dÄ“jÄ pieslÄ“gÅ¡anÄs - label_registered_on: ReÄ£istrÄ“jies - label_activity: AktivitÄte - label_overall_activity: KopÄ“jÄs aktivitÄtes - label_user_activity: "LietotÄja %{value} aktivitÄtes" - label_new: Jauns - label_logged_as: PieslÄ“dzies kÄ - label_environment: Vide - label_authentication: PilnvaroÅ¡ana - label_auth_source: PilnvaroÅ¡anas režīms - label_auth_source_new: Jauns pilnvaroÅ¡anas režīms - label_auth_source_plural: PilnvaroÅ¡anas režīmi - label_subproject_plural: ApakÅ¡projekti - label_subproject_new: Jauns apakÅ¡projekts - label_and_its_subprojects: "%{value} un tÄ apakÅ¡projekti" - label_min_max_length: MinimÄlais - MaksimÄlais garums - label_list: Saraksts - label_date: Datums - label_integer: Vesels skaitlis - label_float: DecimÄlskaitlis - label_boolean: Patiesuma vÄ“rtÄ«ba - label_string: Teksts - label_text: GarÅ¡ teksts - label_attribute: AtribÅ«ts - label_attribute_plural: AtribÅ«ti - label_download: "%{count} LejupielÄde" - label_download_plural: "%{count} LejupielÄdes" - label_no_data: Nav datu, ko parÄdÄ«t - label_change_status: MainÄ«t statusu - label_history: VÄ“sture - label_attachment: Pielikums - label_attachment_new: Jauns pielikums - label_attachment_delete: DzÄ“st pielikumu - label_attachment_plural: Pielikumi - label_file_added: Lauks pievienots - label_report: Atskaite - label_report_plural: Atskaites - label_news: Ziņa - label_news_new: Pievienot ziņu - label_news_plural: Ziņas - label_news_latest: JaunÄkÄs ziņas - label_news_view_all: SkatÄ«t visas ziņas - label_news_added: Ziņas pievienotas - label_settings: IestatÄ«jumi - label_overview: PÄrskats - label_version: Versija - label_version_new: Jauna versija - label_version_plural: Versijas - label_close_versions: AizvÄ“rt pabeigtÄs versijas - label_confirmation: ApstiprinÄjums - label_export_to: 'Pieejams arÄ«:' - label_read: LasÄ«t... - label_public_projects: Publiskie projekti - label_open_issues: atvÄ“rts - label_open_issues_plural: atvÄ“rti - label_closed_issues: slÄ“gts - label_closed_issues_plural: slÄ“gti - label_x_open_issues_abbr_on_total: - zero: 0 atvÄ“rti / %{total} - one: 1 atvÄ“rts / %{total} - other: "%{count} atvÄ“rti / %{total}" - label_x_open_issues_abbr: - zero: 0 atvÄ“rti - one: 1 atvÄ“rts - other: "%{count} atvÄ“rti" - label_x_closed_issues_abbr: - zero: 0 slÄ“gti - one: 1 slÄ“gts - other: "%{count} slÄ“gti" - label_total: KopÄ - label_permissions: Atļaujas - label_current_status: PaÅ¡reizÄ“jais statuss - label_new_statuses_allowed: Jauni statusi atļauti - label_all: visi - label_none: neviens - label_nobody: nekas - label_next: NÄkoÅ¡ais - label_previous: Iepriekšējais - label_used_by: Izmanto - label_details: Detaļas - label_add_note: Pievienot piezÄ«mi - label_per_page: katrÄ lapÄ - label_calendar: KalendÄrs - label_months_from: mÄ“neÅ¡i no - label_gantt: Ganta diagramma - label_internal: Iekšējais - label_last_changes: "pÄ“dÄ“jÄs %{count} izmaiņas" - label_change_view_all: SkatÄ«t visas izmaiņas - label_personalize_page: PielÄgot Å¡o lapu - label_comment: KomentÄrs - label_comment_plural: KomentÄri - label_x_comments: - zero: nav komentÄru - one: 1 komentÄrs - other: "%{count} komentÄri" - label_comment_add: Pievienot komentÄru - label_comment_added: KomentÄrs pievienots - label_comment_delete: DzÄ“st komentÄrus - label_query: PielÄgots pieprasÄ«jums - label_query_plural: PielÄgoti pieprasÄ«jumi - label_query_new: Jauns pieprasÄ«jums - label_filter_add: Pievienot filtru - label_filter_plural: Filtri - label_equals: ir - label_not_equals: nav - label_in_less_than: ir mazÄk kÄ - label_in_more_than: ir vairÄk kÄ - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: iekÅ¡ - label_today: Å¡odien - label_all_time: visu laiku - label_yesterday: vakar - label_this_week: Å¡onedēļ - label_last_week: pagÄjuÅ¡o Å¡onedēļ - label_last_n_days: "pÄ“dÄ“jÄs %{count} dienas" - label_this_month: Å¡omÄ“nes - label_last_month: pagÄjuÅ¡o mÄ“nes - label_this_year: Å¡ogad - label_date_range: Datumu apgabals - label_less_than_ago: mazÄk kÄ dienas iepriekÅ¡ - label_more_than_ago: vairÄk kÄ dienas iepriekÅ¡ - label_ago: dienas iepriekÅ¡ - label_contains: satur - label_not_contains: nesatur - label_day_plural: dienas - label_repository: Repozitorijs - label_repository_plural: Repozitoriji - label_browse: PÄrlÅ«kot - label_modification: "%{count} izmaiņa" - label_modification_plural: "%{count} izmaiņas" - label_branch: Zars - label_tag: Birka - label_revision: RevÄ«zija - label_revision_plural: RevÄ«zijas - label_revision_id: "RevÄ«zija %{value}" - label_associated_revisions: SaistÄ«tÄs revÄ«zijas - label_added: pievienots - label_modified: modificÄ“ts - label_copied: nokopÄ“ts - label_renamed: pÄrsaukts - label_deleted: dzÄ“sts - label_latest_revision: PÄ“dÄ“jÄ revÄ«zija - label_latest_revision_plural: PÄ“dÄ“jÄs revÄ«zijas - label_view_revisions: SkatÄ«t revÄ«zijas - label_view_all_revisions: SkatÄ«t visas revÄ«zijas - label_max_size: MaksimÄlais izmÄ“rs - label_sort_highest: PÄrvietot uz augÅ¡u - label_sort_higher: PÄrvietot soli augÅ¡up - label_sort_lower: PÄrvietot uz leju - label_sort_lowest: PÄrvietot vienu soli uz leju - label_roadmap: Ceļvedis - label_roadmap_due_in: "SagaidÄms pÄ“c %{value}" - label_roadmap_overdue: "nokavÄ“ts %{value}" - label_roadmap_no_issues: Å ai versijai nav uzdevumu - label_search: MeklÄ“t - label_result_plural: RezultÄti - label_all_words: Visi vÄrdi - label_wiki: Wiki - label_wiki_edit: Wiki labojums - label_wiki_edit_plural: Wiki labojumi - label_wiki_page: Wiki lapa - label_wiki_page_plural: Wiki lapas - label_index_by_title: IndeksÄ“t pÄ“c nosaukuma - label_index_by_date: IndeksÄ“t pÄ“c datuma - label_current_version: TekoÅ¡Ä versija - label_preview: PriekÅ¡skatÄ«jums - label_feed_plural: Barotnes - label_changes_details: Visu izmaiņu detaļas - label_issue_tracking: Uzdevumu uzskaite - label_spent_time: PavadÄ«tais laiks - label_f_hour: "%{value} stunda" - label_f_hour_plural: "%{value} stundas" - label_time_tracking: Laika uzskaite - label_change_plural: Izmaiņas - label_statistics: Statistika - label_commits_per_month: Nodevumi mÄ“nesÄ« - label_commits_per_author: Nodevumi no autora - label_view_diff: SkatÄ«t atšķirÄ«bas - label_diff_inline: iekļauts - label_diff_side_by_side: blakus - label_options: Opcijas - label_copy_workflow_from: KopÄ“t darba plÅ«smu no - label_permissions_report: Atļauju atskaite - label_watched_issues: VÄ“rotie uzdevumi - label_related_issues: SaistÄ«tie uzdevumi - label_applied_status: Piešķirtais statuss - label_loading: LÄdÄ“jas... - label_relation_new: Jauna relÄcija - label_relation_delete: DzÄ“st relÄciju - label_relates_to: saistÄ«ts ar - label_duplicates: dublikÄti - label_duplicated_by: dublÄ“jas ar - label_blocks: bloÄ·Ä“ - label_blocked_by: nobloÄ·Ä“jis - label_precedes: pirms - label_follows: seko - label_end_to_start: no beigÄm uz sÄkumu - label_end_to_end: no beigÄm uz beigÄm - label_start_to_start: no sÄkuma uz sÄkumu - label_start_to_end: no sÄkuma uz beigÄm - label_stay_logged_in: AtcerÄ“ties mani - label_disabled: izslÄ“gts - label_show_completed_versions: RÄdÄ«t pabeigtÄs versijas - label_me: es - label_board: Forums - label_board_new: Jauns forums - label_board_plural: Forumi - label_board_locked: SlÄ“gts - label_board_sticky: SvarÄ«gs - label_topic_plural: TÄ“mas - label_message_plural: Ziņas - label_message_last: PÄ“dÄ“jÄ ziņa - label_message_new: Jauna ziņa - label_message_posted: Ziņa pievienota - label_reply_plural: Atbildes - label_send_information: SÅ«tÄ«t konta informÄciju lietotÄjam - label_year: Gads - label_month: MÄ“nesis - label_week: Nedēļa - label_date_from: No - label_date_to: Kam - label_language_based: Izmantot lietotÄja valodu - label_sort_by: "KÄrtot pÄ“c %{value}" - label_send_test_email: "SÅ«tÄ«t testa e-pastu" - label_feeds_access_key: RSS piekļuves atslÄ“ga - label_missing_feeds_access_key: TrÅ«kst RSS piekļuves atslÄ“gas - label_feeds_access_key_created_on: "RSS piekļuves atslÄ“ga izveidota pirms %{value}" - label_module_plural: Moduļi - label_added_time_by: "Pievienojis %{author} pirms %{age}" - label_updated_time_by: "Atjaunojis %{author} pirms %{age}" - label_updated_time: "Atjaunots pirms %{value}" - label_jump_to_a_project: PÄriet uz projektu... - label_file_plural: Datnes - label_changeset_plural: Izmaiņu kopumi - label_default_columns: NoklusÄ“tÄs kolonnas - label_no_change_option: (Nav izmaiņu) - label_bulk_edit_selected_issues: Labot visus izvÄ“lÄ“tos uzdevumus - label_theme: TÄ“ma - label_default: NoklusÄ“ts - label_search_titles_only: MeklÄ“t tikai nosaukumos - label_user_mail_option_all: "Par visiem notikumiem visos manos projektos" - label_user_mail_option_selected: "Par visiem notikumiem tikai izvÄ“lÄ“tajos projektos..." - label_user_mail_no_self_notified: "Neziņot man par izmaiņÄm, kuras veicu es pats" - label_registration_activation_by_email: "konta aktivizÄcija caur e-pastu" - label_registration_manual_activation: manuÄlÄ konta aktivizÄcija - label_registration_automatic_activation: automÄtiskÄ konta aktivizÄcija - label_display_per_page: "RÄdÄ«t vienÄ lapÄ: %{value}" - label_age: Vecums - label_change_properties: MainÄ«t atribÅ«tus - label_general: Galvenais - label_more: VÄ“l - label_scm: SCM - label_plugins: Spraudņi - label_ldap_authentication: LDAP pilnvaroÅ¡ana - label_downloads_abbr: L-lÄd. - label_optional_description: "Apraksts (neobligÄts)" - label_add_another_file: Pievienot citu failu - label_preferences: PriekÅ¡rocÄ«bas - label_chronological_order: HronoloÄ£iskÄ kÄrtÄ«bÄ - label_reverse_chronological_order: Apgriezti hronoloÄ£iskÄ kÄrtÄ«bÄ - label_planning: PlÄnoÅ¡ana - label_incoming_emails: "IenÄkoÅ¡ie e-pasti" - label_generate_key: Ä¢enerÄ“t atslÄ“gu - label_issue_watchers: VÄ“rotÄji - label_example: PiemÄ“rs - label_display: RÄdÄ«t - label_sort: KÄrtot - label_ascending: AugoÅ¡i - label_descending: DilstoÅ¡i - label_date_from_to: "No %{start} lÄ«dz %{end}" - label_wiki_content_added: Wiki lapa pievienota - label_wiki_content_updated: Wiki lapa atjaunota - label_group: Grupa - label_group_plural: Grupas - label_group_new: Jauna grupa - label_time_entry_plural: PavadÄ«tais laiks - label_version_sharing_none: Nav koplietoÅ¡anai - label_version_sharing_descendants: Ar apakÅ¡projektiem - label_version_sharing_hierarchy: Ar projektu hierarhiju - label_version_sharing_tree: Ar projekta koku - label_version_sharing_system: Ar visiem projektiem - label_update_issue_done_ratios: Atjaunot uzdevuma veikuma attiecÄ«bu - label_copy_source: Avots - label_copy_target: MÄ“rÄ·is - label_copy_same_as_target: TÄds pats kÄ mÄ“rÄ·is - label_display_used_statuses_only: "RÄdÄ«t tikai statusus, ko lieto Å¡is trakeris" - label_api_access_key: API pieejas atslÄ“ga - label_missing_api_access_key: TrÅ«kst API pieejas atslÄ“ga - label_api_access_key_created_on: "API pieejas atslÄ“ga izveidota pirms %{value}" - - button_login: PieslÄ“gties - button_submit: NosÅ«tÄ«t - button_save: SaglabÄt - button_check_all: AtzÄ«mÄ“t visu - button_uncheck_all: Noņemt visus atzÄ«mÄ“jumus - button_delete: DzÄ“st - button_create: Izveidot - button_create_and_continue: Izveidot un turpinÄt - button_test: TestÄ“t - button_edit: Labot - button_add: Pievienot - button_change: MainÄ«t - button_apply: ApstiprinÄt - button_clear: NotÄ«rÄ«t - button_lock: SlÄ“gt - button_unlock: AtslÄ“gt - button_download: LejuplÄdÄ“t - button_list: Saraksts - button_view: Skats - button_move: PÄrvietot - button_move_and_follow: PÄrvietot un sekot - button_back: Atpakaļ - button_cancel: Atcelt - button_activate: AktivizÄ“t - button_sort: KÄrtot - button_log_time: Ilgs laiks - button_rollback: Atjaunot uz Å¡o versiju - button_watch: VÄ“rot - button_unwatch: NevÄ“rot - button_reply: AtbildÄ“t - button_archive: ArhivÄ“t - button_unarchive: AtarhivÄ“t - button_reset: AtiestatÄ«t - button_rename: PÄrsaukt - button_change_password: MainÄ«t paroli - button_copy: KopÄ“t - button_copy_and_follow: KopÄ“t un sekot - button_annotate: PierakstÄ«t paskaidrojumu - button_update: Atjaunot - button_configure: KonfigurÄ“t - button_quote: CitÄts - button_duplicate: DublÄ“t - button_show: RÄdÄ«t - - status_active: aktÄ«vs - status_registered: reÄ£istrÄ“ts - status_locked: slÄ“gts - - version_status_open: atvÄ“rta - version_status_locked: slÄ“gta - version_status_closed: aizvÄ“rta - - field_active: AktÄ«vs - - text_select_mail_notifications: "IzvÄ“lieties darbÄ«bas, par kurÄm vÄ“laties saņemt ziņojumus e-pastÄ" - text_regexp_info: "piem. ^[A-Z0-9]+$" - text_min_max_length_info: "0 nozÄ«mÄ“, ka nav ierobežojumu" - text_project_destroy_confirmation: "Vai tieÅ¡Äm vÄ“laties dzÄ“st Å¡o projektu un ar to saistÄ«tos datus?" - text_subprojects_destroy_warning: "TÄ apakÅ¡projekts(i): %{value} arÄ« tiks dzÄ“sts(i)." - text_workflow_edit: Lai labotu darba plÅ«smu, izvÄ“lieties lomu un trakeri - text_are_you_sure: "Vai esat pÄrliecinÄts?" - text_journal_changed: "%{label} mainÄ«ts no %{old} uz %{new}" - text_journal_set_to: "%{label} iestatÄ«ts uz %{value}" - text_journal_deleted: "%{label} dzÄ“sts (%{old})" - text_journal_added: "%{label} %{value} pievienots" - text_tip_issue_begin_day: uzdevums sÄkas Å¡odien - text_tip_issue_end_day: uzdevums beidzas Å¡odien - text_tip_issue_begin_end_day: uzdevums sÄkas un beidzas Å¡odien - text_caracters_maximum: "%{count} simboli maksimÄli." - text_caracters_minimum: "JÄbÅ«t vismaz %{count} simbolu garumÄ." - text_length_between: "Garums starp %{min} un %{max} simboliem." - text_tracker_no_workflow: Å im trakerim nav definÄ“ta darba plÅ«sma - text_unallowed_characters: Neatļauti simboli - text_comma_separated: "Atļautas vairÄkas vÄ“rtÄ«bas (atdalÄ«t ar komatu)." - text_line_separated: "Atļautas vairÄkas vÄ“rtÄ«bas (rakstÄ«t katru savÄ rindÄ)." - text_issues_ref_in_commit_messages: "Izmaiņu salÄ«dzinÄÅ¡ana izejot no ziņojumiem" - text_issue_added: "Uzdevumu %{id} pievienojis %{author}." - text_issue_updated: "Uzdevumu %{id} atjaunojis %{author}." - text_wiki_destroy_confirmation: "Vai esat droÅ¡s, ka vÄ“laties dzÄ“st Å¡o wiki un visu tÄs saturu?" - text_issue_category_destroy_question: "Daži uzdevumi (%{count}) ir nozÄ«mÄ“ti Å¡ai kategorijai. Ko JÅ«s vÄ“laties darÄ«t?" - text_issue_category_destroy_assignments: DzÄ“st kategoriju nozÄ«mÄ“jumus - text_issue_category_reassign_to: NozÄ«mÄ“t uzdevumus Å¡ai kategorijai - text_user_mail_option: "No neizvÄ“lÄ“tajiem projektiem JÅ«s saņemsiet ziņojumus e-pastÄ tikai par notikumiem, kuriem JÅ«s sekojat vai kuros esat iesaistÄ«ts." - text_no_configuration_data: "Lomas, trakeri, uzdevumu statusi un darba plÅ«smas vÄ“l nav konfigurÄ“tas.\nÄ»oti ieteicams ielÄdÄ“t noklusÄ“to konfigurÄciju. PÄ“c ielÄdēšanas to bÅ«s iespÄ“jams modificÄ“t." - text_load_default_configuration: IelÄdÄ“t noklusÄ“to konfigurÄciju - text_status_changed_by_changeset: "ApstiprinÄts izmaiņu kopumÄ %{value}." - text_issues_destroy_confirmation: 'Vai tieÅ¡Äm vÄ“laties dzÄ“st izvÄ“lÄ“to uzdevumu(us)?' - text_select_project_modules: 'IzvÄ“lieties moduļus Å¡im projektam:' - text_default_administrator_account_changed: NoklusÄ“tais administratora konts mainÄ«ts - text_file_repository_writable: Pielikumu direktorijÄ atļauts rakstÄ«t - text_plugin_assets_writable: Spraudņu kataloga direktorijÄ atļauts rakstÄ«t - text_rmagick_available: "RMagick pieejams (neobligÄts)" - text_destroy_time_entries_question: "%{hours} stundas tika ziņotas par uzdevumu, ko vÄ“laties dzÄ“st. Ko darÄ«t?" - text_destroy_time_entries: DzÄ“st ziņotÄs stundas - text_assign_time_entries_to_project: Piešķirt ziņotÄs stundas projektam - text_reassign_time_entries: 'Piešķirt ziņotÄs stundas uzdevumam:' - text_user_wrote: "%{value} rakstÄ«ja:" - text_enumeration_destroy_question: "%{count} objekti ir piešķirti Å¡ai vÄ“rtÄ«bai." - text_enumeration_category_reassign_to: 'Piešķirt tos Å¡ai vÄ“rtÄ«bai:' - text_email_delivery_not_configured: "E-pastu nosÅ«tīšana nav konfigurÄ“ta, un ziņojumi ir izslÄ“gti.\nKonfigurÄ“jiet savu SMTP serveri datnÄ“ config/configuration.yml un pÄrstartÄ“jiet lietotni." - text_repository_usernames_mapping: "IzvÄ“lieties vai atjaunojiet Redmine lietotÄju, saistÄ«tu ar katru lietotÄjvÄrdu, kas atrodams repozitorija žurnÄlÄ.\nLietotÄji ar to paÅ¡u Redmine un repozitorija lietotÄjvÄrdu bÅ«s saistÄ«ti automÄtiski." - text_diff_truncated: '... Å is diff tika nošķelts, jo tas pÄrsniedz maksimÄlo izmÄ“ru, ko var parÄdÄ«t.' - text_custom_field_possible_values_info: 'Katra vÄ“rtÄ«bas savÄ rindÄ' - text_wiki_page_destroy_question: "Å ij lapai ir %{descendants} apakÅ¡lapa(as) un pÄ“cnÄcÄ“ji. Ko darÄ«t?" - text_wiki_page_nullify_children: "PaturÄ“t apakÅ¡lapas kÄ pamatlapas" - text_wiki_page_destroy_children: "DzÄ“st apakÅ¡lapas un visus pÄ“cnÄcÄ“jus" - text_wiki_page_reassign_children: "Piešķirt apakÅ¡lapas Å¡ai lapai" - text_own_membership_delete_confirmation: "JÅ«s tÅ«lÄ«t dzÄ“sÄ«siet dažas vai visas atļaujas, un Jums pÄ“c tam var nebÅ«t atļauja labot Å¡o projektu.\nVai turpinÄt?" - - default_role_manager: Menedžeris - default_role_developer: IzstrÄdÄtÄjs - default_role_reporter: ZiņotÄjs - default_tracker_bug: Kļūda - default_tracker_feature: IezÄ«me - default_tracker_support: Atbalsts - default_issue_status_new: Jauns - default_issue_status_in_progress: AttÄ«stÄ«bÄ - default_issue_status_resolved: AtrisinÄts - default_issue_status_feedback: Atsauksmes - default_issue_status_closed: SlÄ“gts - default_issue_status_rejected: NoraidÄ«ts - default_doc_category_user: LietotÄja dokumentÄcija - default_doc_category_tech: TehniskÄ dokumentÄcija - default_priority_low: Zema - default_priority_normal: NormÄla - default_priority_high: Augsta - default_priority_urgent: Steidzama - default_priority_immediate: TÅ«lÄ«tÄ“ja - default_activity_design: Dizains - default_activity_development: IzstrÄdÄÅ¡ana - - enumeration_issue_priorities: Uzdevumu prioritÄtes - enumeration_doc_categories: Dokumentu kategorijas - enumeration_activities: AktivitÄtes (laika uzskaite) - enumeration_system_activity: SistÄ“mas aktivitÄtes - - error_can_not_delete_custom_field: Unable to delete custom field - permission_manage_subtasks: Manage subtasks - label_profile: Profile - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - field_parent_issue: Parent task - error_unable_delete_issue_status: Unable to delete issue status - label_subtask_plural: Subtasks - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - label_project_copy_notifications: Send email notifications during the project copy - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: KodÄ“t ziņojumus - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 uzdevums - one: 1 uzdevums - other: "%{count} uzdevumi" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: visi - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Ar apakšprojektiem - label_cross_project_tree: Ar projekta koku - label_cross_project_hierarchy: Ar projektu hierarhiju - label_cross_project_system: Ar visiem projektiem - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bb/bb33e344fd09578352c21338fb0d16b09b2dc03c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bb/bb33e344fd09578352c21338fb0d16b09b2dc03c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bb/bb4b688c8d8adb68413657f96f8e02f7400f5329.svn-base --- a/.svn/pristine/bb/bb4b688c8d8adb68413657f96f8e02f7400f5329.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -

    <%= l(:label_issue_plural) %>

    -<%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %>
    -<% if @project %> -<%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %>
    -<% end %> -<%= call_hook(:view_issues_sidebar_issues_bottom) %> - -<% if User.current.allowed_to?(:view_calendar, @project, :global => true) %> - <%= link_to(l(:label_calendar), :controller => 'calendars', :action => 'show', :project_id => @project) %>
    -<% end %> -<% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> - <%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %>
    -<% end %> -<%= call_hook(:view_issues_sidebar_planning_bottom) %> - -<%= render_sidebar_queries %> -<%= call_hook(:view_issues_sidebar_queries_bottom) %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bb/bb834d6c2f5551779bce71506eba9e5321f661e3.svn-base --- a/.svn/pristine/bb/bb834d6c2f5551779bce71506eba9e5321f661e3.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class SettingTest < ActiveSupport::TestCase - - def teardown - Setting.clear_cache - end - - def test_read_default - assert_equal "Redmine", Setting.app_title - assert Setting.self_registration? - assert !Setting.login_required? - end - - def test_update - Setting.app_title = "My title" - assert_equal "My title", Setting.app_title - # make sure db has been updated (INSERT) - assert_equal "My title", Setting.find_by_name('app_title').value - - Setting.app_title = "My other title" - assert_equal "My other title", Setting.app_title - # make sure db has been updated (UPDATE) - assert_equal "My other title", Setting.find_by_name('app_title').value - end - - def test_serialized_setting - Setting.notified_events = ['issue_added', 'issue_updated', 'news_added'] - assert_equal ['issue_added', 'issue_updated', 'news_added'], Setting.notified_events - assert_equal ['issue_added', 'issue_updated', 'news_added'], Setting.find_by_name('notified_events').value - end - - def test_setting_should_be_reloaded_after_clear_cache - Setting.app_title = "My title" - assert_equal "My title", Setting.app_title - - s = Setting.find_by_name("app_title") - s.value = 'New title' - s.save! - assert_equal "My title", Setting.app_title - - Setting.clear_cache - assert_equal "New title", Setting.app_title - end - - def test_per_page_options_array_should_be_an_empty_array_when_setting_is_blank - with_settings :per_page_options => nil do - assert_equal [], Setting.per_page_options_array - end - - with_settings :per_page_options => '' do - assert_equal [], Setting.per_page_options_array - end - end - - def test_per_page_options_array_should_be_an_array_of_integers - with_settings :per_page_options => '10, 25, 50' do - assert_equal [10, 25, 50], Setting.per_page_options_array - end - end - - def test_per_page_options_array_should_omit_non_numerial_values - with_settings :per_page_options => 'a, 25, 50' do - assert_equal [25, 50], Setting.per_page_options_array - end - end - - def test_per_page_options_array_should_be_sorted - with_settings :per_page_options => '25, 10, 50' do - assert_equal [10, 25, 50], Setting.per_page_options_array - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bc/bc30ce4931d78e6c793accaf2d15ef5a8dea2204.svn-base --- a/.svn/pristine/bc/bc30ce4931d78e6c793accaf2d15ef5a8dea2204.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,420 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'cgi' - -module Redmine - module Scm - module Adapters - class CommandFailed < StandardError #:nodoc: - end - - class AbstractAdapter #:nodoc: - - # raised if scm command exited with error, e.g. unknown revision. - class ScmCommandAborted < CommandFailed; end - - class << self - def client_command - "" - end - - def shell_quote_command - if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java' - client_command - else - shell_quote(client_command) - end - end - - # Returns the version of the scm client - # Eg: [1, 5, 0] or [] if unknown - def client_version - [] - end - - # Returns the version string of the scm client - # Eg: '1.5.0' or 'Unknown version' if unknown - def client_version_string - v = client_version || 'Unknown version' - v.is_a?(Array) ? v.join('.') : v.to_s - end - - # Returns true if the current client version is above - # or equals the given one - # If option is :unknown is set to true, it will return - # true if the client version is unknown - def client_version_above?(v, options={}) - ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) - end - - def client_available - true - end - - def shell_quote(str) - if Redmine::Platform.mswin? - '"' + str.gsub(/"/, '\\"') + '"' - else - "'" + str.gsub(/'/, "'\"'\"'") + "'" - end - end - end - - def initialize(url, root_url=nil, login=nil, password=nil, - path_encoding=nil) - @url = url - @login = login if login && !login.empty? - @password = (password || "") if @login - @root_url = root_url.blank? ? retrieve_root_url : root_url - end - - def adapter_name - 'Abstract' - end - - def supports_cat? - true - end - - def supports_annotate? - respond_to?('annotate') - end - - def root_url - @root_url - end - - def url - @url - end - - def path_encoding - nil - end - - # get info about the svn repository - def info - return nil - end - - # Returns the entry identified by path and revision identifier - # or nil if entry doesn't exist in the repository - def entry(path=nil, identifier=nil) - parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?} - search_path = parts[0..-2].join('/') - search_name = parts[-1] - if search_path.blank? && search_name.blank? - # Root entry - Entry.new(:path => '', :kind => 'dir') - else - # Search for the entry in the parent directory - es = entries(search_path, identifier) - es ? es.detect {|e| e.name == search_name} : nil - end - end - - # Returns an Entries collection - # or nil if the given path doesn't exist in the repository - def entries(path=nil, identifier=nil, options={}) - return nil - end - - def branches - return nil - end - - def tags - return nil - end - - def default_branch - return nil - end - - def properties(path, identifier=nil) - return nil - end - - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) - return nil - end - - def diff(path, identifier_from, identifier_to=nil) - return nil - end - - def cat(path, identifier=nil) - return nil - end - - def with_leading_slash(path) - path ||= '' - (path[0,1]!="/") ? "/#{path}" : path - end - - def with_trailling_slash(path) - path ||= '' - (path[-1,1] == "/") ? path : "#{path}/" - end - - def without_leading_slash(path) - path ||= '' - path.gsub(%r{^/+}, '') - end - - def without_trailling_slash(path) - path ||= '' - (path[-1,1] == "/") ? path[0..-2] : path - end - - def shell_quote(str) - self.class.shell_quote(str) - end - - private - def retrieve_root_url - info = self.info - info ? info.root_url : nil - end - - def target(path, sq=true) - path ||= '' - base = path.match(/^\//) ? root_url : url - str = "#{base}/#{path}".gsub(/[?<>\*]/, '') - if sq - str = shell_quote(str) - end - str - end - - def logger - self.class.logger - end - - def shellout(cmd, options = {}, &block) - self.class.shellout(cmd, options, &block) - end - - def self.logger - Rails.logger - end - - def self.shellout(cmd, options = {}, &block) - if logger && logger.debug? - logger.debug "Shelling out: #{strip_credential(cmd)}" - end - if Rails.env == 'development' - # Capture stderr when running in dev environment - cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}" - end - begin - mode = "r+" - IO.popen(cmd, mode) do |io| - io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding) - io.close_write unless options[:write_stdin] - block.call(io) if block_given? - end - ## If scm command does not exist, - ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException - ## in production environment. - # rescue Errno::ENOENT => e - rescue Exception => e - msg = strip_credential(e.message) - # The command failed, log it and re-raise - logmsg = "SCM command failed, " - logmsg += "make sure that your SCM command (e.g. svn) is " - logmsg += "in PATH (#{ENV['PATH']})\n" - logmsg += "You can configure your scm commands in config/configuration.yml.\n" - logmsg += "#{strip_credential(cmd)}\n" - logmsg += "with: #{msg}" - logger.error(logmsg) - raise CommandFailed.new(msg) - end - end - - # Hides username/password in a given command - def self.strip_credential(cmd) - q = (Redmine::Platform.mswin? ? '"' : "'") - cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx') - end - - def strip_credential(cmd) - self.class.strip_credential(cmd) - end - - def scm_iconv(to, from, str) - return nil if str.nil? - return str if to == from - if str.respond_to?(:force_encoding) - str.force_encoding(from) - begin - str.encode(to) - rescue Exception => err - logger.error("failed to convert from #{from} to #{to}. #{err}") - nil - end - else - begin - Iconv.conv(to, from, str) - rescue Iconv::Failure => err - logger.error("failed to convert from #{from} to #{to}. #{err}") - nil - end - end - end - - def parse_xml(xml) - if RUBY_PLATFORM == 'java' - xml = xml.sub(%r{<\?xml[^>]*\?>}, '') - end - ActiveSupport::XmlMini.parse(xml) - end - end - - class Entries < Array - def sort_by_name - dup.sort! {|x,y| - if x.kind == y.kind - x.name.to_s <=> y.name.to_s - else - x.kind <=> y.kind - end - } - end - - def revisions - revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact) - end - end - - class Info - attr_accessor :root_url, :lastrev - def initialize(attributes={}) - self.root_url = attributes[:root_url] if attributes[:root_url] - self.lastrev = attributes[:lastrev] - end - end - - class Entry - attr_accessor :name, :path, :kind, :size, :lastrev, :changeset - - def initialize(attributes={}) - self.name = attributes[:name] if attributes[:name] - self.path = attributes[:path] if attributes[:path] - self.kind = attributes[:kind] if attributes[:kind] - self.size = attributes[:size].to_i if attributes[:size] - self.lastrev = attributes[:lastrev] - end - - def is_file? - 'file' == self.kind - end - - def is_dir? - 'dir' == self.kind - end - - def is_text? - Redmine::MimeType.is_type?('text', name) - end - - def author - if changeset - changeset.author.to_s - elsif lastrev - Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first) - end - end - end - - class Revisions < Array - def latest - sort {|x,y| - unless x.time.nil? or y.time.nil? - x.time <=> y.time - else - 0 - end - }.last - end - end - - class Revision - attr_accessor :scmid, :name, :author, :time, :message, - :paths, :revision, :branch, :identifier, - :parents - - def initialize(attributes={}) - self.identifier = attributes[:identifier] - self.scmid = attributes[:scmid] - self.name = attributes[:name] || self.identifier - self.author = attributes[:author] - self.time = attributes[:time] - self.message = attributes[:message] || "" - self.paths = attributes[:paths] - self.revision = attributes[:revision] - self.branch = attributes[:branch] - self.parents = attributes[:parents] - end - - # Returns the readable identifier. - def format_identifier - self.identifier.to_s - end - - def ==(other) - if other.nil? - false - elsif scmid.present? - scmid == other.scmid - elsif identifier.present? - identifier == other.identifier - elsif revision.present? - revision == other.revision - end - end - end - - class Annotate - attr_reader :lines, :revisions - - def initialize - @lines = [] - @revisions = [] - end - - def add_line(line, revision) - @lines << line - @revisions << revision - end - - def content - content = lines.join("\n") - end - - def empty? - lines.empty? - end - end - - class Branch < String - attr_accessor :revision, :scmid - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bc/bc5cd9a0ce7e6dce66f1e1a15e9e36c9090b2ffd.svn-base --- a/.svn/pristine/bc/bc5cd9a0ce7e6dce66f1e1a15e9e36c9090b2ffd.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -

    <%= l(:label_calendar) %>

    - -<% calendar = Redmine::Helpers::Calendar.new(Date.today, current_language, :week) - calendar.events = Issue.visible.find :all, - :conditions => ["#{Issue.table_name}.project_id in (#{@user.projects.collect{|m| m.id}.join(',')}) AND ((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?))", calendar.startdt, calendar.enddt, calendar.startdt, calendar.enddt], - :include => [:project, :tracker, :priority, :assigned_to] unless @user.projects.empty? %> - -<%= render :partial => 'common/calendar', :locals => {:calendar => calendar } %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bc/bc6c8f57596923e6557efae3c8c80b8c20b03444.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bc/bc6c8f57596923e6557efae3c8c80b8c20b03444.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +
    +<%= link_to l(:button_update), edit_issue_path(@issue), :onclick => 'showAndScrollTo("update", "issue_notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit) if @issue.editable? %> +<%= link_to l(:button_log_time), new_issue_time_entry_path(@issue), :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project) %> +<%= watcher_link(@issue, User.current) %> +<%= link_to l(:button_copy), project_copy_issue_path(@project, @issue), :class => 'icon icon-copy' if User.current.allowed_to?(:add_issues, @project) %> +<%= link_to l(:button_delete), issue_path(@issue), :data => {:confirm => issues_destroy_confirmation_message(@issue)}, :method => :delete, :class => 'icon icon-del' if User.current.allowed_to?(:delete_issues, @project) %> +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bc/bc78f131f015345e2b1b17598e4635974e716b9c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bc/bc78f131f015345e2b1b17598e4635974e716b9c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,51 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class ProjectsTest < ActionController::IntegrationTest + fixtures :projects, :users, :members, :enabled_modules + + def test_archive_project + subproject = Project.find(1).children.first + log_user("admin", "admin") + get "admin/projects" + assert_response :success + assert_template "admin/projects" + post "projects/1/archive" + assert_redirected_to "/admin/projects" + assert !Project.find(1).active? + + get 'projects/1' + assert_response 403 + get "projects/#{subproject.id}" + assert_response 403 + + post "projects/1/unarchive" + assert_redirected_to "/admin/projects" + assert Project.find(1).active? + get "projects/1" + assert_response :success + end + + def test_modules_should_not_allow_get + assert_no_difference 'EnabledModule.count' do + get '/projects/1/modules', {:enabled_module_names => ['']}, credentials('jsmith') + assert_response 404 + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bc/bc9185ee675b6fb8b2207370c1cf95af6aa786d9.svn-base --- a/.svn/pristine/bc/bc9185ee675b6fb8b2207370c1cf95af6aa786d9.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -

    <%=l(:label_document_plural)%>

    - -<% project_ids = @user.projects.select {|p| @user.allowed_to?(:view_documents, p)}.collect(&:id) %> -<%= render(:partial => 'documents/document', - :collection => Document.find(:all, - :limit => 10, - :order => "#{Document.table_name}.created_on DESC", - :conditions => "#{Document.table_name}.project_id in (#{project_ids.join(',')})", - :include => [:project])) unless project_ids.empty? %> \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bc/bcd855d1f37773ea6cbe38e91cc6db056d7aef2e.svn-base --- a/.svn/pristine/bc/bcd855d1f37773ea6cbe38e91cc6db056d7aef2e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -
    -<%= link_to(l(:button_edit), edit_user_path(@user), :class => 'icon icon-edit') if User.current.admin? %> -
    - -

    <%= avatar @user, :size => "50" %> <%=h @user.name %>

    - -
    -
      - <% unless @user.pref.hide_mail %> -
    • <%=l(:field_mail)%>: <%= mail_to(h(@user.mail), nil, :encode => 'javascript') %>
    • - <% end %> - <% @user.visible_custom_field_values.each do |custom_value| %> - <% if !custom_value.value.blank? %> -
    • <%=h custom_value.custom_field.name%>: <%=h show_value(custom_value) %>
    • - <% end %> - <% end %> -
    • <%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %>
    • - <% unless @user.last_login_on.nil? %> -
    • <%=l(:field_last_login_on)%>: <%= format_date(@user.last_login_on) %>
    • - <% end %> -
    - -<% unless @memberships.empty? %> -

    <%=l(:label_project_plural)%>

    -
      -<% for membership in @memberships %> -
    • <%= link_to_project(membership.project) %> - (<%=h membership.roles.sort.collect(&:to_s).join(', ') %>, <%= format_date(membership.created_on) %>)
    • -<% end %> -
    -<% end %> -<%= call_hook :view_account_left_bottom, :user => @user %> -
    - -
    - -<% unless @events_by_day.empty? %> -

    <%= link_to l(:label_activity), :controller => 'activities', - :action => 'index', :id => nil, :user_id => @user, - :from => @events_by_day.keys.first %>

    - -

    -<%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %> -

    - -
    -<% @events_by_day.keys.sort.reverse.each do |day| %> -

    <%= format_activity_day(day) %>

    -
    -<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> -
    - <%= format_time(e.event_datetime, false) %> - <%= content_tag('span', h(e.project), :class => 'project') %> - <%= link_to format_activity_title(e.event_title), e.event_url %>
    -
    <%= format_activity_description(e.event_description) %>
    -<% end -%> -
    -<% end -%> -
    - -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => nil, :user_id => @user, :key => User.current.rss_key} %> -<% end %> - -<% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :user_id => @user, :format => :atom, :key => User.current.rss_key) %> -<% end %> -<% end %> -<%= call_hook :view_account_right_bottom, :user => @user %> -
    - -<% html_title @user.name %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bc/bcefb9a2bbe2d9c2d605eef1915f4d25e465456c.svn-base --- a/.svn/pristine/bc/bcefb9a2bbe2d9c2d605eef1915f4d25e465456c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,772 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require 'active_record' -require 'iconv' -require 'pp' - -namespace :redmine do - desc 'Trac migration script' - task :migrate_from_trac => :environment do - - module TracMigrate - TICKET_MAP = [] - - DEFAULT_STATUS = IssueStatus.default - assigned_status = IssueStatus.find_by_position(2) - resolved_status = IssueStatus.find_by_position(3) - feedback_status = IssueStatus.find_by_position(4) - closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } - STATUS_MAPPING = {'new' => DEFAULT_STATUS, - 'reopened' => feedback_status, - 'assigned' => assigned_status, - 'closed' => closed_status - } - - priorities = IssuePriority.all - DEFAULT_PRIORITY = priorities[0] - PRIORITY_MAPPING = {'lowest' => priorities[0], - 'low' => priorities[0], - 'normal' => priorities[1], - 'high' => priorities[2], - 'highest' => priorities[3], - # --- - 'trivial' => priorities[0], - 'minor' => priorities[1], - 'major' => priorities[2], - 'critical' => priorities[3], - 'blocker' => priorities[4] - } - - TRACKER_BUG = Tracker.find_by_position(1) - TRACKER_FEATURE = Tracker.find_by_position(2) - DEFAULT_TRACKER = TRACKER_BUG - TRACKER_MAPPING = {'defect' => TRACKER_BUG, - 'enhancement' => TRACKER_FEATURE, - 'task' => TRACKER_FEATURE, - 'patch' =>TRACKER_FEATURE - } - - roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') - manager_role = roles[0] - developer_role = roles[1] - DEFAULT_ROLE = roles.last - ROLE_MAPPING = {'admin' => manager_role, - 'developer' => developer_role - } - - class ::Time - class << self - alias :real_now :now - def now - real_now - @fake_diff.to_i - end - def fake(time) - @fake_diff = real_now - time - res = yield - @fake_diff = 0 - res - end - end - end - - class TracComponent < ActiveRecord::Base - self.table_name = :component - end - - class TracMilestone < ActiveRecord::Base - self.table_name = :milestone - # If this attribute is set a milestone has a defined target timepoint - def due - if read_attribute(:due) && read_attribute(:due) > 0 - Time.at(read_attribute(:due)).to_date - else - nil - end - end - # This is the real timepoint at which the milestone has finished. - def completed - if read_attribute(:completed) && read_attribute(:completed) > 0 - Time.at(read_attribute(:completed)).to_date - else - nil - end - end - - def description - # Attribute is named descr in Trac v0.8.x - has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description) - end - end - - class TracTicketCustom < ActiveRecord::Base - self.table_name = :ticket_custom - end - - class TracAttachment < ActiveRecord::Base - self.table_name = :attachment - set_inheritance_column :none - - def time; Time.at(read_attribute(:time)) end - - def original_filename - filename - end - - def content_type - '' - end - - def exist? - File.file? trac_fullpath - end - - def open - File.open("#{trac_fullpath}", 'rb') {|f| - @file = f - yield self - } - end - - def read(*args) - @file.read(*args) - end - - def description - read_attribute(:description).to_s.slice(0,255) - end - - private - def trac_fullpath - attachment_type = read_attribute(:type) - trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) } - "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}" - end - end - - class TracTicket < ActiveRecord::Base - self.table_name = :ticket - set_inheritance_column :none - - # ticket changes: only migrate status changes and comments - has_many :ticket_changes, :class_name => "TracTicketChange", :foreign_key => :ticket - has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket - - def attachments - TracMigrate::TracAttachment.all(:conditions => ["type = 'ticket' AND id = ?", self.id.to_s]) - end - - def ticket_type - read_attribute(:type) - end - - def summary - read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary) - end - - def description - read_attribute(:description).blank? ? summary : read_attribute(:description) - end - - def time; Time.at(read_attribute(:time)) end - def changetime; Time.at(read_attribute(:changetime)) end - end - - class TracTicketChange < ActiveRecord::Base - self.table_name = :ticket_change - - def self.columns - # Hides Trac field 'field' to prevent clash with AR field_changed? method (Rails 3.0) - super.select {|column| column.name.to_s != 'field'} - end - - def time; Time.at(read_attribute(:time)) end - end - - TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \ - TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \ - TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \ - TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \ - TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \ - WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \ - CamelCase TitleIndex) - - class TracWikiPage < ActiveRecord::Base - self.table_name = :wiki - set_primary_key :name - - def self.columns - # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0) - super.select {|column| column.name.to_s != 'readonly'} - end - - def attachments - TracMigrate::TracAttachment.all(:conditions => ["type = 'wiki' AND id = ?", self.id.to_s]) - end - - def time; Time.at(read_attribute(:time)) end - end - - class TracPermission < ActiveRecord::Base - self.table_name = :permission - end - - class TracSessionAttribute < ActiveRecord::Base - self.table_name = :session_attribute - end - - def self.find_or_create_user(username, project_member = false) - return User.anonymous if username.blank? - - u = User.find_by_login(username) - if !u - # Create a new user if not found - mail = username[0, User::MAIL_LENGTH_LIMIT] - if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email') - mail = mail_attr.value - end - mail = "#{mail}@foo.bar" unless mail.include?("@") - - name = username - if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name') - name = name_attr.value - end - name =~ (/(.*)(\s+\w+)?/) - fn = $1.strip - ln = ($2 || '-').strip - - u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), - :firstname => fn[0, limit_for(User, 'firstname')], - :lastname => ln[0, limit_for(User, 'lastname')] - - u.login = username[0, User::LOGIN_LENGTH_LIMIT].gsub(/[^a-z0-9_\-@\.]/i, '-') - u.password = 'trac' - u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') - # finally, a default user is used if the new user is not valid - u = User.find(:first) unless u.save - end - # Make sure he is a member of the project - if project_member && !u.member_of?(@target_project) - role = DEFAULT_ROLE - if u.admin - role = ROLE_MAPPING['admin'] - elsif TracPermission.find_by_username_and_action(username, 'developer') - role = ROLE_MAPPING['developer'] - end - Member.create(:user => u, :project => @target_project, :roles => [role]) - u.reload - end - u - end - - # Basic wiki syntax conversion - def self.convert_wiki_text(text) - # Titles - text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"} - # External Links - text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"} - # Ticket links: - # [ticket:234 Text],[ticket:234 This is a test] - text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1') - # ticket:1234 - # #1 is working cause Redmine uses the same syntax. - text = text.gsub(/ticket\:([^\ ]+)/, '#\1') - # Milestone links: - # [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)] - # The text "Milestone 0.1.0 (Mercury)" is not converted, - # cause Redmine's wiki does not support this. - text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"') - # [milestone:"0.1.0 Mercury"] - text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"') - text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"') - # milestone:0.1.0 - text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1') - text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1') - # Internal Links - text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below - text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} - text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} - text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} - text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} - text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"} - - # Links to pages UsingJustWikiCaps - text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]') - # Normalize things that were supposed to not be links - # like !NotALink - text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2') - # Revisions links - text = text.gsub(/\[(\d+)\]/, 'r\1') - # Ticket number re-writing - text = text.gsub(/#(\d+)/) do |s| - if $1.length < 10 -# TICKET_MAP[$1.to_i] ||= $1 - "\##{TICKET_MAP[$1.to_i] || $1}" - else - s - end - end - # We would like to convert the Code highlighting too - # This will go into the next line. - shebang_line = false - # Reguar expression for start of code - pre_re = /\{\{\{/ - # Code hightlighing... - shebang_re = /^\#\!([a-z]+)/ - # Regular expression for end of code - pre_end_re = /\}\}\}/ - - # Go through the whole text..extract it line by line - text = text.gsub(/^(.*)$/) do |line| - m_pre = pre_re.match(line) - if m_pre - line = '
    '
    -          else
    -            m_sl = shebang_re.match(line)
    -            if m_sl
    -              shebang_line = true
    -              line = ''
    -            end
    -            m_pre_end = pre_end_re.match(line)
    -            if m_pre_end
    -              line = '
    ' - if shebang_line - line = '' + line - end - end - end - line - end - - # Highlighting - text = text.gsub(/'''''([^\s])/, '_*\1') - text = text.gsub(/([^\s])'''''/, '\1*_') - text = text.gsub(/'''/, '*') - text = text.gsub(/''/, '_') - text = text.gsub(/__/, '+') - text = text.gsub(/~~/, '-') - text = text.gsub(/`/, '@') - text = text.gsub(/,,/, '~') - # Lists - text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "} - - text - end - - def self.migrate - establish_connection - - # Quick database test - TracComponent.count - - migrated_components = 0 - migrated_milestones = 0 - migrated_tickets = 0 - migrated_custom_values = 0 - migrated_ticket_attachments = 0 - migrated_wiki_edits = 0 - migrated_wiki_attachments = 0 - - #Wiki system initializing... - @target_project.wiki.destroy if @target_project.wiki - @target_project.reload - wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart') - wiki_edit_count = 0 - - # Components - print "Migrating components" - issues_category_map = {} - TracComponent.find(:all).each do |component| - print '.' - STDOUT.flush - c = IssueCategory.new :project => @target_project, - :name => encode(component.name[0, limit_for(IssueCategory, 'name')]) - next unless c.save - issues_category_map[component.name] = c - migrated_components += 1 - end - puts - - # Milestones - print "Migrating milestones" - version_map = {} - TracMilestone.find(:all).each do |milestone| - print '.' - STDOUT.flush - # First we try to find the wiki page... - p = wiki.find_or_new_page(milestone.name.to_s) - p.content = WikiContent.new(:page => p) if p.new_record? - p.content.text = milestone.description.to_s - p.content.author = find_or_create_user('trac') - p.content.comments = 'Milestone' - p.save - - v = Version.new :project => @target_project, - :name => encode(milestone.name[0, limit_for(Version, 'name')]), - :description => nil, - :wiki_page_title => milestone.name.to_s, - :effective_date => milestone.completed - - next unless v.save - version_map[milestone.name] = v - migrated_milestones += 1 - end - puts - - # Custom fields - # TODO: read trac.ini instead - print "Migrating custom fields" - custom_field_map = {} - TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field| - print '.' - STDOUT.flush - # Redmine custom field name - field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize - # Find if the custom already exists in Redmine - f = IssueCustomField.find_by_name(field_name) - # Or create a new one - f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize, - :field_format => 'string') - - next if f.new_record? - f.trackers = Tracker.find(:all) - f.projects << @target_project - custom_field_map[field.name] = f - end - puts - - # Trac 'resolution' field as a Redmine custom field - r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" }) - r = IssueCustomField.new(:name => 'Resolution', - :field_format => 'list', - :is_filter => true) if r.nil? - r.trackers = Tracker.find(:all) - r.projects << @target_project - r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq - r.save! - custom_field_map['resolution'] = r - - # Tickets - print "Migrating tickets" - TracTicket.find_each(:batch_size => 200) do |ticket| - print '.' - STDOUT.flush - i = Issue.new :project => @target_project, - :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]), - :description => convert_wiki_text(encode(ticket.description)), - :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY, - :created_on => ticket.time - i.author = find_or_create_user(ticket.reporter) - i.category = issues_category_map[ticket.component] unless ticket.component.blank? - i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank? - i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS - i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER - i.id = ticket.id unless Issue.exists?(ticket.id) - next unless Time.fake(ticket.changetime) { i.save } - TICKET_MAP[ticket.id] = i.id - migrated_tickets += 1 - - # Owner - unless ticket.owner.blank? - i.assigned_to = find_or_create_user(ticket.owner, true) - Time.fake(ticket.changetime) { i.save } - end - - # Comments and status/resolution changes - ticket.ticket_changes.group_by(&:time).each do |time, changeset| - status_change = changeset.select {|change| change.field == 'status'}.first - resolution_change = changeset.select {|change| change.field == 'resolution'}.first - comment_change = changeset.select {|change| change.field == 'comment'}.first - - n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''), - :created_on => time - n.user = find_or_create_user(changeset.first.author) - n.journalized = i - if status_change && - STATUS_MAPPING[status_change.oldvalue] && - STATUS_MAPPING[status_change.newvalue] && - (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue]) - n.details << JournalDetail.new(:property => 'attr', - :prop_key => 'status_id', - :old_value => STATUS_MAPPING[status_change.oldvalue].id, - :value => STATUS_MAPPING[status_change.newvalue].id) - end - if resolution_change - n.details << JournalDetail.new(:property => 'cf', - :prop_key => custom_field_map['resolution'].id, - :old_value => resolution_change.oldvalue, - :value => resolution_change.newvalue) - end - n.save unless n.details.empty? && n.notes.blank? - end - - # Attachments - ticket.attachments.each do |attachment| - next unless attachment.exist? - attachment.open { - a = Attachment.new :created_on => attachment.time - a.file = attachment - a.author = find_or_create_user(attachment.author) - a.container = i - a.description = attachment.description - migrated_ticket_attachments += 1 if a.save - } - end - - # Custom fields - custom_values = ticket.customs.inject({}) do |h, custom| - if custom_field = custom_field_map[custom.name] - h[custom_field.id] = custom.value - migrated_custom_values += 1 - end - h - end - if custom_field_map['resolution'] && !ticket.resolution.blank? - custom_values[custom_field_map['resolution'].id] = ticket.resolution - end - i.custom_field_values = custom_values - i.save_custom_field_values - end - - # update issue id sequence if needed (postgresql) - Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!') - puts - - # Wiki - print "Migrating wiki" - if wiki.save - TracWikiPage.find(:all, :order => 'name, version').each do |page| - # Do not migrate Trac manual wiki pages - next if TRAC_WIKI_PAGES.include?(page.name) - wiki_edit_count += 1 - print '.' - STDOUT.flush - p = wiki.find_or_new_page(page.name) - p.content = WikiContent.new(:page => p) if p.new_record? - p.content.text = page.text - p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac' - p.content.comments = page.comment - Time.fake(page.time) { p.new_record? ? p.save : p.content.save } - - next if p.content.new_record? - migrated_wiki_edits += 1 - - # Attachments - page.attachments.each do |attachment| - next unless attachment.exist? - next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page - attachment.open { - a = Attachment.new :created_on => attachment.time - a.file = attachment - a.author = find_or_create_user(attachment.author) - a.description = attachment.description - a.container = p - migrated_wiki_attachments += 1 if a.save - } - end - end - - wiki.reload - wiki.pages.each do |page| - page.content.text = convert_wiki_text(page.content.text) - Time.fake(page.content.updated_on) { page.content.save } - end - end - puts - - puts - puts "Components: #{migrated_components}/#{TracComponent.count}" - puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}" - puts "Tickets: #{migrated_tickets}/#{TracTicket.count}" - puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s - puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}" - puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}" - puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s - end - - def self.limit_for(klass, attribute) - klass.columns_hash[attribute.to_s].limit - end - - def self.encoding(charset) - @ic = Iconv.new('UTF-8', charset) - rescue Iconv::InvalidEncoding - puts "Invalid encoding!" - return false - end - - def self.set_trac_directory(path) - @@trac_directory = path - raise "This directory doesn't exist!" unless File.directory?(path) - raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory) - @@trac_directory - rescue Exception => e - puts e - return false - end - - def self.trac_directory - @@trac_directory - end - - def self.set_trac_adapter(adapter) - return false if adapter.blank? - raise "Unknown adapter: #{adapter}!" unless %w(sqlite3 mysql postgresql).include?(adapter) - # If adapter is sqlite or sqlite3, make sure that trac.db exists - raise "#{trac_db_path} doesn't exist!" if %w(sqlite3).include?(adapter) && !File.exist?(trac_db_path) - @@trac_adapter = adapter - rescue Exception => e - puts e - return false - end - - def self.set_trac_db_host(host) - return nil if host.blank? - @@trac_db_host = host - end - - def self.set_trac_db_port(port) - return nil if port.to_i == 0 - @@trac_db_port = port.to_i - end - - def self.set_trac_db_name(name) - return nil if name.blank? - @@trac_db_name = name - end - - def self.set_trac_db_username(username) - @@trac_db_username = username - end - - def self.set_trac_db_password(password) - @@trac_db_password = password - end - - def self.set_trac_db_schema(schema) - @@trac_db_schema = schema - end - - mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password - - def self.trac_db_path; "#{trac_directory}/db/trac.db" end - def self.trac_attachments_directory; "#{trac_directory}/attachments" end - - def self.target_project_identifier(identifier) - project = Project.find_by_identifier(identifier) - if !project - # create the target project - project = Project.new :name => identifier.humanize, - :description => '' - project.identifier = identifier - puts "Unable to create a project with identifier '#{identifier}'!" unless project.save - # enable issues and wiki for the created project - project.enabled_module_names = ['issue_tracking', 'wiki'] - else - puts - puts "This project already exists in your Redmine database." - print "Are you sure you want to append data to this project ? [Y/n] " - STDOUT.flush - exit if STDIN.gets.match(/^n$/i) - end - project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG) - project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE) - @target_project = project.new_record? ? nil : project - @target_project.reload - end - - def self.connection_params - if trac_adapter == 'sqlite3' - {:adapter => 'sqlite3', - :database => trac_db_path} - else - {:adapter => trac_adapter, - :database => trac_db_name, - :host => trac_db_host, - :port => trac_db_port, - :username => trac_db_username, - :password => trac_db_password, - :schema_search_path => trac_db_schema - } - end - end - - def self.establish_connection - constants.each do |const| - klass = const_get(const) - next unless klass.respond_to? 'establish_connection' - klass.establish_connection connection_params - end - end - - private - def self.encode(text) - @ic.iconv text - rescue - text - end - end - - puts - if Redmine::DefaultData::Loader.no_data? - puts "Redmine configuration need to be loaded before importing data." - puts "Please, run this first:" - puts - puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" - exit - end - - puts "WARNING: a new project will be added to Redmine during this process." - print "Are you sure you want to continue ? [y/N] " - STDOUT.flush - break unless STDIN.gets.match(/^y$/i) - puts - - def prompt(text, options = {}, &block) - default = options[:default] || '' - while true - print "#{text} [#{default}]: " - STDOUT.flush - value = STDIN.gets.chomp! - value = default if value.blank? - break if yield value - end - end - - DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432} - - prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip} - prompt('Trac database adapter (sqlite3, mysql2, postgresql)', :default => 'sqlite3') {|adapter| TracMigrate.set_trac_adapter adapter} - unless %w(sqlite3).include?(TracMigrate.trac_adapter) - prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host} - prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port} - prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name} - prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema} - prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username} - prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password} - end - prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding} - prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier} - puts - - # Turn off email notifications - Setting.notified_events = [] - - TracMigrate.migrate - end -end - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bd/bd00588170200c1a584eeedf772a99216b774e3a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bd/bd00588170200c1a584eeedf772a99216b774e3a.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class UpdateQueriesToSti < ActiveRecord::Migration + def up + ::Query.update_all :type => 'IssueQuery' + end + + def down + ::Query.update_all :type => nil + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bd/bd122c892e2cdfabeea5c22cd41a607729e832fd.svn-base --- a/.svn/pristine/bd/bd122c892e2cdfabeea5c22cd41a607729e832fd.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingReportsTest < ActionController::IntegrationTest - def test_reports - assert_routing( - { :method => 'get', :path => "/projects/567/issues/report" }, - { :controller => 'reports', :action => 'issue_report', :id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/issues/report/assigned_to" }, - { :controller => 'reports', :action => 'issue_report_details', - :id => '567', :detail => 'assigned_to' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bd/bd1a4c5eaed10d182ad04396f746e4bc87bd2e06.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bd/bd1a4c5eaed10d182ad04396f746e4bc87bd2e06.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,45 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class WorkflowPermission < WorkflowRule + validates_inclusion_of :rule, :in => %w(readonly required) + validate :validate_field_name + + # Replaces the workflow permissions for the given tracker and role + # + # Example: + # WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}} + def self.replace_permissions(tracker, role, permissions) + destroy_all(:tracker_id => tracker.id, :role_id => role.id) + + permissions.each { |field, rule_by_status_id| + rule_by_status_id.each { |status_id, rule| + if rule.present? + WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule) + end + } + } + end + + protected + + def validate_field_name + unless Tracker::CORE_FIELDS_ALL.include?(field_name) || field_name.to_s.match(/^\d+$/) + errors.add :field_name, :invalid + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bd/bd20515ba5f4454bba8c775f551e735dca61a27d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bd/bd20515ba5f4454bba8c775f551e735dca61a27d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,870 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class QueryColumn + attr_accessor :name, :sortable, :groupable, :default_order + include Redmine::I18n + + def initialize(name, options={}) + self.name = name + self.sortable = options[:sortable] + self.groupable = options[:groupable] || false + if groupable == true + self.groupable = name.to_s + end + self.default_order = options[:default_order] + @inline = options.key?(:inline) ? options[:inline] : true + @caption_key = options[:caption] || "field_#{name}".to_sym + @frozen = options[:frozen] + end + + def caption + @caption_key.is_a?(Symbol) ? l(@caption_key) : @caption_key + end + + # Returns true if the column is sortable, otherwise false + def sortable? + !@sortable.nil? + end + + def sortable + @sortable.is_a?(Proc) ? @sortable.call : @sortable + end + + def inline? + @inline + end + + def frozen? + @frozen + end + + def value(object) + object.send name + end + + def css_classes + name + end +end + +class QueryCustomFieldColumn < QueryColumn + + def initialize(custom_field) + self.name = "cf_#{custom_field.id}".to_sym + self.sortable = custom_field.order_statement || false + self.groupable = custom_field.group_statement || false + @inline = true + @cf = custom_field + end + + def caption + @cf.name + end + + def custom_field + @cf + end + + def value(object) + if custom_field.visible_by?(object.project, User.current) + cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)} + cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first + else + nil + end + end + + def css_classes + @css_classes ||= "#{name} #{@cf.field_format}" + end +end + +class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn + + def initialize(association, custom_field) + super(custom_field) + self.name = "#{association}.cf_#{custom_field.id}".to_sym + # TODO: support sorting/grouping by association custom field + self.sortable = false + self.groupable = false + @association = association + end + + def value(object) + if assoc = object.send(@association) + super(assoc) + end + end + + def css_classes + @css_classes ||= "#{@association}_cf_#{@cf.id} #{@cf.field_format}" + end +end + +class Query < ActiveRecord::Base + class StatementInvalid < ::ActiveRecord::StatementInvalid + end + + VISIBILITY_PRIVATE = 0 + VISIBILITY_ROLES = 1 + VISIBILITY_PUBLIC = 2 + + belongs_to :project + belongs_to :user + has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "query_id" + serialize :filters + serialize :column_names + serialize :sort_criteria, Array + serialize :options, Hash + + attr_protected :project_id, :user_id + + validates_presence_of :name + validates_length_of :name, :maximum => 255 + validates :visibility, :inclusion => { :in => [VISIBILITY_PUBLIC, VISIBILITY_ROLES, VISIBILITY_PRIVATE] } + validate :validate_query_filters + validate do |query| + errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) if query.visibility == VISIBILITY_ROLES && roles.blank? + end + + after_save do |query| + if query.visibility_changed? && query.visibility != VISIBILITY_ROLES + query.roles.clear + end + end + + class_attribute :operators + self.operators = { + "=" => :label_equals, + "!" => :label_not_equals, + "o" => :label_open_issues, + "c" => :label_closed_issues, + "!*" => :label_none, + "*" => :label_any, + ">=" => :label_greater_or_equal, + "<=" => :label_less_or_equal, + "><" => :label_between, + " :label_in_less_than, + ">t+" => :label_in_more_than, + "> :label_in_the_next_days, + "t+" => :label_in, + "t" => :label_today, + "ld" => :label_yesterday, + "w" => :label_this_week, + "lw" => :label_last_week, + "l2w" => [:label_last_n_weeks, {:count => 2}], + "m" => :label_this_month, + "lm" => :label_last_month, + "y" => :label_this_year, + ">t-" => :label_less_than_ago, + " :label_more_than_ago, + "> :label_in_the_past_days, + "t-" => :label_ago, + "~" => :label_contains, + "!~" => :label_not_contains, + "=p" => :label_any_issues_in_project, + "=!p" => :label_any_issues_not_in_project, + "!p" => :label_no_issues_in_project + } + + class_attribute :operators_by_filter_type + self.operators_by_filter_type = { + :list => [ "=", "!" ], + :list_status => [ "o", "=", "!", "c", "*" ], + :list_optional => [ "=", "!", "!*", "*" ], + :list_subprojects => [ "*", "!*", "=" ], + :date => [ "=", ">=", "<=", "><", "t+", ">t-", " [ "=", ">=", "<=", "><", ">t-", " [ "=", "~", "!", "!~", "!*", "*" ], + :text => [ "~", "!~", "!*", "*" ], + :integer => [ "=", ">=", "<=", "><", "!*", "*" ], + :float => [ "=", ">=", "<=", "><", "!*", "*" ], + :relation => ["=", "=p", "=!p", "!p", "!*", "*"] + } + + class_attribute :available_columns + self.available_columns = [] + + class_attribute :queried_class + + def queried_table_name + @queried_table_name ||= self.class.queried_class.table_name + end + + def initialize(attributes=nil, *args) + super attributes + @is_for_all = project.nil? + end + + # Builds the query from the given params + def build_from_params(params) + if params[:fields] || params[:f] + self.filters = {} + add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) + else + available_filters.keys.each do |field| + add_short_filter(field, params[field]) if params[field] + end + end + self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by]) + self.column_names = params[:c] || (params[:query] && params[:query][:column_names]) + self + end + + # Builds a new query from the given params and attributes + def self.build_from_params(params, attributes={}) + new(attributes).build_from_params(params) + end + + def validate_query_filters + filters.each_key do |field| + if values_for(field) + case type_for(field) + when :integer + add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) } + when :float + add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) } + when :date, :date_past + case operator_for(field) + when "=", ">=", "<=", "><" + add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) } + when ">t-", "t+", " 'activerecord.errors.messages') + errors.add(:base, m) + end + + def editable_by?(user) + return false unless user + # Admin can edit them all and regular users can edit their private queries + return true if user.admin? || (is_private? && self.user_id == user.id) + # Members can not edit public queries that are for all project (only admin is allowed to) + is_public? && !@is_for_all && user.allowed_to?(:manage_public_queries, project) + end + + def trackers + @trackers ||= project.nil? ? Tracker.sorted.all : project.rolled_up_trackers + end + + # Returns a hash of localized labels for all filter operators + def self.operators_labels + operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h} + end + + # Returns a representation of the available filters for JSON serialization + def available_filters_as_json + json = {} + available_filters.each do |field, options| + json[field] = options.slice(:type, :name, :values).stringify_keys + end + json + end + + def all_projects + @all_projects ||= Project.visible.all + end + + def all_projects_values + return @all_projects_values if @all_projects_values + + values = [] + Project.project_tree(all_projects) do |p, level| + prefix = (level > 0 ? ('--' * level + ' ') : '') + values << ["#{prefix}#{p.name}", p.id.to_s] + end + @all_projects_values = values + end + + # Adds available filters + def initialize_available_filters + # implemented by sub-classes + end + protected :initialize_available_filters + + # Adds an available filter + def add_available_filter(field, options) + @available_filters ||= ActiveSupport::OrderedHash.new + @available_filters[field] = options + @available_filters + end + + # Removes an available filter + def delete_available_filter(field) + if @available_filters + @available_filters.delete(field) + end + end + + # Return a hash of available filters + def available_filters + unless @available_filters + initialize_available_filters + @available_filters.each do |field, options| + options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, '')) + end + end + @available_filters + end + + def add_filter(field, operator, values=nil) + # values must be an array + return unless values.nil? || values.is_a?(Array) + # check if field is defined as an available filter + if available_filters.has_key? field + filter_options = available_filters[field] + filters[field] = {:operator => operator, :values => (values || [''])} + end + end + + def add_short_filter(field, expression) + return unless expression && available_filters.has_key?(field) + field_type = available_filters[field][:type] + operators_by_filter_type[field_type].sort.reverse.detect do |operator| + next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/ + values = $1 + add_filter field, operator, values.present? ? values.split('|') : [''] + end || add_filter(field, '=', expression.split('|')) + end + + # Add multiple filters using +add_filter+ + def add_filters(fields, operators, values) + if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash)) + fields.each do |field| + add_filter(field, operators[field], values && values[field]) + end + end + end + + def has_filter?(field) + filters and filters[field] + end + + def type_for(field) + available_filters[field][:type] if available_filters.has_key?(field) + end + + def operator_for(field) + has_filter?(field) ? filters[field][:operator] : nil + end + + def values_for(field) + has_filter?(field) ? filters[field][:values] : nil + end + + def value_for(field, index=0) + (values_for(field) || [])[index] + end + + def label_for(field) + label = available_filters[field][:name] if available_filters.has_key?(field) + label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field) + end + + def self.add_available_column(column) + self.available_columns << (column) if column.is_a?(QueryColumn) + end + + # Returns an array of columns that can be used to group the results + def groupable_columns + available_columns.select {|c| c.groupable} + end + + # Returns a Hash of columns and the key for sorting + def sortable_columns + available_columns.inject({}) {|h, column| + h[column.name.to_s] = column.sortable + h + } + end + + def columns + # preserve the column_names order + cols = (has_default_columns? ? default_columns_names : column_names).collect do |name| + available_columns.find { |col| col.name == name } + end.compact + available_columns.select(&:frozen?) | cols + end + + def inline_columns + columns.select(&:inline?) + end + + def block_columns + columns.reject(&:inline?) + end + + def available_inline_columns + available_columns.select(&:inline?) + end + + def available_block_columns + available_columns.reject(&:inline?) + end + + def default_columns_names + [] + end + + def column_names=(names) + if names + names = names.select {|n| n.is_a?(Symbol) || !n.blank? } + names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } + # Set column_names to nil if default columns + if names == default_columns_names + names = nil + end + end + write_attribute(:column_names, names) + end + + def has_column?(column) + column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column) + end + + def has_custom_field_column? + columns.any? {|column| column.is_a? QueryCustomFieldColumn} + end + + def has_default_columns? + column_names.nil? || column_names.empty? + end + + def sort_criteria=(arg) + c = [] + if arg.is_a?(Hash) + arg = arg.keys.sort.collect {|k| arg[k]} + end + c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']} + write_attribute(:sort_criteria, c) + end + + def sort_criteria + read_attribute(:sort_criteria) || [] + end + + def sort_criteria_key(arg) + sort_criteria && sort_criteria[arg] && sort_criteria[arg].first + end + + def sort_criteria_order(arg) + sort_criteria && sort_criteria[arg] && sort_criteria[arg].last + end + + def sort_criteria_order_for(key) + sort_criteria.detect {|k, order| key.to_s == k}.try(:last) + end + + # Returns the SQL sort order that should be prepended for grouping + def group_by_sort_order + if grouped? && (column = group_by_column) + order = sort_criteria_order_for(column.name) || column.default_order + column.sortable.is_a?(Array) ? + column.sortable.collect {|s| "#{s} #{order}"}.join(',') : + "#{column.sortable} #{order}" + end + end + + # Returns true if the query is a grouped query + def grouped? + !group_by_column.nil? + end + + def group_by_column + groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by} + end + + def group_by_statement + group_by_column.try(:groupable) + end + + def project_statement + project_clauses = [] + if project && !project.descendants.active.empty? + ids = [project.id] + if has_filter?("subproject_id") + case operator_for("subproject_id") + when '=' + # include the selected subprojects + ids += values_for("subproject_id").each(&:to_i) + when '!*' + # main project only + else + # all subprojects + ids += project.descendants.collect(&:id) + end + elsif Setting.display_subprojects_issues? + ids += project.descendants.collect(&:id) + end + project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',') + elsif project + project_clauses << "#{Project.table_name}.id = %d" % project.id + end + project_clauses.any? ? project_clauses.join(' AND ') : nil + end + + def statement + # filters clauses + filters_clauses = [] + filters.each_key do |field| + next if field == "subproject_id" + v = values_for(field).clone + next unless v and !v.empty? + operator = operator_for(field) + + # "me" value subsitution + if %w(assigned_to_id author_id user_id watcher_id).include?(field) + if v.delete("me") + if User.current.logged? + v.push(User.current.id.to_s) + v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id' + else + v.push("0") + end + end + end + + if field == 'project_id' + if v.delete('mine') + v += User.current.memberships.map(&:project_id).map(&:to_s) + end + end + + if field =~ /cf_(\d+)$/ + # custom field + filters_clauses << sql_for_custom_field(field, operator, v, $1) + elsif respond_to?("sql_for_#{field}_field") + # specific statement + filters_clauses << send("sql_for_#{field}_field", field, operator, v) + else + # regular field + filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')' + end + end if filters and valid? + + if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn) + # Excludes results for which the grouped custom field is not visible + filters_clauses << c.custom_field.visibility_by_project_condition + end + + filters_clauses << project_statement + filters_clauses.reject!(&:blank?) + + filters_clauses.any? ? filters_clauses.join(' AND ') : nil + end + + private + + def sql_for_custom_field(field, operator, value, custom_field_id) + db_table = CustomValue.table_name + db_field = 'value' + filter = @available_filters[field] + return nil unless filter + if filter[:format] == 'user' + if value.delete('me') + value.push User.current.id.to_s + end + end + not_in = nil + if operator == '!' + # Makes ! operator work for custom fields with multiple values + operator = '=' + not_in = 'NOT' + end + customized_key = "id" + customized_class = queried_class + if field =~ /^(.+)\.cf_/ + assoc = $1 + customized_key = "#{assoc}_id" + customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil + raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class + end + where = sql_for_field(field, operator, value, db_table, db_field, true) + if operator =~ /[<>]/ + where = "(#{where}) AND #{db_table}.#{db_field} <> ''" + end + "#{queried_table_name}.#{customized_key} #{not_in} IN (" + + "SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name}" + + " LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id}" + + " WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))" + end + + # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ + def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) + sql = '' + case operator + when "=" + if value.any? + case type_for(field) + when :date, :date_past + sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil)) + when :integer + if is_custom_filter + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) = #{value.first.to_i})" + else + sql = "#{db_table}.#{db_field} = #{value.first.to_i}" + end + when :float + if is_custom_filter + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})" + else + sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}" + end + else + sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" + end + else + # IN an empty set + sql = "1=0" + end + when "!" + if value.any? + sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" + else + # NOT IN an empty set + sql = "1=1" + end + when "!*" + sql = "#{db_table}.#{db_field} IS NULL" + sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter + when "*" + sql = "#{db_table}.#{db_field} IS NOT NULL" + sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter + when ">=" + if [:date, :date_past].include?(type_for(field)) + sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil) + else + if is_custom_filter + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})" + else + sql = "#{db_table}.#{db_field} >= #{value.first.to_f}" + end + end + when "<=" + if [:date, :date_past].include?(type_for(field)) + sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil)) + else + if is_custom_filter + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})" + else + sql = "#{db_table}.#{db_field} <= #{value.first.to_f}" + end + end + when "><" + if [:date, :date_past].include?(type_for(field)) + sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil)) + else + if is_custom_filter + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})" + else + sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}" + end + end + when "o" + sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id" + when "c" + sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" + when ">t-" + # >= today - n days + sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil) + when "t+" + # >= today + n days + sql = relative_date_clause(db_table, db_field, value.first.to_i, nil) + when "= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) + sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6) + when "lw" + # = last week + first_day_of_week = l(:general_first_day_of_week).to_i + day_of_week = Date.today.cwday + days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) + sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1) + when "l2w" + # = last 2 weeks + first_day_of_week = l(:general_first_day_of_week).to_i + day_of_week = Date.today.cwday + days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) + sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1) + when "m" + # = this month + date = Date.today + sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month) + when "lm" + # = last month + date = Date.today.prev_month + sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month) + when "y" + # = this year + date = Date.today + sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year) + when "~" + sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" + when "!~" + sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" + else + raise "Unknown query operator #{operator}" + end + + return sql + end + + # Adds a filter for the given custom field + def add_custom_field_filter(field, assoc=nil) + case field.field_format + when "text" + options = { :type => :text } + when "list" + options = { :type => :list_optional, :values => field.possible_values } + when "date" + options = { :type => :date } + when "bool" + options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] } + when "int" + options = { :type => :integer } + when "float" + options = { :type => :float } + when "user", "version" + return unless project + values = field.possible_values_options(project) + if User.current.logged? && field.field_format == 'user' + values.unshift ["<< #{l(:label_me)} >>", "me"] + end + options = { :type => :list_optional, :values => values } + else + options = { :type => :string } + end + filter_id = "cf_#{field.id}" + filter_name = field.name + if assoc.present? + filter_id = "#{assoc}.#{filter_id}" + filter_name = l("label_attribute_of_#{assoc}", :name => filter_name) + end + add_available_filter filter_id, options.merge({ + :name => filter_name, + :format => field.field_format, + :field => field + }) + end + + # Adds filters for the given custom fields scope + def add_custom_fields_filters(scope, assoc=nil) + scope.visible.where(:is_filter => true).sorted.each do |field| + add_custom_field_filter(field, assoc) + end + end + + # Adds filters for the given associations custom fields + def add_associations_custom_fields_filters(*associations) + fields_by_class = CustomField.visible.where(:is_filter => true).group_by(&:class) + associations.each do |assoc| + association_klass = queried_class.reflect_on_association(assoc).klass + fields_by_class.each do |field_class, fields| + if field_class.customized_class <= association_klass + fields.sort.each do |field| + add_custom_field_filter(field, assoc) + end + end + end + end + end + + # Returns a SQL clause for a date or datetime field. + def date_clause(table, field, from, to) + s = [] + if from + from_yesterday = from - 1 + from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day) + if self.class.default_timezone == :utc + from_yesterday_time = from_yesterday_time.utc + end + s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)]) + end + if to + to_time = Time.local(to.year, to.month, to.day) + if self.class.default_timezone == :utc + to_time = to_time.utc + end + s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)]) + end + s.join(' AND ') + end + + # Returns a SQL clause for a date or datetime field using relative dates. + def relative_date_clause(table, field, days_from, days_to) + date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil)) + end + + # Additional joins required for the given sort options + def joins_for_order_statement(order_options) + joins = [] + + if order_options + if order_options.include?('authors') + joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id" + end + order_options.scan(/cf_\d+/).uniq.each do |name| + column = available_columns.detect {|c| c.name.to_s == name} + join = column && column.custom_field.join_for_order_statement + if join + joins << join + end + end + end + + joins.any? ? joins.join(' ') : nil + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bd/bd28ebed5ff75e171d4be4be4a90cc87c79d9d52.svn-base --- a/.svn/pristine/bd/bd28ebed5ff75e171d4be4be4a90cc87c79d9d52.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -<%= form_tag({}) do -%> -<%= hidden_field_tag 'back_url', url_for(params), :id => nil %> -
    - - - - - <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %> - <% query.inline_columns.each do |column| %> - <%= column_header(column) %> - <% end %> - - - <% previous_group = false %> - - <% issue_list(issues) do |issue, level| -%> - <% if @query.grouped? && (group = @query.group_by_column.value(issue)) != previous_group %> - <% reset_cycle %> - - - - <% previous_group = group %> - <% end %> - "> - - - <%= raw query.inline_columns.map {|column| ""}.join %> - - <% @query.block_columns.each do |column| - if (text = column_content(column, issue)) && text.present? -%> - - - - <% end -%> - <% end -%> - <% end -%> - -
    - <%= link_to image_tag('toggle_check.png'), {}, - :onclick => 'toggleIssuesSelection(this); return false;', - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> -
    -   - <%= group.blank? ? l(:label_none) : column_content(@query.group_by_column, issue) %> <%= @issue_count_by_group[group] %> - <%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", - "toggleAllRowGroups(this)", :class => 'toggle-all') %> -
    <%= check_box_tag("ids[]", issue.id, false, :id => nil) %><%= link_to issue.id, issue_path(issue) %>#{column_content(column, issue)}
    <%= text %>
    -
    -<% end -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bd/bdc3036c84c5c2b90538770a9b1b1626e59b6d16.svn-base --- a/.svn/pristine/bd/bdc3036c84c5c2b90538770a9b1b1626e59b6d16.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class DocumentObserver < ActiveRecord::Observer - def after_create(document) - Mailer.document_added(document).deliver if Setting.notified_events.include?('document_added') - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bd/bde53e0adc79ce39deba6abf485a848292107754.svn-base --- a/.svn/pristine/bd/bde53e0adc79ce39deba6abf485a848292107754.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %> - -<% details_to_strings(@journal.details, true).each do |string| -%> -<%= string %> -<% end -%> - -<% if @journal.notes? -%> -<%= @journal.notes %> - -<% end -%> ----------------------------------------- -<%= render :partial => 'issue', :formats => [:text], :locals => { :issue => @issue, :issue_url => @issue_url } %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/be/be06c1fdad33ad10b8d30c53349e2482e780c5fc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/be/be06c1fdad33ad10b8d30c53349e2482e780c5fc.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,159 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'redmine/scm/adapters/mercurial_adapter' + +class Repository::Mercurial < Repository + # sort changesets by revision number + has_many :changesets, + :order => "#{Changeset.table_name}.id DESC", + :foreign_key => 'repository_id' + + attr_protected :root_url + validates_presence_of :url + + # number of changesets to fetch at once + FETCH_AT_ONCE = 100 + + def self.human_attribute_name(attribute_key_name, *args) + attr_name = attribute_key_name.to_s + if attr_name == "url" + attr_name = "path_to_repository" + end + super(attr_name, *args) + end + + def self.scm_adapter_class + Redmine::Scm::Adapters::MercurialAdapter + end + + def self.scm_name + 'Mercurial' + end + + def supports_directory_revisions? + true + end + + def supports_revision_graph? + true + end + + def repo_log_encoding + 'UTF-8' + end + + # Returns the readable identifier for the given mercurial changeset + def self.format_changeset_identifier(changeset) + "#{changeset.revision}:#{changeset.scmid}" + end + + # Returns the identifier for the given Mercurial changeset + def self.changeset_identifier(changeset) + changeset.scmid + end + + def diff_format_revisions(cs, cs_to, sep=':') + super(cs, cs_to, ' ') + end + + # Finds and returns a revision with a number or the beginning of a hash + def find_changeset_by_name(name) + return nil if name.blank? + s = name.to_s + if /[^\d]/ =~ s or s.size > 8 + cs = changesets.where(:scmid => s).first + else + cs = changesets.where(:revision => s).first + end + return cs if cs + changesets.where('scmid LIKE ?', "#{s}%").first + end + + # Returns the latest changesets for +path+; sorted by revision number + # + # Because :order => 'id DESC' is defined at 'has_many', + # there is no need to set 'order'. + # But, MySQL test fails. + # Sqlite3 and PostgreSQL pass. + # Is this MySQL bug? + def latest_changesets(path, rev, limit=10) + changesets. + includes(:user). + where(latest_changesets_cond(path, rev, limit)). + limit(limit). + order("#{Changeset.table_name}.id DESC"). + all + end + + def latest_changesets_cond(path, rev, limit) + cond, args = [], [] + if scm.branchmap.member? rev + # Mercurial named branch is *stable* in each revision. + # So, named branch can be stored in database. + # Mercurial provides *bookmark* which is equivalent with git branch. + # But, bookmark is not implemented. + cond << "#{Changeset.table_name}.scmid IN (?)" + # Revisions in root directory and sub directory are not equal. + # So, in order to get correct limit, we need to get all revisions. + # But, it is very heavy. + # Mercurial does not treat direcotry. + # So, "hg log DIR" is very heavy. + branch_limit = path.blank? ? limit : ( limit * 5 ) + args << scm.nodes_in_branch(rev, :limit => branch_limit) + elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil + cond << "#{Changeset.table_name}.id <= ?" + args << last.id + end + unless path.blank? + cond << "EXISTS (SELECT * FROM #{Change.table_name} + WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id + AND (#{Change.table_name}.path = ? + OR #{Change.table_name}.path LIKE ? ESCAPE ?))" + args << path.with_leading_slash + args << "#{path.with_leading_slash.gsub(%r{[%_\\]}) { |s| "\\#{s}" }}/%" << '\\' + end + [cond.join(' AND '), *args] unless cond.empty? + end + private :latest_changesets_cond + + def fetch_changesets + return if scm.info.nil? + scm_rev = scm.info.lastrev.revision.to_i + db_rev = latest_changeset ? latest_changeset.revision.to_i : -1 + return unless db_rev < scm_rev # already up-to-date + + logger.debug "Fetching changesets for repository #{url}" if logger + (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i| + scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re| + transaction do + parents = (re.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact + cs = Changeset.create(:repository => self, + :revision => re.revision, + :scmid => re.scmid, + :committer => re.author, + :committed_on => re.time, + :comments => re.message, + :parents => parents) + unless cs.new_record? + re.paths.each { |e| cs.create_change(e) } + end + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/be/be1a47ce0b0f5def64a1b6af9eae01ab183cefda.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/be/be1a47ce0b0f5def64a1b6af9eae01ab183cefda.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,2 @@ +<% selector = ".#{watcher_css(watched)}" %> +$("<%= selector %>").each(function(){$(this).replaceWith("<%= escape_javascript watcher_link(watched, user) %>")}); diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/be/be4b32c2aa3919bf1459ac5ceda1f30275086d1d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/be/be4b32c2aa3919bf1459ac5ceda1f30275086d1d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,172 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::MembershipsTest < Redmine::ApiTest::Base + fixtures :projects, :users, :roles, :members, :member_roles + + def setup + Setting.rest_api_enabled = '1' + end + + test "GET /projects/:project_id/memberships.xml should return memberships" do + get '/projects/1/memberships.xml', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'memberships', + :attributes => {:type => 'array'}, + :child => { + :tag => 'membership', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'user', + :attributes => {:id => '3', :name => 'Dave Lopper'}, + :sibling => { + :tag => 'roles', + :child => { + :tag => 'role', + :attributes => {:id => '2', :name => 'Developer'} + } + } + } + } + } + end + + test "GET /projects/:project_id/memberships.json should return memberships" do + get '/projects/1/memberships.json', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/json', @response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_equal({ + "memberships" => + [{"id"=>1, + "project" => {"name"=>"eCookbook", "id"=>1}, + "roles" => [{"name"=>"Manager", "id"=>1}], + "user" => {"name"=>"John Smith", "id"=>2}}, + {"id"=>2, + "project" => {"name"=>"eCookbook", "id"=>1}, + "roles" => [{"name"=>"Developer", "id"=>2}], + "user" => {"name"=>"Dave Lopper", "id"=>3}}], + "limit" => 25, + "total_count" => 2, + "offset" => 0}, + json) + end + + test "POST /projects/:project_id/memberships.xml should create the membership" do + assert_difference 'Member.count' do + post '/projects/1/memberships.xml', {:membership => {:user_id => 7, :role_ids => [2,3]}}, credentials('jsmith') + + assert_response :created + end + end + + test "POST /projects/:project_id/memberships.xml with invalid parameters should return errors" do + assert_no_difference 'Member.count' do + post '/projects/1/memberships.xml', {:membership => {:role_ids => [2,3]}}, credentials('jsmith') + + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => "Principal can't be blank"} + end + end + + test "GET /memberships/:id.xml should return the membership" do + get '/memberships/2.xml', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'membership', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'user', + :attributes => {:id => '3', :name => 'Dave Lopper'}, + :sibling => { + :tag => 'roles', + :child => { + :tag => 'role', + :attributes => {:id => '2', :name => 'Developer'} + } + } + } + } + end + + test "GET /memberships/:id.json should return the membership" do + get '/memberships/2.json', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/json', @response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_equal( + {"membership" => { + "id" => 2, + "project" => {"name"=>"eCookbook", "id"=>1}, + "roles" => [{"name"=>"Developer", "id"=>2}], + "user" => {"name"=>"Dave Lopper", "id"=>3}} + }, + json) + end + + test "PUT /memberships/:id.xml should update the membership" do + assert_not_equal [1,2], Member.find(2).role_ids.sort + assert_no_difference 'Member.count' do + put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [1,2]}}, credentials('jsmith') + + assert_response :ok + assert_equal '', @response.body + end + member = Member.find(2) + assert_equal [1,2], member.role_ids.sort + end + + test "PUT /memberships/:id.xml with invalid parameters should return errors" do + put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [99]}}, credentials('jsmith') + + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => /member_roles is invalid/} + end + + test "DELETE /memberships/:id.xml should destroy the membership" do + assert_difference 'Member.count', -1 do + delete '/memberships/2.xml', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', @response.body + end + assert_nil Member.find_by_id(2) + end + + test "DELETE /memberships/:id.xml should respond with 422 on failure" do + assert_no_difference 'Member.count' do + # A membership with an inherited role can't be deleted + Member.find(2).member_roles.first.update_attribute :inherited_from, 99 + delete '/memberships/2.xml', {}, credentials('jsmith') + + assert_response :unprocessable_entity + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/be/be5eea4d65ddd58278f59654aa0a6ad304fac616.svn-base --- a/.svn/pristine/be/be5eea4d65ddd58278f59654aa0a6ad304fac616.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -api.array :issues, api_meta(:total_count => @issue_count, :offset => @offset, :limit => @limit) do - @issues.each do |issue| - api.issue do - api.id issue.id - api.project(:id => issue.project_id, :name => issue.project.name) unless issue.project.nil? - api.tracker(:id => issue.tracker_id, :name => issue.tracker.name) unless issue.tracker.nil? - api.status(:id => issue.status_id, :name => issue.status.name) unless issue.status.nil? - api.priority(:id => issue.priority_id, :name => issue.priority.name) unless issue.priority.nil? - api.author(:id => issue.author_id, :name => issue.author.name) unless issue.author.nil? - api.assigned_to(:id => issue.assigned_to_id, :name => issue.assigned_to.name) unless issue.assigned_to.nil? - api.category(:id => issue.category_id, :name => issue.category.name) unless issue.category.nil? - api.fixed_version(:id => issue.fixed_version_id, :name => issue.fixed_version.name) unless issue.fixed_version.nil? - api.parent(:id => issue.parent_id) unless issue.parent.nil? - - api.subject issue.subject - api.description issue.description - api.start_date issue.start_date - api.due_date issue.due_date - api.done_ratio issue.done_ratio - api.estimated_hours issue.estimated_hours - - render_api_custom_values issue.custom_field_values, api - - api.created_on issue.created_on - api.updated_on issue.updated_on - - api.array :relations do - issue.relations.each do |relation| - api.relation(:id => relation.id, :issue_id => relation.issue_from_id, :issue_to_id => relation.issue_to_id, :relation_type => relation.relation_type, :delay => relation.delay) - end - end if include_in_api_response?('relations') - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/be/be8e6e4ab944ad41015bb9c64ecc1842cecaa059.svn-base --- a/.svn/pristine/be/be8e6e4ab944ad41015bb9c64ecc1842cecaa059.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class WorkflowsController < ApplicationController - layout 'admin' - - before_filter :require_admin, :find_roles, :find_trackers - - def index - @workflow_counts = WorkflowTransition.count_by_tracker_and_role - end - - def edit - @role = Role.find_by_id(params[:role_id]) if params[:role_id] - @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] - - if request.post? - WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) - (params[:issue_status] || []).each { |status_id, transitions| - transitions.each { |new_status_id, options| - author = options.is_a?(Array) && options.include?('author') && !options.include?('always') - assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') - WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee) - } - } - if @role.save - redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only] - return - end - end - - @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) - if @tracker && @used_statuses_only && @tracker.issue_statuses.any? - @statuses = @tracker.issue_statuses - end - @statuses ||= IssueStatus.sorted.all - - if @tracker && @role && @statuses.any? - workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all - @workflows = {} - @workflows['always'] = workflows.select {|w| !w.author && !w.assignee} - @workflows['author'] = workflows.select {|w| w.author} - @workflows['assignee'] = workflows.select {|w| w.assignee} - end - end - - def permissions - @role = Role.find_by_id(params[:role_id]) if params[:role_id] - @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] - - if request.post? && @role && @tracker - WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {}) - redirect_to :action => 'permissions', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only] - return - end - - @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) - if @tracker && @used_statuses_only && @tracker.issue_statuses.any? - @statuses = @tracker.issue_statuses - end - @statuses ||= IssueStatus.sorted.all - - if @role && @tracker - @fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]} - @custom_fields = @tracker.custom_fields - - @permissions = WorkflowPermission.where(:tracker_id => @tracker.id, :role_id => @role.id).all.inject({}) do |h, w| - h[w.old_status_id] ||= {} - h[w.old_status_id][w.field_name] = w.rule - h - end - @statuses.each {|status| @permissions[status.id] ||= {}} - end - end - - def copy - - if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' - @source_tracker = nil - else - @source_tracker = Tracker.find_by_id(params[:source_tracker_id].to_i) - end - if params[:source_role_id].blank? || params[:source_role_id] == 'any' - @source_role = nil - else - @source_role = Role.find_by_id(params[:source_role_id].to_i) - end - - @target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids]) - @target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids]) - - if request.post? - if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) - flash.now[:error] = l(:error_workflow_copy_source) - elsif @target_trackers.nil? || @target_roles.nil? - flash.now[:error] = l(:error_workflow_copy_target) - else - WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role - end - end - end - - private - - def find_roles - @roles = Role.sorted.all - end - - def find_trackers - @trackers = Tracker.sorted.all - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/be/beabb054b1ce2bec73f7e1f3d3b45e0d6c9f4b45.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/be/beabb054b1ce2bec73f7e1f3d3b45e0d6c9f4b45.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,12 @@ +class IssueAddNote < ActiveRecord::Migration + # model removed + class Permission < ActiveRecord::Base; end + + def self.up + Permission.create :controller => "issues", :action => "add_note", :description => "label_add_note", :sort => 1057, :mail_option => 1, :mail_enabled => 0 + end + + def self.down + Permission.where("controller=? and action=?", 'issues', 'add_note').first.destroy + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/be/bedd1a4658313e01edaa3354b41ccf9ebbf924c3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/be/bedd1a4658313e01edaa3354b41ccf9ebbf924c3.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Acts + module Attachable + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def acts_as_attachable(options = {}) + cattr_accessor :attachable_options + self.attachable_options = {} + attachable_options[:view_permission] = options.delete(:view_permission) || "view_#{self.name.pluralize.underscore}".to_sym + attachable_options[:delete_permission] = options.delete(:delete_permission) || "edit_#{self.name.pluralize.underscore}".to_sym + + has_many :attachments, options.merge(:as => :container, + :order => "#{Attachment.table_name}.created_on ASC, #{Attachment.table_name}.id ASC", + :dependent => :destroy) + send :include, Redmine::Acts::Attachable::InstanceMethods + before_save :attach_saved_attachments + end + end + + module InstanceMethods + def self.included(base) + base.extend ClassMethods + end + + def attachments_visible?(user=User.current) + (respond_to?(:visible?) ? visible?(user) : true) && + user.allowed_to?(self.class.attachable_options[:view_permission], self.project) + end + + def attachments_deletable?(user=User.current) + (respond_to?(:visible?) ? visible?(user) : true) && + user.allowed_to?(self.class.attachable_options[:delete_permission], self.project) + end + + def saved_attachments + @saved_attachments ||= [] + end + + def unsaved_attachments + @unsaved_attachments ||= [] + end + + def save_attachments(attachments, author=User.current) + if attachments.is_a?(Hash) + attachments = attachments.stringify_keys + attachments = attachments.to_a.sort {|a, b| + if a.first.to_i > 0 && b.first.to_i > 0 + a.first.to_i <=> b.first.to_i + elsif a.first.to_i > 0 + 1 + elsif b.first.to_i > 0 + -1 + else + a.first <=> b.first + end + } + attachments = attachments.map(&:last) + end + if attachments.is_a?(Array) + attachments.each do |attachment| + next unless attachment.is_a?(Hash) + a = nil + if file = attachment['file'] + next unless file.size > 0 + a = Attachment.create(:file => file, :author => author) + elsif token = attachment['token'] + a = Attachment.find_by_token(token) + next unless a + a.filename = attachment['filename'] unless attachment['filename'].blank? + a.content_type = attachment['content_type'] + end + next unless a + a.description = attachment['description'].to_s.strip + if a.new_record? + unsaved_attachments << a + else + saved_attachments << a + end + end + end + {:files => saved_attachments, :unsaved => unsaved_attachments} + end + + def attach_saved_attachments + saved_attachments.each do |attachment| + self.attachments << attachment + end + end + + module ClassMethods + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bf/bf2a0b085dae832659996e0ba149d09caa255a18.svn-base Binary file .svn/pristine/bf/bf2a0b085dae832659996e0ba149d09caa255a18.svn-base has changed diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bf/bf4f93978948907accea2adbd188dc8d3101593b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bf/bf4f93978948907accea2adbd188dc8d3101593b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,142 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::TimeEntriesTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :time_entries + + def setup + Setting.rest_api_enabled = '1' + end + + test "GET /time_entries.xml should return time entries" do + get '/time_entries.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'time_entries', + :child => {:tag => 'time_entry', :child => {:tag => 'id', :content => '2'}} + end + + test "GET /time_entries.xml with limit should return limited results" do + get '/time_entries.xml?limit=2', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'time_entries', + :children => {:count => 2} + end + + test "GET /time_entries/:id.xml should return the time entry" do + get '/time_entries/2.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'time_entry', + :child => {:tag => 'id', :content => '2'} + end + + test "POST /time_entries.xml with issue_id should create time entry" do + assert_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => {:issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type + + entry = TimeEntry.first(:order => 'id DESC') + assert_equal 'jsmith', entry.user.login + assert_equal Issue.find(1), entry.issue + assert_equal Project.find(1), entry.project + assert_equal Date.parse('2010-12-02'), entry.spent_on + assert_equal 3.5, entry.hours + assert_equal TimeEntryActivity.find(11), entry.activity + end + + test "POST /time_entries.xml with issue_id should accept custom fields" do + field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'string') + + assert_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => { + :issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11', :custom_fields => [{:id => field.id.to_s, :value => 'accepted'}] + }}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type + + entry = TimeEntry.first(:order => 'id DESC') + assert_equal 'accepted', entry.custom_field_value(field) + end + + test "POST /time_entries.xml with project_id should create time entry" do + assert_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type + + entry = TimeEntry.first(:order => 'id DESC') + assert_equal 'jsmith', entry.user.login + assert_nil entry.issue + assert_equal Project.find(1), entry.project + assert_equal Date.parse('2010-12-02'), entry.spent_on + assert_equal 3.5, entry.hours + assert_equal TimeEntryActivity.find(11), entry.activity + end + + test "POST /time_entries.xml with invalid parameters should return errors" do + assert_no_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :activity_id => '11'}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} + end + + test "PUT /time_entries/:id.xml with valid parameters should update time entry" do + assert_no_difference 'TimeEntry.count' do + put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_equal 'API Update', TimeEntry.find(2).comments + end + + test "PUT /time_entries/:id.xml with invalid parameters should return errors" do + assert_no_difference 'TimeEntry.count' do + put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} + end + + test "DELETE /time_entries/:id.xml should destroy time entry" do + assert_difference 'TimeEntry.count', -1 do + delete '/time_entries/2.xml', {}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_nil TimeEntry.find_by_id(2) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bf/bf5525ed584bcef3f8f764b00823c6be53e94184.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bf/bf5525ed584bcef3f8f764b00823c6be53e94184.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,25 @@ +class PopulateIssuesClosedOn < ActiveRecord::Migration + def up + closed_status_ids = IssueStatus.where(:is_closed => true).pluck(:id) + if closed_status_ids.any? + # First set closed_on for issues that have been closed once + closed_status_values = closed_status_ids.map {|status_id| "'#{status_id}'"}.join(',') + subselect = "SELECT MAX(#{Journal.table_name}.created_on)" + + " FROM #{Journal.table_name}, #{JournalDetail.table_name}" + + " WHERE #{Journal.table_name}.id = #{JournalDetail.table_name}.journal_id" + + " AND #{Journal.table_name}.journalized_type = 'Issue' AND #{Journal.table_name}.journalized_id = #{Issue.table_name}.id" + + " AND #{JournalDetail.table_name}.property = 'attr' AND #{JournalDetail.table_name}.prop_key = 'status_id'" + + " AND #{JournalDetail.table_name}.old_value NOT IN (#{closed_status_values})" + + " AND #{JournalDetail.table_name}.value IN (#{closed_status_values})" + Issue.update_all "closed_on = (#{subselect})" + + # Then set closed_on for closed issues that weren't up updated by the above UPDATE + # No journal was found so we assume that they were closed on creation + Issue.update_all "closed_on = created_on", {:status_id => closed_status_ids, :closed_on => nil} + end + end + + def down + Issue.update_all :closed_on => nil + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bf/bf704ffc85732314f4254ede294fe941c370ac2e.svn-base --- a/.svn/pristine/bf/bf704ffc85732314f4254ede294fe941c370ac2e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class Redmine::ThemesTest < ActiveSupport::TestCase - - def test_themes - themes = Redmine::Themes.themes - assert_kind_of Array, themes - assert_kind_of Redmine::Themes::Theme, themes.first - end - - def test_rescan - Redmine::Themes.themes.pop - - assert_difference 'Redmine::Themes.themes.size' do - Redmine::Themes.rescan - end - end - - def test_theme_loaded - theme = Redmine::Themes.themes.last - - assert_equal theme, Redmine::Themes.theme(theme.id) - end - - def test_theme_loaded_without_rescan - theme = Redmine::Themes.themes.last - - assert_equal theme, Redmine::Themes.theme(theme.id, :rescan => false) - end - - def test_theme_not_loaded - theme = Redmine::Themes.themes.pop - - assert_equal theme, Redmine::Themes.theme(theme.id) - end - - def test_theme_not_loaded_without_rescan - theme = Redmine::Themes.themes.pop - - assert_nil Redmine::Themes.theme(theme.id, :rescan => false) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bf/bf94cf765b345931f682e346df326eaac538f854.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bf/bf94cf765b345931f682e346df326eaac538f854.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1103 @@ +sl: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d.%m.%Y" + short: "%d. %b" + long: "%d. %B, %Y" + + day_names: [Nedelja, Ponedeljek, Torek, Sreda, ÄŒetrtek, Petek, Sobota] + abbr_day_names: [Ned, Pon, To, Sr, ÄŒet, Pet, Sob] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Januar, Februar, Marec, April, Maj, Junij, Julij, Avgust, September, Oktober, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, Maj, Jun, Jul, Aug, Sep, Okt, Nov, Dec] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%a, %d. %b, %Y %H:%M:%S %z" + time: "%H:%M" + short: "%d. %b %H:%M" + long: "%d. %B, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "pol minute" + less_than_x_seconds: + one: "manj kot 1. sekundo" + other: "manj kot %{count} sekund" + x_seconds: + one: "1. sekunda" + other: "%{count} sekund" + less_than_x_minutes: + one: "manj kot minuto" + other: "manj kot %{count} minut" + x_minutes: + one: "1 minuta" + other: "%{count} minut" + about_x_hours: + one: "okrog 1. ure" + other: "okrog %{count} ur" + x_hours: + one: "1 ura" + other: "%{count} ur" + x_days: + one: "1 dan" + other: "%{count} dni" + about_x_months: + one: "okrog 1. mesec" + other: "okrog %{count} mesecev" + x_months: + one: "1 mesec" + other: "%{count} mesecev" + about_x_years: + one: "okrog 1. leto" + other: "okrog %{count} let" + over_x_years: + one: "veÄ kot 1. leto" + other: "veÄ kot %{count} let" + almost_x_years: + one: "skoraj 1. leto" + other: "skoraj %{count} let" + + number: + format: + separator: "," + delimiter: "." + precision: 3 + human: + format: + precision: 3 + delimiter: "" + storage_units: + format: "%n %u" + units: + kb: KB + tb: TB + gb: GB + byte: + one: Byte + other: Bytes + mb: MB + +# Used in array.to_sentence. + support: + array: + sentence_connector: "in" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1. napaka je prepreÄila temu %{model} da bi se shranil" + other: "%{count} napak je prepreÄilo temu %{model} da bi se shranil" + messages: + inclusion: "ni vkljuÄen na seznamu" + exclusion: "je rezerviran" + invalid: "je napaÄen" + confirmation: "ne ustreza potrdilu" + accepted: "mora biti sprejet" + empty: "ne sme biti prazen" + blank: "ne sme biti neizpolnjen" + too_long: "je predolg" + too_short: "je prekratek" + wrong_length: "je napaÄne dolžine" + taken: "je že zaseden" + not_a_number: "ni Å¡tevilo" + not_a_date: "ni veljaven datum" + greater_than: "mora biti veÄji kot %{count}" + greater_than_or_equal_to: "mora biti veÄji ali enak kot %{count}" + equal_to: "mora biti enak kot %{count}" + less_than: "mora biti manjÅ¡i kot %{count}" + less_than_or_equal_to: "mora biti manjÅ¡i ali enak kot %{count}" + odd: "mora biti sodo" + even: "mora biti liho" + greater_than_start_date: "mora biti kasnejÅ¡i kot zaÄetni datum" + not_same_project: "ne pripada istemu projektu" + circular_dependency: "Ta odnos bi povzroÄil krožno odvisnost" + cant_link_an_issue_with_a_descendant: "Zahtevek ne more biti povezan s svojo podnalogo" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Prosimo izberite + + general_text_No: 'Ne' + general_text_Yes: 'Da' + general_text_no: 'ne' + general_text_yes: 'da' + general_lang_name: 'SlovenÅ¡Äina' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: RaÄun je bil uspeÅ¡no posodobljen. + notice_account_invalid_creditentials: NapaÄno uporabniÅ¡ko ime ali geslo + notice_account_password_updated: Geslo je bilo uspeÅ¡no posodobljeno. + notice_account_wrong_password: NapaÄno geslo + notice_account_register_done: RaÄun je bil uspeÅ¡no ustvarjen. Za aktivacijo potrdite povezavo, ki vam je bila poslana v e-nabiralnik. + notice_account_unknown_email: Neznan uporabnik. + notice_can_t_change_password: Ta raÄun za overovljanje uporablja zunanji. Gesla ni mogoÄe spremeniti. + notice_account_lost_email_sent: Poslano vam je bilo e-pismo z navodili za izbiro novega gesla. + notice_account_activated: VaÅ¡ raÄun je bil aktiviran. Sedaj se lahko prijavite. + notice_successful_create: Ustvarjanje uspelo. + notice_successful_update: Posodobitev uspela. + notice_successful_delete: Izbris uspel. + notice_successful_connection: Povezava uspela. + notice_file_not_found: Stran na katero se želite povezati ne obstaja ali pa je bila umaknjena. + notice_locking_conflict: Drug uporabnik je posodobil podatke. + notice_not_authorized: Nimate privilegijev za dostop do te strani. + notice_email_sent: "E-poÅ¡tno sporoÄilo je bilo poslano %{value}" + notice_email_error: "Ob poÅ¡iljanju e-sporoÄila je priÅ¡lo do napake (%{value})" + notice_feeds_access_key_reseted: VaÅ¡ Atom dostopni kljuÄ je bil ponastavljen. + notice_failed_to_save_issues: "Neuspelo shranjevanje %{count} zahtevka na %{total} izbranem: %{ids}." + notice_no_issue_selected: "Izbran ni noben zahtevek! Prosimo preverite zahtevke, ki jih želite urediti." + notice_account_pending: "VaÅ¡ raÄun je bil ustvarjen in Äaka na potrditev s strani administratorja." + notice_default_data_loaded: Privzete nastavitve so bile uspeÅ¡no naložene. + notice_unable_delete_version: Verzije ni bilo mogoÄe izbrisati. + + error_can_t_load_default_data: "Privzetih nastavitev ni bilo mogoÄe naložiti: %{value}" + error_scm_not_found: "Vnos ali revizija v shrambi ni bila najdena ." + error_scm_command_failed: "Med vzpostavljem povezave s shrambo je priÅ¡lo do napake: %{value}" + error_scm_annotate: "Vnos ne obstaja ali pa ga ni mogoÄe komentirati." + error_issue_not_found_in_project: 'Zahtevek ni bil najden ali pa ne pripada temu projektu' + + mail_subject_lost_password: "VaÅ¡e %{value} geslo" + mail_body_lost_password: 'Za spremembo glesla kliknite na naslednjo povezavo:' + mail_subject_register: "Aktivacija %{value} vaÅ¡ega raÄuna" + mail_body_register: 'Za aktivacijo vaÅ¡ega raÄuna kliknite na naslednjo povezavo:' + mail_body_account_information_external: "Za prijavo lahko uporabite vaÅ¡ %{value} raÄun." + mail_body_account_information: "Informacije o vaÅ¡em raÄunu. Za spremembo gesla sledite linku 'Spremeni geslo' na naslovu za prijavo, spodaj." + mail_subject_account_activation_request: "%{value} zahtevek za aktivacijo raÄuna" + mail_body_account_activation_request: "Registriral se je nov uporabnik (%{value}). RaÄun Äaka na vaÅ¡o odobritev:" + mail_subject_reminder: "%{count} zahtevek(zahtevki) zapadejo v naslednjih %{days} dneh" + mail_body_reminder: "%{count} zahtevek(zahtevki), ki so vam dodeljeni bodo zapadli v naslednjih %{days} dneh:" + + + field_name: Ime + field_description: Opis + field_summary: Povzetek + field_is_required: Zahtevano + field_firstname: Ime + field_lastname: Priimek + field_mail: E-naslov + field_filename: Datoteka + field_filesize: Velikost + field_downloads: Prenosi + field_author: Avtor + field_created_on: Ustvarjen + field_updated_on: Posodobljeno + field_field_format: Format + field_is_for_all: Za vse projekte + field_possible_values: Možne vrednosti + field_regexp: Regularni izraz + field_min_length: Minimalna dolžina + field_max_length: Maksimalna dolžina + field_value: Vrednost + field_category: Kategorija + field_title: Naslov + field_project: Projekt + field_issue: Zahtevek + field_status: Status + field_notes: Zabeležka + field_is_closed: Zahtevek zaprt + field_is_default: Privzeta vrednost + field_tracker: Vrsta zahtevka + field_subject: Tema + field_due_date: Do datuma + field_assigned_to: Dodeljen + field_priority: Prioriteta + field_fixed_version: Ciljna verzija + field_user: Uporabnik + field_role: Vloga + field_homepage: DomaÄa stran + field_is_public: Javno + field_parent: Podprojekt projekta + field_is_in_roadmap: Zahtevki prikazani na zemljevidu + field_login: Prijava + field_mail_notification: E-poÅ¡tna oznanila + field_admin: Administrator + field_last_login_on: ZadnjiÄ povezan(a) + field_language: Jezik + field_effective_date: Datum + field_password: Geslo + field_new_password: Novo geslo + field_password_confirmation: Potrditev + field_version: Verzija + field_type: Tip + field_host: Gostitelj + field_port: Vrata + field_account: RaÄun + field_base_dn: Bazni DN + field_attr_login: Oznaka za prijavo + field_attr_firstname: Oznaka za ime + field_attr_lastname: Oznaka za priimek + field_attr_mail: Oznaka za e-naslov + field_onthefly: Sprotna izdelava uporabnikov + field_start_date: ZaÄetek + field_done_ratio: "% Narejeno" + field_auth_source: NaÄin overovljanja + field_hide_mail: Skrij moj e-naslov + field_comments: Komentar + field_url: URL + field_start_page: ZaÄetna stran + field_subproject: Podprojekt + field_hours: Ur + field_activity: Aktivnost + field_spent_on: Datum + field_identifier: Identifikator + field_is_filter: Uporabljen kot filter + field_issue_to: Povezan zahtevek + field_delay: Zamik + field_assignable: Zahtevki so lahko dodeljeni tej vlogi + field_redirect_existing_links: Preusmeri obstojeÄe povezave + field_estimated_hours: Ocenjen Äas + field_column_names: Stolpci + field_time_zone: ÄŒasovni pas + field_searchable: Zmožen iskanja + field_default_value: Privzeta vrednost + field_comments_sorting: Prikaži komentarje + field_parent_title: MatiÄna stran + + setting_app_title: Naslov aplikacije + setting_app_subtitle: Podnaslov aplikacije + setting_welcome_text: Pozdravno besedilo + setting_default_language: Privzeti jezik + setting_login_required: Zahtevano overovljanje + setting_self_registration: Samostojna registracija + setting_attachment_max_size: Maksimalna velikost priponk + setting_issues_export_limit: Skrajna meja za izvoz zahtevkov + setting_mail_from: E-naslov za emisijo + setting_bcc_recipients: Prejemniki slepih kopij (bcc) + setting_plain_text_mail: navadno e-sporoÄilo (ne HTML) + setting_host_name: Ime gostitelja in pot + setting_text_formatting: Oblikovanje besedila + setting_wiki_compression: Stiskanje Wiki zgodovine + setting_feeds_limit: Meja obsega Atom virov + setting_default_projects_public: Novi projekti so privzeto javni + setting_autofetch_changesets: Samodejni izvleÄek zapisa sprememb + setting_sys_api_enabled: OmogoÄi WS za upravljanje shrambe + setting_commit_ref_keywords: Sklicne kljuÄne besede + setting_commit_fix_keywords: Urejanje kljuÄne besede + setting_autologin: Avtomatska prijava + setting_date_format: Oblika datuma + setting_time_format: Oblika Äasa + setting_cross_project_issue_relations: Dovoli povezave zahtevkov med razliÄnimi projekti + setting_issue_list_default_columns: Privzeti stolpci prikazani na seznamu zahtevkov + setting_emails_footer: Noga e-sporoÄil + setting_protocol: Protokol + setting_per_page_options: Å tevilo elementov na stran + setting_user_format: Oblika prikaza uporabnikov + setting_activity_days_default: Prikaz dni na aktivnost projekta + setting_display_subprojects_issues: Privzeti prikaz zahtevkov podprojektov v glavnem projektu + setting_enabled_scm: OmogoÄen SCM + setting_mail_handler_api_enabled: OmogoÄi WS za prihajajoÄo e-poÅ¡to + setting_mail_handler_api_key: API kljuÄ + setting_sequential_project_identifiers: Generiraj projektne identifikatorje sekvenÄno + setting_gravatar_enabled: Uporabljaj Gravatar ikone + setting_diff_max_lines_displayed: Maksimalno Å¡tevilo prikazanih vrstic razliÄnosti + + permission_edit_project: Uredi projekt + permission_select_project_modules: Izberi module projekta + permission_manage_members: Uredi Älane + permission_manage_versions: Uredi verzije + permission_manage_categories: Urejanje kategorij zahtevkov + permission_add_issues: Dodaj zahtevke + permission_edit_issues: Uredi zahtevke + permission_manage_issue_relations: Uredi odnose med zahtevki + permission_add_issue_notes: Dodaj zabeležke + permission_edit_issue_notes: Uredi zabeležke + permission_edit_own_issue_notes: Uredi lastne zabeležke + permission_move_issues: Premakni zahtevke + permission_delete_issues: IzbriÅ¡i zahtevke + permission_manage_public_queries: Uredi javna povpraÅ¡evanja + permission_save_queries: Shrani povpraÅ¡evanje + permission_view_gantt: Poglej gantogram + permission_view_calendar: Poglej koledar + permission_view_issue_watchers: Oglej si listo spremeljevalcev + permission_add_issue_watchers: Dodaj spremljevalce + permission_log_time: Beleži porabljen Äas + permission_view_time_entries: Poglej porabljen Äas + permission_edit_time_entries: Uredi beležko Äasa + permission_edit_own_time_entries: Uredi beležko lastnega Äasa + permission_manage_news: Uredi novice + permission_comment_news: Komentiraj novice + permission_view_documents: Poglej dokumente + permission_manage_files: Uredi datoteke + permission_view_files: Poglej datoteke + permission_manage_wiki: Uredi wiki + permission_rename_wiki_pages: Preimenuj wiki strani + permission_delete_wiki_pages: IzbriÅ¡i wiki strani + permission_view_wiki_pages: Poglej wiki + permission_view_wiki_edits: Poglej wiki zgodovino + permission_edit_wiki_pages: Uredi wiki strani + permission_delete_wiki_pages_attachments: IzbriÅ¡i priponke + permission_protect_wiki_pages: ZaÅ¡Äiti wiki strani + permission_manage_repository: Uredi shrambo + permission_browse_repository: Prebrskaj shrambo + permission_view_changesets: Poglej zapis sprememb + permission_commit_access: Dostop za predajo + permission_manage_boards: Uredi table + permission_view_messages: Poglej sporoÄila + permission_add_messages: Objavi sporoÄila + permission_edit_messages: Uredi sporoÄila + permission_edit_own_messages: Uredi lastna sporoÄila + permission_delete_messages: IzbriÅ¡i sporoÄila + permission_delete_own_messages: IzbriÅ¡i lastna sporoÄila + + project_module_issue_tracking: Sledenje zahtevkom + project_module_time_tracking: Sledenje Äasa + project_module_news: Novice + project_module_documents: Dokumenti + project_module_files: Datoteke + project_module_wiki: Wiki + project_module_repository: Shramba + project_module_boards: Table + + label_user: Uporabnik + label_user_plural: Uporabniki + label_user_new: Nov uporabnik + label_project: Projekt + label_project_new: Nov projekt + label_project_plural: Projekti + label_x_projects: + zero: ni projektov + one: 1 projekt + other: "%{count} projektov" + label_project_all: Vsi projekti + label_project_latest: Zadnji projekti + label_issue: Zahtevek + label_issue_new: Nov zahtevek + label_issue_plural: Zahtevki + label_issue_view_all: Poglej vse zahtevke + label_issues_by: "Zahtevki od %{value}" + label_issue_added: Zahtevek dodan + label_issue_updated: Zahtevek posodobljen + label_document: Dokument + label_document_new: Nov dokument + label_document_plural: Dokumenti + label_document_added: Dokument dodan + label_role: Vloga + label_role_plural: Vloge + label_role_new: Nova vloga + label_role_and_permissions: Vloge in dovoljenja + label_member: ÄŒlan + label_member_new: Nov Älan + label_member_plural: ÄŒlani + label_tracker: Vrsta zahtevka + label_tracker_plural: Vrste zahtevkov + label_tracker_new: Nova vrsta zahtevka + label_workflow: Potek dela + label_issue_status: Stanje zahtevka + label_issue_status_plural: Stanje zahtevkov + label_issue_status_new: Novo stanje + label_issue_category: Kategorija zahtevka + label_issue_category_plural: Kategorije zahtevkov + label_issue_category_new: Nova kategorija + label_custom_field: Polje po meri + label_custom_field_plural: Polja po meri + label_custom_field_new: Novo polje po meri + label_enumerations: Seznami + label_enumeration_new: Nova vrednost + label_information: Informacija + label_information_plural: Informacije + label_please_login: Prosimo prijavite se + label_register: Registracija + label_password_lost: Spremeni geslo + label_home: Domov + label_my_page: Moja stran + label_my_account: Moj raÄun + label_my_projects: Moji projekti + label_administration: Upravljanje + label_login: Prijava + label_logout: Odjava + label_help: PomoÄ + label_reported_issues: Prijavljeni zahtevki + label_assigned_to_me_issues: Zahtevki dodeljeni meni + label_last_login: Zadnja povezava + label_registered_on: Registriran + label_activity: Aktivnosti + label_overall_activity: Celotna aktivnost + label_user_activity: "Aktivnost %{value}" + label_new: Nov + label_logged_as: Prijavljen(a) kot + label_environment: Okolje + label_authentication: Overovitev + label_auth_source: NaÄin overovitve + label_auth_source_new: Nov naÄin overovitve + label_auth_source_plural: NaÄini overovitve + label_subproject_plural: Podprojekti + label_and_its_subprojects: "%{value} in njegovi podprojekti" + label_min_max_length: Min - Max dolžina + label_list: Seznam + label_date: Datum + label_integer: Celo Å¡tevilo + label_float: Decimalno Å¡tevilo + label_boolean: Boolean + label_string: Besedilo + label_text: Dolgo besedilo + label_attribute: Lastnost + label_attribute_plural: Lastnosti + label_no_data: Ni podatkov za prikaz + label_change_status: Spremeni stanje + label_history: Zgodovina + label_attachment: Datoteka + label_attachment_new: Nova datoteka + label_attachment_delete: IzbriÅ¡i datoteko + label_attachment_plural: Datoteke + label_file_added: Datoteka dodana + label_report: PoroÄilo + label_report_plural: PoroÄila + label_news: Novica + label_news_new: Dodaj novico + label_news_plural: Novice + label_news_latest: Zadnje novice + label_news_view_all: Poglej vse novice + label_news_added: Dodane novice + label_settings: Nastavitve + label_overview: Povzetek + label_version: Verzija + label_version_new: Nova verzija + label_version_plural: Verzije + label_confirmation: Potrditev + label_export_to: 'Na razpolago tudi v:' + label_read: Preberi... + label_public_projects: Javni projekti + label_open_issues: odprt zahtevek + label_open_issues_plural: odprti zahtevki + label_closed_issues: zaprt zahtevek + label_closed_issues_plural: zaprti zahtevki + label_x_open_issues_abbr_on_total: + zero: 0 odprtih / %{total} + one: 1 odprt / %{total} + other: "%{count} odprtih / %{total}" + label_x_open_issues_abbr: + zero: 0 odprtih + one: 1 odprt + other: "%{count} odprtih" + label_x_closed_issues_abbr: + zero: 0 zaprtih + one: 1 zaprt + other: "%{count} zaprtih" + label_total: Skupaj + label_permissions: Dovoljenja + label_current_status: Trenutno stanje + label_new_statuses_allowed: Novi zahtevki dovoljeni + label_all: vsi + label_none: noben + label_nobody: nihÄe + label_next: Naslednji + label_previous: PrejÅ¡nji + label_used_by: V uporabi od + label_details: Podrobnosti + label_add_note: Dodaj zabeležko + label_per_page: Na stran + label_calendar: Koledar + label_months_from: mesecev od + label_gantt: Gantogram + label_internal: Notranji + label_last_changes: "zadnjih %{count} sprememb" + label_change_view_all: Poglej vse spremembe + label_personalize_page: Individualiziraj to stran + label_comment: Komentar + label_comment_plural: Komentarji + label_x_comments: + zero: ni komentarjev + one: 1 komentar + other: "%{count} komentarjev" + label_comment_add: Dodaj komentar + label_comment_added: Komentar dodan + label_comment_delete: IzbriÅ¡i komentarje + label_query: Iskanje po meri + label_query_plural: Iskanja po meri + label_query_new: Novo iskanje + label_filter_add: Dodaj filter + label_filter_plural: Filtri + label_equals: je enako + label_not_equals: ni enako + label_in_less_than: v manj kot + label_in_more_than: v veÄ kot + label_in: v + label_today: danes + label_all_time: v vsem Äasu + label_yesterday: vÄeraj + label_this_week: ta teden + label_last_week: pretekli teden + label_last_n_days: "zadnjih %{count} dni" + label_this_month: ta mesec + label_last_month: zadnji mesec + label_this_year: to leto + label_date_range: Razpon datumov + label_less_than_ago: manj kot dni nazaj + label_more_than_ago: veÄ kot dni nazaj + label_ago: dni nazaj + label_contains: vsebuje + label_not_contains: ne vsebuje + label_day_plural: dni + label_repository: Shramba + label_repository_plural: Shrambe + label_browse: Prebrskaj + label_revision: Revizija + label_revision_plural: Revizije + label_associated_revisions: Povezane revizije + label_added: dodano + label_modified: spremenjeno + label_copied: kopirano + label_renamed: preimenovano + label_deleted: izbrisano + label_latest_revision: Zadnja revizija + label_latest_revision_plural: Zadnje revizije + label_view_revisions: Poglej revizije + label_max_size: NajveÄja velikost + label_sort_highest: Premakni na vrh + label_sort_higher: Premakni gor + label_sort_lower: Premakni dol + label_sort_lowest: Premakni na dno + label_roadmap: NaÄrt + label_roadmap_due_in: "Do %{value}" + label_roadmap_overdue: "%{value} zakasnel" + label_roadmap_no_issues: Ni zahtevkov za to verzijo + label_search: IÅ¡Äi + label_result_plural: Rezultati + label_all_words: Vse besede + label_wiki: Predstavitev + label_wiki_edit: Uredi stran + label_wiki_edit_plural: Uredi strani + label_wiki_page: Predstavitvena stran + label_wiki_page_plural: Predstavitvene strani + label_index_by_title: Razvrsti po naslovu + label_index_by_date: Razvrsti po datumu + label_current_version: Trenutna verzija + label_preview: Predogled + label_feed_plural: Atom viri + label_changes_details: Podrobnosti o vseh spremembah + label_issue_tracking: Sledenje zahtevkom + label_spent_time: Porabljen Äas + label_f_hour: "%{value} ura" + label_f_hour_plural: "%{value} ur" + label_time_tracking: Sledenje Äasu + label_change_plural: Spremembe + label_statistics: Statistika + label_commits_per_month: Predaj na mesec + label_commits_per_author: Predaj na avtorja + label_view_diff: Preglej razlike + label_diff_inline: znotraj + label_diff_side_by_side: vzporedno + label_options: Možnosti + label_copy_workflow_from: Kopiraj potek dela od + label_permissions_report: PoroÄilo o dovoljenjih + label_watched_issues: Spremljani zahtevki + label_related_issues: Povezani zahtevki + label_applied_status: Uveljavljeno stanje + label_loading: Nalaganje... + label_relation_new: Nova povezava + label_relation_delete: IzbriÅ¡i povezavo + label_relates_to: povezan z + label_duplicates: duplikati + label_duplicated_by: dupliciral + label_blocks: blok + label_blocked_by: blokiral + label_precedes: ima prednost pred + label_follows: sledi + label_end_to_start: konec na zaÄetek + label_end_to_end: konec na konec + label_start_to_start: zaÄetek na zaÄetek + label_start_to_end: zaÄetek na konec + label_stay_logged_in: Ostani prijavljen(a) + label_disabled: onemogoÄi + label_show_completed_versions: Prikaži zakljuÄene verzije + label_me: jaz + label_board: Forum + label_board_new: Nov forum + label_board_plural: Forumi + label_topic_plural: Teme + label_message_plural: SporoÄila + label_message_last: Zadnje sporoÄilo + label_message_new: Novo sporoÄilo + label_message_posted: SporoÄilo dodano + label_reply_plural: Odgovori + label_send_information: PoÅ¡lji informacijo o raÄunu uporabniku + label_year: Leto + label_month: Mesec + label_week: Teden + label_date_from: Do + label_date_to: Do + label_language_based: Glede na uporabnikov jezik + label_sort_by: "Razporedi po %{value}" + label_send_test_email: PoÅ¡lji testno e-pismo + label_feeds_access_key_created_on: "Atom dostopni kljuÄ narejen %{value} nazaj" + label_module_plural: Moduli + label_added_time_by: "Dodal %{author} %{age} nazaj" + label_updated_time_by: "Posodobil %{author} %{age} nazaj" + label_updated_time: "Posodobljeno %{value} nazaj" + label_jump_to_a_project: SkoÄi na projekt... + label_file_plural: Datoteke + label_changeset_plural: Zapisi sprememb + label_default_columns: Privzeti stolpci + label_no_change_option: (Ni spremembe) + label_bulk_edit_selected_issues: Uredi izbrane zahtevke skupaj + label_theme: Tema + label_default: Privzeto + label_search_titles_only: PreiÅ¡Äi samo naslove + label_user_mail_option_all: "Za vsak dogodek v vseh mojih projektih" + label_user_mail_option_selected: "Za vsak dogodek samo na izbranih projektih..." + label_user_mail_no_self_notified: "Ne želim biti opozorjen(a) na spremembe, ki jih naredim sam(a)" + label_registration_activation_by_email: aktivacija raÄuna po e-poÅ¡ti + label_registration_manual_activation: roÄna aktivacija raÄuna + label_registration_automatic_activation: samodejna aktivacija raÄuna + label_display_per_page: "Na stran: %{value}" + label_age: Starost + label_change_properties: Sprememba lastnosti + label_general: SploÅ¡no + label_more: VeÄ + label_scm: SCM + label_plugins: VtiÄniki + label_ldap_authentication: LDAP overovljanje + label_downloads_abbr: D/L + label_optional_description: Neobvezen opis + label_add_another_file: Dodaj Å¡e eno datoteko + label_preferences: Preference + label_chronological_order: KronoloÅ¡ko + label_reverse_chronological_order: Obrnjeno kronoloÅ¡ko + label_planning: NaÄrtovanje + label_incoming_emails: PrihajajoÄa e-poÅ¡ta + label_generate_key: Ustvari kljuÄ + label_issue_watchers: Spremljevalci + label_example: Vzorec + + button_login: Prijavi se + button_submit: PoÅ¡lji + button_save: Shrani + button_check_all: OznaÄi vse + button_uncheck_all: OdznaÄi vse + button_delete: IzbriÅ¡i + button_create: Ustvari + button_test: Testiraj + button_edit: Uredi + button_add: Dodaj + button_change: Spremeni + button_apply: Uporabi + button_clear: PoÄisti + button_lock: Zakleni + button_unlock: Odkleni + button_download: Prenesi + button_list: Seznam + button_view: Pogled + button_move: Premakni + button_back: Nazaj + button_cancel: PrekliÄi + button_activate: Aktiviraj + button_sort: Razvrsti + button_log_time: Beleži Äas + button_rollback: Povrni na to verzijo + button_watch: Spremljaj + button_unwatch: Ne spremljaj + button_reply: Odgovori + button_archive: Arhiviraj + button_unarchive: Odarhiviraj + button_reset: Ponastavi + button_rename: Preimenuj + button_change_password: Spremeni geslo + button_copy: Kopiraj + button_annotate: ZapiÅ¡i pripombo + button_update: Posodobi + button_configure: Konfiguriraj + button_quote: Citiraj + + status_active: aktivni + status_registered: registriran + status_locked: zaklenjen + + text_select_mail_notifications: Izberi dejanja za katera naj bodo poslana oznanila preko e-poÅ¡to. + text_regexp_info: npr. ^[A-Z0-9]+$ + text_min_max_length_info: 0 pomeni brez omejitev + text_project_destroy_confirmation: Ali ste prepriÄani da želite izbrisati izbrani projekt in vse z njim povezane podatke? + text_subprojects_destroy_warning: "Njegov(i) podprojekt(i): %{value} bodo prav tako izbrisani." + text_workflow_edit: Izberite vlogo in zahtevek za urejanje poteka dela + text_are_you_sure: Ali ste prepriÄani? + text_tip_issue_begin_day: naloga z zaÄetkom na ta dan + text_tip_issue_end_day: naloga z zakljuÄkom na ta dan + text_tip_issue_begin_end_day: naloga ki se zaÄne in konÄa ta dan + text_caracters_maximum: "najveÄ %{count} znakov." + text_caracters_minimum: "Mora biti vsaj dolg vsaj %{count} znakov." + text_length_between: "Dolžina med %{min} in %{max} znakov." + text_tracker_no_workflow: Potek dela za to vrsto zahtevka ni doloÄen + text_unallowed_characters: Nedovoljeni znaki + text_comma_separated: Dovoljenih je veÄ vrednosti (loÄenih z vejico). + text_issues_ref_in_commit_messages: Zahtevki sklicev in popravkov v sporoÄilu predaje + text_issue_added: "Zahtevek %{id} je sporoÄil(a) %{author}." + text_issue_updated: "Zahtevek %{id} je posodobil(a) %{author}." + text_wiki_destroy_confirmation: Ali ste prepriÄani da želite izbrisati to wiki stran in vso njeno vsebino? + text_issue_category_destroy_question: "Nekateri zahtevki (%{count}) so dodeljeni tej kategoriji. Kaj želite storiti?" + text_issue_category_destroy_assignments: Odstrani naloge v kategoriji + text_issue_category_reassign_to: Ponovno dodeli zahtevke tej kategoriji + text_user_mail_option: "Na neizbrane projekte boste prejemali le obvestila o zadevah ki jih spremljate ali v katere ste vkljuÄeni (npr. zahtevki katerih avtor(ica) ste)" + text_no_configuration_data: "Vloge, vrste zahtevkov, statusi zahtevkov in potek dela Å¡e niso bili doloÄeni. \nZelo priporoÄljivo je, da naložite privzeto konfiguracijo, ki jo lahko kasneje tudi prilagodite." + text_load_default_configuration: Naloži privzeto konfiguracijo + text_status_changed_by_changeset: "Dodano v zapis sprememb %{value}." + text_issues_destroy_confirmation: 'Ali ste prepriÄani, da želite izbrisati izbrani(e) zahtevek(ke)?' + text_select_project_modules: 'Izberite module, ki jih želite omogoÄiti za ta projekt:' + text_default_administrator_account_changed: Spremenjen privzeti administratorski raÄun + text_file_repository_writable: OmogoÄeno pisanje v shrambo datotek + text_rmagick_available: RMagick je na voljo(neobvezno) + text_destroy_time_entries_question: "%{hours} ur je bilo opravljenih na zahtevku, ki ga želite izbrisati. Kaj želite storiti?" + text_destroy_time_entries: IzbriÅ¡i opravljene ure + text_assign_time_entries_to_project: Predaj opravljene ure projektu + text_reassign_time_entries: 'Prenesi opravljene ure na ta zahtevek:' + text_user_wrote: "%{value} je napisal(a):" + text_enumeration_destroy_question: "%{count} objektov je doloÄenih tej vrednosti." + text_enumeration_category_reassign_to: 'Ponastavi jih na to vrednost:' + text_email_delivery_not_configured: "E-poÅ¡tna dostava ni nastavljena in oznanila so onemogoÄena.\nNastavite vaÅ¡ SMTP strežnik v config/configuration.yml in ponovno zaženite aplikacijo da ga omogoÄite.\n" + text_repository_usernames_mapping: "Izberite ali posodobite Redmine uporabnika dodeljenega vsakemu uporabniÅ¡kemu imenu najdenemu v zapisniku shrambe.\n Uporabniki z enakim Redmine ali shrambinem uporabniÅ¡kem imenu ali e-poÅ¡tnem naslovu so samodejno dodeljeni." + text_diff_truncated: '... Ta sprememba je bila odsekana ker presega najveÄjo velikost ki je lahko prikazana.' + + default_role_manager: Upravnik + default_role_developer: Razvijalec + default_role_reporter: PoroÄevalec + default_tracker_bug: HroÅ¡Ä + default_tracker_feature: Funkcija + default_tracker_support: Podpora + default_issue_status_new: Nov + default_issue_status_in_progress: V teku + default_issue_status_resolved: ReÅ¡en + default_issue_status_feedback: Povratna informacija + default_issue_status_closed: ZakljuÄen + default_issue_status_rejected: Zavrnjen + default_doc_category_user: UporabniÅ¡ka dokumentacija + default_doc_category_tech: TehniÄna dokumentacija + default_priority_low: Nizka + default_priority_normal: ObiÄajna + default_priority_high: Visoka + default_priority_urgent: Urgentna + default_priority_immediate: TakojÅ¡nje ukrepanje + default_activity_design: Oblikovanje + default_activity_development: Razvoj + + enumeration_issue_priorities: Prioritete zahtevkov + enumeration_doc_categories: Kategorije dokumentov + enumeration_activities: Aktivnosti (sledenje Äasa) + warning_attachments_not_saved: "%{count} datotek(e) ni bilo mogoÄe shraniti." + field_editable: Uredljivo + text_plugin_assets_writable: Zapisljiva mapa za vtiÄnike + label_display: Prikaz + button_create_and_continue: Ustvari in nadaljuj + text_custom_field_possible_values_info: 'Ena vrstica za vsako vrednost' + setting_repository_log_display_limit: NajveÄje Å¡tevilo prikazanih revizij v log datoteki + setting_file_max_size_displayed: NajveÄja velikost besedilnih datotek v vkljuÄenem prikazu + field_watcher: Opazovalec + setting_openid: Dovoli OpenID prijavo in registracijo + field_identity_url: OpenID URL + label_login_with_open_id_option: ali se prijavi z OpenID + field_content: Vsebina + label_descending: PadajoÄe + label_sort: Razvrsti + label_ascending: NaraÅ¡ÄajoÄe + label_date_from_to: Od %{start} do %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Ta stran ima %{descendants} podstran(i) in naslednik(ov). Kaj želite storiti? + text_wiki_page_reassign_children: Znova dodeli podstrani tej glavni strani + text_wiki_page_nullify_children: Obdrži podstrani kot glavne strani + text_wiki_page_destroy_children: IzbriÅ¡i podstrani in vse njihove naslednike + setting_password_min_length: Minimalna dolžina gesla + field_group_by: Združi rezultate po + mail_subject_wiki_content_updated: "'%{id}' wiki stran je bila posodobljena" + label_wiki_content_added: Wiki stran dodana + mail_subject_wiki_content_added: "'%{id}' wiki stran je bila dodana" + mail_body_wiki_content_added: "%{author} je dodal '%{id}' wiki stran" + label_wiki_content_updated: Wiki stran posodobljena + mail_body_wiki_content_updated: "%{author} je posodobil '%{id}' wiki stran." + permission_add_project: Ustvari projekt + setting_new_project_user_role_id: Vloga, dodeljena neadministratorskemu uporabniku, ki je ustvaril projekt + label_view_all_revisions: Poglej vse revizije + label_tag: Oznaka + label_branch: Veja + error_no_tracker_in_project: Noben sledilnik ni povezan s tem projektom. Prosimo preverite nastavitve projekta. + error_no_default_issue_status: Privzeti zahtevek ni definiran. Prosimo preverite svoje nastavitve (Pojdite na "Administracija -> Stanje zahtevkov"). + text_journal_changed: "%{label} se je spremenilo iz %{old} v %{new}" + text_journal_set_to: "%{label} nastavljeno na %{value}" + text_journal_deleted: "%{label} izbrisan (%{old})" + label_group_plural: Skupine + label_group: Skupina + label_group_new: Nova skupina + label_time_entry_plural: Porabljen Äas + text_journal_added: "%{label} %{value} dodan" + field_active: Aktiven + enumeration_system_activity: Sistemska aktivnost + permission_delete_issue_watchers: IzbriÅ¡i opazovalce + version_status_closed: zaprt + version_status_locked: zaklenjen + version_status_open: odprt + error_can_not_reopen_issue_on_closed_version: Zahtevek dodeljen zaprti verziji ne more biti ponovno odprt + label_user_anonymous: Anonimni + button_move_and_follow: Premakni in sledi + setting_default_projects_modules: Privzeti moduli za nove projekte + setting_gravatar_default: Privzeta Gravatar slika + field_sharing: Deljenje + label_version_sharing_hierarchy: S projektno hierarhijo + label_version_sharing_system: Z vsemi projekti + label_version_sharing_descendants: S podprojekti + label_version_sharing_tree: Z drevesom projekta + label_version_sharing_none: Ni deljeno + error_can_not_archive_project: Ta projekt ne more biti arhiviran + button_duplicate: Podvoji + button_copy_and_follow: Kopiraj in sledi + label_copy_source: Vir + setting_issue_done_ratio: IzraÄunaj razmerje opravljenega zahtevka z + setting_issue_done_ratio_issue_status: Uporabi stanje zahtevka + error_issue_done_ratios_not_updated: Razmerje opravljenega zahtevka ni bilo posodobljeno. + error_workflow_copy_target: Prosimo izberite ciljni(e) sledilnik(e) in vlogo(e) + setting_issue_done_ratio_issue_field: Uporabi polje zahtevka + label_copy_same_as_target: Enako kot cilj + label_copy_target: Cilj + notice_issue_done_ratios_updated: Razmerje opravljenega zahtevka posodobljeno. + error_workflow_copy_source: Prosimo izberite vir zahtevka ali vlogo + label_update_issue_done_ratios: Posodobi razmerje opravljenega zahtevka + setting_start_of_week: ZaÄni koledarje z + permission_view_issues: Poglej zahtevke + label_display_used_statuses_only: Prikaži samo stanja ki uporabljajo ta sledilnik + label_revision_id: Revizija %{value} + label_api_access_key: API dostopni kljuÄ + label_api_access_key_created_on: API dostopni kljuÄ ustvarjen pred %{value} + label_feeds_access_key: Atom dostopni kljuÄ + notice_api_access_key_reseted: VaÅ¡ API dostopni kljuÄ je bil ponastavljen. + setting_rest_api_enabled: OmogoÄi REST spletni servis + label_missing_api_access_key: ManjkajoÄ API dostopni kljuÄ + label_missing_feeds_access_key: ManjkajoÄ Atom dostopni kljuÄ + button_show: Prikaži + text_line_separated: Dovoljenih veÄ vrednosti (ena vrstica za vsako vrednost). + setting_mail_handler_body_delimiters: Odreži e-poÅ¡to po eni od teh vrstic + permission_add_subprojects: Ustvari podprojekte + label_subproject_new: Nov podprojekt + text_own_membership_delete_confirmation: |- + Odstranili boste nekatere ali vse od dovoljenj zaradi Äesar morda ne boste mogli veÄ urejati tega projekta. + Ali ste prepriÄani, da želite nadaljevati? + label_close_versions: Zapri dokonÄane verzije + label_board_sticky: Lepljivo + label_board_locked: Zaklenjeno + permission_export_wiki_pages: Izvozi wiki strani + setting_cache_formatted_text: Predpomni oblikovano besedilo + permission_manage_project_activities: Uredi aktivnosti projekta + error_unable_delete_issue_status: Stanja zahtevka ni bilo možno spremeniti + label_profile: Profil + permission_manage_subtasks: Uredi podnaloge + field_parent_issue: Nadrejena naloga + label_subtask_plural: Podnaloge + label_project_copy_notifications: Med kopiranjem projekta poÅ¡lji e-poÅ¡tno sporoÄilo + error_can_not_delete_custom_field: Polja po meri ni mogoÄe izbrisati + error_unable_to_connect: Povezava ni mogoÄa (%{value}) + error_can_not_remove_role: Ta vloga je v uporabi in je ni mogoÄe izbrisati. + error_can_not_delete_tracker: Ta sledilnik vsebuje zahtevke in se ga ne more izbrisati. + field_principal: Upravnik varnosti + label_my_page_block: Moj gradnik strani + notice_failed_to_save_members: "Shranjevanje uporabnika(ov) ni uspelo: %{errors}." + text_zoom_out: Približaj + text_zoom_in: Oddalji + notice_unable_delete_time_entry: Brisanje dnevnika porabljenaga Äasa ni mogoÄe. + label_overall_spent_time: Skupni porabljeni Äas + field_time_entries: Beleži porabljeni Äas + project_module_gantt: Gantogram + project_module_calendar: Koledear + button_edit_associated_wikipage: "Uredi povezano Wiki stran: %{page_title}" + field_text: Besedilno polje + label_user_mail_option_only_owner: Samo za stvari katerih lastnik sem + setting_default_notification_option: Privzeta možnost obveÅ¡Äanja + label_user_mail_option_only_my_events: Samo za stvari, ki jih opazujem ali sem v njih vpleten + label_user_mail_option_only_assigned: Samo za stvari, ki smo mi dodeljene + label_user_mail_option_none: Noben dogodek + field_member_of_group: PooblaÅ¡ÄenÄeva skupina + field_assigned_to_role: PooblaÅ¡ÄenÄeva vloga + notice_not_authorized_archived_project: Projekt, do katerega poskuÅ¡ate dostopati, je bil arhiviran. + label_principal_search: "PoiÅ¡Äi uporabnika ali skupino:" + label_user_search: "PoiÅ¡Äi uporabnikia:" + field_visible: Viden + setting_emails_header: Glava e-poÅ¡te + setting_commit_logtime_activity_id: Aktivnost zabeleženega Äasa + text_time_logged_by_changeset: Uporabljeno v spremembi %{value}. + setting_commit_logtime_enabled: OmogoÄi beleženje Äasa + notice_gantt_chart_truncated: Graf je bil odrezan, ker je prekoraÄil najveÄje dovoljeno Å¡tevilo elementov, ki se jih lahko prikaže (%{max}) + setting_gantt_items_limit: NajveÄje Å¡tevilo elementov prikazano na gantogramu + field_warn_on_leaving_unsaved: Opozori me, kadar zapuÅ¡Äam stran z neshranjenim besedilom + text_warn_on_leaving_unsaved: Trenutna stran vsebuje neshranjeno besedilo ki bo izgubljeno, Äe zapustite to stran. + label_my_queries: Moje poizvedbe po meri + text_journal_changed_no_detail: "%{label} posodobljen" + label_news_comment_added: Komentar dodan novici + button_expand_all: RazÅ¡iri vse + button_collapse_all: SkrÄi vse + label_additional_workflow_transitions_for_assignee: Dovoljeni dodatni prehodi kadar je uporabnik pooblaÅ¡Äenec + label_additional_workflow_transitions_for_author: Dovoljeni dodatni prehodi kadar je uporabnik avtor + label_bulk_edit_selected_time_entries: Skupinsko urejanje izbranih Äasovnih zapisov + text_time_entries_destroy_confirmation: Ali ste prepriÄani, da želite izbristai izbran(e) Äasovn(i/e) zapis(e)? + label_role_anonymous: Anonimni + label_role_non_member: NeÄlan + label_issue_note_added: Dodan zaznamek + label_issue_status_updated: Status posodobljen + label_issue_priority_updated: Prioriteta posodobljena + label_issues_visibility_own: Zahtevek ustvarjen s strani uporabnika ali dodeljen uporabniku + field_issues_visibility: Vidljivost zahtevkov + label_issues_visibility_all: Vsi zahtevki + permission_set_own_issues_private: Nastavi lastne zahtevke kot javne ali zasebne + field_is_private: Zaseben + permission_set_issues_private: Nastavi zahtevke kot javne ali zasebne + label_issues_visibility_public: Vsi nezasebni zahtevki + text_issues_destroy_descendants_confirmation: To bo izbrisalo tudi %{count} podnalog(o). + field_commit_logs_encoding: Kodiranje sporoÄil ob predaji + field_scm_path_encoding: Pot do kodiranja + text_scm_path_encoding_note: "Privzeto: UTF-8" + field_path_to_repository: Pot do shrambe + field_root_directory: Korenska mapa + field_cvs_module: Modul + field_cvsroot: CVSROOT + text_mercurial_repository_note: Lokalna shramba (npr. /hgrepo, c:\hgrepo) + text_scm_command: Ukaz + text_scm_command_version: Verzija + label_git_report_last_commit: SporoÄi zadnje uveljavljanje datotek in map + text_scm_config: Svoje SCM ukaze lahko nastavite v datoteki config/configuration.yml. Po urejanju prosimo ponovno zaženite aplikacijo. + text_scm_command_not_available: SCM ukaz ni na voljo. Prosimo preverite nastavitve v upravljalskem podoknu. + + text_git_repository_note: Shramba je prazna in lokalna (npr. /gitrepo, c:\gitrepo) + + notice_issue_successful_create: Ustvarjen zahtevek %{id}. + label_between: med + setting_issue_group_assignment: Dovoli dodeljevanje zahtevka skupinam + label_diff: diff + + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 zahtevek + one: 1 zahtevek + other: "%{count} zahtevki" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: vsi + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: S podprojekti + label_cross_project_tree: Z drevesom projekta + label_cross_project_hierarchy: S projektno hierarhijo + label_cross_project_system: Z vsemi projekti + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Skupaj + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bf/bfe0563380a11f9cf16cb611af1cccdd37907a3b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bf/bfe0563380a11f9cf16cb611af1cccdd37907a3b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,204 @@ +require 'active_record' + +module ActiveRecord + class Base + include Redmine::I18n + # Translate attribute names for validation errors display + def self.human_attribute_name(attr, *args) + attr = attr.to_s.sub(/_id$/, '') + + l("field_#{name.underscore.gsub('/', '_')}_#{attr}", :default => ["field_#{attr}".to_sym, attr]) + end + end + + # Undefines private Kernel#open method to allow using `open` scopes in models. + # See Defect #11545 (http://www.redmine.org/issues/11545) for details. + class Base + class << self + undef open + end + end + class Relation ; undef open ; end +end + +module ActionView + module Helpers + module DateHelper + # distance_of_time_in_words breaks when difference is greater than 30 years + def distance_of_date_in_words(from_date, to_date = 0, options = {}) + from_date = from_date.to_date if from_date.respond_to?(:to_date) + to_date = to_date.to_date if to_date.respond_to?(:to_date) + distance_in_days = (to_date - from_date).abs + + I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale| + case distance_in_days + when 0..60 then locale.t :x_days, :count => distance_in_days.round + when 61..720 then locale.t :about_x_months, :count => (distance_in_days / 30).round + else locale.t :over_x_years, :count => (distance_in_days / 365).floor + end + end + end + end + end + + class Resolver + def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[]) + cached(key, [name, prefix, partial], details, locals) do + if details[:formats] & [:xml, :json] + details = details.dup + details[:formats] = details[:formats].dup + [:api] + end + find_templates(name, prefix, partial, details) + end + end + end +end + +# Do not HTML escape text templates +module ActionView + class Template + module Handlers + class ERB + def call(template) + if template.source.encoding_aware? + # First, convert to BINARY, so in case the encoding is + # wrong, we can still find an encoding tag + # (<%# encoding %>) inside the String using a regular + # expression + template_source = template.source.dup.force_encoding("BINARY") + + erb = template_source.gsub(ENCODING_TAG, '') + encoding = $2 + + erb.force_encoding valid_encoding(template.source.dup, encoding) + + # Always make sure we return a String in the default_internal + erb.encode! + else + erb = template.source.dup + end + + self.class.erb_implementation.new( + erb, + :trim => (self.class.erb_trim_mode == "-"), + :escape => template.identifier =~ /\.text/ # only escape HTML templates + ).src + end + end + end + end +end + +ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| html_tag || ''.html_safe } + +# HTML5: is invalid, use instead +module ActionView + module Helpers + class InstanceTag + private + def add_options_with_non_empty_blank_option(option_tags, options, value = nil) + if options[:include_blank] == true + options = options.dup + options[:include_blank] = ' '.html_safe + end + add_options_without_non_empty_blank_option(option_tags, options, value) + end + alias_method_chain :add_options, :non_empty_blank_option + end + + module FormTagHelper + def select_tag_with_non_empty_blank_option(name, option_tags = nil, options = {}) + if options.delete(:include_blank) + options[:prompt] = ' '.html_safe + end + select_tag_without_non_empty_blank_option(name, option_tags, options) + end + alias_method_chain :select_tag, :non_empty_blank_option + end + + module FormOptionsHelper + def options_for_select_with_non_empty_blank_option(container, selected = nil) + if container.is_a?(Array) + container = container.map {|element| element.blank? ? [" ".html_safe, ""] : element} + end + options_for_select_without_non_empty_blank_option(container, selected) + end + alias_method_chain :options_for_select, :non_empty_blank_option + end + end +end + +require 'mail' + +module DeliveryMethods + class AsyncSMTP < ::Mail::SMTP + def deliver!(*args) + Thread.start do + super *args + end + end + end + + class AsyncSendmail < ::Mail::Sendmail + def deliver!(*args) + Thread.start do + super *args + end + end + end + + class TmpFile + def initialize(*args); end + + def deliver!(mail) + dest_dir = File.join(Rails.root, 'tmp', 'emails') + Dir.mkdir(dest_dir) unless File.directory?(dest_dir) + File.open(File.join(dest_dir, mail.message_id.gsub(/[<>]/, '') + '.eml'), 'wb') {|f| f.write(mail.encoded) } + end + end +end + +ActionMailer::Base.add_delivery_method :async_smtp, DeliveryMethods::AsyncSMTP +ActionMailer::Base.add_delivery_method :async_sendmail, DeliveryMethods::AsyncSendmail +ActionMailer::Base.add_delivery_method :tmp_file, DeliveryMethods::TmpFile + +# Changes how sent emails are logged +# Rails doesn't log cc and bcc which is misleading when using bcc only (#12090) +module ActionMailer + class LogSubscriber < ActiveSupport::LogSubscriber + def deliver(event) + recipients = [:to, :cc, :bcc].inject("") do |s, header| + r = Array.wrap(event.payload[header]) + if r.any? + s << "\n #{header}: #{r.join(', ')}" + end + s + end + info("\nSent email \"#{event.payload[:subject]}\" (%1.fms)#{recipients}" % event.duration) + debug(event.payload[:mail]) + end + end +end + +module ActionController + module MimeResponds + class Collector + def api(&block) + any(:xml, :json, &block) + end + end + end +end + +module ActionController + class Base + # Displays an explicit message instead of a NoMethodError exception + # when trying to start Redmine with an old session_store.rb + # TODO: remove it in a later version + def self.session=(*args) + $stderr.puts "Please remove config/initializers/session_store.rb and run `rake generate_secret_token`.\n" + + "Setting the session secret with ActionController.session= is no longer supported in Rails 3." + exit 1 + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bf/bfe4ef3580246b72914ef6e2a0108f1ec79d744c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bf/bfe4ef3580246b72914ef6e2a0108f1ec79d744c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,297 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class TimelogController < ApplicationController + menu_item :issues + + before_filter :find_project_for_new_time_entry, :only => [:create] + before_filter :find_time_entry, :only => [:show, :edit, :update] + before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy] + before_filter :authorize, :except => [:new, :index, :report] + + before_filter :find_optional_project, :only => [:index, :report] + before_filter :find_optional_project_for_new_time_entry, :only => [:new] + before_filter :authorize_global, :only => [:new, :index, :report] + + accept_rss_auth :index + accept_api_auth :index, :show, :create, :update, :destroy + + rescue_from Query::StatementInvalid, :with => :query_statement_invalid + + helper :sort + include SortHelper + helper :issues + include TimelogHelper + helper :custom_fields + include CustomFieldsHelper + helper :queries + include QueriesHelper + + def index + @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') + + sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria) + sort_update(@query.sortable_columns) + scope = time_entry_scope(:order => sort_clause). + includes(:project, :user, :issue). + preload(:issue => [:project, :tracker, :status, :assigned_to, :priority]) + + respond_to do |format| + format.html { + @entry_count = scope.count + @entry_pages = Paginator.new @entry_count, per_page_option, params['page'] + @entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).all + @total_hours = scope.sum(:hours).to_f + + render :layout => !request.xhr? + } + format.api { + @entry_count = scope.count + @offset, @limit = api_offset_and_limit + @entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).all + } + format.atom { + entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").all + render_feed(entries, :title => l(:label_spent_time)) + } + format.csv { + # Export all entries + @entries = scope.all + send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv') + } + end + end + + def report + @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') + scope = time_entry_scope + + @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope) + + respond_to do |format| + format.html { render :layout => !request.xhr? } + format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') } + end + end + + def show + respond_to do |format| + # TODO: Implement html response + format.html { render :nothing => true, :status => 406 } + format.api + end + end + + def new + @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) + @time_entry.safe_attributes = params[:time_entry] + end + + def create + @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) + @time_entry.safe_attributes = params[:time_entry] + + call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) + + if @time_entry.save + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_create) + if params[:continue] + if params[:project_id] + options = { + :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, + :back_url => params[:back_url] + } + if @time_entry.issue + redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options) + else + redirect_to new_project_time_entry_path(@time_entry.project, options) + end + else + options = { + :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, + :back_url => params[:back_url] + } + redirect_to new_time_entry_path(options) + end + else + redirect_back_or_default project_time_entries_path(@time_entry.project) + end + } + format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } + end + else + respond_to do |format| + format.html { render :action => 'new' } + format.api { render_validation_errors(@time_entry) } + end + end + end + + def edit + @time_entry.safe_attributes = params[:time_entry] + end + + def update + @time_entry.safe_attributes = params[:time_entry] + + call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) + + if @time_entry.save + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_back_or_default project_time_entries_path(@time_entry.project) + } + format.api { render_api_ok } + end + else + respond_to do |format| + format.html { render :action => 'edit' } + format.api { render_validation_errors(@time_entry) } + end + end + end + + def bulk_edit + @available_activities = TimeEntryActivity.shared.active + @custom_fields = TimeEntry.first.available_custom_fields + end + + def bulk_update + attributes = parse_params_for_bulk_time_entry_attributes(params) + + unsaved_time_entry_ids = [] + @time_entries.each do |time_entry| + time_entry.reload + time_entry.safe_attributes = attributes + call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry }) + unless time_entry.save + logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info + # Keep unsaved time_entry ids to display them in flash error + unsaved_time_entry_ids << time_entry.id + end + end + set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids) + redirect_back_or_default project_time_entries_path(@projects.first) + end + + def destroy + destroyed = TimeEntry.transaction do + @time_entries.each do |t| + unless t.destroy && t.destroyed? + raise ActiveRecord::Rollback + end + end + end + + respond_to do |format| + format.html { + if destroyed + flash[:notice] = l(:notice_successful_delete) + else + flash[:error] = l(:notice_unable_delete_time_entry) + end + redirect_back_or_default project_time_entries_path(@projects.first) + } + format.api { + if destroyed + render_api_ok + else + render_validation_errors(@time_entries) + end + } + end + end + +private + def find_time_entry + @time_entry = TimeEntry.find(params[:id]) + unless @time_entry.editable_by?(User.current) + render_403 + return false + end + @project = @time_entry.project + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_time_entries + @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids]) + raise ActiveRecord::RecordNotFound if @time_entries.empty? + @projects = @time_entries.collect(&:project).compact.uniq + @project = @projects.first if @projects.size == 1 + rescue ActiveRecord::RecordNotFound + render_404 + end + + def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids) + if unsaved_time_entry_ids.empty? + flash[:notice] = l(:notice_successful_update) unless time_entries.empty? + else + flash[:error] = l(:notice_failed_to_save_time_entries, + :count => unsaved_time_entry_ids.size, + :total => time_entries.size, + :ids => '#' + unsaved_time_entry_ids.join(', #')) + end + end + + def find_optional_project_for_new_time_entry + if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present? + @project = Project.find(project_id) + end + if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present? + @issue = Issue.find(issue_id) + @project ||= @issue.project + end + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_project_for_new_time_entry + find_optional_project_for_new_time_entry + if @project.nil? + render_404 + end + end + + def find_optional_project + if !params[:issue_id].blank? + @issue = Issue.find(params[:issue_id]) + @project = @issue.project + elsif !params[:project_id].blank? + @project = Project.find(params[:project_id]) + end + end + + # Returns the TimeEntry scope for index and report actions + def time_entry_scope(options={}) + scope = @query.results_scope(options) + if @issue + scope = scope.on_issue(@issue) + end + scope + end + + def parse_params_for_bulk_time_entry_attributes(params) + attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?} + attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} + attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values] + attributes + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/bf/bffd91ecf4c0b23f00fb9bb59916391c678fcfd5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bf/bffd91ecf4c0b23f00fb9bb59916391c678fcfd5.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ +var NS4 = (navigator.appName == "Netscape" && parseInt(navigator.appVersion) < 5); + +function addOption(theSel, theText, theValue) { + var newOpt = new Option(theText, theValue); + var selLength = theSel.length; + theSel.options[selLength] = newOpt; +} + +function swapOptions(theSel, index1, index2) { + var text, value; + text = theSel.options[index1].text; + value = theSel.options[index1].value; + theSel.options[index1].text = theSel.options[index2].text; + theSel.options[index1].value = theSel.options[index2].value; + theSel.options[index2].text = text; + theSel.options[index2].value = value; +} + +function deleteOption(theSel, theIndex) { + var selLength = theSel.length; + if (selLength > 0) { + theSel.options[theIndex] = null; + } +} + +function moveOptions(theSelFrom, theSelTo) { + var selLength = theSelFrom.length; + var selectedText = new Array(); + var selectedValues = new Array(); + var selectedCount = 0; + var i; + for (i = selLength - 1; i >= 0; i--) { + if (theSelFrom.options[i].selected) { + selectedText[selectedCount] = theSelFrom.options[i].text; + selectedValues[selectedCount] = theSelFrom.options[i].value; + deleteOption(theSelFrom, i); + selectedCount++; + } + } + for (i = selectedCount - 1; i >= 0; i--) { + addOption(theSelTo, selectedText[i], selectedValues[i]); + } + if (NS4) history.go(0); +} + +function moveOptionUp(theSel) { + var index = theSel.selectedIndex; + if (index > 0) { + swapOptions(theSel, index-1, index); + theSel.selectedIndex = index-1; + } +} + +function moveOptionDown(theSel) { + var index = theSel.selectedIndex; + if (index < theSel.length - 1) { + swapOptions(theSel, index, index+1); + theSel.selectedIndex = index+1; + } +} + +// OK +function selectAllOptions(id) { + var select = $('#'+id); + select.children('option').attr('selected', true); +} diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c0/c02eb2e34e399c7d7d5b13e4d8bbd611abc99f12.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c0/c02eb2e34e399c7d7d5b13e4d8bbd611abc99f12.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,977 @@ +begin + require 'zlib' +rescue + # Zlib not available +end + +require 'rexml/document' + +module SVG + module Graph + VERSION = '@ANT_VERSION@' + + # === Base object for generating SVG Graphs + # + # == Synopsis + # + # This class is only used as a superclass of specialized charts. Do not + # attempt to use this class directly, unless creating a new chart type. + # + # For examples of how to subclass this class, see the existing specific + # subclasses, such as SVG::Graph::Pie. + # + # == Examples + # + # For examples of how to use this package, see either the test files, or + # the documentation for the specific class you want to use. + # + # * file:test/plot.rb + # * file:test/single.rb + # * file:test/test.rb + # * file:test/timeseries.rb + # + # == Description + # + # This package should be used as a base for creating SVG graphs. + # + # == Acknowledgements + # + # Leo Lapworth for creating the SVG::TT::Graph package which this Ruby + # port is based on. + # + # Stephen Morgan for creating the TT template and SVG. + # + # == See + # + # * SVG::Graph::BarHorizontal + # * SVG::Graph::Bar + # * SVG::Graph::Line + # * SVG::Graph::Pie + # * SVG::Graph::Plot + # * SVG::Graph::TimeSeries + # + # == Author + # + # Sean E. Russell + # + # Copyright 2004 Sean E. Russell + # This software is available under the Ruby license[LICENSE.txt] + # + class Graph + include REXML + + # Initialize the graph object with the graph settings. You won't + # instantiate this class directly; see the subclass for options. + # [width] 500 + # [height] 300 + # [show_x_guidelines] false + # [show_y_guidelines] true + # [show_data_values] true + # [min_scale_value] 0 + # [show_x_labels] true + # [stagger_x_labels] false + # [rotate_x_labels] false + # [step_x_labels] 1 + # [step_include_first_x_label] true + # [show_y_labels] true + # [rotate_y_labels] false + # [scale_integers] false + # [show_x_title] false + # [x_title] 'X Field names' + # [show_y_title] false + # [y_title_text_direction] :bt + # [y_title] 'Y Scale' + # [show_graph_title] false + # [graph_title] 'Graph Title' + # [show_graph_subtitle] false + # [graph_subtitle] 'Graph Sub Title' + # [key] true, + # [key_position] :right, # bottom or righ + # [font_size] 12 + # [title_font_size] 16 + # [subtitle_font_size] 14 + # [x_label_font_size] 12 + # [x_title_font_size] 14 + # [y_label_font_size] 12 + # [y_title_font_size] 14 + # [key_font_size] 10 + # [no_css] false + # [add_popups] false + def initialize( config ) + @config = config + + self.top_align = self.top_font = self.right_align = self.right_font = 0 + + init_with({ + :width => 500, + :height => 300, + :show_x_guidelines => false, + :show_y_guidelines => true, + :show_data_values => true, + +# :min_scale_value => 0, + + :show_x_labels => true, + :stagger_x_labels => false, + :rotate_x_labels => false, + :step_x_labels => 1, + :step_include_first_x_label => true, + + :show_y_labels => true, + :rotate_y_labels => false, + :stagger_y_labels => false, + :scale_integers => false, + + :show_x_title => false, + :x_title => 'X Field names', + + :show_y_title => false, + :y_title_text_direction => :bt, + :y_title => 'Y Scale', + + :show_graph_title => false, + :graph_title => 'Graph Title', + :show_graph_subtitle => false, + :graph_subtitle => 'Graph Sub Title', + :key => true, + :key_position => :right, # bottom or right + + :font_size =>12, + :title_font_size =>16, + :subtitle_font_size =>14, + :x_label_font_size =>12, + :x_title_font_size =>14, + :y_label_font_size =>12, + :y_title_font_size =>14, + :key_font_size =>10, + + :no_css =>false, + :add_popups =>false, + }) + + set_defaults if respond_to? :set_defaults + + init_with config + end + + + # This method allows you do add data to the graph object. + # It can be called several times to add more data sets in. + # + # data_sales_02 = [12, 45, 21]; + # + # graph.add_data({ + # :data => data_sales_02, + # :title => 'Sales 2002' + # }) + def add_data conf + @data = [] unless defined? @data + + if conf[:data] and conf[:data].kind_of? Array + @data << conf + else + raise "No data provided by #{conf.inspect}" + end + end + + + # This method removes all data from the object so that you can + # reuse it to create a new graph but with the same config options. + # + # graph.clear_data + def clear_data + @data = [] + end + + + # This method processes the template with the data and + # config which has been set and returns the resulting SVG. + # + # This method will croak unless at least one data set has + # been added to the graph object. + # + # print graph.burn + def burn + raise "No data available" unless @data.size > 0 + + calculations if respond_to? :calculations + + start_svg + calculate_graph_dimensions + @foreground = Element.new( "g" ) + draw_graph + draw_titles + draw_legend + draw_data + @graph.add_element( @foreground ) + style + + data = "" + @doc.write( data, 0 ) + + if @config[:compress] + if Object.const_defined?(:Zlib) + inp, out = IO.pipe + gz = Zlib::GzipWriter.new( out ) + gz.write data + gz.close + data = inp.read + else + data << ""; + end + end + + return data + end + + + # Set the height of the graph box, this is the total height + # of the SVG box created - not the graph it self which auto + # scales to fix the space. + attr_accessor :height + # Set the width of the graph box, this is the total width + # of the SVG box created - not the graph it self which auto + # scales to fix the space. + attr_accessor :width + # Set the path to an external stylesheet, set to '' if + # you want to revert back to using the defaut internal version. + # + # To create an external stylesheet create a graph using the + # default internal version and copy the stylesheet section to + # an external file and edit from there. + attr_accessor :style_sheet + # (Bool) Show the value of each element of data on the graph + attr_accessor :show_data_values + # The point at which the Y axis starts, defaults to '0', + # if set to nil it will default to the minimum data value. + attr_accessor :min_scale_value + # Whether to show labels on the X axis or not, defaults + # to true, set to false if you want to turn them off. + attr_accessor :show_x_labels + # This puts the X labels at alternative levels so if they + # are long field names they will not overlap so easily. + # Default it false, to turn on set to true. + attr_accessor :stagger_x_labels + # This puts the Y labels at alternative levels so if they + # are long field names they will not overlap so easily. + # Default it false, to turn on set to true. + attr_accessor :stagger_y_labels + # This turns the X axis labels by 90 degrees. + # Default it false, to turn on set to true. + attr_accessor :rotate_x_labels + # This turns the Y axis labels by 90 degrees. + # Default it false, to turn on set to true. + attr_accessor :rotate_y_labels + # How many "steps" to use between displayed X axis labels, + # a step of one means display every label, a step of two results + # in every other label being displayed (label label label), + # a step of three results in every third label being displayed + # (label label label) and so on. + attr_accessor :step_x_labels + # Whether to (when taking "steps" between X axis labels) step from + # the first label (i.e. always include the first label) or step from + # the X axis origin (i.e. start with a gap if step_x_labels is greater + # than one). + attr_accessor :step_include_first_x_label + # Whether to show labels on the Y axis or not, defaults + # to true, set to false if you want to turn them off. + attr_accessor :show_y_labels + # Ensures only whole numbers are used as the scale divisions. + # Default it false, to turn on set to true. This has no effect if + # scale divisions are less than 1. + attr_accessor :scale_integers + # This defines the gap between markers on the Y axis, + # default is a 10th of the max_value, e.g. you will have + # 10 markers on the Y axis. NOTE: do not set this too + # low - you are limited to 999 markers, after that the + # graph won't generate. + attr_accessor :scale_divisions + # Whether to show the title under the X axis labels, + # default is false, set to true to show. + attr_accessor :show_x_title + # What the title under X axis should be, e.g. 'Months'. + attr_accessor :x_title + # Whether to show the title under the Y axis labels, + # default is false, set to true to show. + attr_accessor :show_y_title + # Aligns writing mode for Y axis label. + # Defaults to :bt (Bottom to Top). + # Change to :tb (Top to Bottom) to reverse. + attr_accessor :y_title_text_direction + # What the title under Y axis should be, e.g. 'Sales in thousands'. + attr_accessor :y_title + # Whether to show a title on the graph, defaults + # to false, set to true to show. + attr_accessor :show_graph_title + # What the title on the graph should be. + attr_accessor :graph_title + # Whether to show a subtitle on the graph, defaults + # to false, set to true to show. + attr_accessor :show_graph_subtitle + # What the subtitle on the graph should be. + attr_accessor :graph_subtitle + # Whether to show a key, defaults to false, set to + # true if you want to show it. + attr_accessor :key + # Where the key should be positioned, defaults to + # :right, set to :bottom if you want to move it. + attr_accessor :key_position + # Set the font size (in points) of the data point labels + attr_accessor :font_size + # Set the font size of the X axis labels + attr_accessor :x_label_font_size + # Set the font size of the X axis title + attr_accessor :x_title_font_size + # Set the font size of the Y axis labels + attr_accessor :y_label_font_size + # Set the font size of the Y axis title + attr_accessor :y_title_font_size + # Set the title font size + attr_accessor :title_font_size + # Set the subtitle font size + attr_accessor :subtitle_font_size + # Set the key font size + attr_accessor :key_font_size + # Show guidelines for the X axis + attr_accessor :show_x_guidelines + # Show guidelines for the Y axis + attr_accessor :show_y_guidelines + # Do not use CSS if set to true. Many SVG viewers do not support CSS, but + # not using CSS can result in larger SVGs as well as making it impossible to + # change colors after the chart is generated. Defaults to false. + attr_accessor :no_css + # Add popups for the data points on some graphs + attr_accessor :add_popups + + + protected + + def sort( *arrys ) + sort_multiple( arrys ) + end + + # Overwrite configuration options with supplied options. Used + # by subclasses. + def init_with config + config.each { |key, value| + self.send((key.to_s+"=").to_sym, value ) if respond_to? key.to_sym + } + end + + attr_accessor :top_align, :top_font, :right_align, :right_font + + KEY_BOX_SIZE = 12 + + # Override this (and call super) to change the margin to the left + # of the plot area. Results in @border_left being set. + def calculate_left_margin + @border_left = 7 + # Check for Y labels + max_y_label_height_px = rotate_y_labels ? + y_label_font_size : + get_y_labels.max{|a,b| + a.to_s.length<=>b.to_s.length + }.to_s.length * y_label_font_size * 0.6 + @border_left += max_y_label_height_px if show_y_labels + @border_left += max_y_label_height_px + 10 if stagger_y_labels + @border_left += y_title_font_size + 5 if show_y_title + end + + + # Calculates the width of the widest Y label. This will be the + # character height if the Y labels are rotated + def max_y_label_width_px + return font_size if rotate_y_labels + end + + + # Override this (and call super) to change the margin to the right + # of the plot area. Results in @border_right being set. + def calculate_right_margin + @border_right = 7 + if key and key_position == :right + val = keys.max { |a,b| a.length <=> b.length } + @border_right += val.length * key_font_size * 0.6 + @border_right += KEY_BOX_SIZE + @border_right += 10 # Some padding around the box + end + end + + + # Override this (and call super) to change the margin to the top + # of the plot area. Results in @border_top being set. + def calculate_top_margin + @border_top = 5 + @border_top += title_font_size if show_graph_title + @border_top += 5 + @border_top += subtitle_font_size if show_graph_subtitle + end + + + # Adds pop-up point information to a graph. + def add_popup( x, y, label ) + txt_width = label.length * font_size * 0.6 + 10 + tx = (x+txt_width > width ? x-5 : x+5) + t = @foreground.add_element( "text", { + "x" => tx.to_s, + "y" => (y - font_size).to_s, + "visibility" => "hidden", + }) + t.attributes["style"] = "fill: #000; "+ + (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;") + t.text = label.to_s + t.attributes["id"] = t.object_id.to_s + + @foreground.add_element( "circle", { + "cx" => x.to_s, + "cy" => y.to_s, + "r" => "10", + "style" => "opacity: 0", + "onmouseover" => + "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )", + "onmouseout" => + "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )", + }) + + end + + + # Override this (and call super) to change the margin to the bottom + # of the plot area. Results in @border_bottom being set. + def calculate_bottom_margin + @border_bottom = 7 + if key and key_position == :bottom + @border_bottom += @data.size * (font_size + 5) + @border_bottom += 10 + end + if show_x_labels + max_x_label_height_px = (not rotate_x_labels) ? + x_label_font_size : + get_x_labels.max{|a,b| + a.to_s.length<=>b.to_s.length + }.to_s.length * x_label_font_size * 0.6 + @border_bottom += max_x_label_height_px + @border_bottom += max_x_label_height_px + 10 if stagger_x_labels + end + @border_bottom += x_title_font_size + 5 if show_x_title + end + + + # Draws the background, axis, and labels. + def draw_graph + @graph = @root.add_element( "g", { + "transform" => "translate( #@border_left #@border_top )" + }) + + # Background + @graph.add_element( "rect", { + "x" => "0", + "y" => "0", + "width" => @graph_width.to_s, + "height" => @graph_height.to_s, + "class" => "graphBackground" + }) + + # Axis + @graph.add_element( "path", { + "d" => "M 0 0 v#@graph_height", + "class" => "axis", + "id" => "xAxis" + }) + @graph.add_element( "path", { + "d" => "M 0 #@graph_height h#@graph_width", + "class" => "axis", + "id" => "yAxis" + }) + + draw_x_labels + draw_y_labels + end + + + # Where in the X area the label is drawn + # Centered in the field, should be width/2. Start, 0. + def x_label_offset( width ) + 0 + end + + def make_datapoint_text( x, y, value, style="" ) + if show_data_values + @foreground.add_element( "text", { + "x" => x.to_s, + "y" => y.to_s, + "class" => "dataPointLabel", + "style" => "#{style} stroke: #fff; stroke-width: 2;" + }).text = value.to_s + text = @foreground.add_element( "text", { + "x" => x.to_s, + "y" => y.to_s, + "class" => "dataPointLabel" + }) + text.text = value.to_s + text.attributes["style"] = style if style.length > 0 + end + end + + + # Draws the X axis labels + def draw_x_labels + stagger = x_label_font_size + 5 + if show_x_labels + label_width = field_width + + count = 0 + for label in get_x_labels + if step_include_first_x_label == true then + step = count % step_x_labels + else + step = (count + 1) % step_x_labels + end + + if step == 0 then + text = @graph.add_element( "text" ) + text.attributes["class"] = "xAxisLabels" + text.text = label.to_s + + x = count * label_width + x_label_offset( label_width ) + y = @graph_height + x_label_font_size + 3 + t = 0 - (font_size / 2) + + if stagger_x_labels and count % 2 == 1 + y += stagger + @graph.add_element( "path", { + "d" => "M#{x} #@graph_height v#{stagger}", + "class" => "staggerGuideLine" + }) + end + + text.attributes["x"] = x.to_s + text.attributes["y"] = y.to_s + if rotate_x_labels + text.attributes["transform"] = + "rotate( 90 #{x} #{y-x_label_font_size} )"+ + " translate( 0 -#{x_label_font_size/4} )" + text.attributes["style"] = "text-anchor: start" + else + text.attributes["style"] = "text-anchor: middle" + end + end + + draw_x_guidelines( label_width, count ) if show_x_guidelines + count += 1 + end + end + end + + + # Where in the Y area the label is drawn + # Centered in the field, should be width/2. Start, 0. + def y_label_offset( height ) + 0 + end + + + def field_width + (@graph_width.to_f - font_size*2*right_font) / + (get_x_labels.length - right_align) + end + + + def field_height + (@graph_height.to_f - font_size*2*top_font) / + (get_y_labels.length - top_align) + end + + + # Draws the Y axis labels + def draw_y_labels + stagger = y_label_font_size + 5 + if show_y_labels + label_height = field_height + + count = 0 + y_offset = @graph_height + y_label_offset( label_height ) + y_offset += font_size/1.2 unless rotate_y_labels + for label in get_y_labels + y = y_offset - (label_height * count) + x = rotate_y_labels ? 0 : -3 + + if stagger_y_labels and count % 2 == 1 + x -= stagger + @graph.add_element( "path", { + "d" => "M#{x} #{y} h#{stagger}", + "class" => "staggerGuideLine" + }) + end + + text = @graph.add_element( "text", { + "x" => x.to_s, + "y" => y.to_s, + "class" => "yAxisLabels" + }) + text.text = label.to_s + if rotate_y_labels + text.attributes["transform"] = "translate( -#{font_size} 0 ) "+ + "rotate( 90 #{x} #{y} ) " + text.attributes["style"] = "text-anchor: middle" + else + text.attributes["y"] = (y - (y_label_font_size/2)).to_s + text.attributes["style"] = "text-anchor: end" + end + draw_y_guidelines( label_height, count ) if show_y_guidelines + count += 1 + end + end + end + + + # Draws the X axis guidelines + def draw_x_guidelines( label_height, count ) + if count != 0 + @graph.add_element( "path", { + "d" => "M#{label_height*count} 0 v#@graph_height", + "class" => "guideLines" + }) + end + end + + + # Draws the Y axis guidelines + def draw_y_guidelines( label_height, count ) + if count != 0 + @graph.add_element( "path", { + "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width", + "class" => "guideLines" + }) + end + end + + + # Draws the graph title and subtitle + def draw_titles + if show_graph_title + @root.add_element( "text", { + "x" => (width / 2).to_s, + "y" => (title_font_size).to_s, + "class" => "mainTitle" + }).text = graph_title.to_s + end + + if show_graph_subtitle + y_subtitle = show_graph_title ? + title_font_size + 10 : + subtitle_font_size + @root.add_element("text", { + "x" => (width / 2).to_s, + "y" => (y_subtitle).to_s, + "class" => "subTitle" + }).text = graph_subtitle.to_s + end + + if show_x_title + y = @graph_height + @border_top + x_title_font_size + if show_x_labels + y += x_label_font_size + 5 if stagger_x_labels + y += x_label_font_size + 5 + end + x = width / 2 + + @root.add_element("text", { + "x" => x.to_s, + "y" => y.to_s, + "class" => "xAxisTitle", + }).text = x_title.to_s + end + + if show_y_title + x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3) + y = height / 2 + + text = @root.add_element("text", { + "x" => x.to_s, + "y" => y.to_s, + "class" => "yAxisTitle", + }) + text.text = y_title.to_s + if y_title_text_direction == :bt + text.attributes["transform"] = "rotate( -90, #{x}, #{y} )" + else + text.attributes["transform"] = "rotate( 90, #{x}, #{y} )" + end + end + end + + def keys + return @data.collect{ |d| d[:title] } + end + + # Draws the legend on the graph + def draw_legend + if key + group = @root.add_element( "g" ) + + key_count = 0 + for key_name in keys + y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5) + group.add_element( "rect", { + "x" => 0.to_s, + "y" => y_offset.to_s, + "width" => KEY_BOX_SIZE.to_s, + "height" => KEY_BOX_SIZE.to_s, + "class" => "key#{key_count+1}" + }) + group.add_element( "text", { + "x" => (KEY_BOX_SIZE + 5).to_s, + "y" => (y_offset + KEY_BOX_SIZE).to_s, + "class" => "keyText" + }).text = key_name.to_s + key_count += 1 + end + + case key_position + when :right + x_offset = @graph_width + @border_left + 10 + y_offset = @border_top + 20 + when :bottom + x_offset = @border_left + 20 + y_offset = @border_top + @graph_height + 5 + if show_x_labels + max_x_label_height_px = (not rotate_x_labels) ? + x_label_font_size : + get_x_labels.max{|a,b| + a.to_s.length<=>b.to_s.length + }.to_s.length * x_label_font_size * 0.6 + x_label_font_size + y_offset += max_x_label_height_px + y_offset += max_x_label_height_px + 5 if stagger_x_labels + end + y_offset += x_title_font_size + 5 if show_x_title + end + group.attributes["transform"] = "translate(#{x_offset} #{y_offset})" + end + end + + + private + + def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 ) + if lo < hi + p = partition(arrys,lo,hi) + sort_multiple(arrys, lo, p-1) + sort_multiple(arrys, p+1, hi) + end + arrys + end + + def partition( arrys, lo, hi ) + p = arrys[0][lo] + l = lo + z = lo+1 + while z <= hi + if arrys[0][z] < p + l += 1 + arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] } + end + z += 1 + end + arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] } + l + end + + def style + if no_css + styles = parse_css + @root.elements.each("//*[@class]") { |el| + cl = el.attributes["class"] + style = styles[cl] + style += el.attributes["style"] if el.attributes["style"] + el.attributes["style"] = style + } + end + end + + def parse_css + css = get_style + rv = {} + while css =~ /^(\.(\w+)(?:\s*,\s*\.\w+)*)\s*\{/m + names_orig = names = $1 + css = $' + css =~ /([^}]+)\}/m + content = $1 + css = $' + + nms = [] + while names =~ /^\s*,?\s*\.(\w+)/ + nms << $1 + names = $' + end + + content = content.tr( "\n\t", " ") + for name in nms + current = rv[name] + current = current ? current+"; "+content : content + rv[name] = current.strip.squeeze(" ") + end + end + return rv + end + + + # Override and place code to add defs here + def add_defs defs + end + + + def start_svg + # Base document + @doc = Document.new + @doc << XMLDecl.new + @doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } + + %q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} ) + if style_sheet && style_sheet != '' + @doc << Instruction.new( "xml-stylesheet", + %Q{href="#{style_sheet}" type="text/css"} ) + end + @root = @doc.add_element( "svg", { + "width" => width.to_s, + "height" => height.to_s, + "viewBox" => "0 0 #{width} #{height}", + "xmlns" => "http://www.w3.org/2000/svg", + "xmlns:xlink" => "http://www.w3.org/1999/xlink", + "xmlns:a3" => "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/", + "a3:scriptImplementation" => "Adobe" + }) + @root << Comment.new( " "+"\\"*66 ) + @root << Comment.new( " Created with SVG::Graph " ) + @root << Comment.new( " SVG::Graph by Sean E. Russell " ) + @root << Comment.new( " Losely based on SVG::TT::Graph for Perl by"+ + " Leo Lapworth & Stephan Morgan " ) + @root << Comment.new( " "+"/"*66 ) + + defs = @root.add_element( "defs" ) + add_defs defs + if not(style_sheet && style_sheet != '') and !no_css + @root << Comment.new(" include default stylesheet if none specified ") + style = defs.add_element( "style", {"type"=>"text/css"} ) + style << CData.new( get_style ) + end + + @root << Comment.new( "SVG Background" ) + @root.add_element( "rect", { + "width" => width.to_s, + "height" => height.to_s, + "x" => "0", + "y" => "0", + "class" => "svgBackground" + }) + end + + + def calculate_graph_dimensions + calculate_left_margin + calculate_right_margin + calculate_bottom_margin + calculate_top_margin + @graph_width = width - @border_left - @border_right + @graph_height = height - @border_top - @border_bottom + end + + def get_style + return < 1, :tracker_id => 2).count + assert_tag :tag => 'a', :content => count.to_s, + :attributes => { :href => '/workflows/edit?role_id=1&tracker_id=2' } + end + + def test_get_edit + get :edit + assert_response :success + assert_template 'edit' + assert_not_nil assigns(:roles) + assert_not_nil assigns(:trackers) + end + + def test_get_edit_with_role_and_tracker + WorkflowTransition.delete_all + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3) + WorkflowTransition.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5) + + get :edit, :role_id => 2, :tracker_id => 1 + assert_response :success + assert_template 'edit' + + # used status only + assert_not_nil assigns(:statuses) + assert_equal [2, 3, 5], assigns(:statuses).collect(&:id) + + # allowed transitions + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'issue_status[3][5][]', + :value => 'always', + :checked => 'checked' } + # not allowed + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'issue_status[3][2][]', + :value => 'always', + :checked => nil } + # unused + assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'issue_status[1][1][]' } + end + + def test_get_edit_with_role_and_tracker_and_all_statuses + WorkflowTransition.delete_all + + get :edit, :role_id => 2, :tracker_id => 1, :used_statuses_only => '0' + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:statuses) + assert_equal IssueStatus.count, assigns(:statuses).size + + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'issue_status[1][1][]', + :value => 'always', + :checked => nil } + end + + def test_post_edit + post :edit, :role_id => 2, :tracker_id => 1, + :issue_status => { + '4' => {'5' => ['always']}, + '3' => {'1' => ['always'], '2' => ['always']} + } + assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' + + assert_equal 3, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count + assert_not_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first + assert_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4).first + end + + def test_post_edit_with_additional_transitions + post :edit, :role_id => 2, :tracker_id => 1, + :issue_status => { + '4' => {'5' => ['always']}, + '3' => {'1' => ['author'], '2' => ['assignee'], '4' => ['author', 'assignee']} + } + assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' + + assert_equal 4, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count + + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5).first + assert ! w.author + assert ! w.assignee + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1).first + assert w.author + assert ! w.assignee + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first + assert ! w.author + assert w.assignee + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4).first + assert w.author + assert w.assignee + end + + def test_clear_workflow + assert WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count > 0 + + post :edit, :role_id => 1, :tracker_id => 2 + assert_equal 0, WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count + end + + def test_get_permissions + get :permissions + + assert_response :success + assert_template 'permissions' + assert_not_nil assigns(:roles) + assert_not_nil assigns(:trackers) + end + + def test_get_permissions_with_role_and_tracker + WorkflowPermission.delete_all + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') + + get :permissions, :role_id => 1, :tracker_id => 2 + assert_response :success + assert_template 'permissions' + + assert_select 'input[name=role_id][value=1]' + assert_select 'input[name=tracker_id][value=2]' + + # Required field + assert_select 'select[name=?]', 'permissions[assigned_to_id][2]' do + assert_select 'option[value=]' + assert_select 'option[value=][selected=selected]', 0 + assert_select 'option[value=readonly]', :text => 'Read-only' + assert_select 'option[value=readonly][selected=selected]', 0 + assert_select 'option[value=required]', :text => 'Required' + assert_select 'option[value=required][selected=selected]' + end + + # Read-only field + assert_select 'select[name=?]', 'permissions[fixed_version_id][3]' do + assert_select 'option[value=]' + assert_select 'option[value=][selected=selected]', 0 + assert_select 'option[value=readonly]', :text => 'Read-only' + assert_select 'option[value=readonly][selected=selected]' + assert_select 'option[value=required]', :text => 'Required' + assert_select 'option[value=required][selected=selected]', 0 + end + + # Other field + assert_select 'select[name=?]', 'permissions[due_date][3]' do + assert_select 'option[value=]' + assert_select 'option[value=][selected=selected]', 0 + assert_select 'option[value=readonly]', :text => 'Read-only' + assert_select 'option[value=readonly][selected=selected]', 0 + assert_select 'option[value=required]', :text => 'Required' + assert_select 'option[value=required][selected=selected]', 0 + end + end + + def test_get_permissions_with_required_custom_field_should_not_show_required_option + cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :tracker_ids => [1], :is_required => true) + + get :permissions, :role_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'permissions' + + # Custom field that is always required + # The default option is "(Required)" + assert_select 'select[name=?]', "permissions[#{cf.id}][3]" do + assert_select 'option[value=]' + assert_select 'option[value=readonly]', :text => 'Read-only' + assert_select 'option[value=required]', 0 + end + end + + def test_get_permissions_should_disable_hidden_custom_fields + cf1 = IssueCustomField.generate!(:tracker_ids => [1], :visible => true) + cf2 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1]) + cf3 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1, 2]) + + get :permissions, :role_id => 2, :tracker_id => 1 + assert_response :success + assert_template 'permissions' + + assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf1.id}][1]" + assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf3.id}][1]" + + assert_select 'select[name=?][disabled=disabled]', "permissions[#{cf2.id}][1]" do + assert_select 'option[value=][selected=selected]', :text => 'Hidden' + end + end + + def test_get_permissions_with_role_and_tracker_and_all_statuses + WorkflowTransition.delete_all + + get :permissions, :role_id => 1, :tracker_id => 2, :used_statuses_only => '0' + assert_response :success + assert_equal IssueStatus.sorted.all, assigns(:statuses) + end + + def test_post_permissions + WorkflowPermission.delete_all + + post :permissions, :role_id => 1, :tracker_id => 2, :permissions => { + 'assigned_to_id' => {'1' => '', '2' => 'readonly', '3' => ''}, + 'fixed_version_id' => {'1' => 'required', '2' => 'readonly', '3' => ''}, + 'due_date' => {'1' => '', '2' => '', '3' => ''}, + } + assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' + + workflows = WorkflowPermission.all + assert_equal 3, workflows.size + workflows.each do |workflow| + assert_equal 1, workflow.role_id + assert_equal 2, workflow.tracker_id + end + assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'assigned_to_id' && wf.rule == 'readonly'} + assert workflows.detect {|wf| wf.old_status_id == 1 && wf.field_name == 'fixed_version_id' && wf.rule == 'required'} + assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'} + end + + def test_post_permissions_should_clear_permissions + WorkflowPermission.delete_all + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') + wf1 = WorkflowPermission.create!(:role_id => 1, :tracker_id => 3, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') + wf2 = WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') + + post :permissions, :role_id => 1, :tracker_id => 2 + assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' + + workflows = WorkflowPermission.all + assert_equal 2, workflows.size + assert wf1.reload + assert wf2.reload + end + + def test_get_copy + get :copy + assert_response :success + assert_template 'copy' + assert_select 'select[name=source_tracker_id]' do + assert_select 'option[value=1]', :text => 'Bug' + end + assert_select 'select[name=source_role_id]' do + assert_select 'option[value=2]', :text => 'Developer' + end + assert_select 'select[name=?]', 'target_tracker_ids[]' do + assert_select 'option[value=3]', :text => 'Support request' + end + assert_select 'select[name=?]', 'target_role_ids[]' do + assert_select 'option[value=1]', :text => 'Manager' + end + end + + def test_post_copy_one_to_one + source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) + + post :copy, :source_tracker_id => '1', :source_role_id => '2', + :target_tracker_ids => ['3'], :target_role_ids => ['1'] + assert_response 302 + assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) + end + + def test_post_copy_one_to_many + source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) + + post :copy, :source_tracker_id => '1', :source_role_id => '2', + :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] + assert_response 302 + assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 1) + assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) + assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 3) + assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 3) + end + + def test_post_copy_many_to_many + source_t2 = status_transitions(:tracker_id => 2, :role_id => 2) + source_t3 = status_transitions(:tracker_id => 3, :role_id => 2) + + post :copy, :source_tracker_id => 'any', :source_role_id => '2', + :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] + assert_response 302 + assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 1) + assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 1) + assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 3) + assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 3) + end + + def test_post_copy_with_incomplete_source_specification_should_fail + assert_no_difference 'WorkflowRule.count' do + post :copy, + :source_tracker_id => '', :source_role_id => '2', + :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] + assert_response 200 + assert_select 'div.flash.error', :text => 'Please select a source tracker or role' + end + end + + def test_post_copy_with_incomplete_target_specification_should_fail + assert_no_difference 'WorkflowRule.count' do + post :copy, + :source_tracker_id => '1', :source_role_id => '2', + :target_tracker_ids => ['2', '3'] + assert_response 200 + assert_select 'div.flash.error', :text => 'Please select target tracker(s) and role(s)' + end + end + + # Returns an array of status transitions that can be compared + def status_transitions(conditions) + WorkflowTransition. + where(conditions). + order('tracker_id, role_id, old_status_id, new_status_id'). + all. + collect {|w| [w.old_status, w.new_status_id]} + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c0/c066ebfaa53749d111d7ed293c5b04920a16ca0e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c0/c066ebfaa53749d111d7ed293c5b04920a16ca0e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,84 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class AdminController < ApplicationController + layout 'admin' + menu_item :projects, :only => :projects + menu_item :plugins, :only => :plugins + menu_item :info, :only => :info + + before_filter :require_admin + helper :sort + include SortHelper + + def index + @no_configuration_data = Redmine::DefaultData::Loader::no_data? + end + + def projects + @status = params[:status] || 1 + + scope = Project.status(@status).order('lft') + scope = scope.like(params[:name]) if params[:name].present? + @projects = scope.all + + render :action => "projects", :layout => false if request.xhr? + end + + def plugins + @plugins = Redmine::Plugin.all + end + + # Loads the default configuration + # (roles, trackers, statuses, workflow, enumerations) + def default_configuration + if request.post? + begin + Redmine::DefaultData::Loader::load(params[:lang]) + flash[:notice] = l(:notice_default_data_loaded) + rescue Exception => e + flash[:error] = l(:error_can_t_load_default_data, e.message) + end + end + redirect_to admin_path + end + + def test_email + raise_delivery_errors = ActionMailer::Base.raise_delivery_errors + # Force ActionMailer to raise delivery errors so we can catch it + ActionMailer::Base.raise_delivery_errors = true + begin + @test = Mailer.test_email(User.current).deliver + flash[:notice] = l(:notice_email_sent, User.current.mail) + rescue Exception => e + flash[:error] = l(:notice_email_error, Redmine::CodesetUtil.replace_invalid_utf8(e.message)) + end + ActionMailer::Base.raise_delivery_errors = raise_delivery_errors + redirect_to settings_path(:tab => 'notifications') + end + + def info + @db_adapter_name = ActiveRecord::Base.connection.adapter_name + @checklist = [ + [:text_default_administrator_account_changed, User.default_admin_account_changed?], + [:text_file_repository_writable, File.writable?(Attachment.storage_path)], + [:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)], + [:text_rmagick_available, Object.const_defined?(:Magick)], + [:text_convert_available, Redmine::Thumbnail.convert_available?] + ] + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c0/c0a2bb74527f8e6c89c1cd62e5d34e73efd38922.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c0/c0a2bb74527f8e6c89c1cd62e5d34e73efd38922.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,41 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::ApiTest < Redmine::ApiTest::Base + fixtures :users + + def setup + Setting.rest_api_enabled = '1' + end + + def test_api_should_work_with_protect_from_forgery + ActionController::Base.allow_forgery_protection = true + assert_difference('User.count') do + post '/users.xml', { + :user => { + :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', + :mail => 'foo@example.net', :password => 'secret123'} + }, + credentials('admin') + assert_response 201 + end + ensure + ActionController::Base.allow_forgery_protection = false + end +end \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c0/c0a5b910b677dd2973ce573c994f3e7cc596e2ec.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c0/c0a5b910b677dd2973ce573c994f3e7cc596e2ec.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-07-27 06:03:49.133257759 +0900 ++++ b.txt 2013-07-27 06:03:58.791221118 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本記 ++日本娘 + bbbb diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c0/c0c6c069aeab7d8e5dbef5b1fe66cdd0f25891ef.svn-base --- a/.svn/pristine/c0/c0c6c069aeab7d8e5dbef5b1fe66cdd0f25891ef.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -<% if version.completed? %> -

    <%= format_date(version.effective_date) %>

    -<% elsif version.effective_date %> -

    <%= due_date_distance_in_words(version.effective_date) %> (<%= format_date(version.effective_date) %>)

    -<% end %> - -

    <%=h version.description %>

    -<% if version.custom_field_values.any? %> -
      - <% version.custom_field_values.each do |custom_value| %> - <% if custom_value.value.present? %> -
    • <%=h custom_value.custom_field.name %>: <%=h show_value(custom_value) %>
    • - <% end %> - <% end %> -
    -<% end %> - -<% if version.issues_count > 0 %> - <%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %> -

    - <%= link_to(l(:label_x_issues, :count => version.issues_count), - project_issues_path(version.project, :status_id => '*', :fixed_version_id => version, :set_filter => 1)) %> -   - (<%= link_to_if(version.closed_issues_count > 0, l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), - project_issues_path(version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1)) %> - — - <%= link_to_if(version.open_issues_count > 0, l(:label_x_open_issues_abbr, :count => version.open_issues_count), - project_issues_path(version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1)) %>) -

    -<% else %> -

    <%= l(:label_roadmap_no_issues) %>

    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c0/c0d36c34eac79fa2295c3fdedaa72256c805d292.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c0/c0d36c34eac79fa2295c3fdedaa72256c805d292.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,739 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require "digest/sha1" + +class User < Principal + include Redmine::SafeAttributes + + # Different ways of displaying/sorting users + USER_FORMATS = { + :firstname_lastname => { + :string => '#{firstname} #{lastname}', + :order => %w(firstname lastname id), + :setting_order => 1 + }, + :firstname_lastinitial => { + :string => '#{firstname} #{lastname.to_s.chars.first}.', + :order => %w(firstname lastname id), + :setting_order => 2 + }, + :firstname => { + :string => '#{firstname}', + :order => %w(firstname id), + :setting_order => 3 + }, + :lastname_firstname => { + :string => '#{lastname} #{firstname}', + :order => %w(lastname firstname id), + :setting_order => 4 + }, + :lastname_coma_firstname => { + :string => '#{lastname}, #{firstname}', + :order => %w(lastname firstname id), + :setting_order => 5 + }, + :lastname => { + :string => '#{lastname}', + :order => %w(lastname id), + :setting_order => 6 + }, + :username => { + :string => '#{login}', + :order => %w(login id), + :setting_order => 7 + }, + } + + MAIL_NOTIFICATION_OPTIONS = [ + ['all', :label_user_mail_option_all], + ['selected', :label_user_mail_option_selected], + ['only_my_events', :label_user_mail_option_only_my_events], + ['only_assigned', :label_user_mail_option_only_assigned], + ['only_owner', :label_user_mail_option_only_owner], + ['none', :label_user_mail_option_none] + ] + + has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)}, + :after_remove => Proc.new {|user, group| group.user_removed(user)} + has_many :changesets, :dependent => :nullify + has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' + has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" + has_one :api_token, :class_name => 'Token', :conditions => "action='api'" + belongs_to :auth_source + + scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") } + scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } + + acts_as_customizable + + attr_accessor :password, :password_confirmation, :generate_password + attr_accessor :last_before_login_on + # Prevents unauthorized assignments + attr_protected :login, :admin, :password, :password_confirmation, :hashed_password + + LOGIN_LENGTH_LIMIT = 60 + MAIL_LENGTH_LIMIT = 60 + + validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } + validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false + validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false + # Login must contain letters, numbers, underscores only + validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i + validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT + validates_length_of :firstname, :lastname, :maximum => 30 + validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true + validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true + validates_confirmation_of :password, :allow_nil => true + validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true + validate :validate_password_length + + before_create :set_mail_notification + before_save :generate_password_if_needed, :update_hashed_password + before_destroy :remove_references_before_destroy + after_save :update_notified_project_ids + + scope :in_group, lambda {|group| + group_id = group.is_a?(Group) ? group.id : group.to_i + where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) + } + scope :not_in_group, lambda {|group| + group_id = group.is_a?(Group) ? group.id : group.to_i + where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) + } + scope :sorted, lambda { order(*User.fields_for_order_statement)} + + def set_mail_notification + self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? + true + end + + def update_hashed_password + # update hashed_password if password was set + if self.password && self.auth_source_id.blank? + salt_password(password) + end + end + + alias :base_reload :reload + def reload(*args) + @name = nil + @projects_by_role = nil + @membership_by_project_id = nil + @notified_projects_ids = nil + @notified_projects_ids_changed = false + @builtin_role = nil + base_reload(*args) + end + + def mail=(arg) + write_attribute(:mail, arg.to_s.strip) + end + + def identity_url=(url) + if url.blank? + write_attribute(:identity_url, '') + else + begin + write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url)) + rescue OpenIdAuthentication::InvalidOpenId + # Invalid url, don't save + end + end + self.read_attribute(:identity_url) + end + + # Returns the user that matches provided login and password, or nil + def self.try_to_login(login, password, active_only=true) + login = login.to_s + password = password.to_s + + # Make sure no one can sign in with an empty login or password + return nil if login.empty? || password.empty? + user = find_by_login(login) + if user + # user is already in local database + return nil unless user.check_password?(password) + return nil if !user.active? && active_only + else + # user is not yet registered, try to authenticate with available sources + attrs = AuthSource.authenticate(login, password) + if attrs + user = new(attrs) + user.login = login + user.language = Setting.default_language + if user.save + user.reload + logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source + end + end + end + user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active? + user + rescue => text + raise text + end + + # Returns the user who matches the given autologin +key+ or nil + def self.try_to_autologin(key) + user = Token.find_active_user('autologin', key, Setting.autologin.to_i) + if user + user.update_column(:last_login_on, Time.now) + user + end + end + + def self.name_formatter(formatter = nil) + USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname] + end + + # Returns an array of fields names than can be used to make an order statement for users + # according to how user names are displayed + # Examples: + # + # User.fields_for_order_statement => ['users.login', 'users.id'] + # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id'] + def self.fields_for_order_statement(table=nil) + table ||= table_name + name_formatter[:order].map {|field| "#{table}.#{field}"} + end + + # Return user's full name for display + def name(formatter = nil) + f = self.class.name_formatter(formatter) + if formatter + eval('"' + f[:string] + '"') + else + @name ||= eval('"' + f[:string] + '"') + end + end + + def active? + self.status == STATUS_ACTIVE + end + + def registered? + self.status == STATUS_REGISTERED + end + + def locked? + self.status == STATUS_LOCKED + end + + def activate + self.status = STATUS_ACTIVE + end + + def register + self.status = STATUS_REGISTERED + end + + def lock + self.status = STATUS_LOCKED + end + + def activate! + update_attribute(:status, STATUS_ACTIVE) + end + + def register! + update_attribute(:status, STATUS_REGISTERED) + end + + def lock! + update_attribute(:status, STATUS_LOCKED) + end + + # Returns true if +clear_password+ is the correct user's password, otherwise false + def check_password?(clear_password) + if auth_source_id.present? + auth_source.authenticate(self.login, clear_password) + else + User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password + end + end + + # Generates a random salt and computes hashed_password for +clear_password+ + # The hashed password is stored in the following form: SHA1(salt + SHA1(password)) + def salt_password(clear_password) + self.salt = User.generate_salt + self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}") + end + + # Does the backend storage allow this user to change their password? + def change_password_allowed? + return true if auth_source.nil? + return auth_source.allow_password_changes? + end + + def must_change_password? + must_change_passwd? && change_password_allowed? + end + + def generate_password? + generate_password == '1' || generate_password == true + end + + # Generate and set a random password on given length + def random_password(length=40) + chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + chars -= %w(0 O 1 l) + password = '' + length.times {|i| password << chars[SecureRandom.random_number(chars.size)] } + self.password = password + self.password_confirmation = password + self + end + + def pref + self.preference ||= UserPreference.new(:user => self) + end + + def time_zone + @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone]) + end + + def wants_comments_in_reverse_order? + self.pref[:comments_sorting] == 'desc' + end + + # Return user's RSS key (a 40 chars long string), used to access feeds + def rss_key + if rss_token.nil? + create_rss_token(:action => 'feeds') + end + rss_token.value + end + + # Return user's API key (a 40 chars long string), used to access the API + def api_key + if api_token.nil? + create_api_token(:action => 'api') + end + api_token.value + end + + # Return an array of project ids for which the user has explicitly turned mail notifications on + def notified_projects_ids + @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id) + end + + def notified_project_ids=(ids) + @notified_projects_ids_changed = true + @notified_projects_ids = ids + end + + # Updates per project notifications (after_save callback) + def update_notified_project_ids + if @notified_projects_ids_changed + ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : []) + members.update_all(:mail_notification => false) + members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any? + end + end + private :update_notified_project_ids + + def valid_notification_options + self.class.valid_notification_options(self) + end + + # Only users that belong to more than 1 project can select projects for which they are notified + def self.valid_notification_options(user=nil) + # Note that @user.membership.size would fail since AR ignores + # :include association option when doing a count + if user.nil? || user.memberships.length < 1 + MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'} + else + MAIL_NOTIFICATION_OPTIONS + end + end + + # Find a user account by matching the exact login and then a case-insensitive + # version. Exact matches will be given priority. + def self.find_by_login(login) + if login.present? + login = login.to_s + # First look for an exact match + user = where(:login => login).all.detect {|u| u.login == login} + unless user + # Fail over to case-insensitive if none was found + user = where("LOWER(login) = ?", login.downcase).first + end + user + end + end + + def self.find_by_rss_key(key) + Token.find_active_user('feeds', key) + end + + def self.find_by_api_key(key) + Token.find_active_user('api', key) + end + + # Makes find_by_mail case-insensitive + def self.find_by_mail(mail) + where("LOWER(mail) = ?", mail.to_s.downcase).first + end + + # Returns true if the default admin account can no longer be used + def self.default_admin_account_changed? + !User.active.find_by_login("admin").try(:check_password?, "admin") + end + + def to_s + name + end + + CSS_CLASS_BY_STATUS = { + STATUS_ANONYMOUS => 'anon', + STATUS_ACTIVE => 'active', + STATUS_REGISTERED => 'registered', + STATUS_LOCKED => 'locked' + } + + def css_classes + "user #{CSS_CLASS_BY_STATUS[status]}" + end + + # Returns the current day according to user's time zone + def today + if time_zone.nil? + Date.today + else + Time.now.in_time_zone(time_zone).to_date + end + end + + # Returns the day of +time+ according to user's time zone + def time_to_date(time) + if time_zone.nil? + time.to_date + else + time.in_time_zone(time_zone).to_date + end + end + + def logged? + true + end + + def anonymous? + !logged? + end + + # Returns user's membership for the given project + # or nil if the user is not a member of project + def membership(project) + project_id = project.is_a?(Project) ? project.id : project + + @membership_by_project_id ||= Hash.new {|h, project_id| + h[project_id] = memberships.where(:project_id => project_id).first + } + @membership_by_project_id[project_id] + end + + # Returns the user's bult-in role + def builtin_role + @builtin_role ||= Role.non_member + end + + # Return user's roles for project + def roles_for_project(project) + roles = [] + # No role on archived projects + return roles if project.nil? || project.archived? + if membership = membership(project) + roles = membership.roles + else + roles << builtin_role + end + roles + end + + # Return true if the user is a member of project + def member_of?(project) + projects.to_a.include?(project) + end + + # Returns a hash of user's projects grouped by roles + def projects_by_role + return @projects_by_role if @projects_by_role + + @projects_by_role = Hash.new([]) + memberships.each do |membership| + if membership.project + membership.roles.each do |role| + @projects_by_role[role] = [] unless @projects_by_role.key?(role) + @projects_by_role[role] << membership.project + end + end + end + @projects_by_role.each do |role, projects| + projects.uniq! + end + + @projects_by_role + end + + # Returns true if user is arg or belongs to arg + def is_or_belongs_to?(arg) + if arg.is_a?(User) + self == arg + elsif arg.is_a?(Group) + arg.users.include?(self) + else + false + end + end + + # Return true if the user is allowed to do the specified action on a specific context + # Action can be: + # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') + # * a permission Symbol (eg. :edit_project) + # Context can be: + # * a project : returns true if user is allowed to do the specified action on this project + # * an array of projects : returns true if user is allowed on every project + # * nil with options[:global] set : check if user has at least one role allowed for this action, + # or falls back to Non Member / Anonymous permissions depending if the user is logged + def allowed_to?(action, context, options={}, &block) + if context && context.is_a?(Project) + return false unless context.allows_to?(action) + # Admin users are authorized for anything else + return true if admin? + + roles = roles_for_project(context) + return false unless roles + roles.any? {|role| + (context.is_public? || role.member?) && + role.allowed_to?(action) && + (block_given? ? yield(role, self) : true) + } + elsif context && context.is_a?(Array) + if context.empty? + false + else + # Authorize if user is authorized on every element of the array + context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&) + end + elsif options[:global] + # Admin users are always authorized + return true if admin? + + # authorize if user has at least one role that has this permission + roles = memberships.collect {|m| m.roles}.flatten.uniq + roles << (self.logged? ? Role.non_member : Role.anonymous) + roles.any? {|role| + role.allowed_to?(action) && + (block_given? ? yield(role, self) : true) + } + else + false + end + end + + # Is the user allowed to do the specified action on any project? + # See allowed_to? for the actions and valid options. + def allowed_to_globally?(action, options, &block) + allowed_to?(action, nil, options.reverse_merge(:global => true), &block) + end + + # Returns true if the user is allowed to delete the user's own account + def own_account_deletable? + Setting.unsubscribe? && + (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?) + end + + safe_attributes 'login', + 'firstname', + 'lastname', + 'mail', + 'mail_notification', + 'notified_project_ids', + 'language', + 'custom_field_values', + 'custom_fields', + 'identity_url' + + safe_attributes 'status', + 'auth_source_id', + 'generate_password', + 'must_change_passwd', + :if => lambda {|user, current_user| current_user.admin?} + + safe_attributes 'group_ids', + :if => lambda {|user, current_user| current_user.admin? && !user.new_record?} + + # Utility method to help check if a user should be notified about an + # event. + # + # TODO: only supports Issue events currently + def notify_about?(object) + if mail_notification == 'all' + true + elsif mail_notification.blank? || mail_notification == 'none' + false + else + case object + when Issue + case mail_notification + when 'selected', 'only_my_events' + # user receives notifications for created/assigned issues on unselected projects + object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_assigned' + is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_owner' + object.author == self + end + when News + # always send to project members except when mail_notification is set to 'none' + true + end + end + end + + def self.current=(user) + Thread.current[:current_user] = user + end + + def self.current + Thread.current[:current_user] ||= User.anonymous + end + + # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only + # one anonymous user per database. + def self.anonymous + anonymous_user = AnonymousUser.first + if anonymous_user.nil? + anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0) + raise 'Unable to create the anonymous user.' if anonymous_user.new_record? + end + anonymous_user + end + + # Salts all existing unsalted passwords + # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password)) + # This method is used in the SaltPasswords migration and is to be kept as is + def self.salt_unsalted_passwords! + transaction do + User.where("salt IS NULL OR salt = ''").find_each do |user| + next if user.hashed_password.blank? + salt = User.generate_salt + hashed_password = User.hash_password("#{salt}#{user.hashed_password}") + User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password) + end + end + end + + protected + + def validate_password_length + return if password.blank? && generate_password? + # Password length validation based on setting + if !password.nil? && password.size < Setting.password_min_length.to_i + errors.add(:password, :too_short, :count => Setting.password_min_length.to_i) + end + end + + private + + def generate_password_if_needed + if generate_password? && auth_source.nil? + length = [Setting.password_min_length.to_i + 2, 10].max + random_password(length) + end + end + + # Removes references that are not handled by associations + # Things that are not deleted are reassociated with the anonymous user + def remove_references_before_destroy + return if self.id.nil? + + substitute = User.anonymous + Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id] + Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] + JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s] + JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s] + Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + # Remove private queries and keep public ones + ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE] + ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] + TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] + Token.delete_all ['user_id = ?', id] + Watcher.delete_all ['user_id = ?', id] + WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + end + + # Return password digest + def self.hash_password(clear_password) + Digest::SHA1.hexdigest(clear_password || "") + end + + # Returns a 128bits random salt as a hex string (32 chars long) + def self.generate_salt + Redmine::Utils.random_hex(16) + end + +end + +class AnonymousUser < User + validate :validate_anonymous_uniqueness, :on => :create + + def validate_anonymous_uniqueness + # There should be only one AnonymousUser in the database + errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists? + end + + def available_custom_fields + [] + end + + # Overrides a few properties + def logged?; false end + def admin; false end + def name(*args); I18n.t(:label_user_anonymous) end + def mail; nil end + def time_zone; nil end + def rss_key; nil end + + def pref + UserPreference.new(:user => self) + end + + # Returns the user's bult-in role + def builtin_role + @builtin_role ||= Role.anonymous + end + + def membership(*args) + nil + end + + def member_of?(*args) + false + end + + # Anonymous user can not be destroyed + def destroy + false + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c0/c0ff820f520e8207d7f4feecd9bcb5cb4d1da9b1.svn-base --- a/.svn/pristine/c0/c0ff820f520e8207d7f4feecd9bcb5cb4d1da9b1.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -

    <%= link_to(h("#{issue.tracker.name} ##{issue.id}: #{issue.subject}"), issue_url) %>

    - -
      -
    • <%=l(:field_author)%>: <%=h issue.author %>
    • -
    • <%=l(:field_status)%>: <%=h issue.status %>
    • -
    • <%=l(:field_priority)%>: <%=h issue.priority %>
    • -
    • <%=l(:field_assigned_to)%>: <%=h issue.assigned_to %>
    • -
    • <%=l(:field_category)%>: <%=h issue.category %>
    • -
    • <%=l(:field_fixed_version)%>: <%=h issue.fixed_version %>
    • -<% issue.custom_field_values.each do |c| %> -
    • <%=h c.custom_field.name %>: <%=h show_value(c) %>
    • -<% end %> -
    - -<%= textilizable(issue, :description, :only_path => false) %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c1/c10e9a1142bb5d5518887fd1a88ba99ec4614876.svn-base --- a/.svn/pristine/c1/c10e9a1142bb5d5518887fd1a88ba99ec4614876.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -

    -<%= label_tag "user_mail_notification", l(:description_user_mail_notification), :class => "hidden-for-sighted" %> -<%= select_tag( - 'user[mail_notification]', - options_for_select( - user_mail_notification_options(@user), @user.mail_notification), - :onchange => 'if (this.value == "selected") {$("#notified-projects").show();} else {$("#notified-projects").hide();}' - ) %> -

    -<%= content_tag 'div', :id => 'notified-projects', :style => (@user.mail_notification == 'selected' ? '' : 'display:none;') do %> - <%= render_project_nested_lists(@user.projects) do |project| - content_tag('label', - check_box_tag( - 'notified_project_ids[]', - project.id, - @user.notified_projects_ids.include?(project.id) - ) + ' ' + h(project.name) - ) - end %> -

    <%= l(:text_user_mail_option) %>

    -<% end %> -

    - -

    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c1/c125a6277f7dc560bacace655471aa7685efdb87.svn-base --- a/.svn/pristine/c1/c125a6277f7dc560bacace655471aa7685efdb87.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -
    -<%= link_to l(:label_group_new), new_group_path, :class => 'icon icon-add' %> -
    - -

    <%= l(:label_group_plural) %>

    - -<% if @groups.any? %> - - - - - - - -<% @groups.each do |group| %> - - - - - -<% end %> -
    <%=l(:label_group)%><%=l(:label_user_plural)%>
    <%= link_to h(group), edit_group_path(group) %><%= group.users.size %><%= delete_link group %>
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c1/c1781c3758d8dc14f56517d75f2f59e2fba8ccc3.svn-base --- a/.svn/pristine/c1/c1781c3758d8dc14f56517d75f2f59e2fba8ccc3.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingMailHandlerTest < ActionController::IntegrationTest - def test_mail_handler - assert_routing( - { :method => "post", :path => "/mail_handler" }, - { :controller => 'mail_handler', :action => 'index' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c1/c17c223645b98b5a67fab194c2d8d177cf3cf7c5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c1/c17c223645b98b5a67fab194c2d8d177cf3cf7c5.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,18 @@ +api.wiki_page do + api.title @page.title + if @page.parent + api.parent :title => @page.parent.title + end + api.text @content.text + api.version @content.version + api.author(:id => @content.author_id, :name => @content.author.name) + api.comments @content.comments + api.created_on @page.created_on + api.updated_on @content.updated_on + + api.array :attachments do + @page.attachments.each do |attachment| + render_api_attachment(attachment, api) + end + end if include_in_api_response?('attachments') +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c1/c1f9e2096ed8b684022346a547814bc032420b8c.svn-base --- a/.svn/pristine/c1/c1f9e2096ed8b684022346a547814bc032420b8c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,468 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class MailHandler < ActionMailer::Base - include ActionView::Helpers::SanitizeHelper - include Redmine::I18n - - class UnauthorizedAction < StandardError; end - class MissingInformation < StandardError; end - - attr_reader :email, :user - - def self.receive(email, options={}) - @@handler_options = options.dup - - @@handler_options[:issue] ||= {} - - if @@handler_options[:allow_override].is_a?(String) - @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) - end - @@handler_options[:allow_override] ||= [] - # Project needs to be overridable if not specified - @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project) - # Status overridable by default - @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) - - @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false) - - email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding) - super(email) - end - - def logger - Rails.logger - end - - cattr_accessor :ignored_emails_headers - @@ignored_emails_headers = { - 'X-Auto-Response-Suppress' => 'oof', - 'Auto-Submitted' => /^auto-/ - } - - # Processes incoming emails - # Returns the created object (eg. an issue, a message) or false - def receive(email) - @email = email - sender_email = email.from.to_a.first.to_s.strip - # Ignore emails received from the application emission address to avoid hell cycles - if sender_email.downcase == Setting.mail_from.to_s.strip.downcase - if logger && logger.info - logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" - end - return false - end - # Ignore auto generated emails - self.class.ignored_emails_headers.each do |key, ignored_value| - value = email.header[key] - if value - value = value.to_s.downcase - if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value - if logger && logger.info - logger.info "MailHandler: ignoring email with #{key}:#{value} header" - end - return false - end - end - end - @user = User.find_by_mail(sender_email) if sender_email.present? - if @user && !@user.active? - if logger && logger.info - logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" - end - return false - end - if @user.nil? - # Email was submitted by an unknown user - case @@handler_options[:unknown_user] - when 'accept' - @user = User.anonymous - when 'create' - @user = create_user_from_email - if @user - if logger && logger.info - logger.info "MailHandler: [#{@user.login}] account created" - end - Mailer.account_information(@user, @user.password).deliver - else - if logger && logger.error - logger.error "MailHandler: could not create account for [#{sender_email}]" - end - return false - end - else - # Default behaviour, emails from unknown users are ignored - if logger && logger.info - logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" - end - return false - end - end - User.current = @user - dispatch - end - - private - - MESSAGE_ID_RE = %r{^ e - # TODO: send a email to the user - logger.error e.message if logger - false - rescue MissingInformation => e - logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger - false - rescue UnauthorizedAction => e - logger.error "MailHandler: unauthorized attempt from #{user}" if logger - false - end - - def dispatch_to_default - receive_issue - end - - # Creates a new issue - def receive_issue - project = target_project - # check permission - unless @@handler_options[:no_permission_check] - raise UnauthorizedAction unless user.allowed_to?(:add_issues, project) - end - - issue = Issue.new(:author => user, :project => project) - issue.safe_attributes = issue_attributes_from_keywords(issue) - issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} - issue.subject = cleaned_up_subject - if issue.subject.blank? - issue.subject = '(no subject)' - end - issue.description = cleaned_up_text_body - - # add To and Cc as watchers before saving so the watchers can reply to Redmine - add_watchers(issue) - issue.save! - add_attachments(issue) - logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info - issue - end - - # Adds a note to an existing issue - def receive_issue_reply(issue_id, from_journal=nil) - issue = Issue.find_by_id(issue_id) - return unless issue - # check permission - unless @@handler_options[:no_permission_check] - unless user.allowed_to?(:add_issue_notes, issue.project) || - user.allowed_to?(:edit_issues, issue.project) - raise UnauthorizedAction - end - end - - # ignore CLI-supplied defaults for new issues - @@handler_options[:issue].clear - - journal = issue.init_journal(user) - if from_journal && from_journal.private_notes? - # If the received email was a reply to a private note, make the added note private - issue.private_notes = true - end - issue.safe_attributes = issue_attributes_from_keywords(issue) - issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} - journal.notes = cleaned_up_text_body - add_attachments(issue) - issue.save! - if logger && logger.info - logger.info "MailHandler: issue ##{issue.id} updated by #{user}" - end - journal - end - - # Reply will be added to the issue - def receive_journal_reply(journal_id) - journal = Journal.find_by_id(journal_id) - if journal && journal.journalized_type == 'Issue' - receive_issue_reply(journal.journalized_id, journal) - end - end - - # Receives a reply to a forum message - def receive_message_reply(message_id) - message = Message.find_by_id(message_id) - if message - message = message.root - - unless @@handler_options[:no_permission_check] - raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project) - end - - if !message.locked? - reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip, - :content => cleaned_up_text_body) - reply.author = user - reply.board = message.board - message.children << reply - add_attachments(reply) - reply - else - if logger && logger.info - logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" - end - end - end - end - - def add_attachments(obj) - if email.attachments && email.attachments.any? - email.attachments.each do |attachment| - obj.attachments << Attachment.create(:container => obj, - :file => attachment.decoded, - :filename => attachment.filename, - :author => user, - :content_type => attachment.mime_type) - end - end - end - - # Adds To and Cc as watchers of the given object if the sender has the - # appropriate permission - def add_watchers(obj) - if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project) - addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase} - unless addresses.empty? - watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses]) - watchers.each {|w| obj.add_watcher(w)} - end - end - end - - def get_keyword(attr, options={}) - @keywords ||= {} - if @keywords.has_key?(attr) - @keywords[attr] - else - @keywords[attr] = begin - if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && - (v = extract_keyword!(plain_text_body, attr, options[:format])) - v - elsif !@@handler_options[:issue][attr].blank? - @@handler_options[:issue][attr] - end - end - end - end - - # Destructively extracts the value for +attr+ in +text+ - # Returns nil if no matching keyword found - def extract_keyword!(text, attr, format=nil) - keys = [attr.to_s.humanize] - if attr.is_a?(Symbol) - if user && user.language.present? - keys << l("field_#{attr}", :default => '', :locale => user.language) - end - if Setting.default_language.present? - keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) - end - end - keys.reject! {|k| k.blank?} - keys.collect! {|k| Regexp.escape(k)} - format ||= '.+' - keyword = nil - regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i - if m = text.match(regexp) - keyword = m[2].strip - text.gsub!(regexp, '') - end - keyword - end - - def target_project - # TODO: other ways to specify project: - # * parse the email To field - # * specific project (eg. Setting.mail_handler_target_project) - target = Project.find_by_identifier(get_keyword(:project)) - raise MissingInformation.new('Unable to determine target project') if target.nil? - target - end - - # Returns a Hash of issue attributes extracted from keywords in the email body - def issue_attributes_from_keywords(issue) - assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue) - - attrs = { - 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id), - 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id), - 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id), - 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id), - 'assigned_to_id' => assigned_to.try(:id), - 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && - issue.project.shared_versions.named(k).first.try(:id), - 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), - 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), - 'estimated_hours' => get_keyword(:estimated_hours, :override => true), - 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0') - }.delete_if {|k, v| v.blank? } - - if issue.new_record? && attrs['tracker_id'].nil? - attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id) - end - - attrs - end - - # Returns a Hash of issue custom field values extracted from keywords in the email body - def custom_field_values_from_keywords(customized) - customized.custom_field_values.inject({}) do |h, v| - if keyword = get_keyword(v.custom_field.name, :override => true) - h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized) - end - h - end - end - - # Returns the text/plain part of the email - # If not found (eg. HTML-only email), returns the body with tags removed - def plain_text_body - return @plain_text_body unless @plain_text_body.nil? - - part = email.text_part || email.html_part || email - @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset) - - # strip html tags and remove doctype directive - @plain_text_body = strip_tags(@plain_text_body.strip) - @plain_text_body.sub! %r{^$/) - addr, name = m[2], m[1] - end - if addr.present? - user = self.class.new_user_from_attributes(addr, name) - if user.save - user - else - logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger - nil - end - else - logger.error "MailHandler: failed to create User: no FROM address found" if logger - nil - end - end - - # Removes the email body of text after the truncation configurations. - def cleanup_body(body) - delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)} - unless delimiters.empty? - regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE) - body = body.gsub(regex, '') - end - body.strip - end - - def find_assignee_from_keyword(keyword, issue) - keyword = keyword.to_s.downcase - assignable = issue.assignable_users - assignee = nil - assignee ||= assignable.detect {|a| - a.mail.to_s.downcase == keyword || - a.login.to_s.downcase == keyword - } - if assignee.nil? && keyword.match(/ /) - firstname, lastname = *(keyword.split) # "First Last Throwaway" - assignee ||= assignable.detect {|a| - a.is_a?(User) && a.firstname.to_s.downcase == firstname && - a.lastname.to_s.downcase == lastname - } - end - if assignee.nil? - assignee ||= assignable.detect {|a| a.name.downcase == keyword} - end - assignee - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c2/c20e62b47c4a114de7fa73898ffad99a1fd633fb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c2/c20e62b47c4a114de7fa73898ffad99a1fd633fb.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,124 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class MemberTest < ActiveSupport::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :groups_users, + :watchers, + :journals, :journal_details, + :messages, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, + :boards + + include Redmine::I18n + + def setup + @jsmith = Member.find(1) + end + + def test_create + member = Member.new(:project_id => 1, :user_id => 4, :role_ids => [1, 2]) + assert member.save + member.reload + + assert_equal 2, member.roles.size + assert_equal Role.find(1), member.roles.sort.first + end + + def test_update + assert_equal "eCookbook", @jsmith.project.name + assert_equal "Manager", @jsmith.roles.first.name + assert_equal "jsmith", @jsmith.user.login + + @jsmith.mail_notification = !@jsmith.mail_notification + assert @jsmith.save + end + + def test_update_roles + assert_equal 1, @jsmith.roles.size + @jsmith.role_ids = [1, 2] + assert @jsmith.save + assert_equal 2, @jsmith.reload.roles.size + end + + def test_validate + member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [2]) + # same use can't have more than one membership for a project + assert !member.save + + # must have one role at least + user = User.new(:firstname => "new1", :lastname => "user1", :mail => "test_validate@somenet.foo") + user.login = "test_validate" + user.password, user.password_confirmation = "password", "password" + assert user.save + + set_language_if_valid 'fr' + member = Member.new(:project_id => 1, :user_id => user.id, :role_ids => []) + assert !member.save + assert_include I18n.translate('activerecord.errors.messages.empty'), member.errors[:role] + str = "R\xc3\xb4le doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + assert_equal str, [member.errors.full_messages].flatten.join + end + + def test_validate_member_role + user = User.new(:firstname => "new1", :lastname => "user1", :mail => "test_validate@somenet.foo") + user.login = "test_validate_member_role" + user.password, user.password_confirmation = "password", "password" + assert user.save + member = Member.new(:project_id => 1, :user_id => user.id, :role_ids => [5]) + assert !member.save + end + + def test_destroy + category1 = IssueCategory.find(1) + assert_equal @jsmith.user.id, category1.assigned_to_id + assert_difference 'Member.count', -1 do + assert_difference 'MemberRole.count', -1 do + @jsmith.destroy + end + end + assert_raise(ActiveRecord::RecordNotFound) { Member.find(@jsmith.id) } + category1.reload + assert_nil category1.assigned_to_id + end + + def test_sort_without_roles + a = Member.new(:roles => [Role.first]) + b = Member.new + + assert_equal -1, a <=> b + assert_equal 1, b <=> a + end + + def test_sort_without_principal + role = Role.first + a = Member.new(:roles => [role], :principal => User.first) + b = Member.new(:roles => [role]) + + assert_equal -1, a <=> b + assert_equal 1, b <=> a + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c2/c2cb8c7008cd50c99f1e1bcec32385aa815b5b9d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c2/c2cb8c7008cd50c99f1e1bcec32385aa815b5b9d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,244 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Pagination + class Paginator + attr_reader :item_count, :per_page, :page, :page_param + + def initialize(*args) + if args.first.is_a?(ActionController::Base) + args.shift + ActiveSupport::Deprecation.warn "Paginator no longer takes a controller instance as the first argument. Remove it from #new arguments." + end + item_count, per_page, page, page_param = *args + + @item_count = item_count + @per_page = per_page + page = (page || 1).to_i + if page < 1 + page = 1 + end + @page = page + @page_param = page_param || :page + end + + def offset + (page - 1) * per_page + end + + def first_page + if item_count > 0 + 1 + end + end + + def previous_page + if page > 1 + page - 1 + end + end + + def next_page + if last_item < item_count + page + 1 + end + end + + def last_page + if item_count > 0 + (item_count - 1) / per_page + 1 + end + end + + def first_item + item_count == 0 ? 0 : (offset + 1) + end + + def last_item + l = first_item + per_page - 1 + l > item_count ? item_count : l + end + + def linked_pages + pages = [] + if item_count > 0 + pages += [first_page, page, last_page] + pages += ((page-2)..(page+2)).to_a.select {|p| p > first_page && p < last_page} + end + pages = pages.compact.uniq.sort + if pages.size > 1 + pages + else + [] + end + end + + def items_per_page + ActiveSupport::Deprecation.warn "Paginator#items_per_page will be removed. Use #per_page instead." + per_page + end + + def current + ActiveSupport::Deprecation.warn "Paginator#current will be removed. Use .offset instead of .current.offset." + self + end + end + + # Paginates the given scope or model. Returns a Paginator instance and + # the collection of objects for the current page. + # + # Options: + # :parameter name of the page parameter + # + # Examples: + # @user_pages, @users = paginate User.where(:status => 1) + # + def paginate(scope, options={}) + options = options.dup + finder_options = options.extract!( + :conditions, + :order, + :joins, + :include, + :select + ) + if scope.is_a?(Symbol) || finder_options.values.compact.any? + return deprecated_paginate(scope, finder_options, options) + end + + paginator = paginator(scope.count, options) + collection = scope.limit(paginator.per_page).offset(paginator.offset).to_a + + return paginator, collection + end + + def deprecated_paginate(arg, finder_options, options={}) + ActiveSupport::Deprecation.warn "#paginate with a Symbol and/or find options is depreceted and will be removed. Use a scope instead." + klass = arg.is_a?(Symbol) ? arg.to_s.classify.constantize : arg + scope = klass.scoped(finder_options) + paginate(scope, options) + end + + def paginator(item_count, options={}) + options.assert_valid_keys :parameter, :per_page + + page_param = options[:parameter] || :page + page = (params[page_param] || 1).to_i + per_page = options[:per_page] || per_page_option + Paginator.new(item_count, per_page, page, page_param) + end + + module Helper + include Redmine::I18n + + # Renders the pagination links for the given paginator. + # + # Options: + # :per_page_links if set to false, the "Per page" links are not rendered + # + def pagination_links_full(*args) + pagination_links_each(*args) do |text, parameters, options| + if block_given? + yield text, parameters, options + else + link_to text, params.merge(parameters), options + end + end + end + + # Yields the given block with the text and parameters + # for each pagination link and returns a string that represents the links + def pagination_links_each(paginator, count=nil, options={}, &block) + options.assert_valid_keys :per_page_links + + per_page_links = options.delete(:per_page_links) + per_page_links = false if count.nil? + page_param = paginator.page_param + + html = '' + if paginator.previous_page + # \xc2\xab(utf-8) = « + text = "\xc2\xab " + l(:label_previous) + html << yield(text, {page_param => paginator.previous_page}, :class => 'previous') + ' ' + end + + previous = nil + paginator.linked_pages.each do |page| + if previous && previous != page - 1 + html << content_tag('span', '...', :class => 'spacer') + ' ' + end + if page == paginator.page + html << content_tag('span', page.to_s, :class => 'current page') + else + html << yield(page.to_s, {page_param => page}, :class => 'page') + end + html << ' ' + previous = page + end + + if paginator.next_page + # \xc2\xbb(utf-8) = » + text = l(:label_next) + " \xc2\xbb" + html << yield(text, {page_param => paginator.next_page}, :class => 'next') + ' ' + end + + html << content_tag('span', "(#{paginator.first_item}-#{paginator.last_item}/#{paginator.item_count})", :class => 'items') + ' ' + + if per_page_links != false && links = per_page_links(paginator, &block) + html << content_tag('span', links.to_s, :class => 'per-page') + end + + html.html_safe + end + + # Renders the "Per page" links. + def per_page_links(paginator, &block) + values = per_page_options(paginator.per_page, paginator.item_count) + if values.any? + links = values.collect do |n| + if n == paginator.per_page + content_tag('span', n.to_s) + else + yield(n, :per_page => n, paginator.page_param => nil) + end + end + l(:label_display_per_page, links.join(', ')).html_safe + end + end + + def per_page_options(selected=nil, item_count=nil) + options = Setting.per_page_options_array + if item_count && options.any? + if item_count > options.first + max = options.detect {|value| value >= item_count} || item_count + else + max = item_count + end + options = options.select {|value| value <= max || value == selected} + end + if options.empty? || (options.size == 1 && options.first == selected) + [] + else + options + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c2/c2ce8fbb5ce0bf6a28a6dfab6f8aeaecba66e770.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c2/c2ce8fbb5ce0bf6a28a6dfab6f8aeaecba66e770.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,47 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class DocumentCategoryTest < ActiveSupport::TestCase + fixtures :enumerations, :documents, :issues + + def test_should_be_an_enumeration + assert DocumentCategory.ancestors.include?(Enumeration) + end + + def test_objects_count + assert_equal 2, DocumentCategory.find_by_name("Uncategorized").objects_count + assert_equal 0, DocumentCategory.find_by_name("User documentation").objects_count + end + + def test_option_name + assert_equal :enumeration_doc_categories, DocumentCategory.new.option_name + end + + def test_default + assert_nil DocumentCategory.where(:is_default => true).first + e = Enumeration.find_by_name('Technical documentation') + e.update_attributes(:is_default => true) + assert_equal 3, DocumentCategory.default.id + end + + def test_force_default + assert_nil DocumentCategory.where(:is_default => true).first + assert_equal 1, DocumentCategory.default.id + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c2/c2ec64c5d6c36464ee48541ac73c783fbbd73aa9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c2/c2ec64c5d6c36464ee48541ac73c783fbbd73aa9.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1104 @@ +hr: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%m/%d/%Y" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Nedjelja, Ponedjeljak, Utorak, Srijeda, ÄŒetvrtak, Petak, Subota] + abbr_day_names: [Ned, Pon, Uto, Sri, ÄŒet, Pet, Sub] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, SijeÄanj, VeljaÄa, Ožujak, Travanj, Svibanj, Lipanj, Srpanj, Kolovoz, Rujan, Listopad, Studeni, Prosinac] + abbr_month_names: [~, Sij, Velj, Ožu, Tra, Svi, Lip, Srp, Kol, Ruj, Lis, Stu, Pro] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%m/%d/%Y %I:%M %p" + time: "%I:%M %p" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "pola minute" + less_than_x_seconds: + one: "manje od sekunde" + other: "manje od %{count} sekundi" + x_seconds: + one: "1 sekunda" + other: "%{count} sekundi" + less_than_x_minutes: + one: "manje od minute" + other: "manje od %{count} minuta" + x_minutes: + one: "1 minuta" + other: "%{count} minuta" + about_x_hours: + one: "oko sat vremena" + other: "oko %{count} sati" + x_hours: + one: "1 sata" + other: "%{count} sati" + x_days: + one: "1 dan" + other: "%{count} dana" + about_x_months: + one: "oko 1 mjesec" + other: "oko %{count} mjeseci" + x_months: + one: "mjesec" + other: "%{count} mjeseci" + about_x_years: + one: "1 godina" + other: "%{count} godina" + over_x_years: + one: "preko 1 godine" + other: "preko %{count} godina" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "i" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "nije ukljuceno u listu" + exclusion: "je rezervirano" + invalid: "nije ispravno" + confirmation: "ne odgovara za potvrdu" + accepted: "mora biti prihvaćen" + empty: "ne može biti prazno" + blank: "ne može biti razmaka" + too_long: "je predug (maximum is %{count} characters)" + too_short: "je prekratak (minimum is %{count} characters)" + wrong_length: "je pogreÅ¡ne dužine (should be %{count} characters)" + taken: "već je zauzeto" + not_a_number: "nije broj" + not_a_date: "nije ispravan datum" + greater_than: "mora biti veći od %{count}" + greater_than_or_equal_to: "mora biti veći ili jednak %{count}" + equal_to: "mora biti jednak %{count}" + less_than: "mora biti manji od %{count}" + less_than_or_equal_to: "mora bit manji ili jednak%{count}" + odd: "mora biti neparan" + even: "mora biti paran" + greater_than_start_date: "mora biti veci nego pocetni datum" + not_same_project: "ne pripada istom projektu" + circular_dependency: "Ovaj relacija stvara kružnu ovisnost" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Molimo odaberite + + general_text_No: 'Ne' + general_text_Yes: 'Da' + general_text_no: 'ne' + general_text_yes: 'da' + general_lang_name: 'Hrvatski' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_updated: VaÅ¡ profil je uspjeÅ¡no promijenjen. + notice_account_invalid_creditentials: Neispravno korisniÄko ime ili zaporka. + notice_account_password_updated: Zaporka je uspjeÅ¡no promijenjena. + notice_account_wrong_password: PogreÅ¡na zaporka + notice_account_register_done: Racun je uspjeÅ¡no napravljen. Da biste aktivirali svoj raÄun, kliknite na link koji vam je poslan na e-mail. + notice_account_unknown_email: Nepoznati korisnik. + notice_can_t_change_password: Ovaj raÄun koristi eksterni izvor prijavljivanja. Nemoguće je promijeniti zaporku. + notice_account_lost_email_sent: E-mail s uputama kako bi odabrali novu zaporku je poslan na na vaÅ¡u e-mail adresu. + notice_account_activated: VaÅ¡ racun je aktiviran. Možete se prijaviti. + notice_successful_create: UspjeÅ¡no napravljeno. + notice_successful_update: UspjeÅ¡na promjena. + notice_successful_delete: UspjeÅ¡no brisanje. + notice_successful_connection: UspjeÅ¡na veza. + notice_file_not_found: Stranica kojoj ste pokuÅ¡ali pristupiti ne postoji ili je uklonjena. + notice_locking_conflict: Podataci su ažurirani od strane drugog korisnika. + notice_not_authorized: Niste ovlaÅ¡teni za pristup ovoj stranici. + notice_email_sent: E-mail je poslan %{value}" + notice_email_error: Dogodila se pogreÅ¡ka tijekom slanja E-maila (%{value})" + notice_feeds_access_key_reseted: VaÅ¡ Atom pristup je resetovan. + notice_api_access_key_reseted: VaÅ¡ API pristup je resetovan. + notice_failed_to_save_issues: "Neuspjelo spremanje %{count} predmeta na %{total} odabrane: %{ids}." + notice_no_issue_selected: "Niti jedan predmet nije odabran! Molim, odaberite predmete koje želite urediti." + notice_account_pending: "VaÅ¡ korisnicki raÄun je otvoren, Äeka odobrenje administratora." + notice_default_data_loaded: Konfiguracija je uspjeÅ¡no uÄitana. + notice_unable_delete_version: Nije moguće izbrisati verziju. + notice_issue_done_ratios_updated: Issue done ratios updated. + + error_can_t_load_default_data: "Zadanu konfiguracija nije uÄitana: %{value}" + error_scm_not_found: "Unos i/ili revizija nije pronaÄ‘en." + error_scm_command_failed: "Dogodila se pogreÅ¡ka prilikom pokuÅ¡aja pristupa: %{value}" + error_scm_annotate: "Ne postoji ili ne može biti obilježen." + error_issue_not_found_in_project: 'Nije pronaÄ‘en ili ne pripada u ovaj projekt' + error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' + error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' + error_can_not_archive_project: This project can not be archived + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + + warning_attachments_not_saved: "%{count} Datoteka/e nije mogla biti spremljena." + + mail_subject_lost_password: "VaÅ¡a %{value} zaporka" + mail_body_lost_password: 'Kako biste promijenili VaÅ¡u zaporku slijedite poveznicu:' + mail_subject_register: "Aktivacija korisniÄog raÄuna %{value}" + mail_body_register: 'Da biste aktivirali svoj raÄun, kliknite na sljedeci link:' + mail_body_account_information_external: "Možete koristiti vaÅ¡ raÄun %{value} za prijavu." + mail_body_account_information: VaÅ¡i korisniÄki podaci + mail_subject_account_activation_request: "%{value} predmet za aktivaciju korisniÄkog raÄuna" + mail_body_account_activation_request: "Novi korisnik (%{value}) je registriran. Njegov korisniÄki raÄun Äeka vaÅ¡e odobrenje:" + mail_subject_reminder: "%{count} predmet(a) dospijeva sljedećih %{days} dana" + mail_body_reminder: "%{count} vama dodijeljen(ih) predmet(a) dospijeva u sljedećih %{days} dana:" + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." + + + field_name: Ime + field_description: Opis + field_summary: Sažetak + field_is_required: Obavezno + field_firstname: Ime + field_lastname: Prezime + field_mail: E-poÅ¡ta + field_filename: Datoteka + field_filesize: VeliÄina + field_downloads: Preuzimanja + field_author: Autor + field_created_on: Napravljen + field_updated_on: Promijenjen + field_field_format: Format + field_is_for_all: Za sve projekte + field_possible_values: Moguće vrijednosti + field_regexp: Regularni izraz + field_min_length: Minimalna dužina + field_max_length: Maksimalna dužina + field_value: Vrijednost + field_category: Kategorija + field_title: Naslov + field_project: Projekt + field_issue: Predmet + field_status: Status + field_notes: Napomene + field_is_closed: Predmet je zatvoren + field_is_default: Zadana vrijednost + field_tracker: Tracker + field_subject: Predmet + field_due_date: Do datuma + field_assigned_to: Dodijeljeno + field_priority: Prioritet + field_fixed_version: Verzija + field_user: Korisnik + field_role: Uloga + field_homepage: Naslovnica + field_is_public: Javni projekt + field_parent: Potprojekt od + field_is_in_roadmap: Predmeti se prikazuju u Putokazu + field_login: KorisniÄko ime + field_mail_notification: Obavijest putem e-poÅ¡te + field_admin: Administrator + field_last_login_on: Zadnja prijava + field_language: Primarni jezik + field_effective_date: Datum + field_password: Zaporka + field_new_password: Nova zaporka + field_password_confirmation: Potvrda zaporke + field_version: Verzija + field_type: Tip + field_host: Host + field_port: Port + field_account: Racun + field_base_dn: Osnovni DN + field_attr_login: Login atribut + field_attr_firstname: Atribut imena + field_attr_lastname: Atribut prezimena + field_attr_mail: Atribut e-poÅ¡te + field_onthefly: "Izrada korisnika \"u hodu\"" + field_start_date: Pocetak + field_done_ratio: "% UÄinjeno" + field_auth_source: Vrsta prijavljivanja + field_hide_mail: Sakrij moju adresu e-poÅ¡te + field_comments: Komentar + field_url: URL + field_start_page: PoÄetna stranica + field_subproject: Potprojekt + field_hours: Sati + field_activity: Aktivnost + field_spent_on: Datum + field_identifier: Identifikator + field_is_filter: KoriÅ¡teno kao filtar + field_issue_to_id: Povezano s predmetom + field_delay: Odgodeno + field_assignable: Predmeti mogu biti dodijeljeni ovoj ulozi + field_redirect_existing_links: Preusmjeravanje postojećih linkova + field_estimated_hours: Procijenjeno vrijeme + field_column_names: Stupci + field_time_zone: Vremenska zona + field_searchable: Pretraživo + field_default_value: Zadana vrijednost + field_comments_sorting: Prikaz komentara + field_parent_title: Parent page + field_editable: Editable + field_watcher: Watcher + field_identity_url: OpenID URL + field_content: Content + field_group_by: Group results by + + setting_app_title: Naziv aplikacije + setting_app_subtitle: Podnaslov aplikacije + setting_welcome_text: Tekst dobrodoÅ¡lice + setting_default_language: Zadani jezik + setting_login_required: Potrebna je prijava + setting_self_registration: Samoregistracija je dozvoljena + setting_attachment_max_size: Maksimalna veliÄina privitka + setting_issues_export_limit: OgraniÄenje izvoza predmeta + setting_mail_from: Izvorna adresa e-poÅ¡te + setting_bcc_recipients: Blind carbon copy primatelja (bcc) + setting_plain_text_mail: obiÄni tekst poÅ¡te (bez HTML-a) + setting_host_name: Naziv domaćina (host) + setting_text_formatting: Oblikovanje teksta + setting_wiki_compression: Sažimanje + setting_feeds_limit: Ogranicenje unosa sadržaja + setting_default_projects_public: Novi projekti su javni po defaultu + setting_autofetch_changesets: Autofetch commits + setting_sys_api_enabled: Omogući WS za upravljanje skladiÅ¡tem + setting_commit_ref_keywords: Referentne kljuÄne rijeÄi + setting_commit_fix_keywords: Fiksne kljuÄne rijeÄi + setting_autologin: Automatska prijava + setting_date_format: Format datuma + setting_time_format: Format vremena + setting_cross_project_issue_relations: Dozvoli povezivanje predmeta izmedu razliÄitih projekata + setting_issue_list_default_columns: Stupci prikazani na listi predmeta + setting_emails_footer: Zaglavlje e-poÅ¡te + setting_protocol: Protokol + setting_per_page_options: Objekata po stranici opcija + setting_user_format: Oblik prikaza korisnika + setting_activity_days_default: Dani prikazane aktivnosti na projektu + setting_display_subprojects_issues: Prikaz predmeta potprojekta na glavnom projektu po defaultu + setting_enabled_scm: Omogućen SCM + setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" + setting_mail_handler_api_enabled: Omoguci WS za dolaznu e-poÅ¡tu + setting_mail_handler_api_key: API kljuÄ + setting_sequential_project_identifiers: Generiraj slijedne identifikatore projekta + setting_gravatar_enabled: Koristi Gravatar korisniÄke ikone + setting_gravatar_default: Default Gravatar image + setting_diff_max_lines_displayed: Maksimalni broj diff linija za prikazati + setting_file_max_size_displayed: Max size of text files displayed inline + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_openid: Allow OpenID login and registration + setting_password_min_length: Minimum password length + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + setting_default_projects_modules: Default enabled modules for new projects + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_field: Use the issue field + setting_issue_done_ratio_issue_status: Use the issue status + setting_start_of_week: Start calendars on + setting_rest_api_enabled: Enable REST web service + + permission_add_project: Dodaj projekt + permission_add_subprojects: Dodaj potprojekt + permission_edit_project: Uredi projekt + permission_select_project_modules: Odaberi projektne module + permission_manage_members: Upravljaj Älanovima + permission_manage_versions: Upravljaj verzijama + permission_manage_categories: Upravljaj kategorijama predmeta + permission_view_issues: Pregledaj zahtjeve + permission_add_issues: Dodaj predmete + permission_edit_issues: Uredi predmete + permission_manage_issue_relations: Upravljaj relacijama predmeta + permission_add_issue_notes: Dodaj biljeÅ¡ke + permission_edit_issue_notes: Uredi biljeÅ¡ke + permission_edit_own_issue_notes: Uredi vlastite biljeÅ¡ke + permission_move_issues: Premjesti predmete + permission_delete_issues: Brisanje predmeta + permission_manage_public_queries: Upravljaj javnim upitima + permission_save_queries: Spremi upite + permission_view_gantt: Pregledaj gantt grafikon + permission_view_calendar: Pregledaj kalendar + permission_view_issue_watchers: Pregledaj listu promatraca + permission_add_issue_watchers: Dodaj promatraÄa + permission_delete_issue_watchers: Delete watchers + permission_log_time: Dnevnik utroÅ¡enog vremena + permission_view_time_entries: Pregledaj utroÅ¡eno vrijeme + permission_edit_time_entries: Uredi vremenske dnevnike + permission_edit_own_time_entries: Edit own time logs + permission_manage_news: Upravljaj novostima + permission_comment_news: Komentiraj novosti + permission_view_documents: Pregledaj dokumente + permission_manage_files: Upravljaj datotekama + permission_view_files: Pregledaj datoteke + permission_manage_wiki: Upravljaj wikijem + permission_rename_wiki_pages: Promijeni ime wiki stranicama + permission_delete_wiki_pages: ObriÅ¡i wiki stranice + permission_view_wiki_pages: Pregledaj wiki + permission_view_wiki_edits: Pregledaj povijest wikija + permission_edit_wiki_pages: Uredi wiki stranice + permission_delete_wiki_pages_attachments: ObriÅ¡i privitke + permission_protect_wiki_pages: ZaÅ¡titi wiki stranice + permission_manage_repository: Upravljaj skladiÅ¡tem + permission_browse_repository: Browse repository + permission_view_changesets: View changesets + permission_commit_access: Mogućnost pohranjivanja + permission_manage_boards: Manage boards + permission_view_messages: Pregledaj poruke + permission_add_messages: Objavi poruke + permission_edit_messages: Uredi poruke + permission_edit_own_messages: Uredi vlastite poruke + permission_delete_messages: ObriÅ¡i poruke + permission_delete_own_messages: ObriÅ¡i vlastite poruke + + project_module_issue_tracking: Praćenje predmeta + project_module_time_tracking: Praćenje vremena + project_module_news: Novosti + project_module_documents: Dokumenti + project_module_files: Datoteke + project_module_wiki: Wiki + project_module_repository: SkladiÅ¡te + project_module_boards: Boards + + label_user: Korisnik + label_user_plural: Korisnici + label_user_new: Novi korisnik + label_user_anonymous: Anonymous + label_project: Projekt + label_project_new: Novi projekt + label_project_plural: Projekti + label_x_projects: + zero: no projects + one: 1 project + other: "%{count} projects" + label_project_all: Svi Projekti + label_project_latest: Najnoviji projekt + label_issue: Predmet + label_issue_new: Novi predmet + label_issue_plural: Predmeti + label_issue_view_all: Pregled svih predmeta + label_issues_by: "Predmeti od %{value}" + label_issue_added: Predmet dodan + label_issue_updated: Predmet promijenjen + label_document: Dokument + label_document_new: Novi dokument + label_document_plural: Dokumenti + label_document_added: Dokument dodan + label_role: Uloga + label_role_plural: Uloge + label_role_new: Nova uloga + label_role_and_permissions: Uloge i ovlasti + label_member: ÄŒlan + label_member_new: Novi Älan + label_member_plural: ÄŒlanovi + label_tracker: Vrsta + label_tracker_plural: Vrste predmeta + label_tracker_new: Nova vrsta + label_workflow: Tijek rada + label_issue_status: Status predmeta + label_issue_status_plural: Status predmeta + label_issue_status_new: Novi status + label_issue_category: Kategorija predmeta + label_issue_category_plural: Kategorije predmeta + label_issue_category_new: Nova kategorija + label_custom_field: KorisniÄki definirano polje + label_custom_field_plural: KorisniÄki definirana polja + label_custom_field_new: Novo korisniÄki definirano polje + label_enumerations: Pobrojenice + label_enumeration_new: Nova vrijednost + label_information: Informacija + label_information_plural: Informacije + label_please_login: Molim prijavite se + label_register: Registracija + label_login_with_open_id_option: or login with OpenID + label_password_lost: Izgubljena zaporka + label_home: PoÄetna stranica + label_my_page: Moja stranica + label_my_account: Moj profil + label_my_projects: Moji projekti + label_administration: Administracija + label_login: Korisnik + label_logout: Odjava + label_help: Pomoć + label_reported_issues: Prijavljeni predmeti + label_assigned_to_me_issues: Moji predmeti + label_last_login: Last connection + label_registered_on: Registrirano + label_activity: Aktivnosti + label_overall_activity: Aktivnosti + label_user_activity: "%{value} ova/ina aktivnost" + label_new: Novi + label_logged_as: Prijavljeni ste kao + label_environment: Okolina + label_authentication: Autentikacija + label_auth_source: NaÄin prijavljivanja + label_auth_source_new: Novi naÄin prijavljivanja + label_auth_source_plural: NaÄini prijavljivanja + label_subproject_plural: Potprojekti + label_subproject_new: Novi potprojekt + label_and_its_subprojects: "%{value} i njegovi potprojekti" + label_min_max_length: Min - Maks veliÄina + label_list: Liste + label_date: Datum + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: Text + label_text: Long text + label_attribute: Atribut + label_attribute_plural: Atributi + label_no_data: Nema podataka za prikaz + label_change_status: Promjena statusa + label_history: Povijest + label_attachment: Datoteka + label_attachment_new: Nova datoteka + label_attachment_delete: Brisanje datoteke + label_attachment_plural: Datoteke + label_file_added: Datoteka dodana + label_report: Izvješće + label_report_plural: Izvješća + label_news: Novosti + label_news_new: Dodaj novost + label_news_plural: Novosti + label_news_latest: Novosti + label_news_view_all: Pregled svih novosti + label_news_added: Novosti dodane + label_settings: Postavke + label_overview: Pregled + label_version: Verzija + label_version_new: Nova verzija + label_version_plural: Verzije + label_confirmation: Potvrda + label_export_to: 'Izvoz u:' + label_read: ÄŒitaj... + label_public_projects: Javni projekti + label_open_issues: Otvoren + label_open_issues_plural: Otvoreno + label_closed_issues: Zatvoren + label_closed_issues_plural: Zatvoreno + label_x_open_issues_abbr_on_total: + zero: 0 open / %{total} + one: 1 open / %{total} + other: "%{count} open / %{total}" + label_x_open_issues_abbr: + zero: 0 open + one: 1 open + other: "%{count} open" + label_x_closed_issues_abbr: + zero: 0 closed + one: 1 closed + other: "%{count} closed" + label_total: Ukupno + label_permissions: Dozvole + label_current_status: Trenutni status + label_new_statuses_allowed: Novi status je dozvoljen + label_all: Svi + label_none: nema + label_nobody: nitko + label_next: Naredni + label_previous: Prethodni + label_used_by: KoriÅ¡ten od + label_details: Detalji + label_add_note: Dodaj napomenu + label_per_page: Po stranici + label_calendar: Kalendar + label_months_from: Mjeseci od + label_gantt: Gantt + label_internal: Interno + label_last_changes: "Posljednjih %{count} promjena" + label_change_view_all: Prikaz svih promjena + label_personalize_page: Prilagodite ovu stranicu + label_comment: Komentar + label_comment_plural: Komentari + label_x_comments: + zero: no comments + one: 1 comment + other: "%{count} comments" + label_comment_add: Dodaj komentar + label_comment_added: Komentar dodan + label_comment_delete: Brisanje komentara + label_query: KorisniÄki upit + label_query_plural: KorisniÄki upiti + label_query_new: Novi upit + label_filter_add: Dodaj filtar + label_filter_plural: Filtri + label_equals: je + label_not_equals: nije + label_in_less_than: za manje od + label_in_more_than: za viÅ¡e od + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: za toÄno + label_today: danas + label_all_time: sva vremena + label_yesterday: juÄer + label_this_week: ovog tjedna + label_last_week: proÅ¡log tjedna + label_last_n_days: "zadnjih %{count} dana" + label_this_month: ovog mjeseca + label_last_month: proÅ¡log mjeseca + label_this_year: ove godine + label_date_range: vremenski raspon + label_less_than_ago: manje od + label_more_than_ago: viÅ¡e od + label_ago: prije + label_contains: Sadrži + label_not_contains: ne sadrži + label_day_plural: dana + label_repository: SkladiÅ¡te + label_repository_plural: SkladiÅ¡ta + label_browse: Pregled + label_branch: Branch + label_tag: Tag + label_revision: Revizija + label_revision_plural: Revizije + label_revision_id: "Revision %{value}" + label_associated_revisions: Dodijeljene revizije + label_added: dodano + label_modified: promijenjen + label_copied: kopirano + label_renamed: preimenovano + label_deleted: obrisano + label_latest_revision: Posljednja revizija + label_latest_revision_plural: Posljednje revizije + label_view_revisions: Pregled revizija + label_view_all_revisions: View all revisions + label_max_size: Maksimalna veliÄina + label_sort_highest: Premjesti na vrh + label_sort_higher: Premjesti prema gore + label_sort_lower: Premjesti prema dolje + label_sort_lowest: Premjesti na dno + label_roadmap: Putokaz + label_roadmap_due_in: "ZavrÅ¡ava se za %{value}" + label_roadmap_overdue: "%{value} kasni" + label_roadmap_no_issues: Nema predmeta za ovu verziju + label_search: Traži + label_result_plural: Rezultati + label_all_words: Sve rijeÄi + label_wiki: Wiki + label_wiki_edit: Wiki promjena + label_wiki_edit_plural: Wiki promjene + label_wiki_page: Wiki stranica + label_wiki_page_plural: Wiki stranice + label_index_by_title: Indeks po naslovima + label_index_by_date: Indeks po datumu + label_current_version: Trenutna verzija + label_preview: Brzi pregled + label_feed_plural: Feeds + label_changes_details: Detalji svih promjena + label_issue_tracking: Praćenje predmeta + label_spent_time: UtroÅ¡eno vrijeme + label_f_hour: "%{value} sata" + label_f_hour_plural: "%{value} sati" + label_time_tracking: Praćenje vremena + label_change_plural: Promjene + label_statistics: Statistika + label_commits_per_month: Pohrana po mjesecu + label_commits_per_author: Pohrana po autoru + label_view_diff: Pregled razlika + label_diff_inline: uvuÄeno + label_diff_side_by_side: paralelno + label_options: Opcije + label_copy_workflow_from: Kopiraj tijek rada od + label_permissions_report: Izvješće o dozvolama + label_watched_issues: Praćeni predmeti + label_related_issues: Povezani predmeti + label_applied_status: Primijenjen status + label_loading: UÄitavam... + label_relation_new: Nova relacija + label_relation_delete: Brisanje relacije + label_relates_to: u relaciji sa + label_duplicates: Duplira + label_duplicated_by: ponovljen kao + label_blocks: blokira + label_blocked_by: blokiran od strane + label_precedes: prethodi + label_follows: slijedi + label_end_to_start: od kraja do poÄetka + label_end_to_end: od kraja do kraja + label_end_to_start: od kraja do poÄetka + label_end_to_end: od kraja do kraja + label_stay_logged_in: Ostanite prijavljeni + label_disabled: IskljuÄen + label_show_completed_versions: Prikaži zavrÅ¡ene verzije + label_me: ja + label_board: Forum + label_board_new: Novi forum + label_board_plural: Forumi + label_topic_plural: Teme + label_message_plural: Poruke + label_message_last: Posljednja poruka + label_message_new: Nova poruka + label_message_posted: Poruka dodana + label_reply_plural: Odgovori + label_send_information: PoÅ¡alji korisniku informaciju o profilu + label_year: Godina + label_month: Mjesec + label_week: Tjedan + label_date_from: Od + label_date_to: Do + label_language_based: Zasnovano na jeziku + label_sort_by: "Uredi po %{value}" + label_send_test_email: PoÅ¡alji testno E-pismo + label_feeds_access_key: Atom access key + label_missing_feeds_access_key: Missing a Atom access key + label_feeds_access_key_created_on: "Atom kljuc za pristup je napravljen prije %{value}" + label_module_plural: Moduli + label_added_time_by: "Promijenio %{author} prije %{age}" + label_updated_time_by: "Dodao/la %{author} prije %{age}" + label_updated_time: "Promijenjeno prije %{value}" + label_jump_to_a_project: Prebaci se na projekt... + label_file_plural: Datoteke + label_changeset_plural: Promjene + label_default_columns: Zadani stupci + label_no_change_option: (Bez promjene) + label_bulk_edit_selected_issues: ZajedniÄka promjena izabranih predmeta + label_theme: Tema + label_default: Zadana + label_search_titles_only: Pretraživanje samo naslova + label_user_mail_option_all: "Za bilo koji dogaÄ‘aj na svim mojim projektima" + label_user_mail_option_selected: "Za bilo koji dogaÄ‘aj samo za izabrane projekte..." + label_user_mail_no_self_notified: "Ne želim primati obavijesti o promjenama koje sam napravim" + label_registration_activation_by_email: aktivacija putem e-poÅ¡te + label_registration_manual_activation: ruÄna aktivacija + label_registration_automatic_activation: automatska aktivacija + label_display_per_page: "Po stranici: %{value}" + label_age: Starost + label_change_properties: Promijeni svojstva + label_general: Općenito + label_more: JoÅ¡ + label_scm: SCM + label_plugins: Plugins + label_ldap_authentication: LDAP autentikacija + label_downloads_abbr: D/L + label_optional_description: Opcije + label_add_another_file: Dodaj joÅ¡ jednu datoteku + label_preferences: Preferences + label_chronological_order: U kronoloÅ¡kom redoslijedu + label_reverse_chronological_order: U obrnutom kronoloÅ¡kom redoslijedu + label_planning: Planiranje + label_incoming_emails: Dolazne poruke e-poÅ¡te + label_generate_key: Generiraj kljuÄ + label_issue_watchers: PromatraÄi + label_example: Primjer + label_display: Display + label_sort: Sort + label_ascending: Ascending + label_descending: Descending + label_date_from_to: From %{start} to %{end} + label_wiki_content_added: Wiki page added + label_wiki_content_updated: Wiki page updated + label_group: Group + label_group_plural: Grupe + label_group_new: Nova grupa + label_time_entry_plural: Spent time + label_version_sharing_none: Not shared + label_version_sharing_descendants: With subprojects + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_tree: With project tree + label_version_sharing_system: With all projects + label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Source + label_copy_target: Target + label_copy_same_as_target: Same as target + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_api_access_key: API access key + label_missing_api_access_key: Missing an API access key + label_api_access_key_created_on: "API access key created %{value} ago" + + button_login: Prijavi + button_submit: PoÅ¡alji + button_save: Spremi + button_check_all: OznaÄi sve + button_uncheck_all: IskljuÄi sve + button_delete: ObriÅ¡i + button_create: Napravi + button_create_and_continue: Napravi i nastavi + button_test: Test + button_edit: Uredi + button_add: Dodaj + button_change: Promijeni + button_apply: Primijeni + button_clear: Ukloni + button_lock: ZakljuÄaj + button_unlock: OtkljuÄaj + button_download: Preuzmi + button_list: Spisak + button_view: Pregled + button_move: Premjesti + button_move_and_follow: Move and follow + button_back: Nazad + button_cancel: Odustani + button_activate: Aktiviraj + button_sort: Redoslijed + button_log_time: ZapiÅ¡i vrijeme + button_rollback: IzvrÅ¡i rollback na ovu verziju + button_watch: Prati + button_unwatch: Prekini pracenje + button_reply: Odgovori + button_archive: Arhiviraj + button_rollback: Dearhiviraj + button_reset: PoniÅ¡ti + button_rename: Promijeni ime + button_change_password: Promjena zaporke + button_copy: Kopiraj + button_copy_and_follow: Copy and follow + button_annotate: Annotate + button_update: Promijeni + button_configure: Konfiguracija + button_quote: Navod + button_duplicate: Duplicate + button_show: Show + + status_active: aktivan + status_registered: Registriran + status_locked: zakljuÄan + + version_status_open: open + version_status_locked: locked + version_status_closed: closed + + field_active: Active + + text_select_mail_notifications: Izbor akcija za koje će biti poslana obavijest e-poÅ¡tom. + text_regexp_info: eg. ^[A-Z0-9]+$ + text_min_max_length_info: 0 znaÄi bez ograniÄenja + text_project_destroy_confirmation: Da li ste sigurni da želite izbrisati ovaj projekt i sve njegove podatke? + text_subprojects_destroy_warning: "Njegov(i) potprojekt(i): %{value} će takoÄ‘er biti obrisan." + text_workflow_edit: Select a role and a tracker to edit the workflow + text_are_you_sure: Da li ste sigurni? + text_journal_changed: "%{label} promijenjen iz %{old} u %{new}" + text_journal_set_to: "%{label} postavi na %{value}" + text_journal_deleted: "%{label} izbrisano (%{old})" + text_journal_added: "%{label} %{value} added" + text_tip_issue_begin_day: Zadaci koji poÄinju ovog dana + text_tip_issue_end_day: zadaci koji se zavrÅ¡avaju ovog dana + text_tip_issue_begin_end_day: Zadaci koji poÄinju i zavrÅ¡avaju se ovog dana + text_caracters_maximum: "NajviÅ¡e %{count} znakova." + text_caracters_minimum: "Mora biti dugaÄko najmanje %{count} znakova." + text_length_between: "Dužina izmedu %{min} i %{max} znakova." + text_tracker_no_workflow: Tijek rada nije definiran za ovaj tracker + text_unallowed_characters: Nedozvoljeni znakovi + text_comma_separated: ViÅ¡estruke vrijednosti su dozvoljene (razdvojene zarezom). + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_tracker_no_workflow: No workflow defined for this tracker + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_issue_added: "Predmet %{id} je prijavljen (prijavio %{author})." + text_issue_updated: "Predmet %{id} je promijenjen %{author})." + text_wiki_destroy_confirmation: Da li ste sigurni da želite izbrisati ovaj wiki i njegov sadržaj? + text_issue_category_destroy_question: "Neke predmeti (%{count}) su dodijeljeni ovoj kategoriji. Å to želite uraditi?" + text_issue_category_destroy_assignments: Ukloni dodjeljivanje kategorija + text_issue_category_reassign_to: Ponovo dodijeli predmete ovoj kategoriji + text_user_mail_option: "Za neizabrane projekte, primit ćete obavjesti samo o stvarima koje pratite ili u kojima sudjelujete (npr. predmete koje ste vi napravili ili koje su vama dodjeljeni)." + text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." + text_load_default_configuration: UÄitaj poÄetnu konfiguraciju + text_status_changed_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Jeste li sigurni da želite obrisati izabrani/e predmet(e)?' + text_select_project_modules: 'Odaberite module koji će biti omogućeni za ovaj projekt:' + text_default_administrator_account_changed: Default administrator account changed + text_file_repository_writable: Dozvoljeno pisanje u direktorij za privitke + text_plugin_assets_writable: Plugin assets directory writable + text_rmagick_available: RMagick dostupan (nije obavezno) + text_destroy_time_entries_question: "%{hours} sati je prijavljeno za predmete koje želite obrisati. Å to ćete uÄiniti?" + text_destroy_time_entries: ObriÅ¡i prijavljene sate + text_assign_time_entries_to_project: Pridruži prijavljene sate projektu + text_reassign_time_entries: 'Premjesti prijavljene sate ovom predmetu:' + text_user_wrote: "%{value} je napisao/la:" + text_enumeration_destroy_question: "%{count} objekata je pridruženo toj vrijednosti." + text_enumeration_category_reassign_to: 'Premjesti ih ovoj vrijednosti:' + text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." + text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." + text_diff_truncated: '... Ovaj diff je odrezan zato Å¡to prelazi maksimalnu veliÄinu koja može biti prikazana.' + text_custom_field_possible_values_info: 'One line for each value' + text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" + text_wiki_page_nullify_children: "Keep child pages as root pages" + text_wiki_page_destroy_children: "Delete child pages and all their descendants" + text_wiki_page_reassign_children: "Reassign child pages to this parent page" + default_role_manager: Upravitelj + default_role_developer: Razvojni inženjer + default_role_reporter: Korisnik + default_tracker_bug: PogreÅ¡ka + default_tracker_feature: Funkcionalnost + default_tracker_support: PodrÅ¡ka + default_issue_status_new: Novo + default_issue_status_assigned: Dodijeljeno + default_issue_status_resolved: RijeÅ¡eno + default_issue_status_feedback: Povratna informacija + default_issue_status_closed: Zatvoreno + default_issue_status_rejected: Odbaceno + default_doc_category_user: KorisniÄka dokumentacija + default_doc_category_tech: TehniÄka dokumentacija + default_priority_low: Nizak + default_priority_normal: Redovan + default_priority_high: Visok + default_priority_urgent: Hitan + default_priority_immediate: Odmah + default_activity_design: Dizajn + default_activity_development: Razvoj + enumeration_issue_priorities: Prioriteti predmeta + enumeration_doc_categories: Kategorija dokumenata + enumeration_activities: Aktivnosti (po vremenu) + enumeration_system_activity: System Activity + field_sharing: Sharing + text_line_separated: Multiple values allowed (one line for each value). + label_close_versions: Close completed versions + button_unarchive: Unarchive + label_start_to_end: start to end + label_start_to_start: start to start + field_issue_to: Related issue + default_issue_status_in_progress: In Progress + text_own_membership_delete_confirmation: |- + You are about to remove some or all of your permissions and may no longer be able to edit this project after that. + Are you sure you want to continue? + label_board_sticky: Sticky + label_board_locked: Locked + permission_export_wiki_pages: Export wiki pages + setting_cache_formatted_text: Cache formatted text + permission_manage_project_activities: Manage project activities + error_unable_delete_issue_status: Unable to delete issue status + label_profile: Profile + permission_manage_subtasks: Manage subtasks + field_parent_issue: Parent task + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + error_can_not_delete_custom_field: Unable to delete custom field + error_unable_to_connect: Unable to connect (%{value}) + error_can_not_remove_role: This role is in use and can not be deleted. + error_can_not_delete_tracker: This tracker contains issues and can't be deleted. + field_principal: Principal + label_my_page_block: My page block + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + text_zoom_out: Zoom out + text_zoom_in: Zoom in + notice_unable_delete_time_entry: Unable to delete time log entry. + label_overall_spent_time: Overall spent time + field_time_entries: Log time + project_module_gantt: Gantt + project_module_calendar: Calendar + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Commit messages encoding + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 predmet + one: 1 predmet + other: "%{count} predmeti" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: Svi + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: With subprojects + label_cross_project_tree: With project tree + label_cross_project_hierarchy: With project hierarchy + label_cross_project_system: With all projects + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c2/c2fe0492869882e0e1f91f746a29fa8ca653bf0c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c2/c2fe0492869882e0e1f91f746a29fa8ca653bf0c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,13 @@ +<%= "#{issue.tracker.name} ##{issue.id}: #{issue.subject}" %> +<%= issue_url %> + +<%= render_email_issue_attributes(issue, users.first) %> +---------------------------------------- +<%= issue.description %> + +<% if issue.attachments.any? -%> +---<%= l(:label_attachment_plural).ljust(37, '-') %> +<% issue.attachments.each do |attachment| -%> +<%= attachment.filename %> (<%= number_to_human_size(attachment.filesize) %>) +<% end -%> +<% end -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c3/c33253e7d9fe3dcf6d331a6400b5f9b5e73ef2a5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c33253e7d9fe3dcf6d331a6400b5f9b5e73ef2a5.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,184 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class MessageTest < ActiveSupport::TestCase + fixtures :projects, :roles, :members, :member_roles, :boards, :messages, + :users, :watchers, :enabled_modules + + def setup + @board = Board.find(1) + @user = User.find(1) + end + + def test_create + topics_count = @board.topics_count + messages_count = @board.messages_count + + message = Message.new(:board => @board, :subject => 'Test message', + :content => 'Test message content', + :author => @user) + assert message.save + @board.reload + # topics count incremented + assert_equal topics_count + 1, @board[:topics_count] + # messages count incremented + assert_equal messages_count + 1, @board[:messages_count] + assert_equal message, @board.last_message + # author should be watching the message + assert message.watched_by?(@user) + end + + def test_reply + topics_count = @board.topics_count + messages_count = @board.messages_count + message = Message.find(1) + replies_count = message.replies_count + + reply_author = User.find(2) + reply = Message.new(:board => @board, :subject => 'Test reply', + :content => 'Test reply content', + :parent => message, :author => reply_author) + assert reply.save + @board.reload + # same topics count + assert_equal topics_count, @board[:topics_count] + # messages count incremented + assert_equal messages_count+1, @board[:messages_count] + assert_equal reply, @board.last_message + message.reload + # replies count incremented + assert_equal replies_count+1, message[:replies_count] + assert_equal reply, message.last_reply + # author should be watching the message + assert message.watched_by?(reply_author) + end + + def test_cannot_reply_to_locked_topic + topics_count = @board.topics_count + messages_count = @board.messages_count + message = Message.find(1) + replies_count = message.replies_count + assert_equal false, message.locked + message.locked = true + assert message.save + assert_equal true, message.locked + + reply_author = User.find(2) + reply = Message.new(:board => @board, :subject => 'Test reply', + :content => 'Test reply content', + :parent => message, :author => reply_author) + reply.save + assert_equal 1, reply.errors.count + end + + def test_moving_message_should_update_counters + message = Message.find(1) + assert_no_difference 'Message.count' do + # Previous board + assert_difference 'Board.find(1).topics_count', -1 do + assert_difference 'Board.find(1).messages_count', -(1 + message.replies_count) do + # New board + assert_difference 'Board.find(2).topics_count' do + assert_difference 'Board.find(2).messages_count', (1 + message.replies_count) do + message.update_attributes(:board_id => 2) + end + end + end + end + end + end + + def test_destroy_topic + message = Message.find(1) + board = message.board + topics_count, messages_count = board.topics_count, board.messages_count + + assert_difference('Watcher.count', -1) do + assert message.destroy + end + board.reload + + # Replies deleted + assert Message.find_all_by_parent_id(1).empty? + # Checks counters + assert_equal topics_count - 1, board.topics_count + assert_equal messages_count - 3, board.messages_count + # Watchers removed + end + + def test_destroy_reply + message = Message.find(5) + board = message.board + topics_count, messages_count = board.topics_count, board.messages_count + assert message.destroy + board.reload + + # Checks counters + assert_equal topics_count, board.topics_count + assert_equal messages_count - 1, board.messages_count + end + + def test_destroying_last_reply_should_update_topic_last_reply_id + topic = Message.find(4) + assert_equal 6, topic.last_reply_id + + assert_difference 'Message.count', -1 do + Message.find(6).destroy + end + assert_equal 5, topic.reload.last_reply_id + + assert_difference 'Message.count', -1 do + Message.find(5).destroy + end + assert_nil topic.reload.last_reply_id + end + + def test_editable_by + message = Message.find(6) + author = message.author + assert message.editable_by?(author) + + author.roles_for_project(message.project).first.remove_permission!(:edit_own_messages) + assert !message.reload.editable_by?(author.reload) + end + + def test_destroyable_by + message = Message.find(6) + author = message.author + assert message.destroyable_by?(author) + + author.roles_for_project(message.project).first.remove_permission!(:delete_own_messages) + assert !message.reload.destroyable_by?(author.reload) + end + + def test_set_sticky + message = Message.new + assert_equal 0, message.sticky + message.sticky = nil + assert_equal 0, message.sticky + message.sticky = false + assert_equal 0, message.sticky + message.sticky = true + assert_equal 1, message.sticky + message.sticky = '0' + assert_equal 0, message.sticky + message.sticky = '1' + assert_equal 1, message.sticky + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c3/c336c255f0c991cc835201626f5e37be82ee02c6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c336c255f0c991cc835201626f5e37be82ee02c6.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,536 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class MailHandler < ActionMailer::Base + include ActionView::Helpers::SanitizeHelper + include Redmine::I18n + + class UnauthorizedAction < StandardError; end + class MissingInformation < StandardError; end + + attr_reader :email, :user + + def self.receive(email, options={}) + @@handler_options = options.dup + + @@handler_options[:issue] ||= {} + + if @@handler_options[:allow_override].is_a?(String) + @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) + end + @@handler_options[:allow_override] ||= [] + # Project needs to be overridable if not specified + @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project) + # Status overridable by default + @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) + + @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1') + @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1') + @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1') + + email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding) + super(email) + end + + # Extracts MailHandler options from environment variables + # Use when receiving emails with rake tasks + def self.extract_options_from_env(env) + options = {:issue => {}} + %w(project status tracker category priority).each do |option| + options[:issue][option.to_sym] = env[option] if env[option] + end + %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option| + options[option.to_sym] = env[option] if env[option] + end + options + end + + def logger + Rails.logger + end + + cattr_accessor :ignored_emails_headers + @@ignored_emails_headers = { + 'X-Auto-Response-Suppress' => 'oof', + 'Auto-Submitted' => /^auto-/ + } + + # Processes incoming emails + # Returns the created object (eg. an issue, a message) or false + def receive(email) + @email = email + sender_email = email.from.to_a.first.to_s.strip + # Ignore emails received from the application emission address to avoid hell cycles + if sender_email.downcase == Setting.mail_from.to_s.strip.downcase + if logger + logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" + end + return false + end + # Ignore auto generated emails + self.class.ignored_emails_headers.each do |key, ignored_value| + value = email.header[key] + if value + value = value.to_s.downcase + if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value + if logger + logger.info "MailHandler: ignoring email with #{key}:#{value} header" + end + return false + end + end + end + @user = User.find_by_mail(sender_email) if sender_email.present? + if @user && !@user.active? + if logger + logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" + end + return false + end + if @user.nil? + # Email was submitted by an unknown user + case @@handler_options[:unknown_user] + when 'accept' + @user = User.anonymous + when 'create' + @user = create_user_from_email + if @user + if logger + logger.info "MailHandler: [#{@user.login}] account created" + end + add_user_to_group(@@handler_options[:default_group]) + unless @@handler_options[:no_account_notice] + Mailer.account_information(@user, @user.password).deliver + end + else + if logger + logger.error "MailHandler: could not create account for [#{sender_email}]" + end + return false + end + else + # Default behaviour, emails from unknown users are ignored + if logger + logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" + end + return false + end + end + User.current = @user + dispatch + end + + private + + MESSAGE_ID_RE = %r{^ e + # TODO: send a email to the user + logger.error e.message if logger + false + rescue MissingInformation => e + logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger + false + rescue UnauthorizedAction => e + logger.error "MailHandler: unauthorized attempt from #{user}" if logger + false + end + + def dispatch_to_default + receive_issue + end + + # Creates a new issue + def receive_issue + project = target_project + # check permission + unless @@handler_options[:no_permission_check] + raise UnauthorizedAction unless user.allowed_to?(:add_issues, project) + end + + issue = Issue.new(:author => user, :project => project) + issue.safe_attributes = issue_attributes_from_keywords(issue) + issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} + issue.subject = cleaned_up_subject + if issue.subject.blank? + issue.subject = '(no subject)' + end + issue.description = cleaned_up_text_body + + # add To and Cc as watchers before saving so the watchers can reply to Redmine + add_watchers(issue) + issue.save! + add_attachments(issue) + logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger + issue + end + + # Adds a note to an existing issue + def receive_issue_reply(issue_id, from_journal=nil) + issue = Issue.find_by_id(issue_id) + return unless issue + # check permission + unless @@handler_options[:no_permission_check] + unless user.allowed_to?(:add_issue_notes, issue.project) || + user.allowed_to?(:edit_issues, issue.project) + raise UnauthorizedAction + end + end + + # ignore CLI-supplied defaults for new issues + @@handler_options[:issue].clear + + journal = issue.init_journal(user) + if from_journal && from_journal.private_notes? + # If the received email was a reply to a private note, make the added note private + issue.private_notes = true + end + issue.safe_attributes = issue_attributes_from_keywords(issue) + issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} + journal.notes = cleaned_up_text_body + add_attachments(issue) + issue.save! + if logger + logger.info "MailHandler: issue ##{issue.id} updated by #{user}" + end + journal + end + + # Reply will be added to the issue + def receive_journal_reply(journal_id) + journal = Journal.find_by_id(journal_id) + if journal && journal.journalized_type == 'Issue' + receive_issue_reply(journal.journalized_id, journal) + end + end + + # Receives a reply to a forum message + def receive_message_reply(message_id) + message = Message.find_by_id(message_id) + if message + message = message.root + + unless @@handler_options[:no_permission_check] + raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project) + end + + if !message.locked? + reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip, + :content => cleaned_up_text_body) + reply.author = user + reply.board = message.board + message.children << reply + add_attachments(reply) + reply + else + if logger + logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" + end + end + end + end + + def add_attachments(obj) + if email.attachments && email.attachments.any? + email.attachments.each do |attachment| + next unless accept_attachment?(attachment) + obj.attachments << Attachment.create(:container => obj, + :file => attachment.decoded, + :filename => attachment.filename, + :author => user, + :content_type => attachment.mime_type) + end + end + end + + # Returns false if the +attachment+ of the incoming email should be ignored + def accept_attachment?(attachment) + @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?) + @excluded.each do |pattern| + regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i + if attachment.filename.to_s =~ regexp + logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}" + return false + end + end + true + end + + # Adds To and Cc as watchers of the given object if the sender has the + # appropriate permission + def add_watchers(obj) + if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project) + addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase} + unless addresses.empty? + watchers = User.active.where('LOWER(mail) IN (?)', addresses).all + watchers.each {|w| obj.add_watcher(w)} + end + end + end + + def get_keyword(attr, options={}) + @keywords ||= {} + if @keywords.has_key?(attr) + @keywords[attr] + else + @keywords[attr] = begin + if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && + (v = extract_keyword!(plain_text_body, attr, options[:format])) + v + elsif !@@handler_options[:issue][attr].blank? + @@handler_options[:issue][attr] + end + end + end + end + + # Destructively extracts the value for +attr+ in +text+ + # Returns nil if no matching keyword found + def extract_keyword!(text, attr, format=nil) + keys = [attr.to_s.humanize] + if attr.is_a?(Symbol) + if user && user.language.present? + keys << l("field_#{attr}", :default => '', :locale => user.language) + end + if Setting.default_language.present? + keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) + end + end + keys.reject! {|k| k.blank?} + keys.collect! {|k| Regexp.escape(k)} + format ||= '.+' + keyword = nil + regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i + if m = text.match(regexp) + keyword = m[2].strip + text.gsub!(regexp, '') + end + keyword + end + + def target_project + # TODO: other ways to specify project: + # * parse the email To field + # * specific project (eg. Setting.mail_handler_target_project) + target = Project.find_by_identifier(get_keyword(:project)) + if target.nil? + # Invalid project keyword, use the project specified as the default one + default_project = @@handler_options[:issue][:project] + if default_project.present? + target = Project.find_by_identifier(default_project) + end + end + raise MissingInformation.new('Unable to determine target project') if target.nil? + target + end + + # Returns a Hash of issue attributes extracted from keywords in the email body + def issue_attributes_from_keywords(issue) + assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue) + + attrs = { + 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id), + 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id), + 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id), + 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id), + 'assigned_to_id' => assigned_to.try(:id), + 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && + issue.project.shared_versions.named(k).first.try(:id), + 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), + 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), + 'estimated_hours' => get_keyword(:estimated_hours, :override => true), + 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0') + }.delete_if {|k, v| v.blank? } + + if issue.new_record? && attrs['tracker_id'].nil? + attrs['tracker_id'] = issue.project.trackers.first.try(:id) + end + + attrs + end + + # Returns a Hash of issue custom field values extracted from keywords in the email body + def custom_field_values_from_keywords(customized) + customized.custom_field_values.inject({}) do |h, v| + if keyword = get_keyword(v.custom_field.name, :override => true) + h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized) + end + h + end + end + + # Returns the text/plain part of the email + # If not found (eg. HTML-only email), returns the body with tags removed + def plain_text_body + return @plain_text_body unless @plain_text_body.nil? + + parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present? + text_parts + elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present? + html_parts + else + [email] + end + + parts.reject! do |part| + part.header[:content_disposition].try(:disposition_type) == 'attachment' + end + + @plain_text_body = parts.map {|p| Redmine::CodesetUtil.to_utf8(p.body.decoded, p.charset)}.join("\r\n") + + # strip html tags and remove doctype directive + if parts.any? {|p| p.mime_type == 'text/html'} + @plain_text_body = strip_tags(@plain_text_body.strip) + @plain_text_body.sub! %r{^$/) + addr, name = m[2], m[1] + end + if addr.present? + user = self.class.new_user_from_attributes(addr, name) + if @@handler_options[:no_notification] + user.mail_notification = 'none' + end + if user.save + user + else + logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger + nil + end + else + logger.error "MailHandler: failed to create User: no FROM address found" if logger + nil + end + end + + # Adds the newly created user to default group + def add_user_to_group(default_group) + if default_group.present? + default_group.split(',').each do |group_name| + if group = Group.named(group_name).first + group.users << @user + elsif logger + logger.warn "MailHandler: could not add user to [#{group_name}], group not found" + end + end + end + end + + # Removes the email body of text after the truncation configurations. + def cleanup_body(body) + delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)} + unless delimiters.empty? + regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE) + body = body.gsub(regex, '') + end + body.strip + end + + def find_assignee_from_keyword(keyword, issue) + keyword = keyword.to_s.downcase + assignable = issue.assignable_users + assignee = nil + assignee ||= assignable.detect {|a| + a.mail.to_s.downcase == keyword || + a.login.to_s.downcase == keyword + } + if assignee.nil? && keyword.match(/ /) + firstname, lastname = *(keyword.split) # "First Last Throwaway" + assignee ||= assignable.detect {|a| + a.is_a?(User) && a.firstname.to_s.downcase == firstname && + a.lastname.to_s.downcase == lastname + } + end + if assignee.nil? + assignee ||= assignable.detect {|a| a.name.downcase == keyword} + end + assignee + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c3/c337c1d71535f0f645a5577b10e1a55ce3ca183c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c337c1d71535f0f645a5577b10e1a55ce3ca183c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,19 @@ +<%= title l(:label_confirmation) %> + +
    +

    <%=h @project_to_destroy %>
    +<%=l(:text_project_destroy_confirmation)%> + +<% if @project_to_destroy.descendants.any? %> +
    <%= l(:text_subprojects_destroy_warning, + content_tag('strong', h(@project_to_destroy.descendants.collect{|p| p.to_s}.join(', ')))).html_safe %> +<% end %> +

    +

    + <%= form_tag(project_path(@project_to_destroy), :method => :delete) do %> + + <%= submit_tag l(:button_delete) %> + <%= link_to l(:button_cancel), :controller => 'admin', :action => 'projects' %> + <% end %> +

    +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c3/c3445c0cb311dd51294d15b24c99859759c6e162.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c3445c0cb311dd51294d15b24c99859759c6e162.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,175 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class AccountControllerOpenidTest < ActionController::TestCase + tests AccountController + fixtures :users, :roles + + def setup + User.current = nil + Setting.openid = '1' + end + + def teardown + Setting.openid = '0' + end + + if Object.const_defined?(:OpenID) + + def test_login_with_openid_for_existing_user + Setting.self_registration = '3' + existing_user = User.new(:firstname => 'Cool', + :lastname => 'User', + :mail => 'user@somedomain.com', + :identity_url => 'http://openid.example.com/good_user') + existing_user.login = 'cool_user' + assert existing_user.save! + + post :login, :openid_url => existing_user.identity_url + assert_redirected_to '/my/page' + end + + def test_login_with_invalid_openid_provider + Setting.self_registration = '0' + post :login, :openid_url => 'http;//openid.example.com/good_user' + assert_redirected_to home_url + end + + def test_login_with_openid_for_existing_non_active_user + Setting.self_registration = '2' + existing_user = User.new(:firstname => 'Cool', + :lastname => 'User', + :mail => 'user@somedomain.com', + :identity_url => 'http://openid.example.com/good_user', + :status => User::STATUS_REGISTERED) + existing_user.login = 'cool_user' + assert existing_user.save! + + post :login, :openid_url => existing_user.identity_url + assert_redirected_to '/login' + end + + def test_login_with_openid_with_new_user_created + Setting.self_registration = '3' + post :login, :openid_url => 'http://openid.example.com/good_user' + assert_redirected_to '/my/account' + user = User.find_by_login('cool_user') + assert user + assert_equal 'Cool', user.firstname + assert_equal 'User', user.lastname + end + + def test_login_with_openid_with_new_user_and_self_registration_off + Setting.self_registration = '0' + post :login, :openid_url => 'http://openid.example.com/good_user' + assert_redirected_to home_url + user = User.find_by_login('cool_user') + assert_nil user + end + + def test_login_with_openid_with_new_user_created_with_email_activation_should_have_a_token + Setting.self_registration = '1' + post :login, :openid_url => 'http://openid.example.com/good_user' + assert_redirected_to '/login' + user = User.find_by_login('cool_user') + assert user + + token = Token.find_by_user_id_and_action(user.id, 'register') + assert token + end + + def test_login_with_openid_with_new_user_created_with_manual_activation + Setting.self_registration = '2' + post :login, :openid_url => 'http://openid.example.com/good_user' + assert_redirected_to '/login' + user = User.find_by_login('cool_user') + assert user + assert_equal User::STATUS_REGISTERED, user.status + end + + def test_login_with_openid_with_new_user_with_conflict_should_register + Setting.self_registration = '3' + existing_user = User.new(:firstname => 'Cool', :lastname => 'User', :mail => 'user@somedomain.com') + existing_user.login = 'cool_user' + assert existing_user.save! + + post :login, :openid_url => 'http://openid.example.com/good_user' + assert_response :success + assert_template 'register' + assert assigns(:user) + assert_equal 'http://openid.example.com/good_user', assigns(:user)[:identity_url] + end + + def test_login_with_openid_with_new_user_with_missing_information_should_register + Setting.self_registration = '3' + + post :login, :openid_url => 'http://openid.example.com/good_blank_user' + assert_response :success + assert_template 'register' + assert assigns(:user) + assert_equal 'http://openid.example.com/good_blank_user', assigns(:user)[:identity_url] + + assert_select 'input[name=?]', 'user[login]' + assert_select 'input[name=?]', 'user[password]' + assert_select 'input[name=?]', 'user[password_confirmation]' + assert_select 'input[name=?][value=?]', 'user[identity_url]', 'http://openid.example.com/good_blank_user' + end + + def test_post_login_should_not_verify_token_when_using_open_id + ActionController::Base.allow_forgery_protection = true + AccountController.any_instance.stubs(:using_open_id?).returns(true) + AccountController.any_instance.stubs(:authenticate_with_open_id).returns(true) + post :login + assert_response 200 + ensure + ActionController::Base.allow_forgery_protection = false + end + + def test_register_after_login_failure_should_not_require_user_to_enter_a_password + Setting.self_registration = '3' + + assert_difference 'User.count' do + post :register, :user => { + :login => 'good_blank_user', + :password => '', + :password_confirmation => '', + :firstname => 'Cool', + :lastname => 'User', + :mail => 'user@somedomain.com', + :identity_url => 'http://openid.example.com/good_blank_user' + } + assert_response 302 + end + + user = User.first(:order => 'id DESC') + assert_equal 'http://openid.example.com/good_blank_user', user.identity_url + assert user.hashed_password.blank?, "Hashed password was #{user.hashed_password}" + end + + def test_setting_openid_should_return_true_when_set_to_true + assert_equal true, Setting.openid? + end + + else + puts "Skipping openid tests." + + def test_dummy + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c3/c38aab00aa8a965e8166d0c70c2dcf7d79174471.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c38aab00aa8a965e8166d0c70c2dcf7d79174471.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,100 @@ +<%= error_messages_for 'project' %> + +
    + +

    <%= f.text_field :name, :required => true, :size => 60 %>

    + +

    <%= f.text_area :description, :rows => 8, :class => 'wiki-edit' %>

    +

    <%= f.text_field :identifier, :required => true, :size => 60, :disabled => @project.identifier_frozen?, :maxlength => Project::IDENTIFIER_MAX_LENGTH %> +<% unless @project.identifier_frozen? %> + <%= l(:text_length_between, :min => 1, :max => Project::IDENTIFIER_MAX_LENGTH) %> <%= l(:text_project_identifier_info).html_safe %> +<% end %>

    +

    <%= f.text_field :homepage, :size => 60 %>

    +

    <%= f.check_box :is_public %>

    + +<% unless @project.allowed_parents.compact.empty? %> +

    <%= label(:project, :parent_id, l(:field_parent)) %><%= parent_project_select_tag(@project) %>

    +<% end %> + +<% if @project.safe_attribute? 'inherit_members' %> +

    <%= f.check_box :inherit_members %>

    +<% end %> + +<%= wikitoolbar_for 'project_description' %> + +<% @project.custom_field_values.each do |value| %> +

    <%= custom_field_tag_with_label :project, value %>

    +<% end %> +<%= call_hook(:view_projects_form, :project => @project, :form => f) %> +
    + +<% if @project.new_record? %> +
    <%= l(:label_module_plural) %> +<% Redmine::AccessControl.available_project_modules.each do |m| %> + +<% end %> +<%= hidden_field_tag 'project[enabled_module_names][]', '' %> +
    +<% end %> + +<% if @project.new_record? || @project.module_enabled?('issue_tracking') %> +<% unless @trackers.empty? %> +
    <%=l(:label_tracker_plural)%> +<% @trackers.each do |tracker| %> + +<% end %> +<%= hidden_field_tag 'project[tracker_ids][]', '' %> +
    +<% end %> + +<% unless @issue_custom_fields.empty? %> +
    <%=l(:label_custom_field_plural)%> +<% @issue_custom_fields.each do |custom_field| %> + +<% end %> +<%= hidden_field_tag 'project[issue_custom_field_ids][]', '' %> +
    +<% end %> +<% end %> + + +<% unless @project.identifier_frozen? %> + <% content_for :header_tags do %> + <%= javascript_include_tag 'project_identifier' %> + <% end %> +<% end %> + +<% if !User.current.admin? && @project.inherit_members? && @project.parent && User.current.member_of?(@project.parent) %> + <%= javascript_tag do %> + $(document).ready(function() { + $("#project_inherit_members").change(function(){ + if (!$(this).is(':checked')) { + if (!confirm("<%= escape_javascript(l(:text_own_membership_delete_confirmation)) %>")) { + $("#project_inherit_members").attr("checked", true); + } + } + }); + }); + <% end %> +<% end %> + +<%= javascript_tag do %> +$(document).ready(function() { + $('#project_enabled_module_names_issue_tracking').on('change', function(){ + if ($(this).attr('checked')){ + $('#project_trackers, #project_issue_custom_fields').show(); + } else { + $('#project_trackers, #project_issue_custom_fields').hide(); + } + }).trigger('change'); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c3/c3a64cc995ecf5031ca7af6e71034d712ca5328d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c3a64cc995ecf5031ca7af6e71034d712ca5328d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,364 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::UnifiedDiffTest < ActiveSupport::TestCase + def test_subversion_diff + diff = Redmine::UnifiedDiff.new(read_diff_fixture('subversion.diff')) + # number of files + assert_equal 4, diff.size + assert diff.detect {|file| file.file_name =~ %r{^config/settings.yml}} + end + + def test_truncate_diff + diff = Redmine::UnifiedDiff.new(read_diff_fixture('subversion.diff'), :max_lines => 20) + assert_equal 2, diff.size + end + + def test_inline_partials + diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff')) + assert_equal 1, diff.size + diff = diff.first + assert_equal 43, diff.size + + assert_equal [51, -1], diff[0].offsets + assert_equal [51, -1], diff[1].offsets + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', diff[0].html_line + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing xx', diff[1].html_line + + assert_nil diff[2].offsets + assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[2].html_line + + assert_equal [0, -14], diff[3].offsets + assert_equal [0, -14], diff[4].offsets + assert_equal 'Ut sed auctor justo', diff[3].html_line + assert_equal 'xxx auctor justo', diff[4].html_line + + assert_equal [13, -19], diff[6].offsets + assert_equal [13, -19], diff[7].offsets + + assert_equal [24, -8], diff[9].offsets + assert_equal [24, -8], diff[10].offsets + + assert_equal [37, -1], diff[12].offsets + assert_equal [37, -1], diff[13].offsets + + assert_equal [0, -38], diff[15].offsets + assert_equal [0, -38], diff[16].offsets + end + + def test_side_by_side_partials + diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff'), :type => 'sbs') + assert_equal 1, diff.size + diff = diff.first + assert_equal 32, diff.size + + assert_equal [51, -1], diff[0].offsets + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', diff[0].html_line_left + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing xx', diff[0].html_line_right + + assert_nil diff[1].offsets + assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_left + assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_right + + assert_equal [0, -14], diff[2].offsets + assert_equal 'Ut sed auctor justo', diff[2].html_line_left + assert_equal 'xxx auctor justo', diff[2].html_line_right + + assert_equal [13, -19], diff[4].offsets + assert_equal [24, -8], diff[6].offsets + assert_equal [37, -1], diff[8].offsets + assert_equal [0, -38], diff[10].offsets + + end + + def test_partials_with_html_entities + raw = <<-DIFF +--- test.orig.txt Wed Feb 15 16:10:39 2012 ++++ test.new.txt Wed Feb 15 16:11:25 2012 +@@ -1,5 +1,5 @@ + Semicolons were mysteriously appearing in code diffs in the repository + +-void DoSomething(std::auto_ptr myObj) ++void DoSomething(const MyClass& myObj) + +DIFF + + diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs') + assert_equal 1, diff.size + assert_equal 'void DoSomething(std::auto_ptr<MyClass> myObj)', diff.first[2].html_line_left + assert_equal 'void DoSomething(const MyClass& myObj)', diff.first[2].html_line_right + + diff = Redmine::UnifiedDiff.new(raw, :type => 'inline') + assert_equal 1, diff.size + assert_equal 'void DoSomething(std::auto_ptr<MyClass> myObj)', diff.first[2].html_line + assert_equal 'void DoSomething(const MyClass& myObj)', diff.first[3].html_line + end + + def test_line_starting_with_dashes + diff = Redmine::UnifiedDiff.new(<<-DIFF +--- old.txt Wed Nov 11 14:24:58 2009 ++++ new.txt Wed Nov 11 14:25:02 2009 +@@ -1,8 +1,4 @@ +-Lines that starts with dashes: +- +------------------------- +--- file.c +------------------------- ++A line that starts with dashes: + + and removed. + +@@ -23,4 +19,4 @@ + + + +-Another chunk of change ++Another chunk of changes + +DIFF + ) + assert_equal 1, diff.size + end + + def test_one_line_new_files + diff = Redmine::UnifiedDiff.new(<<-DIFF +diff -r 000000000000 -r ea98b14f75f0 README1 +--- /dev/null ++++ b/README1 +@@ -0,0 +1,1 @@ ++test1 +diff -r 000000000000 -r ea98b14f75f0 README2 +--- /dev/null ++++ b/README2 +@@ -0,0 +1,1 @@ ++test2 +diff -r 000000000000 -r ea98b14f75f0 README3 +--- /dev/null ++++ b/README3 +@@ -0,0 +1,3 @@ ++test4 ++test5 ++test6 +diff -r 000000000000 -r ea98b14f75f0 README4 +--- /dev/null ++++ b/README4 +@@ -0,0 +1,3 @@ ++test4 ++test5 ++test6 +DIFF + ) + assert_equal 4, diff.size + assert_equal "README1", diff[0].file_name + end + + def test_both_git_diff + diff = Redmine::UnifiedDiff.new(<<-DIFF +# HG changeset patch +# User test +# Date 1348014182 -32400 +# Node ID d1c871b8ef113df7f1c56d41e6e3bfbaff976e1f +# Parent 180b6605936cdc7909c5f08b59746ec1a7c99b3e +modify test1.txt + +diff -r 180b6605936c -r d1c871b8ef11 test1.txt +--- a/test1.txt ++++ b/test1.txt +@@ -1,1 +1,1 @@ +-test1 ++modify test1 +DIFF + ) + assert_equal 1, diff.size + assert_equal "test1.txt", diff[0].file_name + end + + def test_include_a_b_slash + diff = Redmine::UnifiedDiff.new(<<-DIFF +--- test1.txt ++++ b/test02.txt +@@ -1 +0,0 @@ +-modify test1 +DIFF + ) + assert_equal 1, diff.size + assert_equal "b/test02.txt", diff[0].file_name + + diff = Redmine::UnifiedDiff.new(<<-DIFF +--- a/test1.txt ++++ a/test02.txt +@@ -1 +0,0 @@ +-modify test1 +DIFF + ) + assert_equal 1, diff.size + assert_equal "a/test02.txt", diff[0].file_name + + diff = Redmine::UnifiedDiff.new(<<-DIFF +--- a/test1.txt ++++ test02.txt +@@ -1 +0,0 @@ +-modify test1 +DIFF + ) + assert_equal 1, diff.size + assert_equal "test02.txt", diff[0].file_name + end + + def test_utf8_ja + ja = " text_tip_issue_end_day: " + ja += "\xe3\x81\x93\xe3\x81\xae\xe6\x97\xa5\xe3\x81\xab\xe7\xb5\x82\xe4\xba\x86\xe3\x81\x99\xe3\x82\x8b\xe3\x82\xbf\xe3\x82\xb9\xe3\x82\xaf" + ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new(read_diff_fixture('issue-12641-ja.diff'), :type => 'inline') + assert_equal 1, diff.size + assert_equal 12, diff.first.size + assert_equal ja, diff.first[4].html_line_left + end + end + + def test_utf8_ru + ru = " other: "\xd0\xbe\xd0\xba\xd0\xbe\xd0\xbb\xd0\xbe %{count} \xd1\x87\xd0\xb0\xd1\x81\xd0\xb0"" + ru.force_encoding('UTF-8') if ru.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new(read_diff_fixture('issue-12641-ru.diff'), :type => 'inline') + assert_equal 1, diff.size + assert_equal 8, diff.first.size + assert_equal ru, diff.first[3].html_line_left + end + end + + def test_offset_range_ascii_1 + raw = <<-DIFF +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-abc ++abcd + bbbb +DIFF + diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal "abc", diff.first[1].html_line_left + assert_equal "abcd", diff.first[1].html_line_right + end + + def test_offset_range_ascii_2 + raw = <<-DIFF +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-abc ++zabc + bbbb +DIFF + diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal "abc", diff.first[1].html_line_left + assert_equal "zabc", diff.first[1].html_line_right + end + + def test_offset_range_japanese_1 + ja1 = "\xe6\x97\xa5\xe6\x9c\xac" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-1.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + def test_offset_range_japanese_2 + ja1 = "\xe6\x97\xa5\xe6\x9c\xac" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe3\x81\xab\xe3\x81\xa3\xe3\x81\xbd\xe3\x82\x93\xe6\x97\xa5\xe6\x9c\xac" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-2.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + def test_offset_range_japanese_3 + # UTF-8 The 1st byte differs. + ja1 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xa8\x98" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe5\xa8\x98" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-3.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + def test_offset_range_japanese_4 + # UTF-8 The 2nd byte differs. + ja1 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xa8\x98" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x98" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-4.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + def test_offset_range_japanese_5 + # UTF-8 The 2nd byte differs. + ja1 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xa8\x98ok" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x98ok" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-5.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + private + + def read_diff_fixture(filename) + File.new(File.join(File.dirname(__FILE__), '/../../../fixtures/diffs', filename)).read + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c3/c3c5375fc5b9a4879f32c02c4bdbcca621876a10.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c3c5375fc5b9a4879f32c02c4bdbcca621876a10.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1098 @@ +ro: + direction: ltr + date: + formats: + default: "%d-%m-%Y" + short: "%d %b" + long: "%d %B %Y" + only_day: "%e" + + day_names: [Duminică, Luni, Marti, Miercuri, Joi, Vineri, Sâmbătă] + abbr_day_names: [Dum, Lun, Mar, Mie, Joi, Vin, Sâm] + + month_names: [~, Ianuarie, Februarie, Martie, Aprilie, Mai, Iunie, Iulie, August, Septembrie, Octombrie, Noiembrie, Decembrie] + abbr_month_names: [~, Ian, Feb, Mar, Apr, Mai, Iun, Iul, Aug, Sep, Oct, Noi, Dec] + order: + - :day + - :month + - :year + + time: + formats: + default: "%m/%d/%Y %I:%M %p" + time: "%I:%M %p" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "jumătate de minut" + less_than_x_seconds: + one: "mai puțin de o secundă" + other: "mai puțin de %{count} secunde" + x_seconds: + one: "o secundă" + other: "%{count} secunde" + less_than_x_minutes: + one: "mai puțin de un minut" + other: "mai puțin de %{count} minute" + x_minutes: + one: "un minut" + other: "%{count} minute" + about_x_hours: + one: "aproximativ o oră" + other: "aproximativ %{count} ore" + x_hours: + one: "1 oră" + other: "%{count} ore" + x_days: + one: "o zi" + other: "%{count} zile" + about_x_months: + one: "aproximativ o lună" + other: "aproximativ %{count} luni" + x_months: + one: "o luna" + other: "%{count} luni" + about_x_years: + one: "aproximativ un an" + other: "aproximativ %{count} ani" + over_x_years: + one: "peste un an" + other: "peste %{count} ani" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + + human: + format: + precision: 3 + delimiter: "" + storage_units: + format: "%n %u" + units: + kb: KB + tb: TB + gb: GB + byte: + one: Byte + other: Bytes + mb: MB + +# Used in array.to_sentence. + support: + array: + sentence_connector: "și" + skip_last_comma: true + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "nu este inclus în listă" + exclusion: "este rezervat" + invalid: "nu este valid" + confirmation: "nu este identică" + accepted: "trebuie acceptat" + empty: "trebuie completat" + blank: "nu poate fi gol" + too_long: "este prea lung" + too_short: "este prea scurt" + wrong_length: "nu are lungimea corectă" + taken: "a fost luat deja" + not_a_number: "nu este un număr" + not_a_date: "nu este o dată validă" + greater_than: "trebuie să fie mai mare de %{count}" + greater_than_or_equal_to: "trebuie să fie mai mare sau egal cu %{count}" + equal_to: "trebuie să fie egal cu {count}}" + less_than: "trebuie să fie mai mic decat %{count}" + less_than_or_equal_to: "trebuie să fie mai mic sau egal cu %{count}" + odd: "trebuie să fie impar" + even: "trebuie să fie par" + greater_than_start_date: "trebuie să fie după data de început" + not_same_project: "trebuie să aparțină aceluiași proiect" + circular_dependency: "Această relație ar crea o dependență circulară" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Selectați + + general_text_No: 'Nu' + general_text_Yes: 'Da' + general_text_no: 'nu' + general_text_yes: 'da' + general_lang_name: 'Română' + general_csv_separator: '.' + general_csv_decimal_separator: ',' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '2' + + notice_account_updated: Cont actualizat. + notice_account_invalid_creditentials: Utilizator sau parola nevalidă + notice_account_password_updated: Parolă actualizată. + notice_account_wrong_password: Parolă greșită + notice_account_register_done: Contul a fost creat. Pentru activare, urmați legătura trimisă prin email. + notice_account_unknown_email: Utilizator necunoscut. + notice_can_t_change_password: Acest cont folosește o sursă externă de autentificare. Nu se poate schimba parola. + notice_account_lost_email_sent: S-a trimis un email cu instrucțiuni de schimbare a parolei. + notice_account_activated: Contul a fost activat. Vă puteți autentifica acum. + notice_successful_create: Creat. + notice_successful_update: Actualizat. + notice_successful_delete: Șters. + notice_successful_connection: Conectat. + notice_file_not_found: Pagina pe care doriți să o accesați nu există sau a fost ștearsă. + notice_locking_conflict: Datele au fost actualizate de alt utilizator. + notice_not_authorized: Nu sunteți autorizat sa accesați această pagină. + notice_email_sent: "S-a trimis un email către %{value}" + notice_email_error: "A intervenit o eroare la trimiterea de email (%{value})" + notice_feeds_access_key_reseted: Cheia de acces Atom a fost resetată. + notice_failed_to_save_issues: "Nu s-au putut salva %{count} tichete din cele %{total} selectate: %{ids}." + notice_no_issue_selected: "Niciun tichet selectat! Vă rugăm să selectați tichetele pe care doriți să le editați." + notice_account_pending: "Contul dumneavoastră a fost creat și așteaptă aprobarea administratorului." + notice_default_data_loaded: S-a încărcat configurația implicită. + notice_unable_delete_version: Nu se poate șterge versiunea. + + error_can_t_load_default_data: "Nu s-a putut încărca configurația implicită: %{value}" + error_scm_not_found: "Nu s-a găsit articolul sau revizia în depozit." + error_scm_command_failed: "A intervenit o eroare la accesarea depozitului: %{value}" + error_scm_annotate: "Nu există sau nu poate fi adnotată." + error_issue_not_found_in_project: 'Tichetul nu a fost găsit sau nu aparține acestui proiect' + + warning_attachments_not_saved: "Nu s-au putut salva %{count} fișiere." + + mail_subject_lost_password: "Parola dumneavoastră: %{value}" + mail_body_lost_password: 'Pentru a schimba parola, accesați:' + mail_subject_register: "Activarea contului %{value}" + mail_body_register: 'Pentru activarea contului, accesați:' + mail_body_account_information_external: "Puteți folosi contul „{value}}†pentru a vă autentifica." + mail_body_account_information: Informații despre contul dumneavoastră + mail_subject_account_activation_request: "Cerere de activare a contului %{value}" + mail_body_account_activation_request: "S-a înregistrat un utilizator nou (%{value}). Contul așteaptă aprobarea dumneavoastră:" + mail_subject_reminder: "%{count} tichete trebuie rezolvate în următoarele %{days} zile" + mail_body_reminder: "%{count} tichete atribuite dumneavoastră trebuie rezolvate în următoarele %{days} zile:" + + + field_name: Nume + field_description: Descriere + field_summary: Rezumat + field_is_required: Obligatoriu + field_firstname: Prenume + field_lastname: Nume + field_mail: Email + field_filename: Fișier + field_filesize: Mărime + field_downloads: Descărcări + field_author: Autor + field_created_on: Creat la + field_updated_on: Actualizat la + field_field_format: Format + field_is_for_all: Pentru toate proiectele + field_possible_values: Valori posibile + field_regexp: Expresie regulară + field_min_length: lungime minimă + field_max_length: lungime maximă + field_value: Valoare + field_category: Categorie + field_title: Titlu + field_project: Proiect + field_issue: Tichet + field_status: Stare + field_notes: Note + field_is_closed: Rezolvat + field_is_default: Implicit + field_tracker: Tip de tichet + field_subject: Subiect + field_due_date: Data finalizării + field_assigned_to: Atribuit + field_priority: Prioritate + field_fixed_version: Versiune țintă + field_user: Utilizator + field_role: Rol + field_homepage: Pagina principală + field_is_public: Public + field_parent: Sub-proiect al + field_is_in_roadmap: Tichete afișate în plan + field_login: Autentificare + field_mail_notification: Notificări prin e-mail + field_admin: Administrator + field_last_login_on: Ultima autentificare în + field_language: Limba + field_effective_date: Data + field_password: Parola + field_new_password: Parola nouă + field_password_confirmation: Confirmare + field_version: Versiune + field_type: Tip + field_host: Gazdă + field_port: Port + field_account: Cont + field_base_dn: Base DN + field_attr_login: Atribut autentificare + field_attr_firstname: Atribut prenume + field_attr_lastname: Atribut nume + field_attr_mail: Atribut email + field_onthefly: Creare utilizator pe loc + field_start_date: Data începerii + field_done_ratio: Realizat (%) + field_auth_source: Mod autentificare + field_hide_mail: Nu se afișează adresa de email + field_comments: Comentariu + field_url: URL + field_start_page: Pagina de start + field_subproject: Subproiect + field_hours: Ore + field_activity: Activitate + field_spent_on: Data + field_identifier: Identificator + field_is_filter: Filtru + field_issue_to: Tichet asociat + field_delay: Întârziere + field_assignable: Se pot atribui tichete acestui rol + field_redirect_existing_links: Redirecționează legăturile existente + field_estimated_hours: Timp estimat + field_column_names: Coloane + field_time_zone: Fus orar + field_searchable: Căutare + field_default_value: Valoare implicita + field_comments_sorting: Afișează comentarii + field_parent_title: Pagina superioara + field_editable: Modificabil + field_watcher: Urmărește + field_identity_url: URL OpenID + field_content: Conținut + + setting_app_title: Titlu aplicație + setting_app_subtitle: Subtitlu aplicație + setting_welcome_text: Text de întâmpinare + setting_default_language: Limba implicita + setting_login_required: Necesita autentificare + setting_self_registration: Înregistrare automată + setting_attachment_max_size: Mărime maxima atașament + setting_issues_export_limit: Limită de tichete exportate + setting_mail_from: Adresa de email a expeditorului + setting_bcc_recipients: Alți destinatari pentru email (BCC) + setting_plain_text_mail: Mesaje text (fără HTML) + setting_host_name: Numele gazdei și calea + setting_text_formatting: Formatare text + setting_wiki_compression: Comprimare istoric Wiki + setting_feeds_limit: Limita de actualizări din feed + setting_default_projects_public: Proiectele noi sunt implicit publice + setting_autofetch_changesets: Preluare automată a modificărilor din depozit + setting_sys_api_enabled: Activare WS pentru gestionat depozitul + setting_commit_ref_keywords: Cuvinte cheie pt. referire tichet + setting_commit_fix_keywords: Cuvinte cheie pt. rezolvare tichet + setting_autologin: Autentificare automată + setting_date_format: Format dată + setting_time_format: Format oră + setting_cross_project_issue_relations: Permite legături de tichete între proiecte + setting_issue_list_default_columns: Coloane implicite afișate în lista de tichete + setting_emails_footer: Subsol email + setting_protocol: Protocol + setting_per_page_options: Număr de obiecte pe pagină + setting_user_format: Stil de afișare pentru utilizator + setting_activity_days_default: Se afișează zile în jurnalul proiectului + setting_display_subprojects_issues: Afișează implicit tichetele sub-proiectelor în proiectele principale + setting_enabled_scm: SCM activat + setting_mail_handler_api_enabled: Activare WS pentru email primit + setting_mail_handler_api_key: cheie API + setting_sequential_project_identifiers: Generează secvențial identificatoarele de proiect + setting_gravatar_enabled: Folosește poze Gravatar pentru utilizatori + setting_diff_max_lines_displayed: Număr maxim de linii de diferență afișate + setting_file_max_size_displayed: Număr maxim de fișiere text afișate în pagină (inline) + setting_repository_log_display_limit: Număr maxim de revizii afișate în istoricul fișierului + setting_openid: Permite înregistrare și autentificare cu OpenID + + permission_edit_project: Editează proiectul + permission_select_project_modules: Alege module pentru proiect + permission_manage_members: Editează membri + permission_manage_versions: Editează versiuni + permission_manage_categories: Editează categorii + permission_add_issues: Adaugă tichete + permission_edit_issues: Editează tichete + permission_manage_issue_relations: Editează relații tichete + permission_add_issue_notes: Adaugă note + permission_edit_issue_notes: Editează note + permission_edit_own_issue_notes: Editează notele proprii + permission_move_issues: Mută tichete + permission_delete_issues: Șterge tichete + permission_manage_public_queries: Editează căutările implicite + permission_save_queries: Salvează căutările + permission_view_gantt: Afișează Gantt + permission_view_calendar: Afișează calendarul + permission_view_issue_watchers: Afișează lista de persoane interesate + permission_add_issue_watchers: Adaugă persoane interesate + permission_log_time: Înregistrează timpul de lucru + permission_view_time_entries: Afișează timpul de lucru + permission_edit_time_entries: Editează jurnalele cu timp de lucru + permission_edit_own_time_entries: Editează jurnalele proprii cu timpul de lucru + permission_manage_news: Editează știri + permission_comment_news: Comentează știrile + permission_view_documents: Afișează documente + permission_manage_files: Editează fișiere + permission_view_files: Afișează fișiere + permission_manage_wiki: Editează wiki + permission_rename_wiki_pages: Redenumește pagini wiki + permission_delete_wiki_pages: Șterge pagini wiki + permission_view_wiki_pages: Afișează wiki + permission_view_wiki_edits: Afișează istoricul wiki + permission_edit_wiki_pages: Editează pagini wiki + permission_delete_wiki_pages_attachments: Șterge atașamente + permission_protect_wiki_pages: Blochează pagini wiki + permission_manage_repository: Gestionează depozitul + permission_browse_repository: Răsfoiește depozitul + permission_view_changesets: Afișează modificările din depozit + permission_commit_access: Acces commit + permission_manage_boards: Editează forum + permission_view_messages: Afișează mesaje + permission_add_messages: Scrie mesaje + permission_edit_messages: Editează mesaje + permission_edit_own_messages: Editează mesajele proprii + permission_delete_messages: Șterge mesaje + permission_delete_own_messages: Șterge mesajele proprii + + project_module_issue_tracking: Tichete + project_module_time_tracking: Timp de lucru + project_module_news: Știri + project_module_documents: Documente + project_module_files: Fișiere + project_module_wiki: Wiki + project_module_repository: Depozit + project_module_boards: Forum + + label_user: Utilizator + label_user_plural: Utilizatori + label_user_new: Utilizator nou + label_project: Proiect + label_project_new: Proiect nou + label_project_plural: Proiecte + label_x_projects: + zero: niciun proiect + one: un proiect + other: "%{count} proiecte" + label_project_all: Toate proiectele + label_project_latest: Proiecte noi + label_issue: Tichet + label_issue_new: Tichet nou + label_issue_plural: Tichete + label_issue_view_all: Afișează toate tichetele + label_issues_by: "Sortează după %{value}" + label_issue_added: Adaugat + label_issue_updated: Actualizat + label_document: Document + label_document_new: Document nou + label_document_plural: Documente + label_document_added: Adăugat + label_role: Rol + label_role_plural: Roluri + label_role_new: Rol nou + label_role_and_permissions: Roluri și permisiuni + label_member: Membru + label_member_new: membru nou + label_member_plural: Membri + label_tracker: Tip de tichet + label_tracker_plural: Tipuri de tichete + label_tracker_new: Tip nou de tichet + label_workflow: Mod de lucru + label_issue_status: Stare tichet + label_issue_status_plural: Stare tichete + label_issue_status_new: Stare nouă + label_issue_category: Categorie de tichet + label_issue_category_plural: Categorii de tichete + label_issue_category_new: Categorie nouă + label_custom_field: Câmp personalizat + label_custom_field_plural: Câmpuri personalizate + label_custom_field_new: Câmp nou personalizat + label_enumerations: Enumerări + label_enumeration_new: Valoare nouă + label_information: Informație + label_information_plural: Informații + label_please_login: Vă rugăm să vă autentificați + label_register: Înregistrare + label_login_with_open_id_option: sau autentificare cu OpenID + label_password_lost: Parolă uitată + label_home: Acasă + label_my_page: Pagina mea + label_my_account: Contul meu + label_my_projects: Proiectele mele + label_administration: Administrare + label_login: Autentificare + label_logout: Ieșire din cont + label_help: Ajutor + label_reported_issues: Tichete + label_assigned_to_me_issues: Tichetele mele + label_last_login: Ultima conectare + label_registered_on: Înregistrat la + label_activity: Activitate + label_overall_activity: Activitate - vedere de ansamblu + label_user_activity: "Activitate %{value}" + label_new: Nou + label_logged_as: Autentificat ca + label_environment: Mediu + label_authentication: Autentificare + label_auth_source: Mod de autentificare + label_auth_source_new: Nou + label_auth_source_plural: Moduri de autentificare + label_subproject_plural: Sub-proiecte + label_and_its_subprojects: "%{value} și sub-proiecte" + label_min_max_length: lungime min - max + label_list: Listă + label_date: Dată + label_integer: Întreg + label_float: Zecimal + label_boolean: Valoare logică + label_string: Text + label_text: Text lung + label_attribute: Atribut + label_attribute_plural: Atribute + label_no_data: Nu există date de afișat + label_change_status: Schimbă starea + label_history: Istoric + label_attachment: Fișier + label_attachment_new: Fișier nou + label_attachment_delete: Șterge fișier + label_attachment_plural: Fișiere + label_file_added: Adăugat + label_report: Raport + label_report_plural: Rapoarte + label_news: Știri + label_news_new: Adaugă știre + label_news_plural: Știri + label_news_latest: Ultimele știri + label_news_view_all: Afișează toate știrile + label_news_added: Adăugat + label_settings: Setări + label_overview: Pagină proiect + label_version: Versiune + label_version_new: Versiune nouă + label_version_plural: Versiuni + label_confirmation: Confirmare + label_export_to: 'Disponibil și în:' + label_read: Citește... + label_public_projects: Proiecte publice + label_open_issues: deschis + label_open_issues_plural: deschise + label_closed_issues: închis + label_closed_issues_plural: închise + label_x_open_issues_abbr_on_total: + zero: 0 deschise / %{total} + one: 1 deschis / %{total} + other: "%{count} deschise / %{total}" + label_x_open_issues_abbr: + zero: 0 deschise + one: 1 deschis + other: "%{count} deschise" + label_x_closed_issues_abbr: + zero: 0 închise + one: 1 închis + other: "%{count} închise" + label_total: Total + label_permissions: Permisiuni + label_current_status: Stare curentă + label_new_statuses_allowed: Stări noi permise + label_all: toate + label_none: niciunul + label_nobody: nimeni + label_next: Înainte + label_previous: Înapoi + label_used_by: Folosit de + label_details: Detalii + label_add_note: Adaugă o notă + label_per_page: pe pagină + label_calendar: Calendar + label_months_from: luni de la + label_gantt: Gantt + label_internal: Intern + label_last_changes: "ultimele %{count} schimbări" + label_change_view_all: Afișează toate schimbările + label_personalize_page: Personalizează aceasta pagina + label_comment: Comentariu + label_comment_plural: Comentarii + label_x_comments: + zero: fara comentarii + one: 1 comentariu + other: "%{count} comentarii" + label_comment_add: Adaugă un comentariu + label_comment_added: Adăugat + label_comment_delete: Șterge comentariul + label_query: Cautare personalizata + label_query_plural: Căutări personalizate + label_query_new: Căutare nouă + label_filter_add: Adaugă filtru + label_filter_plural: Filtre + label_equals: este + label_not_equals: nu este + label_in_less_than: în mai puțin de + label_in_more_than: în mai mult de + label_in: în + label_today: astăzi + label_all_time: oricând + label_yesterday: ieri + label_this_week: săptămâna aceasta + label_last_week: săptămâna trecută + label_last_n_days: "ultimele %{count} zile" + label_this_month: luna aceasta + label_last_month: luna trecută + label_this_year: anul acesta + label_date_range: Perioada + label_less_than_ago: mai puțin de ... zile + label_more_than_ago: mai mult de ... zile + label_ago: în urma + label_contains: conține + label_not_contains: nu conține + label_day_plural: zile + label_repository: Depozit + label_repository_plural: Depozite + label_browse: Afișează + label_revision: Revizie + label_revision_plural: Revizii + label_associated_revisions: Revizii asociate + label_added: adaugată + label_modified: modificată + label_copied: copiată + label_renamed: redenumită + label_deleted: ștearsă + label_latest_revision: Ultima revizie + label_latest_revision_plural: Ultimele revizii + label_view_revisions: Afișează revizii + label_max_size: Mărime maximă + label_sort_highest: Prima + label_sort_higher: În sus + label_sort_lower: În jos + label_sort_lowest: Ultima + label_roadmap: Planificare + label_roadmap_due_in: "De terminat în %{value}" + label_roadmap_overdue: "Întârziat cu %{value}" + label_roadmap_no_issues: Nu există tichete pentru această versiune + label_search: Caută + label_result_plural: Rezultate + label_all_words: toate cuvintele + label_wiki: Wiki + label_wiki_edit: Editare Wiki + label_wiki_edit_plural: Editări Wiki + label_wiki_page: Pagină Wiki + label_wiki_page_plural: Pagini Wiki + label_index_by_title: Sortează după titlu + label_index_by_date: Sortează după dată + label_current_version: Versiunea curentă + label_preview: Previzualizare + label_feed_plural: Feed-uri + label_changes_details: Detaliile tuturor schimbărilor + label_issue_tracking: Urmărire tichete + label_spent_time: Timp alocat + label_f_hour: "%{value} oră" + label_f_hour_plural: "%{value} ore" + label_time_tracking: Urmărire timp de lucru + label_change_plural: Schimbări + label_statistics: Statistici + label_commits_per_month: Commit pe luna + label_commits_per_author: Commit per autor + label_view_diff: Afișează diferențele + label_diff_inline: în linie + label_diff_side_by_side: una lângă alta + label_options: Opțiuni + label_copy_workflow_from: Copiază modul de lucru de la + label_permissions_report: Permisiuni + label_watched_issues: Tichete urmărite + label_related_issues: Tichete asociate + label_applied_status: Stare aplicată + label_loading: Încarcă... + label_relation_new: Asociere nouă + label_relation_delete: Șterge asocierea + label_relates_to: asociat cu + label_duplicates: duplicate + label_duplicated_by: la fel ca + label_blocks: blocări + label_blocked_by: blocat de + label_precedes: precede + label_follows: urmează + label_end_to_start: de la sfârșit la început + label_end_to_end: de la sfârșit la sfârșit + label_start_to_start: de la început la început + label_start_to_end: de la început la sfârșit + label_stay_logged_in: Păstrează autentificarea + label_disabled: dezactivat + label_show_completed_versions: Arată versiunile terminate + label_me: eu + label_board: Forum + label_board_new: Forum nou + label_board_plural: Forumuri + label_topic_plural: Subiecte + label_message_plural: Mesaje + label_message_last: Ultimul mesaj + label_message_new: Mesaj nou + label_message_posted: Adăugat + label_reply_plural: Răspunsuri + label_send_information: Trimite utilizatorului informațiile despre cont + label_year: An + label_month: Lună + label_week: Săptămână + label_date_from: De la + label_date_to: La + label_language_based: Un funcție de limba de afișare a utilizatorului + label_sort_by: "Sortează după %{value}" + label_send_test_email: Trimite email de test + label_feeds_access_key_created_on: "Cheie de acces creată acum %{value}" + label_module_plural: Module + label_added_time_by: "Adăugat de %{author} acum %{age}" + label_updated_time_by: "Actualizat de %{author} acum %{age}" + label_updated_time: "Actualizat acum %{value}" + label_jump_to_a_project: Alege proiectul... + label_file_plural: Fișiere + label_changeset_plural: Schimbări + label_default_columns: Coloane implicite + label_no_change_option: (fără schimbări) + label_bulk_edit_selected_issues: Editează toate tichetele selectate + label_theme: Tema + label_default: Implicită + label_search_titles_only: Caută numai în titluri + label_user_mail_option_all: "Pentru orice eveniment, în toate proiectele mele" + label_user_mail_option_selected: " Pentru orice eveniment, în proiectele selectate..." + label_user_mail_no_self_notified: "Nu trimite notificări pentru modificările mele" + label_registration_activation_by_email: activare cont prin email + label_registration_manual_activation: activare manuală a contului + label_registration_automatic_activation: activare automată a contului + label_display_per_page: "pe pagină: %{value}" + label_age: vechime + label_change_properties: Schimbă proprietățile + label_general: General + label_more: Mai mult + label_scm: SCM + label_plugins: Plugin-uri + label_ldap_authentication: autentificare LDAP + label_downloads_abbr: D/L + label_optional_description: Descriere (opțională) + label_add_another_file: Adaugă alt fișier + label_preferences: Preferințe + label_chronological_order: în ordine cronologică + label_reverse_chronological_order: În ordine invers cronologică + label_planning: Planificare + label_incoming_emails: Mesaje primite + label_generate_key: Generează o cheie + label_issue_watchers: Cine urmărește + label_example: Exemplu + label_display: Afișează + + label_sort: Sortează + label_ascending: Crescător + label_descending: Descrescător + label_date_from_to: De la %{start} la %{end} + + button_login: Autentificare + button_submit: Trimite + button_save: Salvează + button_check_all: Bifează tot + button_uncheck_all: Debifează tot + button_delete: Șterge + button_create: Creează + button_create_and_continue: Creează și continua + button_test: Testează + button_edit: Editează + button_add: Adaugă + button_change: Modifică + button_apply: Aplică + button_clear: Șterge + button_lock: Blochează + button_unlock: Deblochează + button_download: Descarcă + button_list: Listează + button_view: Afișează + button_move: Mută + button_back: Înapoi + button_cancel: Anulează + button_activate: Activează + button_sort: Sortează + button_log_time: Înregistrează timpul de lucru + button_rollback: Revenire la această versiune + button_watch: Urmăresc + button_unwatch: Nu urmăresc + button_reply: Răspunde + button_archive: Arhivează + button_unarchive: Dezarhivează + button_reset: Resetează + button_rename: Redenumește + button_change_password: Schimbare parolă + button_copy: Copiază + button_annotate: Adnotează + button_update: Actualizează + button_configure: Configurează + button_quote: Citează + + status_active: activ + status_registered: înregistrat + status_locked: blocat + + text_select_mail_notifications: Selectați acțiunile notificate prin email. + text_regexp_info: ex. ^[A-Z0-9]+$ + text_min_max_length_info: 0 înseamnă fără restricții + text_project_destroy_confirmation: Sigur doriți să ștergeți proiectul și toate datele asociate? + text_subprojects_destroy_warning: "Se vor șterge și sub-proiectele: %{value}." + text_workflow_edit: Selectați un rol și un tip de tichet pentru a edita modul de lucru + text_are_you_sure: Sunteți sigur(ă)? + text_tip_issue_begin_day: sarcină care începe în această zi + text_tip_issue_end_day: sarcină care se termină în această zi + text_tip_issue_begin_end_day: sarcină care începe și se termină în această zi + text_caracters_maximum: "maxim %{count} caractere." + text_caracters_minimum: "Trebuie să fie minim %{count} caractere." + text_length_between: "Lungime între %{min} și %{max} caractere." + text_tracker_no_workflow: Nu sunt moduri de lucru pentru acest tip de tichet + text_unallowed_characters: Caractere nepermise + text_comma_separated: Sunt permise mai multe valori (separate cu virgulă). + text_issues_ref_in_commit_messages: Referire la tichete și rezolvare în textul mesajului + text_issue_added: "Tichetul %{id} a fost adăugat de %{author}." + text_issue_updated: "Tichetul %{id} a fost actualizat de %{author}." + text_wiki_destroy_confirmation: Sigur doriți ștergerea Wiki și a conținutului asociat? + text_issue_category_destroy_question: "Această categorie conține (%{count}) tichete. Ce doriți să faceți?" + text_issue_category_destroy_assignments: Șterge apartenența la categorie. + text_issue_category_reassign_to: Atribuie tichetele la această categorie + text_user_mail_option: "Pentru proiectele care nu sunt selectate, veți primi notificări doar pentru ceea ce urmăriți sau în ce sunteți implicat (ex: tichete create de dumneavoastră sau care vă sunt atribuite)." + text_no_configuration_data: "Nu s-au configurat încă rolurile, stările tichetelor și modurile de lucru.\nEste recomandat să încărcați configurația implicită. O veți putea modifica ulterior." + text_load_default_configuration: Încarcă configurația implicită + text_status_changed_by_changeset: "Aplicat în setul %{value}." + text_issues_destroy_confirmation: 'Sigur doriți să ștergeți tichetele selectate?' + text_select_project_modules: 'Selectați modulele active pentru acest proiect:' + text_default_administrator_account_changed: S-a schimbat contul administratorului implicit + text_file_repository_writable: Se poate scrie în directorul de atașamente + text_plugin_assets_writable: Se poate scrie în directorul de plugin-uri + text_rmagick_available: Este disponibil RMagick (opțional) + text_destroy_time_entries_question: "%{hours} ore sunt înregistrate la tichetele pe care doriți să le ștergeți. Ce doriți sa faceți?" + text_destroy_time_entries: Șterge orele înregistrate + text_assign_time_entries_to_project: Atribuie orele la proiect + text_reassign_time_entries: 'Atribuie orele înregistrate la tichetul:' + text_user_wrote: "%{value} a scris:" + text_enumeration_destroy_question: "Această valoare are %{count} obiecte." + text_enumeration_category_reassign_to: 'Atribuie la această valoare:' + text_email_delivery_not_configured: "Trimiterea de emailuri nu este configurată și ca urmare, notificările sunt dezactivate.\nConfigurați serverul SMTP în config/configuration.yml și reporniți aplicația pentru a le activa." + text_repository_usernames_mapping: "Selectați sau modificați contul Redmine echivalent contului din istoricul depozitului.\nUtilizatorii cu un cont (sau e-mail) identic în Redmine și depozit sunt echivalate automat." + text_diff_truncated: '... Comparația a fost trunchiată pentru ca depășește lungimea maximă de text care poate fi afișat.' + text_custom_field_possible_values_info: 'O linie pentru fiecare valoare' + + default_role_manager: Manager + default_role_developer: Dezvoltator + default_role_reporter: Creator de rapoarte + default_tracker_bug: Defect + default_tracker_feature: Funcție + default_tracker_support: Suport + default_issue_status_new: Nou + default_issue_status_in_progress: In Progress + default_issue_status_resolved: Rezolvat + default_issue_status_feedback: Așteaptă reacții + default_issue_status_closed: Închis + default_issue_status_rejected: Respins + default_doc_category_user: Documentație + default_doc_category_tech: Documentație tehnică + default_priority_low: mică + default_priority_normal: normală + default_priority_high: mare + default_priority_urgent: urgentă + default_priority_immediate: imediată + default_activity_design: Design + default_activity_development: Dezvoltare + + enumeration_issue_priorities: Priorități tichete + enumeration_doc_categories: Categorii documente + enumeration_activities: Activități (timp de lucru) + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Această pagină are %{descendants} pagini anterioare și descendenți. Ce doriți să faceți? + text_wiki_page_reassign_children: Atribuie paginile la această pagină + text_wiki_page_nullify_children: Menține paginile ca și pagini inițiale (root) + text_wiki_page_destroy_children: Șterge paginile și descendenții + setting_password_min_length: Lungime minimă parolă + field_group_by: Grupează după + mail_subject_wiki_content_updated: "Pagina wiki '%{id}' a fost actualizată" + label_wiki_content_added: Adăugat + mail_subject_wiki_content_added: "Pagina wiki '%{id}' a fost adăugată" + mail_body_wiki_content_added: Pagina wiki '%{id}' a fost adăugată de %{author}. + label_wiki_content_updated: Actualizat + mail_body_wiki_content_updated: Pagina wiki '%{id}' a fost actualizată de %{author}. + permission_add_project: Crează proiect + setting_new_project_user_role_id: Rol atribuit utilizatorului non-admin care crează un proiect. + label_view_all_revisions: Arată toate reviziile + label_tag: Tag + label_branch: Branch + error_no_tracker_in_project: Nu există un tracker asociat cu proiectul. Verificați vă rog setările proiectului. + error_no_default_issue_status: Nu există un status implicit al tichetelor. Verificați vă rog configurația (Mergeți la "Administrare -> Stări tichete"). + text_journal_changed: "%{label} schimbat din %{old} în %{new}" + text_journal_set_to: "%{label} setat ca %{value}" + text_journal_deleted: "%{label} șters (%{old})" + label_group_plural: Grupuri + label_group: Grup + label_group_new: Grup nou + label_time_entry_plural: Timp alocat + text_journal_added: "%{label} %{value} added" + field_active: Active + enumeration_system_activity: System Activity + permission_delete_issue_watchers: Delete watchers + version_status_closed: closed + version_status_locked: locked + version_status_open: open + error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened + label_user_anonymous: Anonymous + button_move_and_follow: Move and follow + setting_default_projects_modules: Default enabled modules for new projects + setting_gravatar_default: Default Gravatar image + field_sharing: Sharing + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_system: With all projects + label_version_sharing_descendants: With subprojects + label_version_sharing_tree: With project tree + label_version_sharing_none: Not shared + error_can_not_archive_project: This project can not be archived + button_duplicate: Duplicate + button_copy_and_follow: Copy and follow + label_copy_source: Source + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_status: Use the issue status + error_issue_done_ratios_not_updated: Issue done ratios not updated. + error_workflow_copy_target: Please select target tracker(s) and role(s) + setting_issue_done_ratio_issue_field: Use the issue field + label_copy_same_as_target: Same as target + label_copy_target: Target + notice_issue_done_ratios_updated: Issue done ratios updated. + error_workflow_copy_source: Please select a source tracker or role + label_update_issue_done_ratios: Update issue done ratios + setting_start_of_week: Start calendars on + permission_view_issues: View Issues + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_revision_id: Revision %{value} + label_api_access_key: API access key + label_api_access_key_created_on: API access key created %{value} ago + label_feeds_access_key: Atom access key + notice_api_access_key_reseted: Your API access key was reset. + setting_rest_api_enabled: Enable REST web service + label_missing_api_access_key: Missing an API access key + label_missing_feeds_access_key: Missing a Atom access key + button_show: Show + text_line_separated: Multiple values allowed (one line for each value). + setting_mail_handler_body_delimiters: Truncate emails after one of these lines + permission_add_subprojects: Create subprojects + label_subproject_new: New subproject + text_own_membership_delete_confirmation: |- + You are about to remove some or all of your permissions and may no longer be able to edit this project after that. + Are you sure you want to continue? + label_close_versions: Close completed versions + label_board_sticky: Sticky + label_board_locked: Locked + permission_export_wiki_pages: Export wiki pages + setting_cache_formatted_text: Cache formatted text + permission_manage_project_activities: Manage project activities + error_unable_delete_issue_status: Unable to delete issue status + label_profile: Profile + permission_manage_subtasks: Manage subtasks + field_parent_issue: Parent task + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + error_can_not_delete_custom_field: Unable to delete custom field + error_unable_to_connect: Unable to connect (%{value}) + error_can_not_remove_role: This role is in use and can not be deleted. + error_can_not_delete_tracker: This tracker contains issues and can't be deleted. + field_principal: Principal + label_my_page_block: My page block + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + text_zoom_out: Zoom out + text_zoom_in: Zoom in + notice_unable_delete_time_entry: Unable to delete time log entry. + label_overall_spent_time: Overall spent time + field_time_entries: Log time + project_module_gantt: Gantt + project_module_calendar: Calendar + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Codare pentru mesaje + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 tichet + one: 1 tichet + other: "%{count} tichete" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: toate + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: With subprojects + label_cross_project_tree: With project tree + label_cross_project_hierarchy: With project hierarchy + label_cross_project_system: With all projects + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c4/c427c4a0d41e44c20850962dc38a70772b208946.svn-base --- a/.svn/pristine/c4/c427c4a0d41e44c20850962dc38a70772b208946.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1078 +0,0 @@ -uk: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] - abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] - abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%a, %d %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "less than 1 second" - other: "less than %{count} seconds" - x_seconds: - one: "1 second" - other: "%{count} seconds" - less_than_x_minutes: - one: "less than a minute" - other: "less than %{count} minutes" - x_minutes: - one: "1 minute" - other: "%{count} minutes" - about_x_hours: - one: "about 1 hour" - other: "about %{count} hours" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 day" - other: "%{count} days" - about_x_months: - one: "about 1 month" - other: "about %{count} months" - x_months: - one: "1 month" - other: "%{count} months" - about_x_years: - one: "about 1 year" - other: "about %{count} years" - over_x_years: - one: "over 1 year" - other: "over %{count} years" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - precision: 3 - delimiter: "" - storage_units: - format: "%n %u" - units: - kb: KB - tb: TB - gb: GB - byte: - one: Byte - other: Bytes - mb: MB - -# Used in array.to_sentence. - support: - array: - sentence_connector: "and" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "немає в ÑпиÑку" - exclusion: "зарезервовано" - invalid: "невірне" - confirmation: "не збігаєтьÑÑ Ð· підтвердженнÑм" - accepted: "необхідно прийнÑти" - empty: "не може бути порожнім" - blank: "не може бути незаповненим" - too_long: "дуже довге" - too_short: "дуже коротке" - wrong_length: "не відповідає довжині" - taken: "вже викориÑтовуєтьÑÑ" - not_a_number: "не Ñ” чиÑлом" - not_a_date: "Ñ” недійÑною датою" - greater_than: "must be greater than %{count}" - greater_than_or_equal_to: "must be greater than or equal to %{count}" - equal_to: "must be equal to %{count}" - less_than: "must be less than %{count}" - less_than_or_equal_to: "must be less than or equal to %{count}" - odd: "must be odd" - even: "must be even" - greater_than_start_date: "повинна бути пізніша за дату початку" - not_same_project: "не відноÑÑтьÑÑ Ð´Ð¾ одного проекту" - circular_dependency: "Такий зв'Ñзок приведе до циклічної залежноÑті" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Оберіть - - general_text_No: 'ÐÑ–' - general_text_Yes: 'Так' - general_text_no: 'ÐÑ–' - general_text_yes: 'Так' - general_lang_name: 'Ukrainian (УкраїнÑька)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Обліковий Ð·Ð°Ð¿Ð¸Ñ ÑƒÑпішно оновлений. - notice_account_invalid_creditentials: Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача або пароль - notice_account_password_updated: Пароль уÑпішно оновлений. - notice_account_wrong_password: Ðевірний пароль - notice_account_register_done: Обліковий Ð·Ð°Ð¿Ð¸Ñ ÑƒÑпішно Ñтворений. Ð”Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ— Вашого облікового запиÑу зайдіть по поÑиланню, Ñке відіÑлане вам електронною поштою. - notice_account_unknown_email: Ðевідомий кориÑтувач. - notice_can_t_change_password: Ð”Ð»Ñ Ð´Ð°Ð½Ð¾Ð³Ð¾ облікового запиÑу викориÑтовуєтьÑÑ Ð´Ð¶ÐµÑ€ÐµÐ»Ð¾ зовнішньої аутентифікації. Ðеможливо змінити пароль. - notice_account_lost_email_sent: Вам відправлений лиÑÑ‚ з інÑтрукціÑми по вибору нового паролÑ. - notice_account_activated: Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð°ÐºÑ‚Ð¸Ð²Ð¾Ð²Ð°Ð½Ð¸Ð¹. Ви можете увійти. - notice_successful_create: Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÑƒÑпішно завершене. - notice_successful_update: ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÑƒÑпішно завершене. - notice_successful_delete: Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÑƒÑпішно завершене. - notice_successful_connection: ÐŸÑ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ ÑƒÑпішно вÑтановлене. - notice_file_not_found: Сторінка, на Ñку ви намагаєтеÑÑ Ð·Ð°Ð¹Ñ‚Ð¸, не Ñ–Ñнує або видалена. - notice_locking_conflict: Дані оновлено іншим кориÑтувачем. - notice_scm_error: ЗапиÑу та/або Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð½ÐµÐ¼Ð°Ñ” в репозиторії. - notice_not_authorized: У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” прав Ð´Ð»Ñ Ð²Ñ–Ð´Ð²Ñ–Ð´Ð¸Ð½Ð¸ даної Ñторінки. - notice_email_sent: "Відправлено лиÑта %{value}" - notice_email_error: "Під Ñ‡Ð°Ñ Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²ÐºÐ¸ лиÑта відбулаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° (%{value})" - notice_feeds_access_key_reseted: Ваш ключ доÑтупу RSS було Ñкинуто. - notice_failed_to_save_issues: "Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ %{count} пункт(ів) з %{total} вибраних: %{ids}." - notice_no_issue_selected: "Ðе вибрано жодної задачі! Будь лаÑка, відзначте задачу, Ñку ви хочете відредагувати." - notice_account_pending: "Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ñтворено Ñ– він чекає на Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратором." - - mail_subject_lost_password: "Ваш %{value} пароль" - mail_body_lost_password: 'Ð”Ð»Ñ Ð·Ð¼Ñ–Ð½Ð¸ паролÑ, зайдіть за наÑтупним поÑиланнÑм:' - mail_subject_register: "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу %{value}" - mail_body_register: 'Ð”Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ— облікового запиÑу, зайдіть за наÑтупним поÑиланнÑм:' - mail_body_account_information_external: "Ви можете викориÑтовувати ваш %{value} обліковий Ð·Ð°Ð¿Ð¸Ñ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ." - mail_body_account_information: Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ð¾ Вашому обліковому запиÑу - mail_subject_account_activation_request: "Запит на активацію облікового запиÑу %{value}" - mail_body_account_activation_request: "Ðовий кориÑтувач (%{value}) зареєÑтрувавÑÑ. Його обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ‡ÐµÐºÐ°Ñ” на ваше підтвердженнÑ:" - - gui_validation_error: 1 помилка - gui_validation_error_plural: "%{count} помилки(ок)" - - field_name: Ім'Ñ - field_description: ÐžÐ¿Ð¸Ñ - field_summary: Короткий Ð¾Ð¿Ð¸Ñ - field_is_required: Ðеобхідно - field_firstname: Ім'Ñ - field_lastname: Прізвище - field_mail: Ел. пошта - field_filename: Файл - field_filesize: Розмір - field_downloads: Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ - field_author: Ðвтор - field_created_on: Створено - field_updated_on: Оновлено - field_field_format: Формат - field_is_for_all: Ð”Ð»Ñ ÑƒÑÑ–Ñ… проектів - field_possible_values: Можливі Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ - field_regexp: РегулÑрний вираз - field_min_length: Мінімальна довжина - field_max_length: МакÑимальна довжина - field_value: Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ - field_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ñ–Ñ - field_title: Ðазва - field_project: Проект - field_issue: ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ - field_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ - field_notes: Примітки - field_is_closed: ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¾ - field_is_default: Типове Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ - field_tracker: Координатор - field_subject: Тема - field_due_date: Дата Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ - field_assigned_to: Призначена до - field_priority: Пріоритет - field_fixed_version: Target version - field_user: КориÑтувач - field_role: Роль - field_homepage: Ð”Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñторінка - field_is_public: Публічний - field_parent: Підпроект - field_is_in_roadmap: ПитаннÑ, що відображаютьÑÑ Ð² оперативному плані - field_login: Вхід - field_mail_notification: ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð·Ð° електронною поштою - field_admin: ÐдмініÑтратор - field_last_login_on: ОÑтаннє Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ - field_language: Мова - field_effective_date: Дата - field_password: Пароль - field_new_password: Ðовий пароль - field_password_confirmation: ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ - field_version: ВерÑÑ–Ñ - field_type: Тип - field_host: Машина - field_port: Порт - field_account: Обліковий Ð·Ð°Ð¿Ð¸Ñ - field_base_dn: Базове відмітне ім'Ñ - field_attr_login: Ðтрибут РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ - field_attr_firstname: Ðтрибут Ім'Ñ - field_attr_lastname: Ðтрибут Прізвище - field_attr_mail: Ðтрибут Email - field_onthefly: Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача на льоту - field_start_date: Початок - field_done_ratio: "% зроблено" - field_auth_source: Режим аутентифікації - field_hide_mail: Приховувати мій email - field_comments: Коментар - field_url: URL - field_start_page: Стартова Ñторінка - field_subproject: Підпроект - field_hours: Годин(и/а) - field_activity: ДіÑльніÑть - field_spent_on: Дата - field_identifier: Ідентифікатор - field_is_filter: ВикориÑтовуєтьÑÑ Ñк фільтр - field_issue_to: Зв'Ñзані задачі - field_delay: ВідклаÑти - field_assignable: Задача може бути призначена цій ролі - field_redirect_existing_links: Перенаправити Ñ–Ñнуючі поÑÐ¸Ð»Ð°Ð½Ð½Ñ - field_estimated_hours: Оцінний Ñ‡Ð°Ñ - field_column_names: Колонки - field_time_zone: ЧаÑовий поÑÑ - field_searchable: ВживаєтьÑÑ Ñƒ пошуку - - setting_app_title: Ðазва додатку - setting_app_subtitle: Підзаголовок додатку - setting_welcome_text: ТекÑÑ‚ Ð¿Ñ€Ð¸Ð²Ñ–Ñ‚Ð°Ð½Ð½Ñ - setting_default_language: Стандартна мова - setting_login_required: Ðеобхідна Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ - setting_self_registration: Можлива Ñамо-реєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ - setting_attachment_max_size: МакÑимальний размір Ð²ÐºÐ»Ð°Ð´ÐµÐ½Ð½Ñ - setting_issues_export_limit: ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð¿Ð¾ задачах, що екÑпортуютьÑÑ - setting_mail_from: email адреÑа Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ñ– інформації - setting_bcc_recipients: Отримувачі Ñліпої копії (bcc) - setting_host_name: Им'Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð¸ - setting_text_formatting: Ð¤Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚ÐµÐºÑту - setting_wiki_compression: СтиÑÐ½ÐµÐ½Ð½Ñ Ñ–Ñторії Wiki - setting_feeds_limit: ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ñту подачі - setting_autofetch_changesets: Ðвтоматично доÑтавати Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ - setting_sys_api_enabled: Дозволити WS Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ”Ð¼ - setting_commit_ref_keywords: Ключові Ñлова Ð´Ð»Ñ Ð¿Ð¾ÑÐ¸Ð»Ð°Ð½Ð½Ñ - setting_commit_fix_keywords: ÐŸÑ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð¾Ð²Ð¸Ñ… Ñлів - setting_autologin: Ðвтоматичний вхід - setting_date_format: Формат дати - setting_time_format: Формат чаÑу - setting_cross_project_issue_relations: Дозволити міжпроектні відноÑини між питаннÑми - setting_issue_list_default_columns: Колонки, що відображаютьÑÑ Ð·Ð° умовчаннÑм в ÑпиÑку питань - setting_emails_footer: ÐŸÑ–Ð´Ð¿Ð¸Ñ Ð´Ð¾ електронної пошти - setting_protocol: Протокол - - label_user: КориÑтувач - label_user_plural: КориÑтувачі - label_user_new: Ðовий кориÑтувач - label_project: Проект - label_project_new: Ðовий проект - label_project_plural: Проекти - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: УÑÑ– проекти - label_project_latest: ОÑтанні проекти - label_issue: ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ - label_issue_new: Ðові Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ - label_issue_plural: ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ - label_issue_view_all: ПроглÑнути вÑÑ– Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ - label_issues_by: "ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ Ð·Ð° %{value}" - label_document: Документ - label_document_new: Ðовий документ - label_document_plural: Документи - label_role: Роль - label_role_plural: Ролі - label_role_new: Ðова роль - label_role_and_permissions: Ролі Ñ– права доÑтупу - label_member: УчаÑник - label_member_new: Ðовий учаÑник - label_member_plural: УчаÑники - label_tracker: Координатор - label_tracker_plural: Координатори - label_tracker_new: Ðовий Координатор - label_workflow: ПоÑлідовніÑть дій - label_issue_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ - label_issue_status_plural: СтатуÑи питань - label_issue_status_new: Ðовий ÑÑ‚Ð°Ñ‚ÑƒÑ - label_issue_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ñ–Ñ Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ - label_issue_category_plural: Категорії питань - label_issue_category_new: Ðова ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ñ–Ñ - label_custom_field: Поле клієнта - label_custom_field_plural: ÐŸÐ¾Ð»Ñ ÐºÐ»Ñ–Ñ”Ð½Ñ‚Ð° - label_custom_field_new: Ðове поле клієнта - label_enumerations: Довідники - label_enumeration_new: Ðове Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ - label_information: Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ - label_information_plural: Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ - label_please_login: Будь лаÑка, увійдіть - label_register: ЗареєÑтруватиÑÑ - label_password_lost: Забули пароль - label_home: Ð”Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñторінка - label_my_page: ÐœÐ¾Ñ Ñторінка - label_my_account: Мій обліковий Ð·Ð°Ð¿Ð¸Ñ - label_my_projects: Мої проекти - label_administration: ÐдмініÑÑ‚Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ - label_login: Увійти - label_logout: Вийти - label_help: Допомога - label_reported_issues: Створені Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ - label_assigned_to_me_issues: Мої Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ - label_last_login: ОÑтаннє Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ - label_registered_on: ЗареєÑтрований(а) - label_activity: ÐктивніÑть - label_new: Ðовий - label_logged_as: Увійшов Ñк - label_environment: ÐžÑ‚Ð¾Ñ‡ÐµÐ½Ð½Ñ - label_authentication: ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ - label_auth_source: Режим аутентифікації - label_auth_source_new: Ðовий режим аутентифікації - label_auth_source_plural: Режими аутентифікації - label_subproject_plural: Підпроекти - label_min_max_length: Мінімальна - макÑимальна довжина - label_list: СпиÑок - label_date: Дата - label_integer: Цілий - label_float: З плаваючою крапкою - label_boolean: Логічний - label_string: ТекÑÑ‚ - label_text: Довгий текÑÑ‚ - label_attribute: Ðтрибут - label_attribute_plural: атрибути - label_download: "%{count} Завантажено" - label_download_plural: "%{count} Завантажень" - label_no_data: Ðемає даних Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ - label_change_status: Змінити ÑÑ‚Ð°Ñ‚ÑƒÑ - label_history: ІÑÑ‚Ð¾Ñ€Ñ–Ñ - label_attachment: Файл - label_attachment_new: Ðовий файл - label_attachment_delete: Видалити файл - label_attachment_plural: Файли - label_report: Звіт - label_report_plural: Звіти - label_news: Ðовини - label_news_new: Додати новину - label_news_plural: Ðовини - label_news_latest: ОÑтанні новини - label_news_view_all: ПодивитиÑÑ Ð²ÑÑ– новини - label_settings: ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ - label_overview: ПереглÑд - label_version: ВерÑÑ–Ñ - label_version_new: Ðова верÑÑ–Ñ - label_version_plural: ВерÑÑ–Ñ— - label_confirmation: ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ - label_export_to: ЕкÑпортувати в - label_read: ЧитаннÑ... - label_public_projects: Публічні проекти - label_open_issues: відкрите - label_open_issues_plural: відкриті - label_closed_issues: закрите - label_closed_issues_plural: закриті - label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_total: Ð’Ñього - label_permissions: Права доÑтупу - label_current_status: Поточний ÑÑ‚Ð°Ñ‚ÑƒÑ - label_new_statuses_allowed: Дозволені нові ÑтатуÑи - label_all: УÑÑ– - label_none: Ðікому - label_nobody: Ðіхто - label_next: ÐаÑтупний - label_previous: Попередній - label_used_by: ВикориÑтовуєтьÑÑ - label_details: Подробиці - label_add_note: Додати Ð·Ð°ÑƒÐ²Ð°Ð¶ÐµÐ½Ð½Ñ - label_per_page: Ðа Ñторінку - label_calendar: Календар - label_months_from: міÑÑців(цÑ) з - label_gantt: Діаграма Ганта - label_internal: Внутрішній - label_last_changes: "оÑтанні %{count} змін" - label_change_view_all: ПроглÑнути вÑÑ– зміни - label_personalize_page: ПерÑоналізувати цю Ñторінку - label_comment: Коментувати - label_comment_plural: Коментарі - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: Залишити коментар - label_comment_added: Коментар додано - label_comment_delete: Видалити коментарі - label_query: Запит клієнта - label_query_plural: Запити клієнтів - label_query_new: Ðовий запит - label_filter_add: Додати фільтр - label_filter_plural: Фільтри - label_equals: Ñ” - label_not_equals: немає - label_in_less_than: менш ніж - label_in_more_than: більш ніж - label_in: у - label_today: Ñьогодні - label_this_week: цього Ñ‚Ð¸Ð¶Ð½Ñ - label_less_than_ago: менш ніж днів(Ñ) назад - label_more_than_ago: більш ніж днів(Ñ) назад - label_ago: днів(Ñ) назад - label_contains: міÑтить - label_not_contains: не міÑтить - label_day_plural: днів(Ñ) - label_repository: Репозиторій - label_browse: ПроглÑнути - label_modification: "%{count} зміна" - label_modification_plural: "%{count} змін" - label_revision: ВерÑÑ–Ñ - label_revision_plural: ВерÑій - label_added: додано - label_modified: змінене - label_deleted: видалено - label_latest_revision: ОÑÑ‚Ð°Ð½Ð½Ñ Ð²ÐµÑ€ÑÑ–Ñ - label_latest_revision_plural: ОÑтанні верÑÑ–Ñ— - label_view_revisions: ПроглÑнути верÑÑ–Ñ— - label_max_size: МакÑимальний розмір - label_sort_highest: У початок - label_sort_higher: Вгору - label_sort_lower: Вниз - label_sort_lowest: У кінець - label_roadmap: Оперативний план - label_roadmap_due_in: "Строк %{value}" - label_roadmap_overdue: "%{value} запізненнÑ" - label_roadmap_no_issues: Ðемає питань Ð´Ð»Ñ Ð´Ð°Ð½Ð¾Ñ— верÑÑ–Ñ— - label_search: Пошук - label_result_plural: Результати - label_all_words: Ð’ÑÑ– Ñлова - label_wiki: Wiki - label_wiki_edit: Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Wiki - label_wiki_edit_plural: Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Wiki - label_wiki_page: Сторінка Wiki - label_wiki_page_plural: Сторінки Wiki - label_index_by_title: Ð†Ð½Ð´ÐµÐºÑ Ð·Ð° назвою - label_index_by_date: Ð†Ð½Ð´ÐµÐºÑ Ð·Ð° датою - label_current_version: Поточна верÑÑ–Ñ - label_preview: Попередній переглÑд - label_feed_plural: ÐŸÐ¾Ð´Ð°Ð½Ð½Ñ - label_changes_details: Подробиці по вÑÑ–Ñ… змінах - label_issue_tracking: ÐšÐ¾Ð¾Ñ€Ð´Ð¸Ð½Ð°Ñ†Ñ–Ñ Ð¿Ð¸Ñ‚Ð°Ð½ÑŒ - label_spent_time: Витрачений Ñ‡Ð°Ñ - label_f_hour: "%{value} година" - label_f_hour_plural: "%{value} годин(и)" - label_time_tracking: Облік чаÑу - label_change_plural: Зміни - label_statistics: СтатиÑтика - label_commits_per_month: Подань на міÑÑць - label_commits_per_author: Подань на кориÑтувача - label_view_diff: ПроглÑнути відмінноÑті - label_diff_inline: підключений - label_diff_side_by_side: порÑд - label_options: Опції - label_copy_workflow_from: Скопіювати поÑлідовніÑть дій з - label_permissions_report: Звіт про права доÑтупу - label_watched_issues: ПроглÑнуті Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ - label_related_issues: Зв'Ñзані Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ - label_applied_status: ЗаÑтоÑовний ÑÑ‚Ð°Ñ‚ÑƒÑ - label_loading: ЗавантаженнÑ... - label_relation_new: Ðовий зв'Ñзок - label_relation_delete: Видалити зв'Ñзок - label_relates_to: пов'Ñзане з - label_duplicates: дублює - label_blocks: блокує - label_blocked_by: заблоковане - label_precedes: передує - label_follows: наÑтупний за - label_end_to_start: з ÐºÑ–Ð½Ñ†Ñ Ð´Ð¾ початку - label_end_to_end: з ÐºÑ–Ð½Ñ†Ñ Ð´Ð¾ ÐºÑ–Ð½Ñ†Ñ - label_start_to_start: з початку до початку - label_start_to_end: з початку до ÐºÑ–Ð½Ñ†Ñ - label_stay_logged_in: ЗалишатиÑÑ Ð² ÑиÑтемі - label_disabled: відключений - label_show_completed_versions: Показати завершені верÑÑ–Ñ— - label_me: мене - label_board: Форум - label_board_new: Ðовий форум - label_board_plural: Форуми - label_topic_plural: Теми - label_message_plural: ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ - label_message_last: ОÑтаннє Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ - label_message_new: Ðове Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ - label_reply_plural: Відповіді - label_send_information: Відправити кориÑтувачеві інформацію з облікового запиÑу - label_year: Рік - label_month: МіÑÑць - label_week: ÐÐµÐ´Ñ–Ð»Ñ - label_date_from: З - label_date_to: Кому - label_language_based: Ðа оÑнові мови кориÑтувача - label_sort_by: "Сортувати за %{value}" - label_send_test_email: ПоÑлати email Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ - label_feeds_access_key_created_on: "Ключ доÑтупу RSS Ñтворений %{value} назад " - label_module_plural: Модулі - label_added_time_by: "Доданий %{author} %{age} назад" - label_updated_time: "Оновлений %{value} назад" - label_jump_to_a_project: Перейти до проекту... - label_file_plural: Файли - label_changeset_plural: Ðабори змін - label_default_columns: Типові колонки - label_no_change_option: (Ðемає змін) - label_bulk_edit_selected_issues: Редагувати вÑÑ– вибрані Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ - label_theme: Тема - label_default: Типовий - label_search_titles_only: Шукати тільки в назвах - label_user_mail_option_all: "Ð”Ð»Ñ Ð²ÑÑ–Ñ… подій у вÑÑ–Ñ… моїх проектах" - label_user_mail_option_selected: "Ð”Ð»Ñ Ð²ÑÑ–Ñ… подій тільки у вибраному проекті..." - label_user_mail_no_self_notified: "Ðе Ñповіщати про зміни, Ñкі Ñ Ð·Ñ€Ð¾Ð±Ð¸Ð² Ñам" - label_registration_activation_by_email: Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу електронною поштою - label_registration_manual_activation: ручна Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу - label_registration_automatic_activation: автоматична Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ Ð¾Ð±Ð»Ñ‹ÐºÐ¾Ð²Ð¾Ð³Ð¾ - label_my_time_report: Мій звіт витраченого чаÑу - - button_login: Вхід - button_submit: Відправити - button_save: Зберегти - button_check_all: Відзначити вÑе - button_uncheck_all: ОчиÑтити - button_delete: Видалити - button_create: Створити - button_test: Перевірити - button_edit: Редагувати - button_add: Додати - button_change: Змінити - button_apply: ЗаÑтоÑувати - button_clear: ОчиÑтити - button_lock: Заблокувати - button_unlock: Разблокувати - button_download: Завантажити - button_list: СпиÑок - button_view: ПереглÑнути - button_move: ПереміÑтити - button_back: Ðазад - button_cancel: Відмінити - button_activate: Ðктивувати - button_sort: Сортувати - button_log_time: ЗапиÑати Ñ‡Ð°Ñ - button_rollback: Відкотити до даної верÑÑ–Ñ— - button_watch: ДивитиÑÑ - button_unwatch: Ðе дивитиÑÑ - button_reply: ВідповіÑти - button_archive: Ðрхівувати - button_unarchive: Розархівувати - button_reset: ПерезапуÑтити - button_rename: Перейменувати - button_change_password: Змінити пароль - button_copy: Копіювати - button_annotate: Ðнотувати - - status_active: Ðктивний - status_registered: ЗареєÑтрований - status_locked: Заблокований - - text_select_mail_notifications: Виберіть дії, на Ñкі відÑилатиметьÑÑ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð° електронну пошту. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 означає відÑутніÑть заборон - text_project_destroy_confirmation: Ви наполÑгаєте на видаленні цього проекту Ñ– вÑієї інформації, що відноÑитьÑÑ Ð´Ð¾ нього? - text_workflow_edit: Виберіть роль Ñ– координатор Ð´Ð»Ñ Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ÑлідовноÑті дій - text_are_you_sure: Ви впевнені? - text_tip_issue_begin_day: день початку задачі - text_tip_issue_end_day: день Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– - text_tip_issue_begin_end_day: початок задачі Ñ– Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ð´Ð½Ñ - text_caracters_maximum: "%{count} Ñимволів(а) макÑимум." - text_caracters_minimum: "Повинно мати Ñкнайменше %{count} Ñимволів(а) у довжину." - text_length_between: "Довжина між %{min} Ñ– %{max} Ñимволів." - text_tracker_no_workflow: Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ координатора поÑлідовніÑть дій не визначена - text_unallowed_characters: Заборонені Ñимволи - text_comma_separated: ДопуÑтимі декілька значень (розділені комою). - text_issues_ref_in_commit_messages: ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ñ‚Ð° зміна питань у повідомленнÑÑ… до подавань - text_issue_added: "Issue %{id} has been reported by %{author}." - text_issue_updated: "Issue %{id} has been updated by %{author}." - text_wiki_destroy_confirmation: Ви впевнені, що хочете видалити цю wiki Ñ– веÑÑŒ зміÑÑ‚? - text_issue_category_destroy_question: "Декілька питань (%{count}) призначено в цю категорію. Що ви хочете зробити?" - text_issue_category_destroy_assignments: Видалити Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ñ–Ñ— - text_issue_category_reassign_to: Перепризначити задачі до даної категорії - text_user_mail_option: "Ð”Ð»Ñ Ð½ÐµÐ²Ð¸Ð±Ñ€Ð°Ð½Ð¸Ñ… проектів ви отримуватимете Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ про те, що проглÑдаєте або в чому берете учаÑть (наприклад, Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¾Ð¼ Ñких ви Ñ” або Ñкі вам призначені)." - - default_role_manager: Менеджер - default_role_developer: Розробник - default_role_reporter: Репортер - # default_tracker_bug: Bug - # звітів default_tracker_bug: Помилка - default_tracker_bug: Помилка - default_tracker_feature: ВлаÑтивіÑть - default_tracker_support: Підтримка - default_issue_status_new: Ðовий - default_issue_status_in_progress: In Progress - default_issue_status_resolved: Вирішено - default_issue_status_feedback: Зворотний зв'Ñзок - default_issue_status_closed: Зачинено - default_issue_status_rejected: Відмовлено - default_doc_category_user: Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ ÐºÐ¾Ñ€Ð¸Ñтувача - default_doc_category_tech: Технічна Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ - default_priority_low: Ðизький - default_priority_normal: Ðормальний - default_priority_high: ВиÑокий - default_priority_urgent: Терміновий - default_priority_immediate: Ðегайний - default_activity_design: ÐŸÑ€Ð¾ÐµÐºÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ - default_activity_development: Розробка - - enumeration_issue_priorities: Пріоритети питань - enumeration_doc_categories: Категорії документів - enumeration_activities: Дії (облік чаÑу) - text_status_changed_by_changeset: "Applied in changeset %{value}." - label_display_per_page: "Per page: %{value}" - label_issue_added: Issue added - label_issue_updated: Issue updated - setting_per_page_options: Objects per page options - notice_default_data_loaded: Default configuration successfully loaded. - error_scm_not_found: "Entry and/or revision doesn't exist in the repository." - label_associated_revisions: Associated revisions - label_document_added: Document added - label_message_posted: Message added - text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?' - error_scm_command_failed: "An error occurred when trying to access the repository: %{value}" - setting_user_format: Users display format - label_age: Age - label_file_added: File added - label_more: More - field_default_value: Default value - label_scm: SCM - label_general: General - button_update: Update - text_select_project_modules: 'Select modules to enable for this project:' - label_change_properties: Change properties - text_load_default_configuration: Load the default configuration - text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." - label_news_added: News added - label_repository_plural: Repositories - error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" - project_module_boards: Boards - project_module_issue_tracking: Issue tracking - project_module_wiki: Wiki - project_module_files: Files - project_module_documents: Documents - project_module_repository: Repository - project_module_news: News - project_module_time_tracking: Time tracking - text_file_repository_writable: File repository writable - text_default_administrator_account_changed: Default administrator account changed - text_rmagick_available: RMagick available (optional) - button_configure: Configure - label_plugins: Plugins - label_ldap_authentication: LDAP authentication - label_downloads_abbr: D/L - label_this_month: this month - label_last_n_days: "last %{count} days" - label_all_time: all time - label_this_year: this year - label_date_range: Date range - label_last_week: last week - label_yesterday: yesterday - label_last_month: last month - label_add_another_file: Add another file - label_optional_description: Optional description - text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do ?" - error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' - text_assign_time_entries_to_project: Assign reported hours to the project - text_destroy_time_entries: Delete reported hours - text_reassign_time_entries: 'Reassign reported hours to this issue:' - setting_activity_days_default: Days displayed on project activity - label_chronological_order: In chronological order - field_comments_sorting: Display comments - label_reverse_chronological_order: In reverse chronological order - label_preferences: Preferences - setting_display_subprojects_issues: Display subprojects issues on main projects by default - label_overall_activity: Overall activity - setting_default_projects_public: New projects are public by default - error_scm_annotate: "The entry does not exist or can not be annotated." - label_planning: Planning - text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." - label_and_its_subprojects: "%{value} and its subprojects" - mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" - mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" - text_user_wrote: "%{value} wrote:" - label_duplicated_by: duplicated by - setting_enabled_scm: Enabled SCM - text_enumeration_category_reassign_to: 'Reassign them to this value:' - text_enumeration_destroy_question: "%{count} objects are assigned to this value." - label_incoming_emails: Incoming emails - label_generate_key: Generate a key - setting_mail_handler_api_enabled: Enable WS for incoming emails - setting_mail_handler_api_key: API key - text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." - field_parent_title: Parent page - label_issue_watchers: Watchers - button_quote: Quote - setting_sequential_project_identifiers: Generate sequential project identifiers - notice_unable_delete_version: Unable to delete version - label_renamed: renamed - label_copied: copied - setting_plain_text_mail: plain text only (no HTML) - permission_view_files: View files - permission_edit_issues: Edit issues - permission_edit_own_time_entries: Edit own time logs - permission_manage_public_queries: Manage public queries - permission_add_issues: Add issues - permission_log_time: Log spent time - permission_view_changesets: View changesets - permission_view_time_entries: View spent time - permission_manage_versions: Manage versions - permission_manage_wiki: Manage wiki - permission_manage_categories: Manage issue categories - permission_protect_wiki_pages: Protect wiki pages - permission_comment_news: Comment news - permission_delete_messages: Delete messages - permission_select_project_modules: Select project modules - permission_manage_documents: Manage documents - permission_edit_wiki_pages: Edit wiki pages - permission_add_issue_watchers: Add watchers - permission_view_gantt: View gantt chart - permission_move_issues: Move issues - permission_manage_issue_relations: Manage issue relations - permission_delete_wiki_pages: Delete wiki pages - permission_manage_boards: Manage boards - permission_delete_wiki_pages_attachments: Delete attachments - permission_view_wiki_edits: View wiki history - permission_add_messages: Post messages - permission_view_messages: View messages - permission_manage_files: Manage files - permission_edit_issue_notes: Edit notes - permission_manage_news: Manage news - permission_view_calendar: View calendrier - permission_manage_members: Manage members - permission_edit_messages: Edit messages - permission_delete_issues: Delete issues - permission_view_issue_watchers: View watchers list - permission_manage_repository: Manage repository - permission_commit_access: Commit access - permission_browse_repository: Browse repository - permission_view_documents: View documents - permission_edit_project: Edit project - permission_add_issue_notes: Add notes - permission_save_queries: Save queries - permission_view_wiki_pages: View wiki - permission_rename_wiki_pages: Rename wiki pages - permission_edit_time_entries: Edit time logs - permission_edit_own_issue_notes: Edit own notes - setting_gravatar_enabled: Use Gravatar user icons - label_example: Example - text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." - permission_edit_own_messages: Edit own messages - permission_delete_own_messages: Delete own messages - label_user_activity: "%{value}'s activity" - label_updated_time_by: "Updated by %{author} %{age} ago" - text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' - setting_diff_max_lines_displayed: Max number of diff lines displayed - text_plugin_assets_writable: Plugin assets directory writable - warning_attachments_not_saved: "%{count} file(s) could not be saved." - button_create_and_continue: Create and continue - text_custom_field_possible_values_info: 'One line for each value' - label_display: Display - field_editable: Editable - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_file_max_size_displayed: Max size of text files displayed inline - field_watcher: Watcher - setting_openid: Allow OpenID login and registration - field_identity_url: OpenID URL - label_login_with_open_id_option: or login with OpenID - field_content: Content - label_descending: Descending - label_sort: Sort - label_ascending: Ascending - label_date_from_to: From %{start} to %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? - text_wiki_page_reassign_children: Reassign child pages to this parent page - text_wiki_page_nullify_children: Keep child pages as root pages - text_wiki_page_destroy_children: Delete child pages and all their descendants - setting_password_min_length: Minimum password length - field_group_by: Group results by - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - label_wiki_content_added: Wiki page added - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: The '%{id}' wiki page has been added by %{author}. - label_wiki_content_updated: Wiki page updated - mail_body_wiki_content_updated: The '%{id}' wiki page has been updated by %{author}. - permission_add_project: Create project - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - label_view_all_revisions: View all revisions - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. - error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). - text_journal_changed: "%{label} changed from %{old} to %{new}" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} deleted (%{old})" - label_group_plural: Groups - label_group: Group - label_group_new: New group - label_time_entry_plural: Spent time - text_journal_added: "%{label} %{value} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Commit messages encoding - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ - one: 1 ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ - other: "%{count} ПитаннÑ" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: "Ваш обліковій Ð·Ð°Ð¿Ð¸Ñ Ð¿Ð¾Ð²Ð½Ñ–Ñтю видалений" - setting_unsubscribe: "Дозволити кориÑтувачам видалÑти Ñвої облікові запиÑи" - button_delete_my_account: "Видалити мій обліковий запиÑ" - text_account_destroy_confirmation: "Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð±ÑƒÐ´Ðµ повніÑтю видалений без можливоÑті відновленнÑ.\nВи певні, что бажаете продовжити?" - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: УÑÑ– - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: With subprojects - label_cross_project_tree: With project tree - label_cross_project_hierarchy: With project hierarchy - label_cross_project_system: With all projects - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c4/c4531d47cc56461cf6b79ce17579ddd86c41ae44.svn-base --- a/.svn/pristine/c4/c4531d47cc56461cf6b79ce17579ddd86c41ae44.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module Redmine - module Activity - # Class used to retrieve activity events - class Fetcher - attr_reader :user, :project, :scope - - # Needs to be unloaded in development mode - @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } } - - def initialize(user, options={}) - options.assert_valid_keys(:project, :with_subprojects, :author) - @user = user - @project = options[:project] - @options = options - - @scope = event_types - end - - # Returns an array of available event types - def event_types - return @event_types unless @event_types.nil? - - @event_types = Redmine::Activity.available_event_types - @event_types = @event_types.select {|o| @project.self_and_descendants.detect {|p| @user.allowed_to?("view_#{o}".to_sym, p)}} if @project - @event_types - end - - # Yields to filter the activity scope - def scope_select(&block) - @scope = @scope.select {|t| yield t } - end - - # Sets the scope - # Argument can be :all, :default or an array of event types - def scope=(s) - case s - when :all - @scope = event_types - when :default - default_scope! - else - @scope = s & event_types - end - end - - # Resets the scope to the default scope - def default_scope! - @scope = Redmine::Activity.default_event_types - end - - # Returns an array of events for the given date range - # sorted in reverse chronological order - def events(from = nil, to = nil, options={}) - e = [] - @options[:limit] = options[:limit] - - @scope.each do |event_type| - constantized_providers(event_type).each do |provider| - e += provider.find_events(event_type, @user, from, to, @options) - end - end - - e.sort! {|a,b| b.event_datetime <=> a.event_datetime} - - if options[:limit] - e = e.slice(0, options[:limit]) - end - e - end - - private - - def constantized_providers(event_type) - @@constantized_providers[event_type] - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c4/c481d11c94e3c3761d0a696830bd24523a9fba1c.svn-base --- a/.svn/pristine/c4/c481d11c94e3c3761d0a696830bd24523a9fba1c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class EnabledModule < ActiveRecord::Base - belongs_to :project - - validates_presence_of :name - validates_uniqueness_of :name, :scope => :project_id - - after_create :module_enabled - - private - - # after_create callback used to do things when a module is enabled - def module_enabled - case name - when 'wiki' - # Create a wiki with a default start page - if project && project.wiki.nil? - Wiki.create(:project => project, :start_page => 'Wiki') - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c4/c4c79c5f62cd0556288272ff3c097890e44b9076.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c4/c4c79c5f62cd0556288272ff3c097890e44b9076.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,94 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class DocumentsController < ApplicationController + default_search_scope :documents + model_object Document + before_filter :find_project_by_project_id, :only => [:index, :new, :create] + before_filter :find_model_object, :except => [:index, :new, :create] + before_filter :find_project_from_association, :except => [:index, :new, :create] + before_filter :authorize + + helper :attachments + + def index + @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category' + documents = @project.documents.includes(:attachments, :category).all + case @sort_by + when 'date' + @grouped = documents.group_by {|d| d.updated_on.to_date } + when 'title' + @grouped = documents.group_by {|d| d.title.first.upcase} + when 'author' + @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author} + else + @grouped = documents.group_by(&:category) + end + @document = @project.documents.build + render :layout => false if request.xhr? + end + + def show + @attachments = @document.attachments.all + end + + def new + @document = @project.documents.build + @document.safe_attributes = params[:document] + end + + def create + @document = @project.documents.build + @document.safe_attributes = params[:document] + @document.save_attachments(params[:attachments]) + if @document.save + render_attachment_warning_if_needed(@document) + flash[:notice] = l(:notice_successful_create) + redirect_to project_documents_path(@project) + else + render :action => 'new' + end + end + + def edit + end + + def update + @document.safe_attributes = params[:document] + if request.put? and @document.save + flash[:notice] = l(:notice_successful_update) + redirect_to document_path(@document) + else + render :action => 'edit' + end + end + + def destroy + @document.destroy if request.delete? + redirect_to project_documents_path(@project) + end + + def add_attachment + attachments = Attachment.attach_files(@document, params[:attachments]) + render_attachment_warning_if_needed(@document) + + if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') + Mailer.attachments_added(attachments[:files]).deliver + end + redirect_to document_path(@document) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c5/c52ac0b1aedde2b32cfd9cfb643d8e7ca16ea86c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c5/c52ac0b1aedde2b32cfd9cfb643d8e7ca16ea86c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1115 @@ +# German translations for Ruby on Rails +# by Clemens Kofler (clemens@railway.at) +# additions for Redmine 1.2 by Jens Martsch (jmartsch@gmail.com) + +de: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d.%m.%Y" + short: "%e. %b" + long: "%e. %B %Y" + + day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag] + abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember] + abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%d.%m.%Y %H:%M" + time: "%H:%M" + short: "%e. %b %H:%M" + long: "%A, %e. %B %Y, %H:%M Uhr" + am: "vormittags" + pm: "nachmittags" + + datetime: + distance_in_words: + half_a_minute: 'eine halbe Minute' + less_than_x_seconds: + one: 'weniger als 1 Sekunde' + other: 'weniger als %{count} Sekunden' + x_seconds: + one: '1 Sekunde' + other: '%{count} Sekunden' + less_than_x_minutes: + one: 'weniger als 1 Minute' + other: 'weniger als %{count} Minuten' + x_minutes: + one: '1 Minute' + other: '%{count} Minuten' + about_x_hours: + one: 'etwa 1 Stunde' + other: 'etwa %{count} Stunden' + x_hours: + one: "1 Stunde" + other: "%{count} Stunden" + x_days: + one: '1 Tag' + other: '%{count} Tagen' + about_x_months: + one: 'etwa 1 Monat' + other: 'etwa %{count} Monaten' + x_months: + one: '1 Monat' + other: '%{count} Monaten' + about_x_years: + one: 'etwa 1 Jahr' + other: 'etwa %{count} Jahren' + over_x_years: + one: 'mehr als 1 Jahr' + other: 'mehr als %{count} Jahren' + almost_x_years: + one: "fast 1 Jahr" + other: "fast %{count} Jahren" + + number: + # Default format for numbers + format: + separator: ',' + delimiter: '.' + precision: 2 + currency: + format: + unit: '€' + format: '%n %u' + delimiter: '' + percentage: + format: + delimiter: "" + precision: + format: + delimiter: "" + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "und" + skip_last_comma: true + + activerecord: + errors: + template: + header: + one: "Dieses %{model}-Objekt konnte nicht gespeichert werden: %{count} Fehler." + other: "Dieses %{model}-Objekt konnte nicht gespeichert werden: %{count} Fehler." + body: "Bitte überprüfen Sie die folgenden Felder:" + + messages: + inclusion: "ist kein gültiger Wert" + exclusion: "ist nicht verfügbar" + invalid: "ist nicht gültig" + confirmation: "stimmt nicht mit der Bestätigung überein" + accepted: "muss akzeptiert werden" + empty: "muss ausgefüllt werden" + blank: "muss ausgefüllt werden" + too_long: "ist zu lang (nicht mehr als %{count} Zeichen)" + too_short: "ist zu kurz (nicht weniger als %{count} Zeichen)" + wrong_length: "hat die falsche Länge (muss genau %{count} Zeichen haben)" + taken: "ist bereits vergeben" + not_a_number: "ist keine Zahl" + not_a_date: "ist kein gültiges Datum" + greater_than: "muss größer als %{count} sein" + greater_than_or_equal_to: "muss größer oder gleich %{count} sein" + equal_to: "muss genau %{count} sein" + less_than: "muss kleiner als %{count} sein" + less_than_or_equal_to: "muss kleiner oder gleich %{count} sein" + odd: "muss ungerade sein" + even: "muss gerade sein" + greater_than_start_date: "muss größer als Anfangsdatum sein" + not_same_project: "gehört nicht zum selben Projekt" + circular_dependency: "Diese Beziehung würde eine zyklische Abhängigkeit erzeugen" + cant_link_an_issue_with_a_descendant: "Ein Ticket kann nicht mit einer Ihrer Unteraufgaben verlinkt werden" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + actionview_instancetag_blank_option: Bitte auswählen + + button_activate: Aktivieren + button_add: Hinzufügen + button_annotate: Annotieren + button_apply: Anwenden + button_archive: Archivieren + button_back: Zurück + button_cancel: Abbrechen + button_change: Wechseln + button_change_password: Kennwort ändern + button_check_all: Alles auswählen + button_clear: Zurücksetzen + button_close: Schließen + button_collapse_all: Alle einklappen + button_configure: Konfigurieren + button_copy: Kopieren + button_copy_and_follow: Kopieren und Ticket anzeigen + button_create: Anlegen + button_create_and_continue: Anlegen und weiter + button_delete: Löschen + button_delete_my_account: Mein Benutzerkonto löschen + button_download: Download + button_duplicate: Duplizieren + button_edit: Bearbeiten + button_edit_associated_wikipage: "Zugehörige Wikiseite bearbeiten: %{page_title}" + button_edit_section: Diesen Bereich bearbeiten + button_expand_all: Alle ausklappen + button_export: Exportieren + button_hide: Verstecken + button_list: Liste + button_lock: Sperren + button_log_time: Aufwand buchen + button_login: Anmelden + button_move: Verschieben + button_move_and_follow: Verschieben und Ticket anzeigen + button_quote: Zitieren + button_rename: Umbenennen + button_reopen: Öffnen + button_reply: Antworten + button_reset: Zurücksetzen + button_rollback: Auf diese Version zurücksetzen + button_save: Speichern + button_show: Anzeigen + button_sort: Sortieren + button_submit: OK + button_test: Testen + button_unarchive: Entarchivieren + button_uncheck_all: Alles abwählen + button_unlock: Entsperren + button_unwatch: Nicht beobachten + button_update: Bearbeiten + button_view: Anzeigen + button_watch: Beobachten + + default_activity_design: Design + default_activity_development: Entwicklung + default_doc_category_tech: Technische Dokumentation + default_doc_category_user: Benutzerdokumentation + default_issue_status_closed: Erledigt + default_issue_status_feedback: Feedback + default_issue_status_in_progress: In Bearbeitung + default_issue_status_new: Neu + default_issue_status_rejected: Abgewiesen + default_issue_status_resolved: Gelöst + default_priority_high: Hoch + default_priority_immediate: Sofort + default_priority_low: Niedrig + default_priority_normal: Normal + default_priority_urgent: Dringend + default_role_developer: Entwickler + default_role_manager: Manager + default_role_reporter: Reporter + default_tracker_bug: Fehler + default_tracker_feature: Feature + default_tracker_support: Unterstützung + + description_all_columns: Alle Spalten + description_available_columns: Verfügbare Spalten + description_choose_project: Projekte + description_date_from: Startdatum eintragen + description_date_range_interval: Zeitraum durch Start- und Enddatum festlegen + description_date_range_list: Zeitraum aus einer Liste wählen + description_date_to: Enddatum eintragen + description_filter: Filter + description_issue_category_reassign: Neue Kategorie wählen + description_message_content: Nachrichteninhalt + description_notes: Kommentare + description_project_scope: Suchbereich + description_query_sort_criteria_attribute: Sortierattribut + description_query_sort_criteria_direction: Sortierrichtung + description_search: Suchfeld + description_selected_columns: Ausgewählte Spalten + + description_user_mail_notification: Mailbenachrichtigungseinstellung + description_wiki_subpages_reassign: Neue Elternseite wählen + + enumeration_activities: Aktivitäten (Zeiterfassung) + enumeration_doc_categories: Dokumentenkategorien + enumeration_issue_priorities: Ticket-Prioritäten + enumeration_system_activity: System-Aktivität + + error_attachment_too_big: Diese Datei kann nicht hochgeladen werden, da sie die maximale Dateigröße von (%{max_size}) überschreitet. + error_can_not_archive_project: Dieses Projekt kann nicht archiviert werden. + error_can_not_delete_custom_field: Kann das benutzerdefinierte Feld nicht löschen. + error_can_not_delete_tracker: Dieser Tracker enthält Tickets und kann nicht gelöscht werden. + error_can_not_remove_role: Diese Rolle wird verwendet und kann nicht gelöscht werden. + error_can_not_reopen_issue_on_closed_version: Das Ticket ist einer abgeschlossenen Version zugeordnet und kann daher nicht wieder geöffnet werden. + error_can_t_load_default_data: "Die Standard-Konfiguration konnte nicht geladen werden: %{value}" + error_issue_done_ratios_not_updated: Der Ticket-Fortschritt wurde nicht aktualisiert. + error_issue_not_found_in_project: 'Das Ticket wurde nicht gefunden oder gehört nicht zu diesem Projekt.' + error_no_default_issue_status: Es ist kein Status als Standard definiert. Bitte überprüfen Sie Ihre Konfiguration (unter "Administration -> Ticket-Status"). + error_no_tracker_in_project: Diesem Projekt ist kein Tracker zugeordnet. Bitte überprüfen Sie die Projekteinstellungen. + error_scm_annotate: "Der Eintrag existiert nicht oder kann nicht annotiert werden." + error_scm_annotate_big_text_file: Der Eintrag kann nicht umgesetzt werden, da er die maximale Textlänge überschreitet. + error_scm_command_failed: "Beim Zugriff auf das Projektarchiv ist ein Fehler aufgetreten: %{value}" + error_scm_not_found: Eintrag und/oder Revision existiert nicht im Projektarchiv. + error_session_expired: Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an. + error_unable_delete_issue_status: "Der Ticket-Status konnte nicht gelöscht werden." + error_unable_to_connect: Fehler beim Verbinden (%{value}) + error_workflow_copy_source: Bitte wählen Sie einen Quell-Tracker und eine Quell-Rolle. + error_workflow_copy_target: Bitte wählen Sie die Ziel-Tracker und -Rollen. + + field_account: Konto + field_active: Aktiv + field_activity: Aktivität + field_admin: Administrator + field_assignable: Tickets können dieser Rolle zugewiesen werden + field_assigned_to: Zugewiesen an + field_assigned_to_role: Zuständigkeitsrolle + field_attr_firstname: Vorname-Attribut + field_attr_lastname: Name-Attribut + field_attr_login: Mitgliedsname-Attribut + field_attr_mail: E-Mail-Attribut + field_auth_source: Authentifizierungs-Modus + field_auth_source_ldap_filter: LDAP-Filter + field_author: Autor + field_base_dn: Base DN + field_board_parent: Übergeordnetes Forum + field_category: Kategorie + field_column_names: Spalten + field_closed_on: Geschlossen am + field_comments: Kommentar + field_comments_sorting: Kommentare anzeigen + field_commit_logs_encoding: Kodierung der Commit-Log-Meldungen + field_content: Inhalt + field_core_fields: Standardwerte + field_created_on: Angelegt + field_cvs_module: Modul + field_cvsroot: CVSROOT + field_default_value: Standardwert + field_delay: Pufferzeit + field_description: Beschreibung + field_done_ratio: "% erledigt" + field_downloads: Downloads + field_due_date: Abgabedatum + field_editable: Bearbeitbar + field_effective_date: Datum + field_estimated_hours: Geschätzter Aufwand + field_field_format: Format + field_filename: Datei + field_filesize: Größe + field_firstname: Vorname + field_fixed_version: Zielversion + field_generate_password: Passwort generieren + field_group_by: Gruppiere Ergebnisse nach + field_hide_mail: E-Mail-Adresse nicht anzeigen + field_homepage: Projekt-Homepage + field_host: Host + field_hours: Stunden + field_identifier: Kennung + field_identity_url: OpenID-URL + field_inherit_members: Benutzer erben + field_is_closed: Ticket geschlossen + field_is_default: Standardeinstellung + field_is_filter: Als Filter benutzen + field_is_for_all: Für alle Projekte + field_is_in_roadmap: In der Roadmap anzeigen + field_is_private: Privat + field_is_public: Öffentlich + field_is_required: Erforderlich + field_issue: Ticket + field_issue_to: Zugehöriges Ticket + field_issues_visibility: Ticket Sichtbarkeit + field_language: Sprache + field_last_login_on: Letzte Anmeldung + field_lastname: Nachname + field_login: Mitgliedsname + field_mail: E-Mail + field_mail_notification: Mailbenachrichtigung + field_max_length: Maximale Länge + field_member_of_group: Zuständigkeitsgruppe + field_min_length: Minimale Länge + field_multiple: Mehrere Werte + field_must_change_passwd: Passwort beim nächsten Login ändern + field_name: Name + field_new_password: Neues Kennwort + field_notes: Kommentare + field_onthefly: On-the-fly-Benutzererstellung + field_parent: Unterprojekt von + field_parent_issue: Übergeordnete Aufgabe + field_parent_title: Übergeordnete Seite + field_password: Kennwort + field_password_confirmation: Bestätigung + field_path_to_repository: Pfad zum Repository + field_port: Port + field_possible_values: Mögliche Werte + field_principal: Auftraggeber + field_priority: Priorität + field_private_notes: Privater Kommentar + field_project: Projekt + field_redirect_existing_links: Existierende Links umleiten + field_regexp: Regulärer Ausdruck + field_repository_is_default: Haupt-Repository + field_role: Rolle + field_root_directory: Wurzelverzeichnis + field_scm_path_encoding: Pfad-Kodierung + field_searchable: Durchsuchbar + field_sharing: Gemeinsame Verwendung + field_spent_on: Datum + field_start_date: Beginn + field_start_page: Hauptseite + field_status: Status + field_subject: Thema + field_subproject: Unterprojekt von + field_summary: Zusammenfassung + field_text: Textfeld + field_time_entries: Logzeit + field_time_zone: Zeitzone + field_timeout: Auszeit (in Sekunden) + field_title: Titel + field_tracker: Tracker + field_type: Typ + field_updated_on: Aktualisiert + field_url: URL + field_user: Benutzer + field_value: Wert + field_version: Version + field_visible: Sichtbar + field_warn_on_leaving_unsaved: Vor dem Verlassen einer Seite mit ungesichertem Text im Editor warnen + field_watcher: Beobachter + + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-1 + general_csv_separator: ';' + general_first_day_of_week: '1' + general_lang_name: 'Deutsch' + general_pdf_encoding: UTF-8 + general_text_No: 'Nein' + general_text_Yes: 'Ja' + general_text_no: 'nein' + general_text_yes: 'ja' + + label_activity: Aktivität + label_add_another_file: Eine weitere Datei hinzufügen + label_add_note: Kommentar hinzufügen + label_added: hinzugefügt + label_added_time_by: "Von %{author} vor %{age} hinzugefügt" + label_additional_workflow_transitions_for_assignee: Zusätzliche Berechtigungen wenn der Benutzer der Zugewiesene ist + label_additional_workflow_transitions_for_author: Zusätzliche Berechtigungen wenn der Benutzer der Autor ist + label_administration: Administration + label_age: Geändert vor + label_ago: vor + label_all: alle + label_all_time: gesamter Zeitraum + label_all_words: Alle Wörter + label_and_its_subprojects: "%{value} und dessen Unterprojekte" + label_any: alle + label_any_issues_in_project: irgendein Ticket im Projekt + label_any_issues_not_in_project: irgendein Ticket nicht im Projekt + label_api_access_key: API-Zugriffsschlüssel + label_api_access_key_created_on: Der API-Zugriffsschlüssel wurde vor %{value} erstellt + label_applied_status: Zugewiesener Status + label_ascending: Aufsteigend + label_assigned_to_me_issues: Mir zugewiesene Tickets + label_associated_revisions: Zugehörige Revisionen + label_attachment: Datei + label_attachment_delete: Anhang löschen + label_attachment_new: Neue Datei + label_attachment_plural: Dateien + label_attribute: Attribut + label_attribute_of_assigned_to: "%{name} des Bearbeiters" + label_attribute_of_author: "%{name} des Autors" + label_attribute_of_fixed_version: "%{name} der Zielversion" + label_attribute_of_issue: "%{name} des Tickets" + label_attribute_of_project: "%{name} des Projekts" + label_attribute_of_user: "%{name} des Benutzers" + label_attribute_plural: Attribute + label_auth_source: Authentifizierungs-Modus + label_auth_source_new: Neuer Authentifizierungs-Modus + label_auth_source_plural: Authentifizierungs-Arten + label_authentication: Authentifizierung + label_between: zwischen + label_blocked_by: Blockiert durch + label_blocks: Blockiert + label_board: Forum + label_board_locked: Gesperrt + label_board_new: Neues Forum + label_board_plural: Foren + label_board_sticky: Wichtig (immer oben) + label_boolean: Boolean + label_branch: Zweig + label_browse: Codebrowser + label_bulk_edit_selected_issues: Alle ausgewählten Tickets bearbeiten + label_bulk_edit_selected_time_entries: Ausgewählte Zeitaufwände bearbeiten + label_calendar: Kalender + label_change_plural: Änderungen + label_change_properties: Eigenschaften ändern + label_change_status: Statuswechsel + label_change_view_all: Alle Änderungen anzeigen + label_changes_details: Details aller Änderungen + label_changeset_plural: Changesets + label_child_revision: Nachfolger + label_chronological_order: in zeitlicher Reihenfolge + label_close_versions: Vollständige Versionen schließen + label_closed_issues: geschlossen + label_closed_issues_plural: geschlossen + label_comment: Kommentar + label_comment_add: Kommentar hinzufügen + label_comment_added: Kommentar hinzugefügt + label_comment_delete: Kommentar löschen + label_comment_plural: Kommentare + label_commits_per_author: Übertragungen pro Autor + label_commits_per_month: Übertragungen pro Monat + label_completed_versions: Abgeschlossene Versionen + label_confirmation: Bestätigung + label_contains: enthält + label_copied: kopiert + label_copied_from: Kopiert von + label_copied_to: Kopiert nach + label_copy_attachments: Anhänge kopieren + label_copy_same_as_target: So wie das Ziel + label_copy_source: Quelle + label_copy_subtasks: Unteraufgaben kopieren + label_copy_target: Ziel + label_copy_workflow_from: Workflow kopieren von + label_cross_project_descendants: Mit Unterprojekten + label_cross_project_hierarchy: Mit Projekthierarchie + label_cross_project_system: Mit allen Projekten + label_cross_project_tree: Mit Projektbaum + label_current_status: Gegenwärtiger Status + label_current_version: Gegenwärtige Version + label_custom_field: Benutzerdefiniertes Feld + label_custom_field_new: Neues Feld + label_custom_field_plural: Benutzerdefinierte Felder + label_date: Datum + label_date_from: Von + label_date_from_to: von %{start} bis %{end} + label_date_range: Zeitraum + label_date_to: Bis + label_day_plural: Tage + label_default: Standard + label_default_columns: Standard-Spalten + label_deleted: gelöscht + label_descending: Absteigend + label_details: Details + label_diff: diff + label_diff_inline: einspaltig + label_diff_side_by_side: nebeneinander + label_disabled: gesperrt + label_display: Anzeige + label_display_per_page: "Pro Seite: %{value}" + label_display_used_statuses_only: Zeige nur Status an, die von diesem Tracker verwendet werden + label_document: Dokument + label_document_added: Dokument hinzugefügt + label_document_new: Neues Dokument + label_document_plural: Dokumente + label_downloads_abbr: D/L + label_duplicated_by: Dupliziert durch + label_duplicates: Duplikat von + label_end_to_end: Ende - Ende + label_end_to_start: Ende - Anfang + label_enumeration_new: Neuer Wert + label_enumerations: Aufzählungen + label_environment: Umgebung + label_equals: ist + label_example: Beispiel + label_export_options: "%{export_format} Export-Eigenschaften" + label_export_to: "Auch abrufbar als:" + label_f_hour: "%{value} Stunde" + label_f_hour_plural: "%{value} Stunden" + label_feed_plural: Feeds + label_feeds_access_key: Atom-Zugriffsschlüssel + label_feeds_access_key_created_on: "Atom-Zugriffsschlüssel vor %{value} erstellt" + label_fields_permissions: Feldberechtigungen + label_file_added: Datei hinzugefügt + label_file_plural: Dateien + label_filter_add: Filter hinzufügen + label_filter_plural: Filter + label_float: Fließkommazahl + label_follows: Nachfolger von + label_gantt: Gantt-Diagramm + label_gantt_progress_line: Fortschrittslinie + label_general: Allgemein + label_generate_key: Generieren + label_git_report_last_commit: Bericht des letzten Commits für Dateien und Verzeichnisse + label_greater_or_equal: ">=" + label_group: Gruppe + label_group_new: Neue Gruppe + label_group_plural: Gruppen + label_help: Hilfe + label_hidden: Versteckt + label_history: Historie + label_home: Hauptseite + label_in: in + label_in_less_than: in weniger als + label_in_more_than: in mehr als + label_in_the_next_days: in den nächsten + label_in_the_past_days: in den letzten + label_incoming_emails: Eingehende E-Mails + label_index_by_date: Seiten nach Datum sortiert + label_index_by_title: Seiten nach Titel sortiert + label_information: Information + label_information_plural: Informationen + label_integer: Zahl + label_internal: Intern + label_issue: Ticket + label_issue_added: Ticket hinzugefügt + label_issue_category: Ticket-Kategorie + label_issue_category_new: Neue Kategorie + label_issue_category_plural: Ticket-Kategorien + label_issue_new: Neues Ticket + label_issue_note_added: Notiz hinzugefügt + label_issue_plural: Tickets + label_issue_priority_updated: Priorität aktualisiert + label_issue_status: Ticket-Status + label_issue_status_new: Neuer Status + label_issue_status_plural: Ticket-Status + label_issue_status_updated: Status aktualisiert + label_issue_tracking: Tickets + label_issue_updated: Ticket aktualisiert + label_issue_view_all: Alle Tickets anzeigen + label_issue_watchers: Beobachter + label_issues_by: "Tickets pro %{value}" + label_issues_visibility_all: Alle Tickets + label_issues_visibility_own: Tickets die folgender Benutzer erstellt hat oder die ihm zugewiesen sind + label_issues_visibility_public: Alle öffentlichen Tickets + label_item_position: "%{position}/%{count}" + label_jump_to_a_project: Zu einem Projekt springen... + label_language_based: Sprachabhängig + label_last_changes: "%{count} letzte Änderungen" + label_last_login: Letzte Anmeldung + label_last_month: voriger Monat + label_last_n_days: "die letzten %{count} Tage" + label_last_n_weeks: letzte %{count} Wochen + label_last_week: vorige Woche + label_latest_revision: Aktuellste Revision + label_latest_revision_plural: Aktuellste Revisionen + label_ldap_authentication: LDAP-Authentifizierung + label_less_or_equal: "<=" + label_less_than_ago: vor weniger als + label_list: Liste + label_loading: Lade... + label_logged_as: Angemeldet als + label_login: Anmelden + label_login_with_open_id_option: oder mit OpenID anmelden + label_logout: Abmelden + label_max_size: Maximale Größe + label_me: ich + label_member: Mitglied + label_member_new: Neues Mitglied + label_member_plural: Mitglieder + label_message_last: Letzter Forenbeitrag + label_message_new: Neues Thema + label_message_plural: Forenbeiträge + label_message_posted: Forenbeitrag hinzugefügt + label_min_max_length: Länge (Min. - Max.) + label_missing_api_access_key: Der API-Zugriffsschlüssel fehlt. + label_missing_feeds_access_key: Der Atom-Zugriffsschlüssel fehlt. + label_modified: geändert + label_module_plural: Module + label_month: Monat + label_months_from: Monate ab + label_more: Mehr + label_more_than_ago: vor mehr als + label_my_account: Mein Konto + label_my_page: Meine Seite + label_my_page_block: Verfügbare Widgets + label_my_projects: Meine Projekte + label_my_queries: Meine eigenen Abfragen + label_new: Neu + label_new_statuses_allowed: Neue Berechtigungen + label_news: News + label_news_added: News hinzugefügt + label_news_comment_added: Kommentar zu einer News hinzugefügt + label_news_latest: Letzte News + label_news_new: News hinzufügen + label_news_plural: News + label_news_view_all: Alle News anzeigen + label_next: Weiter + label_no_change_option: (Keine Änderung) + label_no_data: Nichts anzuzeigen + label_no_issues_in_project: keine Tickets im Projekt + label_nobody: Niemand + label_none: kein + label_not_contains: enthält nicht + label_not_equals: ist nicht + label_open_issues: offen + label_open_issues_plural: offen + label_optional_description: Beschreibung (optional) + label_options: Optionen + label_overall_activity: Aktivitäten aller Projekte anzeigen + label_overall_spent_time: Aufgewendete Zeit aller Projekte anzeigen + label_overview: Übersicht + label_parent_revision: Vorgänger + label_password_lost: Kennwort vergessen + label_per_page: Pro Seite + label_permissions: Berechtigungen + label_permissions_report: Berechtigungsübersicht + label_personalize_page: Diese Seite anpassen + label_planning: Terminplanung + label_please_login: Anmelden + label_plugins: Plugins + label_precedes: Vorgänger von + label_preferences: Präferenzen + label_preview: Vorschau + label_previous: Zurück + label_principal_search: "Nach Benutzer oder Gruppe suchen:" + label_profile: Profil + label_project: Projekt + label_project_all: Alle Projekte + label_project_copy_notifications: Sende Mailbenachrichtigungen beim Kopieren des Projekts. + label_project_latest: Neueste Projekte + label_project_new: Neues Projekt + label_project_plural: Projekte + label_public_projects: Öffentliche Projekte + label_query: Benutzerdefinierte Abfrage + label_query_new: Neue Abfrage + label_query_plural: Benutzerdefinierte Abfragen + label_read: Lesen... + label_readonly: Nur-Lese-Zugriff + label_register: Registrieren + label_registered_on: Angemeldet am + label_registration_activation_by_email: Kontoaktivierung durch E-Mail + label_registration_automatic_activation: Automatische Kontoaktivierung + label_registration_manual_activation: Manuelle Kontoaktivierung + label_related_issues: Zugehörige Tickets + label_relates_to: Beziehung mit + label_relation_delete: Beziehung löschen + label_relation_new: Neue Beziehung + label_renamed: umbenannt + label_reply_plural: Antworten + label_report: Bericht + label_report_plural: Berichte + label_reported_issues: Erstellte Tickets + label_repository: Projektarchiv + label_repository_new: Neues Repository + label_repository_plural: Projektarchive + label_required: Erforderlich + label_result_plural: Resultate + label_reverse_chronological_order: in umgekehrter zeitlicher Reihenfolge + label_revision: Revision + label_revision_id: Revision %{value} + label_revision_plural: Revisionen + label_roadmap: Roadmap + label_roadmap_due_in: "Fällig in %{value}" + label_roadmap_no_issues: Keine Tickets für diese Version + label_roadmap_overdue: "%{value} verspätet" + label_role: Rolle + label_role_and_permissions: Rollen und Rechte + label_role_anonymous: Anonymous + label_role_new: Neue Rolle + label_role_non_member: Nichtmitglied + label_role_plural: Rollen + label_scm: Versionskontrollsystem + label_search: Suche + label_search_for_watchers: Nach hinzufügbaren Beobachtern suchen + label_search_titles_only: Nur Titel durchsuchen + label_send_information: Sende Kontoinformationen an Benutzer + label_send_test_email: Test-E-Mail senden + label_session_expiration: Ende einer Sitzung + label_settings: Konfiguration + label_show_closed_projects: Geschlossene Projekte anzeigen + label_show_completed_versions: Abgeschlossene Versionen anzeigen + label_sort: Sortierung + label_sort_by: "Sortiert nach %{value}" + label_sort_higher: Eins höher + label_sort_highest: An den Anfang + label_sort_lower: Eins tiefer + label_sort_lowest: Ans Ende + label_spent_time: Aufgewendete Zeit + label_start_to_end: Anfang - Ende + label_start_to_start: Anfang - Anfang + label_statistics: Statistiken + label_status_transitions: Statusänderungen + label_stay_logged_in: Angemeldet bleiben + label_string: Text + label_subproject_new: Neues Unterprojekt + label_subproject_plural: Unterprojekte + label_subtask_plural: Unteraufgaben + label_tag: Markierung + label_text: Langer Text + label_theme: Stil + label_this_month: aktueller Monat + label_this_week: aktuelle Woche + label_this_year: aktuelles Jahr + label_time_entry_plural: Benötigte Zeit + label_time_tracking: Zeiterfassung + label_today: heute + label_topic_plural: Themen + label_total: Gesamtzahl + label_total_time: Gesamtzeit + label_tracker: Tracker + label_tracker_new: Neuer Tracker + label_tracker_plural: Tracker + label_update_issue_done_ratios: Ticket-Fortschritt aktualisieren + label_updated_time: "Vor %{value} aktualisiert" + label_updated_time_by: "Von %{author} vor %{age} aktualisiert" + label_used_by: Benutzt von + label_user: Benutzer + label_user_activity: "Aktivität von %{value}" + label_user_anonymous: Anonym + label_user_mail_no_self_notified: "Ich möchte nicht über Änderungen benachrichtigt werden, die ich selbst durchführe." + label_user_mail_option_all: "Für alle Ereignisse in all meinen Projekten" + label_user_mail_option_none: Keine Ereignisse + label_user_mail_option_only_assigned: Nur für Aufgaben für die ich zuständig bin + label_user_mail_option_only_my_events: Nur für Aufgaben die ich beobachte oder an welchen ich mitarbeite + label_user_mail_option_only_owner: Nur für Aufgaben die ich angelegt habe + label_user_mail_option_selected: "Für alle Ereignisse in den ausgewählten Projekten" + label_user_new: Neuer Benutzer + label_user_plural: Benutzer + label_user_search: "Nach Benutzer suchen:" + label_version: Version + label_version_new: Neue Version + label_version_plural: Versionen + label_version_sharing_descendants: Mit Unterprojekten + label_version_sharing_hierarchy: Mit Projekthierarchie + label_version_sharing_none: Nicht gemeinsam verwenden + label_version_sharing_system: Mit allen Projekten + label_version_sharing_tree: Mit Projektbaum + label_view_all_revisions: Alle Revisionen anzeigen + label_view_diff: Unterschiede anzeigen + label_view_revisions: Revisionen anzeigen + label_visibility_private: nur für mich + label_visibility_public: für jeden Benutzer + label_visibility_roles: nur für diese Rollen + label_watched_issues: Beobachtete Tickets + label_week: Woche + label_wiki: Wiki + label_wiki_content_added: Wiki-Seite hinzugefügt + label_wiki_content_updated: Wiki-Seite aktualisiert + label_wiki_edit: Wiki-Bearbeitung + label_wiki_edit_plural: Wiki-Bearbeitungen + label_wiki_page: Wiki-Seite + label_wiki_page_plural: Wiki-Seiten + label_workflow: Workflow + label_x_closed_issues_abbr: + zero: 0 geschlossen + one: 1 geschlossen + other: "%{count} geschlossen" + label_x_comments: + zero: keine Kommentare + one: 1 Kommentar + other: "%{count} Kommentare" + label_x_issues: + zero: 0 Tickets + one: 1 Ticket + other: "%{count} Tickets" + label_x_open_issues_abbr: + zero: 0 offen + one: 1 offen + other: "%{count} offen" + label_x_open_issues_abbr_on_total: + zero: 0 offen / %{total} + one: 1 offen / %{total} + other: "%{count} offen / %{total}" + label_x_projects: + zero: keine Projekte + one: 1 Projekt + other: "%{count} Projekte" + label_year: Jahr + label_yesterday: gestern + + mail_body_account_activation_request: "Ein neuer Benutzer (%{value}) hat sich registriert. Sein Konto wartet auf Ihre Genehmigung:" + mail_body_account_information: Ihre Konto-Informationen + mail_body_account_information_external: "Sie können sich mit Ihrem Konto %{value} anmelden." + mail_body_lost_password: 'Benutzen Sie den folgenden Link, um Ihr Kennwort zu ändern:' + mail_body_register: 'Um Ihr Konto zu aktivieren, benutzen Sie folgenden Link:' + mail_body_reminder: "%{count} Tickets, die Ihnen zugewiesen sind, müssen in den nächsten %{days} Tagen abgegeben werden:" + mail_body_wiki_content_added: "Die Wiki-Seite '%{id}' wurde von %{author} hinzugefügt." + mail_body_wiki_content_updated: "Die Wiki-Seite '%{id}' wurde von %{author} aktualisiert." + mail_subject_account_activation_request: "Antrag auf %{value} Kontoaktivierung" + mail_subject_lost_password: "Ihr %{value} Kennwort" + mail_subject_register: "%{value} Kontoaktivierung" + mail_subject_reminder: "%{count} Tickets müssen in den nächsten %{days} Tagen abgegeben werden" + mail_subject_wiki_content_added: "Wiki-Seite '%{id}' hinzugefügt" + mail_subject_wiki_content_updated: "Wiki-Seite '%{id}' erfolgreich aktualisiert" + + notice_account_activated: Ihr Konto ist aktiviert. Sie können sich jetzt anmelden. + notice_account_deleted: Ihr Benutzerkonto wurde unwiderruflich gelöscht. + notice_account_invalid_creditentials: Benutzer oder Kennwort ist ungültig. + notice_account_lost_email_sent: Eine E-Mail mit Anweisungen, ein neues Kennwort zu wählen, wurde Ihnen geschickt. + notice_account_locked: Ihr Konto ist gesperrt. + notice_account_not_activated_yet: Sie haben Ihr Konto noch nicht aktiviert. Wenn Sie die Aktivierungsmail erneut erhalten wollen, klicken Sie bitte hier. + notice_account_password_updated: Kennwort wurde erfolgreich aktualisiert. + notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Genehmigung des Administrators." + notice_account_register_done: Konto wurde erfolgreich angelegt. Eine E-Mail mit weiteren Instruktionen zur Kontoaktivierung wurde an %{email} gesendet. + notice_account_unknown_email: Unbekannter Benutzer. + notice_account_updated: Konto wurde erfolgreich aktualisiert. + notice_account_wrong_password: Falsches Kennwort. + notice_api_access_key_reseted: Ihr API-Zugriffsschlüssel wurde zurückgesetzt. + notice_can_t_change_password: Dieses Konto verwendet eine externe Authentifizierungs-Quelle. Unmöglich, das Kennwort zu ändern. + notice_default_data_loaded: Die Standard-Konfiguration wurde erfolgreich geladen. + notice_email_error: "Beim Senden einer E-Mail ist ein Fehler aufgetreten (%{value})." + notice_email_sent: "Eine E-Mail wurde an %{value} gesendet." + notice_failed_to_save_issues: "%{count} von %{total} ausgewählten Tickets konnte(n) nicht gespeichert werden: %{ids}." + notice_failed_to_save_members: "Benutzer konnte nicht gespeichert werden: %{errors}." + notice_failed_to_save_time_entries: "Gescheitert %{count} Zeiteinträge für %{total} von ausgewählten: %{ids} zu speichern." + notice_feeds_access_key_reseted: Ihr Atom-Zugriffsschlüssel wurde zurückgesetzt. + notice_file_not_found: Anhang existiert nicht oder ist gelöscht worden. + notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max}) + notice_issue_done_ratios_updated: Der Ticket-Fortschritt wurde aktualisiert. + notice_issue_successful_create: Ticket %{id} erstellt. + notice_issue_update_conflict: Das Ticket wurde während Ihrer Bearbeitung von einem anderen Nutzer überarbeitet. + notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert. + notice_new_password_must_be_different: Das neue Passwort muss sich vom dem aktuellen + unterscheiden + notice_no_issue_selected: "Kein Ticket ausgewählt! Bitte wählen Sie die Tickets, die Sie bearbeiten möchten." + notice_not_authorized: Sie sind nicht berechtigt, auf diese Seite zuzugreifen. + notice_not_authorized_archived_project: Das Projekt wurde archiviert und ist daher nicht nicht verfügbar. + notice_successful_connection: Verbindung erfolgreich. + notice_successful_create: Erfolgreich angelegt + notice_successful_delete: Erfolgreich gelöscht. + notice_successful_update: Erfolgreich aktualisiert. + notice_unable_delete_time_entry: Der Zeiterfassungseintrag konnte nicht gelöscht werden. + notice_unable_delete_version: Die Version konnte nicht gelöscht werden. + notice_user_successful_create: Benutzer %{id} angelegt. + + permission_add_issue_notes: Kommentare hinzufügen + permission_add_issue_watchers: Beobachter hinzufügen + permission_add_issues: Tickets hinzufügen + permission_add_messages: Forenbeiträge hinzufügen + permission_add_project: Projekt erstellen + permission_add_subprojects: Unterprojekte erstellen + permission_add_documents: Dokumente hinzufügen + permission_browse_repository: Projektarchiv ansehen + permission_close_project: Schließen / erneutes Öffnen eines Projekts + permission_comment_news: News kommentieren + permission_commit_access: Commit-Zugriff + permission_delete_issue_watchers: Beobachter löschen + permission_delete_issues: Tickets löschen + permission_delete_messages: Forenbeiträge löschen + permission_delete_own_messages: Eigene Forenbeiträge löschen + permission_delete_wiki_pages: Wiki-Seiten löschen + permission_delete_wiki_pages_attachments: Anhänge löschen + permission_delete_documents: Dokumente löschen + permission_edit_issue_notes: Kommentare bearbeiten + permission_edit_issues: Tickets bearbeiten + permission_edit_messages: Forenbeiträge bearbeiten + permission_edit_own_issue_notes: Eigene Kommentare bearbeiten + permission_edit_own_messages: Eigene Forenbeiträge bearbeiten + permission_edit_own_time_entries: Selbst gebuchte Aufwände bearbeiten + permission_edit_project: Projekt bearbeiten + permission_edit_time_entries: Gebuchte Aufwände bearbeiten + permission_edit_wiki_pages: Wiki-Seiten bearbeiten + permission_edit_documents: Dokumente bearbeiten + permission_export_wiki_pages: Wiki-Seiten exportieren + permission_log_time: Aufwände buchen + permission_manage_boards: Foren verwalten + permission_manage_categories: Ticket-Kategorien verwalten + permission_manage_files: Dateien verwalten + permission_manage_issue_relations: Ticket-Beziehungen verwalten + permission_manage_members: Mitglieder verwalten + permission_manage_news: News verwalten + permission_manage_project_activities: Aktivitäten (Zeiterfassung) verwalten + permission_manage_public_queries: Öffentliche Filter verwalten + permission_manage_related_issues: Zugehörige Tickets verwalten + permission_manage_repository: Projektarchiv verwalten + permission_manage_subtasks: Unteraufgaben verwalten + permission_manage_versions: Versionen verwalten + permission_manage_wiki: Wiki verwalten + permission_move_issues: Tickets verschieben + permission_protect_wiki_pages: Wiki-Seiten schützen + permission_rename_wiki_pages: Wiki-Seiten umbenennen + permission_save_queries: Filter speichern + permission_select_project_modules: Projektmodule auswählen + permission_set_issues_private: Tickets privat oder öffentlich markieren + permission_set_notes_private: Kommentar als privat markieren + permission_set_own_issues_private: Eigene Tickets privat oder öffentlich markieren + permission_view_calendar: Kalender ansehen + permission_view_changesets: Changesets ansehen + permission_view_documents: Dokumente ansehen + permission_view_files: Dateien ansehen + permission_view_gantt: Gantt-Diagramm ansehen + permission_view_issue_watchers: Liste der Beobachter ansehen + permission_view_issues: Tickets anzeigen + permission_view_messages: Forenbeiträge ansehen + permission_view_private_notes: Private Kommentare sehen + permission_view_time_entries: Gebuchte Aufwände ansehen + permission_view_wiki_edits: Wiki-Versionsgeschichte ansehen + permission_view_wiki_pages: Wiki ansehen + + project_module_boards: Foren + project_module_calendar: Kalender + project_module_documents: Dokumente + project_module_files: Dateien + project_module_gantt: Gantt + project_module_issue_tracking: Ticket-Verfolgung + project_module_news: News + project_module_repository: Projektarchiv + project_module_time_tracking: Zeiterfassung + project_module_wiki: Wiki + project_status_active: aktiv + project_status_archived: archiviert + project_status_closed: geschlossen + + setting_activity_days_default: Anzahl Tage pro Seite der Projekt-Aktivität + setting_app_subtitle: Applikations-Untertitel + setting_app_title: Applikations-Titel + setting_attachment_max_size: Max. Dateigröße + setting_autofetch_changesets: Changesets automatisch abrufen + setting_autologin: Automatische Anmeldung + setting_bcc_recipients: E-Mails als Blindkopie (BCC) senden + setting_cache_formatted_text: Formatierten Text im Cache speichern + setting_commit_cross_project_ref: Erlauben auf Tickets aller anderen Projekte zu referenzieren + setting_commit_fix_keywords: Schlüsselwörter (Status) + setting_commit_logtime_activity_id: Aktivität für die Zeiterfassung + setting_commit_logtime_enabled: Aktiviere Zeitlogging + setting_commit_ref_keywords: Schlüsselwörter (Beziehungen) + setting_cross_project_issue_relations: Ticket-Beziehungen zwischen Projekten erlauben + setting_cross_project_subtasks: Projektübergreifende Unteraufgaben erlauben + setting_date_format: Datumsformat + setting_default_issue_start_date_to_creation_date: Aktuelles Datum als Beginn für neue Tickets verwenden + setting_default_language: Standardsprache + setting_default_notification_option: Standard Benachrichtigungsoptionen + setting_default_projects_modules: Standardmäßig aktivierte Module für neue Projekte + setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich + setting_diff_max_lines_displayed: Maximale Anzahl anzuzeigender Diff-Zeilen + setting_display_subprojects_issues: Tickets von Unterprojekten im Hauptprojekt anzeigen + setting_emails_footer: E-Mail-Fußzeile + setting_emails_header: E-Mail-Kopfzeile + setting_enabled_scm: Aktivierte Versionskontrollsysteme + setting_feeds_limit: Max. Anzahl Einträge pro Atom-Feed + setting_file_max_size_displayed: Maximale Größe inline angezeigter Textdateien + setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden + setting_gravatar_default: Standard-Gravatar-Bild + setting_gravatar_enabled: Gravatar-Benutzerbilder benutzen + setting_host_name: Hostname + setting_issue_done_ratio: Berechne den Ticket-Fortschritt mittels + setting_issue_done_ratio_issue_field: Ticket-Feld %-erledigt + setting_issue_done_ratio_issue_status: Ticket-Status + setting_issue_group_assignment: Ticketzuweisung an Gruppen erlauben + setting_issue_list_default_columns: Standard-Spalten in der Ticket-Auflistung + setting_issues_export_limit: Max. Anzahl Tickets bei CSV/PDF-Export + setting_jsonp_enabled: JSONP Unterstützung aktivieren + setting_login_required: Authentifizierung erforderlich + setting_mail_from: E-Mail-Absender + setting_mail_handler_api_enabled: Abruf eingehender E-Mails aktivieren + setting_mail_handler_api_key: API-Schlüssel + setting_mail_handler_body_delimiters: "Schneide E-Mails nach einer dieser Zeilen ab" + setting_mail_handler_excluded_filenames: Anhänge nach Namen ausschließen + setting_new_project_user_role_id: Rolle, die einem Nicht-Administrator zugeordnet wird, der ein Projekt erstellt + setting_non_working_week_days: Arbeitsfreie Tage + setting_openid: Erlaube OpenID-Anmeldung und -Registrierung + setting_password_min_length: Mindestlänge des Kennworts + setting_per_page_options: Objekte pro Seite + setting_plain_text_mail: Nur reinen Text (kein HTML) senden + setting_protocol: Protokoll + setting_repositories_encodings: Enkodierung von Anhängen und Repositories + setting_repository_log_display_limit: Maximale Anzahl anzuzeigender Revisionen in der Historie einer Datei + setting_rest_api_enabled: REST-Schnittstelle aktivieren + setting_self_registration: Anmeldung ermöglicht + setting_sequential_project_identifiers: Fortlaufende Projektkennungen generieren + setting_session_lifetime: Längste Dauer einer Sitzung + setting_session_timeout: Zeitüberschreitung bei Inaktivität + setting_start_of_week: Wochenanfang + setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen + setting_text_formatting: Textformatierung + setting_thumbnails_enabled: Vorschaubilder von Dateianhängen anzeigen + setting_thumbnails_size: Größe der Vorschaubilder (in Pixel) + setting_time_format: Zeitformat + setting_unsubscribe: Erlaubt Benutzern das eigene Benutzerkonto zu löschen + setting_user_format: Benutzer-Anzeigeformat + setting_welcome_text: Willkommenstext + setting_wiki_compression: Wiki-Historie komprimieren + setting_default_projects_tracker_ids: Standardmäßig aktivierte Tracker für neue Projekte + + status_active: aktiv + status_locked: gesperrt + status_registered: nicht aktivierte + + text_account_destroy_confirmation: Möchten Sie wirklich fortfahren?\nIhr Benutzerkonto wird für immer gelöscht und kann nicht wiederhergestellt werden. + text_are_you_sure: Sind Sie sicher? + text_assign_time_entries_to_project: Gebuchte Aufwände dem Projekt zuweisen + text_caracters_maximum: "Max. %{count} Zeichen." + text_caracters_minimum: "Muss mindestens %{count} Zeichen lang sein." + text_comma_separated: Mehrere Werte erlaubt (durch Komma getrennt). + text_convert_available: ImageMagick Konvertierung verfügbar (optional) + text_custom_field_possible_values_info: 'Eine Zeile pro Wert' + text_default_administrator_account_changed: Administrator-Kennwort geändert + text_destroy_time_entries: Gebuchte Aufwände löschen + text_destroy_time_entries_question: Es wurden bereits %{hours} Stunden auf dieses Ticket gebucht. Was soll mit den Aufwänden geschehen? + text_diff_truncated: '... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.' + text_email_delivery_not_configured: "Der SMTP-Server ist nicht konfiguriert und Mailbenachrichtigungen sind ausgeschaltet.\nNehmen Sie die Einstellungen für Ihren SMTP-Server in config/configuration.yml vor und starten Sie die Applikation neu." + text_enumeration_category_reassign_to: 'Die Objekte stattdessen diesem Wert zuordnen:' + text_enumeration_destroy_question: "%{count} Objekt(e) sind diesem Wert zugeordnet." + text_file_repository_writable: Verzeichnis für Dateien beschreibbar + text_git_repository_note: Repository steht für sich alleine (bare) und liegt lokal (z.B. /gitrepo, c:\gitrepo) + text_issue_added: "Ticket %{id} wurde erstellt von %{author}." + text_issue_category_destroy_assignments: Kategorie-Zuordnung entfernen + text_issue_category_destroy_question: "Einige Tickets (%{count}) sind dieser Kategorie zugeodnet. Was möchten Sie tun?" + text_issue_category_reassign_to: Tickets dieser Kategorie zuordnen + text_issue_conflict_resolution_add_notes: Meine Änderungen übernehmen und alle anderen Änderungen verwerfen + text_issue_conflict_resolution_cancel: Meine Änderungen verwerfen und %{link} neu anzeigen + text_issue_conflict_resolution_overwrite: Meine Änderungen trotzdem übernehmen (vorherige Notizen bleiben erhalten aber manche können überschrieben werden) + text_issue_updated: "Ticket %{id} wurde aktualisiert von %{author}." + text_issues_destroy_confirmation: 'Sind Sie sicher, dass Sie die ausgewählten Tickets löschen möchten?' + text_issues_destroy_descendants_confirmation: Dies wird auch %{count} Unteraufgabe/n löschen. + text_issues_ref_in_commit_messages: Ticket-Beziehungen und -Status in Commit-Log-Meldungen + text_journal_added: "%{label} %{value} wurde hinzugefügt" + text_journal_changed: "%{label} wurde von %{old} zu %{new} geändert" + text_journal_changed_no_detail: "%{label} aktualisiert" + text_journal_deleted: "%{label} %{old} wurde gelöscht" + text_journal_set_to: "%{label} wurde auf %{value} gesetzt" + text_length_between: "Länge zwischen %{min} und %{max} Zeichen." + text_line_separated: Mehrere Werte sind erlaubt (eine Zeile pro Wert). + text_load_default_configuration: Standard-Konfiguration laden + text_mercurial_repository_note: Lokales repository (e.g. /hgrepo, c:\hgrepo) + text_min_max_length_info: 0 heißt keine Beschränkung + text_no_configuration_data: "Rollen, Tracker, Ticket-Status und Workflows wurden noch nicht konfiguriert.\nEs ist sehr zu empfehlen, die Standard-Konfiguration zu laden. Sobald sie geladen ist, können Sie diese abändern." + text_own_membership_delete_confirmation: "Sie sind dabei, einige oder alle Ihre Berechtigungen zu entfernen. Es ist möglich, dass Sie danach das Projekt nicht mehr ansehen oder bearbeiten dürfen.\nSind Sie sicher, dass Sie dies tun möchten?" + text_plugin_assets_writable: Verzeichnis für Plugin-Assets beschreibbar + text_project_closed: Dieses Projekt ist geschlossen und kann nicht bearbeitet werden. + text_project_destroy_confirmation: Sind Sie sicher, dass Sie das Projekt löschen wollen? + text_project_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt, muss mit einem Kleinbuchstaben beginnen.
    Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + text_reassign_time_entries: 'Gebuchte Aufwände diesem Ticket zuweisen:' + text_regexp_info: z. B. ^[A-Z0-9]+$ + text_repository_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
    Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + text_repository_usernames_mapping: "Bitte legen Sie die Zuordnung der Redmine-Benutzer zu den Benutzernamen der Commit-Log-Meldungen des Projektarchivs fest.\nBenutzer mit identischen Redmine- und Projektarchiv-Benutzernamen oder -E-Mail-Adressen werden automatisch zugeordnet." + text_rmagick_available: RMagick verfügbar (optional) + text_scm_command: Kommando + text_scm_command_not_available: SCM-Kommando ist nicht verfügbar. Bitte prüfen Sie die Einstellungen im Administrationspanel. + text_scm_command_version: Version + text_scm_config: Die SCM-Kommandos können in der in config/configuration.yml konfiguriert werden. Redmine muss anschließend neu gestartet werden. + text_scm_path_encoding_note: "Standard: UTF-8" + text_select_mail_notifications: Bitte wählen Sie die Aktionen aus, für die eine Mailbenachrichtigung gesendet werden soll. + text_select_project_modules: 'Bitte wählen Sie die Module aus, die in diesem Projekt aktiviert sein sollen:' + text_session_expiration_settings: "Achtung: Änderungen können aktuelle Sitzungen beenden, Ihre eingeschlossen!" + text_status_changed_by_changeset: "Status geändert durch Changeset %{value}." + text_subprojects_destroy_warning: "Dessen Unterprojekte (%{value}) werden ebenfalls gelöscht." + text_time_entries_destroy_confirmation: Sind Sie sicher, dass Sie die ausgewählten Zeitaufwände löschen möchten? + text_time_logged_by_changeset: Angewendet in Changeset %{value}. + text_tip_issue_begin_day: Aufgabe, die an diesem Tag beginnt + text_tip_issue_begin_end_day: Aufgabe, die an diesem Tag beginnt und endet + text_tip_issue_end_day: Aufgabe, die an diesem Tag endet + text_tracker_no_workflow: Kein Workflow für diesen Tracker definiert. + text_turning_multiple_off: Wenn Sie die Mehrfachauswahl deaktivieren, werden Felder mit Mehrfachauswahl bereinigt. + Dadurch wird sichergestellt, dass lediglich ein Wert pro Feld ausgewählt ist. + text_unallowed_characters: Nicht erlaubte Zeichen + text_user_mail_option: "Für nicht ausgewählte Projekte werden Sie nur Benachrichtigungen für Dinge erhalten, die Sie beobachten oder an denen Sie beteiligt sind (z. B. Tickets, deren Autor Sie sind oder die Ihnen zugewiesen sind)." + text_user_wrote: "%{value} schrieb:" + text_warn_on_leaving_unsaved: Die aktuellen Änderungen gehen verloren, wenn Sie diese Seite verlassen. + text_wiki_destroy_confirmation: Sind Sie sicher, dass Sie dieses Wiki mit sämtlichem Inhalt löschen möchten? + text_wiki_page_destroy_children: Lösche alle Unterseiten + text_wiki_page_destroy_question: "Diese Seite hat %{descendants} Unterseite(n). Was möchten Sie tun?" + text_wiki_page_nullify_children: Verschiebe die Unterseiten auf die oberste Ebene + text_wiki_page_reassign_children: Ordne die Unterseiten dieser Seite zu + text_workflow_edit: Workflow zum Bearbeiten auswählen + text_zoom_in: Ansicht vergrößern + text_zoom_out: Ansicht verkleinern + + version_status_closed: abgeschlossen + version_status_locked: gesperrt + version_status_open: offen + + warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden." diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c5/c52f7b6c9c463e8a51ddc62dfba2a59799b361b2.svn-base --- a/.svn/pristine/c5/c52f7b6c9c463e8a51ddc62dfba2a59799b361b2.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,294 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../test_case', __FILE__) -require 'tmpdir' - -class RedminePmTest::RepositorySubversionTest < RedminePmTest::TestCase - fixtures :projects, :users, :members, :roles, :member_roles, :auth_sources - - SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn" - - def test_anonymous_read_on_public_repo_with_permission_should_succeed - assert_success "ls", svn_url - end - - def test_anonymous_read_on_public_repo_without_permission_should_fail - Role.anonymous.remove_permission! :browse_repository - assert_failure "ls", svn_url - end - - def test_anonymous_read_on_private_repo_should_fail - Project.find(1).update_attribute :is_public, false - assert_failure "ls", svn_url - end - - def test_anonymous_commit_on_public_repo_should_fail - Role.anonymous.add_permission! :commit_access - assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) - end - - def test_anonymous_commit_on_private_repo_should_fail - Role.anonymous.add_permission! :commit_access - Project.find(1).update_attribute :is_public, false - assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) - end - - def test_non_member_read_on_public_repo_with_permission_should_succeed - Role.anonymous.remove_permission! :browse_repository - with_credentials "miscuser8", "foo" do - assert_success "ls", svn_url - end - end - - def test_non_member_read_on_public_repo_without_permission_should_fail - Role.anonymous.remove_permission! :browse_repository - Role.non_member.remove_permission! :browse_repository - with_credentials "miscuser8", "foo" do - assert_failure "ls", svn_url - end - end - - def test_non_member_read_on_private_repo_should_fail - Project.find(1).update_attribute :is_public, false - with_credentials "miscuser8", "foo" do - assert_failure "ls", svn_url - end - end - - def test_non_member_commit_on_public_repo_should_fail - Role.non_member.add_permission! :commit_access - assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) - end - - def test_non_member_commit_on_private_repo_should_fail - Role.non_member.add_permission! :commit_access - Project.find(1).update_attribute :is_public, false - assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) - end - - def test_member_read_on_public_repo_with_permission_should_succeed - Role.anonymous.remove_permission! :browse_repository - Role.non_member.remove_permission! :browse_repository - with_credentials "dlopper", "foo" do - assert_success "ls", svn_url - end - end - - def test_member_read_on_public_repo_without_permission_should_fail - Role.anonymous.remove_permission! :browse_repository - Role.non_member.remove_permission! :browse_repository - Role.find(2).remove_permission! :browse_repository - with_credentials "dlopper", "foo" do - assert_failure "ls", svn_url - end - end - - def test_member_read_on_private_repo_with_permission_should_succeed - Project.find(1).update_attribute :is_public, false - with_credentials "dlopper", "foo" do - assert_success "ls", svn_url - end - end - - def test_member_read_on_private_repo_without_permission_should_fail - Role.find(2).remove_permission! :browse_repository - Project.find(1).update_attribute :is_public, false - with_credentials "dlopper", "foo" do - assert_failure "ls", svn_url - end - end - - def test_member_commit_on_public_repo_with_permission_should_succeed - Role.find(2).add_permission! :commit_access - with_credentials "dlopper", "foo" do - assert_success "mkdir --message Creating_a_directory", svn_url(random_filename) - end - end - - def test_member_commit_on_public_repo_without_permission_should_fail - Role.find(2).remove_permission! :commit_access - with_credentials "dlopper", "foo" do - assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) - end - end - - def test_member_commit_on_private_repo_with_permission_should_succeed - Role.find(2).add_permission! :commit_access - Project.find(1).update_attribute :is_public, false - with_credentials "dlopper", "foo" do - assert_success "mkdir --message Creating_a_directory", svn_url(random_filename) - end - end - - def test_member_commit_on_private_repo_without_permission_should_fail - Role.find(2).remove_permission! :commit_access - Project.find(1).update_attribute :is_public, false - with_credentials "dlopper", "foo" do - assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) - end - end - - def test_invalid_credentials_should_fail - Project.find(1).update_attribute :is_public, false - with_credentials "dlopper", "foo" do - assert_success "ls", svn_url - end - with_credentials "dlopper", "wrong" do - assert_failure "ls", svn_url - end - end - - def test_anonymous_read_should_fail_with_login_required - assert_success "ls", svn_url - with_settings :login_required => '1' do - assert_failure "ls", svn_url - end - end - - def test_authenticated_read_should_succeed_with_login_required - with_settings :login_required => '1' do - with_credentials "miscuser8", "foo" do - assert_success "ls", svn_url - end - end - end - - def test_read_on_archived_projects_should_fail - Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED - assert_failure "ls", svn_url - end - - def test_read_on_archived_private_projects_should_fail - Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED - Project.find(1).update_attribute :is_public, false - with_credentials "dlopper", "foo" do - assert_failure "ls", svn_url - end - end - - def test_read_on_closed_projects_should_succeed - Project.find(1).update_attribute :status, Project::STATUS_CLOSED - assert_success "ls", svn_url - end - - def test_read_on_closed_private_projects_should_succeed - Project.find(1).update_attribute :status, Project::STATUS_CLOSED - Project.find(1).update_attribute :is_public, false - with_credentials "dlopper", "foo" do - assert_success "ls", svn_url - end - end - - def test_commit_on_closed_projects_should_fail - Project.find(1).update_attribute :status, Project::STATUS_CLOSED - Role.find(2).add_permission! :commit_access - with_credentials "dlopper", "foo" do - assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) - end - end - - def test_commit_on_closed_private_projects_should_fail - Project.find(1).update_attribute :status, Project::STATUS_CLOSED - Project.find(1).update_attribute :is_public, false - Role.find(2).add_permission! :commit_access - with_credentials "dlopper", "foo" do - assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) - end - end - - if ldap_configured? - def test_user_with_ldap_auth_source_should_authenticate_with_ldap_credentials - ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1) - ldap_user.login = 'example1' - ldap_user.save! - - with_settings :login_required => '1' do - with_credentials "example1", "123456" do - assert_success "ls", svn_url - end - end - - with_settings :login_required => '1' do - with_credentials "example1", "wrong" do - assert_failure "ls", svn_url - end - end - end - end - - def test_checkout - Dir.mktmpdir do |dir| - assert_success "checkout", svn_url, dir - end - end - - def test_read_commands - assert_success "info", svn_url - assert_success "ls", svn_url - assert_success "log", svn_url - end - - def test_write_commands - Role.find(2).add_permission! :commit_access - filename = random_filename - - Dir.mktmpdir do |dir| - assert_success "checkout", svn_url, dir - Dir.chdir(dir) do - # creates a file in the working copy - f = File.new(File.join(dir, filename), "w") - f.write "test file content" - f.close - - assert_success "add", filename - with_credentials "dlopper", "foo" do - assert_success "commit --message Committing_a_file" - assert_success "copy --message Copying_a_file", svn_url(filename), svn_url("#{filename}_copy") - assert_success "delete --message Deleting_a_file", svn_url(filename) - assert_success "mkdir --message Creating_a_directory", svn_url("#{filename}_dir") - end - assert_success "update" - - # checks that the working copy was updated - assert File.exists?(File.join(dir, "#{filename}_copy")) - assert File.directory?(File.join(dir, "#{filename}_dir")) - end - end - end - - def test_read_invalid_repo_should_fail - assert_failure "ls", svn_url("invalid") - end - - protected - - def execute(*args) - a = [SVN_BIN, "--no-auth-cache --non-interactive"] - a << "--username #{username}" if username - a << "--password #{password}" if password - - super a, *args - end - - def svn_url(path=nil) - host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1' - url = "http://#{host}/svn/ecookbook" - url << "/#{path}" if path - url - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c5/c585c9323b1f894b886cb662ebd3c804674a5129.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c5/c585c9323b1f894b886cb662ebd3c804674a5129.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,54 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module UsersHelper + def users_status_options_for_select(selected) + user_count_by_status = User.count(:group => 'status').to_hash + options_for_select([[l(:label_all), ''], + ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'], + ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'], + ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s) + end + + def user_mail_notification_options(user) + user.valid_notification_options.collect {|o| [l(o.last), o.first]} + end + + def change_status_link(user) + url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil} + + if user.locked? + link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock' + elsif user.registered? + link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock' + elsif user != User.current + link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock' + end + end + + def user_settings_tabs + tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general}, + {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural} + ] + if Group.all.any? + tabs.insert 1, {:name => 'groups', :partial => 'users/groups', :label => :label_group_plural} + end + tabs + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c5/c5875d3c8e61841ef5ad08f475a3db29a1da98e1.svn-base --- a/.svn/pristine/c5/c5875d3c8e61841ef5ad08f475a3db29a1da98e1.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class ProjectsTest < ActionController::IntegrationTest - fixtures :projects, :users, :members, :enabled_modules - - def test_archive_project - subproject = Project.find(1).children.first - log_user("admin", "admin") - get "admin/projects" - assert_response :success - assert_template "admin/projects" - post "projects/1/archive" - assert_redirected_to "/admin/projects" - assert !Project.find(1).active? - - get 'projects/1' - assert_response 403 - get "projects/#{subproject.id}" - assert_response 403 - - post "projects/1/unarchive" - assert_redirected_to "/admin/projects" - assert Project.find(1).active? - get "projects/1" - assert_response :success - end - - def test_modules_should_not_allow_get - assert_no_difference 'EnabledModule.count' do - get '/projects/1/modules', {:enabled_module_names => ['']}, credentials('jsmith') - assert_response 404 - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c5/c5a5dd0715c90581373d0e0b7b8ea344f1c040db.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c5/c5a5dd0715c90581373d0e0b7b8ea344f1c040db.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# 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. + +module Redmine + class CustomFieldFormat + include Redmine::I18n + + cattr_accessor :available + @@available = {} + + attr_accessor :name, :order, :label, :edit_as, :class_names + + def initialize(name, options={}) + self.name = name + self.label = options[:label] || "label_#{name}".to_sym + self.order = options[:order] || self.class.available_formats.size + self.edit_as = options[:edit_as] || name + self.class_names = options[:only] + end + + def format(value) + send "format_as_#{name}", value + end + + def format_as_date(value) + begin; format_date(value.to_date); rescue; value end + end + + def format_as_bool(value) + l(value == "1" ? :general_text_Yes : :general_text_No) + end + + ['string','text','int','float','list'].each do |name| + define_method("format_as_#{name}") {|value| + return value + } + end + + ['user', 'version'].each do |name| + define_method("format_as_#{name}") {|value| + return value.blank? ? "" : name.classify.constantize.find_by_id(value.to_i).to_s + } + end + + class << self + def map(&block) + yield self + end + + # Registers a custom field format + def register(*args) + custom_field_format = args.first + unless custom_field_format.is_a?(Redmine::CustomFieldFormat) + custom_field_format = Redmine::CustomFieldFormat.new(*args) + end + @@available[custom_field_format.name] = custom_field_format unless @@available.keys.include?(custom_field_format.name) + end + + def delete(format) + if format.is_a?(Redmine::CustomFieldFormat) + format = format.name + end + @@available.delete(format) + end + + def available_formats + @@available.keys + end + + def find_by_name(name) + @@available[name.to_s] + end + + def label_for(name) + format = @@available[name.to_s] + format.label if format + end + + # Return an array of custom field formats which can be used in select_tag + def as_select(class_name=nil) + fields = @@available.values + fields = fields.select {|field| field.class_names.nil? || field.class_names.include?(class_name)} + fields.sort {|a,b| + a.order <=> b.order + }.collect {|custom_field_format| + [ l(custom_field_format.label), custom_field_format.name ] + } + end + + def format_value(value, field_format) + return "" unless value && !value.empty? + + if format_type = find_by_name(field_format) + format_type.format(value) + else + value + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c5/c5ac85b1b901fa66f9ca32fcaa2fc8702a7953a0.svn-base --- a/.svn/pristine/c5/c5ac85b1b901fa66f9ca32fcaa2fc8702a7953a0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,214 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class UsersController < ApplicationController - layout 'admin' - - before_filter :require_admin, :except => :show - before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership] - accept_api_auth :index, :show, :create, :update, :destroy - - helper :sort - include SortHelper - helper :custom_fields - include CustomFieldsHelper - - def index - sort_init 'login', 'asc' - sort_update %w(login firstname lastname mail admin created_on last_login_on) - - case params[:format] - when 'xml', 'json' - @offset, @limit = api_offset_and_limit - else - @limit = per_page_option - end - - @status = params[:status] || 1 - - scope = User.logged.status(@status) - scope = scope.like(params[:name]) if params[:name].present? - scope = scope.in_group(params[:group_id]) if params[:group_id].present? - - @user_count = scope.count - @user_pages = Paginator.new self, @user_count, @limit, params['page'] - @offset ||= @user_pages.current.offset - @users = scope.find :all, - :order => sort_clause, - :limit => @limit, - :offset => @offset - - respond_to do |format| - format.html { - @groups = Group.all.sort - render :layout => !request.xhr? - } - format.api - end - end - - def show - # show projects based on current user visibility - @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current)) - - events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10) - @events_by_day = events.group_by(&:event_date) - - unless User.current.admin? - if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?) - render_404 - return - end - end - - respond_to do |format| - format.html { render :layout => 'base' } - format.api - end - end - - def new - @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) - @auth_sources = AuthSource.find(:all) - end - - def create - @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) - @user.safe_attributes = params[:user] - @user.admin = params[:user][:admin] || false - @user.login = params[:user][:login] - @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id - - if @user.save - @user.pref.attributes = params[:pref] - @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') - @user.pref.save - @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) - - Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information] - - respond_to do |format| - format.html { - flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user))) - redirect_to(params[:continue] ? - {:controller => 'users', :action => 'new'} : - {:controller => 'users', :action => 'edit', :id => @user} - ) - } - format.api { render :action => 'show', :status => :created, :location => user_url(@user) } - end - else - @auth_sources = AuthSource.find(:all) - # Clear password input - @user.password = @user.password_confirmation = nil - - respond_to do |format| - format.html { render :action => 'new' } - format.api { render_validation_errors(@user) } - end - end - end - - def edit - @auth_sources = AuthSource.find(:all) - @membership ||= Member.new - end - - def update - @user.admin = params[:user][:admin] if params[:user][:admin] - @user.login = params[:user][:login] if params[:user][:login] - if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?) - @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] - end - @user.safe_attributes = params[:user] - # Was the account actived ? (do it before User#save clears the change) - was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) - # TODO: Similar to My#account - @user.pref.attributes = params[:pref] - @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') - - if @user.save - @user.pref.save - @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) - - if was_activated - Mailer.account_activated(@user).deliver - elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil? - Mailer.account_information(@user, params[:user][:password]).deliver - end - - respond_to do |format| - format.html { - flash[:notice] = l(:notice_successful_update) - redirect_to_referer_or edit_user_path(@user) - } - format.api { render_api_ok } - end - else - @auth_sources = AuthSource.find(:all) - @membership ||= Member.new - # Clear password input - @user.password = @user.password_confirmation = nil - - respond_to do |format| - format.html { render :action => :edit } - format.api { render_validation_errors(@user) } - end - end - end - - def destroy - @user.destroy - respond_to do |format| - format.html { redirect_back_or_default(users_url) } - format.api { render_api_ok } - end - end - - def edit_membership - @membership = Member.edit_membership(params[:membership_id], params[:membership], @user) - @membership.save - respond_to do |format| - format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } - format.js - end - end - - def destroy_membership - @membership = Member.find(params[:membership_id]) - if @membership.deletable? - @membership.destroy - end - respond_to do |format| - format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } - format.js - end - end - - private - - def find_user - if params[:id] == 'current' - require_login || return - @user = User.current - else - @user = User.find(params[:id]) - end - rescue ActiveRecord::RecordNotFound - render_404 - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c5/c5d488c8c009bfaf1b3e16a81cc375cbb4e01e5b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c5/c5d488c8c009bfaf1b3e16a81cc375cbb4e01e5b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,165 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class NewsControllerTest < ActionController::TestCase + fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news, :comments + + def setup + User.current = nil + end + + def test_index + get :index + assert_response :success + assert_template 'index' + assert_not_nil assigns(:newss) + assert_nil assigns(:project) + end + + def test_index_with_project + get :index, :project_id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:newss) + end + + def test_index_with_invalid_project_should_respond_with_404 + get :index, :project_id => 999 + assert_response 404 + end + + def test_show + get :show, :id => 1 + assert_response :success + assert_template 'show' + assert_tag :tag => 'h2', :content => /eCookbook first release/ + end + + def test_show_should_show_attachments + attachment = Attachment.first + attachment.container = News.find(1) + attachment.save! + + get :show, :id => 1 + assert_response :success + assert_tag 'a', :content => attachment.filename + end + + def test_show_not_found + get :show, :id => 999 + assert_response 404 + end + + def test_get_new + @request.session[:user_id] = 2 + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + end + + def test_post_create + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 2 + + with_settings :notified_events => %w(news_added) do + post :create, :project_id => 1, :news => { :title => 'NewsControllerTest', + :description => 'This is the description', + :summary => '' } + end + assert_redirected_to '/projects/ecookbook/news' + + news = News.find_by_title('NewsControllerTest') + assert_not_nil news + assert_equal 'This is the description', news.description + assert_equal User.find(2), news.author + assert_equal Project.find(1), news.project + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_post_create_with_attachment + set_tmp_attachments_directory + @request.session[:user_id] = 2 + assert_difference 'News.count' do + assert_difference 'Attachment.count' do + post :create, :project_id => 1, + :news => { :title => 'Test', :description => 'This is the description' }, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} + end + end + attachment = Attachment.first(:order => 'id DESC') + news = News.first(:order => 'id DESC') + assert_equal news, attachment.container + end + + def test_post_create_with_validation_failure + @request.session[:user_id] = 2 + post :create, :project_id => 1, :news => { :title => '', + :description => 'This is the description', + :summary => '' } + assert_response :success + assert_template 'new' + assert_not_nil assigns(:news) + assert assigns(:news).new_record? + assert_error_tag :content => /title can't be blank/i + end + + def test_get_edit + @request.session[:user_id] = 2 + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + end + + def test_put_update + @request.session[:user_id] = 2 + put :update, :id => 1, :news => { :description => 'Description changed by test_post_edit' } + assert_redirected_to '/news/1' + news = News.find(1) + assert_equal 'Description changed by test_post_edit', news.description + end + + def test_put_update_with_attachment + set_tmp_attachments_directory + @request.session[:user_id] = 2 + assert_no_difference 'News.count' do + assert_difference 'Attachment.count' do + put :update, :id => 1, + :news => { :description => 'This is the description' }, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} + end + end + attachment = Attachment.first(:order => 'id DESC') + assert_equal News.find(1), attachment.container + end + + def test_update_with_failure + @request.session[:user_id] = 2 + put :update, :id => 1, :news => { :description => '' } + assert_response :success + assert_template 'edit' + assert_error_tag :content => /description can't be blank/i + end + + def test_destroy + @request.session[:user_id] = 2 + delete :destroy, :id => 1 + assert_redirected_to '/projects/ecookbook/news' + assert_nil News.find_by_id(1) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c5/c5dd710a7f43b09dab39c8f1a38c740cf467e460.svn-base --- a/.svn/pristine/c5/c5dd710a7f43b09dab39c8f1a38c740cf467e460.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -
    -<%= link_to l(:label_user_new), new_user_path, :class => 'icon icon-add' %> -
    - -

    <%=l(:label_user_plural)%>

    - -<%= form_tag({}, :method => :get) do %> -
    <%= l(:label_filter_plural) %> - -<%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> - -<% if @groups.present? %> - -<%= select_tag 'group_id', content_tag('option') + options_from_collection_for_select(@groups, :id, :name, params[:group_id].to_i), :onchange => "this.form.submit(); return false;" %> -<% end %> - - -<%= text_field_tag 'name', params[:name], :size => 30 %> -<%= submit_tag l(:button_apply), :class => "small", :name => nil %> -<%= link_to l(:button_clear), users_path, :class => 'icon icon-reload' %> -
    -<% end %> -  - -
    - - - <%= sort_header_tag('login', :caption => l(:field_login)) %> - <%= sort_header_tag('firstname', :caption => l(:field_firstname)) %> - <%= sort_header_tag('lastname', :caption => l(:field_lastname)) %> - <%= sort_header_tag('mail', :caption => l(:field_mail)) %> - <%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %> - <%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %> - <%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %> - - - -<% for user in @users -%> - "> - - - - - - - - - -<% end -%> - -
    <%= avatar(user, :size => "14") %><%= link_to h(user.login), edit_user_path(user) %><%= h(user.firstname) %><%= h(user.lastname) %><%= checked_image user.admin? %><%= format_time(user.created_on) %> - <%= change_status_link(user) %> - <%= delete_link user_path(user, :back_url => users_path(params)) unless User.current == user %> -
    -
    -

    <%= pagination_links_full @user_pages, @user_count %>

    - -<% html_title(l(:label_user_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c6/c6259edf6d4a9d5a1738455be1808f3b0eaae4e1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c6259edf6d4a9d5a1738455be1808f3b0eaae4e1.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,53 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class FilesController < ApplicationController + menu_item :files + + before_filter :find_project_by_project_id + before_filter :authorize + + helper :sort + include SortHelper + + def index + sort_init 'filename', 'asc' + sort_update 'filename' => "#{Attachment.table_name}.filename", + 'created_on' => "#{Attachment.table_name}.created_on", + 'size' => "#{Attachment.table_name}.filesize", + 'downloads' => "#{Attachment.table_name}.downloads" + + @containers = [ Project.includes(:attachments).reorder(sort_clause).find(@project.id)] + @containers += @project.versions.includes(:attachments).reorder(sort_clause).all.sort.reverse + render :layout => !request.xhr? + end + + def new + @versions = @project.versions.sort + end + + def create + container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) + attachments = Attachment.attach_files(container, params[:attachments]) + render_attachment_warning_if_needed(container) + + if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') + Mailer.attachments_added(attachments[:files]).deliver + end + redirect_to project_files_path(@project) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c6/c65ce198718375cbe82249d0ec15351586b70868.svn-base --- a/.svn/pristine/c6/c65ce198718375cbe82249d0ec15351586b70868.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class MessageObserver < ActiveRecord::Observer - def after_create(message) - Mailer.message_posted(message).deliver if Setting.notified_events.include?('message_posted') - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c6/c66ed3b558cdd1c02b7c08d87594f47f1c08002f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c66ed3b558cdd1c02b7c08d87594f47f1c08002f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,322 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class IssuesCustomFieldsVisibilityTest < ActionController::TestCase + tests IssuesController + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issue_statuses, + :trackers, + :projects_trackers, + :enabled_modules, + :enumerations, + :workflows + + def setup + CustomField.delete_all + Issue.delete_all + field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all} + @fields = [] + @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) + @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) + @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) + @issue = Issue.generate!( + :author_id => 1, + :project_id => 1, + :tracker_id => 1, + :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'} + ) + + @user_with_role_on_other_project = User.generate! + User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3)) + + @users_to_test = { + User.find(1) => [@field1, @field2, @field3], + User.find(3) => [@field1, @field2], + @user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 + User.generate! => [@field1], + User.anonymous => [@field1] + } + + Member.where(:project_id => 1).each do |member| + member.destroy unless @users_to_test.keys.include?(member.principal) + end + end + + def test_show_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :show, :id => @issue.id + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'td', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name}" + else + assert_select 'td', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name}" + end + end + end + end + + def test_show_should_show_visible_custom_fields_only_in_api + @users_to_test.each do |user, fields| + with_settings :rest_api_enabled => '1' do + get :show, :id => @issue.id, :format => 'xml', :include => 'custom_fields', :key => user.api_key + end + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select "custom_field[id=#{field.id}] value", {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} in API" + else + assert_select "custom_field[id=#{field.id}] value", {:text => "Value#{i}", :count => 0}, "User #{user.id} was not able to view #{field.name} in API" + end + end + end + end + + def test_show_should_show_visible_custom_fields_only_in_history + @issue.init_journal(User.find(1)) + @issue.custom_field_values = {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'} + @issue.save! + + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :show, :id => @issue.id + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'ul.details i', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change" + else + assert_select 'ul.details i', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change" + end + end + end + end + + def test_show_should_show_visible_custom_fields_only_in_history_api + @issue.init_journal(User.find(1)) + @issue.custom_field_values = {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'} + @issue.save! + + @users_to_test.each do |user, fields| + with_settings :rest_api_enabled => '1' do + get :show, :id => @issue.id, :format => 'xml', :include => 'journals', :key => user.api_key + end + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'details old_value', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change in API" + else + assert_select 'details old_value', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change in API" + end + end + end + end + + def test_edit_should_show_visible_custom_fields_only + Role.anonymous.add_permission! :edit_issues + + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :edit, :id => @issue.id + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'input[value=?]', "Value#{i}", 1, "User #{user.id} was not able to edit #{field.name}" + else + assert_select 'input[value=?]', "Value#{i}", 0, "User #{user.id} was able to edit #{field.name}" + end + end + end + end + + def test_update_should_update_visible_custom_fields_only + Role.anonymous.add_permission! :edit_issues + + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + put :update, :id => @issue.id, + :issue => {:custom_field_values => { + @field1.id.to_s => "User#{user.id}Value0", + @field2.id.to_s => "User#{user.id}Value1", + @field3.id.to_s => "User#{user.id}Value2", + }} + @issue.reload + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_equal "User#{user.id}Value#{i}", @issue.custom_field_value(field), "User #{user.id} was not able to update #{field.name}" + else + assert_not_equal "User#{user.id}Value#{i}", @issue.custom_field_value(field), "User #{user.id} was able to update #{field.name}" + end + end + end + end + + def test_index_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :c => (["subject"] + @fields.map{|f| "cf_#{f.id}"}) + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'td', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name}" + else + assert_select 'td', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name}" + end + end + end + end + + def test_index_as_csv_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :c => (["subject"] + @fields.map{|f| "cf_#{f.id}"}), :format => 'csv' + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_include "Value#{i}", response.body, "User #{user.id} was not able to view #{field.name} in CSV" + else + assert_not_include "Value#{i}", response.body, "User #{user.id} was able to view #{field.name} in CSV" + end + end + end + end + + def test_index_with_partial_custom_field_visibility + Issue.delete_all + p1 = Project.generate! + p2 = Project.generate! + user = User.generate! + User.add_to_project(user, p1, Role.find_all_by_id(1,3)) + User.add_to_project(user, p2, Role.find_all_by_id(3)) + Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueA'}) + Issue.generate!(:project => p2, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueB'}) + Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueC'}) + + @request.session[:user_id] = user.id + get :index, :c => ["subject", "cf_#{@field2.id}"] + assert_select 'td', :text => 'ValueA' + assert_select 'td', :text => 'ValueB', :count => 0 + assert_select 'td', :text => 'ValueC' + + get :index, :sort => "cf_#{@field2.id}" + # ValueB is not visible to user and ignored while sorting + assert_equal %w(ValueB ValueA ValueC), assigns(:issues).map{|i| i.custom_field_value(@field2)} + + get :index, :set_filter => '1', "cf_#{@field2.id}" => '*' + assert_equal %w(ValueA ValueC), assigns(:issues).map{|i| i.custom_field_value(@field2)} + + CustomField.update_all(:field_format => 'list') + get :index, :group => "cf_#{@field2.id}" + assert_equal %w(ValueA ValueC), assigns(:issues).map{|i| i.custom_field_value(@field2)} + end + + def test_create_should_send_notifications_according_custom_fields_visibility + # anonymous user is never notified + users_to_test = @users_to_test.reject {|k,v| k.anonymous?} + + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 1 + with_settings :bcc_recipients => '1' do + assert_difference 'Issue.count' do + post :create, + :project_id => 1, + :issue => { + :tracker_id => 1, + :status_id => 1, + :subject => 'New issue', + :priority_id => 5, + :custom_field_values => {@field1.id.to_s => 'Value0', @field2.id.to_s => 'Value1', @field3.id.to_s => 'Value2'}, + :watcher_user_ids => users_to_test.keys.map(&:id) + } + assert_response 302 + end + end + assert_equal users_to_test.values.uniq.size, ActionMailer::Base.deliveries.size + # tests that each user receives 1 email with the custom fields he is allowed to see only + users_to_test.each do |user, fields| + mails = ActionMailer::Base.deliveries.select {|m| m.bcc.include? user.mail} + assert_equal 1, mails.size + mail = mails.first + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_mail_body_match "Value#{i}", mail, "User #{user.id} was not able to view #{field.name} in notification" + else + assert_mail_body_no_match "Value#{i}", mail, "User #{user.id} was able to view #{field.name} in notification" + end + end + end + end + + def test_update_should_send_notifications_according_custom_fields_visibility + # anonymous user is never notified + users_to_test = @users_to_test.reject {|k,v| k.anonymous?} + + users_to_test.keys.each do |user| + Watcher.create!(:user => user, :watchable => @issue) + end + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 1 + with_settings :bcc_recipients => '1' do + put :update, + :id => @issue.id, + :issue => { + :custom_field_values => {@field1.id.to_s => 'NewValue0', @field2.id.to_s => 'NewValue1', @field3.id.to_s => 'NewValue2'} + } + assert_response 302 + end + assert_equal users_to_test.values.uniq.size, ActionMailer::Base.deliveries.size + # tests that each user receives 1 email with the custom fields he is allowed to see only + users_to_test.each do |user, fields| + mails = ActionMailer::Base.deliveries.select {|m| m.bcc.include? user.mail} + assert_equal 1, mails.size + mail = mails.first + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_mail_body_match "Value#{i}", mail, "User #{user.id} was not able to view #{field.name} in notification" + else + assert_mail_body_no_match "Value#{i}", mail, "User #{user.id} was able to view #{field.name} in notification" + end + end + end + end + + def test_updating_hidden_custom_fields_only_should_not_notifiy_user + # anonymous user is never notified + users_to_test = @users_to_test.reject {|k,v| k.anonymous?} + + users_to_test.keys.each do |user| + Watcher.create!(:user => user, :watchable => @issue) + end + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 1 + with_settings :bcc_recipients => '1' do + put :update, + :id => @issue.id, + :issue => { + :custom_field_values => {@field2.id.to_s => 'NewValue1', @field3.id.to_s => 'NewValue2'} + } + assert_response 302 + end + users_to_test.each do |user, fields| + mails = ActionMailer::Base.deliveries.select {|m| m.bcc.include? user.mail} + if (fields & [@field2, @field3]).any? + assert_equal 1, mails.size, "User #{user.id} was not notified" + else + assert_equal 0, mails.size, "User #{user.id} was notified" + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c6/c673436b013667ece4d8f947cdd3efc48718bb36.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c673436b013667ece4d8f947cdd3efc48718bb36.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,35 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingSysTest < ActionController::IntegrationTest + def test_sys + assert_routing( + { :method => 'get', :path => "/sys/projects" }, + { :controller => 'sys', :action => 'projects' } + ) + assert_routing( + { :method => 'post', :path => "/sys/projects/testid/repository" }, + { :controller => 'sys', :action => 'create_project_repository', :id => 'testid' } + ) + assert_routing( + { :method => 'get', :path => "/sys/fetch_changesets" }, + { :controller => 'sys', :action => 'fetch_changesets' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c6/c68a764ff663d1fa551848f69b2759490df8f986.svn-base --- a/.svn/pristine/c6/c68a764ff663d1fa551848f69b2759490df8f986.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,712 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require "digest/sha1" - -class User < Principal - include Redmine::SafeAttributes - - # Account statuses - STATUS_ANONYMOUS = 0 - STATUS_ACTIVE = 1 - STATUS_REGISTERED = 2 - STATUS_LOCKED = 3 - - # Different ways of displaying/sorting users - USER_FORMATS = { - :firstname_lastname => { - :string => '#{firstname} #{lastname}', - :order => %w(firstname lastname id), - :setting_order => 1 - }, - :firstname_lastinitial => { - :string => '#{firstname} #{lastname.to_s.chars.first}.', - :order => %w(firstname lastname id), - :setting_order => 2 - }, - :firstname => { - :string => '#{firstname}', - :order => %w(firstname id), - :setting_order => 3 - }, - :lastname_firstname => { - :string => '#{lastname} #{firstname}', - :order => %w(lastname firstname id), - :setting_order => 4 - }, - :lastname_coma_firstname => { - :string => '#{lastname}, #{firstname}', - :order => %w(lastname firstname id), - :setting_order => 5 - }, - :lastname => { - :string => '#{lastname}', - :order => %w(lastname id), - :setting_order => 6 - }, - :username => { - :string => '#{login}', - :order => %w(login id), - :setting_order => 7 - }, - } - - MAIL_NOTIFICATION_OPTIONS = [ - ['all', :label_user_mail_option_all], - ['selected', :label_user_mail_option_selected], - ['only_my_events', :label_user_mail_option_only_my_events], - ['only_assigned', :label_user_mail_option_only_assigned], - ['only_owner', :label_user_mail_option_only_owner], - ['none', :label_user_mail_option_none] - ] - - has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)}, - :after_remove => Proc.new {|user, group| group.user_removed(user)} - has_many :changesets, :dependent => :nullify - has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' - has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" - has_one :api_token, :class_name => 'Token', :conditions => "action='api'" - belongs_to :auth_source - - scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}" - scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} } - - acts_as_customizable - - attr_accessor :password, :password_confirmation - attr_accessor :last_before_login_on - # Prevents unauthorized assignments - attr_protected :login, :admin, :password, :password_confirmation, :hashed_password - - LOGIN_LENGTH_LIMIT = 60 - MAIL_LENGTH_LIMIT = 60 - - validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } - validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false - validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false - # Login must contain lettres, numbers, underscores only - validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i - validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT - validates_length_of :firstname, :lastname, :maximum => 30 - validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true - validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true - validates_confirmation_of :password, :allow_nil => true - validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true - validate :validate_password_length - - before_create :set_mail_notification - before_save :update_hashed_password - before_destroy :remove_references_before_destroy - - scope :in_group, lambda {|group| - group_id = group.is_a?(Group) ? group.id : group.to_i - where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) - } - scope :not_in_group, lambda {|group| - group_id = group.is_a?(Group) ? group.id : group.to_i - where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) - } - - def set_mail_notification - self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? - true - end - - def update_hashed_password - # update hashed_password if password was set - if self.password && self.auth_source_id.blank? - salt_password(password) - end - end - - def reload(*args) - @name = nil - @projects_by_role = nil - super - end - - def mail=(arg) - write_attribute(:mail, arg.to_s.strip) - end - - def identity_url=(url) - if url.blank? - write_attribute(:identity_url, '') - else - begin - write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url)) - rescue OpenIdAuthentication::InvalidOpenId - # Invlaid url, don't save - end - end - self.read_attribute(:identity_url) - end - - # Returns the user that matches provided login and password, or nil - def self.try_to_login(login, password) - login = login.to_s - password = password.to_s - - # Make sure no one can sign in with an empty password - return nil if password.empty? - user = find_by_login(login) - if user - # user is already in local database - return nil if !user.active? - if user.auth_source - # user has an external authentication method - return nil unless user.auth_source.authenticate(login, password) - else - # authentication with local password - return nil unless user.check_password?(password) - end - else - # user is not yet registered, try to authenticate with available sources - attrs = AuthSource.authenticate(login, password) - if attrs - user = new(attrs) - user.login = login - user.language = Setting.default_language - if user.save - user.reload - logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source - end - end - end - user.update_attribute(:last_login_on, Time.now) if user && !user.new_record? - user - rescue => text - raise text - end - - # Returns the user who matches the given autologin +key+ or nil - def self.try_to_autologin(key) - tokens = Token.find_all_by_action_and_value('autologin', key.to_s) - # Make sure there's only 1 token that matches the key - if tokens.size == 1 - token = tokens.first - if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active? - token.user.update_attribute(:last_login_on, Time.now) - token.user - end - end - end - - def self.name_formatter(formatter = nil) - USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname] - end - - # Returns an array of fields names than can be used to make an order statement for users - # according to how user names are displayed - # Examples: - # - # User.fields_for_order_statement => ['users.login', 'users.id'] - # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id'] - def self.fields_for_order_statement(table=nil) - table ||= table_name - name_formatter[:order].map {|field| "#{table}.#{field}"} - end - - # Return user's full name for display - def name(formatter = nil) - f = self.class.name_formatter(formatter) - if formatter - eval('"' + f[:string] + '"') - else - @name ||= eval('"' + f[:string] + '"') - end - end - - def active? - self.status == STATUS_ACTIVE - end - - def registered? - self.status == STATUS_REGISTERED - end - - def locked? - self.status == STATUS_LOCKED - end - - def activate - self.status = STATUS_ACTIVE - end - - def register - self.status = STATUS_REGISTERED - end - - def lock - self.status = STATUS_LOCKED - end - - def activate! - update_attribute(:status, STATUS_ACTIVE) - end - - def register! - update_attribute(:status, STATUS_REGISTERED) - end - - def lock! - update_attribute(:status, STATUS_LOCKED) - end - - # Returns true if +clear_password+ is the correct user's password, otherwise false - def check_password?(clear_password) - if auth_source_id.present? - auth_source.authenticate(self.login, clear_password) - else - User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password - end - end - - # Generates a random salt and computes hashed_password for +clear_password+ - # The hashed password is stored in the following form: SHA1(salt + SHA1(password)) - def salt_password(clear_password) - self.salt = User.generate_salt - self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}") - end - - # Does the backend storage allow this user to change their password? - def change_password_allowed? - return true if auth_source.nil? - return auth_source.allow_password_changes? - end - - # Generate and set a random password. Useful for automated user creation - # Based on Token#generate_token_value - # - def random_password - chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a - password = '' - 40.times { |i| password << chars[rand(chars.size-1)] } - self.password = password - self.password_confirmation = password - self - end - - def pref - self.preference ||= UserPreference.new(:user => self) - end - - def time_zone - @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone]) - end - - def wants_comments_in_reverse_order? - self.pref[:comments_sorting] == 'desc' - end - - # Return user's RSS key (a 40 chars long string), used to access feeds - def rss_key - if rss_token.nil? - create_rss_token(:action => 'feeds') - end - rss_token.value - end - - # Return user's API key (a 40 chars long string), used to access the API - def api_key - if api_token.nil? - create_api_token(:action => 'api') - end - api_token.value - end - - # Return an array of project ids for which the user has explicitly turned mail notifications on - def notified_projects_ids - @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id) - end - - def notified_project_ids=(ids) - Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id]) - Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty? - @notified_projects_ids = nil - notified_projects_ids - end - - def valid_notification_options - self.class.valid_notification_options(self) - end - - # Only users that belong to more than 1 project can select projects for which they are notified - def self.valid_notification_options(user=nil) - # Note that @user.membership.size would fail since AR ignores - # :include association option when doing a count - if user.nil? || user.memberships.length < 1 - MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'} - else - MAIL_NOTIFICATION_OPTIONS - end - end - - # Find a user account by matching the exact login and then a case-insensitive - # version. Exact matches will be given priority. - def self.find_by_login(login) - # First look for an exact match - user = where(:login => login).all.detect {|u| u.login == login} - unless user - # Fail over to case-insensitive if none was found - user = where("LOWER(login) = ?", login.to_s.downcase).first - end - user - end - - def self.find_by_rss_key(key) - token = Token.find_by_action_and_value('feeds', key.to_s) - token && token.user.active? ? token.user : nil - end - - def self.find_by_api_key(key) - token = Token.find_by_action_and_value('api', key.to_s) - token && token.user.active? ? token.user : nil - end - - # Makes find_by_mail case-insensitive - def self.find_by_mail(mail) - where("LOWER(mail) = ?", mail.to_s.downcase).first - end - - # Returns true if the default admin account can no longer be used - def self.default_admin_account_changed? - !User.active.find_by_login("admin").try(:check_password?, "admin") - end - - def to_s - name - end - - CSS_CLASS_BY_STATUS = { - STATUS_ANONYMOUS => 'anon', - STATUS_ACTIVE => 'active', - STATUS_REGISTERED => 'registered', - STATUS_LOCKED => 'locked' - } - - def css_classes - "user #{CSS_CLASS_BY_STATUS[status]}" - end - - # Returns the current day according to user's time zone - def today - if time_zone.nil? - Date.today - else - Time.now.in_time_zone(time_zone).to_date - end - end - - # Returns the day of +time+ according to user's time zone - def time_to_date(time) - if time_zone.nil? - time.to_date - else - time.in_time_zone(time_zone).to_date - end - end - - def logged? - true - end - - def anonymous? - !logged? - end - - # Return user's roles for project - def roles_for_project(project) - roles = [] - # No role on archived projects - return roles if project.nil? || project.archived? - if logged? - # Find project membership - membership = memberships.detect {|m| m.project_id == project.id} - if membership - roles = membership.roles - else - @role_non_member ||= Role.non_member - roles << @role_non_member - end - else - @role_anonymous ||= Role.anonymous - roles << @role_anonymous - end - roles - end - - # Return true if the user is a member of project - def member_of?(project) - !roles_for_project(project).detect {|role| role.member?}.nil? - end - - # Returns a hash of user's projects grouped by roles - def projects_by_role - return @projects_by_role if @projects_by_role - - @projects_by_role = Hash.new([]) - memberships.each do |membership| - if membership.project - membership.roles.each do |role| - @projects_by_role[role] = [] unless @projects_by_role.key?(role) - @projects_by_role[role] << membership.project - end - end - end - @projects_by_role.each do |role, projects| - projects.uniq! - end - - @projects_by_role - end - - # Returns true if user is arg or belongs to arg - def is_or_belongs_to?(arg) - if arg.is_a?(User) - self == arg - elsif arg.is_a?(Group) - arg.users.include?(self) - else - false - end - end - - # Return true if the user is allowed to do the specified action on a specific context - # Action can be: - # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') - # * a permission Symbol (eg. :edit_project) - # Context can be: - # * a project : returns true if user is allowed to do the specified action on this project - # * an array of projects : returns true if user is allowed on every project - # * nil with options[:global] set : check if user has at least one role allowed for this action, - # or falls back to Non Member / Anonymous permissions depending if the user is logged - def allowed_to?(action, context, options={}, &block) - if context && context.is_a?(Project) - return false unless context.allows_to?(action) - # Admin users are authorized for anything else - return true if admin? - - roles = roles_for_project(context) - return false unless roles - roles.any? {|role| - (context.is_public? || role.member?) && - role.allowed_to?(action) && - (block_given? ? yield(role, self) : true) - } - elsif context && context.is_a?(Array) - if context.empty? - false - else - # Authorize if user is authorized on every element of the array - context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&) - end - elsif options[:global] - # Admin users are always authorized - return true if admin? - - # authorize if user has at least one role that has this permission - roles = memberships.collect {|m| m.roles}.flatten.uniq - roles << (self.logged? ? Role.non_member : Role.anonymous) - roles.any? {|role| - role.allowed_to?(action) && - (block_given? ? yield(role, self) : true) - } - else - false - end - end - - # Is the user allowed to do the specified action on any project? - # See allowed_to? for the actions and valid options. - def allowed_to_globally?(action, options, &block) - allowed_to?(action, nil, options.reverse_merge(:global => true), &block) - end - - # Returns true if the user is allowed to delete his own account - def own_account_deletable? - Setting.unsubscribe? && - (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?) - end - - safe_attributes 'login', - 'firstname', - 'lastname', - 'mail', - 'mail_notification', - 'language', - 'custom_field_values', - 'custom_fields', - 'identity_url' - - safe_attributes 'status', - 'auth_source_id', - :if => lambda {|user, current_user| current_user.admin?} - - safe_attributes 'group_ids', - :if => lambda {|user, current_user| current_user.admin? && !user.new_record?} - - # Utility method to help check if a user should be notified about an - # event. - # - # TODO: only supports Issue events currently - def notify_about?(object) - case mail_notification - when 'all' - true - when 'selected' - # user receives notifications for created/assigned issues on unselected projects - if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)) - true - else - false - end - when 'none' - false - when 'only_my_events' - if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)) - true - else - false - end - when 'only_assigned' - if object.is_a?(Issue) && (is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)) - true - else - false - end - when 'only_owner' - if object.is_a?(Issue) && object.author == self - true - else - false - end - else - false - end - end - - def self.current=(user) - @current_user = user - end - - def self.current - @current_user ||= User.anonymous - end - - # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only - # one anonymous user per database. - def self.anonymous - anonymous_user = AnonymousUser.first - if anonymous_user.nil? - anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0) - raise 'Unable to create the anonymous user.' if anonymous_user.new_record? - end - anonymous_user - end - - # Salts all existing unsalted passwords - # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password)) - # This method is used in the SaltPasswords migration and is to be kept as is - def self.salt_unsalted_passwords! - transaction do - User.where("salt IS NULL OR salt = ''").find_each do |user| - next if user.hashed_password.blank? - salt = User.generate_salt - hashed_password = User.hash_password("#{salt}#{user.hashed_password}") - User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password) - end - end - end - - protected - - def validate_password_length - # Password length validation based on setting - if !password.nil? && password.size < Setting.password_min_length.to_i - errors.add(:password, :too_short, :count => Setting.password_min_length.to_i) - end - end - - private - - # Removes references that are not handled by associations - # Things that are not deleted are reassociated with the anonymous user - def remove_references_before_destroy - return if self.id.nil? - - substitute = User.anonymous - Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id] - Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] - JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s] - JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s] - Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - # Remove private queries and keep public ones - ::Query.delete_all ['user_id = ? AND is_public = ?', id, false] - ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] - TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] - Token.delete_all ['user_id = ?', id] - Watcher.delete_all ['user_id = ?', id] - WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] - end - - # Return password digest - def self.hash_password(clear_password) - Digest::SHA1.hexdigest(clear_password || "") - end - - # Returns a 128bits random salt as a hex string (32 chars long) - def self.generate_salt - Redmine::Utils.random_hex(16) - end - -end - -class AnonymousUser < User - validate :validate_anonymous_uniqueness, :on => :create - - def validate_anonymous_uniqueness - # There should be only one AnonymousUser in the database - errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first) - end - - def available_custom_fields - [] - end - - # Overrides a few properties - def logged?; false end - def admin; false end - def name(*args); I18n.t(:label_user_anonymous) end - def mail; nil end - def time_zone; nil end - def rss_key; nil end - - def pref - UserPreference.new(:user => self) - end - - # Anonymous user can not be destroyed - def destroy - false - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c6/c694dfda8151ff2b41dbc5035ac34e1f8ed39803.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c694dfda8151ff2b41dbc5035ac34e1f8ed39803.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,62 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'net/pop' + +module Redmine + module POP3 + class << self + def check(pop_options={}, options={}) + host = pop_options[:host] || '127.0.0.1' + port = pop_options[:port] || '110' + apop = (pop_options[:apop].to_s == '1') + delete_unprocessed = (pop_options[:delete_unprocessed].to_s == '1') + + pop = Net::POP3.APOP(apop).new(host,port) + logger.debug "Connecting to #{host}..." if logger && logger.debug? + pop.start(pop_options[:username], pop_options[:password]) do |pop_session| + if pop_session.mails.empty? + logger.debug "No email to process" if logger && logger.debug? + else + logger.debug "#{pop_session.mails.size} email(s) to process..." if logger && logger.debug? + pop_session.each_mail do |msg| + message = msg.pop + message_id = (message =~ /^Message-I[dD]: (.*)/ ? $1 : '').strip + if MailHandler.receive(message, options) + msg.delete + logger.debug "--> Message #{message_id} processed and deleted from the server" if logger && logger.debug? + else + if delete_unprocessed + msg.delete + logger.debug "--> Message #{message_id} NOT processed and deleted from the server" if logger && logger.debug? + else + logger.debug "--> Message #{message_id} NOT processed and left on the server" if logger && logger.debug? + end + end + end + end + end + end + + private + + def logger + ::Rails.logger + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c6/c69b9e953e7f9b3d22f5c668c4e090860e04cf57.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c69b9e953e7f9b3d22f5c668c4e090860e04cf57.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,28 @@ += Redmine + +Redmine is a flexible project management web application written using Ruby on Rails framework. + +More details can be found at http://www.redmine.org + += License + +Copyright (C) 2006-2014 Jean-Philippe Lang + +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. + +Icons credits: + +* Mark James (Silk Icons) licensed under a Creative Commons Attribution 2.5 License. +* Yusuke Kamiyamane (Fugue Icons) licensed under a Creative Commons Attribution 3.0 License. diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c6/c6d9ab86ea1cdaba4e340d8bf5228b6e3d11c2ff.svn-base --- a/.svn/pristine/c6/c6d9ab86ea1cdaba4e340d8bf5228b6e3d11c2ff.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class CommentsController < ApplicationController - default_search_scope :news - model_object News - before_filter :find_model_object - before_filter :find_project_from_association - before_filter :authorize - - def create - raise Unauthorized unless @news.commentable? - - @comment = Comment.new - @comment.safe_attributes = params[:comment] - @comment.author = User.current - if @news.comments << @comment - flash[:notice] = l(:label_comment_added) - end - - redirect_to :controller => 'news', :action => 'show', :id => @news - end - - def destroy - @news.comments.find(params[:comment_id]).destroy - redirect_to :controller => 'news', :action => 'show', :id => @news - end - - private - - # ApplicationController's find_model_object sets it based on the controller - # name so it needs to be overriden and set to @news instead - def find_model_object - super - @news = @object - @comment = nil - @news - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c7/c7537906149075d910ea5c8b0b1bfdd4bc722018.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c7/c7537906149075d910ea5c8b0b1bfdd4bc722018.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,6 @@ +

    <%= l(:label_calendar) %>

    + +<% calendar = Redmine::Helpers::Calendar.new(Date.today, current_language, :week) + calendar.events = calendar_items(calendar.startdt, calendar.enddt) %> + +<%= render :partial => 'common/calendar', :locals => {:calendar => calendar } %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c7/c76b4386c82424294b50c9641cdeb22f3fba841c.svn-base --- a/.svn/pristine/c7/c76b4386c82424294b50c9641cdeb22f3fba841c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,183 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class ProjectNestedSetTest < ActiveSupport::TestCase - - def setup - Project.delete_all - - @a = Project.create!(:name => 'A', :identifier => 'projecta') - @a1 = Project.create!(:name => 'A1', :identifier => 'projecta1') - @a1.set_parent!(@a) - @a2 = Project.create!(:name => 'A2', :identifier => 'projecta2') - @a2.set_parent!(@a) - - @c = Project.create!(:name => 'C', :identifier => 'projectc') - @c1 = Project.create!(:name => 'C1', :identifier => 'projectc1') - @c1.set_parent!(@c) - - @b = Project.create!(:name => 'B', :identifier => 'projectb') - @b2 = Project.create!(:name => 'B2', :identifier => 'projectb2') - @b2.set_parent!(@b) - @b1 = Project.create!(:name => 'B1', :identifier => 'projectb1') - @b1.set_parent!(@b) - @b11 = Project.create!(:name => 'B11', :identifier => 'projectb11') - @b11.set_parent!(@b1) - - @a, @a1, @a2, @b, @b1, @b11, @b2, @c, @c1 = *(Project.all.sort_by(&:name)) - end - - def test_valid_tree - assert_valid_nested_set - end - - def test_rebuild_should_build_valid_tree - Project.update_all "lft = NULL, rgt = NULL" - - Project.rebuild! - assert_valid_nested_set - end - - def test_rebuild_tree_should_build_valid_tree_even_with_valid_lft_rgt_values - Project.update_all "name = 'YY'", {:id => @a.id } - # lft and rgt values are still valid (Project.rebuild! would not update anything) - # but projects are not ordered properly (YY is in the first place) - - Project.rebuild_tree! - assert_valid_nested_set - end - - def test_moving_a_child_to_a_different_parent_should_keep_valid_tree - assert_no_difference 'Project.count' do - Project.find_by_name('B1').set_parent!(Project.find_by_name('A2')) - end - assert_valid_nested_set - end - - def test_renaming_a_root_to_first_position_should_update_nested_set_order - @c.name = '1' - @c.save! - assert_valid_nested_set - end - - def test_renaming_a_root_to_middle_position_should_update_nested_set_order - @a.name = 'BA' - @a.save! - assert_valid_nested_set - end - - def test_renaming_a_root_to_last_position_should_update_nested_set_order - @a.name = 'D' - @a.save! - assert_valid_nested_set - end - - def test_renaming_a_root_to_same_position_should_update_nested_set_order - @c.name = 'D' - @c.save! - assert_valid_nested_set - end - - def test_renaming_a_child_should_update_nested_set_order - @a1.name = 'A3' - @a1.save! - assert_valid_nested_set - end - - def test_renaming_a_child_with_child_should_update_nested_set_order - @b1.name = 'B3' - @b1.save! - assert_valid_nested_set - end - - def test_adding_a_root_to_first_position_should_update_nested_set_order - project = Project.create!(:name => '1', :identifier => 'projectba') - assert_valid_nested_set - end - - def test_adding_a_root_to_middle_position_should_update_nested_set_order - project = Project.create!(:name => 'BA', :identifier => 'projectba') - assert_valid_nested_set - end - - def test_adding_a_root_to_last_position_should_update_nested_set_order - project = Project.create!(:name => 'Z', :identifier => 'projectba') - assert_valid_nested_set - end - - def test_destroying_a_root_with_children_should_keep_valid_tree - assert_difference 'Project.count', -4 do - Project.find_by_name('B').destroy - end - assert_valid_nested_set - end - - def test_destroying_a_child_with_children_should_keep_valid_tree - assert_difference 'Project.count', -2 do - Project.find_by_name('B1').destroy - end - assert_valid_nested_set - end - - private - - def assert_nested_set_values(h) - assert Project.valid? - h.each do |project, expected| - project.reload - assert_equal expected, [project.parent_id, project.lft, project.rgt], "Unexpected nested set values for #{project.name}" - end - end - - def assert_valid_nested_set - projects = Project.all - lft_rgt = projects.map {|p| [p.lft, p.rgt]}.flatten - assert_equal projects.size * 2, lft_rgt.uniq.size - assert_equal 1, lft_rgt.min - assert_equal projects.size * 2, lft_rgt.max - - projects.each do |project| - # lft should always be < rgt - assert project.lft < project.rgt, "lft=#{project.lft} was not < rgt=#{project.rgt} for project #{project.name}" - if project.parent_id - # child lft/rgt values must be greater/lower - assert_not_nil project.parent, "parent was nil for project #{project.name}" - assert project.lft > project.parent.lft, "lft=#{project.lft} was not > parent.lft=#{project.parent.lft} for project #{project.name}" - assert project.rgt < project.parent.rgt, "rgt=#{project.rgt} was not < parent.rgt=#{project.parent.rgt} for project #{project.name}" - end - # no overlapping lft/rgt values - overlapping = projects.detect {|other| - other != project && ( - (other.lft > project.lft && other.lft < project.rgt && other.rgt > project.rgt) || - (other.rgt > project.lft && other.rgt < project.rgt && other.lft < project.lft) - ) - } - assert_nil overlapping, (overlapping && "Project #{overlapping.name} (#{overlapping.lft}/#{overlapping.rgt}) overlapped #{project.name} (#{project.lft}/#{project.rgt})") - end - - # root projects sorted alphabetically - assert_equal Project.roots.map(&:name).sort, Project.roots.sort_by(&:lft).map(&:name), "Root projects were not properly sorted" - projects.each do |project| - if project.children.any? - # sibling projects sorted alphabetically - assert_equal project.children.map(&:name).sort, project.children.order('lft').map(&:name), "Project #{project.name}'s children were not properly sorted" - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c7/c7caca3b76ded3c7cd983503d6e3ad0588e89467.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c7/c7caca3b76ded3c7cd983503d6e3ad0588e89467.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,64 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class CommentsControllerTest < ActionController::TestCase + fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news, :comments + + def setup + User.current = nil + end + + def test_add_comment + @request.session[:user_id] = 2 + post :create, :id => 1, :comment => { :comments => 'This is a test comment' } + assert_redirected_to '/news/1' + + comment = News.find(1).comments.last + assert_not_nil comment + assert_equal 'This is a test comment', comment.comments + assert_equal User.find(2), comment.author + end + + def test_empty_comment_should_not_be_added + @request.session[:user_id] = 2 + assert_no_difference 'Comment.count' do + post :create, :id => 1, :comment => { :comments => '' } + assert_response :redirect + assert_redirected_to '/news/1' + end + end + + def test_create_should_be_denied_if_news_is_not_commentable + News.any_instance.stubs(:commentable?).returns(false) + @request.session[:user_id] = 2 + assert_no_difference 'Comment.count' do + post :create, :id => 1, :comment => { :comments => 'This is a test comment' } + assert_response 403 + end + end + + def test_destroy_comment + comments_count = News.find(1).comments.size + @request.session[:user_id] = 2 + delete :destroy, :id => 1, :comment_id => 2 + assert_redirected_to '/news/1' + assert_nil Comment.find_by_id(2) + assert_equal comments_count - 1, News.find(1).comments.size + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c8/c8161dc18d368b5a9f7435cd9c889d8ea63a8a29.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c8/c8161dc18d368b5a9f7435cd9c889d8ea63a8a29.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,252 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'diff' +require 'enumerator' + +class WikiPage < ActiveRecord::Base + include Redmine::SafeAttributes + + belongs_to :wiki + has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy + acts_as_attachable :delete_permission => :delete_wiki_pages_attachments + acts_as_tree :dependent => :nullify, :order => 'title' + + acts_as_watchable + acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"}, + :description => :text, + :datetime => :created_on, + :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}} + + acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"], + :include => [{:wiki => :project}, :content], + :permission => :view_wiki_pages, + :project_key => "#{Wiki.table_name}.project_id" + + attr_accessor :redirect_existing_links + + validates_presence_of :title + validates_format_of :title, :with => /\A[^,\.\/\?\;\|\s]*\z/ + validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false + validates_associated :content + + validate :validate_parent_title + before_destroy :remove_redirects + before_save :handle_redirects + + # eager load information about last updates, without loading text + scope :with_updated_on, lambda { + select("#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version"). + joins("LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id") + } + + # Wiki pages that are protected by default + DEFAULT_PROTECTED_PAGES = %w(sidebar) + + safe_attributes 'parent_id', 'parent_title', + :if => lambda {|page, user| page.new_record? || user.allowed_to?(:rename_wiki_pages, page.project)} + + def initialize(attributes=nil, *args) + super + if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase) + self.protected = true + end + end + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_wiki_pages, project) + end + + def title=(value) + value = Wiki.titleize(value) + @previous_title = read_attribute(:title) if @previous_title.blank? + write_attribute(:title, value) + end + + def handle_redirects + self.title = Wiki.titleize(title) + # Manage redirects if the title has changed + if !@previous_title.blank? && (@previous_title != title) && !new_record? + # Update redirects that point to the old title + wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r| + r.redirects_to = title + r.title == r.redirects_to ? r.destroy : r.save + end + # Remove redirects for the new title + wiki.redirects.find_all_by_title(title).each(&:destroy) + # Create a redirect to the new title + wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0" + @previous_title = nil + end + end + + def remove_redirects + # Remove redirects to this page + wiki.redirects.find_all_by_redirects_to(title).each(&:destroy) + end + + def pretty_title + WikiPage.pretty_title(title) + end + + def content_for_version(version=nil) + result = content.versions.find_by_version(version.to_i) if version + result ||= content + result + end + + def diff(version_to=nil, version_from=nil) + version_to = version_to ? version_to.to_i : self.content.version + content_to = content.versions.find_by_version(version_to) + content_from = version_from ? content.versions.find_by_version(version_from.to_i) : content_to.try(:previous) + return nil unless content_to && content_from + + if content_from.version > content_to.version + content_to, content_from = content_from, content_to + end + + (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil + end + + def annotate(version=nil) + version = version ? version.to_i : self.content.version + c = content.versions.find_by_version(version) + c ? WikiAnnotate.new(c) : nil + end + + def self.pretty_title(str) + (str && str.is_a?(String)) ? str.tr('_', ' ') : str + end + + def project + wiki.project + end + + def text + content.text if content + end + + def updated_on + unless @updated_on + if time = read_attribute(:updated_on) + # content updated_on was eager loaded with the page + begin + @updated_on = (self.class.default_timezone == :utc ? Time.parse(time.to_s).utc : Time.parse(time.to_s).localtime) + rescue + end + else + @updated_on = content && content.updated_on + end + end + @updated_on + end + + # Returns true if usr is allowed to edit the page, otherwise false + def editable_by?(usr) + !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project) + end + + def attachments_deletable?(usr=User.current) + editable_by?(usr) && super(usr) + end + + def parent_title + @parent_title || (self.parent && self.parent.pretty_title) + end + + def parent_title=(t) + @parent_title = t + parent_page = t.blank? ? nil : self.wiki.find_page(t) + self.parent = parent_page + end + + # Saves the page and its content if text was changed + def save_with_content(content) + ret = nil + transaction do + self.content = content + if new_record? + # Rails automatically saves associated content + ret = save + else + ret = save && (content.text_changed? ? content.save : true) + end + raise ActiveRecord::Rollback unless ret + end + ret + end + + protected + + def validate_parent_title + errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil? + errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self)) + errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id) + end +end + +class WikiDiff < Redmine::Helpers::Diff + attr_reader :content_to, :content_from + + def initialize(content_to, content_from) + @content_to = content_to + @content_from = content_from + super(content_to.text, content_from.text) + end +end + +class WikiAnnotate + attr_reader :lines, :content + + def initialize(content) + @content = content + current = content + current_lines = current.text.split(/\r?\n/) + @lines = current_lines.collect {|t| [nil, nil, t]} + positions = [] + current_lines.size.times {|i| positions << i} + while (current.previous) + d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten + d.each_slice(3) do |s| + sign, line = s[0], s[1] + if sign == '+' && positions[line] && positions[line] != -1 + if @lines[positions[line]][0].nil? + @lines[positions[line]][0] = current.version + @lines[positions[line]][1] = current.author + end + end + end + d.each_slice(3) do |s| + sign, line = s[0], s[1] + if sign == '-' + positions.insert(line, -1) + else + positions[line] = nil + end + end + positions.compact! + # Stop if every line is annotated + break unless @lines.detect { |line| line[0].nil? } + current = current.previous + end + @lines.each { |line| + line[0] ||= current.version + # if the last known version is > 1 (eg. history was cleared), we don't know the author + line[1] ||= current.author if current.version == 1 + } + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c8/c851d242d762b6f733cca62cd2f74278f083ce0f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c8/c851d242d762b6f733cca62cd2f74278f083ce0f.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,33 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingProjectEnumerationsTest < ActionController::IntegrationTest + def test_project_enumerations + assert_routing( + { :method => 'put', :path => "/projects/64/enumerations" }, + { :controller => 'project_enumerations', :action => 'update', + :project_id => '64' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/64/enumerations" }, + { :controller => 'project_enumerations', :action => 'destroy', + :project_id => '64' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c8/c86b179f5d130043d8c3b043bc12c22e16c1356d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c8/c86b179f5d130043d8c3b043bc12c22e16c1356d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,437 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module MenuManager + class MenuError < StandardError #:nodoc: + end + + module MenuController + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}} + mattr_accessor :menu_items + + # Set the menu item name for a controller or specific actions + # Examples: + # * menu_item :tickets # => sets the menu name to :tickets for the whole controller + # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only + # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only + # + # The default menu item name for a controller is controller_name by default + # Eg. the default menu item name for ProjectsController is :projects + def menu_item(id, options = {}) + if actions = options[:only] + actions = [] << actions unless actions.is_a?(Array) + actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id} + else + menu_items[controller_name.to_sym][:default] = id + end + end + end + + def menu_items + self.class.menu_items + end + + # Returns the menu item name according to the current action + def current_menu_item + @current_menu_item ||= menu_items[controller_name.to_sym][:actions][action_name.to_sym] || + menu_items[controller_name.to_sym][:default] + end + + # Redirects user to the menu item of the given project + # Returns false if user is not authorized + def redirect_to_project_menu_item(project, name) + item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name.to_s == name.to_s} + if item && User.current.allowed_to?(item.url, project) && (item.condition.nil? || item.condition.call(project)) + redirect_to({item.param => project}.merge(item.url)) + return true + end + false + end + end + + module MenuHelper + # Returns the current menu item name + def current_menu_item + controller.current_menu_item + end + + # Renders the application main menu + def render_main_menu(project) + render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project) + end + + def display_main_menu?(project) + menu_name = project && !project.new_record? ? :project_menu : :application_menu + Redmine::MenuManager.items(menu_name).children.present? + end + + def render_menu(menu, project=nil) + links = [] + menu_items_for(menu, project) do |node| + links << render_menu_node(node, project) + end + links.empty? ? nil : content_tag('ul', links.join("\n").html_safe) + end + + def render_menu_node(node, project=nil) + if node.children.present? || !node.child_menus.nil? + return render_menu_node_with_children(node, project) + else + caption, url, selected = extract_node_details(node, project) + return content_tag('li', + render_single_menu_node(node, caption, url, selected)) + end + end + + def render_menu_node_with_children(node, project=nil) + caption, url, selected = extract_node_details(node, project) + + html = [].tap do |html| + html << '
  • ' + # Parent + html << render_single_menu_node(node, caption, url, selected) + + # Standard children + standard_children_list = "".html_safe.tap do |child_html| + node.children.each do |child| + child_html << render_menu_node(child, project) + end + end + + html << content_tag(:ul, standard_children_list, :class => 'menu-children') unless standard_children_list.empty? + + # Unattached children + unattached_children_list = render_unattached_children_menu(node, project) + html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank? + + html << '
  • ' + end + return html.join("\n").html_safe + end + + # Returns a list of unattached children menu items + def render_unattached_children_menu(node, project) + return nil unless node.child_menus + + "".html_safe.tap do |child_html| + unattached_children = node.child_menus.call(project) + # Tree nodes support #each so we need to do object detection + if unattached_children.is_a? Array + unattached_children.each do |child| + child_html << content_tag(:li, render_unattached_menu_item(child, project)) + end + else + raise MenuError, ":child_menus must be an array of MenuItems" + end + end + end + + def render_single_menu_node(item, caption, url, selected) + link_to(h(caption), url, item.html_options(:selected => selected)) + end + + def render_unattached_menu_item(menu_item, project) + raise MenuError, ":child_menus must be an array of MenuItems" unless menu_item.is_a? MenuItem + + if User.current.allowed_to?(menu_item.url, project) + link_to(h(menu_item.caption), + menu_item.url, + menu_item.html_options) + end + end + + def menu_items_for(menu, project=nil) + items = [] + Redmine::MenuManager.items(menu).root.children.each do |node| + if allowed_node?(node, User.current, project) + if block_given? + yield node + else + items << node # TODO: not used? + end + end + end + return block_given? ? nil : items + end + + def extract_node_details(node, project=nil) + item = node + url = case item.url + when Hash + project.nil? ? item.url : {item.param => project}.merge(item.url) + when Symbol + send(item.url) + else + item.url + end + caption = item.caption(project) + return [caption, url, (current_menu_item == item.name)] + end + + # Checks if a user is allowed to access the menu item by: + # + # * Checking the url target (project only) + # * Checking the conditions of the item + def allowed_node?(node, user, project) + if project && user && !user.allowed_to?(node.url, project) + return false + end + if node.condition && !node.condition.call(project) + # Condition that doesn't pass + return false + end + return true + end + end + + class << self + def map(menu_name) + @items ||= {} + mapper = Mapper.new(menu_name.to_sym, @items) + if block_given? + yield mapper + else + mapper + end + end + + def items(menu_name) + @items[menu_name.to_sym] || MenuNode.new(:root, {}) + end + end + + class Mapper + attr_reader :menu, :menu_items + + def initialize(menu, items) + items[menu] ||= MenuNode.new(:root, {}) + @menu = menu + @menu_items = items[menu] + end + + # Adds an item at the end of the menu. Available options: + # * param: the parameter name that is used for the project id (default is :id) + # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true + # * caption that can be: + # * a localized string Symbol + # * a String + # * a Proc that can take the project as argument + # * before, after: specify where the menu item should be inserted (eg. :after => :activity) + # * parent: menu item will be added as a child of another named menu (eg. :parent => :issues) + # * children: a Proc that is called before rendering the item. The Proc should return an array of MenuItems, which will be added as children to this item. + # eg. :children => Proc.new {|project| [Redmine::MenuManager::MenuItem.new(...)] } + # * last: menu item will stay at the end (eg. :last => true) + # * html_options: a hash of html options that are passed to link_to + def push(name, url, options={}) + options = options.dup + + if options[:parent] + subtree = self.find(options[:parent]) + if subtree + target_root = subtree + else + target_root = @menu_items.root + end + + else + target_root = @menu_items.root + end + + # menu item position + if first = options.delete(:first) + target_root.prepend(MenuItem.new(name, url, options)) + elsif before = options.delete(:before) + + if exists?(before) + target_root.add_at(MenuItem.new(name, url, options), position_of(before)) + else + target_root.add(MenuItem.new(name, url, options)) + end + + elsif after = options.delete(:after) + + if exists?(after) + target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1) + else + target_root.add(MenuItem.new(name, url, options)) + end + + elsif options[:last] # don't delete, needs to be stored + target_root.add_last(MenuItem.new(name, url, options)) + else + target_root.add(MenuItem.new(name, url, options)) + end + end + + # Removes a menu item + def delete(name) + if found = self.find(name) + @menu_items.remove!(found) + end + end + + # Checks if a menu item exists + def exists?(name) + @menu_items.any? {|node| node.name == name} + end + + def find(name) + @menu_items.find {|node| node.name == name} + end + + def position_of(name) + @menu_items.each do |node| + if node.name == name + return node.position + end + end + end + end + + class MenuNode + include Enumerable + attr_accessor :parent + attr_reader :last_items_count, :name + + def initialize(name, content = nil) + @name = name + @children = [] + @last_items_count = 0 + end + + def children + if block_given? + @children.each {|child| yield child} + else + @children + end + end + + # Returns the number of descendants + 1 + def size + @children.inject(1) {|sum, node| sum + node.size} + end + + def each &block + yield self + children { |child| child.each(&block) } + end + + # Adds a child at first position + def prepend(child) + add_at(child, 0) + end + + # Adds a child at given position + def add_at(child, position) + raise "Child already added" if find {|node| node.name == child.name} + + @children = @children.insert(position, child) + child.parent = self + child + end + + # Adds a child as last child + def add_last(child) + add_at(child, -1) + @last_items_count += 1 + child + end + + # Adds a child + def add(child) + position = @children.size - @last_items_count + add_at(child, position) + end + alias :<< :add + + # Removes a child + def remove!(child) + @children.delete(child) + @last_items_count -= +1 if child && child.last + child.parent = nil + child + end + + # Returns the position for this node in it's parent + def position + self.parent.children.index(self) + end + + # Returns the root for this node + def root + root = self + root = root.parent while root.parent + root + end + end + + class MenuItem < MenuNode + include Redmine::I18n + attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last + + def initialize(name, url, options) + raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call) + raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash) + raise ArgumentError, "Cannot set the :parent to be the same as this item" if options[:parent] == name.to_sym + raise ArgumentError, "Invalid option :children for menu item '#{name}'" if options[:children] && !options[:children].respond_to?(:call) + @name = name + @url = url + @condition = options[:if] + @param = options[:param] || :id + @caption = options[:caption] + @html_options = options[:html] || {} + # Adds a unique class to each menu item based on its name + @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ') + @parent = options[:parent] + @child_menus = options[:children] + @last = options[:last] || false + super @name.to_sym + end + + def caption(project=nil) + if @caption.is_a?(Proc) + c = @caption.call(project).to_s + c = @name.to_s.humanize if c.blank? + c + else + if @caption.nil? + l_or_humanize(name, :prefix => 'label_') + else + @caption.is_a?(Symbol) ? l(@caption) : @caption + end + end + end + + def html_options(options={}) + if options[:selected] + o = @html_options.dup + o[:class] += ' selected' + o + else + @html_options + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c8/c8ead97161810444d935cbc0189f12e56246f822.svn-base --- a/.svn/pristine/c8/c8ead97161810444d935cbc0189f12e56246f822.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,494 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -#require 'shoulda' -ENV["RAILS_ENV"] = "test" -require File.expand_path(File.dirname(__FILE__) + "/../config/environment") -require 'rails/test_help' -require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s - -require File.expand_path(File.dirname(__FILE__) + '/object_helpers') -include ObjectHelpers - -class ActiveSupport::TestCase - include ActionDispatch::TestProcess - - # Transactional fixtures accelerate your tests by wrapping each test method - # in a transaction that's rolled back on completion. This ensures that the - # test database remains unchanged so your fixtures don't have to be reloaded - # between every test method. Fewer database queries means faster tests. - # - # Read Mike Clark's excellent walkthrough at - # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting - # - # Every Active Record database supports transactions except MyISAM tables - # in MySQL. Turn off transactional fixtures in this case; however, if you - # don't care one way or the other, switching from MyISAM to InnoDB tables - # is recommended. - self.use_transactional_fixtures = true - - # Instantiated fixtures are slow, but give you @david where otherwise you - # would need people(:david). If you don't want to migrate your existing - # test cases which use the @david style and don't mind the speed hit (each - # instantiated fixtures translates to a database query per test method), - # then set this back to true. - self.use_instantiated_fixtures = false - - # Add more helper methods to be used by all tests here... - - def log_user(login, password) - User.anonymous - get "/login" - assert_equal nil, session[:user_id] - assert_response :success - assert_template "account/login" - post "/login", :username => login, :password => password - assert_equal login, User.find(session[:user_id]).login - end - - def uploaded_test_file(name, mime) - fixture_file_upload("files/#{name}", mime, true) - end - - def credentials(user, password=nil) - {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)} - end - - # Mock out a file - def self.mock_file - file = 'a_file.png' - file.stubs(:size).returns(32) - file.stubs(:original_filename).returns('a_file.png') - file.stubs(:content_type).returns('image/png') - file.stubs(:read).returns(false) - file - end - - def mock_file - self.class.mock_file - end - - def mock_file_with_options(options={}) - file = '' - file.stubs(:size).returns(32) - original_filename = options[:original_filename] || nil - file.stubs(:original_filename).returns(original_filename) - content_type = options[:content_type] || nil - file.stubs(:content_type).returns(content_type) - file.stubs(:read).returns(false) - file - end - - # Use a temporary directory for attachment related tests - def set_tmp_attachments_directory - Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test") - unless File.directory?("#{Rails.root}/tmp/test/attachments") - Dir.mkdir "#{Rails.root}/tmp/test/attachments" - end - Attachment.storage_path = "#{Rails.root}/tmp/test/attachments" - end - - def set_fixtures_attachments_directory - Attachment.storage_path = "#{Rails.root}/test/fixtures/files" - end - - def with_settings(options, &block) - saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].is_a?(Symbol) ? Setting[k] : Setting[k].dup; h} - options.each {|k, v| Setting[k] = v} - yield - ensure - saved_settings.each {|k, v| Setting[k] = v} if saved_settings - end - - # Yields the block with user as the current user - def with_current_user(user, &block) - saved_user = User.current - User.current = user - yield - ensure - User.current = saved_user - end - - def change_user_password(login, new_password) - user = User.first(:conditions => {:login => login}) - user.password, user.password_confirmation = new_password, new_password - user.save! - end - - def self.ldap_configured? - @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389) - return @test_ldap.bind - rescue Exception => e - # LDAP is not listening - return nil - end - - def self.convert_installed? - Redmine::Thumbnail.convert_available? - end - - # Returns the path to the test +vendor+ repository - def self.repository_path(vendor) - Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s - end - - # Returns the url of the subversion test repository - def self.subversion_repository_url - path = repository_path('subversion') - path = '/' + path unless path.starts_with?('/') - "file://#{path}" - end - - # Returns true if the +vendor+ test repository is configured - def self.repository_configured?(vendor) - File.directory?(repository_path(vendor)) - end - - def repository_path_hash(arr) - hs = {} - hs[:path] = arr.join("/") - hs[:param] = arr.join("/") - hs - end - - def assert_save(object) - saved = object.save - message = "#{object.class} could not be saved" - errors = object.errors.full_messages.map {|m| "- #{m}"} - message << ":\n#{errors.join("\n")}" if errors.any? - assert_equal true, saved, message - end - - def assert_error_tag(options={}) - assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options)) - end - - def assert_include(expected, s, message=nil) - assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"") - end - - def assert_not_include(expected, s) - assert !s.include?(expected), "\"#{expected}\" found in \"#{s}\"" - end - - def assert_select_in(text, *args, &block) - d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root - assert_select(d, *args, &block) - end - - def assert_mail_body_match(expected, mail) - if expected.is_a?(String) - assert_include expected, mail_body(mail) - else - assert_match expected, mail_body(mail) - end - end - - def assert_mail_body_no_match(expected, mail) - if expected.is_a?(String) - assert_not_include expected, mail_body(mail) - else - assert_no_match expected, mail_body(mail) - end - end - - def mail_body(mail) - mail.parts.first.body.encoded - end - - # Shoulda macros - def self.should_render_404 - should_respond_with :not_found - should_render_template 'common/error' - end - - def self.should_have_before_filter(expected_method, options = {}) - should_have_filter('before', expected_method, options) - end - - def self.should_have_after_filter(expected_method, options = {}) - should_have_filter('after', expected_method, options) - end - - def self.should_have_filter(filter_type, expected_method, options) - description = "have #{filter_type}_filter :#{expected_method}" - description << " with #{options.inspect}" unless options.empty? - - should description do - klass = "action_controller/filters/#{filter_type}_filter".classify.constantize - expected = klass.new(:filter, expected_method.to_sym, options) - assert_equal 1, @controller.class.filter_chain.select { |filter| - filter.method == expected.method && filter.kind == expected.kind && - filter.options == expected.options && filter.class == expected.class - }.size - end - end - - # Test that a request allows the three types of API authentication - # - # * HTTP Basic with username and password - # * HTTP Basic with an api key for the username - # * Key based with the key=X parameter - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_api_authentication(http_method, url, parameters={}, options={}) - should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options) - should_allow_http_basic_auth_with_key(http_method, url, parameters, options) - should_allow_key_based_auth(http_method, url, parameters, options) - end - - # Test that a request allows the username and password for HTTP BASIC - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow http basic auth using a username and password for #{http_method} #{url}" do - context "with a valid HTTP authentication" do - setup do - @user = User.generate! do |user| - user.admin = true - user.password = 'my_password' - end - send(http_method, url, parameters, credentials(@user.login, 'my_password')) - end - - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - - context "with an invalid HTTP authentication" do - setup do - @user = User.generate! - send(http_method, url, parameters, credentials(@user.login, 'wrong_password')) - end - - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - - context "without credentials" do - setup do - send(http_method, url, parameters) - end - - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "include_www_authenticate_header" do - assert @controller.response.headers.has_key?('WWW-Authenticate') - end - end - end - end - - # Test that a request allows the API key with HTTP BASIC - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow http basic auth with a key for #{http_method} #{url}" do - context "with a valid HTTP authentication using the API token" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'api') - send(http_method, url, parameters, credentials(@token.value, 'X')) - end - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should_be_a_valid_response_string_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - - context "with an invalid HTTP authentication" do - setup do - @user = User.generate! - @token = Token.create!(:user => @user, :action => 'feeds') - send(http_method, url, parameters, credentials(@token.value, 'X')) - end - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - end - end - - # Test that a request allows full key authentication - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url, without the key=ZXY parameter - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_key_based_auth(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow key based auth using key=X for #{http_method} #{url}" do - context "with a valid api token" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'api') - # Simple url parse to add on ?key= or &key= - request_url = if url.match(/\?/) - url + "&key=#{@token.value}" - else - url + "?key=#{@token.value}" - end - send(http_method, request_url, parameters) - end - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should_be_a_valid_response_string_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - - context "with an invalid api token" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'feeds') - # Simple url parse to add on ?key= or &key= - request_url = if url.match(/\?/) - url + "&key=#{@token.value}" - else - url + "?key=#{@token.value}" - end - send(http_method, request_url, parameters) - end - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - end - - context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'api') - send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s}) - end - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should_be_a_valid_response_string_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - end - - # Uses should_respond_with_content_type based on what's in the url: - # - # '/project/issues.xml' => should_respond_with_content_type :xml - # '/project/issues.json' => should_respond_with_content_type :json - # - # @param [String] url Request - def self.should_respond_with_content_type_based_on_url(url) - case - when url.match(/xml/i) - should "respond with XML" do - assert_equal 'application/xml', @response.content_type - end - when url.match(/json/i) - should "respond with JSON" do - assert_equal 'application/json', @response.content_type - end - else - raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}" - end - end - - # Uses the url to assert which format the response should be in - # - # '/project/issues.xml' => should_be_a_valid_xml_string - # '/project/issues.json' => should_be_a_valid_json_string - # - # @param [String] url Request - def self.should_be_a_valid_response_string_based_on_url(url) - case - when url.match(/xml/i) - should_be_a_valid_xml_string - when url.match(/json/i) - should_be_a_valid_json_string - else - raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}" - end - end - - # Checks that the response is a valid JSON string - def self.should_be_a_valid_json_string - should "be a valid JSON string (or empty)" do - assert(response.body.blank? || ActiveSupport::JSON.decode(response.body)) - end - end - - # Checks that the response is a valid XML string - def self.should_be_a_valid_xml_string - should "be a valid XML string" do - assert REXML::Document.new(response.body) - end - end - - def self.should_respond_with(status) - should "respond with #{status}" do - assert_response status - end - end -end - -# Simple module to "namespace" all of the API tests -module ApiTest -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c9/c9a4606fd56695d0574a554ddcfcebb976dd9120.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c9/c9a4606fd56695d0574a554ddcfcebb976dd9120.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,190 @@ +/* Redmine - project management software + Copyright (C) 2006-2014 Jean-Philippe Lang */ + +function addFile(inputEl, file, eagerUpload) { + + if ($('#attachments_fields').children().length < 10) { + + var attachmentId = addFile.nextAttachmentId++; + + var fileSpan = $('', { id: 'attachments_' + attachmentId }); + + fileSpan.append( + $('', { type: 'text', 'class': 'filename readonly', name: 'attachments[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name), + $('', { type: 'text', 'class': 'description', name: 'attachments[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload), + $(' ').attr({ href: "#", 'class': 'remove-upload' }).click(removeFile).toggle(!eagerUpload) + ).appendTo('#attachments_fields'); + + if(eagerUpload) { + ajaxUpload(file, attachmentId, fileSpan, inputEl); + } + + return attachmentId; + } + return null; +} + +addFile.nextAttachmentId = 1; + +function ajaxUpload(file, attachmentId, fileSpan, inputEl) { + + function onLoadstart(e) { + fileSpan.removeClass('ajax-waiting'); + fileSpan.addClass('ajax-loading'); + $('input:submit', $(this).parents('form')).attr('disabled', 'disabled'); + } + + function onProgress(e) { + if(e.lengthComputable) { + this.progressbar( 'value', e.loaded * 100 / e.total ); + } + } + + function actualUpload(file, attachmentId, fileSpan, inputEl) { + + ajaxUpload.uploading++; + + uploadBlob(file, $(inputEl).data('upload-path'), attachmentId, { + loadstartEventHandler: onLoadstart.bind(progressSpan), + progressEventHandler: onProgress.bind(progressSpan) + }) + .done(function(result) { + progressSpan.progressbar( 'value', 100 ).remove(); + fileSpan.find('input.description, a').css('display', 'inline-block'); + }) + .fail(function(result) { + progressSpan.text(result.statusText); + }).always(function() { + ajaxUpload.uploading--; + fileSpan.removeClass('ajax-loading'); + var form = fileSpan.parents('form'); + if (form.queue('upload').length == 0 && ajaxUpload.uploading == 0) { + $('input:submit', form).removeAttr('disabled'); + } + form.dequeue('upload'); + }); + } + + var progressSpan = $('
    ').insertAfter(fileSpan.find('input.filename')); + progressSpan.progressbar(); + fileSpan.addClass('ajax-waiting'); + + var maxSyncUpload = $(inputEl).data('max-concurrent-uploads'); + + if(maxSyncUpload == null || maxSyncUpload <= 0 || ajaxUpload.uploading < maxSyncUpload) + actualUpload(file, attachmentId, fileSpan, inputEl); + else + $(inputEl).parents('form').queue('upload', actualUpload.bind(this, file, attachmentId, fileSpan, inputEl)); +} + +ajaxUpload.uploading = 0; + +function removeFile() { + $(this).parent('span').remove(); + return false; +} + +function uploadBlob(blob, uploadUrl, attachmentId, options) { + + var actualOptions = $.extend({ + loadstartEventHandler: $.noop, + progressEventHandler: $.noop + }, options); + + uploadUrl = uploadUrl + '?attachment_id=' + attachmentId; + if (blob instanceof window.File) { + uploadUrl += '&filename=' + encodeURIComponent(blob.name); + } + + return $.ajax(uploadUrl, { + type: 'POST', + contentType: 'application/octet-stream', + beforeSend: function(jqXhr, settings) { + jqXhr.setRequestHeader('Accept', 'application/js'); + // attach proper File object + settings.data = blob; + }, + xhr: function() { + var xhr = $.ajaxSettings.xhr(); + xhr.upload.onloadstart = actualOptions.loadstartEventHandler; + xhr.upload.onprogress = actualOptions.progressEventHandler; + return xhr; + }, + data: blob, + cache: false, + processData: false + }); +} + +function addInputFiles(inputEl) { + var clearedFileInput = $(inputEl).clone().val(''); + + if (inputEl.files) { + // upload files using ajax + uploadAndAttachFiles(inputEl.files, inputEl); + $(inputEl).remove(); + } else { + // browser not supporting the file API, upload on form submission + var attachmentId; + var aFilename = inputEl.value.split(/\/|\\/); + attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false); + if (attachmentId) { + $(inputEl).attr({ name: 'attachments[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId); + } + } + + clearedFileInput.insertAfter('#attachments_fields').on('change', function(){addInputFiles(this);}); +} + +function uploadAndAttachFiles(files, inputEl) { + + var maxFileSize = $(inputEl).data('max-file-size'); + var maxFileSizeExceeded = $(inputEl).data('max-file-size-message'); + + var sizeExceeded = false; + $.each(files, function() { + if (this.size && maxFileSize != null && this.size > parseInt(maxFileSize)) {sizeExceeded=true;} + }); + if (sizeExceeded) { + window.alert(maxFileSizeExceeded); + } else { + $.each(files, function() {addFile(inputEl, this, true);}); + } +} + +function handleFileDropEvent(e) { + + $(this).removeClass('fileover'); + blockEventPropagation(e); + + if ($.inArray('Files', e.dataTransfer.types) > -1) { + uploadAndAttachFiles(e.dataTransfer.files, $('input:file.file_selector')); + } +} + +function dragOverHandler(e) { + $(this).addClass('fileover'); + blockEventPropagation(e); +} + +function dragOutHandler(e) { + $(this).removeClass('fileover'); + blockEventPropagation(e); +} + +function setupFileDrop() { + if (window.File && window.FileList && window.ProgressEvent && window.FormData) { + + $.event.fixHooks.drop = { props: [ 'dataTransfer' ] }; + + $('form div.box').has('input:file').each(function() { + $(this).on({ + dragover: dragOverHandler, + dragleave: dragOutHandler, + drop: handleFileDropEvent + }); + }); + } +} + +$(document).ready(setupFileDrop); diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c9/c9e75dce917bf0005974fff08b7f395523e3b965.svn-base --- a/.svn/pristine/c9/c9e75dce917bf0005974fff08b7f395523e3b965.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1080 +0,0 @@ -en: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%m/%d/%Y" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] - abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] - abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%m/%d/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "less than 1 second" - other: "less than %{count} seconds" - x_seconds: - one: "1 second" - other: "%{count} seconds" - less_than_x_minutes: - one: "less than a minute" - other: "less than %{count} minutes" - x_minutes: - one: "1 minute" - other: "%{count} minutes" - about_x_hours: - one: "about 1 hour" - other: "about %{count} hours" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 day" - other: "%{count} days" - about_x_months: - one: "about 1 month" - other: "about %{count} months" - x_months: - one: "1 month" - other: "%{count} months" - about_x_years: - one: "about 1 year" - other: "about %{count} years" - over_x_years: - one: "over 1 year" - other: "over %{count} years" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "and" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "is not included in the list" - exclusion: "is reserved" - invalid: "is invalid" - confirmation: "doesn't match confirmation" - accepted: "must be accepted" - empty: "can't be empty" - blank: "can't be blank" - too_long: "is too long (maximum is %{count} characters)" - too_short: "is too short (minimum is %{count} characters)" - wrong_length: "is the wrong length (should be %{count} characters)" - taken: "has already been taken" - not_a_number: "is not a number" - not_a_date: "is not a valid date" - greater_than: "must be greater than %{count}" - greater_than_or_equal_to: "must be greater than or equal to %{count}" - equal_to: "must be equal to %{count}" - less_than: "must be less than %{count}" - less_than_or_equal_to: "must be less than or equal to %{count}" - odd: "must be odd" - even: "must be even" - greater_than_start_date: "must be greater than start date" - not_same_project: "doesn't belong to the same project" - circular_dependency: "This relation would create a circular dependency" - cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks" - - actionview_instancetag_blank_option: Please select - - general_text_No: 'No' - general_text_Yes: 'Yes' - general_text_no: 'no' - general_text_yes: 'yes' - general_lang_name: 'English' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '7' - - notice_account_updated: Account was successfully updated. - notice_account_invalid_creditentials: Invalid user or password - notice_account_password_updated: Password was successfully updated. - notice_account_wrong_password: Wrong password - notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you. - notice_account_unknown_email: Unknown user. - notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. - notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. - notice_account_activated: Your account has been activated. You can now log in. - notice_successful_create: Successful creation. - notice_successful_update: Successful update. - notice_successful_delete: Successful deletion. - notice_successful_connection: Successful connection. - notice_file_not_found: The page you were trying to access doesn't exist or has been removed. - notice_locking_conflict: Data has been updated by another user. - notice_not_authorized: You are not authorized to access this page. - notice_not_authorized_archived_project: The project you're trying to access has been archived. - notice_email_sent: "An email was sent to %{value}" - notice_email_error: "An error occurred while sending mail (%{value})" - notice_feeds_access_key_reseted: Your RSS access key was reset. - notice_api_access_key_reseted: Your API access key was reset. - notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." - notice_account_pending: "Your account was created and is now pending administrator approval." - notice_default_data_loaded: Default configuration successfully loaded. - notice_unable_delete_version: Unable to delete version. - notice_unable_delete_time_entry: Unable to delete time log entry. - notice_issue_done_ratios_updated: Issue done ratios updated. - notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})" - notice_issue_successful_create: "Issue %{id} created." - notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it." - notice_account_deleted: "Your account has been permanently deleted." - notice_user_successful_create: "User %{id} created." - - error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" - error_scm_not_found: "The entry or revision was not found in the repository." - error_scm_command_failed: "An error occurred when trying to access the repository: %{value}" - error_scm_annotate: "The entry does not exist or cannot be annotated." - error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size." - error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' - error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' - error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' - error_can_not_delete_custom_field: Unable to delete custom field - error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted." - error_can_not_remove_role: "This role is in use and cannot be deleted." - error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened' - error_can_not_archive_project: This project cannot be archived - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' - error_unable_delete_issue_status: 'Unable to delete issue status' - error_unable_to_connect: "Unable to connect (%{value})" - error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})" - error_session_expired: "Your session has expired. Please login again." - warning_attachments_not_saved: "%{count} file(s) could not be saved." - - mail_subject_lost_password: "Your %{value} password" - mail_body_lost_password: 'To change your password, click on the following link:' - mail_subject_register: "Your %{value} account activation" - mail_body_register: 'To activate your account, click on the following link:' - mail_body_account_information_external: "You can use your %{value} account to log in." - mail_body_account_information: Your account information - mail_subject_account_activation_request: "%{value} account activation request" - mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:" - mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" - mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errors" - - field_name: Name - field_description: Description - field_summary: Summary - field_is_required: Required - field_firstname: First name - field_lastname: Last name - field_mail: Email - field_filename: File - field_filesize: Size - field_downloads: Downloads - field_author: Author - field_created_on: Created - field_updated_on: Updated - field_field_format: Format - field_is_for_all: For all projects - field_possible_values: Possible values - field_regexp: Regular expression - field_min_length: Minimum length - field_max_length: Maximum length - field_value: Value - field_category: Category - field_title: Title - field_project: Project - field_issue: Issue - field_status: Status - field_notes: Notes - field_is_closed: Issue closed - field_is_default: Default value - field_tracker: Tracker - field_subject: Subject - field_due_date: Due date - field_assigned_to: Assignee - field_priority: Priority - field_fixed_version: Target version - field_user: User - field_principal: Principal - field_role: Role - field_homepage: Homepage - field_is_public: Public - field_parent: Subproject of - field_is_in_roadmap: Issues displayed in roadmap - field_login: Login - field_mail_notification: Email notifications - field_admin: Administrator - field_last_login_on: Last connection - field_language: Language - field_effective_date: Date - field_password: Password - field_new_password: New password - field_password_confirmation: Confirmation - field_version: Version - field_type: Type - field_host: Host - field_port: Port - field_account: Account - field_base_dn: Base DN - field_attr_login: Login attribute - field_attr_firstname: Firstname attribute - field_attr_lastname: Lastname attribute - field_attr_mail: Email attribute - field_onthefly: On-the-fly user creation - field_start_date: Start date - field_done_ratio: "% Done" - field_auth_source: Authentication mode - field_hide_mail: Hide my email address - field_comments: Comment - field_url: URL - field_start_page: Start page - field_subproject: Subproject - field_hours: Hours - field_activity: Activity - field_spent_on: Date - field_identifier: Identifier - field_is_filter: Used as a filter - field_issue_to: Related issue - field_delay: Delay - field_assignable: Issues can be assigned to this role - field_redirect_existing_links: Redirect existing links - field_estimated_hours: Estimated time - field_column_names: Columns - field_time_entries: Log time - field_time_zone: Time zone - field_searchable: Searchable - field_default_value: Default value - field_comments_sorting: Display comments - field_parent_title: Parent page - field_editable: Editable - field_watcher: Watcher - field_identity_url: OpenID URL - field_content: Content - field_group_by: Group results by - field_sharing: Sharing - field_parent_issue: Parent task - field_member_of_group: "Assignee's group" - field_assigned_to_role: "Assignee's role" - field_text: Text field - field_visible: Visible - field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" - field_issues_visibility: Issues visibility - field_is_private: Private - field_commit_logs_encoding: Commit messages encoding - field_scm_path_encoding: Path encoding - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvsroot: CVSROOT - field_cvs_module: Module - field_repository_is_default: Main repository - field_multiple: Multiple values - field_auth_source_ldap_filter: LDAP filter - field_core_fields: Standard fields - field_timeout: "Timeout (in seconds)" - field_board_parent: Parent forum - field_private_notes: Private notes - - setting_app_title: Application title - setting_app_subtitle: Application subtitle - setting_welcome_text: Welcome text - setting_default_language: Default language - setting_login_required: Authentication required - setting_self_registration: Self-registration - setting_attachment_max_size: Maximum attachment size - setting_issues_export_limit: Issues export limit - setting_mail_from: Emission email address - setting_bcc_recipients: Blind carbon copy recipients (bcc) - setting_plain_text_mail: Plain text mail (no HTML) - setting_host_name: Host name and path - setting_text_formatting: Text formatting - setting_wiki_compression: Wiki history compression - setting_feeds_limit: Maximum number of items in Atom feeds - setting_default_projects_public: New projects are public by default - setting_autofetch_changesets: Fetch commits automatically - setting_sys_api_enabled: Enable WS for repository management - setting_commit_ref_keywords: Referencing keywords - setting_commit_fix_keywords: Fixing keywords - setting_autologin: Autologin - setting_date_format: Date format - setting_time_format: Time format - setting_cross_project_issue_relations: Allow cross-project issue relations - setting_cross_project_subtasks: Allow cross-project subtasks - setting_issue_list_default_columns: Default columns displayed on the issue list - setting_repositories_encodings: Attachments and repositories encodings - setting_emails_header: Emails header - setting_emails_footer: Emails footer - setting_protocol: Protocol - setting_per_page_options: Objects per page options - setting_user_format: Users display format - setting_activity_days_default: Days displayed on project activity - setting_display_subprojects_issues: Display subprojects issues on main projects by default - setting_enabled_scm: Enabled SCM - setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" - setting_mail_handler_api_enabled: Enable WS for incoming emails - setting_mail_handler_api_key: API key - setting_sequential_project_identifiers: Generate sequential project identifiers - setting_gravatar_enabled: Use Gravatar user icons - setting_gravatar_default: Default Gravatar image - setting_diff_max_lines_displayed: Maximum number of diff lines displayed - setting_file_max_size_displayed: Maximum size of text files displayed inline - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_openid: Allow OpenID login and registration - setting_password_min_length: Minimum password length - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - setting_default_projects_modules: Default enabled modules for new projects - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_field: Use the issue field - setting_issue_done_ratio_issue_status: Use the issue status - setting_start_of_week: Start calendars on - setting_rest_api_enabled: Enable REST web service - setting_cache_formatted_text: Cache formatted text - setting_default_notification_option: Default notification option - setting_commit_logtime_enabled: Enable time logging - setting_commit_logtime_activity_id: Activity for logged time - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - setting_issue_group_assignment: Allow issue assignment to groups - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - setting_unsubscribe: Allow users to delete their own account - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - setting_non_working_week_days: Non-working days - - permission_add_project: Create project - permission_add_subprojects: Create subprojects - permission_edit_project: Edit project - permission_close_project: Close / reopen the project - permission_select_project_modules: Select project modules - permission_manage_members: Manage members - permission_manage_project_activities: Manage project activities - permission_manage_versions: Manage versions - permission_manage_categories: Manage issue categories - permission_view_issues: View Issues - permission_add_issues: Add issues - permission_edit_issues: Edit issues - permission_manage_issue_relations: Manage issue relations - permission_set_issues_private: Set issues public or private - permission_set_own_issues_private: Set own issues public or private - permission_add_issue_notes: Add notes - permission_edit_issue_notes: Edit notes - permission_edit_own_issue_notes: Edit own notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - permission_move_issues: Move issues - permission_delete_issues: Delete issues - permission_manage_public_queries: Manage public queries - permission_save_queries: Save queries - permission_view_gantt: View gantt chart - permission_view_calendar: View calendar - permission_view_issue_watchers: View watchers list - permission_add_issue_watchers: Add watchers - permission_delete_issue_watchers: Delete watchers - permission_log_time: Log spent time - permission_view_time_entries: View spent time - permission_edit_time_entries: Edit time logs - permission_edit_own_time_entries: Edit own time logs - permission_manage_news: Manage news - permission_comment_news: Comment news - permission_manage_documents: Manage documents - permission_view_documents: View documents - permission_manage_files: Manage files - permission_view_files: View files - permission_manage_wiki: Manage wiki - permission_rename_wiki_pages: Rename wiki pages - permission_delete_wiki_pages: Delete wiki pages - permission_view_wiki_pages: View wiki - permission_view_wiki_edits: View wiki history - permission_edit_wiki_pages: Edit wiki pages - permission_delete_wiki_pages_attachments: Delete attachments - permission_protect_wiki_pages: Protect wiki pages - permission_manage_repository: Manage repository - permission_browse_repository: Browse repository - permission_view_changesets: View changesets - permission_commit_access: Commit access - permission_manage_boards: Manage forums - permission_view_messages: View messages - permission_add_messages: Post messages - permission_edit_messages: Edit messages - permission_edit_own_messages: Edit own messages - permission_delete_messages: Delete messages - permission_delete_own_messages: Delete own messages - permission_export_wiki_pages: Export wiki pages - permission_manage_subtasks: Manage subtasks - permission_manage_related_issues: Manage related issues - - project_module_issue_tracking: Issue tracking - project_module_time_tracking: Time tracking - project_module_news: News - project_module_documents: Documents - project_module_files: Files - project_module_wiki: Wiki - project_module_repository: Repository - project_module_boards: Forums - project_module_calendar: Calendar - project_module_gantt: Gantt - - label_user: User - label_user_plural: Users - label_user_new: New user - label_user_anonymous: Anonymous - label_project: Project - label_project_new: New project - label_project_plural: Projects - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: All Projects - label_project_latest: Latest projects - label_issue: Issue - label_issue_new: New issue - label_issue_plural: Issues - label_issue_view_all: View all issues - label_issues_by: "Issues by %{value}" - label_issue_added: Issue added - label_issue_updated: Issue updated - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_document: Document - label_document_new: New document - label_document_plural: Documents - label_document_added: Document added - label_role: Role - label_role_plural: Roles - label_role_new: New role - label_role_and_permissions: Roles and permissions - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_member: Member - label_member_new: New member - label_member_plural: Members - label_tracker: Tracker - label_tracker_plural: Trackers - label_tracker_new: New tracker - label_workflow: Workflow - label_issue_status: Issue status - label_issue_status_plural: Issue statuses - label_issue_status_new: New status - label_issue_category: Issue category - label_issue_category_plural: Issue categories - label_issue_category_new: New category - label_custom_field: Custom field - label_custom_field_plural: Custom fields - label_custom_field_new: New custom field - label_enumerations: Enumerations - label_enumeration_new: New value - label_information: Information - label_information_plural: Information - label_please_login: Please log in - label_register: Register - label_login_with_open_id_option: or login with OpenID - label_password_lost: Lost password - label_home: Home - label_my_page: My page - label_my_account: My account - label_my_projects: My projects - label_my_page_block: My page block - label_administration: Administration - label_login: Sign in - label_logout: Sign out - label_help: Help - label_reported_issues: Reported issues - label_assigned_to_me_issues: Issues assigned to me - label_last_login: Last connection - label_registered_on: Registered on - label_activity: Activity - label_overall_activity: Overall activity - label_user_activity: "%{value}'s activity" - label_new: New - label_logged_as: Logged in as - label_environment: Environment - label_authentication: Authentication - label_auth_source: Authentication mode - label_auth_source_new: New authentication mode - label_auth_source_plural: Authentication modes - label_subproject_plural: Subprojects - label_subproject_new: New subproject - label_and_its_subprojects: "%{value} and its subprojects" - label_min_max_length: Min - Max length - label_list: List - label_date: Date - label_integer: Integer - label_float: Float - label_boolean: Boolean - label_string: Text - label_text: Long text - label_attribute: Attribute - label_attribute_plural: Attributes - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - label_no_data: No data to display - label_change_status: Change status - label_history: History - label_attachment: File - label_attachment_new: New file - label_attachment_delete: Delete file - label_attachment_plural: Files - label_file_added: File added - label_report: Report - label_report_plural: Reports - label_news: News - label_news_new: Add news - label_news_plural: News - label_news_latest: Latest news - label_news_view_all: View all news - label_news_added: News added - label_news_comment_added: Comment added to a news - label_settings: Settings - label_overview: Overview - label_version: Version - label_version_new: New version - label_version_plural: Versions - label_close_versions: Close completed versions - label_confirmation: Confirmation - label_export_to: 'Also available in:' - label_read: Read... - label_public_projects: Public projects - label_open_issues: open - label_open_issues_plural: open - label_closed_issues: closed - label_closed_issues_plural: closed - label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_x_issues: - zero: 0 issues - one: 1 issue - other: "%{count} issues" - label_total: Total - label_permissions: Permissions - label_current_status: Current status - label_new_statuses_allowed: New statuses allowed - label_all: all - label_any: any - label_none: none - label_nobody: nobody - label_next: Next - label_previous: Previous - label_used_by: Used by - label_details: Details - label_add_note: Add a note - label_per_page: Per page - label_calendar: Calendar - label_months_from: months from - label_gantt: Gantt - label_internal: Internal - label_last_changes: "last %{count} changes" - label_change_view_all: View all changes - label_personalize_page: Personalize this page - label_comment: Comment - label_comment_plural: Comments - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: Add a comment - label_comment_added: Comment added - label_comment_delete: Delete comments - label_query: Custom query - label_query_plural: Custom queries - label_query_new: New query - label_my_queries: My custom queries - label_filter_add: Add filter - label_filter_plural: Filters - label_equals: is - label_not_equals: is not - label_in_less_than: in less than - label_in_more_than: in more than - label_in_the_next_days: in the next - label_in_the_past_days: in the past - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_between: between - label_in: in - label_today: today - label_all_time: all time - label_yesterday: yesterday - label_this_week: this week - label_last_week: last week - label_last_n_weeks: "last %{count} weeks" - label_last_n_days: "last %{count} days" - label_this_month: this month - label_last_month: last month - label_this_year: this year - label_date_range: Date range - label_less_than_ago: less than days ago - label_more_than_ago: more than days ago - label_ago: days ago - label_contains: contains - label_not_contains: doesn't contain - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - label_no_issues_in_project: no issues in project - label_day_plural: days - label_repository: Repository - label_repository_new: New repository - label_repository_plural: Repositories - label_browse: Browse - label_modification: "%{count} change" - label_modification_plural: "%{count} changes" - label_branch: Branch - label_tag: Tag - label_revision: Revision - label_revision_plural: Revisions - label_revision_id: "Revision %{value}" - label_associated_revisions: Associated revisions - label_added: added - label_modified: modified - label_copied: copied - label_renamed: renamed - label_deleted: deleted - label_latest_revision: Latest revision - label_latest_revision_plural: Latest revisions - label_view_revisions: View revisions - label_view_all_revisions: View all revisions - label_max_size: Maximum size - label_sort_highest: Move to top - label_sort_higher: Move up - label_sort_lower: Move down - label_sort_lowest: Move to bottom - label_roadmap: Roadmap - label_roadmap_due_in: "Due in %{value}" - label_roadmap_overdue: "%{value} late" - label_roadmap_no_issues: No issues for this version - label_search: Search - label_result_plural: Results - label_all_words: All words - label_wiki: Wiki - label_wiki_edit: Wiki edit - label_wiki_edit_plural: Wiki edits - label_wiki_page: Wiki page - label_wiki_page_plural: Wiki pages - label_index_by_title: Index by title - label_index_by_date: Index by date - label_current_version: Current version - label_preview: Preview - label_feed_plural: Feeds - label_changes_details: Details of all changes - label_issue_tracking: Issue tracking - label_spent_time: Spent time - label_overall_spent_time: Overall spent time - label_f_hour: "%{value} hour" - label_f_hour_plural: "%{value} hours" - label_time_tracking: Time tracking - label_change_plural: Changes - label_statistics: Statistics - label_commits_per_month: Commits per month - label_commits_per_author: Commits per author - label_diff: diff - label_view_diff: View differences - label_diff_inline: inline - label_diff_side_by_side: side by side - label_options: Options - label_copy_workflow_from: Copy workflow from - label_permissions_report: Permissions report - label_watched_issues: Watched issues - label_related_issues: Related issues - label_applied_status: Applied status - label_loading: Loading... - label_relation_new: New relation - label_relation_delete: Delete relation - label_relates_to: Related to - label_duplicates: Duplicates - label_duplicated_by: Duplicated by - label_blocks: Blocks - label_blocked_by: Blocked by - label_precedes: Precedes - label_follows: Follows - label_copied_to: Copied to - label_copied_from: Copied from - label_end_to_start: end to start - label_end_to_end: end to end - label_start_to_start: start to start - label_start_to_end: start to end - label_stay_logged_in: Stay logged in - label_disabled: disabled - label_show_completed_versions: Show completed versions - label_me: me - label_board: Forum - label_board_new: New forum - label_board_plural: Forums - label_board_locked: Locked - label_board_sticky: Sticky - label_topic_plural: Topics - label_message_plural: Messages - label_message_last: Last message - label_message_new: New message - label_message_posted: Message added - label_reply_plural: Replies - label_send_information: Send account information to the user - label_year: Year - label_month: Month - label_week: Week - label_date_from: From - label_date_to: To - label_language_based: Based on user's language - label_sort_by: "Sort by %{value}" - label_send_test_email: Send a test email - label_feeds_access_key: RSS access key - label_missing_feeds_access_key: Missing a RSS access key - label_feeds_access_key_created_on: "RSS access key created %{value} ago" - label_module_plural: Modules - label_added_time_by: "Added by %{author} %{age} ago" - label_updated_time_by: "Updated by %{author} %{age} ago" - label_updated_time: "Updated %{value} ago" - label_jump_to_a_project: Jump to a project... - label_file_plural: Files - label_changeset_plural: Changesets - label_default_columns: Default columns - label_no_change_option: (No change) - label_bulk_edit_selected_issues: Bulk edit selected issues - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - label_theme: Theme - label_default: Default - label_search_titles_only: Search titles only - label_user_mail_option_all: "For any event on all my projects" - label_user_mail_option_selected: "For any event on the selected projects only..." - label_user_mail_option_none: "No events" - label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in" - label_user_mail_option_only_assigned: "Only for things I am assigned to" - label_user_mail_option_only_owner: "Only for things I am the owner of" - label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" - label_registration_activation_by_email: account activation by email - label_registration_manual_activation: manual account activation - label_registration_automatic_activation: automatic account activation - label_display_per_page: "Per page: %{value}" - label_age: Age - label_change_properties: Change properties - label_general: General - label_more: More - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: LDAP authentication - label_downloads_abbr: D/L - label_optional_description: Optional description - label_add_another_file: Add another file - label_preferences: Preferences - label_chronological_order: In chronological order - label_reverse_chronological_order: In reverse chronological order - label_planning: Planning - label_incoming_emails: Incoming emails - label_generate_key: Generate a key - label_issue_watchers: Watchers - label_example: Example - label_display: Display - label_sort: Sort - label_ascending: Ascending - label_descending: Descending - label_date_from_to: From %{start} to %{end} - label_wiki_content_added: Wiki page added - label_wiki_content_updated: Wiki page updated - label_group: Group - label_group_plural: Groups - label_group_new: New group - label_time_entry_plural: Spent time - label_version_sharing_none: Not shared - label_version_sharing_descendants: With subprojects - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_tree: With project tree - label_version_sharing_system: With all projects - label_update_issue_done_ratios: Update issue done ratios - label_copy_source: Source - label_copy_target: Target - label_copy_same_as_target: Same as target - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_api_access_key: API access key - label_missing_api_access_key: Missing an API access key - label_api_access_key_created_on: "API access key created %{value} ago" - label_profile: Profile - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_issues_visibility_all: All issues - label_issues_visibility_public: All non private issues - label_issues_visibility_own: Issues created by or assigned to the user - label_git_report_last_commit: Report last commit for files and directories - label_parent_revision: Parent - label_child_revision: Child - label_export_options: "%{export_format} export options" - label_copy_attachments: Copy attachments - label_copy_subtasks: Copy subtasks - label_item_position: "%{position} of %{count}" - label_completed_versions: Completed versions - label_search_for_watchers: Search for watchers to add - label_session_expiration: Session expiration - label_show_closed_projects: View closed projects - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - label_attribute_of_project: "Project's %{name}" - label_attribute_of_author: "Author's %{name}" - label_attribute_of_assigned_to: "Assignee's %{name}" - label_attribute_of_fixed_version: "Target version's %{name}" - label_cross_project_descendants: With subprojects - label_cross_project_tree: With project tree - label_cross_project_hierarchy: With project hierarchy - label_cross_project_system: With all projects - - button_login: Login - button_submit: Submit - button_save: Save - button_check_all: Check all - button_uncheck_all: Uncheck all - button_collapse_all: Collapse all - button_expand_all: Expand all - button_delete: Delete - button_create: Create - button_create_and_continue: Create and continue - button_test: Test - button_edit: Edit - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - button_add: Add - button_change: Change - button_apply: Apply - button_clear: Clear - button_lock: Lock - button_unlock: Unlock - button_download: Download - button_list: List - button_view: View - button_move: Move - button_move_and_follow: Move and follow - button_back: Back - button_cancel: Cancel - button_activate: Activate - button_sort: Sort - button_log_time: Log time - button_rollback: Rollback to this version - button_watch: Watch - button_unwatch: Unwatch - button_reply: Reply - button_archive: Archive - button_unarchive: Unarchive - button_reset: Reset - button_rename: Rename - button_change_password: Change password - button_copy: Copy - button_copy_and_follow: Copy and follow - button_annotate: Annotate - button_update: Update - button_configure: Configure - button_quote: Quote - button_duplicate: Duplicate - button_show: Show - button_hide: Hide - button_edit_section: Edit this section - button_export: Export - button_delete_my_account: Delete my account - button_close: Close - button_reopen: Reopen - - status_active: active - status_registered: registered - status_locked: locked - - project_status_active: active - project_status_closed: closed - project_status_archived: archived - - version_status_open: open - version_status_locked: locked - version_status_closed: closed - - field_active: Active - - text_select_mail_notifications: Select actions for which email notifications should be sent. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 means no restriction - text_project_destroy_confirmation: Are you sure you want to delete this project and related data? - text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." - text_workflow_edit: Select a role and a tracker to edit the workflow - text_are_you_sure: Are you sure? - text_journal_changed: "%{label} changed from %{old} to %{new}" - text_journal_changed_no_detail: "%{label} updated" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} deleted (%{old})" - text_journal_added: "%{label} %{value} added" - text_tip_issue_begin_day: issue beginning this day - text_tip_issue_end_day: issue ending this day - text_tip_issue_begin_end_day: issue beginning and ending this day - text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed.' - text_caracters_maximum: "%{count} characters maximum." - text_caracters_minimum: "Must be at least %{count} characters long." - text_length_between: "Length between %{min} and %{max} characters." - text_tracker_no_workflow: No workflow defined for this tracker - text_unallowed_characters: Unallowed characters - text_comma_separated: Multiple values allowed (comma separated). - text_line_separated: Multiple values allowed (one line for each value). - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_issue_added: "Issue %{id} has been reported by %{author}." - text_issue_updated: "Issue %{id} has been updated by %{author}." - text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? - text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?" - text_issue_category_destroy_assignments: Remove category assignments - text_issue_category_reassign_to: Reassign issues to this category - text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." - text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." - text_load_default_configuration: Load the default configuration - text_status_changed_by_changeset: "Applied in changeset %{value}." - text_time_logged_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' - text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)." - text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?' - text_select_project_modules: 'Select modules to enable for this project:' - text_default_administrator_account_changed: Default administrator account changed - text_file_repository_writable: Attachments directory writable - text_plugin_assets_writable: Plugin assets directory writable - text_rmagick_available: RMagick available (optional) - text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" - text_destroy_time_entries: Delete reported hours - text_assign_time_entries_to_project: Assign reported hours to the project - text_reassign_time_entries: 'Reassign reported hours to this issue:' - text_user_wrote: "%{value} wrote:" - text_enumeration_destroy_question: "%{count} objects are assigned to this value." - text_enumeration_category_reassign_to: 'Reassign them to this value:' - text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." - text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." - text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' - text_custom_field_possible_values_info: 'One line for each value' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" - text_wiki_page_nullify_children: "Keep child pages as root pages" - text_wiki_page_destroy_children: "Delete child pages and all their descendants" - text_wiki_page_reassign_children: "Reassign child pages to this parent page" - text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" - text_zoom_in: Zoom in - text_zoom_out: Zoom out - text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." - text_scm_path_encoding_note: "Default: UTF-8" - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" - text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" - text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" - text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it." - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - text_project_closed: This project is closed and read-only. - - default_role_manager: Manager - default_role_developer: Developer - default_role_reporter: Reporter - default_tracker_bug: Bug - default_tracker_feature: Feature - default_tracker_support: Support - default_issue_status_new: New - default_issue_status_in_progress: In Progress - default_issue_status_resolved: Resolved - default_issue_status_feedback: Feedback - default_issue_status_closed: Closed - default_issue_status_rejected: Rejected - default_doc_category_user: User documentation - default_doc_category_tech: Technical documentation - default_priority_low: Low - default_priority_normal: Normal - default_priority_high: High - default_priority_urgent: Urgent - default_priority_immediate: Immediate - default_activity_design: Design - default_activity_development: Development - - enumeration_issue_priorities: Issue priorities - enumeration_doc_categories: Document categories - enumeration_activities: Activities (time tracking) - enumeration_system_activity: System Activity - description_filter: Filter - description_search: Searchfield - description_choose_project: Projects - description_project_scope: Search scope - description_notes: Notes - description_message_content: Message content - description_query_sort_criteria_attribute: Sort attribute - description_query_sort_criteria_direction: Sort direction - description_user_mail_notification: Mail notification settings - description_available_columns: Available Columns - description_selected_columns: Selected Columns - description_all_columns: All Columns - description_issue_category_reassign: Choose issue category - description_wiki_subpages_reassign: Choose new parent page - description_date_range_list: Choose range from list - description_date_range_interval: Choose range by selecting start and end date - description_date_from: Enter start date - description_date_to: Enter end date - text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed.' diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c9/c9f7bfe8669185939c28b9d5ebcda0535187eb09.svn-base --- a/.svn/pristine/c9/c9f7bfe8669185939c28b9d5ebcda0535187eb09.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingSearchTest < ActionController::IntegrationTest - def test_search - assert_routing( - { :method => 'get', :path => "/search" }, - { :controller => 'search', :action => 'index' } - ) - assert_routing( - { :method => 'get', :path => "/projects/foo/search" }, - { :controller => 'search', :action => 'index', :id => 'foo' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/c9/c9fd7a76ff620549e7000ece93101838a7257ba8.svn-base --- a/.svn/pristine/c9/c9fd7a76ff620549e7000ece93101838a7257ba8.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -
    -<% if !@query.new_record? && @query.editable_by?(User.current) %> - <%= link_to l(:button_edit), edit_query_path(@query), :class => 'icon icon-edit' %> - <%= delete_link query_path(@query) %> -<% end %> -
    - -

    <%= @query.new_record? ? l(:label_issue_plural) : h(@query.name) %>

    -<% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %> - -<%= form_tag({ :controller => 'issues', :action => 'index', :project_id => @project }, - :method => :get, :id => 'query_form') do %> - <%= hidden_field_tag 'set_filter', '1' %> -
    -
    "> - <%= l(:label_filter_plural) %> -
    "> - <%= render :partial => 'queries/filters', :locals => {:query => @query} %> -
    -
    - -
    -

    - - <%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %> - <%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %> - <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %> - <%= link_to_function l(:button_save), - "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }'); submit_query_form('query_form')", - :class => 'icon icon-save' %> - <% end %> -

    -<% end %> - -<%= error_messages_for 'query' %> -<% if @query.valid? %> -<% if @issues.empty? %> -

    <%= l(:label_no_data) %>

    -<% else %> -<%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %> -

    <%= pagination_links_full @issue_pages, @issue_count %>

    -<% end %> - -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => params.merge(:key => User.current.rss_key) %> - <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '330px'); return false;" %> - <%= f.link_to 'PDF', :url => params %> -<% end %> - - - -<% end %> -<%= call_hook(:view_issues_index_bottom, { :issues => @issues, :project => @project, :query => @query }) %> - -<% content_for :sidebar do %> - <%= render :partial => 'issues/sidebar' %> -<% end %> - -<% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, - {:query_id => @query, :format => 'atom', - :page => nil, :key => User.current.rss_key}, - :title => l(:label_issue_plural)) %> - <%= auto_discovery_link_tag(:atom, - {:controller => 'journals', :action => 'index', - :query_id => @query, :format => 'atom', - :page => nil, :key => User.current.rss_key}, - :title => l(:label_changes_details)) %> -<% end %> - -<%= context_menu issues_context_menu_path %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ca/ca2275089c53a641c8dbf8851084e0351d6242de.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ca/ca2275089c53a641c8dbf8851084e0351d6242de.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,5 @@ +<%= title [l(:label_tracker_plural), trackers_path], l(:label_tracker_new) %> + +<%= labelled_form_for @tracker do |f| %> +<%= render :partial => 'form', :locals => { :f => f } %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ca/ca41c33ed3955765c0d4842df7d17bad1f14926d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ca/ca41c33ed3955765c0d4842df7d17bad1f14926d.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,290 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class AttachmentTest < ActiveSupport::TestCase + fixtures :users, :projects, :roles, :members, :member_roles, + :enabled_modules, :issues, :trackers, :attachments + + class MockFile + attr_reader :original_filename, :content_type, :content, :size + + def initialize(attributes) + @original_filename = attributes[:original_filename] + @content_type = attributes[:content_type] + @content = attributes[:content] || "Content" + @size = content.size + end + end + + def setup + set_tmp_attachments_directory + end + + def test_container_for_new_attachment_should_be_nil + assert_nil Attachment.new.container + end + + def test_filename_should_remove_eols + assert_equal "line_feed", Attachment.new(:filename => "line\nfeed").filename + assert_equal "line_feed", Attachment.new(:filename => "some\npath/line\nfeed").filename + assert_equal "carriage_return", Attachment.new(:filename => "carriage\rreturn").filename + assert_equal "carriage_return", Attachment.new(:filename => "some\rpath/carriage\rreturn").filename + end + + def test_create + a = Attachment.new(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", "text/plain"), + :author => User.find(1)) + assert a.save + assert_equal 'testfile.txt', a.filename + assert_equal 59, a.filesize + assert_equal 'text/plain', a.content_type + assert_equal 0, a.downloads + assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest + + assert a.disk_directory + assert_match %r{\A\d{4}/\d{2}\z}, a.disk_directory + + assert File.exist?(a.diskfile) + assert_equal 59, File.size(a.diskfile) + end + + def test_copy_should_preserve_attributes + a = Attachment.find(1) + copy = a.copy + + assert_save copy + copy = Attachment.order('id DESC').first + %w(filename filesize content_type author_id created_on description digest disk_filename disk_directory diskfile).each do |attribute| + assert_equal a.send(attribute), copy.send(attribute), "#{attribute} was different" + end + end + + def test_size_should_be_validated_for_new_file + with_settings :attachment_max_size => 0 do + a = Attachment.new(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", "text/plain"), + :author => User.find(1)) + assert !a.save + end + end + + def test_size_should_not_be_validated_when_copying + a = Attachment.create!(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", "text/plain"), + :author => User.find(1)) + with_settings :attachment_max_size => 0 do + copy = a.copy + assert copy.save + end + end + + def test_description_length_should_be_validated + a = Attachment.new(:description => 'a' * 300) + assert !a.save + assert_not_equal [], a.errors[:description] + end + + def test_destroy + a = Attachment.new(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", "text/plain"), + :author => User.find(1)) + assert a.save + assert_equal 'testfile.txt', a.filename + assert_equal 59, a.filesize + assert_equal 'text/plain', a.content_type + assert_equal 0, a.downloads + assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest + diskfile = a.diskfile + assert File.exist?(diskfile) + assert_equal 59, File.size(a.diskfile) + assert a.destroy + assert !File.exist?(diskfile) + end + + def test_destroy_should_not_delete_file_referenced_by_other_attachment + a = Attachment.create!(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", "text/plain"), + :author => User.find(1)) + diskfile = a.diskfile + + copy = a.copy + copy.save! + + assert File.exists?(diskfile) + a.destroy + assert File.exists?(diskfile) + copy.destroy + assert !File.exists?(diskfile) + end + + def test_create_should_auto_assign_content_type + a = Attachment.new(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", ""), + :author => User.find(1)) + assert a.save + assert_equal 'text/plain', a.content_type + end + + def test_identical_attachments_at_the_same_time_should_not_overwrite + a1 = Attachment.create!(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", ""), + :author => User.find(1)) + a2 = Attachment.create!(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", ""), + :author => User.find(1)) + assert a1.disk_filename != a2.disk_filename + end + + def test_filename_should_be_basenamed + a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file")) + assert_equal 'file', a.filename + end + + def test_filename_should_be_sanitized + a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars")) + assert_equal 'valid_[] invalid_chars', a.filename + end + + def test_diskfilename + assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/ + assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1] + assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentué.txt")[13..-1] + assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentué")[13..-1] + assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentué.ça")[13..-1] + end + + def test_title + a = Attachment.new(:filename => "test.png") + assert_equal "test.png", a.title + + a = Attachment.new(:filename => "test.png", :description => "Cool image") + assert_equal "test.png (Cool image)", a.title + end + + def test_prune_should_destroy_old_unattached_attachments + Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago) + Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago) + Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1) + + assert_difference 'Attachment.count', -2 do + Attachment.prune + end + end + + def test_move_from_root_to_target_directory_should_move_root_files + a = Attachment.find(20) + assert a.disk_directory.blank? + # Create a real file for this fixture + File.open(a.diskfile, "w") do |f| + f.write "test file at the root of files directory" + end + assert a.readable? + Attachment.move_from_root_to_target_directory + + a.reload + assert_equal '2012/05', a.disk_directory + assert a.readable? + end + + test "Attachmnet.attach_files should attach the file" do + issue = Issue.first + assert_difference 'Attachment.count' do + Attachment.attach_files(issue, + '1' => { + 'file' => uploaded_test_file('testfile.txt', 'text/plain'), + 'description' => 'test' + }) + end + + attachment = Attachment.first(:order => 'id DESC') + assert_equal issue, attachment.container + assert_equal 'testfile.txt', attachment.filename + assert_equal 59, attachment.filesize + assert_equal 'test', attachment.description + assert_equal 'text/plain', attachment.content_type + assert File.exists?(attachment.diskfile) + assert_equal 59, File.size(attachment.diskfile) + end + + test "Attachmnet.attach_files should add unsaved files to the object as unsaved attachments" do + # Max size of 0 to force Attachment creation failures + with_settings(:attachment_max_size => 0) do + @project = Project.find(1) + response = Attachment.attach_files(@project, { + '1' => {'file' => mock_file, 'description' => 'test'}, + '2' => {'file' => mock_file, 'description' => 'test'} + }) + + assert response[:unsaved].present? + assert_equal 2, response[:unsaved].length + assert response[:unsaved].first.new_record? + assert response[:unsaved].second.new_record? + assert_equal response[:unsaved], @project.unsaved_attachments + end + end + + def test_latest_attach + set_fixtures_attachments_directory + a1 = Attachment.find(16) + assert_equal "testfile.png", a1.filename + assert a1.readable? + assert (! a1.visible?(User.anonymous)) + assert a1.visible?(User.find(2)) + a2 = Attachment.find(17) + assert_equal "testfile.PNG", a2.filename + assert a2.readable? + assert (! a2.visible?(User.anonymous)) + assert a2.visible?(User.find(2)) + assert a1.created_on < a2.created_on + + la1 = Attachment.latest_attach([a1, a2], "testfile.png") + assert_equal 17, la1.id + la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG") + assert_equal 17, la2.id + + set_tmp_attachments_directory + end + + def test_thumbnailable_should_be_true_for_images + assert_equal true, Attachment.new(:filename => 'test.jpg').thumbnailable? + end + + def test_thumbnailable_should_be_true_for_non_images + assert_equal false, Attachment.new(:filename => 'test.txt').thumbnailable? + end + + if convert_installed? + def test_thumbnail_should_generate_the_thumbnail + set_fixtures_attachments_directory + attachment = Attachment.find(16) + Attachment.clear_thumbnails + + assert_difference "Dir.glob(File.join(Attachment.thumbnails_storage_path, '*.thumb')).size" do + thumbnail = attachment.thumbnail + assert_equal "16_8e0294de2441577c529f170b6fb8f638_100.thumb", File.basename(thumbnail) + assert File.exists?(thumbnail) + end + end + else + puts '(ImageMagick convert not available)' + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ca/ca427707add26575487d2ee417a7e9710ba1f781.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ca/ca427707add26575487d2ee417a7e9710ba1f781.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,40 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class MailHandlerController < ActionController::Base + before_filter :check_credential + + # Submits an incoming email to MailHandler + def index + options = params.dup + email = options.delete(:email) + if MailHandler.receive(email, options) + render :nothing => true, :status => :created + else + render :nothing => true, :status => :unprocessable_entity + end + end + + private + + def check_credential + User.current = nil + unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key + render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403 + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ca/ca491b24f09d42967bf5ee8640ce6de9cf8a855e.svn-base --- a/.svn/pristine/ca/ca491b24f09d42967bf5ee8640ce6de9cf8a855e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingJournalsTest < ActionController::IntegrationTest - def test_journals - assert_routing( - { :method => 'post', :path => "/issues/1/quoted" }, - { :controller => 'journals', :action => 'new', :id => '1' } - ) - assert_routing( - { :method => 'get', :path => "/issues/changes" }, - { :controller => 'journals', :action => 'index' } - ) - assert_routing( - { :method => 'get', :path => "/journals/diff/1" }, - { :controller => 'journals', :action => 'diff', :id => '1' } - ) - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/journals/edit/1" }, - { :controller => 'journals', :action => 'edit', :id => '1' } - ) - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ca/ca6a83d87a789a365f28655b1d414ca917f8787b.svn-base --- a/.svn/pristine/ca/ca6a83d87a789a365f28655b1d414ca917f8787b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class Redmine::Hook::ManagerTest < ActionView::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, - :groups_users, - :trackers, :projects_trackers, - :enabled_modules, - :versions, - :issue_statuses, :issue_categories, :issue_relations, :workflows, - :enumerations, - :issues - - # Some hooks that are manually registered in these tests - class TestHook < Redmine::Hook::ViewListener; end - - class TestHook1 < TestHook - def view_layouts_base_html_head(context) - 'Test hook 1 listener.' - end - end - - class TestHook2 < TestHook - def view_layouts_base_html_head(context) - 'Test hook 2 listener.' - end - end - - class TestHook3 < TestHook - def view_layouts_base_html_head(context) - "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}." - end - end - - class TestLinkToHook < TestHook - def view_layouts_base_html_head(context) - link_to('Issues', :controller => 'issues') - end - end - - class TestHookHelperController < ActionController::Base - include Redmine::Hook::Helper - end - - class TestHookHelperView < ActionView::Base - include Redmine::Hook::Helper - end - - Redmine::Hook.clear_listeners - - def setup - @hook_module = Redmine::Hook - end - - def teardown - @hook_module.clear_listeners - end - - def test_clear_listeners - assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size - @hook_module.add_listener(TestHook1) - @hook_module.add_listener(TestHook2) - assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size - - @hook_module.clear_listeners - assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size - end - - def test_add_listener - assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size - @hook_module.add_listener(TestHook1) - assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size - end - - def test_call_hook - @hook_module.add_listener(TestHook1) - assert_equal ['Test hook 1 listener.'], hook_helper.call_hook(:view_layouts_base_html_head) - end - - def test_call_hook_with_context - @hook_module.add_listener(TestHook3) - assert_equal ['Context keys: bar, controller, foo, hook_caller, project, request.'], - hook_helper.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a') - end - - def test_call_hook_with_multiple_listeners - @hook_module.add_listener(TestHook1) - @hook_module.add_listener(TestHook2) - assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], hook_helper.call_hook(:view_layouts_base_html_head) - end - - # Context: Redmine::Hook::Helper.call_hook default_url - def test_call_hook_default_url_options - @hook_module.add_listener(TestLinkToHook) - - assert_equal ['Issues'], hook_helper.call_hook(:view_layouts_base_html_head) - end - - # Context: Redmine::Hook::Helper.call_hook - def test_call_hook_with_project_added_to_context - @hook_module.add_listener(TestHook3) - assert_match /project/i, hook_helper.call_hook(:view_layouts_base_html_head)[0] - end - - def test_call_hook_from_controller_with_controller_added_to_context - @hook_module.add_listener(TestHook3) - assert_match /controller/i, hook_helper.call_hook(:view_layouts_base_html_head)[0] - end - - def test_call_hook_from_controller_with_request_added_to_context - @hook_module.add_listener(TestHook3) - assert_match /request/i, hook_helper.call_hook(:view_layouts_base_html_head)[0] - end - - def test_call_hook_from_view_with_project_added_to_context - @hook_module.add_listener(TestHook3) - assert_match /project/i, view_hook_helper.call_hook(:view_layouts_base_html_head) - end - - def test_call_hook_from_view_with_controller_added_to_context - @hook_module.add_listener(TestHook3) - assert_match /controller/i, view_hook_helper.call_hook(:view_layouts_base_html_head) - end - - def test_call_hook_from_view_with_request_added_to_context - @hook_module.add_listener(TestHook3) - assert_match /request/i, view_hook_helper.call_hook(:view_layouts_base_html_head) - end - - def test_call_hook_from_view_should_join_responses_with_a_space - @hook_module.add_listener(TestHook1) - @hook_module.add_listener(TestHook2) - assert_equal 'Test hook 1 listener. Test hook 2 listener.', - view_hook_helper.call_hook(:view_layouts_base_html_head) - end - - def test_call_hook_should_not_change_the_default_url_for_email_notifications - issue = Issue.find(1) - - ActionMailer::Base.deliveries.clear - Mailer.issue_add(issue).deliver - mail = ActionMailer::Base.deliveries.last - - @hook_module.add_listener(TestLinkToHook) - hook_helper.call_hook(:view_layouts_base_html_head) - - ActionMailer::Base.deliveries.clear - Mailer.issue_add(issue).deliver - mail2 = ActionMailer::Base.deliveries.last - - assert_equal mail_body(mail), mail_body(mail2) - end - - def hook_helper - @hook_helper ||= TestHookHelperController.new - end - - def view_hook_helper - @view_hook_helper ||= TestHookHelperView.new(Rails.root.to_s + '/app/views') - end -end - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ca/ca8b0f9fbdc1a332f4c2f4861ef22155d5ef176c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ca/ca8b0f9fbdc1a332f4c2f4861ef22155d5ef176c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,33 @@ + +<% if defined?(container) && container && container.saved_attachments %> + <% container.saved_attachments.each_with_index do |attachment, i| %> + + <%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename') + + text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') + + link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %> + <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %> + + <% end %> +<% end %> + + +<%= file_field_tag 'attachments[dummy][file]', + :id => nil, + :class => 'file_selector', + :multiple => true, + :data => { + :max_file_size => Setting.attachment_max_size.to_i.kilobytes, + :max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)), + :max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i, + :upload_path => uploads_path(:format => 'js'), + :description_placeholder => l(:label_optional_description) + } %> +(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) + +<%= javascript_tag do %> + $('input.file_selector').on('change', function(){addInputFiles(this);}); +<% end %> + +<% content_for :header_tags do %> + <%= javascript_include_tag 'attachments' %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ca/ca8eec8a84331139968b1ea82955bae2ed0f8643.svn-base --- a/.svn/pristine/ca/ca8eec8a84331139968b1ea82955bae2ed0f8643.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class WikiContentObserver < ActiveRecord::Observer - def after_create(wiki_content) - Mailer.wiki_content_added(wiki_content).deliver if Setting.notified_events.include?('wiki_content_added') - end - - def after_update(wiki_content) - if wiki_content.text_changed? - Mailer.wiki_content_updated(wiki_content).deliver if Setting.notified_events.include?('wiki_content_updated') - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ca/ca9a94f4269bc05500b823a9f42b210c9322360b.svn-base --- a/.svn/pristine/ca/ca9a94f4269bc05500b823a9f42b210c9322360b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,255 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) -require 'search_controller' - -# Re-raise errors caught by the controller. -class SearchController; def rescue_action(e) raise e end; end - -class SearchControllerTest < ActionController::TestCase - fixtures :projects, :enabled_modules, :roles, :users, :members, :member_roles, - :issues, :trackers, :issue_statuses, :enumerations, - :custom_fields, :custom_values, - :repositories, :changesets - - def setup - @controller = SearchController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_search_for_projects - get :index - assert_response :success - assert_template 'index' - - get :index, :q => "cook" - assert_response :success - assert_template 'index' - assert assigns(:results).include?(Project.find(1)) - end - - def test_search_all_projects - get :index, :q => 'recipe subproject commit', :all_words => '' - assert_response :success - assert_template 'index' - - assert assigns(:results).include?(Issue.find(2)) - assert assigns(:results).include?(Issue.find(5)) - assert assigns(:results).include?(Changeset.find(101)) - assert_tag :dt, :attributes => { :class => /issue/ }, - :child => { :tag => 'a', :content => /Add ingredients categories/ }, - :sibling => { :tag => 'dd', :content => /should be classified by categories/ } - - assert assigns(:results_by_type).is_a?(Hash) - assert_equal 5, assigns(:results_by_type)['changesets'] - assert_tag :a, :content => 'Changesets (5)' - end - - def test_search_issues - get :index, :q => 'issue', :issues => 1 - assert_response :success - assert_template 'index' - - assert_equal true, assigns(:all_words) - assert_equal false, assigns(:titles_only) - assert assigns(:results).include?(Issue.find(8)) - assert assigns(:results).include?(Issue.find(5)) - assert_tag :dt, :attributes => { :class => /issue closed/ }, - :child => { :tag => 'a', :content => /Closed/ } - end - - def test_search_issues_should_search_notes - Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword') - - get :index, :q => 'searchkeyword', :issues => 1 - assert_response :success - assert_include Issue.find(2), assigns(:results) - end - - def test_search_issues_with_multiple_matches_in_journals_should_return_issue_once - Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword') - Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword') - - get :index, :q => 'searchkeyword', :issues => 1 - assert_response :success - assert_include Issue.find(2), assigns(:results) - assert_equal 1, assigns(:results).size - end - - def test_search_issues_should_search_private_notes_with_permission_only - Journal.create!(:journalized => Issue.find(2), :notes => 'Private notes with searchkeyword', :private_notes => true) - @request.session[:user_id] = 2 - - Role.find(1).add_permission! :view_private_notes - get :index, :q => 'searchkeyword', :issues => 1 - assert_response :success - assert_include Issue.find(2), assigns(:results) - - Role.find(1).remove_permission! :view_private_notes - get :index, :q => 'searchkeyword', :issues => 1 - assert_response :success - assert_not_include Issue.find(2), assigns(:results) - end - - def test_search_all_projects_with_scope_param - get :index, :q => 'issue', :scope => 'all' - assert_response :success - assert_template 'index' - assert assigns(:results).present? - end - - def test_search_my_projects - @request.session[:user_id] = 2 - get :index, :id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => '' - assert_response :success - assert_template 'index' - assert assigns(:results).include?(Issue.find(1)) - assert !assigns(:results).include?(Issue.find(5)) - end - - def test_search_my_projects_without_memberships - # anonymous user has no memberships - get :index, :id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => '' - assert_response :success - assert_template 'index' - assert assigns(:results).empty? - end - - def test_search_project_and_subprojects - get :index, :id => 1, :q => 'recipe subproject', :scope => 'subprojects', :all_words => '' - assert_response :success - assert_template 'index' - assert assigns(:results).include?(Issue.find(1)) - assert assigns(:results).include?(Issue.find(5)) - end - - def test_search_without_searchable_custom_fields - CustomField.update_all "searchable = #{ActiveRecord::Base.connection.quoted_false}" - - get :index, :id => 1 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:project) - - get :index, :id => 1, :q => "can" - assert_response :success - assert_template 'index' - end - - def test_search_with_searchable_custom_fields - get :index, :id => 1, :q => "stringforcustomfield" - assert_response :success - results = assigns(:results) - assert_not_nil results - assert_equal 1, results.size - assert results.include?(Issue.find(7)) - end - - def test_search_all_words - # 'all words' is on by default - get :index, :id => 1, :q => 'recipe updating saving', :all_words => '1' - assert_equal true, assigns(:all_words) - results = assigns(:results) - assert_not_nil results - assert_equal 1, results.size - assert results.include?(Issue.find(3)) - end - - def test_search_one_of_the_words - get :index, :id => 1, :q => 'recipe updating saving', :all_words => '' - assert_equal false, assigns(:all_words) - results = assigns(:results) - assert_not_nil results - assert_equal 3, results.size - assert results.include?(Issue.find(3)) - end - - def test_search_titles_only_without_result - get :index, :id => 1, :q => 'recipe updating saving', :titles_only => '1' - results = assigns(:results) - assert_not_nil results - assert_equal 0, results.size - end - - def test_search_titles_only - get :index, :id => 1, :q => 'recipe', :titles_only => '1' - assert_equal true, assigns(:titles_only) - results = assigns(:results) - assert_not_nil results - assert_equal 2, results.size - end - - def test_search_content - Issue.update_all("description = 'This is a searchkeywordinthecontent'", "id=1") - - get :index, :id => 1, :q => 'searchkeywordinthecontent', :titles_only => '' - assert_equal false, assigns(:titles_only) - results = assigns(:results) - assert_not_nil results - assert_equal 1, results.size - end - - def test_search_with_offset - get :index, :q => 'coo', :offset => '20080806073000' - assert_response :success - results = assigns(:results) - assert results.any? - assert results.map(&:event_datetime).max < '20080806T073000'.to_time - end - - def test_search_previous_with_offset - get :index, :q => 'coo', :offset => '20080806073000', :previous => '1' - assert_response :success - results = assigns(:results) - assert results.any? - assert results.map(&:event_datetime).min >= '20080806T073000'.to_time - end - - def test_search_with_invalid_project_id - get :index, :id => 195, :q => 'recipe' - assert_response 404 - assert_nil assigns(:results) - end - - def test_quick_jump_to_issue - # issue of a public project - get :index, :q => "3" - assert_redirected_to '/issues/3' - - # issue of a private project - get :index, :q => "4" - assert_response :success - assert_template 'index' - end - - def test_large_integer - get :index, :q => '4615713488' - assert_response :success - assert_template 'index' - end - - def test_tokens_with_quotes - get :index, :id => 1, :q => '"good bye" hello "bye bye"' - assert_equal ["good bye", "hello", "bye bye"], assigns(:tokens) - end - - def test_results_should_be_escaped_once - assert Issue.find(1).update_attributes(:subject => ' escaped_once', :description => ' escaped_once') - get :index, :q => 'escaped_once' - assert_response :success - assert_select '#search-results' do - assert_select 'dt.issue a', :text => /<subject>/ - assert_select 'dd', :text => /<description>/ - end - end - - def test_keywords_should_be_highlighted - assert Issue.find(1).update_attributes(:subject => 'subject highlighted', :description => 'description highlighted') - get :index, :q => 'highlighted' - assert_response :success - assert_select '#search-results' do - assert_select 'dt.issue a span.highlight', :text => 'highlighted' - assert_select 'dd span.highlight', :text => 'highlighted' - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cb/cb011925faaf39d091329859f0fbe2f0e4eeb869.svn-base --- a/.svn/pristine/cb/cb011925faaf39d091329859f0fbe2f0e4eeb869.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,225 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../../../test_helper', __FILE__) -begin - require 'mocha' - - class BazaarAdapterTest < ActiveSupport::TestCase - REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s - REPOSITORY_PATH.gsub!(/\/+/, '/') - - if File.directory?(REPOSITORY_PATH) - def setup - @adapter = Redmine::Scm::Adapters::BazaarAdapter.new( - File.join(REPOSITORY_PATH, "trunk") - ) - end - - def test_scm_version - to_test = { "Bazaar (bzr) 2.1.2\n" => [2,1,2], - "2.1.1\n1.7\n1.8" => [2,1,1], - "2.0.1\r\n1.8.1\r\n1.9.1" => [2,0,1]} - to_test.each do |s, v| - test_scm_version_for(s, v) - end - end - - def test_cat - cat = @adapter.cat('directory/document.txt') - assert cat =~ /Write the contents of a file as of a given revision to standard output/ - end - - def test_cat_path_invalid - assert_nil @adapter.cat('invalid') - end - - def test_cat_revision_invalid - assert_nil @adapter.cat('doc-mkdir.txt', '12345678') - end - - def test_diff - diff1 = @adapter.diff('doc-mkdir.txt', 3, 2) - assert_equal 21, diff1.size - buf = diff1[14].gsub(/\r\n|\r|\n/, "") - assert_equal "-Display more information.", buf - end - - def test_diff_path_invalid - assert_equal [], @adapter.diff('invalid', 1) - end - - def test_diff_revision_invalid - assert_equal [], @adapter.diff(nil, 12345678) - assert_equal [], @adapter.diff(nil, 12345678, 87654321) - end - - def test_annotate - annotate = @adapter.annotate('doc-mkdir.txt') - assert_equal 17, annotate.lines.size - assert_equal '1', annotate.revisions[0].identifier - assert_equal 'jsmith@', annotate.revisions[0].author - assert_equal 'mkdir', annotate.lines[0] - end - - def test_annotate_path_invalid - assert_nil @adapter.annotate('invalid') - end - - def test_annotate_revision_invalid - assert_nil @adapter.annotate('doc-mkdir.txt', '12345678') - end - - def test_branch_conf_path - p = "c:\\test\\test\\" - bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) - assert_equal File.join("c:\\test\\test", ".bzr", "branch", "branch.conf"), bcp - p = "c:\\test\\test\\.bzr" - bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) - assert_equal File.join("c:\\test\\test", ".bzr", "branch", "branch.conf"), bcp - p = "c:\\test\\test\\.bzr\\" - bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) - assert_equal File.join("c:\\test\\test", ".bzr", "branch", "branch.conf"), bcp - p = "c:\\test\\test" - bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) - assert_equal File.join("c:\\test\\test", ".bzr", "branch", "branch.conf"), bcp - p = "\\\\server\\test\\test\\" - bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) - assert_equal File.join("\\\\server\\test\\test", ".bzr", "branch", "branch.conf"), bcp - end - - def test_append_revisions_only_true - assert_equal true, @adapter.append_revisions_only - end - - def test_append_revisions_only_false - adpt = Redmine::Scm::Adapters::BazaarAdapter.new( - File.join(REPOSITORY_PATH, "empty-branch") - ) - assert_equal false, adpt.append_revisions_only - end - - def test_append_revisions_only_shared_repo - adpt = Redmine::Scm::Adapters::BazaarAdapter.new( - REPOSITORY_PATH - ) - assert_equal false, adpt.append_revisions_only - end - - def test_info_not_nil - assert_not_nil @adapter.info - end - - def test_info_nil - adpt = Redmine::Scm::Adapters::BazaarAdapter.new( - "/invalid/invalid/" - ) - assert_nil adpt.info - end - - def test_info - info = @adapter.info - assert_equal 4, info.lastrev.identifier.to_i - end - - def test_info_emtpy - adpt = Redmine::Scm::Adapters::BazaarAdapter.new( - File.join(REPOSITORY_PATH, "empty-branch") - ) - assert_equal 0, adpt.info.lastrev.identifier.to_i - end - - def test_entries_path_invalid - assert_equal [], @adapter.entries('invalid') - end - - def test_entries_revision_invalid - assert_nil @adapter.entries(nil, 12345678) - end - - def test_revisions - revisions = @adapter.revisions(nil, 4, 2) - assert_equal 3, revisions.size - assert_equal 2, revisions[2].identifier - assert_equal 'jsmith@foo.bar-20071203175224-v0eog5d5wrgdrshg', revisions[2].scmid - assert_equal 4, revisions[0].identifier - assert_equal 'jsmith@foo.bar-20071203175422-t40bf8li5zz0c4cg', revisions[0].scmid - assert_equal 2, revisions[0].paths.size - assert_equal 'D', revisions[0].paths[0][:action] - assert_equal '/doc-deleted.txt', revisions[0].paths[0][:path] - assert_equal 'docdeleted.txt-20071203175320-iwwj561ojuubs3gt-1', revisions[0].paths[0][:revision] - assert_equal 'M', revisions[0].paths[1][:action] - assert_equal '/directory/doc-ls.txt', revisions[0].paths[1][:path] - assert_equal 'docls.txt-20071203175005-a3hyc3mn0shl7cgu-1', revisions[0].paths[1][:revision] - end - - def test_revisions_path_invalid - assert_nil @adapter.revisions('invalid') - end - - def test_revisions_revision_invalid - assert_nil @adapter.revisions(nil, 12345678) - assert_nil @adapter.revisions(nil, 12345678, 87654321) - end - - def test_entry - entry = @adapter.entry() - assert_equal "", entry.path - assert_equal "dir", entry.kind - entry = @adapter.entry('') - assert_equal "", entry.path - assert_equal "dir", entry.kind - assert_nil @adapter.entry('invalid') - assert_nil @adapter.entry('/invalid') - assert_nil @adapter.entry('/invalid/') - assert_nil @adapter.entry('invalid/invalid') - assert_nil @adapter.entry('invalid/invalid/') - assert_nil @adapter.entry('/invalid/invalid') - assert_nil @adapter.entry('/invalid/invalid/') - ["doc-ls.txt", "/doc-ls.txt"].each do |path| - entry = @adapter.entry(path, 2) - assert_equal "doc-ls.txt", entry.path - assert_equal "file", entry.kind - end - ["directory", "/directory", "/directory/"].each do |path| - entry = @adapter.entry(path, 2) - assert_equal "directory", entry.path - assert_equal "dir", entry.kind - end - ["directory/document.txt", "/directory/document.txt"].each do |path| - entry = @adapter.entry(path, 2) - assert_equal "directory/document.txt", entry.path - assert_equal "file", entry.kind - end - end - - private - - def test_scm_version_for(scm_command_version, version) - @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) - assert_equal version, @adapter.class.scm_command_version - end - else - puts "Bazaar test repository NOT FOUND. Skipping unit tests !!!" - def test_fake; assert true end - end - end -rescue LoadError - class BazaarMochaFake < ActiveSupport::TestCase - def test_fake; assert(false, "Requires mocha to run those tests") end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cb/cb35b0c83c5d7dbb01a2f023d75853f295f8ebcc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cb/cb35b0c83c5d7dbb01a2f023d75853f295f8ebcc.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,11 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
    a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
    t
    ",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
    ",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
    ",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

    ",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
    ","
    "]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
    ").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); + +/*! jQuery UI - v1.9.2 - 2012-12-26 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js +* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */ +(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return"area"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!=="map"?!1:(o=e("img[usemap=#"+i+"]")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:"a"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().andSelf().filter(function(){return e.css(this,"visibility")==="hidden"}).length}var n=0,r=/^ui-id-\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:"1.9.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t=="number"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css("zIndex",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css("position");if(i==="absolute"||i==="relative"||i==="fixed"){s=parseInt(r.css("zIndex"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e(function(){var t=document.body,n=t.appendChild(n=document.createElement("div"));n.offsetHeight,e.extend(n.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),e.support.minHeight=n.offsetHeight===100,e.support.selectstart="onselectstart"in n,t.removeChild(n).style.display="none"}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,"padding"+this))||0,r&&(n-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(n-=parseFloat(e.css(t,"margin"+this))||0)}),n}var i=r==="Width"?["Left","Right"]:["Top","Bottom"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+r]=function(n){return n===t?o["inner"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+"px")})},e.fn["outer"+r]=function(t,n){return typeof t!="number"?o["outer"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+"px")})}}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(n){return arguments.length?t.call(this,e.camelCase(n)):t.call(this)}}(e.fn.removeData)),function(){var t=/msie ([\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];e.ui.ie=t.length?!0:!1,e.ui.ie6=parseFloat(t[1],10)===6}(),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)},isOverAxis:function(e,t,n){return e>t&&e",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetName,this),e.data(r,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n=="string"){i={},s=n.split("."),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u=9||!!t.button?this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted):this._mouseUp(t)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(e){return this.mouseDelayMet},_mouseStart:function(e){},_mouseDrag:function(e){},_mouseStop:function(e){},_mouseCapture:function(e){return!0}})})(jQuery);(function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\+\-]\d+%?/,f=/^\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e("
    "),o=s.children()[0];return e("body").append(s),r=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?"":t.element.css("overflow-x"),r=t.isWindow?"":t.element.css("overflow-y"),i=n==="scroll"||n==="auto"&&t.width0?"right":"center",vertical:u<0?"top":o>0?"bottom":"middle"};lr(i(o),i(u))?h.important="horizontal":h.important="vertical",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]==="left"?-t.elemWidth:t.my[0]==="right"?t.elemWidth:0,c=t.at[0]==="left"?t.targetWidth:t.at[0]==="right"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)a&&(v<0||v0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)10&&i<11,t.innerHTML="",n.removeChild(t)}(),e.uiBackCompat!==!1&&function(e){var n=e.fn.position;e.fn.position=function(r){if(!r||!r.offset)return n.call(this,r);var i=r.offset.split(" "),s=r.at.split(" ");return i.length===1&&(i[1]=i[0]),/^\d/.test(i[0])&&(i[0]="+"+i[0]),/^\d/.test(i[1])&&(i[1]="+"+i[1]),s.length===1&&(/left|center|right/.test(s[0])?s[1]="center":(s[1]=s[0],s[0]="center")),n.call(this,e.extend(r,{at:s[0]+i[0]+" "+s[1]+i[1],offset:t}))}}(jQuery)})(jQuery);(function(e,t){e.widget("ui.draggable",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(t){var n=this.options;return this.helper||n.disabled||e(t.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(t),this.handle?(e(n.iframeFix===!0?"iframe":n.iframeFix).each(function(){e('
    ').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(e(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(t){var n=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,n.cursorAt&&this._adjustOffsetFromHelper(n.cursorAt),n.containment&&this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_mouseDrag:function(t,n){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute");if(!n){var r=this._uiHash();if(this._trigger("drag",t,r)===!1)return this._mouseUp({}),!1;this.position=r.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var n=!1;e.ui.ddmanager&&!this.options.dropBehaviour&&(n=e.ui.ddmanager.drop(this,t)),this.dropped&&(n=this.dropped,this.dropped=!1);var r=this.element[0],i=!1;while(r&&(r=r.parentNode))r==document&&(i=!0);if(!i&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!n||this.options.revert=="valid"&&n||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,n)){var s=this;e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){s._trigger("stop",t)!==!1&&s._clear()})}else this._trigger("stop",t)!==!1&&this._clear();return!1},_mouseUp:function(t){return e("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){var n=!this.options.handle||!e(this.options.handle,this.element).length?!0:!1;return e(this.options.handle,this.element).find("*").andSelf().each(function(){this==t.target&&(n=!0)}),n},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t])):n.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return r.parents("body").length||r.appendTo(n.appendTo=="parent"?this.element[0].parentNode:n.appendTo),r[0]!=this.element[0]&&!/(fixed|absolute)/.test(r.css("position"))&&r.css("position","absolute"),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var e=this.element.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t=this.options;t.containment=="parent"&&(t.containment=this.helper[0].parentNode);if(t.containment=="document"||t.containment=="window")this.containment=[t.containment=="document"?0:e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t.containment=="document"?0:e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(t.containment=="document"?0:e(window).scrollLeft())+e(t.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(t.containment=="document"?0:e(window).scrollTop())+(e(t.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(t.containment)&&t.containment.constructor!=Array){var n=e(t.containment),r=n[0];if(!r)return;var i=n.offset(),s=e(r).css("overflow")!="hidden";this.containment=[(parseInt(e(r).css("borderLeftWidth"),10)||0)+(parseInt(e(r).css("paddingLeft"),10)||0),(parseInt(e(r).css("borderTopWidth"),10)||0)+(parseInt(e(r).css("paddingTop"),10)||0),(s?Math.max(r.scrollWidth,r.offsetWidth):r.offsetWidth)-(parseInt(e(r).css("borderLeftWidth"),10)||0)-(parseInt(e(r).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(s?Math.max(r.scrollHeight,r.offsetHeight):r.offsetHeight)-(parseInt(e(r).css("borderTopWidth"),10)||0)-(parseInt(e(r).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=n}else t.containment.constructor==Array&&(this.containment=t.containment)},_convertPositionTo:function(t,n){n||(n=this.position);var r=t=="absolute"?1:-1,i=this.options,s=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(s[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():o?0:s.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():o?0:s.scrollLeft())*r}},_generatePosition:function(t){var n=this.options,r=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,i=/(html|body)/i.test(r[0].tagName),s=t.pageX,o=t.pageY;if(this.originalPosition){var u;if(this.containment){if(this.relative_container){var a=this.relative_container.offset();u=[this.containment[0]+a.left,this.containment[1]+a.top,this.containment[2]+a.left,this.containment[3]+a.top]}else u=this.containment;t.pageX-this.offset.click.leftu[2]&&(s=u[2]+this.offset.click.left),t.pageY-this.offset.click.top>u[3]&&(o=u[3]+this.offset.click.top)}if(n.grid){var f=n.grid[1]?this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1]:this.originalPageY;o=u?f-this.offset.click.topu[3]?f-this.offset.click.topu[2]?l-this.offset.click.left=0;l--){var c=r.snapElements[l].left,h=c+r.snapElements[l].width,p=r.snapElements[l].top,d=p+r.snapElements[l].height;if(!(c-s=l&&o<=c||u>=l&&u<=c||oc)&&(i>=a&&i<=f||s>=a&&s<=f||if);default:return!1}},e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,n){var r=e.ui.ddmanager.droppables[t.options.scope]||[],i=n?n.type:null,s=(t.currentItem||t.element).find(":data(droppable)").andSelf();e:for(var o=0;o
    ').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=n.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var r=this.handles.split(",");this.handles={};for(var i=0;i');u.css({zIndex:n.zIndex}),"se"==s&&u.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(u)}}this._renderAxis=function(t){t=t||this.element;for(var n in this.handles){this.handles[n].constructor==String&&(this.handles[n]=e(this.handles[n],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var r=e(this.handles[n],this.element),i=0;i=/sw|ne|nw|se|n|s/.test(n)?r.outerHeight():r.outerWidth();var s=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join("");t.css(s,i),this._proportionallyResize()}if(!e(this.handles[n]).length)continue}},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!t.resizing){if(this.className)var e=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);t.axis=e&&e[1]?e[1]:"se"}}),n.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){if(n.disabled)return;e(this).removeClass("ui-resizable-autohide"),t._handles.show()}).mouseleave(function(){if(n.disabled)return;t.resizing||(e(this).addClass("ui-resizable-autohide"),t._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){t(this.element);var n=this.element;this.originalElement.css({position:n.css("position"),width:n.outerWidth(),height:n.outerHeight(),top:n.css("top"),left:n.css("left")}).insertAfter(n),n.remove()}return this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_mouseCapture:function(t){var n=!1;for(var r in this.handles)e(this.handles[r])[0]==t.target&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(t){var r=this.options,i=this.element.position(),s=this.element;this.resizing=!0,this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()},(s.is(".ui-draggable")||/absolute/.test(s.css("position")))&&s.css({position:"absolute",top:i.top,left:i.left}),this._renderProxy();var o=n(this.helper.css("left")),u=n(this.helper.css("top"));r.containment&&(o+=e(r.containment).scrollLeft()||0,u+=e(r.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:o,top:u},this.size=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalSize=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalPosition={left:o,top:u},this.sizeDiff={width:s.outerWidth()-s.width(),height:s.outerHeight()-s.height()},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio=typeof r.aspectRatio=="number"?r.aspectRatio:this.originalSize.width/this.originalSize.height||1;var a=e(".ui-resizable-"+this.axis).css("cursor");return e("body").css("cursor",a=="auto"?this.axis+"-resize":a),s.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(e){var t=this.helper,n=this.options,r={},i=this,s=this.originalMousePosition,o=this.axis,u=e.pageX-s.left||0,a=e.pageY-s.top||0,f=this._change[o];if(!f)return!1;var l=f.apply(this,[e,u,a]);this._updateVirtualBoundaries(e.shiftKey);if(this._aspectRatio||e.shiftKey)l=this._updateRatio(l,e);return l=this._respectSize(l,e),this._propagate("resize",e),t.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",e,this.ui()),!1},_mouseStop:function(t){this.resizing=!1;var n=this.options,r=this;if(this._helper){var i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),o=s&&e.ui.hasScroll(i[0],"left")?0:r.sizeDiff.height,u=s?0:r.sizeDiff.width,a={width:r.helper.width()-u,height:r.helper.height()-o},f=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,l=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;n.animate||this.element.css(e.extend(a,{top:l,left:f})),r.helper.height(r.size.height),r.helper.width(r.size.width),this._helper&&!n.animate&&this._proportionallyResize()}return e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(e){var t=this.options,n,i,s,o,u;u={minWidth:r(t.minWidth)?t.minWidth:0,maxWidth:r(t.maxWidth)?t.maxWidth:Infinity,minHeight:r(t.minHeight)?t.minHeight:0,maxHeight:r(t.maxHeight)?t.maxHeight:Infinity};if(this._aspectRatio||e)n=u.minHeight*this.aspectRatio,s=u.minWidth/this.aspectRatio,i=u.maxHeight*this.aspectRatio,o=u.maxWidth/this.aspectRatio,n>u.minWidth&&(u.minWidth=n),s>u.minHeight&&(u.minHeight=s),ie.width,l=r(e.height)&&i.minHeight&&i.minHeight>e.height;f&&(e.width=i.minWidth),l&&(e.height=i.minHeight),u&&(e.width=i.maxWidth),a&&(e.height=i.maxHeight);var c=this.originalPosition.left+this.originalSize.width,h=this.position.top+this.size.height,p=/sw|nw|w/.test(o),d=/nw|ne|n/.test(o);f&&p&&(e.left=c-i.minWidth),u&&p&&(e.left=c-i.maxWidth),l&&d&&(e.top=h-i.minHeight),a&&d&&(e.top=h-i.maxHeight);var v=!e.width&&!e.height;return v&&!e.left&&e.top?e.top=null:v&&!e.top&&e.left&&(e.left=null),e},_proportionallyResize:function(){var t=this.options;if(!this._proportionallyResizeElements.length)return;var n=this.helper||this.element;for(var r=0;r');var r=e.ui.ie6?1:0,i=e.ui.ie6?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+i,height:this.element.outerHeight()+i,position:"absolute",left:this.elementOffset.left-r+"px",top:this.elementOffset.top-r+"px",zIndex:++n.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(e,t,n){return{width:this.originalSize.width+t}},w:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{top:s.top+n,height:i.height-n}},s:function(e,t,n){return{height:this.originalSize.height+n}},se:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},sw:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,n,r]))},ne:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},nw:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,n,r]))}},_propagate:function(t,n){e.ui.plugin.call(this,t,[n,this.ui()]),t!="resize"&&this._trigger(t,n,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","alsoResize",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=function(t){e(t).each(function(){var t=e(this);t.data("resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};typeof i.alsoResize=="object"&&!i.alsoResize.parentNode?i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):e.each(i.alsoResize,function(e){s(e)}):s(i.alsoResize)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.originalSize,o=r.originalPosition,u={height:r.size.height-s.height||0,width:r.size.width-s.width||0,top:r.position.top-o.top||0,left:r.position.left-o.left||0},a=function(t,r){e(t).each(function(){var t=e(this),i=e(this).data("resizable-alsoresize"),s={},o=r&&r.length?r:t.parents(n.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(o,function(e,t){var n=(i[t]||0)+(u[t]||0);n&&n>=0&&(s[t]=n||null)}),t.css(s)})};typeof i.alsoResize=="object"&&!i.alsoResize.nodeType?e.each(i.alsoResize,function(e,t){a(e,t)}):a(i.alsoResize)},stop:function(t,n){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","animate",{stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r._proportionallyResizeElements,o=s.length&&/textarea/i.test(s[0].nodeName),u=o&&e.ui.hasScroll(s[0],"left")?0:r.sizeDiff.height,a=o?0:r.sizeDiff.width,f={width:r.size.width-a,height:r.size.height-u},l=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,c=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;r.element.animate(e.extend(f,c&&l?{top:c,left:l}:{}),{duration:i.animateDuration,easing:i.animateEasing,step:function(){var n={width:parseInt(r.element.css("width"),10),height:parseInt(r.element.css("height"),10),top:parseInt(r.element.css("top"),10),left:parseInt(r.element.css("left"),10)};s&&s.length&&e(s[0]).css({width:n.width,height:n.height}),r._updateCache(n),r._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(t,r){var i=e(this).data("resizable"),s=i.options,o=i.element,u=s.containment,a=u instanceof e?u.get(0):/parent/.test(u)?o.parent().get(0):u;if(!a)return;i.containerElement=e(a);if(/document/.test(u)||u==document)i.containerOffset={left:0,top:0},i.containerPosition={left:0,top:0},i.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight};else{var f=e(a),l=[];e(["Top","Right","Left","Bottom"]).each(function(e,t){l[e]=n(f.css("padding"+t))}),i.containerOffset=f.offset(),i.containerPosition=f.position(),i.containerSize={height:f.innerHeight()-l[3],width:f.innerWidth()-l[1]};var c=i.containerOffset,h=i.containerSize.height,p=i.containerSize.width,d=e.ui.hasScroll(a,"left")?a.scrollWidth:p,v=e.ui.hasScroll(a)?a.scrollHeight:h;i.parentData={element:a,left:c.left,top:c.top,width:d,height:v}}},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.containerSize,o=r.containerOffset,u=r.size,a=r.position,f=r._aspectRatio||t.shiftKey,l={top:0,left:0},c=r.containerElement;c[0]!=document&&/static/.test(c.css("position"))&&(l=o),a.left<(r._helper?o.left:0)&&(r.size.width=r.size.width+(r._helper?r.position.left-o.left:r.position.left-l.left),f&&(r.size.height=r.size.width/r.aspectRatio),r.position.left=i.helper?o.left:0),a.top<(r._helper?o.top:0)&&(r.size.height=r.size.height+(r._helper?r.position.top-o.top:r.position.top),f&&(r.size.width=r.size.height*r.aspectRatio),r.position.top=r._helper?o.top:0),r.offset.left=r.parentData.left+r.position.left,r.offset.top=r.parentData.top+r.position.top;var h=Math.abs((r._helper?r.offset.left-l.left:r.offset.left-l.left)+r.sizeDiff.width),p=Math.abs((r._helper?r.offset.top-l.top:r.offset.top-o.top)+r.sizeDiff.height),d=r.containerElement.get(0)==r.element.parent().get(0),v=/relative|absolute/.test(r.containerElement.css("position"));d&&v&&(h-=r.parentData.left),h+r.size.width>=r.parentData.width&&(r.size.width=r.parentData.width-h,f&&(r.size.height=r.size.width/r.aspectRatio)),p+r.size.height>=r.parentData.height&&(r.size.height=r.parentData.height-p,f&&(r.size.width=r.size.height*r.aspectRatio))},stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.position,o=r.containerOffset,u=r.containerPosition,a=r.containerElement,f=e(r.helper),l=f.offset(),c=f.outerWidth()-r.sizeDiff.width,h=f.outerHeight()-r.sizeDiff.height;r._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h}),r._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h})}}),e.ui.plugin.add("resizable","ghost",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size;r.ghost=r.originalElement.clone(),r.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof i.ghost=="string"?i.ghost:""),r.ghost.appendTo(r.helper)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.ghost.css({position:"relative",height:r.size.height,width:r.size.width})},stop:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.helper&&r.helper.get(0).removeChild(r.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size,o=r.originalSize,u=r.originalPosition,a=r.axis,f=i._aspectRatio||t.shiftKey;i.grid=typeof i.grid=="number"?[i.grid,i.grid]:i.grid;var l=Math.round((s.width-o.width)/(i.grid[0]||1))*(i.grid[0]||1),c=Math.round((s.height-o.height)/(i.grid[1]||1))*(i.grid[1]||1);/^(se|s|e)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c):/^(ne)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c):/^(sw)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.left=u.left-l):(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c,r.position.left=u.left-l)}});var n=function(e){return parseInt(e,10)||0},r=function(e){return!isNaN(parseInt(e,10))}})(jQuery);(function(e,t){e.widget("ui.selectable",e.ui.mouse,{version:"1.9.2",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var t=this;this.element.addClass("ui-selectable"),this.dragged=!1;var n;this.refresh=function(){n=e(t.options.filter,t.element[0]),n.addClass("ui-selectee"),n.each(function(){var t=e(this),n=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:n.left,top:n.top,right:n.left+t.outerWidth(),bottom:n.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=n.addClass("ui-selectee"),this._mouseInit(),this.helper=e("
    ")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var n=this;this.opos=[t.pageX,t.pageY];if(this.options.disabled)return;var r=this.options;this.selectees=e(r.filter,this.element[0]),this._trigger("start",t),e(r.appendTo).append(this.helper),this.helper.css({left:t.clientX,top:t.clientY,width:0,height:0}),r.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var r=e.data(this,"selectable-item");r.startselected=!0,!t.metaKey&&!t.ctrlKey&&(r.$element.removeClass("ui-selected"),r.selected=!1,r.$element.addClass("ui-unselecting"),r.unselecting=!0,n._trigger("unselecting",t,{unselecting:r.element}))}),e(t.target).parents().andSelf().each(function(){var r=e.data(this,"selectable-item");if(r){var i=!t.metaKey&&!t.ctrlKey||!r.$element.hasClass("ui-selected");return r.$element.removeClass(i?"ui-unselecting":"ui-selected").addClass(i?"ui-selecting":"ui-unselecting"),r.unselecting=!i,r.selecting=i,r.selected=i,i?n._trigger("selecting",t,{selecting:r.element}):n._trigger("unselecting",t,{unselecting:r.element}),!1}})},_mouseDrag:function(t){var n=this;this.dragged=!0;if(this.options.disabled)return;var r=this.options,i=this.opos[0],s=this.opos[1],o=t.pageX,u=t.pageY;if(i>o){var a=o;o=i,i=a}if(s>u){var a=u;u=s,s=a}return this.helper.css({left:i,top:s,width:o-i,height:u-s}),this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!a||a.element==n.element[0])return;var f=!1;r.tolerance=="touch"?f=!(a.left>o||a.rightu||a.bottomi&&a.rights&&a.bottom *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var e=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?e.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_setOption:function(t,n){t==="disabled"?(this.options[t]=n,this.widget().toggleClass("ui-sortable-disabled",!!n)):e.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(t,n){var r=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(t);var i=null,s=e(t.target).parents().each(function(){if(e.data(this,r.widgetName+"-item")==r)return i=e(this),!1});e.data(t.target,r.widgetName+"-item")==r&&(i=e(t.target));if(!i)return!1;if(this.options.handle&&!n){var o=!1;e(this.options.handle,i).find("*").andSelf().each(function(){this==t.target&&(o=!0)});if(!o)return!1}return this.currentItem=i,this._removeCurrentsFromItems(),!0},_mouseStart:function(t,n,r){var i=this.options;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),i.containment&&this._setContainment(),i.cursor&&(e("body").css("cursor")&&(this._storedCursor=e("body").css("cursor")),e("body").css("cursor",i.cursor)),i.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",i.opacity)),i.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",i.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!r)for(var s=this.containers.length-1;s>=0;s--)this.containers[s]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var n=this.options,r=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY=0;i--){var s=this.items[i],o=s.item[0],u=this._intersectsWithPointer(s);if(!u)continue;if(s.instance!==this.currentContainer)continue;if(o!=this.currentItem[0]&&this.placeholder[u==1?"next":"prev"]()[0]!=o&&!e.contains(this.placeholder[0],o)&&(this.options.type=="semi-dynamic"?!e.contains(this.element[0],o):!0)){this.direction=u==1?"down":"up";if(this.options.tolerance!="pointer"&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,n){if(!t)return;e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t);if(this.options.revert){var r=this,i=this.placeholder.offset();this.reverting=!0,e(this.helper).animate({left:i.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:i.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){r._clear(t)})}else this._clear(t,n);return!1},cancel:function(){if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},e(n).each(function(){var n=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[-=_](.+)/);n&&r.push((t.key||n[1]+"[]")+"="+(t.key&&t.expression?n[1]:n[2]))}),!r.length&&t.key&&r.push(t.key+"="),r.join("&")},toArray:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},n.each(function(){r.push(e(t.item||this).attr(t.attribute||"id")||"")}),r},_intersectsWith:function(e){var t=this.positionAbs.left,n=t+this.helperProportions.width,r=this.positionAbs.top,i=r+this.helperProportions.height,s=e.left,o=s+e.width,u=e.top,a=u+e.height,f=this.offset.click.top,l=this.offset.click.left,c=r+f>u&&r+fs&&t+le[this.floating?"width":"height"]?c:s0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return e!=0&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor==String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){var n=[],r=[],i=this._connectWith();if(i&&t)for(var s=i.length-1;s>=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&r.push([e.isFunction(a.options.items)?a.options.items.call(a.element):e(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a])}}r.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var s=r.length-1;s>=0;s--)r[s][0].each(function(){n.push(this)});return e(n)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var n=0;n=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&(r.push([e.isFunction(a.options.items)?a.options.items.call(a.element[0],t,{item:this.currentItem}):e(a.options.items,a.element),a]),this.containers.push(a))}}for(var s=r.length-1;s>=0;s--){var f=r[s][1],l=r[s][0];for(var u=0,c=l.length;u=0;n--){var r=this.items[n];if(r.instance!=this.currentContainer&&this.currentContainer&&r.item[0]!=this.currentItem[0])continue;var i=this.options.toleranceElement?e(this.options.toleranceElement,r.item):r.item;t||(r.width=i.outerWidth(),r.height=i.outerHeight());var s=i.offset();r.left=s.left,r.top=s.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var n=this.containers.length-1;n>=0;n--){var s=this.containers[n].element.offset();this.containers[n].containerCache.left=s.left,this.containers[n].containerCache.top=s.top,this.containers[n].containerCache.width=this.containers[n].element.outerWidth(),this.containers[n].containerCache.height=this.containers[n].element.outerHeight()}return this},_createPlaceholder:function(t){t=t||this;var n=t.options;if(!n.placeholder||n.placeholder.constructor==String){var r=n.placeholder;n.placeholder={element:function(){var n=e(document.createElement(t.currentItem[0].nodeName)).addClass(r||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return r||(n.style.visibility="hidden"),n},update:function(e,i){if(r&&!n.forcePlaceholderSize)return;i.height()||i.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),i.width()||i.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10))}}}t.placeholder=e(n.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),n.placeholder.update(t,t.placeholder)},_contactContainers:function(t){var n=null,r=null;for(var i=this.containers.length-1;i>=0;i--){if(e.contains(this.currentItem[0],this.containers[i].element[0]))continue;if(this._intersectsWith(this.containers[i].containerCache)){if(n&&e.contains(this.containers[i].element[0],n.element[0]))continue;n=this.containers[i],r=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",t,this._uiHash(this)),this.containers[i].containerCache.over=0)}if(!n)return;if(this.containers.length===1)this.containers[r]._trigger("over",t,this._uiHash(this)),this.containers[r].containerCache.over=1;else{var s=1e4,o=null,u=this.containers[r].floating?"left":"top",a=this.containers[r].floating?"width":"height",f=this.positionAbs[u]+this.offset.click[u];for(var l=this.items.length-1;l>=0;l--){if(!e.contains(this.containers[r].element[0],this.items[l].item[0]))continue;if(this.items[l].item[0]==this.currentItem[0])continue;var c=this.items[l].item.offset()[u],h=!1;Math.abs(c-f)>Math.abs(c+this.items[l][a]-f)&&(h=!0,c+=this.items[l][a]),Math.abs(c-f)this.containment[2]&&(s=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(o=this.containment[3]+this.offset.click.top));if(n.grid){var u=this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1];o=this.containment?u-this.offset.click.topthis.containment[3]?u-this.offset.click.topthis.containment[2]?a-this.offset.click.left=0;i--)n||r.push(function(e){return function(t){e._trigger("deactivate",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(r.push(function(e){return function(t){e._trigger("out",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);this._storedCursor&&e("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!n){this._trigger("beforeStop",t,this._uiHash());for(var i=0;i li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var t=this.accordionId="ui-accordion-"+(this.element.attr("id")||++n),r=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset"),this.headers=this.element.find(r.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all"),this._hoverable(this.headers),this._focusable(this.headers),this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").hide(),!r.collapsible&&(r.active===!1||r.active==null)&&(r.active=0),r.active<0&&(r.active+=this.headers.length),this.active=this._findActive(r.active).addClass("ui-accordion-header-active ui-state-active").toggleClass("ui-corner-all ui-corner-top"),this.active.next().addClass("ui-accordion-content-active").show(),this._createIcons(),this.refresh(),this.element.attr("role","tablist"),this.headers.attr("role","tab").each(function(n){var r=e(this),i=r.attr("id"),s=r.next(),o=s.attr("id");i||(i=t+"-header-"+n,r.attr("id",i)),o||(o=t+"-panel-"+n,s.attr("id",o)),r.attr("aria-controls",o),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false",tabIndex:-1}).next().attr({"aria-expanded":"false","aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true",tabIndex:0}).next().attr({"aria-expanded":"true","aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._on(this.headers,{keydown:"_keydown"}),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._setupEvents(r.event)},_getCreateEventData:function(){return{header:this.active,content:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this._destroyIcons(),e=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this.options.heightStyle!=="content"&&e.css("height","")},_setOption:function(e,t){if(e==="active"){this._activate(t);return}e==="event"&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),e==="collapsible"&&!t&&this.options.active===!1&&this._activate(0),e==="icons"&&(this._destroyIcons(),t&&this._createIcons()),e==="disabled"&&this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t)},_keydown:function(t){if(t.altKey||t.ctrlKey)return;var n=e.ui.keyCode,r=this.headers.length,i=this.headers.index(t.target),s=!1;switch(t.keyCode){case n.RIGHT:case n.DOWN:s=this.headers[(i+1)%r];break;case n.LEFT:case n.UP:s=this.headers[(i-1+r)%r];break;case n.SPACE:case n.ENTER:this._eventHandler(t);break;case n.HOME:s=this.headers[0];break;case n.END:s=this.headers[r-1]}s&&(e(t.target).attr("tabIndex",-1),e(s).attr("tabIndex",0),s.focus(),t.preventDefault())},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t,n,r=this.options.heightStyle,i=this.element.parent();r==="fill"?(e.support.minHeight||(n=i.css("overflow"),i.css("overflow","hidden")),t=i.height(),this.element.siblings(":visible").each(function(){var n=e(this),r=n.css("position");if(r==="absolute"||r==="fixed")return;t-=n.outerHeight(!0)}),n&&i.css("overflow",n),this.headers.each(function(){t-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,t-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):r==="auto"&&(t=0,this.headers.next().each(function(){t=Math.max(t,e(this).css("height","").height())}).height(t))},_activate:function(t){var n=this._findActive(t)[0];if(n===this.active[0])return;n=n||this.active[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return typeof t=="number"?this.headers.eq(t):e()},_setupEvents:function(t){var n={};if(!t)return;e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._on(this.headers,n)},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i[0]===r[0],o=s&&n.collapsible,u=o?e():i.next(),a=r.next(),f={oldHeader:r,oldPanel:a,newHeader:o?e():i,newPanel:u};t.preventDefault();if(s&&!n.collapsible||this._trigger("beforeActivate",t,f)===!1)return;n.active=o?!1:this.headers.index(i),this.active=s?e():i,this._toggle(f),r.removeClass("ui-accordion-header-active ui-state-active"),n.icons&&r.children(".ui-accordion-header-icon").removeClass(n.icons.activeHeader).addClass(n.icons.header),s||(i.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),n.icons&&i.children(".ui-accordion-header-icon").removeClass(n.icons.header).addClass(n.icons.activeHeader),i.next().addClass("ui-accordion-content-active"))},_toggle:function(t){var n=t.newPanel,r=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=n,this.prevHide=r,this.options.animate?this._animate(n,r,t):(r.hide(),n.show(),this._toggleComplete(t)),r.attr({"aria-expanded":"false","aria-hidden":"true"}),r.prev().attr("aria-selected","false"),n.length&&r.length?r.prev().attr("tabIndex",-1):n.length&&this.headers.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),n.attr({"aria-expanded":"true","aria-hidden":"false"}).prev().attr({"aria-selected":"true",tabIndex:0})},_animate:function(e,t,n){var s,o,u,a=this,f=0,l=e.length&&(!t.length||e.index()",options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,n,r;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is("input,textarea")?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(i){if(this.element.prop("readOnly")){t=!0,r=!0,n=!0;return}t=!1,r=!1,n=!1;var s=e.ui.keyCode;switch(i.keyCode){case s.PAGE_UP:t=!0,this._move("previousPage",i);break;case s.PAGE_DOWN:t=!0,this._move("nextPage",i);break;case s.UP:t=!0,this._keyEvent("previous",i);break;case s.DOWN:t=!0,this._keyEvent("next",i);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(t=!0,i.preventDefault(),this.menu.select(i));break;case s.TAB:this.menu.active&&this.menu.select(i);break;case s.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(i),i.preventDefault());break;default:n=!0,this._searchTimeout(i)}},keypress:function(r){if(t){t=!1,r.preventDefault();return}if(n)return;var i=e.ui.keyCode;switch(r.keyCode){case i.PAGE_UP:this._move("previousPage",r);break;case i.PAGE_DOWN:this._move("nextPage",r);break;case i.UP:this._keyEvent("previous",r);break;case i.DOWN:this._keyEvent("next",r)}},input:function(e){if(r){r=!1,e.preventDefault();return}this._searchTimeout(e)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching),this.close(e),this._change(e)}}),this._initSource(),this.menu=e("
    '+"";var z=N?'":"";for(var W=0;W<7;W++){var X=(W+T)%7;z+="=5?' class="ui-datepicker-week-end"':"")+">"+''+L[X]+""}U+=z+"";var V=this._getDaysInMonth(d,p);d==e.selectedYear&&p==e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,V));var J=(this._getFirstDayOfMonth(d,p)-T+7)%7,K=Math.ceil((J+V)/7),Q=f?this.maxRows>K?this.maxRows:K:K;this.maxRows=Q;var G=this._daylightSavingAdjust(new Date(d,p,1-J));for(var Y=0;Y";var Z=N?'":"";for(var W=0;W<7;W++){var et=M?M.apply(e.input?e.input[0]:null,[G]):[!0,""],tt=G.getMonth()!=p,nt=tt&&!D||!et[0]||c&&Gh;Z+='",G.setDate(G.getDate()+1),G=this._daylightSavingAdjust(G)}U+=Z+""}p++,p>11&&(p=0,d++),U+="
    '+this._get(e,"weekHeader")+"
    '+this._get(e,"calculateWeek")(G)+""+(tt&&!_?" ":nt?''+G.getDate()+"":''+G.getDate()+"")+"
    "+(f?"
    "+(o[0]>0&&I==o[1]-1?'
    ':""):""),F+=U}B+=F}return B+=x+($.ui.ie6&&!e.inline?'':""),e._keyEvent=!1,B},_generateMonthYearHeader:function(e,t,n,r,i,s,o,u){var a=this._get(e,"changeMonth"),f=this._get(e,"changeYear"),l=this._get(e,"showMonthAfterYear"),c='
    ',h="";if(s||!a)h+=''+o[t]+"";else{var p=r&&r.getFullYear()==n,d=i&&i.getFullYear()==n;h+='"}l||(c+=h+(s||!a||!f?" ":""));if(!e.yearshtml){e.yearshtml="";if(s||!f)c+=''+n+"";else{var m=this._get(e,"yearRange").split(":"),g=(new Date).getFullYear(),y=function(e){var t=e.match(/c[+-].*/)?n+parseInt(e.substring(1),10):e.match(/[+-].*/)?g+parseInt(e,10):parseInt(e,10);return isNaN(t)?g:t},b=y(m[0]),w=Math.max(b,y(m[1]||""));b=r?Math.max(b,r.getFullYear()):b,w=i?Math.min(w,i.getFullYear()):w,e.yearshtml+='",c+=e.yearshtml,e.yearshtml=null}}return c+=this._get(e,"yearSuffix"),l&&(c+=(s||!a||!f?" ":"")+h),c+="
    ",c},_adjustInstDate:function(e,t,n){var r=e.drawYear+(n=="Y"?t:0),i=e.drawMonth+(n=="M"?t:0),s=Math.min(e.selectedDay,this._getDaysInMonth(r,i))+(n=="D"?t:0),o=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(r,i,s)));e.selectedDay=o.getDate(),e.drawMonth=e.selectedMonth=o.getMonth(),e.drawYear=e.selectedYear=o.getFullYear(),(n=="M"||n=="Y")&&this._notifyChange(e)},_restrictMinMax:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max"),i=n&&tr?r:i,i},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return t==null?[1,1]:typeof t=="number"?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return(new Date(e,t,1)).getDay()},_canAdjustMonth:function(e,t,n,r){var i=this._getNumberOfMonths(e),s=this._daylightSavingAdjust(new Date(n,r+(t<0?t:i[0]*i[1]),1));return t<0&&s.setDate(this._getDaysInMonth(s.getFullYear(),s.getMonth())),this._isInRange(e,s)},_isInRange:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max");return(!n||t.getTime()>=n.getTime())&&(!r||t.getTime()<=r.getTime())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t=typeof t!="string"?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,n,r){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var i=t?typeof t=="object"?t:this._daylightSavingAdjust(new Date(r,n,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),i,this._getFormatConfig(e))}}),$.fn.datepicker=function(e){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find(document.body).append($.datepicker.dpDiv),$.datepicker.initialized=!0);var t=Array.prototype.slice.call(arguments,1);return typeof e!="string"||e!="isDisabled"&&e!="getDate"&&e!="widget"?e=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t)):this.each(function(){typeof e=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this].concat(t)):$.datepicker._attachDatepicker(this,e)}):$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.9.2",window["DP_jQuery_"+dpuuid]=$})(jQuery);(function(e,t){var n="ui-dialog ui-widget ui-widget-content ui-corner-all ",r={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},i={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0};e.widget("ui.dialog",{version:"1.9.2",options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var n=e(this).css(t).offset().top;n<0&&e(this).css("top",t.top-n)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.oldPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.options.title=this.options.title||this.originalTitle;var t=this,r=this.options,i=r.title||" ",s,o,u,a,f;s=(this.uiDialog=e("
    ")).addClass(n+r.dialogClass).css({display:"none",outline:0,zIndex:r.zIndex}).attr("tabIndex",-1).keydown(function(n){r.closeOnEscape&&!n.isDefaultPrevented()&&n.keyCode&&n.keyCode===e.ui.keyCode.ESCAPE&&(t.close(n),n.preventDefault())}).mousedown(function(e){t.moveToTop(!1,e)}).appendTo("body"),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(s),o=(this.uiDialogTitlebar=e("
    ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").bind("mousedown",function(){s.focus()}).prependTo(s),u=e("").addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").click(function(e){e.preventDefault(),t.close(e)}).appendTo(o),(this.uiDialogTitlebarCloseText=e("")).addClass("ui-icon ui-icon-closethick").text(r.closeText).appendTo(u),a=e("").uniqueId().addClass("ui-dialog-title").html(i).prependTo(o),f=(this.uiDialogButtonPane=e("
    ")).addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),(this.uiButtonSet=e("
    ")).addClass("ui-dialog-buttonset").appendTo(f),s.attr({role:"dialog","aria-labelledby":a.attr("id")}),o.find("*").add(o).disableSelection(),this._hoverable(u),this._focusable(u),r.draggable&&e.fn.draggable&&this._makeDraggable(),r.resizable&&e.fn.resizable&&this._makeResizable(),this._createButtons(r.buttons),this._isOpen=!1,e.fn.bgiframe&&s.bgiframe(),this._on(s,{keydown:function(t){if(!r.modal||t.keyCode!==e.ui.keyCode.TAB)return;var n=e(":tabbable",s),i=n.filter(":first"),o=n.filter(":last");if(t.target===o[0]&&!t.shiftKey)return i.focus(1),!1;if(t.target===i[0]&&t.shiftKey)return o.focus(1),!1}})},_init:function(){this.options.autoOpen&&this.open()},_destroy:function(){var e,t=this.oldPosition;this.overlay&&this.overlay.destroy(),this.uiDialog.hide(),this.element.removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),this.uiDialog.remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},close:function(t){var n=this,r,i;if(!this._isOpen)return;if(!1===this._trigger("beforeClose",t))return;return this._isOpen=!1,this.overlay&&this.overlay.destroy(),this.options.hide?this._hide(this.uiDialog,this.options.hide,function(){n._trigger("close",t)}):(this.uiDialog.hide(),this._trigger("close",t)),e.ui.dialog.overlay.resize(),this.options.modal&&(r=0,e(".ui-dialog").each(function(){this!==n.uiDialog[0]&&(i=e(this).css("z-index"),isNaN(i)||(r=Math.max(r,i)))}),e.ui.dialog.maxZ=r),this},isOpen:function(){return this._isOpen},moveToTop:function(t,n){var r=this.options,i;return r.modal&&!t||!r.stack&&!r.modal?this._trigger("focus",n):(r.zIndex>e.ui.dialog.maxZ&&(e.ui.dialog.maxZ=r.zIndex),this.overlay&&(e.ui.dialog.maxZ+=1,e.ui.dialog.overlay.maxZ=e.ui.dialog.maxZ,this.overlay.$el.css("z-index",e.ui.dialog.overlay.maxZ)),i={scrollTop:this.element.scrollTop(),scrollLeft:this.element.scrollLeft()},e.ui.dialog.maxZ+=1,this.uiDialog.css("z-index",e.ui.dialog.maxZ),this.element.attr(i),this._trigger("focus",n),this)},open:function(){if(this._isOpen)return;var t,n=this.options,r=this.uiDialog;return this._size(),this._position(n.position),r.show(n.show),this.overlay=n.modal?new e.ui.dialog.overlay(this):null,this.moveToTop(!0),t=this.element.find(":tabbable"),t.length||(t=this.uiDialogButtonPane.find(":tabbable"),t.length||(t=r)),t.eq(0).focus(),this._isOpen=!0,this._trigger("open"),this},_createButtons:function(t){var n=this,r=!1;this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),typeof t=="object"&&t!==null&&e.each(t,function(){return!(r=!0)}),r?(e.each(t,function(t,r){var i,s;r=e.isFunction(r)?{click:r,text:t}:r,r=e.extend({type:"button"},r),s=r.click,r.click=function(){s.apply(n.element[0],arguments)},i=e("",r).appendTo(n.uiButtonSet),e.fn.button&&i.button()}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog)):this.uiDialog.removeClass("ui-dialog-buttons")},_makeDraggable:function(){function r(e){return{position:e.position,offset:e.offset}}var t=this,n=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(n,i){e(this).addClass("ui-dialog-dragging"),t._trigger("dragStart",n,r(i))},drag:function(e,n){t._trigger("drag",e,r(n))},stop:function(i,s){n.position=[s.position.left-t.document.scrollLeft(),s.position.top-t.document.scrollTop()],e(this).removeClass("ui-dialog-dragging"),t._trigger("dragStop",i,r(s)),e.ui.dialog.overlay.resize()}})},_makeResizable:function(n){function u(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}n=n===t?this.options.resizable:n;var r=this,i=this.options,s=this.uiDialog.css("position"),o=typeof n=="string"?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:i.maxWidth,maxHeight:i.maxHeight,minWidth:i.minWidth,minHeight:this._minHeight(),handles:o,start:function(t,n){e(this).addClass("ui-dialog-resizing"),r._trigger("resizeStart",t,u(n))},resize:function(e,t){r._trigger("resize",e,u(t))},stop:function(t,n){e(this).removeClass("ui-dialog-resizing"),i.height=e(this).height(),i.width=e(this).width(),r._trigger("resizeStop",t,u(n)),e.ui.dialog.overlay.resize()}}).css("position",s).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var e=this.options;return e.height==="auto"?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(t){var n=[],r=[0,0],i;if(t){if(typeof t=="string"||typeof t=="object"&&"0"in t)n=t.split?t.split(" "):[t[0],t[1]],n.length===1&&(n[1]=n[0]),e.each(["left","top"],function(e,t){+n[e]===n[e]&&(r[e]=n[e],n[e]=t)}),t={my:n[0]+(r[0]<0?r[0]:"+"+r[0])+" "+n[1]+(r[1]<0?r[1]:"+"+r[1]),at:n.join(" ")};t=e.extend({},e.ui.dialog.prototype.options.position,t)}else t=e.ui.dialog.prototype.options.position;i=this.uiDialog.is(":visible"),i||this.uiDialog.show(),this.uiDialog.position(t),i||this.uiDialog.hide()},_setOptions:function(t){var n=this,s={},o=!1;e.each(t,function(e,t){n._setOption(e,t),e in r&&(o=!0),e in i&&(s[e]=t)}),o&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",s)},_setOption:function(t,r){var i,s,o=this.uiDialog;switch(t){case"buttons":this._createButtons(r);break;case"closeText":this.uiDialogTitlebarCloseText.text(""+r);break;case"dialogClass":o.removeClass(this.options.dialogClass).addClass(n+r);break;case"disabled":r?o.addClass("ui-dialog-disabled"):o.removeClass("ui-dialog-disabled");break;case"draggable":i=o.is(":data(draggable)"),i&&!r&&o.draggable("destroy"),!i&&r&&this._makeDraggable();break;case"position":this._position(r);break;case"resizable":s=o.is(":data(resizable)"),s&&!r&&o.resizable("destroy"),s&&typeof r=="string"&&o.resizable("option","handles",r),!s&&r!==!1&&this._makeResizable(r);break;case"title":e(".ui-dialog-title",this.uiDialogTitlebar).html(""+(r||" "))}this._super(t,r)},_size:function(){var t,n,r,i=this.options,s=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),i.minWidth>i.width&&(i.width=i.minWidth),t=this.uiDialog.css({height:"auto",width:i.width}).outerHeight(),n=Math.max(0,i.minHeight-t),i.height==="auto"?e.support.minHeight?this.element.css({minHeight:n,height:"auto"}):(this.uiDialog.show(),r=this.element.css("height","auto").height(),s||this.uiDialog.hide(),this.element.height(Math.max(r,n))):this.element.height(Math.max(i.height-t,0)),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),e.extend(e.ui.dialog,{uuid:0,maxZ:0,getTitleId:function(e){var t=e.attr("id");return t||(this.uuid+=1,t=this.uuid),"ui-dialog-title-"+t},overlay:function(t){this.$el=e.ui.dialog.overlay.create(t)}}),e.extend(e.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:e.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(e){return e+".dialog-overlay"}).join(" "),create:function(t){this.instances.length===0&&(setTimeout(function(){e.ui.dialog.overlay.instances.length&&e(document).bind(e.ui.dialog.overlay.events,function(t){if(e(t.target).zIndex()").addClass("ui-widget-overlay");return e(document).bind("keydown.dialog-overlay",function(r){var i=e.ui.dialog.overlay.instances;i.length!==0&&i[i.length-1]===n&&t.options.closeOnEscape&&!r.isDefaultPrevented()&&r.keyCode&&r.keyCode===e.ui.keyCode.ESCAPE&&(t.close(r),r.preventDefault())}),n.appendTo(document.body).css({width:this.width(),height:this.height()}),e.fn.bgiframe&&n.bgiframe(),this.instances.push(n),n},destroy:function(t){var n=e.inArray(t,this.instances),r=0;n!==-1&&this.oldInstances.push(this.instances.splice(n,1)[0]),this.instances.length===0&&e([document,window]).unbind(".dialog-overlay"),t.height(0).width(0).remove(),e.each(this.instances,function(){r=Math.max(r,this.css("z-index"))}),this.maxZ=r},height:function(){var t,n;return e.ui.ie?(t=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),n=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),t",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,e.proxy(function(e){this.options.disabled&&e.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(e){e.preventDefault()},"click .ui-state-disabled > a":function(e){e.preventDefault()},"click .ui-menu-item:has(a)":function(t){var r=e(t.target).closest(".ui-menu-item");!n&&r.not(".ui-state-disabled").length&&(n=!0,this.select(t),r.has(".ui-menu").length?this.expand(t):this.element.is(":focus")||(this.element.trigger("focus",[!0]),this.active&&this.active.parents(".ui-menu").length===1&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(t){var n=e(t.currentTarget);n.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(t,n)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,t){var n=this.active||this.element.children(".ui-menu-item").eq(0);t||this.focus(e,n)},blur:function(t){this._delay(function(){e.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){e(t.target).closest(".ui-menu").length||this.collapseAll(t),n=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").andSelf().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var t=e(this);t.data("ui-menu-submenu-carat")&&t.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(t){function a(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var n,r,i,s,o,u=!0;switch(t.keyCode){case e.ui.keyCode.PAGE_UP:this.previousPage(t);break;case e.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case e.ui.keyCode.HOME:this._move("first","first",t);break;case e.ui.keyCode.END:this._move("last","last",t);break;case e.ui.keyCode.UP:this.previous(t);break;case e.ui.keyCode.DOWN:this.next(t);break;case e.ui.keyCode.LEFT:this.collapse(t);break;case e.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case e.ui.keyCode.ENTER:case e.ui.keyCode.SPACE:this._activate(t);break;case e.ui.keyCode.ESCAPE:this.collapse(t);break;default:u=!1,r=this.previousFilter||"",i=String.fromCharCode(t.keyCode),s=!1,clearTimeout(this.filterTimer),i===r?s=!0:i=r+i,o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())}),n=s&&n.index(this.active.next())!==-1?this.active.nextAll(".ui-menu-item"):n,n.length||(i=String.fromCharCode(t.keyCode),o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())})),n.length?(this.focus(t,n),n.length>1?(this.previousFilter=i,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}u&&t.preventDefault()},_activate:function(e){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(e):this.select(e))},refresh:function(){var t,n=this.options.icons.submenu,r=this.element.find(this.options.menus);r.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=e(this),r=t.prev("a"),i=e("").addClass("ui-menu-icon ui-icon "+n).data("ui-menu-submenu-carat",!0);r.attr("aria-haspopup","true").prepend(i),t.attr("aria-labelledby",r.attr("id"))}),t=r.add(this.element),t.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),t.children(":not(.ui-menu-item)").each(function(){var t=e(this);/[^\-â€â€Ã¢â‚¬â€œ\s]/.test(t.text())||t.addClass("ui-widget-content ui-menu-divider")}),t.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!e.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},focus:function(e,t){var n,r;this.blur(e,e&&e.type==="focus"),this._scrollIntoView(t),this.active=t.first(),r=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",r.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),e&&e.type==="keydown"?this._close():this.timer=this._delay(function(){this._close()},this.delay),n=t.children(".ui-menu"),n.length&&/^mouse/.test(e.type)&&this._startOpening(n),this.activeMenu=t.parent(),this._trigger("focus",e,{item:t})},_scrollIntoView:function(t){var n,r,i,s,o,u;this._hasScroll()&&(n=parseFloat(e.css(this.activeMenu[0],"borderTopWidth"))||0,r=parseFloat(e.css(this.activeMenu[0],"paddingTop"))||0,i=t.offset().top-this.activeMenu.offset().top-n-r,s=this.activeMenu.scrollTop(),o=this.activeMenu.height(),u=t.height(),i<0?this.activeMenu.scrollTop(s+i):i+u>o&&this.activeMenu.scrollTop(s+i-o+u))},blur:function(e,t){t||clearTimeout(this.timer);if(!this.active)return;this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",e,{item:this.active})},_startOpening:function(e){clearTimeout(this.timer);if(e.attr("aria-hidden")!=="true")return;this.timer=this._delay(function(){this._close(),this._open(e)},this.delay)},_open:function(t){var n=e.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(t.parents(".ui-menu")).hide().attr("aria-hidden","true"),t.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(n)},collapseAll:function(t,n){clearTimeout(this.timer),this.timer=this._delay(function(){var r=n?this.element:e(t&&t.target).closest(this.element.find(".ui-menu"));r.length||(r=this.element),this._close(r),this.blur(t),this.activeMenu=r},this.delay)},_close:function(e){e||(e=this.active?this.active.parent():this.element),e.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(e){var t=this.active&&this.active.parent().closest(".ui-menu-item",this.element);t&&t.length&&(this._close(),this.focus(e,t))},expand:function(e){var t=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();t&&t.length&&(this._open(t.parent()),this._delay(function(){this.focus(e,t)}))},next:function(e){this._move("next","first",e)},previous:function(e){this._move("prev","last",e)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(e,t,n){var r;this.active&&(e==="first"||e==="last"?r=this.active[e==="first"?"prevAll":"nextAll"](".ui-menu-item").eq(-1):r=this.active[e+"All"](".ui-menu-item").eq(0));if(!r||!r.length||!this.active)r=this.activeMenu.children(".ui-menu-item")[t]();this.focus(n,r)},nextPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isLastItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r-i<0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())},previousPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isFirstItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r+i>0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item").first())},_hasScroll:function(){return this.element.outerHeight()
    ").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(e){return e===t?this._value():(this._setOption("value",e),this)},_setOption:function(e,t){e==="value"&&(this.options.value=t,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),this._super(e,t)},_value:function(){var e=this.options.value;return typeof e!="number"&&(e=0),Math.min(this.options.max,Math.max(this.min,e))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var e=this.value(),t=this._percentage();this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),this.valueDiv.toggle(e>this.min).toggleClass("ui-corner-right",e===this.options.max).width(t.toFixed(0)+"%"),this.element.attr("aria-valuenow",e)}})})(jQuery);(function(e,t){var n=5;e.widget("ui.slider",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var t,r,i=this.options,s=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),o="",u=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"+(i.disabled?" ui-slider-disabled ui-disabled":"")),this.range=e([]),i.range&&(i.range===!0&&(i.values||(i.values=[this._valueMin(),this._valueMin()]),i.values.length&&i.values.length!==2&&(i.values=[i.values[0],i.values[0]])),this.range=e("
    ").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(i.range==="min"||i.range==="max"?" ui-slider-range-"+i.range:""))),r=i.values&&i.values.length||1;for(t=s.length;tn&&(i=n,s=e(this),o=t)}),c.range===!0&&this.values(1)===c.min&&(o+=1,s=e(this.handles[o])),u=this._start(t,o),u===!1?!1:(this._mouseSliding=!0,this._handleIndex=o,s.addClass("ui-state-active").focus(),a=s.offset(),f=!e(t.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=f?{left:0,top:0}:{left:t.pageX-a.left-s.width()/2,top:t.pageY-a.top-s.height()/2-(parseInt(s.css("borderTopWidth"),10)||0)-(parseInt(s.css("borderBottomWidth"),10)||0)+(parseInt(s.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(t,o,r),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(e){var t={x:e.pageX,y:e.pageY},n=this._normValueFromMouse(t);return this._slide(e,this._handleIndex,n),!1},_mouseStop:function(e){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(e,this._handleIndex),this._change(e,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(e){var t,n,r,i,s;return this.orientation==="horizontal"?(t=this.elementSize.width,n=e.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(t=this.elementSize.height,n=e.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),r=n/t,r>1&&(r=1),r<0&&(r=0),this.orientation==="vertical"&&(r=1-r),i=this._valueMax()-this._valueMin(),s=this._valueMin()+r*i,this._trimAlignValue(s)},_start:function(e,t){var n={handle:this.handles[t],value:this.value()};return this.options.values&&this.options.values.length&&(n.value=this.values(t),n.values=this.values()),this._trigger("start",e,n)},_slide:function(e,t,n){var r,i,s;this.options.values&&this.options.values.length?(r=this.values(t?0:1),this.options.values.length===2&&this.options.range===!0&&(t===0&&n>r||t===1&&n1){this.options.values[t]=this._trimAlignValue(n),this._refreshValue(),this._change(null,t);return}if(!arguments.length)return this._values();if(!e.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(t):this.value();r=this.options.values,i=arguments[0];for(s=0;s=this._valueMax())return this._valueMax();var t=this.options.step>0?this.options.step:1,n=(e-this._valueMin())%t,r=e-n;return Math.abs(n)*2>=t&&(r+=n>0?t:-t),parseFloat(r.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var t,n,r,i,s,o=this.options.range,u=this.options,a=this,f=this._animateOff?!1:u.animate,l={};this.options.values&&this.options.values.length?this.handles.each(function(r){n=(a.values(r)-a._valueMin())/(a._valueMax()-a._valueMin())*100,l[a.orientation==="horizontal"?"left":"bottom"]=n+"%",e(this).stop(1,1)[f?"animate":"css"](l,u.animate),a.options.range===!0&&(a.orientation==="horizontal"?(r===0&&a.range.stop(1,1)[f?"animate":"css"]({left:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({width:n-t+"%"},{queue:!1,duration:u.animate})):(r===0&&a.range.stop(1,1)[f?"animate":"css"]({bottom:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({height:n-t+"%"},{queue:!1,duration:u.animate}))),t=n}):(r=this.value(),i=this._valueMin(),s=this._valueMax(),n=s!==i?(r-i)/(s-i)*100:0,l[this.orientation==="horizontal"?"left":"bottom"]=n+"%",this.handle.stop(1,1)[f?"animate":"css"](l,u.animate),o==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[f?"animate":"css"]({width:n+"%"},u.animate),o==="max"&&this.orientation==="horizontal"&&this.range[f?"animate":"css"]({width:100-n+"%"},{queue:!1,duration:u.animate}),o==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[f?"animate":"css"]({height:n+"%"},u.animate),o==="max"&&this.orientation==="vertical"&&this.range[f?"animate":"css"]({height:100-n+"%"},{queue:!1,duration:u.animate}))}})})(jQuery);(function(e){function t(e){return function(){var t=this.element.val();e.apply(this,arguments),this._refresh(),t!==this.element.val()&&this._trigger("change")}}e.widget("ui.spinner",{version:"1.9.2",defaultElement:"",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},n=this.element;return e.each(["min","max","step"],function(e,r){var i=n.attr(r);i!==undefined&&i.length&&(t[r]=i)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e)},mousewheel:function(e,t){if(!t)return;if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()},"mousedown .ui-spinner-button":function(t){function r(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=n,this._delay(function(){this.previous=n}))}var n;n=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),r.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,r.call(this)});if(this._start(t)===!1)return;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){if(!e(t.currentTarget).hasClass("ui-state-active"))return;if(this._start(t)===!1)return!1;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(e.height()*.5)&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()},_keydown:function(t){var n=this.options,r=e.ui.keyCode;switch(t.keyCode){case r.UP:return this._repeat(null,1,t),!0;case r.DOWN:return this._repeat(null,-1,t),!0;case r.PAGE_UP:return this._repeat(null,n.page,t),!0;case r.PAGE_DOWN:return this._repeat(null,-n.page,t),!0}return!1},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""+""+""+""+""},_start:function(e){return!this.spinning&&this._trigger("start",e)===!1?!1:(this.counter||(this.counter=1),this.spinning=!0,!0)},_repeat:function(e,t,n){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,n)},e),this._spin(t*this.options.step,n)},_spin:function(e,t){var n=this.value()||0;this.counter||(this.counter=1),n=this._adjustValue(n+e*this._increment(this.counter));if(!this.spinning||this._trigger("spin",t,{value:n})!==!1)this._value(n),this.counter++},_increment:function(t){var n=this.options.incremental;return n?e.isFunction(n)?n(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return this.options.min!==null&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=e.toString(),n=t.indexOf(".");return n===-1?0:t.length-n-1},_adjustValue:function(e){var t,n,r=this.options;return t=r.min!==null?r.min:0,n=e-t,n=Math.round(n/r.step)*r.step,e=t+n,e=parseFloat(e.toFixed(this._precision())),r.max!==null&&e>r.max?r.max:r.min!==null&&e1&&e.href.replace(r,"")===location.href.replace(r,"").replace(/\s/g,"%20")}var n=0,r=/#.*$/;e.widget("ui.tabs",{version:"1.9.2",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var t=this,n=this.options,r=n.active,i=location.hash.substring(1);this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",n.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this._processTabs();if(r===null){i&&this.tabs.each(function(t,n){if(e(n).attr("aria-controls")===i)return r=t,!1}),r===null&&(r=this.tabs.index(this.tabs.filter(".ui-tabs-active")));if(r===null||r===-1)r=this.tabs.length?0:!1}r!==!1&&(r=this.tabs.index(this.tabs.eq(r)),r===-1&&(r=n.collapsible?!1:0)),n.active=r,!n.collapsible&&n.active===!1&&this.anchors.length&&(n.active=0),e.isArray(n.disabled)&&(n.disabled=e.unique(n.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.options.active!==!1&&this.anchors.length?this.active=this._findActive(this.options.active):this.active=e(),this._refresh(),this.active.length&&this.load(n.active)},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var n=e(this.document[0].activeElement).closest("li"),r=this.tabs.index(n),i=!0;if(this._handlePageNav(t))return;switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:r++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:i=!1,r--;break;case e.ui.keyCode.END:r=this.anchors.length-1;break;case e.ui.keyCode.HOME:r=0;break;case e.ui.keyCode.SPACE:t.preventDefault(),clearTimeout(this.activating),this._activate(r);return;case e.ui.keyCode.ENTER:t.preventDefault(),clearTimeout(this.activating),this._activate(r===this.options.active?!1:r);return;default:return}t.preventDefault(),clearTimeout(this.activating),r=this._focusNextTab(r,i),t.ctrlKey||(n.attr("aria-selected","false"),this.tabs.eq(r).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",r)},this.delay))},_panelKeydown:function(t){if(this._handlePageNav(t))return;t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP)return this._activate(this._focusNextTab(this.options.active-1,!1)),!0;if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN)return this._activate(this._focusNextTab(this.options.active+1,!0)),!0},_findNextTab:function(t,n){function i(){return t>r&&(t=0),t<0&&(t=r),t}var r=this.tabs.length-1;while(e.inArray(i(),this.options.disabled)!==-1)t=n?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){if(e==="active"){this._activate(t);return}if(e==="disabled"){this._setupDisabled(t);return}this._super(e,t),e==="collapsible"&&(this.element.toggleClass("ui-tabs-collapsible",t),!t&&this.options.active===!1&&this._activate(0)),e==="event"&&this._setupEvents(t),e==="heightStyle"&&this._setupHeightStyle(t)},_tabId:function(e){return e.attr("aria-controls")||"ui-tabs-"+i()},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,n=this.tablist.children(":has(a[href])");t.disabled=e.map(n.filter(".ui-state-disabled"),function(e){return n.index(e)}),this._processTabs(),t.active===!1||!this.anchors.length?(t.active=!1,this.active=e()):this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist"),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(n,r){var i,o,u,a=e(r).uniqueId().attr("id"),f=e(r).closest("li"),l=f.attr("aria-controls");s(r)?(i=r.hash,o=t.element.find(t._sanitizeSelector(i))):(u=t._tabId(f),i="#"+u,o=t.element.find(i),o.length||(o=t._createPanel(u),o.insertAfter(t.panels[n-1]||t.tablist)),o.attr("aria-live","polite")),o.length&&(t.panels=t.panels.add(o)),l&&f.data("ui-tabs-aria-controls",l),f.attr({"aria-controls":i.substring(1),"aria-labelledby":a}),o.attr("aria-labelledby",a)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("
    ").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var n=0,r;r=this.tabs[n];n++)t===!0||e.inArray(n,t)!==-1?e(r).addClass("ui-state-disabled").attr("aria-disabled","true"):e(r).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var n={click:function(e){e.preventDefault()}};t&&e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(this.anchors,n),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var n,r,i=this.element.parent();t==="fill"?(e.support.minHeight||(r=i.css("overflow"),i.css("overflow","hidden")),n=i.height(),this.element.siblings(":visible").each(function(){var t=e(this),r=t.css("position");if(r==="absolute"||r==="fixed")return;n-=t.outerHeight(!0)}),r&&i.css("overflow",r),this.element.children().not(this.panels).each(function(){n-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,n-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):t==="auto"&&(n=0,this.panels.each(function(){n=Math.max(n,e(this).height("").height())}).height(n))},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i.closest("li"),o=s[0]===r[0],u=o&&n.collapsible,a=u?e():this._getPanelForTab(s),f=r.length?this._getPanelForTab(r):e(),l={oldTab:r,oldPanel:f,newTab:u?e():s,newPanel:a};t.preventDefault();if(s.hasClass("ui-state-disabled")||s.hasClass("ui-tabs-loading")||this.running||o&&!n.collapsible||this._trigger("beforeActivate",t,l)===!1)return;n.active=u?!1:this.tabs.index(s),this.active=o?e():s,this.xhr&&this.xhr.abort(),!f.length&&!a.length&&e.error("jQuery UI Tabs: Mismatching fragment identifier."),a.length&&this.load(this.tabs.index(s),t),this._toggle(t,l)},_toggle:function(t,n){function o(){r.running=!1,r._trigger("activate",t,n)}function u(){n.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),i.length&&r.options.show?r._show(i,r.options.show,o):(i.show(),o())}var r=this,i=n.newPanel,s=n.oldPanel;this.running=!0,s.length&&this.options.hide?this._hide(s,this.options.hide,function(){n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),s.hide(),u()),s.attr({"aria-expanded":"false","aria-hidden":"true"}),n.oldTab.attr("aria-selected","false"),i.length&&s.length?n.oldTab.attr("tabIndex",-1):i.length&&this.tabs.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),i.attr({"aria-expanded":"true","aria-hidden":"false"}),n.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(t){var n,r=this._findActive(t);if(r[0]===this.active[0])return;r.length||(r=this.active),n=r.find(".ui-tabs-anchor")[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return typeof e=="string"&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeData("href.tabs").removeData("load.tabs").removeUniqueId(),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),n=t.data("ui-tabs-aria-controls");n?t.attr("aria-controls",n):t.removeAttr("aria-controls")}),this.panels.show(),this.options.heightStyle!=="content"&&this.panels.css("height","")},enable:function(n){var r=this.options.disabled;if(r===!1)return;n===t?r=!1:(n=this._getIndex(n),e.isArray(r)?r=e.map(r,function(e){return e!==n?e:null}):r=e.map(this.tabs,function(e,t){return t!==n?t:null})),this._setupDisabled(r)},disable:function(n){var r=this.options.disabled;if(r===!0)return;if(n===t)r=!0;else{n=this._getIndex(n);if(e.inArray(n,r)!==-1)return;e.isArray(r)?r=e.merge([n],r).sort():r=[n]}this._setupDisabled(r)},load:function(t,n){t=this._getIndex(t);var r=this,i=this.tabs.eq(t),o=i.find(".ui-tabs-anchor"),u=this._getPanelForTab(i),a={tab:i,panel:u};if(s(o[0]))return;this.xhr=e.ajax(this._ajaxSettings(o,n,a)),this.xhr&&this.xhr.statusText!=="canceled"&&(i.addClass("ui-tabs-loading"),u.attr("aria-busy","true"),this.xhr.success(function(e){setTimeout(function(){u.html(e),r._trigger("load",n,a)},1)}).complete(function(e,t){setTimeout(function(){t==="abort"&&r.panels.stop(!1,!0),i.removeClass("ui-tabs-loading"),u.removeAttr("aria-busy"),e===r.xhr&&delete r.xhr},1)}))},_ajaxSettings:function(t,n,r){var i=this;return{url:t.attr("href"),beforeSend:function(t,s){return i._trigger("beforeLoad",n,e.extend({jqXHR:t,ajaxSettings:s},r))}}},_getPanelForTab:function(t){var n=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+n))}}),e.uiBackCompat!==!1&&(e.ui.tabs.prototype._ui=function(e,t){return{tab:e,panel:t,index:this.anchors.index(e)}},e.widget("ui.tabs",e.ui.tabs,{url:function(e,t){this.anchors.eq(e).attr("href",t)}}),e.widget("ui.tabs",e.ui.tabs,{options:{ajaxOptions:null,cache:!1},_create:function(){this._super();var t=this;this._on({tabsbeforeload:function(n,r){if(e.data(r.tab[0],"cache.tabs")){n.preventDefault();return}r.jqXHR.success(function(){t.options.cache&&e.data(r.tab[0],"cache.tabs",!0)})}})},_ajaxSettings:function(t,n,r){var i=this.options.ajaxOptions;return e.extend({},i,{error:function(e,t){try{i.error(e,t,r.tab.closest("li").index(),r.tab[0])}catch(n){}}},this._superApply(arguments))},_setOption:function(e,t){e==="cache"&&t===!1&&this.anchors.removeData("cache.tabs"),this._super(e,t)},_destroy:function(){this.anchors.removeData("cache.tabs"),this._super()},url:function(e){this.anchors.eq(e).removeData("cache.tabs"),this._superApply(arguments)}}),e.widget("ui.tabs",e.ui.tabs,{abort:function(){this.xhr&&this.xhr.abort()}}),e.widget("ui.tabs",e.ui.tabs,{options:{spinner:"Loading…"},_create:function(){this._super(),this._on({tabsbeforeload:function(e,t){if(e.target!==this.element[0]||!this.options.spinner)return;var n=t.tab.find("span"),r=n.html();n.html(this.options.spinner),t.jqXHR.complete(function(){n.html(r)})}})}}),e.widget("ui.tabs",e.ui.tabs,{options:{enable:null,disable:null},enable:function(t){var n=this.options,r;if(t&&n.disabled===!0||e.isArray(n.disabled)&&e.inArray(t,n.disabled)!==-1)r=!0;this._superApply(arguments),r&&this._trigger("enable",null,this._ui(this.anchors[t],this.panels[t]))},disable:function(t){var n=this.options,r;if(t&&n.disabled===!1||e.isArray(n.disabled)&&e.inArray(t,n.disabled)===-1)r=!0;this._superApply(arguments),r&&this._trigger("disable",null,this._ui(this.anchors[t],this.panels[t]))}}),e.widget("ui.tabs",e.ui.tabs,{options:{add:null,remove:null,tabTemplate:"
  • #{label}
  • "},add:function(n,r,i){i===t&&(i=this.anchors.length);var s,o,u=this.options,a=e(u.tabTemplate.replace(/#\{href\}/g,n).replace(/#\{label\}/g,r)),f=n.indexOf("#")?this._tabId(a):n.replace("#","");return a.addClass("ui-state-default ui-corner-top").data("ui-tabs-destroy",!0),a.attr("aria-controls",f),s=i>=this.tabs.length,o=this.element.find("#"+f),o.length||(o=this._createPanel(f),s?i>0?o.insertAfter(this.panels.eq(-1)):o.appendTo(this.element):o.insertBefore(this.panels[i])),o.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").hide(),s?a.appendTo(this.tablist):a.insertBefore(this.tabs[i]),u.disabled=e.map(u.disabled,function(e){return e>=i?++e:e}),this.refresh(),this.tabs.length===1&&u.active===!1&&this.option("active",0),this._trigger("add",null,this._ui(this.anchors[i],this.panels[i])),this},remove:function(t){t=this._getIndex(t);var n=this.options,r=this.tabs.eq(t).remove(),i=this._getPanelForTab(r).remove();return r.hasClass("ui-tabs-active")&&this.anchors.length>2&&this._activate(t+(t+1=t?--e:e}),this.refresh(),this._trigger("remove",null,this._ui(r.find("a")[0],i[0])),this}}),e.widget("ui.tabs",e.ui.tabs,{length:function(){return this.anchors.length}}),e.widget("ui.tabs",e.ui.tabs,{options:{idPrefix:"ui-tabs-"},_tabId:function(t){var n=t.is("li")?t.find("a[href]"):t;return n=n[0],e(n).closest("li").attr("aria-controls")||n.title&&n.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF\-]/g,"")||this.options.idPrefix+i()}}),e.widget("ui.tabs",e.ui.tabs,{options:{panelTemplate:"
    "},_createPanel:function(t){return e(this.options.panelTemplate).attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)}}),e.widget("ui.tabs",e.ui.tabs,{_create:function(){var e=this.options;e.active===null&&e.selected!==t&&(e.active=e.selected===-1?!1:e.selected),this._super(),e.selected=e.active,e.selected===!1&&(e.selected=-1)},_setOption:function(e,t){if(e!=="selected")return this._super(e,t);var n=this.options;this._super("active",t===-1?!1:t),n.selected=n.active,n.selected===!1&&(n.selected=-1)},_eventHandler:function(){this._superApply(arguments),this.options.selected=this.options.active,this.options.selected===!1&&(this.options.selected=-1)}}),e.widget("ui.tabs",e.ui.tabs,{options:{show:null,select:null},_create:function(){this._super(),this.options.active!==!1&&this._trigger("show",null,this._ui(this.active.find(".ui-tabs-anchor")[0],this._getPanelForTab(this.active)[0]))},_trigger:function(e,t,n){var r,i,s=this._superApply(arguments);return s?(e==="beforeActivate"?(r=n.newTab.length?n.newTab:n.oldTab,i=n.newPanel.length?n.newPanel:n.oldPanel,s=this._super("select",t,{tab:r.find(".ui-tabs-anchor")[0],panel:i[0],index:r.closest("li").index()})):e==="activate"&&n.newTab.length&&(s=this._super("show",t,{tab:n.newTab.find(".ui-tabs-anchor")[0],panel:n.newPanel[0],index:n.newTab.closest("li").index()})),s):!1}}),e.widget("ui.tabs",e.ui.tabs,{select:function(e){e=this._getIndex(e);if(e===-1){if(!this.options.collapsible||this.options.selected===-1)return;e=this.options.selected}this.anchors.eq(e).trigger(this.options.event+this.eventNamespace)}}),function(){var t=0;e.widget("ui.tabs",e.ui.tabs,{options:{cookie:null},_create:function(){var e=this.options,t;e.active==null&&e.cookie&&(t=parseInt(this._cookie(),10),t===-1&&(t=!1),e.active=t),this._super()},_cookie:function(n){var r=[this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+ ++t)];return arguments.length&&(r.push(n===!1?-1:n),r.push(this.options.cookie)),e.cookie.apply(null,r)},_refresh:function(){this._super(),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_eventHandler:function(){this._superApply(arguments),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_destroy:function(){this._super(),this.options.cookie&&this._cookie(null,this.options.cookie)}})}(),e.widget("ui.tabs",e.ui.tabs,{_trigger:function(t,n,r){var i=e.extend({},r);return t==="load"&&(i.panel=i.panel[0],i.tab=i.tab.find(".ui-tabs-anchor")[0]),this._super(t,n,i)}}),e.widget("ui.tabs",e.ui.tabs,{options:{fx:null},_getFx:function(){var t,n,r=this.options.fx;return r&&(e.isArray(r)?(t=r[0],n=r[1]):t=n=r),r?{show:n,hide:t}:null},_toggle:function(e,t){function o(){n.running=!1,n._trigger("activate",e,t)}function u(){t.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),r.length&&s.show?r.animate(s.show,s.show.duration,function(){o()}):(r.show(),o())}var n=this,r=t.newPanel,i=t.oldPanel,s=this._getFx();if(!s)return this._super(e,t);n.running=!0,i.length&&s.hide?i.animate(s.hide,s.hide.duration,function(){t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),i.hide(),u())}}))})(jQuery);(function(e){function n(t,n){var r=(t.attr("aria-describedby")||"").split(/\s+/);r.push(n),t.data("ui-tooltip-id",n).attr("aria-describedby",e.trim(r.join(" ")))}function r(t){var n=t.data("ui-tooltip-id"),r=(t.attr("aria-describedby")||"").split(/\s+/),i=e.inArray(n,r);i!==-1&&r.splice(i,1),t.removeData("ui-tooltip-id"),r=e.trim(r.join(" ")),r?t.attr("aria-describedby",r):t.removeAttr("aria-describedby")}var t=0;e.widget("ui.tooltip",{version:"1.9.2",options:{content:function(){return e(this).attr("title")},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable()},_setOption:function(t,n){var r=this;if(t==="disabled"){this[n?"_disable":"_enable"](),this.options[t]=n;return}this._super(t,n),t==="content"&&e.each(this.tooltips,function(e,t){r._updateContent(t)})},_disable:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0)}),this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).attr("title","")})},_enable:function(){this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var n=this,r=e(t?t.target:this.element).closest(this.options.items);if(!r.length||r.data("ui-tooltip-id"))return;r.attr("title")&&r.data("ui-tooltip-title",r.attr("title")),r.data("ui-tooltip-open",!0),t&&t.type==="mouseover"&&r.parents().each(function(){var t=e(this),r;t.data("ui-tooltip-open")&&(r=e.Event("blur"),r.target=r.currentTarget=this,n.close(r,!0)),t.attr("title")&&(t.uniqueId(),n.parents[this.id]={element:this,title:t.attr("title")},t.attr("title",""))}),this._updateContent(r,t)},_updateContent:function(e,t){var n,r=this.options.content,i=this,s=t?t.type:null;if(typeof r=="string")return this._open(t,e,r);n=r.call(e[0],function(n){if(!e.data("ui-tooltip-open"))return;i._delay(function(){t&&(t.type=s),this._open(t,e,n)})}),n&&this._open(t,e,n)},_open:function(t,r,i){function f(e){a.of=e;if(s.is(":hidden"))return;s.position(a)}var s,o,u,a=e.extend({},this.options.position);if(!i)return;s=this._find(r);if(s.length){s.find(".ui-tooltip-content").html(i);return}r.is("[title]")&&(t&&t.type==="mouseover"?r.attr("title",""):r.removeAttr("title")),s=this._tooltip(r),n(r,s.attr("id")),s.find(".ui-tooltip-content").html(i),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:f}),f(t)):s.position(e.extend({of:r},this.options.position)),s.hide(),this._show(s,this.options.show),this.options.show&&this.options.show.delay&&(u=setInterval(function(){s.is(":visible")&&(f(a.of),clearInterval(u))},e.fx.interval)),this._trigger("open",t,{tooltip:s}),o={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var n=e.Event(t);n.currentTarget=r[0],this.close(n,!0)}},remove:function(){this._removeTooltip(s)}};if(!t||t.type==="mouseover")o.mouseleave="close";if(!t||t.type==="focusin")o.focusout="close";this._on(!0,r,o)},close:function(t){var n=this,i=e(t?t.currentTarget:this.element),s=this._find(i);if(this.closing)return;i.data("ui-tooltip-title")&&i.attr("title",i.data("ui-tooltip-title")),r(i),s.stop(!0),this._hide(s,this.options.hide,function(){n._removeTooltip(e(this))}),i.removeData("ui-tooltip-open"),this._off(i,"mouseleave focusout keyup"),i[0]!==this.element[0]&&this._off(i,"remove"),this._off(this.document,"mousemove"),t&&t.type==="mouseleave"&&e.each(this.parents,function(t,r){e(r.element).attr("title",r.title),delete n.parents[t]}),this.closing=!0,this._trigger("close",t,{tooltip:s}),this.closing=!1},_tooltip:function(n){var r="ui-tooltip-"+t++,i=e("
    ").attr({id:r,role:"tooltip"}).addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||""));return e("
    ").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),e.fn.bgiframe&&i.bgiframe(),this.tooltips[r]=n,i},_find:function(t){var n=t.data("ui-tooltip-id");return n?e("#"+n):e()},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0),e("#"+n).remove(),r.data("ui-tooltip-title")&&(r.attr("title",r.data("ui-tooltip-title")),r.removeData("ui-tooltip-title"))})}})})(jQuery);jQuery.effects||function(e,t){var n=e.uiBackCompat!==!1,r="ui-effects-";e.effects={effect:{}},function(t,n){function p(e,t,n){var r=a[t.type]||{};return e==null?n||!t.def?null:t.def:(e=r.floor?~~e:parseFloat(e),isNaN(e)?t.def:r.mod?(e+r.mod)%r.mod:0>e?0:r.max")[0],c,h=t.each;l.style.cssText="background-color:rgba(1,1,1,.5)",f.rgba=l.style.backgroundColor.indexOf("rgba")>-1,h(u,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),o.fn=t.extend(o.prototype,{parse:function(r,i,s,a){if(r===n)return this._rgba=[null,null,null,null],this;if(r.jquery||r.nodeType)r=t(r).css(i),i=n;var f=this,l=t.type(r),v=this._rgba=[];i!==n&&(r=[r,i,s,a],l="array");if(l==="string")return this.parse(d(r)||c._default);if(l==="array")return h(u.rgba.props,function(e,t){v[t.idx]=p(r[t.idx],t)}),this;if(l==="object")return r instanceof o?h(u,function(e,t){r[t.cache]&&(f[t.cache]=r[t.cache].slice())}):h(u,function(t,n){var i=n.cache;h(n.props,function(e,t){if(!f[i]&&n.to){if(e==="alpha"||r[e]==null)return;f[i]=n.to(f._rgba)}f[i][t.idx]=p(r[e],t,!0)}),f[i]&&e.inArray(null,f[i].slice(0,3))<0&&(f[i][3]=1,n.from&&(f._rgba=n.from(f[i])))}),this},is:function(e){var t=o(e),n=!0,r=this;return h(u,function(e,i){var s,o=t[i.cache];return o&&(s=r[i.cache]||i.to&&i.to(r._rgba)||[],h(i.props,function(e,t){if(o[t.idx]!=null)return n=o[t.idx]===s[t.idx],n})),n}),n},_space:function(){var e=[],t=this;return h(u,function(n,r){t[r.cache]&&e.push(n)}),e.pop()},transition:function(e,t){var n=o(e),r=n._space(),i=u[r],s=this.alpha()===0?o("transparent"):this,f=s[i.cache]||i.to(s._rgba),l=f.slice();return n=n[i.cache],h(i.props,function(e,r){var i=r.idx,s=f[i],o=n[i],u=a[r.type]||{};if(o===null)return;s===null?l[i]=o:(u.mod&&(o-s>u.mod/2?s+=u.mod:s-o>u.mod/2&&(s-=u.mod)),l[i]=p((o-s)*t+s,r))}),this[r](l)},blend:function(e){if(this._rgba[3]===1)return this;var n=this._rgba.slice(),r=n.pop(),i=o(e)._rgba;return o(t.map(n,function(e,t){return(1-r)*i[t]+r*e}))},toRgbaString:function(){var e="rgba(",n=t.map(this._rgba,function(e,t){return e==null?t>2?1:0:e});return n[3]===1&&(n.pop(),e="rgb("),e+n.join()+")"},toHslaString:function(){var e="hsla(",n=t.map(this.hsla(),function(e,t){return e==null&&(e=t>2?1:0),t&&t<3&&(e=Math.round(e*100)+"%"),e});return n[3]===1&&(n.pop(),e="hsl("),e+n.join()+")"},toHexString:function(e){var n=this._rgba.slice(),r=n.pop();return e&&n.push(~~(r*255)),"#"+t.map(n,function(e){return e=(e||0).toString(16),e.length===1?"0"+e:e}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}}),o.fn.parse.prototype=o.fn,u.hsla.to=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/255,n=e[1]/255,r=e[2]/255,i=e[3],s=Math.max(t,n,r),o=Math.min(t,n,r),u=s-o,a=s+o,f=a*.5,l,c;return o===s?l=0:t===s?l=60*(n-r)/u+360:n===s?l=60*(r-t)/u+120:l=60*(t-n)/u+240,f===0||f===1?c=f:f<=.5?c=u/a:c=u/(2-a),[Math.round(l)%360,c,f,i==null?1:i]},u.hsla.from=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/360,n=e[1],r=e[2],i=e[3],s=r<=.5?r*(1+n):r+n-r*n,o=2*r-s;return[Math.round(v(o,s,t+1/3)*255),Math.round(v(o,s,t)*255),Math.round(v(o,s,t-1/3)*255),i]},h(u,function(e,r){var s=r.props,u=r.cache,a=r.to,f=r.from;o.fn[e]=function(e){a&&!this[u]&&(this[u]=a(this._rgba));if(e===n)return this[u].slice();var r,i=t.type(e),l=i==="array"||i==="object"?e:arguments,c=this[u].slice();return h(s,function(e,t){var n=l[i==="object"?e:t.idx];n==null&&(n=c[t.idx]),c[t.idx]=p(n,t)}),f?(r=o(f(c)),r[u]=c,r):o(c)},h(s,function(n,r){if(o.fn[n])return;o.fn[n]=function(s){var o=t.type(s),u=n==="alpha"?this._hsla?"hsla":"rgba":e,a=this[u](),f=a[r.idx],l;return o==="undefined"?f:(o==="function"&&(s=s.call(this,f),o=t.type(s)),s==null&&r.empty?this:(o==="string"&&(l=i.exec(s),l&&(s=f+parseFloat(l[2])*(l[1]==="+"?1:-1))),a[r.idx]=s,this[u](a)))}})}),h(r,function(e,n){t.cssHooks[n]={set:function(e,r){var i,s,u="";if(t.type(r)!=="string"||(i=d(r))){r=o(i||r);if(!f.rgba&&r._rgba[3]!==1){s=n==="backgroundColor"?e.parentNode:e;while((u===""||u==="transparent")&&s&&s.style)try{u=t.css(s,"backgroundColor"),s=s.parentNode}catch(a){}r=r.blend(u&&u!=="transparent"?u:"_default")}r=r.toRgbaString()}try{e.style[n]=r}catch(l){}}},t.fx.step[n]=function(e){e.colorInit||(e.start=o(e.elem,n),e.end=o(e.end),e.colorInit=!0),t.cssHooks[n].set(e.elem,e.start.transition(e.end,e.pos))}}),t.cssHooks.borderColor={expand:function(e){var t={};return h(["Top","Right","Bottom","Left"],function(n,r){t["border"+r+"Color"]=e}),t}},c=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(){var t=this.ownerDocument.defaultView?this.ownerDocument.defaultView.getComputedStyle(this,null):this.currentStyle,n={},r,i;if(t&&t.length&&t[0]&&t[t[0]]){i=t.length;while(i--)r=t[i],typeof t[r]=="string"&&(n[e.camelCase(r)]=t[r])}else for(r in t)typeof t[r]=="string"&&(n[r]=t[r]);return n}function s(t,n){var i={},s,o;for(s in n)o=n[s],t[s]!==o&&!r[s]&&(e.fx.step[s]||!isNaN(parseFloat(o)))&&(i[s]=o);return i}var n=["add","remove","toggle"],r={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,n){e.fx.step[n]=function(e){if(e.end!=="none"&&!e.setAttr||e.pos===1&&!e.setAttr)jQuery.style(e.elem,n,e.end),e.setAttr=!0}}),e.effects.animateClass=function(t,r,o,u){var a=e.speed(r,o,u);return this.queue(function(){var r=e(this),o=r.attr("class")||"",u,f=a.children?r.find("*").andSelf():r;f=f.map(function(){var t=e(this);return{el:t,start:i.call(this)}}),u=function(){e.each(n,function(e,n){t[n]&&r[n+"Class"](t[n])})},u(),f=f.map(function(){return this.end=i.call(this.el[0]),this.diff=s(this.start,this.end),this}),r.attr("class",o),f=f.map(function(){var t=this,n=e.Deferred(),r=jQuery.extend({},a,{queue:!1,complete:function(){n.resolve(t)}});return this.el.animate(this.diff,r),n.promise()}),e.when.apply(e,f.get()).done(function(){u(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),a.complete.call(r[0])})})},e.fn.extend({_addClass:e.fn.addClass,addClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{add:t},n,r,i):this._addClass(t)},_removeClass:e.fn.removeClass,removeClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{remove:t},n,r,i):this._removeClass(t)},_toggleClass:e.fn.toggleClass,toggleClass:function(n,r,i,s,o){return typeof r=="boolean"||r===t?i?e.effects.animateClass.call(this,r?{add:n}:{remove:n},i,s,o):this._toggleClass(n,r):e.effects.animateClass.call(this,{toggle:n},r,i,s)},switchClass:function(t,n,r,i,s){return e.effects.animateClass.call(this,{add:n,remove:t},r,i,s)}})}(),function(){function i(t,n,r,i){e.isPlainObject(t)&&(n=t,t=t.effect),t={effect:t},n==null&&(n={}),e.isFunction(n)&&(i=n,r=null,n={});if(typeof n=="number"||e.fx.speeds[n])i=r,r=n,n={};return e.isFunction(r)&&(i=r,r=null),n&&e.extend(t,n),r=r||n.duration,t.duration=e.fx.off?0:typeof r=="number"?r:r in e.fx.speeds?e.fx.speeds[r]:e.fx.speeds._default,t.complete=i||n.complete,t}function s(t){return!t||typeof t=="number"||e.fx.speeds[t]?!0:typeof t=="string"&&!e.effects.effect[t]?n&&e.effects[t]?!1:!0:!1}e.extend(e.effects,{version:"1.9.2",save:function(e,t){for(var n=0;n
    ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),i={width:t.width(),height:t.height()},s=document.activeElement;try{s.id}catch(o){s=document.body}return t.wrap(r),(t[0]===s||e.contains(t[0],s))&&e(s).focus(),r=t.parent(),t.css("position")==="static"?(r.css({position:"relative"}),t.css({position:"relative"})):(e.extend(n,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,r){n[r]=t.css(r),isNaN(parseInt(n[r],10))&&(n[r]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(i),r.css(n).show()},removeWrapper:function(t){var n=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===n||e.contains(t[0],n))&&e(n).focus()),t},setTransition:function(t,n,r,i){return i=i||{},e.each(n,function(e,n){var s=t.cssUnit(n);s[0]>0&&(i[n]=s[0]*r+s[1])}),i}}),e.fn.extend({effect:function(){function a(n){function u(){e.isFunction(i)&&i.call(r[0]),e.isFunction(n)&&n()}var r=e(this),i=t.complete,s=t.mode;(r.is(":hidden")?s==="hide":s==="show")?u():o.call(r[0],t,u)}var t=i.apply(this,arguments),r=t.mode,s=t.queue,o=e.effects.effect[t.effect],u=!o&&n&&e.effects[t.effect];return e.fx.off||!o&&!u?r?this[r](t.duration,t.complete):this.each(function(){t.complete&&t.complete.call(this)}):o?s===!1?this.each(a):this.queue(s||"fx",a):u.call(this,{options:t,duration:t.duration,callback:t.complete,mode:t.mode})},_show:e.fn.show,show:function(e){if(s(e))return this._show.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="show",this.effect.call(this,t)},_hide:e.fn.hide,hide:function(e){if(s(e))return this._hide.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="hide",this.effect.call(this,t)},__toggle:e.fn.toggle,toggle:function(t){if(s(t)||typeof t=="boolean"||e.isFunction(t))return this.__toggle.apply(this,arguments);var n=i.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)},cssUnit:function(t){var n=this.css(t),r=[];return e.each(["em","px","%","pt"],function(e,t){n.indexOf(t)>0&&(r=[parseFloat(n),t])}),r}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,n){t[n]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return e===0||e===1?e:-Math.pow(2,8*(e-1))*Math.sin(((e-1)*80-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){var t,n=4;while(e<((t=Math.pow(2,--n))-1)/11);return 1/Math.pow(4,3-n)-7.5625*Math.pow((t*3-2)/22-e,2)}}),e.each(t,function(t,n){e.easing["easeIn"+t]=n,e.easing["easeOut"+t]=function(e){return 1-n(1-e)},e.easing["easeInOut"+t]=function(e){return e<.5?n(e*2)/2:1-n(e*-2+2)/2}})}()}(jQuery);(function(e,t){var n=/up|down|vertical/,r=/up|left|vertical|horizontal/;e.effects.effect.blind=function(t,i){var s=e(this),o=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(s,t.mode||"hide"),a=t.direction||"up",f=n.test(a),l=f?"height":"width",c=f?"top":"left",h=r.test(a),p={},d=u==="show",v,m,g;s.parent().is(".ui-effects-wrapper")?e.effects.save(s.parent(),o):e.effects.save(s,o),s.show(),v=e.effects.createWrapper(s).css({overflow:"hidden"}),m=v[l](),g=parseFloat(v.css(c))||0,p[l]=d?m:0,h||(s.css(f?"bottom":"right",0).css(f?"top":"left","auto").css({position:"absolute"}),p[c]=d?g:m+g),d&&(v.css(l,0),h||v.css(c,g+m)),v.animate(p,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){u==="hide"&&s.hide(),e.effects.restore(s,o),e.effects.removeWrapper(s),i()}})}})(jQuery);(function(e,t){e.effects.effect.bounce=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=s==="hide",u=s==="show",a=t.direction||"up",f=t.distance,l=t.times||5,c=l*2+(u||o?1:0),h=t.duration/c,p=t.easing,d=a==="up"||a==="down"?"top":"left",v=a==="up"||a==="left",m,g,y,b=r.queue(),w=b.length;(u||o)&&i.push("opacity"),e.effects.save(r,i),r.show(),e.effects.createWrapper(r),f||(f=r[d==="top"?"outerHeight":"outerWidth"]()/3),u&&(y={opacity:1},y[d]=0,r.css("opacity",0).css(d,v?-f*2:f*2).animate(y,h,p)),o&&(f/=Math.pow(2,l-1)),y={},y[d]=0;for(m=0;m1&&b.splice.apply(b,[1,0].concat(b.splice(w,c+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.clip=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"vertical",a=u==="vertical",f=a?"height":"width",l=a?"top":"left",c={},h,p,d;e.effects.save(r,i),r.show(),h=e.effects.createWrapper(r).css({overflow:"hidden"}),p=r[0].tagName==="IMG"?h:r,d=p[f](),o&&(p.css(f,0),p.css(l,d/2)),c[f]=o?d:0,c[l]=o?0:d/2,p.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o||r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.drop=function(t,n){var r=e(this),i=["position","top","bottom","left","right","opacity","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left"?"pos":"neg",l={opacity:o?1:0},c;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),c=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0)/2,o&&r.css("opacity",0).css(a,f==="pos"?-c:c),l[a]=(o?f==="pos"?"+=":"-=":f==="pos"?"-=":"+=")+c,r.animate(l,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.explode=function(t,n){function y(){c.push(this),c.length===r*i&&b()}function b(){s.css({visibility:"visible"}),e(c).remove(),u||s.hide(),n()}var r=t.pieces?Math.round(Math.sqrt(t.pieces)):3,i=r,s=e(this),o=e.effects.setMode(s,t.mode||"hide"),u=o==="show",a=s.show().css("visibility","hidden").offset(),f=Math.ceil(s.outerWidth()/i),l=Math.ceil(s.outerHeight()/r),c=[],h,p,d,v,m,g;for(h=0;h
    ").css({position:"absolute",visibility:"visible",left:-p*f,top:-h*l}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:f,height:l,left:d+(u?m*f:0),top:v+(u?g*l:0),opacity:u?0:1}).animate({left:d+(u?0:m*f),top:v+(u?0:g*l),opacity:u?1:0},t.duration||500,t.easing,y)}}})(jQuery);(function(e,t){e.effects.effect.fade=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"toggle");r.animate({opacity:i},{queue:!1,duration:t.duration,easing:t.easing,complete:n})}})(jQuery);(function(e,t){e.effects.effect.fold=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=s==="hide",a=t.size||15,f=/([0-9]+)%/.exec(a),l=!!t.horizFirst,c=o!==l,h=c?["width","height"]:["height","width"],p=t.duration/2,d,v,m={},g={};e.effects.save(r,i),r.show(),d=e.effects.createWrapper(r).css({overflow:"hidden"}),v=c?[d.width(),d.height()]:[d.height(),d.width()],f&&(a=parseInt(f[1],10)/100*v[u?0:1]),o&&d.css(l?{height:0,width:a}:{height:a,width:0}),m[h[0]]=o?v[0]:a,g[h[1]]=o?v[1]:0,d.animate(m,p,t.easing).animate(g,p,t.easing,function(){u&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()})}})(jQuery);(function(e,t){e.effects.effect.highlight=function(t,n){var r=e(this),i=["backgroundImage","backgroundColor","opacity"],s=e.effects.setMode(r,t.mode||"show"),o={backgroundColor:r.css("backgroundColor")};s==="hide"&&(o.opacity=0),e.effects.save(r,i),r.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),n()}})}})(jQuery);(function(e,t){e.effects.effect.pulsate=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"show"),s=i==="show",o=i==="hide",u=s||i==="hide",a=(t.times||5)*2+(u?1:0),f=t.duration/a,l=0,c=r.queue(),h=c.length,p;if(s||!r.is(":visible"))r.css("opacity",0).show(),l=1;for(p=1;p1&&c.splice.apply(c,[1,0].concat(c.splice(h,a+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.puff=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"hide"),s=i==="hide",o=parseInt(t.percent,10)||150,u=o/100,a={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:i,complete:n,percent:s?o:100,from:s?a:{height:a.height*u,width:a.width*u,outerHeight:a.outerHeight*u,outerWidth:a.outerWidth*u}}),r.effect(t)},e.effects.effect.scale=function(t,n){var r=e(this),i=e.extend(!0,{},t),s=e.effects.setMode(r,t.mode||"effect"),o=parseInt(t.percent,10)||(parseInt(t.percent,10)===0?0:s==="hide"?0:100),u=t.direction||"both",a=t.origin,f={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()},l={y:u!=="horizontal"?o/100:1,x:u!=="vertical"?o/100:1};i.effect="size",i.queue=!1,i.complete=n,s!=="effect"&&(i.origin=a||["middle","center"],i.restore=!0),i.from=t.from||(s==="show"?{height:0,width:0,outerHeight:0,outerWidth:0}:f),i.to={height:f.height*l.y,width:f.width*l.x,outerHeight:f.outerHeight*l.y,outerWidth:f.outerWidth*l.x},i.fade&&(s==="show"&&(i.from.opacity=0,i.to.opacity=1),s==="hide"&&(i.from.opacity=1,i.to.opacity=0)),r.effect(i)},e.effects.effect.size=function(t,n){var r,i,s,o=e(this),u=["position","top","bottom","left","right","width","height","overflow","opacity"],a=["position","top","bottom","left","right","overflow","opacity"],f=["width","height","overflow"],l=["fontSize"],c=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],h=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),d=t.restore||p!=="effect",v=t.scale||"both",m=t.origin||["middle","center"],g=o.css("position"),y=d?u:a,b={height:0,width:0,outerHeight:0,outerWidth:0};p==="show"&&o.show(),r={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},t.mode==="toggle"&&p==="show"?(o.from=t.to||b,o.to=t.from||r):(o.from=t.from||(p==="show"?b:r),o.to=t.to||(p==="hide"?b:r)),s={from:{y:o.from.height/r.height,x:o.from.width/r.width},to:{y:o.to.height/r.height,x:o.to.width/r.width}};if(v==="box"||v==="both")s.from.y!==s.to.y&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,s.from.y,o.from),o.to=e.effects.setTransition(o,c,s.to.y,o.to)),s.from.x!==s.to.x&&(y=y.concat(h),o.from=e.effects.setTransition(o,h,s.from.x,o.from),o.to=e.effects.setTransition(o,h,s.to.x,o.to));(v==="content"||v==="both")&&s.from.y!==s.to.y&&(y=y.concat(l).concat(f),o.from=e.effects.setTransition(o,l,s.from.y,o.from),o.to=e.effects.setTransition(o,l,s.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),m&&(i=e.effects.getBaseline(m,r),o.from.top=(r.outerHeight-o.outerHeight())*i.y,o.from.left=(r.outerWidth-o.outerWidth())*i.x,o.to.top=(r.outerHeight-o.to.outerHeight)*i.y,o.to.left=(r.outerWidth-o.to.outerWidth)*i.x),o.css(o.from);if(v==="content"||v==="both")c=c.concat(["marginTop","marginBottom"]).concat(l),h=h.concat(["marginLeft","marginRight"]),f=u.concat(c).concat(h),o.find("*[width]").each(function(){var n=e(this),r={height:n.height(),width:n.width(),outerHeight:n.outerHeight(),outerWidth:n.outerWidth()};d&&e.effects.save(n,f),n.from={height:r.height*s.from.y,width:r.width*s.from.x,outerHeight:r.outerHeight*s.from.y,outerWidth:r.outerWidth*s.from.x},n.to={height:r.height*s.to.y,width:r.width*s.to.x,outerHeight:r.height*s.to.y,outerWidth:r.width*s.to.x},s.from.y!==s.to.y&&(n.from=e.effects.setTransition(n,c,s.from.y,n.from),n.to=e.effects.setTransition(n,c,s.to.y,n.to)),s.from.x!==s.to.x&&(n.from=e.effects.setTransition(n,h,s.from.x,n.from),n.to=e.effects.setTransition(n,h,s.to.x,n.to)),n.css(n.from),n.animate(n.to,t.duration,t.easing,function(){d&&e.effects.restore(n,f)})});o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o.to.opacity===0&&o.css("opacity",o.from.opacity),p==="hide"&&o.hide(),e.effects.restore(o,y),d||(g==="static"?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,n){var r=parseInt(n,10),i=e?o.to.left:o.to.top;return n==="auto"?i+"px":r+i+"px"})})),e.effects.removeWrapper(o),n()}})}})(jQuery);(function(e,t){e.effects.effect.shake=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=t.direction||"left",u=t.distance||20,a=t.times||3,f=a*2+1,l=Math.round(t.duration/f),c=o==="up"||o==="down"?"top":"left",h=o==="up"||o==="left",p={},d={},v={},m,g=r.queue(),y=g.length;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),p[c]=(h?"-=":"+=")+u,d[c]=(h?"+=":"-=")+u*2,v[c]=(h?"-=":"+=")+u*2,r.animate(p,l,t.easing);for(m=1;m1&&g.splice.apply(g,[1,0].concat(g.splice(y,f+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.slide=function(t,n){var r=e(this),i=["position","top","bottom","left","right","width","height"],s=e.effects.setMode(r,t.mode||"show"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left",l,c={};e.effects.save(r,i),r.show(),l=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(r).css({overflow:"hidden"}),o&&r.css(a,f?isNaN(l)?"-"+l:-l:l),c[a]=(o?f?"+=":"-=":f?"-=":"+=")+l,r.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.transfer=function(t,n){var r=e(this),i=e(t.to),s=i.css("position")==="fixed",o=e("body"),u=s?o.scrollTop():0,a=s?o.scrollLeft():0,f=i.offset(),l={top:f.top-u,left:f.left-a,height:i.innerHeight(),width:i.innerWidth()},c=r.offset(),h=e('
    ').appendTo(document.body).addClass(t.className).css({top:c.top-u,left:c.left-a,height:r.innerHeight(),width:r.innerWidth(),position:s?"fixed":"absolute"}).animate(l,t.duration,t.easing,function(){h.remove(),n()})}})(jQuery); + +/* JQuery UJS 2.0.3 */ +(function(a,b){var c=function(){var b=a(document).data("events");return b&&b.click&&a.grep(b.click,function(a){return a.namespace==="rails"}).length};if(c()){a.error("jquery-ujs has already been loaded!")}var d;a.rails=d={linkClickSelector:"a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]",inputChangeSelector:"select[data-remote], input[data-remote], textarea[data-remote]",formSubmitSelector:"form",formInputClickSelector:"form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])",disableSelector:"input[data-disable-with], button[data-disable-with], textarea[data-disable-with]",enableSelector:"input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled",requiredInputSelector:"input[name][required]:not([disabled]),textarea[name][required]:not([disabled])",fileInputSelector:"input:file",linkDisableSelector:"a[data-disable-with]",CSRFProtection:function(b){var c=a('meta[name="csrf-token"]').attr("content");if(c)b.setRequestHeader("X-CSRF-Token",c)},fire:function(b,c,d){var e=a.Event(c);b.trigger(e,d);return e.result!==false},confirm:function(a){return confirm(a)},ajax:function(b){return a.ajax(b)},href:function(a){return a.attr("href")},handleRemote:function(c){var e,f,g,h,i,j,k,l;if(d.fire(c,"ajax:before")){h=c.data("cross-domain");i=h===b?null:h;j=c.data("with-credentials")||null;k=c.data("type")||a.ajaxSettings&&a.ajaxSettings.dataType;if(c.is("form")){e=c.attr("method");f=c.attr("action");g=c.serializeArray();var m=c.data("ujs:submit-button");if(m){g.push(m);c.data("ujs:submit-button",null)}}else if(c.is(d.inputChangeSelector)){e=c.data("method");f=c.data("url");g=c.serialize();if(c.data("params"))g=g+"&"+c.data("params")}else{e=c.data("method");f=d.href(c);g=c.data("params")||null}l={type:e||"GET",data:g,dataType:k,beforeSend:function(a,e){if(e.dataType===b){a.setRequestHeader("accept","*/*;q=0.5, "+e.accepts.script)}return d.fire(c,"ajax:beforeSend",[a,e])},success:function(a,b,d){c.trigger("ajax:success",[a,b,d])},complete:function(a,b){c.trigger("ajax:complete",[a,b])},error:function(a,b,d){c.trigger("ajax:error",[a,b,d])},xhrFields:{withCredentials:j},crossDomain:i};if(f){l.url=f}var n=d.ajax(l);c.trigger("ajax:send",n);return n}else{return false}},handleMethod:function(c){var e=d.href(c),f=c.data("method"),g=c.attr("target"),h=a("meta[name=csrf-token]").attr("content"),i=a("meta[name=csrf-param]").attr("content"),j=a('
    '),k='';if(i!==b&&h!==b){k+=''}if(g){j.attr("target",g)}j.hide().append(k).appendTo("body");j.submit()},disableFormElements:function(b){b.find(d.disableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";b.data("ujs:enable-with",b[c]());b[c](b.data("disable-with"));b.prop("disabled",true)})},enableFormElements:function(b){b.find(d.enableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";if(b.data("ujs:enable-with"))b[c](b.data("ujs:enable-with"));b.prop("disabled",false)})},allowAction:function(a){var b=a.data("confirm"),c=false,e;if(!b){return true}if(d.fire(a,"confirm")){c=d.confirm(b);e=d.fire(a,"confirm:complete",[c])}return c&&e},blankInputs:function(b,c,d){var e=a(),f,g,h=c||"input,textarea";b.find(h).each(function(){f=a(this);g=f.is(":checkbox,:radio")?f.is(":checked"):f.val();if(g==!!d){e=e.add(f)}});return e.length?e:false},nonBlankInputs:function(a,b){return d.blankInputs(a,b,true)},stopEverything:function(b){a(b.target).trigger("ujs:everythingStopped");b.stopImmediatePropagation();return false},callFormSubmitBindings:function(c,d){var e=c.data("events"),f=true;if(e!==b&&e["submit"]!==b){a.each(e["submit"],function(a,b){if(typeof b.handler==="function")return f=b.handler(d)})}return f},disableElement:function(a){a.data("ujs:enable-with",a.html());a.html(a.data("disable-with"));a.bind("click.railsDisable",function(a){return d.stopEverything(a)})},enableElement:function(a){if(a.data("ujs:enable-with")!==b){a.html(a.data("ujs:enable-with"));a.data("ujs:enable-with",false)}a.unbind("click.railsDisable")}};if(d.fire(a(document),"rails:attachBindings")){a.ajaxPrefilter(function(a,b,c){if(!a.crossDomain){d.CSRFProtection(c)}});a(document).delegate(d.linkDisableSelector,"ajax:complete",function(){d.enableElement(a(this))});a(document).delegate(d.linkClickSelector,"click.rails",function(c){var e=a(this),f=e.data("method"),g=e.data("params");if(!d.allowAction(e))return d.stopEverything(c);if(e.is(d.linkDisableSelector))d.disableElement(e);if(e.data("remote")!==b){if((c.metaKey||c.ctrlKey)&&(!f||f==="GET")&&!g){return true}if(d.handleRemote(e)===false){d.enableElement(e)}return false}else if(e.data("method")){d.handleMethod(e);return false}});a(document).delegate(d.inputChangeSelector,"change.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);d.handleRemote(c);return false});a(document).delegate(d.formSubmitSelector,"submit.rails",function(c){var e=a(this),f=e.data("remote")!==b,g=d.blankInputs(e,d.requiredInputSelector),h=d.nonBlankInputs(e,d.fileInputSelector);if(!d.allowAction(e))return d.stopEverything(c);if(g&&e.attr("novalidate")==b&&d.fire(e,"ajax:aborted:required",[g])){return d.stopEverything(c)}if(f){if(h){setTimeout(function(){d.disableFormElements(e)},13);return d.fire(e,"ajax:aborted:file",[h])}if(!a.support.submitBubbles&&a().jquery<"1.7"&&d.callFormSubmitBindings(e,c)===false)return d.stopEverything(c);d.handleRemote(e);return false}else{setTimeout(function(){d.disableFormElements(e)},13)}});a(document).delegate(d.formInputClickSelector,"click.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);var e=c.attr("name"),f=e?{name:e,value:c.val()}:null;c.closest("form").data("ujs:submit-button",f)});a(document).delegate(d.formSubmitSelector,"ajax:beforeSend.rails",function(b){if(this==b.target)d.disableFormElements(a(this))});a(document).delegate(d.formSubmitSelector,"ajax:complete.rails",function(b){if(this==b.target)d.enableFormElements(a(this))});a(function(){csrf_token=a("meta[name=csrf-token]").attr("content");csrf_param=a("meta[name=csrf-param]").attr("content");a('form input[name="'+csrf_param+'"]').val(csrf_token)})}})(jQuery) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cc/cc116c68789b7e01bd1de7433bc9b662e6a41e18.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cc/cc116c68789b7e01bd1de7433bc9b662e6a41e18.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,10 @@ +<%= title [l(:label_plugins), {:controller => 'admin', :action => 'plugins'}], @plugin.name %> + +
    +<%= form_tag({:action => 'plugin'}) do %> +
    +<%= render :partial => @partial, :locals => {:settings => @settings}%> +
    +<%= submit_tag l(:button_apply) %> +<% end %> +
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cc/cca369f9be126f556b2935f2f589ff1e46939064.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cc/cca369f9be126f556b2935f2f589ff1e46939064.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,3 @@ +

    <%=l(:label_news_latest)%>

    + +<%= render :partial => 'news/news', :collection => news_items %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cd/cd08316525f41ab12ddac1b7021cfbcc0f617e7e.svn-base --- a/.svn/pristine/cd/cd08316525f41ab12ddac1b7021cfbcc0f617e7e.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -<%= wiki_page_breadcrumb(@page) %> - -

    <%= h(@page.pretty_title) %>

    - -

    <%= l(:label_history) %>

    - -<%= form_tag({:controller => 'wiki', :action => 'diff', - :project_id => @page.project, :id => @page.title}, - :method => :get) do %> - - - - - - - - - - - -<% show_diff = @versions.size > 1 %> -<% line_num = 1 %> -<% @versions.each do |ver| %> -"> - - - - - - - - -<% line_num += 1 %> -<% end %> - -
    #<%= l(:field_updated_on) %><%= l(:field_author) %><%= l(:field_comments) %>
    <%= link_to h(ver.version), :action => 'show', :id => @page.title, :project_id => @page.project, :version => ver.version %><%= radio_button_tag('version', ver.version, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('#cbto-#{line_num+1}').attr('checked', true);") if show_diff && (line_num < @versions.size) %><%= radio_button_tag('version_from', ver.version, (line_num==2), :id => "cbto-#{line_num}") if show_diff && (line_num > 1) %><%= format_time(ver.updated_on) %><%= link_to_user ver.author %><%=h ver.comments %> - <%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %> - <%= delete_link wiki_page_path(@page, :version => ver.version) if User.current.allowed_to?(:delete_wiki_pages, @page.project) && @version_count > 1 %> -
    -<%= submit_tag l(:label_view_diff), :class => 'small' if show_diff %> -<%= pagination_links_full @version_pages, @version_count %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cd/cd2d5ab029a36ca237e5cbe16a9436e83ff2e3a8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cd/cd2d5ab029a36ca237e5cbe16a9436e83ff2e3a8.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,3 @@ +

    <%=l(:label_document_plural)%>

    + +<%= render :partial => 'documents/document', :collection => documents_items %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cd/cd3d594ac426506e373d0e5b944a6cc7b681c8b8.svn-base --- a/.svn/pristine/cd/cd3d594ac426506e373d0e5b944a6cc7b681c8b8.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module AdminHelper - def project_status_options_for_select(selected) - options_for_select([[l(:label_all), ''], - [l(:project_status_active), '1'], - [l(:project_status_closed), '5'], - [l(:project_status_archived), '9']], selected.to_s) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cd/cd616cb8a21cd26998b00199d18b19fd0b8aadef.svn-base --- a/.svn/pristine/cd/cd616cb8a21cd26998b00199d18b19fd0b8aadef.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -
    -<% if User.current.allowed_to?(:manage_documents, @project) %> -<%= link_to l(:button_edit), edit_document_path(@document), :class => 'icon icon-edit', :accesskey => accesskey(:edit) %> -<%= delete_link document_path(@document) %> -<% end %> -
    - -

    <%=h @document.title %>

    - -

    <%=h @document.category.name %>
    -<%= format_date @document.created_on %>

    -
    -<%= textilizable @document.description, :attachments => @document.attachments %> -
    - -

    <%= l(:label_attachment_plural) %>

    -<%= link_to_attachments @document %> - -<% if authorize_for('documents', 'add_attachment') %> -

    <%= link_to l(:label_attachment_new), {}, :onclick => "$('#add_attachment_form').show(); return false;", - :id => 'attach_files_link' %>

    - <%= form_tag({ :controller => 'documents', :action => 'add_attachment', :id => @document }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %> -
    -

    <%= render :partial => 'attachments/form' %>

    -
    - <%= submit_tag l(:button_add) %> - <% end %> -<% end %> - -<% html_title @document.title -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cd/cd94073d8372c27a92619877cd254e80d8edcb0e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cd/cd94073d8372c27a92619877cd254e80d8edcb0e.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1130 @@ +# Japanese translations for Ruby on Rails +# by Akira Matsuda (ronnie@dio.jp) +# AR error messages are basically taken from Ruby-GetText-Package. Thanks to Masao Mutoh. + +ja: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y/%m/%d" + short: "%m/%d" + long: "%Yå¹´%m月%dæ—¥(%a)" + + day_names: [日曜日, 月曜日, ç«æ›œæ—¥, 水曜日, 木曜日, 金曜日, 土曜日] + abbr_day_names: [æ—¥, 月, ç«, æ°´, 木, 金, 土] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月] + abbr_month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%Y/%m/%d %H:%M:%S" + time: "%H:%M" + short: "%y/%m/%d %H:%M" + long: "%Yå¹´%m月%dæ—¥(%a) %H時%M分%Sç§’ %Z" + am: "åˆå‰" + pm: "åˆå¾Œ" + + datetime: + distance_in_words: + half_a_minute: "30ç§’å‰å¾Œ" + less_than_x_seconds: + one: "1秒以内" + other: "%{count}秒以内" + x_seconds: + one: "1ç§’" + other: "%{count}ç§’" + less_than_x_minutes: + one: "1分以内" + other: "%{count}分以内" + x_minutes: + one: "1分" + other: "%{count}分" + about_x_hours: + one: "ç´„1時間" + other: "ç´„%{count}時間" + x_hours: + one: "1時間" + other: "%{count}時間" + x_days: + one: "1æ—¥" + other: "%{count}æ—¥" + about_x_months: + one: "ç´„1ヶ月" + other: "ç´„%{count}ヶ月" + x_months: + one: "1ヶ月" + other: "%{count}ヶ月" + about_x_years: + one: "ç´„1å¹´" + other: "ç´„%{count}å¹´" + over_x_years: + one: "1年以上" + other: "%{count}年以上" + almost_x_years: + one: "ã»ã¼1å¹´" + other: "ã»ã¼%{count}å¹´" + + number: + format: + separator: "." + delimiter: "," + precision: 3 + + currency: + format: + format: "%n%u" + unit: "円" + separator: "." + delimiter: "," + precision: 0 + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "åŠã³" + skip_last_comma: true + + activerecord: + errors: + template: + header: + one: "%{model} ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" + other: "%{model} ã« %{count} ã¤ã®ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" + body: "次ã®é …目を確èªã—ã¦ãã ã•ã„。" + + messages: + inclusion: "ã¯ä¸€è¦§ã«ã‚りã¾ã›ã‚“。" + exclusion: "ã¯äºˆç´„ã•れã¦ã„ã¾ã™ã€‚" + invalid: "ã¯ä¸æ­£ãªå€¤ã§ã™ã€‚" + confirmation: "ãŒä¸€è‡´ã—ã¾ã›ã‚“。" + accepted: "ã‚’å—諾ã—ã¦ãã ã•ã„。" + empty: "を入力ã—ã¦ãã ã•ã„。" + blank: "を入力ã—ã¦ãã ã•ã„。" + too_long: "ã¯%{count}文字以内ã§å…¥åŠ›ã—ã¦ãã ã•ã„。" + too_short: "ã¯%{count}文字以上ã§å…¥åŠ›ã—ã¦ãã ã•ã„。" + wrong_length: "ã¯%{count}文字ã§å…¥åŠ›ã—ã¦ãã ã•ã„。" + taken: "ã¯ã™ã§ã«å­˜åœ¨ã—ã¾ã™ã€‚" + not_a_number: "ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。" + not_a_date: "ã¯æ—¥ä»˜ã‚’入力ã—ã¦ãã ã•ã„。" + greater_than: "ã¯%{count}より大ãã„値ã«ã—ã¦ãã ã•ã„。" + greater_than_or_equal_to: "ã¯%{count}以上ã®å€¤ã«ã—ã¦ãã ã•ã„。" + equal_to: "ã¯%{count}ã«ã—ã¦ãã ã•ã„。" + less_than: "ã¯%{count}よりå°ã•ã„値ã«ã—ã¦ãã ã•ã„。" + less_than_or_equal_to: "ã¯%{count}以下ã®å€¤ã«ã—ã¦ãã ã•ã„。" + odd: "ã¯å¥‡æ•°ã«ã—ã¦ãã ã•ã„。" + even: "ã¯å¶æ•°ã«ã—ã¦ãã ã•ã„。" + greater_than_start_date: "を開始日より後ã«ã—ã¦ãã ã•ã„。" + not_same_project: "åŒã˜ãƒ—ロジェクトã«å±žã—ã¦ã„ã¾ã›ã‚“。" + circular_dependency: "ã“ã®é–¢ä¿‚ã§ã¯ã€å¾ªç’°ä¾å­˜ã«ãªã‚Šã¾ã™ã€‚" + cant_link_an_issue_with_a_descendant: "親å­é–¢ä¿‚ã«ã‚ã‚‹ãƒã‚±ãƒƒãƒˆé–“ã§ã®é–¢é€£ã®è¨­å®šã¯ã§ãã¾ã›ã‚“。" + earlier_than_minimum_start_date: "ã‚’%{date}よりå‰ã«ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。先行ã™ã‚‹ãƒã‚±ãƒƒãƒˆãŒã‚りã¾ã™ã€‚" + + actionview_instancetag_blank_option: é¸ã‚“ã§ãã ã•ã„ + + general_text_No: 'ã„ã„ãˆ' + general_text_Yes: 'ã¯ã„' + general_text_no: 'ã„ã„ãˆ' + general_text_yes: 'ã¯ã„' + general_lang_name: 'Japanese (日本語)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: CP932 + ## Redmine 1.2.0 ç¾åœ¨ã€ã“ã®å€¤ã«ã‚ˆã£ã¦ã€pdfã®å‡ºåŠ›ã®ãƒ•ォントを切り替ãˆã¦ã„ã¾ã™ã€‚ + ## CRuby ã§ã¯ CP932 ã«ã—ã¦ãã ã•ã„。 + ## JRuby 1.6.2 (ruby-1.8.7-p330) ã§ã¯ã€CP932 ã§ã™ã¨ + ## Iconv::InvalidEncoding例外ãŒç™ºç”Ÿã—ã¾ã™ã€‚ + ## JRuby ã§ã¯ã€SJIS ã‹ Shift_JIS ã«ã—ã¦ãã ã•ã„。 + ## ã”存知ã®é€šã‚Šã€CP932 㨠SJIS ã¯åˆ¥ç‰©ã§ã™ãŒã€ + ## ãã“ã¾ã§ã®æ¤œè¨¼ã¯ã—ã¦ã„ã¾ã›ã‚“。 + # general_pdf_encoding: SJIS + general_pdf_encoding: CP932 + general_first_day_of_week: '7' + + notice_account_updated: ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒæ›´æ–°ã•れã¾ã—ãŸã€‚ + notice_account_invalid_creditentials: ユーザーåã‚‚ã—ãã¯ãƒ‘スワードãŒç„¡åйã§ã™ + notice_account_password_updated: ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ãŒæ›´æ–°ã•れã¾ã—ãŸã€‚ + notice_account_wrong_password: パスワードãŒé•ã„ã¾ã™ + notice_account_register_done: アカウントを作æˆã—ã¾ã—ãŸã€‚アカウントを有効ã«ã™ã‚‹ãŸã‚ã®æ‰‹é †ã‚’記載ã—ãŸãƒ¡ãƒ¼ãƒ«ã‚’ %{email} å®›ã«é€ä¿¡ã—ã¾ã—ãŸã€‚ + notice_account_unknown_email: ユーザーãŒå­˜åœ¨ã—ã¾ã›ã‚“。 + notice_can_t_change_password: ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã¯å¤–部èªè¨¼ã‚’使ã£ã¦ã„ã¾ã™ã€‚パスワードã¯å¤‰æ›´ã§ãã¾ã›ã‚“。 + notice_account_lost_email_sent: æ–°ã—ã„パスワードã®ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚ + notice_account_activated: ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒæœ‰åйã«ãªã‚Šã¾ã—ãŸã€‚ログインã§ãã¾ã™ã€‚ + notice_successful_create: 作æˆã—ã¾ã—ãŸã€‚ + notice_successful_update: æ›´æ–°ã—ã¾ã—ãŸã€‚ + notice_successful_delete: 削除ã—ã¾ã—ãŸã€‚ + notice_successful_connection: 接続ã—ã¾ã—ãŸã€‚ + notice_file_not_found: アクセスã—よã†ã¨ã—ãŸãƒšãƒ¼ã‚¸ã¯å­˜åœ¨ã—ãªã„ã‹å‰Šé™¤ã•れã¦ã„ã¾ã™ã€‚ + notice_locking_conflict: 別ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒãƒ‡ãƒ¼ã‚¿ã‚’æ›´æ–°ã—ã¦ã„ã¾ã™ã€‚ + notice_not_authorized: ã“ã®ãƒšãƒ¼ã‚¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“。 + notice_not_authorized_archived_project: プロジェクトã¯ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ã•れã¦ã„ã¾ã™ã€‚ + notice_email_sent: "%{value} å®›ã«ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚" + notice_email_error: "メールé€ä¿¡ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—㟠(%{value})" + notice_feeds_access_key_reseted: Atomã‚¢ã‚¯ã‚»ã‚¹ã‚­ãƒ¼ã‚’åˆæœŸåŒ–ã—ã¾ã—ãŸã€‚ + notice_api_access_key_reseted: APIã‚¢ã‚¯ã‚»ã‚¹ã‚­ãƒ¼ã‚’åˆæœŸåŒ–ã—ã¾ã—ãŸã€‚ + notice_failed_to_save_issues: "å…¨%{total}件中%{count}ä»¶ã®ãƒã‚±ãƒƒãƒˆãŒä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸ: %{ids}." + notice_failed_to_save_members: "メンãƒãƒ¼ã®ä¿å­˜ã«å¤±æ•—ã—ã¾ã—ãŸ: %{errors}." + notice_no_issue_selected: "ãƒã‚±ãƒƒãƒˆãŒé¸æŠžã•れã¦ã„ã¾ã›ã‚“! 更新対象ã®ãƒã‚±ãƒƒãƒˆã‚’é¸æŠžã—ã¦ãã ã•ã„。" + notice_account_pending: アカウントを作æˆã—ã¾ã—ãŸã€‚システム管ç†è€…ã®æ‰¿èªå¾…ã¡ã§ã™ã€‚ + notice_default_data_loaded: デフォルト設定をロードã—ã¾ã—ãŸã€‚ + notice_unable_delete_version: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’削除ã§ãã¾ã›ã‚“ + notice_unable_delete_time_entry: 作業時間を削除ã§ãã¾ã›ã‚“ + notice_issue_done_ratios_updated: ãƒã‚±ãƒƒãƒˆã®é€²æ—率を更新ã—ã¾ã—ãŸã€‚ + notice_gantt_chart_truncated: ガントãƒãƒ£ãƒ¼ãƒˆã¯ã€æœ€å¤§è¡¨ç¤ºé …目数(%{max})ã‚’è¶…ãˆãŸãŸãŸã‚切りæ¨ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚ + + error_can_t_load_default_data: "デフォルト設定ãŒãƒ­ãƒ¼ãƒ‰ã§ãã¾ã›ã‚“ã§ã—ãŸ: %{value}" + error_scm_not_found: リãƒã‚¸ãƒˆãƒªã«ã€ã‚¨ãƒ³ãƒˆãƒª/リビジョンãŒå­˜åœ¨ã—ã¾ã›ã‚“。 + error_scm_command_failed: "リãƒã‚¸ãƒˆãƒªã¸ã‚¢ã‚¯ã‚»ã‚¹ã—よã†ã¨ã—ã¦ã‚¨ãƒ©ãƒ¼ã«ãªã‚Šã¾ã—ãŸ: %{value}" + error_scm_annotate: "エントリãŒå­˜åœ¨ã—ãªã„ã€ã‚‚ã—ãã¯ã‚¢ãƒŽãƒ†ãƒ¼ãƒˆã§ãã¾ã›ã‚“。" + error_issue_not_found_in_project: 'ãƒã‚±ãƒƒãƒˆãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã€ã‚‚ã—ãã¯ã“ã®ãƒ—ロジェクトã«å±žã—ã¦ã„ã¾ã›ã‚“' + error_unable_delete_issue_status: "ãƒã‚±ãƒƒãƒˆã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" + error_no_tracker_in_project: 'ã“ã®ãƒ—ロジェクトã«ã¯ãƒˆãƒ©ãƒƒã‚«ãƒ¼ãŒç™»éŒ²ã•れã¦ã„ã¾ã›ã‚“。プロジェクト設定を確èªã—ã¦ãã ã•ã„。' + error_no_default_issue_status: 'デフォルトã®ãƒã‚±ãƒƒãƒˆã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ãŒå®šç¾©ã•れã¦ã„ã¾ã›ã‚“。設定を確èªã—ã¦ãã ã•ã„(管ç†â†’ãƒã‚±ãƒƒãƒˆã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ï¼‰ã€‚' + error_can_not_delete_custom_field: 'カスタムフィールドを削除ã§ãã¾ã›ã‚“。' + error_unable_to_connect: "接続ã§ãã¾ã›ã‚“。 (%{value})" + error_can_not_remove_role: 'ã“ã®ãƒ­ãƒ¼ãƒ«ã¯ä½¿ç”¨ã•れã¦ã„ã¾ã™ã€‚削除ã§ãã¾ã›ã‚“。' + error_can_not_reopen_issue_on_closed_version: '終了ã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ã²ã‚‚付ã‘ã•れãŸãƒã‚±ãƒƒãƒˆã®å†ã‚ªãƒ¼ãƒ—ンã¯ã§ãã¾ã›ã‚“。' + error_can_not_archive_project: ã“ã®ãƒ—ロジェクトã¯ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ã§ãã¾ã›ã‚“ + error_issue_done_ratios_not_updated: "ãƒã‚±ãƒƒãƒˆã®é€²æ—çŽ‡ãŒæ›´æ–°ã§ãã¾ã›ã‚“。" + error_workflow_copy_source: 'コピー元ã¨ãªã‚‹ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã¾ãŸã¯ãƒ­ãƒ¼ãƒ«ã‚’é¸æŠžã—ã¦ãã ã•ã„' + error_workflow_copy_target: 'コピー先ã¨ãªã‚‹ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã¨ãƒ­ãƒ¼ãƒ«ã‚’é¸æŠžã—ã¦ãã ã•ã„' + error_can_not_delete_tracker: 'ã“ã®ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã¯ä½¿ç”¨ã•れã¦ã„ã¾ã™ã€‚削除ã§ãã¾ã›ã‚“。' + + warning_attachments_not_saved: "%{count}å€‹ã®æ·»ä»˜ãƒ•ァイルãŒä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" + + mail_subject_lost_password: "%{value} パスワードå†ç™ºè¡Œ" + mail_body_lost_password: 'パスワードを変更ã™ã‚‹ã«ã¯ã€ä»¥ä¸‹ã®ãƒªãƒ³ã‚¯ã‚’クリックã—ã¦ãã ã•ã„:' + mail_subject_register: "%{value} アカウント登録ã®ç¢ºèª" + mail_body_register: 'アカウント登録を完了ã™ã‚‹ã«ã¯ã€ä»¥ä¸‹ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’クリックã—ã¦ãã ã•ã„:' + mail_body_account_information_external: "%{value} アカウントを使ã£ã¦ã«ãƒ­ã‚°ã‚¤ãƒ³ã§ãã¾ã™ã€‚" + mail_body_account_information: アカウント情報 + mail_subject_account_activation_request: "%{value} ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®æ‰¿èªè¦æ±‚" + mail_body_account_activation_request: "æ–°ã—ã„ユーザー %{value} ãŒç™»éŒ²ã•れã¾ã—ãŸã€‚ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ã‚ãªãŸã®æ‰¿èªå¾…ã¡ã§ã™ï¼š" + mail_subject_reminder: "%{count}ä»¶ã®ãƒã‚±ãƒƒãƒˆã®æœŸæ—¥ãŒ%{days}日以内ã«åˆ°æ¥ã—ã¾ã™" + mail_body_reminder: "%{count}ä»¶ã®æ‹…当ãƒã‚±ãƒƒãƒˆã®æœŸæ—¥ãŒ%{days}日以内ã«åˆ°æ¥ã—ã¾ã™:" + mail_subject_wiki_content_added: "Wikiページ %{id} ãŒè¿½åŠ ã•れã¾ã—ãŸ" + mail_body_wiki_content_added: "%{author} ã«ã‚ˆã£ã¦Wikiページ %{id} ãŒè¿½åŠ ã•れã¾ã—ãŸã€‚" + mail_subject_wiki_content_updated: "Wikiページ %{id} ãŒæ›´æ–°ã•れã¾ã—ãŸ" + mail_body_wiki_content_updated: "%{author} ã«ã‚ˆã£ã¦Wikiページ %{id} ãŒæ›´æ–°ã•れã¾ã—ãŸã€‚" + + + field_name: åç§° + field_description: 説明 + field_summary: サマリー + field_is_required: å¿…é ˆ + field_firstname: åå‰ + field_lastname: è‹—å­— + field_mail: メールアドレス + field_filename: ファイル + field_filesize: サイズ + field_downloads: ダウンロード + field_author: 作æˆè€… + field_created_on: ä½œæˆæ—¥ + field_updated_on: æ›´æ–°æ—¥ + field_field_format: æ›¸å¼ + field_is_for_all: 全プロジェクトå‘ã‘ + field_possible_values: é¸æŠžè‚¢ + field_regexp: æ­£è¦è¡¨ç¾ + field_min_length: 最å°å€¤ + field_max_length: 最大値 + field_value: 値 + field_category: カテゴリ + field_title: タイトル + field_project: プロジェクト + field_issue: ãƒã‚±ãƒƒãƒˆ + field_status: ステータス + field_notes: 注記 + field_is_closed: 終了ã—ãŸãƒã‚±ãƒƒãƒˆ + field_is_default: デフォルト値 + field_tracker: トラッカー + field_subject: 題å + field_due_date: 期日 + field_assigned_to: 担当者 + field_priority: 優先度 + field_fixed_version: 対象ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + field_user: ユーザー + field_principal: 主体 + field_role: ロール + field_homepage: ホームページ + field_is_public: 公開 + field_parent: 親プロジェクトå + field_is_in_roadmap: ãƒã‚±ãƒƒãƒˆã‚’ロードマップã«è¡¨ç¤ºã™ã‚‹ + field_login: ログイン + field_mail_notification: メール通知 + field_admin: システム管ç†è€… + field_last_login_on: 最終接続日 + field_language: 言語 + field_effective_date: 期日 + field_password: パスワード + field_new_password: æ–°ã—ã„パスワード + field_password_confirmation: パスワードã®ç¢ºèª + field_version: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + field_type: タイプ + field_host: ホスト + field_port: ãƒãƒ¼ãƒˆ + field_account: アカウント + field_base_dn: 検索範囲 + field_attr_login: ログインå属性 + field_attr_firstname: åå‰å±žæ€§ + field_attr_lastname: 苗字属性 + field_attr_mail: メール属性 + field_onthefly: ã‚ã‚ã›ã¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’ä½œæˆ + field_start_date: é–‹å§‹æ—¥ + field_done_ratio: 進æ—率 + field_auth_source: èªè¨¼æ–¹å¼ + field_hide_mail: メールアドレスを隠㙠+ field_comments: コメント + field_url: URL + field_start_page: メインページ + field_subproject: サブプロジェクト + field_hours: 時間 + field_activity: 活動 + field_spent_on: 日付 + field_identifier: è­˜åˆ¥å­ + field_is_filter: フィルタã¨ã—ã¦ä½¿ã† + field_issue_to: 関連ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + field_delay: é…å»¶ + field_assignable: ã“ã®ãƒ­ãƒ¼ãƒ«ã«ãƒã‚±ãƒƒãƒˆã‚’割り当ã¦å¯èƒ½ + field_redirect_existing_links: 既存ã®ãƒªãƒ³ã‚¯ã‚’リダイレクトã™ã‚‹ + field_estimated_hours: 予定工数 + field_column_names: é …ç›® + field_time_entries: 時間を記録 + field_time_zone: タイムゾーン + field_searchable: 検索æ¡ä»¶ã«è¨­å®šå¯èƒ½ã¨ã™ã‚‹ + field_default_value: デフォルト値 + field_comments_sorting: コメントã®è¡¨ç¤ºé † + field_parent_title: 親ページ + field_editable: 編集å¯èƒ½ + field_watcher: ウォッãƒãƒ£ãƒ¼ + field_identity_url: OpenID URL + field_content: 内容 + field_group_by: グループæ¡ä»¶ + field_sharing: 共有 + field_parent_issue: 親ãƒã‚±ãƒƒãƒˆ + field_member_of_group: 担当者ã®ã‚°ãƒ«ãƒ¼ãƒ— + field_assigned_to_role: 担当者ã®ãƒ­ãƒ¼ãƒ« + field_text: テキスト + field_visible: 表示 + field_warn_on_leaving_unsaved: データをä¿å­˜ã›ãšã«ãƒšãƒ¼ã‚¸ã‹ã‚‰ç§»å‹•ã™ã‚‹ã¨ãã«è­¦å‘Š + field_commit_logs_encoding: コミットメッセージã®ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚° + field_scm_path_encoding: パスã®ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚° + field_path_to_repository: リãƒã‚¸ãƒˆãƒªã®ãƒ‘ス + field_root_directory: ルートディレクトリ + field_cvsroot: CVSROOT + field_cvs_module: モジュール + + setting_app_title: アプリケーションã®ã‚¿ã‚¤ãƒˆãƒ« + setting_app_subtitle: アプリケーションã®ã‚µãƒ–タイトル + setting_welcome_text: ウェルカムメッセージ + setting_default_language: 既定ã®è¨€èªž + setting_login_required: èªè¨¼ãŒå¿…è¦ + setting_self_registration: ユーザーã«ã‚ˆã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆç™»éŒ² + setting_attachment_max_size: 添付ファイルサイズã®ä¸Šé™ + setting_issues_export_limit: エクスãƒãƒ¼ãƒˆã™ã‚‹ãƒã‚±ãƒƒãƒˆæ•°ã®ä¸Šé™ + setting_mail_from: é€ä¿¡å…ƒãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ + setting_bcc_recipients: ブラインドカーボンコピーã§å—ä¿¡(bcc) + setting_plain_text_mail: プレインテキストã®ã¿(HTMLãªã—) + setting_host_name: ホストå + setting_text_formatting: ãƒ†ã‚­ã‚¹ãƒˆã®æ›¸å¼ + setting_cache_formatted_text: 書å¼åŒ–ã•れãŸãƒ†ã‚­ã‚¹ãƒˆã‚’キャッシュã™ã‚‹ + setting_wiki_compression: Wiki履歴を圧縮ã™ã‚‹ + setting_feeds_limit: フィード内容ã®ä¸Šé™ + setting_default_projects_public: ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§æ–°ã—ã„プロジェクトã¯å…¬é–‹ã«ã™ã‚‹ + setting_autofetch_changesets: コミットを自動å–å¾—ã™ã‚‹ + setting_sys_api_enabled: リãƒã‚¸ãƒˆãƒªç®¡ç†ç”¨ã®Webサービスを有効ã«ã™ã‚‹ + setting_commit_ref_keywords: å‚照用キーワード + setting_commit_fix_keywords: 修正用キーワード + setting_autologin: 自動ログイン + setting_date_format: 日付ã®å½¢å¼ + setting_time_format: 時刻ã®å½¢å¼ + setting_cross_project_issue_relations: ç•°ãªã‚‹ãƒ—ロジェクトã®ãƒã‚±ãƒƒãƒˆé–“ã§é–¢é€£ã®è¨­å®šã‚’è¨±å¯ + setting_issue_list_default_columns: ãƒã‚±ãƒƒãƒˆã®ä¸€è¦§ã§è¡¨ç¤ºã™ã‚‹é …ç›® + setting_repositories_encodings: 添付ファイルã¨ãƒªãƒã‚¸ãƒˆãƒªã®ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚° + setting_emails_header: メールã®ãƒ˜ãƒƒãƒ€ + setting_emails_footer: メールã®ãƒ•ッタ + setting_protocol: プロトコル + setting_per_page_options: ページ毎ã®è¡¨ç¤ºä»¶æ•° + setting_user_format: ユーザーåã®è¡¨ç¤ºæ›¸å¼ + setting_activity_days_default: ãƒ—ãƒ­ã‚¸ã‚§ã‚¯ãƒˆã®æ´»å‹•ページã«è¡¨ç¤ºã•れる日数 + setting_display_subprojects_issues: サブプロジェクトã®ãƒã‚±ãƒƒãƒˆã‚’メインプロジェクトã«è¡¨ç¤ºã™ã‚‹ + setting_enabled_scm: 使用ã™ã‚‹ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã‚·ã‚¹ãƒ†ãƒ  + setting_mail_handler_body_delimiters: "メール本文ã‹ã‚‰ä¸€è‡´ã™ã‚‹è¡Œä»¥é™ã‚’切りæ¨ã¦ã‚‹" + setting_mail_handler_api_enabled: å—信メール用ã®Webサービスを有効ã«ã™ã‚‹ + setting_mail_handler_api_key: APIキー + setting_sequential_project_identifiers: プロジェクト識別å­ã‚’連番ã§ç”Ÿæˆã™ã‚‹ + setting_gravatar_enabled: Gravatarã®ã‚¢ã‚¤ã‚³ãƒ³ã‚’使用ã™ã‚‹ + setting_gravatar_default: デフォルトã®Gravatarアイコン + setting_diff_max_lines_displayed: 差分ã®è¡¨ç¤ºè¡Œæ•°ã®ä¸Šé™ + setting_file_max_size_displayed: ç”»é¢è¡¨ç¤ºã™ã‚‹ãƒ†ã‚­ã‚¹ãƒˆãƒ•ァイルサイズã®ä¸Šé™ + setting_repository_log_display_limit: ファイルã®ãƒªãƒ“ジョン表示数ã®ä¸Šé™ + setting_openid: OpenIDã«ã‚ˆã‚‹ãƒ­ã‚°ã‚¤ãƒ³ã¨ç™»éŒ² + setting_password_min_length: ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ã®æœ€ä½Žå¿…è¦æ–‡å­—æ•° + setting_new_project_user_role_id: システム管ç†è€…以外ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒä½œæˆã—ãŸãƒ—ロジェクトã«è¨­å®šã™ã‚‹ãƒ­ãƒ¼ãƒ« + setting_default_projects_modules: æ–°è¦ãƒ—ロジェクトã«ãŠã„ã¦ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§æœ‰åйã«ãªã‚‹ãƒ¢ã‚¸ãƒ¥ãƒ¼ãƒ« + setting_issue_done_ratio: 進æ—率ã®ç®—出方法 + setting_issue_done_ratio_issue_field: ãƒã‚±ãƒƒãƒˆã®ãƒ•ィールドを使用ã™ã‚‹ + setting_issue_done_ratio_issue_status: ãƒã‚±ãƒƒãƒˆã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’使用ã™ã‚‹ + setting_start_of_week: 週ã®é–‹å§‹æ›œæ—¥ + setting_rest_api_enabled: RESTã«ã‚ˆã‚‹Webサービスを有効ã«ã™ã‚‹ + setting_default_notification_option: デフォルトã®ãƒ¡ãƒ¼ãƒ«é€šçŸ¥ã‚ªãƒ—ション + setting_commit_logtime_enabled: コミット時ã«ä½œæ¥­æ™‚間を記録ã™ã‚‹ + setting_commit_logtime_activity_id: 作業時間ã®ä½œæ¥­åˆ†é¡ž + setting_gantt_items_limit: ガントãƒãƒ£ãƒ¼ãƒˆæœ€å¤§è¡¨ç¤ºé …目数 + setting_default_projects_tracker_ids: æ–°è¦ãƒ—ロジェクトã«ãŠã„ã¦ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§æœ‰åйã«ãªã‚‹ãƒˆãƒ©ãƒƒã‚«ãƒ¼ + + permission_add_project: プロジェクトã®è¿½åŠ  + permission_add_subprojects: サブプロジェクトã®è¿½åŠ  + permission_edit_project: プロジェクトã®ç·¨é›† + permission_select_project_modules: モジュールã®é¸æŠž + permission_manage_members: メンãƒãƒ¼ã®ç®¡ç† + permission_manage_versions: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ç®¡ç† + permission_manage_categories: ãƒã‚±ãƒƒãƒˆã®ã‚«ãƒ†ã‚´ãƒªã®ç®¡ç† + permission_view_issues: ãƒã‚±ãƒƒãƒˆã®é–²è¦§ + permission_add_issues: ãƒã‚±ãƒƒãƒˆã®è¿½åŠ  + permission_edit_issues: ãƒã‚±ãƒƒãƒˆã®ç·¨é›† + permission_manage_issue_relations: 関連ã™ã‚‹ãƒã‚±ãƒƒãƒˆã®ç®¡ç† + permission_add_issue_notes: 注記ã®è¿½åŠ  + permission_edit_issue_notes: 注記ã®ç·¨é›† + permission_edit_own_issue_notes: 自身ãŒè¨˜å…¥ã—ãŸæ³¨è¨˜ã®ç·¨é›† + permission_move_issues: ãƒã‚±ãƒƒãƒˆã®ç§»å‹• + permission_delete_issues: ãƒã‚±ãƒƒãƒˆã®å‰Šé™¤ + permission_manage_public_queries: 公開クエリã®ç®¡ç† + permission_save_queries: クエリã®ä¿å­˜ + permission_view_gantt: ガントãƒãƒ£ãƒ¼ãƒˆã®é–²è¦§ + permission_view_calendar: カレンダーã®é–²è¦§ + permission_view_issue_watchers: ウォッãƒãƒ£ãƒ¼ä¸€è¦§ã®é–²è¦§ + permission_add_issue_watchers: ウォッãƒãƒ£ãƒ¼ã®è¿½åŠ  + permission_delete_issue_watchers: ウォッãƒãƒ£ãƒ¼ã®å‰Šé™¤ + permission_log_time: 作業時間ã®è¨˜å…¥ + permission_view_time_entries: 作業時間ã®é–²è¦§ + permission_edit_time_entries: 作業時間ã®ç·¨é›† + permission_edit_own_time_entries: 自身ãŒè¨˜å…¥ã—ãŸä½œæ¥­æ™‚é–“ã®ç·¨é›† + permission_manage_project_activities: 作業分類 (時間トラッキング) ã®ç®¡ç† + permission_manage_news: ニュースã®ç®¡ç† + permission_comment_news: ニュースã¸ã®ã‚³ãƒ¡ãƒ³ãƒˆ + permission_view_documents: 文書ã®é–²è¦§ + permission_manage_files: ファイルã®ç®¡ç† + permission_view_files: ファイルã®é–²è¦§ + permission_manage_wiki: Wikiã®ç®¡ç† + permission_rename_wiki_pages: Wikiページåã®å¤‰æ›´ + permission_delete_wiki_pages: Wikiページã®å‰Šé™¤ + permission_view_wiki_pages: Wikiã®é–²è¦§ + permission_export_wiki_pages: Wikiページを他ã®å½¢å¼ã«ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆ + permission_view_wiki_edits: Wiki履歴ã®é–²è¦§ + permission_edit_wiki_pages: Wikiページã®ç·¨é›† + permission_delete_wiki_pages_attachments: 添付ファイルã®å‰Šé™¤ + permission_protect_wiki_pages: Wikiページã®å‡çµ + permission_manage_repository: リãƒã‚¸ãƒˆãƒªã®ç®¡ç† + permission_browse_repository: リãƒã‚¸ãƒˆãƒªã®é–²è¦§ + permission_view_changesets: 更新履歴ã®é–²è¦§ + permission_commit_access: ã‚³ãƒŸãƒƒãƒˆæ¨©é™ + permission_manage_boards: フォーラムã®ç®¡ç† + permission_view_messages: メッセージã®é–²è¦§ + permission_add_messages: メッセージã®è¿½åŠ  + permission_edit_messages: メッセージã®ç·¨é›† + permission_edit_own_messages: 自身ãŒè¨˜å…¥ã—ãŸãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã®ç·¨é›† + permission_delete_messages: メッセージã®å‰Šé™¤ + permission_delete_own_messages: 自身ãŒè¨˜å…¥ã—ãŸãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã®å‰Šé™¤ + permission_manage_subtasks: å­ãƒã‚±ãƒƒãƒˆã®ç®¡ç† + + project_module_issue_tracking: ãƒã‚±ãƒƒãƒˆãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚° + project_module_time_tracking: 時間トラッキング + project_module_news: ニュース + project_module_documents: 文書 + project_module_files: ファイル + project_module_wiki: Wiki + project_module_repository: リãƒã‚¸ãƒˆãƒª + project_module_boards: フォーラム + project_module_gantt: ガントãƒãƒ£ãƒ¼ãƒˆ + project_module_calendar: カレンダー + + label_user: ユーザー + label_user_plural: ユーザー + label_user_new: æ–°ã—ã„ユーザー + label_user_anonymous: 匿åユーザー + label_profile: プロフィール + label_project: プロジェクト + label_project_new: æ–°ã—ã„プロジェクト + label_project_plural: プロジェクト + label_x_projects: + zero: プロジェクトã¯ã‚りã¾ã›ã‚“ + one: 1プロジェクト + other: "%{count}プロジェクト" + label_project_all: 全プロジェクト + label_project_latest: 最近ã®ãƒ—ロジェクト + label_issue: ãƒã‚±ãƒƒãƒˆ + label_issue_new: æ–°ã—ã„ãƒã‚±ãƒƒãƒˆ + label_issue_plural: ãƒã‚±ãƒƒãƒˆ + label_issue_view_all: ã™ã¹ã¦ã®ãƒã‚±ãƒƒãƒˆã‚’見る + label_issues_by: "%{value} 別ã®ãƒã‚±ãƒƒãƒˆ" + label_issue_added: ãƒã‚±ãƒƒãƒˆãŒè¿½åŠ ã•れã¾ã—㟠+ label_issue_updated: ãƒã‚±ãƒƒãƒˆãŒæ›´æ–°ã•れã¾ã—㟠+ label_document: 文書 + label_document_new: æ–°ã—ã„æ–‡æ›¸ + label_document_plural: 文書 + label_document_added: 文書ãŒè¿½åŠ ã•れã¾ã—㟠+ label_role: ロール + label_role_plural: ロール + label_role_new: æ–°ã—ã„ロール + label_role_and_permissions: ãƒ­ãƒ¼ãƒ«ã¨æ¨©é™ + label_member: メンãƒãƒ¼ + label_member_new: æ–°ã—ã„メンãƒãƒ¼ + label_member_plural: メンãƒãƒ¼ + label_tracker: トラッカー + label_tracker_plural: トラッカー + label_tracker_new: æ–°ã—ã„ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã‚’ä½œæˆ + label_workflow: ワークフロー + label_issue_status: ãƒã‚±ãƒƒãƒˆã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ + label_issue_status_plural: ãƒã‚±ãƒƒãƒˆã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ + label_issue_status_new: æ–°ã—ã„ステータス + label_issue_category: ãƒã‚±ãƒƒãƒˆã®ã‚«ãƒ†ã‚´ãƒª + label_issue_category_plural: ãƒã‚±ãƒƒãƒˆã®ã‚«ãƒ†ã‚´ãƒª + label_issue_category_new: æ–°ã—ã„カテゴリ + label_custom_field: カスタムフィールド + label_custom_field_plural: カスタムフィールド + label_custom_field_new: æ–°ã—ã„ã‚«ã‚¹ã‚¿ãƒ ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ã‚’ä½œæˆ + label_enumerations: 列挙項目 + label_enumeration_new: æ–°ã—ã„値 + label_information: 情報 + label_information_plural: 情報 + label_please_login: ログインã—ã¦ãã ã•ã„ + label_register: 登録ã™ã‚‹ + label_login_with_open_id_option: ã¾ãŸã¯OpenIDã§ãƒ­ã‚°ã‚¤ãƒ³ã™ã‚‹ + label_password_lost: パスワードã®å†ç™ºè¡Œ + label_home: ホーム + label_my_page: マイページ + label_my_account: 個人設定 + label_my_projects: マイプロジェクト + label_my_page_block: マイページパーツ + label_administration: ç®¡ç† + label_login: ログイン + label_logout: ログアウト + label_help: ヘルプ + label_reported_issues: 報告ã—ãŸãƒã‚±ãƒƒãƒˆ + label_assigned_to_me_issues: 担当ã—ã¦ã„ã‚‹ãƒã‚±ãƒƒãƒˆ + label_last_login: æœ€è¿‘ã®æŽ¥ç¶š + label_registered_on: 登録日 + label_activity: 活動 + label_overall_activity: ã™ã¹ã¦ã®æ´»å‹• + label_user_activity: "%{value} ã®æ´»å‹•" + label_new: æ–°ã—ãä½œæˆ + label_logged_as: ログイン中: + label_environment: 環境 + label_authentication: èªè¨¼ + label_auth_source: èªè¨¼æ–¹å¼ + label_auth_source_new: æ–°ã—ã„èªè¨¼æ–¹å¼ + label_auth_source_plural: èªè¨¼æ–¹å¼ + label_subproject_plural: サブプロジェクト + label_subproject_new: æ–°ã—ã„サブプロジェクト + label_and_its_subprojects: "%{value} ã¨ã‚µãƒ–プロジェクト" + label_min_max_length: 最å°å€¤ - 最大値ã®é•·ã• + label_list: リストã‹ã‚‰é¸æŠž + label_date: 日付 + label_integer: æ•´æ•° + label_float: å°æ•° + label_boolean: 真å½å€¤ + label_string: テキスト + label_text: é•·ã„テキスト + label_attribute: 属性 + label_attribute_plural: 属性 + label_no_data: 表示ã™ã‚‹ãƒ‡ãƒ¼ã‚¿ãŒã‚りã¾ã›ã‚“ + label_change_status: ステータスã®å¤‰æ›´ + label_history: 履歴 + label_attachment: ファイル + label_attachment_new: æ–°ã—ã„ファイル + label_attachment_delete: ファイルを削除 + label_attachment_plural: ファイル + label_file_added: ファイルãŒè¿½åŠ ã•れã¾ã—㟠+ label_report: レãƒãƒ¼ãƒˆ + label_report_plural: レãƒãƒ¼ãƒˆ + label_news: ニュース + label_news_new: ニュースを追加 + label_news_plural: ニュース + label_news_latest: 最新ニュース + label_news_view_all: ã™ã¹ã¦ã®ãƒ‹ãƒ¥ãƒ¼ã‚¹ã‚’見る + label_news_added: ニュースãŒè¿½åŠ ã•れã¾ã—㟠+ label_news_comment_added: ニュースã«ã‚³ãƒ¡ãƒ³ãƒˆãŒè¿½åŠ ã•れã¾ã—㟠+ label_settings: 設定 + label_overview: æ¦‚è¦ + label_version: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + label_version_new: æ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + label_version_plural: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + label_confirmation: ç¢ºèª + label_close_versions: 完了ã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’終了ã«ã™ã‚‹ + label_export_to: 'ä»–ã®å½¢å¼ã«ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆ:' + label_read: 読む... + label_public_projects: 公開プロジェクト + label_open_issues: 未完了 + label_open_issues_plural: 未完了 + label_closed_issues: 完了 + label_closed_issues_plural: 完了 + label_x_open_issues_abbr_on_total: + zero: 0件未完了 / å…¨%{total}ä»¶ + one: 1件未完了 / å…¨%{total}ä»¶ + other: "%{count}件未完了 / å…¨%{total}ä»¶" + label_x_open_issues_abbr: + zero: 0件未完了 + one: 1件未完了 + other: "%{count}件未完了" + label_x_closed_issues_abbr: + zero: 0件完了 + one: 1件完了 + other: "%{count}件完了" + label_total: åˆè¨ˆ + label_permissions: æ¨©é™ + label_current_status: ç¾åœ¨ã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ + label_new_statuses_allowed: ステータスã®ç§»è¡Œå…ˆ + label_all: ã™ã¹ã¦ + label_none: ãªã— + label_nobody: 無記å + label_next: 次 + label_previous: å‰ + label_used_by: 使用中 + label_details: 詳細 + label_add_note: 注記を追加 + label_per_page: ページ毎 + label_calendar: カレンダー + label_months_from: ヶ月分 + label_gantt: ガントãƒãƒ£ãƒ¼ãƒˆ + label_internal: 内部 + label_last_changes: "最新ã®å¤‰æ›´ %{count}ä»¶" + label_change_view_all: ã™ã¹ã¦ã®å¤‰æ›´ã‚’見る + label_personalize_page: ã“ã®ãƒšãƒ¼ã‚¸ã‚’パーソナライズã™ã‚‹ + label_comment: コメント + label_comment_plural: コメント + label_x_comments: + zero: コメントãŒã‚りã¾ã›ã‚“ + one: 1コメント + other: "%{count}コメント" + label_comment_add: コメント追加 + label_comment_added: 追加ã•れãŸã‚³ãƒ¡ãƒ³ãƒˆ + label_comment_delete: コメント削除 + label_query: カスタムクエリ + label_query_plural: カスタムクエリ + label_query_new: æ–°ã—ã„クエリ + label_my_queries: マイカスタムクエリ + label_filter_add: フィルタ追加 + label_filter_plural: フィルタ + label_equals: ç­‰ã—ã„ + label_not_equals: ç­‰ã—ããªã„ + label_in_less_than: 今日ã‹ã‚‰â—‹æ—¥å¾Œä»¥å‰ + label_in_more_than: 今日ã‹ã‚‰â—‹æ—¥å¾Œä»¥é™ + label_greater_or_equal: 以上 + label_less_or_equal: 以下 + label_in: 今日ã‹ã‚‰â—‹æ—¥å¾Œ + label_today: 今日 + label_all_time: 全期間 + label_yesterday: 昨日 + label_this_week: 今週 + label_last_week: 先週 + label_last_n_days: "ç›´è¿‘%{count}日間" + label_this_month: 今月 + label_last_month: 先月 + label_this_year: 今年 + label_date_range: 期間 + label_less_than_ago: 今日より○日å‰ä»¥é™ + label_more_than_ago: 今日より○日å‰ä»¥å‰ + label_ago: â—‹æ—¥å‰ + label_contains: å«ã‚€ + label_not_contains: å«ã¾ãªã„ + label_day_plural: æ—¥ + label_repository: リãƒã‚¸ãƒˆãƒª + label_repository_plural: リãƒã‚¸ãƒˆãƒª + label_browse: ブラウズ + label_branch: ブランム+ label_tag: ã‚¿ã‚° + label_revision: リビジョン + label_revision_plural: リビジョン + label_revision_id: リビジョン %{value} + label_associated_revisions: 関係ã—ã¦ã„るリビジョン + label_added: 追加 + label_modified: 変更 + label_copied: コピー + label_renamed: å称変更 + label_deleted: 削除 + label_latest_revision: 最新リビジョン + label_latest_revision_plural: 最新リビジョン + label_view_revisions: リビジョンを見る + label_view_all_revisions: ã™ã¹ã¦ã®ãƒªãƒ“ジョンを見る + label_max_size: サイズã®ä¸Šé™ + label_sort_highest: 一番上㸠+ label_sort_higher: 上㸠+ label_sort_lower: 下㸠+ label_sort_lowest: 一番下㸠+ label_roadmap: ロードマップ + label_roadmap_due_in: "期日ã¾ã§ %{value}" + label_roadmap_overdue: "%{value} é…れ" + label_roadmap_no_issues: ã“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«é–¢ã™ã‚‹ãƒã‚±ãƒƒãƒˆã¯ã‚りã¾ã›ã‚“ + label_search: 検索 + label_result_plural: çµæžœ + label_all_words: ã™ã¹ã¦ã®å˜èªž + label_wiki: Wiki + label_wiki_edit: Wiki編集 + label_wiki_edit_plural: Wiki編集 + label_wiki_page: Wikiページ + label_wiki_page_plural: Wikiページ + label_index_by_title: 索引(åå‰é †) + label_index_by_date: 索引(日付順) + label_current_version: 最新版 + label_preview: プレビュー + label_feed_plural: フィード + label_changes_details: 全変更ã®è©³ç´° + label_issue_tracking: ãƒã‚±ãƒƒãƒˆãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚° + label_spent_time: 作業時間ã®è¨˜éŒ² + label_overall_spent_time: ã™ã¹ã¦ã®ä½œæ¥­æ™‚é–“ã®è¨˜éŒ² + label_f_hour: "%{value}時間" + label_f_hour_plural: "%{value}時間" + label_time_tracking: 時間トラッキング + label_change_plural: 変更 + label_statistics: 統計 + label_commits_per_month: 月別ã®ã‚³ãƒŸãƒƒãƒˆ + label_commits_per_author: 起票者別ã®ã‚³ãƒŸãƒƒãƒˆ + label_diff: 差分 + label_view_diff: 差分を見る + label_diff_inline: インライン + label_diff_side_by_side: 横ã«ä¸¦ã¹ã‚‹ + label_options: オプション + label_copy_workflow_from: ワークフローをã“ã“ã‹ã‚‰ã‚³ãƒ”ー + label_permissions_report: 権é™ãƒ¬ãƒãƒ¼ãƒˆ + label_watched_issues: ウォッãƒã—ã¦ã„ã‚‹ãƒã‚±ãƒƒãƒˆ + label_related_issues: 関連ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + label_applied_status: é©ç”¨ã•れるステータス + label_loading: ロード中... + label_relation_new: æ–°ã—ã„関連 + label_relation_delete: 関連ã®å‰Šé™¤ + label_relates_to: 関連ã—ã¦ã„ã‚‹ + label_duplicates: 次ã®ãƒã‚±ãƒƒãƒˆã¨é‡è¤‡ + label_duplicated_by: 次ã®ãƒã‚±ãƒƒãƒˆãŒé‡è¤‡ + label_blocks: ブロックã—ã¦ã„ã‚‹ + label_blocked_by: ブロックã•れã¦ã„ã‚‹ + label_precedes: 次ã®ãƒã‚±ãƒƒãƒˆã«å…ˆè¡Œ + label_follows: 次ã®ãƒã‚±ãƒƒãƒˆã«å¾Œç¶š + label_end_to_start: 最後-æœ€åˆ + label_end_to_end: 最後-最後 + label_start_to_start: 最åˆ-æœ€åˆ + label_start_to_end: 最åˆ-最後 + label_stay_logged_in: ãƒ­ã‚°ã‚¤ãƒ³ã‚’ç¶­æŒ + label_disabled: 無効 + label_show_completed_versions: 完了ã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’表示 + label_me: 自分 + label_board: フォーラム + label_board_new: æ–°ã—ã„フォーラム + label_board_plural: フォーラム + label_board_sticky: スティッキー + label_board_locked: ロック + label_topic_plural: トピック + label_message_plural: メッセージ + label_message_last: 最新ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ + label_message_new: æ–°ã—ã„メッセージ + label_message_posted: メッセージãŒè¿½åŠ ã•れã¾ã—㟠+ label_reply_plural: 返答 + label_send_information: アカウント情報をユーザーã«é€ä¿¡ + label_year: å¹´ + label_month: 月 + label_week: 週 + label_date_from: "日付指定: " + label_date_to: ã‹ã‚‰ + label_language_based: ユーザーã®è¨€èªžã®è¨­å®šã«å¾“ㆠ+ label_sort_by: "ä¸¦ã³æ›¿ãˆ %{value}" + label_send_test_email: テストメールをé€ä¿¡ + label_feeds_access_key: Atomアクセスキー + label_missing_feeds_access_key: AtomアクセスキーãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ + label_feeds_access_key_created_on: "Atomアクセスキーã¯%{value}å‰ã«ä½œæˆã•れã¾ã—ãŸ" + label_module_plural: モジュール + label_added_time_by: "%{author} ãŒ%{age}å‰ã«è¿½åŠ " + label_updated_time_by: "%{author} ãŒ%{age}å‰ã«æ›´æ–°" + label_updated_time: "%{value}å‰ã«æ›´æ–°" + label_jump_to_a_project: プロジェクトã¸ç§»å‹•... + label_file_plural: ファイル + label_changeset_plural: 更新履歴 + label_default_columns: 既定ã®é …ç›® + label_no_change_option: (変更無ã—) + label_bulk_edit_selected_issues: ãƒã‚±ãƒƒãƒˆã®ä¸€æ‹¬ç·¨é›† + label_theme: テーマ + label_default: 既定 + label_search_titles_only: タイトルã®ã¿ + label_user_mail_option_all: "å‚加ã—ã¦ã„るプロジェクトã®ã™ã¹ã¦ã®é€šçŸ¥" + label_user_mail_option_selected: "é¸æŠžã—ãŸãƒ—ロジェクトã®ã™ã¹ã¦ã®é€šçŸ¥..." + label_user_mail_option_none: "通知ã—ãªã„" + label_user_mail_option_only_my_events: "ウォッãƒã¾ãŸã¯é–¢ä¿‚ã—ã¦ã„る事柄ã®ã¿" + label_user_mail_option_only_assigned: "è‡ªåˆ†ãŒæ‹…当ã—ã¦ã„る事柄ã®ã¿" + label_user_mail_option_only_owner: "自分ãŒä½œæˆã—ãŸäº‹æŸ„ã®ã¿" + label_user_mail_no_self_notified: 自分自身ã«ã‚ˆã‚‹å¤‰æ›´ã®é€šçŸ¥ã¯ä¸è¦ + label_registration_activation_by_email: メールã§ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’有効化 + label_registration_manual_activation: 手動ã§ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’有効化 + label_registration_automatic_activation: 自動ã§ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’有効化 + label_display_per_page: "1ページã«: %{value}" + label_age: å¹´é½¢ + label_change_properties: プロパティã®å¤‰æ›´ + label_general: 全般 + label_more: ç¶šã + label_scm: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã‚·ã‚¹ãƒ†ãƒ  + label_plugins: プラグイン + label_ldap_authentication: LDAPèªè¨¼ + label_downloads_abbr: DL + label_optional_description: ä»»æ„ã®ã‚³ãƒ¡ãƒ³ãƒˆ + label_add_another_file: 別ã®ãƒ•ァイルを追加 + label_preferences: 設定 + label_chronological_order: å¤ã„é † + label_reverse_chronological_order: æ–°ã—ã„é † + label_planning: 計画 + label_incoming_emails: å—信メール + label_generate_key: キーã®ç”Ÿæˆ + label_issue_watchers: ãƒã‚±ãƒƒãƒˆã®ã‚¦ã‚©ãƒƒãƒãƒ£ãƒ¼ + label_example: 例 + label_display: 表示 + label_sort: ソートæ¡ä»¶ + label_ascending: 昇順 + label_descending: é™é † + label_date_from_to: "%{start}ã‹ã‚‰%{end}ã¾ã§" + label_wiki_content_added: WikiページãŒè¿½åŠ ã•れã¾ã—㟠+ label_wiki_content_updated: Wikiãƒšãƒ¼ã‚¸ãŒæ›´æ–°ã•れã¾ã—㟠+ label_group: グループ + label_group_plural: グループ + label_group_new: æ–°ã—ã„グループ + label_time_entry_plural: 作業時間ã®è¨˜éŒ² + label_version_sharing_none: 共有ã—ãªã„ + label_version_sharing_descendants: サブプロジェクトå˜ä½ + label_version_sharing_hierarchy: プロジェクト階層å˜ä½ + label_version_sharing_tree: プロジェクトツリーå˜ä½ + label_version_sharing_system: ã™ã¹ã¦ã®ãƒ—ロジェクト + label_update_issue_done_ratios: 進æ—çŽ‡ã®æ›´æ–° + label_copy_source: コピー元 + label_copy_target: コピー先 + label_copy_same_as_target: åŒã˜ã‚³ãƒ”ー先 + label_display_used_statuses_only: ã“ã®ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã§ä½¿ã‚れã¦ã„るステータスã®ã¿è¡¨ç¤ºã™ã‚‹ + label_api_access_key: APIアクセスキー + label_missing_api_access_key: APIアクセスキーãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ + label_api_access_key_created_on: "APIアクセスキーã¯%{value}å‰ã«ä½œæˆã•れã¾ã—ãŸ" + label_subtask_plural: å­ãƒã‚±ãƒƒãƒˆ + label_project_copy_notifications: コピーã—ãŸãƒã‚±ãƒƒãƒˆã®ãƒ¡ãƒ¼ãƒ«é€šçŸ¥ã‚’é€ä¿¡ã™ã‚‹ + label_principal_search: "ユーザーã¾ãŸã¯ã‚°ãƒ«ãƒ¼ãƒ—ã®æ¤œç´¢:" + label_user_search: "ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®æ¤œç´¢:" + label_git_report_last_commit: ファイルã¨ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®æœ€æ–°ã‚³ãƒŸãƒƒãƒˆã‚’表示ã™ã‚‹ + label_parent_revision: 親 + label_child_revision: å­ + label_gantt_progress_line: イナズマ線 + + button_login: ログイン + button_submit: é€ä¿¡ + button_save: ä¿å­˜ + button_check_all: ã™ã¹ã¦ã«ãƒã‚§ãƒƒã‚¯ã‚’ã¤ã‘ã‚‹ + button_uncheck_all: ã™ã¹ã¦ã®ãƒã‚§ãƒƒã‚¯ã‚’外㙠+ button_expand_all: 展開 + button_collapse_all: 折りãŸãŸã¿ + button_delete: 削除 + button_create: ä½œæˆ + button_create_and_continue: é€£ç¶šä½œæˆ + button_test: テスト + button_edit: 編集 + button_edit_associated_wikipage: "関連ã™ã‚‹Wikiページを編集: %{page_title}" + button_add: 追加 + button_change: 変更 + button_apply: é©ç”¨ + button_clear: クリア + button_lock: ロック + button_unlock: アンロック + button_download: ダウンロード + button_list: 一覧 + button_view: 表示 + button_move: 移動 + button_move_and_follow: 移動後表示 + button_back: 戻る + button_cancel: キャンセル + button_activate: 有効ã«ã™ã‚‹ + button_sort: ソート + button_log_time: 時間を記録 + button_rollback: ã“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ãƒ­ãƒ¼ãƒ«ãƒãƒƒã‚¯ + button_watch: ウォッム+ button_unwatch: ウォッãƒã‚’ã‚„ã‚ã‚‹ + button_reply: 返答 + button_archive: アーカイブ + button_unarchive: アーカイブ解除 + button_reset: リセット + button_rename: åå‰å¤‰æ›´ + button_change_password: パスワード変更 + button_copy: コピー + button_copy_and_follow: コピー後表示 + button_annotate: アノテート + button_update: æ›´æ–° + button_configure: 設定 + button_quote: 引用 + button_duplicate: 複製 + button_show: 表示 + + status_active: 有効 + status_registered: 登録 + status_locked: ロック + + version_status_open: 進行中 + version_status_locked: ロック中 + version_status_closed: 終了 + + field_active: 有効 + + text_select_mail_notifications: ã©ã®ãƒ¡ãƒ¼ãƒ«é€šçŸ¥ã‚’é€ä¿¡ã™ã‚‹ã‹ã€ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + text_regexp_info: 例) ^[A-Z0-9]+$ + text_min_max_length_info: 0ã ã¨ç„¡åˆ¶é™ã«ãªã‚Šã¾ã™ + text_project_destroy_confirmation: 本当ã«ã“ã®ãƒ—ロジェクトã¨é–¢é€£ãƒ‡ãƒ¼ã‚¿ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ + text_subprojects_destroy_warning: "サブプロジェクト %{value} も削除ã•れã¾ã™ã€‚" + text_workflow_edit: ワークフローを編集ã™ã‚‹ãƒ­ãƒ¼ãƒ«ã¨ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã‚’é¸ã‚“ã§ãã ã•ã„ + text_are_you_sure: よã‚ã—ã„ã§ã™ã‹ï¼Ÿ + text_journal_changed: "%{label} ã‚’ %{old} ã‹ã‚‰ %{new} ã«å¤‰æ›´" + text_journal_changed_no_detail: "%{label} ã‚’æ›´æ–°" + text_journal_set_to: "%{label} ã‚’ %{value} ã«ã‚»ãƒƒãƒˆ" + text_journal_deleted: "%{label} を削除 (%{old})" + text_journal_added: "%{label} %{value} を追加" + text_tip_issue_begin_day: ã“ã®æ—¥ã«é–‹å§‹ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + text_tip_issue_end_day: ã“ã®æ—¥ã«çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + text_tip_issue_begin_end_day: ã“ã®æ—¥ã«é–‹å§‹ãƒ»çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + text_caracters_maximum: "最大%{count}文字ã§ã™ã€‚" + text_caracters_minimum: "最低%{count}文字ã®é•·ã•ãŒå¿…è¦ã§ã™" + text_length_between: "é•·ã•ã¯%{min}ã‹ã‚‰%{max}文字ã¾ã§ã§ã™ã€‚" + text_tracker_no_workflow: ã“ã®ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã«ãƒ¯ãƒ¼ã‚¯ãƒ•ローãŒå®šç¾©ã•れã¦ã„ã¾ã›ã‚“ + text_unallowed_characters: æ¬¡ã®æ–‡å­—ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“ + text_comma_separated: (カンマã§åŒºåˆ‡ã‚‹ã“ã¨ã§)複数ã®å€¤ã‚’設定ã§ãã¾ã™ã€‚ + text_line_separated: (1行ã”ã¨ã«æ›¸ãã“ã¨ã§)複数ã®å€¤ã‚’設定ã§ãã¾ã™ã€‚ + text_issues_ref_in_commit_messages: コミットメッセージ内ã§ãƒã‚±ãƒƒãƒˆã®å‚ç…§/修正 + text_issue_added: "ãƒã‚±ãƒƒãƒˆ %{id} ㌠%{author} ã«ã‚ˆã£ã¦å ±å‘Šã•れã¾ã—ãŸã€‚" + text_issue_updated: "ãƒã‚±ãƒƒãƒˆ %{id} ㌠%{author} ã«ã‚ˆã£ã¦æ›´æ–°ã•れã¾ã—ãŸã€‚" + text_wiki_destroy_confirmation: 本当ã«ã“ã®wikiã¨ãã®å†…容ã®ã™ã¹ã¦ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ + text_issue_category_destroy_question: "%{count}ä»¶ã®ãƒã‚±ãƒƒãƒˆãŒã“ã®ã‚«ãƒ†ã‚´ãƒªã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦ã„ã¾ã™ã€‚" + text_issue_category_destroy_assignments: カテゴリã®å‰²ã‚Šå½“ã¦ã‚’削除ã™ã‚‹ + text_issue_category_reassign_to: ãƒã‚±ãƒƒãƒˆã‚’ã“ã®ã‚«ãƒ†ã‚´ãƒªã«å†å‰²ã‚Šå½“ã¦ã™ã‚‹ + text_user_mail_option: "æœªé¸æŠžã®ãƒ—ロジェクトã§ã¯ã€ã‚¦ã‚©ãƒƒãƒã¾ãŸã¯é–¢ä¿‚ã—ã¦ã„る事柄(例: 自分ãŒå ±å‘Šè€…ã‚‚ã—ãã¯æ‹…当者ã§ã‚ã‚‹ãƒã‚±ãƒƒãƒˆ)ã®ã¿ãƒ¡ãƒ¼ãƒ«ãŒé€ä¿¡ã•れã¾ã™ã€‚" + text_no_configuration_data: "ロールã€ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã€ãƒã‚±ãƒƒãƒˆã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã€ãƒ¯ãƒ¼ã‚¯ãƒ•ローãŒã¾ã è¨­å®šã•れã¦ã„ã¾ã›ã‚“。\nデフォルト設定ã®ãƒ­ãƒ¼ãƒ‰ã‚’å¼·ããŠå‹§ã‚ã—ã¾ã™ã€‚ロードã—ãŸå¾Œã€ãれを修正ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚" + text_load_default_configuration: デフォルト設定をロード + text_status_changed_by_changeset: "更新履歴 %{value} ã§é©ç”¨ã•れã¾ã—ãŸã€‚" + text_time_logged_by_changeset: "更新履歴 %{value} ã§é©ç”¨ã•れã¾ã—ãŸã€‚" + text_issues_destroy_confirmation: '本当ã«é¸æŠžã—ãŸãƒã‚±ãƒƒãƒˆã‚’削除ã—ã¾ã™ã‹ï¼Ÿ' + text_select_project_modules: 'ã“ã®ãƒ—ロジェクトã§ä½¿ç”¨ã™ã‚‹ãƒ¢ã‚¸ãƒ¥ãƒ¼ãƒ«ã‚’é¸æŠžã—ã¦ãã ã•ã„:' + text_default_administrator_account_changed: デフォルト管ç†ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒå¤‰æ›´æ¸ˆ + text_file_repository_writable: ファイルリãƒã‚¸ãƒˆãƒªã«æ›¸ãè¾¼ã¿å¯èƒ½ + text_plugin_assets_writable: Plugin assetsãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«æ›¸ãè¾¼ã¿å¯èƒ½ + text_rmagick_available: RMagickãŒåˆ©ç”¨å¯èƒ½ (オプション) + text_destroy_time_entries_question: ã“ã®ãƒã‚±ãƒƒãƒˆã®%{hours}時間分ã®ä½œæ¥­è¨˜éŒ²ã®æ‰±ã„ã‚’é¸æŠžã—ã¦ãã ã•ã„。 + text_destroy_time_entries: 記録ã•れãŸä½œæ¥­æ™‚é–“ã‚’å«ã‚ã¦å‰Šé™¤ + text_assign_time_entries_to_project: 記録ã•れãŸä½œæ¥­æ™‚間をプロジェクト自体ã«å‰²ã‚Šå½“㦠+ text_reassign_time_entries: '記録ã•れãŸä½œæ¥­æ™‚é–“ã‚’ã“ã®ãƒã‚±ãƒƒãƒˆã«å†å‰²ã‚Šå½“ã¦ï¼š' + text_user_wrote: "%{value} ã¯æ›¸ãã¾ã—ãŸ:" + text_enumeration_destroy_question: "%{count}個ã®ã‚ªãƒ–ジェクトãŒã“ã®å€¤ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦ã„ã¾ã™ã€‚" + text_enumeration_category_reassign_to: '次ã®å€¤ã«å‰²ã‚Šå½“ã¦ç›´ã™:' + text_email_delivery_not_configured: "メールをé€ä¿¡ã™ã‚‹ãŸã‚ã«å¿…è¦ãªè¨­å®šãŒè¡Œã‚れã¦ã„ãªã„ãŸã‚ã€ãƒ¡ãƒ¼ãƒ«é€šçŸ¥ã¯åˆ©ç”¨ã§ãã¾ã›ã‚“。\nconfig/configuration.ymlã§SMTPサーãƒã®è¨­å®šã‚’行ã„ã€ã‚¢ãƒ—リケーションをå†èµ·å‹•ã—ã¦ãã ã•ã„。" + text_repository_usernames_mapping: "リãƒã‚¸ãƒˆãƒªã®ãƒ­ã‚°ã‹ã‚‰æ¤œå‡ºã•れãŸãƒ¦ãƒ¼ã‚¶ãƒ¼åã‚’ã©ã®Redmineユーザーã«é–¢é€£ã¥ã‘ã‚‹ã®ã‹é¸æŠžã—ã¦ãã ã•ã„。\nログ上ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼åã¾ãŸã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒRedmineã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨ä¸€è‡´ã™ã‚‹å ´åˆã¯è‡ªå‹•çš„ã«é–¢é€£ã¥ã‘られã¾ã™ã€‚" + text_diff_truncated: '... 差分ã®è¡Œæ•°ãŒè¡¨ç¤ºå¯èƒ½ãªä¸Šé™ã‚’è¶…ãˆã¾ã—ãŸã€‚è¶…éŽåˆ†ã¯è¡¨ç¤ºã—ã¾ã›ã‚“。' + text_custom_field_possible_values_info: 'é¸æŠžè‚¢ã®å€¤ã¯1行ã«1個ãšã¤è¨˜è¿°ã—ã¦ãã ã•ã„。' + text_wiki_page_destroy_question: "ã“ã®è¦ªãƒšãƒ¼ã‚¸ã®é…下ã«%{descendants}ページã®å­å­«ãƒšãƒ¼ã‚¸ãŒã‚りã¾ã™ã€‚" + text_wiki_page_nullify_children: "å­ãƒšãƒ¼ã‚¸ã‚’メインページé…下ã«ç§»å‹•ã™ã‚‹" + text_wiki_page_destroy_children: "é…下ã®å­å­«ãƒšãƒ¼ã‚¸ã‚‚削除ã™ã‚‹" + text_wiki_page_reassign_children: "å­ãƒšãƒ¼ã‚¸ã‚’次ã®è¦ªãƒšãƒ¼ã‚¸ã®é…下ã«ç§»å‹•ã™ã‚‹" + text_own_membership_delete_confirmation: "一部ã¾ãŸã¯ã™ã¹ã¦ã®æ¨©é™ã‚’自分自身ã‹ã‚‰å‰¥å¥ªã—よã†ã¨ã—ã¦ã„ã‚‹ãŸã‚ã€ã“ã®ãƒ—ロジェクトを編集ã§ããªããªã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚\n本当ã«ç¶šã‘ã¾ã™ã‹ï¼Ÿ" + text_zoom_in: 拡大 + text_zoom_out: ç¸®å° + text_warn_on_leaving_unsaved: ã“ã®ãƒšãƒ¼ã‚¸ã‹ã‚‰ç§»å‹•ã™ã‚‹ã¨ã€ä¿å­˜ã•れã¦ã„ãªã„データãŒå¤±ã‚れã¾ã™ã€‚ + text_scm_path_encoding_note: "デフォルト: UTF-8" + text_mercurial_repository_note: "ローカルリãƒã‚¸ãƒˆãƒª (例: /hgrepo, c:\\hgrepo)" + text_git_repository_note: "Bareã€ã‹ã¤ã€ãƒ­ãƒ¼ã‚«ãƒ«ãƒªãƒã‚¸ãƒˆãƒª (例: /gitrepo, c:\\gitrepo)" + text_scm_command: コマンド + text_scm_command_version: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + text_scm_config: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã‚·ã‚¹ãƒ†ãƒ ã®ã‚³ãƒžãƒ³ãƒ‰ã‚’config/configuration.ymlã§è¨­å®šã§ãã¾ã™ã€‚設定後ã€Redmineã‚’å†èµ·å‹•ã—ã¦ãã ã•ã„。 + text_scm_command_not_available: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã‚·ã‚¹ãƒ†ãƒ ã®ã‚³ãƒžãƒ³ãƒ‰ãŒåˆ©ç”¨ã§ãã¾ã›ã‚“。管ç†ç”»é¢ã«ã¦è¨­å®šã‚’確èªã—ã¦ãã ã•ã„。 + + default_role_manager: 管ç†è€… + default_role_developer: 開発者 + default_role_reporter: 報告者 + default_tracker_bug: ãƒã‚° + default_tracker_feature: 機能 + default_tracker_support: サãƒãƒ¼ãƒˆ + default_issue_status_new: æ–°è¦ + default_issue_status_in_progress: 進行中 + default_issue_status_resolved: 解決 + default_issue_status_feedback: フィードãƒãƒƒã‚¯ + default_issue_status_closed: 終了 + default_issue_status_rejected: å´ä¸‹ + default_doc_category_user: ユーザー文書 + default_doc_category_tech: 技術文書 + default_priority_low: 低゠+ default_priority_normal: 通常 + default_priority_high: 高゠+ default_priority_urgent: 急ã„ã§ + default_priority_immediate: 今ã™ã + default_activity_design: 設計作業 + default_activity_development: 開発作業 + + enumeration_issue_priorities: ãƒã‚±ãƒƒãƒˆã®å„ªå…ˆåº¦ + enumeration_doc_categories: 文書カテゴリ + enumeration_activities: 作業分類 (時間トラッキング) + enumeration_system_activity: システム作業分類 + label_additional_workflow_transitions_for_assignee: ãƒã‚±ãƒƒãƒˆæ‹…当者ã«è¿½åŠ ã§è¨±å¯ã™ã‚‹é·ç§» + label_additional_workflow_transitions_for_author: ãƒã‚±ãƒƒãƒˆä½œæˆè€…ã«è¿½åŠ ã§è¨±å¯ã™ã‚‹é·ç§» + label_bulk_edit_selected_time_entries: 作業時間ã®ä¸€æ‹¬ç·¨é›† + text_time_entries_destroy_confirmation: 本当ã«é¸æŠžã—ãŸä½œæ¥­æ™‚間を削除ã—ã¾ã™ã‹ï¼Ÿ + + label_role_anonymous: 匿åユーザー + label_role_non_member: éžãƒ¡ãƒ³ãƒãƒ¼ + + label_issue_note_added: 注記ãŒè¿½åŠ ã•れã¾ã—㟠+ label_issue_status_updated: ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ãŒæ›´æ–°ã•れã¾ã—㟠+ label_issue_priority_updated: å„ªå…ˆåº¦ãŒæ›´æ–°ã•れã¾ã—㟠+ label_issues_visibility_own: 作æˆè€…ã‹æ‹…当者ã§ã‚ã‚‹ãƒã‚±ãƒƒãƒˆ + field_issues_visibility: 表示ã§ãã‚‹ãƒã‚±ãƒƒãƒˆ + label_issues_visibility_all: ã™ã¹ã¦ã®ãƒã‚±ãƒƒãƒˆ + permission_set_own_issues_private: 自分ã®ãƒã‚±ãƒƒãƒˆã‚’プライベートã«è¨­å®š + field_is_private: プライベート + permission_set_issues_private: ãƒã‚±ãƒƒãƒˆã‚’プライベートã«è¨­å®š + label_issues_visibility_public: プライベートãƒã‚±ãƒƒãƒˆä»¥å¤– + text_issues_destroy_descendants_confirmation: "%{count}個ã®å­ãƒã‚±ãƒƒãƒˆã‚‚削除ã•れã¾ã™ã€‚" + notice_issue_successful_create: ãƒã‚±ãƒƒãƒˆ %{id} ãŒä½œæˆã•れã¾ã—ãŸã€‚ + label_between: 次ã®ç¯„囲内 + setting_issue_group_assignment: グループã¸ã®ãƒã‚±ãƒƒãƒˆå‰²ã‚Šå½“ã¦ã‚’è¨±å¯ + description_query_sort_criteria_direction: é †åº + description_project_scope: 検索範囲 + description_filter: Filter + description_user_mail_notification: メール通知ã®è¨­å®š + description_date_from: é–‹å§‹æ—¥ + description_message_content: 内容 + description_available_columns: 利用ã§ãã‚‹é …ç›® + description_date_range_interval: æ—¥ä»˜ã§æŒ‡å®š + description_issue_category_reassign: æ–°ã—ã„ã‚«ãƒ†ã‚´ãƒªã‚’é¸æŠžã—ã¦ãã ã•ã„ + description_search: 検索キーワード + description_notes: 注記 + description_date_range_list: 一覧ã‹ã‚‰é¸æŠž + description_choose_project: プロジェクト + description_date_to: 終了日 + description_query_sort_criteria_attribute: é …ç›® + description_wiki_subpages_reassign: æ–°ã—ã„è¦ªãƒšãƒ¼ã‚¸ã‚’é¸æŠžã—ã¦ãã ã•ã„ + description_selected_columns: é¸æŠžã•れãŸé …ç›® + error_scm_annotate_big_text_file: テキストファイルサイズã®ä¸Šé™ã‚’è¶…ãˆã¦ã„ã‚‹ãŸã‚アノテートã§ãã¾ã›ã‚“。 + setting_default_issue_start_date_to_creation_date: ç¾åœ¨ã®æ—¥ä»˜ã‚’æ–°ã—ã„ãƒã‚±ãƒƒãƒˆã®é–‹å§‹æ—¥ã¨ã™ã‚‹ + button_edit_section: ã“ã®ã‚»ã‚¯ã‚·ãƒ§ãƒ³ã‚’編集 + description_all_columns: ã™ã¹ã¦ã®é …ç›® + button_export: エクスãƒãƒ¼ãƒˆ + label_export_options: "%{export_format} エクスãƒãƒ¼ãƒˆè¨­å®š" + error_attachment_too_big: ã“ã®ãƒ•ァイルã¯ã‚¢ãƒƒãƒ—ロードã§ãã¾ã›ã‚“。添付ファイルサイズã®ä¸Šé™(%{max_size})ã‚’è¶…ãˆã¦ã„ã¾ã™ã€‚ + notice_failed_to_save_time_entries: "å…¨%{total}件中%{count}ä»¶ã®ä½œæ¥­æ™‚é–“ãŒä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸ: %{ids}." + label_x_issues: + zero: 0 ãƒã‚±ãƒƒãƒˆ + one: 1 ãƒã‚±ãƒƒãƒˆ + other: "%{count} ãƒã‚±ãƒƒãƒˆ" + label_repository_new: æ–°ã—ã„リãƒã‚¸ãƒˆãƒª + field_repository_is_default: メインリãƒã‚¸ãƒˆãƒª + label_copy_attachments: 添付ファイルをコピー + label_item_position: "%{position}/%{count}" + label_completed_versions: 完了ã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ + text_project_identifier_info: ã‚¢ãƒ«ãƒ•ã‚¡ãƒ™ãƒƒãƒˆå°æ–‡å­—(a-z)・数字・ãƒã‚¤ãƒ•ン・アンダースコアãŒä½¿ãˆã¾ã™ã€‚最åˆã®æ–‡å­—ã¯ã‚¢ãƒ«ãƒ•ァベットã®å°æ–‡å­—ã«ã—ã¦ãã ã•ã„。
    識別å­ã¯å¾Œã§å¤‰æ›´ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。 + field_multiple: è¤‡æ•°é¸æŠžå¯ + setting_commit_cross_project_ref: ç•°ãªã‚‹ãƒ—ロジェクトã®ãƒã‚±ãƒƒãƒˆã®å‚ç…§/ä¿®æ­£ã‚’è¨±å¯ + text_issue_conflict_resolution_add_notes: 自分ã®ç·¨é›†å†…å®¹ã‚’ç ´æ£„ã—æ³¨è¨˜ã®ã¿è¿½åŠ  + text_issue_conflict_resolution_overwrite: 自分ã®ç·¨é›†å†…容ã®ä¿å­˜ã‚’強行 (ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®æ›´æ–°å†…å®¹ã¯æ³¨è¨˜ã‚’除ã上書ãã•れã¾ã™) + notice_issue_update_conflict: ã“ã®ãƒã‚±ãƒƒãƒˆã‚’編集中ã«ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒæ›´æ–°ã—ã¾ã—ãŸã€‚ + text_issue_conflict_resolution_cancel: 自分ã®ç·¨é›†å†…容を破棄㗠%{link} ã‚’å†è¡¨ç¤º + permission_manage_related_issues: 関連ã™ã‚‹ãƒã‚±ãƒƒãƒˆã®ç®¡ç† + field_auth_source_ldap_filter: LDAPフィルタ + label_search_for_watchers: ウォッãƒãƒ£ãƒ¼ã‚’検索ã—ã¦è¿½åŠ  + notice_account_deleted: アカウントãŒå‰Šé™¤ã•れã¾ã—ãŸã€‚ + setting_unsubscribe: ユーザーã«ã‚ˆã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆå‰Šé™¤ã‚’è¨±å¯ + button_delete_my_account: 自分ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’削除 + text_account_destroy_confirmation: |- + 本当ã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’削除ã—ã¾ã™ã‹ï¼Ÿ + ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯æ’ä¹…çš„ã«å‰Šé™¤ã•れã¾ã™ã€‚削除後ã«å†åº¦ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’有効ã«ã™ã‚‹æ‰‹æ®µã¯ã‚りã¾ã›ã‚“。 + error_session_expired: セッションãŒå¤±åйã—ã¾ã—ãŸã€‚ログインã—ç›´ã—ã¦ãã ã•ã„。 + text_session_expiration_settings: "警告: ã“ã®è¨­å®šã‚’変更ã™ã‚‹ã¨ç¾åœ¨æœ‰åйãªã‚»ãƒƒã‚·ãƒ§ãƒ³ãŒå¤±åйã™ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚" + setting_session_lifetime: æœ‰åŠ¹æœŸé–“ã®æœ€å¤§å€¤ + setting_session_timeout: ç„¡æ“作タイムアウト + label_session_expiration: セッション有効期間 + permission_close_project: プロジェクトã®çµ‚了/å†é–‹ + label_show_closed_projects: 終了ã—ãŸãƒ—ロジェクトを表示 + button_close: 終了 + button_reopen: å†é–‹ + project_status_active: 有効 + project_status_closed: 終了 + project_status_archived: アーカイブ + text_project_closed: ã“ã®ãƒ—ロジェクトã¯çµ‚了ã—ã¦ã„ã‚‹ãŸã‚読ã¿å–り専用ã§ã™ã€‚ + notice_user_successful_create: ユーザー %{id} を作æˆã—ã¾ã—ãŸã€‚ + field_core_fields: 標準フィールド + field_timeout: タイムアウト(ç§’å˜ä½) + setting_thumbnails_enabled: 添付ファイルã®ã‚µãƒ ãƒã‚¤ãƒ«ç”»åƒã‚’表示 + setting_thumbnails_size: サムãƒã‚¤ãƒ«ç”»åƒã®å¤§ãã•(ピクセルå˜ä½) + label_status_transitions: ステータスã®é·ç§» + label_fields_permissions: フィールドã«å¯¾ã™ã‚‹æ¨©é™ + label_readonly: 読ã¿å–り専用 + label_required: å¿…é ˆ + text_repository_identifier_info: ã‚¢ãƒ«ãƒ•ã‚¡ãƒ™ãƒƒãƒˆå°æ–‡å­—(a-z)・数字・ãƒã‚¤ãƒ•ン・アンダースコアãŒä½¿ãˆã¾ã™ã€‚
    識別å­ã¯å¾Œã§å¤‰æ›´ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。 + field_board_parent: 親フォーラム + label_attribute_of_project: プロジェクト㮠%{name} + label_attribute_of_author: 作æˆè€…ã® %{name} + label_attribute_of_assigned_to: 担当者㮠%{name} + label_attribute_of_fixed_version: 対象ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã® %{name} + label_copy_subtasks: å­ãƒã‚±ãƒƒãƒˆã‚’コピー + label_copied_to: コピー先 + label_copied_from: コピー元 + label_any_issues_in_project: 次ã®ãƒ—ロジェクト内ã®ãƒã‚±ãƒƒãƒˆ + label_any_issues_not_in_project: 次ã®ãƒ—ロジェクト外ã®ãƒã‚±ãƒƒãƒˆ + field_private_notes: プライベート注記 + permission_view_private_notes: プライベート注記ã®é–²è¦§ + permission_set_notes_private: 注記をプライベートã«è¨­å®š + label_no_issues_in_project: 次ã®ãƒ—ロジェクト内ã®ãƒã‚±ãƒƒãƒˆã‚’除ã + label_any: ã™ã¹ã¦ + label_last_n_weeks: ç›´è¿‘%{count}週間 + setting_cross_project_subtasks: ç•°ãªã‚‹ãƒ—ロジェクトã®ãƒã‚±ãƒƒãƒˆé–“ã®è¦ªå­é–¢ä¿‚ã‚’è¨±å¯ + label_cross_project_descendants: サブプロジェクトå˜ä½ + label_cross_project_tree: プロジェクトツリーå˜ä½ + label_cross_project_hierarchy: プロジェクト階層å˜ä½ + label_cross_project_system: ã™ã¹ã¦ã®ãƒ—ロジェクト + button_hide: éš ã™ + setting_non_working_week_days: 休業日 + label_in_the_next_days: 今後○日 + label_in_the_past_days: éŽåŽ»â—‹æ—¥ + label_attribute_of_user: ユーザー㮠%{name} + text_turning_multiple_off: ã“ã®è¨­å®šã‚’無効ã«ã™ã‚‹ã¨ã€è¤‡æ•°é¸æŠžã•れã¦ã„る値ã®ã†ã¡1個ã ã‘ãŒä¿æŒã•れ残りã¯é¸æŠžè§£é™¤ã•れã¾ã™ã€‚ + label_attribute_of_issue: ãƒã‚±ãƒƒãƒˆã® %{name} + permission_add_documents: 文書ã®è¿½åŠ  + permission_edit_documents: 文書ã®ç·¨é›† + permission_delete_documents: 文書ã®å‰Šé™¤ + setting_jsonp_enabled: JSONPを有効ã«ã™ã‚‹ + field_inherit_members: メンãƒãƒ¼ã‚’継承 + field_closed_on: 終了日 + field_generate_password: ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ã‚’è‡ªå‹•ç”Ÿæˆ + label_total_time: åˆè¨ˆ + notice_account_not_activated_yet: ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒæœ‰åŠ¹åŒ–ã•れã¦ã„ã¾ã›ã‚“。アカウントを有効ã«ã™ã‚‹ãŸã‚ã®ãƒ¡ãƒ¼ãƒ«ã‚’ã‚‚ã†ä¸€åº¦å—ä¿¡ã—ãŸã„ã¨ãã¯ã“ã®ãƒªãƒ³ã‚¯ã‚’クリックã—ã¦ãã ã•ã„。 + notice_account_locked: アカウントãŒãƒ­ãƒƒã‚¯ã•れã¦ã„ã¾ã™ + label_hidden: éžè¡¨ç¤º + label_visibility_private: 自分ã®ã¿ + label_visibility_roles: 以下ã®ãƒ­ãƒ¼ãƒ«ã®ã¿ + label_visibility_public: ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ + field_must_change_passwd: 次回ログイン時ã«ãƒ‘スワード変更を強制 + notice_new_password_must_be_different: æ–°ã—ã„パスワードã¯ç¾åœ¨ã®ãƒ‘スワードã¨ç•°ãªã‚‹ã‚‚ã®ã§ãªã‘れã°ãªã‚Šã¾ã›ã‚“ + setting_mail_handler_excluded_filenames: 除外ã™ã‚‹æ·»ä»˜ãƒ•ァイルå + text_convert_available: ImageMagickã®convertコマンドãŒåˆ©ç”¨å¯èƒ½ (オプション) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cd/cdcaf09edb26a939ee34d0a3f6367f9c2030416c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cd/cdcaf09edb26a939ee34d0a3f6367f9c2030416c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki æ ¼å¼è¨­å®š + + + + +

    Wiki 語法快速å°ç…§è¡¨

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    字型樣å¼
    粗體(加強語氣)*粗體(加強語氣)*粗體(加強語氣)
    斜體_斜體_斜體
    底線+底線+底線
    刪除線-刪除線-刪除線
    ??引文??引文
    內嵌程å¼ç¢¼@內嵌程å¼ç¢¼@內嵌程å¼ç¢¼ (inline code)
    é å…ˆæ ¼å¼åŒ–çš„æ®µè½æ–‡å­—<pre>
     æ ¼å¼åŒ–
     çš„æ®µè½
    </pre>
    +
    + æ ¼å¼åŒ–
    + 的段è½
    +
    +
    清單
    ä¸æŽ’åºæ¸…å–®* 清單項目 1
    * 清單項目 2
    • 清單項目 1
    • 清單項目 2
    æŽ’åºæ¸…å–®# 清單項目 1
    # 清單項目 2
    1. 清單項目 1
    2. 清單項目 2
    標題
    標題 1h1. 標題 1

    標題 1

    標題 2h2. 標題 2

    標題 2

    標題 3h3. 標題 3

    標題 3

    連çµ
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine 連çµ
    連çµè‡³ä¸€å€‹ Wiki é é¢[[Wiki é é¢]]Wiki é é¢
    å•題 #12å•題 #12
    版次 r43版次 r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    內置圖åƒ
    圖åƒ!圖åƒ_url!
    !附加_圖åƒ!
    + +

    更多資訊

    + + + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cd/cdd5355f3247a131962bb1b3e4aef9c1b10eacdb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cd/cdd5355f3247a131962bb1b3e4aef9c1b10eacdb.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,71 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class MemberRole < ActiveRecord::Base + belongs_to :member + belongs_to :role + + after_destroy :remove_member_if_empty + + after_create :add_role_to_group_users, :add_role_to_subprojects + after_destroy :remove_inherited_roles + + validates_presence_of :role + validate :validate_role_member + + def validate_role_member + errors.add :role_id, :invalid if role && !role.member? + end + + def inherited? + !inherited_from.nil? + end + + private + + def remove_member_if_empty + if member.roles.empty? + member.destroy + end + end + + def add_role_to_group_users + if member.principal.is_a?(Group) && !inherited? + member.principal.users.each do |user| + user_member = Member.find_or_new(member.project_id, user.id) + user_member.member_roles << MemberRole.new(:role => role, :inherited_from => id) + user_member.save! + end + end + end + + def add_role_to_subprojects + member.project.children.each do |subproject| + if subproject.inherit_members? + child_member = Member.find_or_new(subproject.id, member.user_id) + child_member.member_roles << MemberRole.new(:role => role, :inherited_from => id) + child_member.save! + end + end + end + + def remove_inherited_roles + MemberRole.where(:inherited_from => id).all.group_by(&:member).each do |member, member_roles| + member_roles.each(&:destroy) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ce/ce01f810d3a29f63f33f44d575176d9d1fd50f81.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/ce01f810d3a29f63f33f44d575176d9d1fd50f81.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ +<%= board_breadcrumb(@board) %> + +
    +<%= link_to l(:label_message_new), + new_board_message_path(@board), + :class => 'icon icon-add', + :onclick => 'showAndScrollTo("add-message", "message_subject"); return false;' if User.current.allowed_to?(:add_messages, @board.project) %> +<%= watcher_link(@board, User.current) %> +
    + + + +

    <%=h @board.name %>

    +

    <%=h @board.description %>

    + +<% if @topics.any? %> + + + + + <%= sort_header_tag('created_on', :caption => l(:field_created_on)) %> + <%= sort_header_tag('replies', :caption => l(:label_reply_plural)) %> + <%= sort_header_tag('updated_on', :caption => l(:label_message_last)) %> + + + <% @topics.each do |topic| %> + + + + + + + + <% end %> + +
    <%= l(:field_subject) %><%= l(:field_author) %>
    <%= link_to h(topic.subject), board_message_path(@board, topic) %><%= link_to_user(topic.author) %><%= format_time(topic.created_on) %><%= topic.replies_count %> + <% if topic.last_reply %> + <%= authoring topic.last_reply.created_on, topic.last_reply.author %>
    + <%= link_to_message topic.last_reply %> + <% end %> +
    +

    <%= pagination_links_full @topic_pages, @topic_count %>

    +<% else %> +

    <%= l(:label_no_data) %>

    +<% end %> + +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> +<% end %> + +<% html_title @board.name %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ce/ce029d1276c9c02edcd3a28e6f067a6b863bd8d9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/ce029d1276c9c02edcd3a28e6f067a6b863bd8d9.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,27 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class RoutingMailHandlerTest < ActionController::IntegrationTest + def test_mail_handler + assert_routing( + { :method => "post", :path => "/mail_handler" }, + { :controller => 'mail_handler', :action => 'index' } + ) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ce/ce1d1b7dae18c3f2642bcd49171987cc00fe9c59.svn-base --- a/.svn/pristine/ce/ce1d1b7dae18c3f2642bcd49171987cc00fe9c59.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -source 'http://rubygems.org' - -gem "rails", "3.2.13" -gem "jquery-rails", "~> 2.0.2" -gem "i18n", "~> 0.6.0" -gem "coderay", "~> 1.0.6" -gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] -gem "builder", "3.0.0" - -# Optional gem for LDAP authentication -group :ldap do - gem "net-ldap", "~> 0.3.1" -end - -# Optional gem for OpenID authentication -group :openid do - gem "ruby-openid", "~> 2.1.4", :require => "openid" - gem "rack-openid" -end - -# Optional gem for exporting the gantt to a PNG file, not supported with jruby -platforms :mri, :mingw do - group :rmagick do - # RMagick 2 supports ruby 1.9 - # RMagick 1 would be fine for ruby 1.8 but Bundler does not support - # different requirements for the same gem on different platforms - gem "rmagick", ">= 2.0.0" - end -end - -# Database gems -platforms :mri, :mingw do - group :postgresql do - gem "pg", ">= 0.11.0" - end - - group :sqlite do - gem "sqlite3" - end -end - -platforms :mri_18, :mingw_18 do - group :mysql do - gem "mysql", "~> 2.8.1" - end -end - -platforms :mri_19, :mingw_19 do - group :mysql do - gem "mysql2", "~> 0.3.11" - end -end - -platforms :jruby do - gem "jruby-openssl" - - group :mysql do - gem "activerecord-jdbcmysql-adapter" - end - - group :postgresql do - gem "activerecord-jdbcpostgresql-adapter" - end - - group :sqlite do - gem "activerecord-jdbcsqlite3-adapter" - end -end - -group :development do - gem "rdoc", ">= 2.4.2" - gem "yard" -end - -group :test do - gem "shoulda", "~> 2.11" - # Shoulda does not work nice on Ruby 1.9.3 and JRuby 1.7. - # It seems to need test-unit explicitely. - platforms = [:mri_19] - platforms << :jruby if defined?(JRUBY_VERSION) && JRUBY_VERSION >= "1.7" - gem "test-unit", :platforms => platforms - gem "mocha", "~> 0.13.3" -end - -local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") -if File.exists?(local_gemfile) - puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v` - instance_eval File.read(local_gemfile) -end - -# Load plugins' Gemfiles -Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file| - puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v` - instance_eval File.read(file) -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ce/ce296b72f23f19005cbacbf0993e6db53bb2b243.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/ce296b72f23f19005cbacbf0993e6db53bb2b243.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,128 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class WorkflowsController < ApplicationController + layout 'admin' + + before_filter :require_admin, :find_roles, :find_trackers + + def index + @workflow_counts = WorkflowTransition.count_by_tracker_and_role + end + + def edit + @role = Role.find_by_id(params[:role_id]) if params[:role_id] + @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] + + if request.post? + WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) + (params[:issue_status] || []).each { |status_id, transitions| + transitions.each { |new_status_id, options| + author = options.is_a?(Array) && options.include?('author') && !options.include?('always') + assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') + WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee) + } + } + if @role.save + redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) + return + end + end + + @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) + if @tracker && @used_statuses_only && @tracker.issue_statuses.any? + @statuses = @tracker.issue_statuses + end + @statuses ||= IssueStatus.sorted.all + + if @tracker && @role && @statuses.any? + workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all + @workflows = {} + @workflows['always'] = workflows.select {|w| !w.author && !w.assignee} + @workflows['author'] = workflows.select {|w| w.author} + @workflows['assignee'] = workflows.select {|w| w.assignee} + end + end + + def permissions + @role = Role.find_by_id(params[:role_id]) if params[:role_id] + @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] + + if request.post? && @role && @tracker + WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {}) + redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) + return + end + + @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) + if @tracker && @used_statuses_only && @tracker.issue_statuses.any? + @statuses = @tracker.issue_statuses + end + @statuses ||= IssueStatus.sorted.all + + if @role && @tracker + @fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]} + @custom_fields = @tracker.custom_fields + + @permissions = WorkflowPermission.where(:tracker_id => @tracker.id, :role_id => @role.id).all.inject({}) do |h, w| + h[w.old_status_id] ||= {} + h[w.old_status_id][w.field_name] = w.rule + h + end + @statuses.each {|status| @permissions[status.id] ||= {}} + end + end + + def copy + + if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' + @source_tracker = nil + else + @source_tracker = Tracker.find_by_id(params[:source_tracker_id].to_i) + end + if params[:source_role_id].blank? || params[:source_role_id] == 'any' + @source_role = nil + else + @source_role = Role.find_by_id(params[:source_role_id].to_i) + end + + @target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids]) + @target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids]) + + if request.post? + if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) + flash.now[:error] = l(:error_workflow_copy_source) + elsif @target_trackers.blank? || @target_roles.blank? + flash.now[:error] = l(:error_workflow_copy_target) + else + WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles) + flash[:notice] = l(:notice_successful_update) + redirect_to workflows_copy_path(:source_tracker_id => @source_tracker, :source_role_id => @source_role) + end + end + end + + private + + def find_roles + @roles = Role.sorted.all + end + + def find_trackers + @trackers = Tracker.sorted.all + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ce/ce353587e94a2173fc11172d92c51303bcbfa3ef.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/ce353587e94a2173fc11172d92c51303bcbfa3ef.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,166 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Hook + @@listener_classes = [] + @@listeners = nil + @@hook_listeners = {} + + class << self + # Adds a listener class. + # Automatically called when a class inherits from Redmine::Hook::Listener. + def add_listener(klass) + raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton) + @@listener_classes << klass + clear_listeners_instances + end + + # Returns all the listerners instances. + def listeners + @@listeners ||= @@listener_classes.collect {|listener| listener.instance} + end + + # Returns the listeners instances for the given hook. + def hook_listeners(hook) + @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)} + end + + # Clears all the listeners. + def clear_listeners + @@listener_classes = [] + clear_listeners_instances + end + + # Clears all the listeners instances. + def clear_listeners_instances + @@listeners = nil + @@hook_listeners = {} + end + + # Calls a hook. + # Returns the listeners response. + def call_hook(hook, context={}) + [].tap do |response| + hls = hook_listeners(hook) + if hls.any? + hls.each {|listener| response << listener.send(hook, context)} + end + end + end + end + + # Base class for hook listeners. + class Listener + include Singleton + include Redmine::I18n + + # Registers the listener + def self.inherited(child) + Redmine::Hook.add_listener(child) + super + end + + end + + # Listener class used for views hooks. + # Listeners that inherit this class will include various helpers by default. + class ViewListener < Listener + include ERB::Util + include ActionView::Helpers::TagHelper + include ActionView::Helpers::FormHelper + include ActionView::Helpers::FormTagHelper + include ActionView::Helpers::FormOptionsHelper + include ActionView::Helpers::JavaScriptHelper + include ActionView::Helpers::NumberHelper + include ActionView::Helpers::UrlHelper + include ActionView::Helpers::AssetTagHelper + include ActionView::Helpers::TextHelper + include Rails.application.routes.url_helpers + include ApplicationHelper + + # Default to creating links using only the path. Subclasses can + # change this default as needed + def self.default_url_options + {:only_path => true } + end + + # Helper method to directly render a partial using the context: + # + # class MyHook < Redmine::Hook::ViewListener + # render_on :view_issues_show_details_bottom, :partial => "show_more_data" + # end + # + def self.render_on(hook, options={}) + define_method hook do |context| + if context[:hook_caller].respond_to?(:render) + context[:hook_caller].send(:render, {:locals => context}.merge(options)) + elsif context[:controller].is_a?(ActionController::Base) + context[:controller].send(:render_to_string, {:locals => context}.merge(options)) + else + raise "Cannot render #{self.name} hook from #{context[:hook_caller].class.name}" + end + end + end + + def controller + nil + end + + def config + ActionController::Base.config + end + end + + # Helper module included in ApplicationHelper and ActionController so that + # hooks can be called in views like this: + # + # <%= call_hook(:some_hook) %> + # <%= call_hook(:another_hook, :foo => 'bar') %> + # + # Or in controllers like: + # call_hook(:some_hook) + # call_hook(:another_hook, :foo => 'bar') + # + # Hooks added to views will be concatenated into a string. Hooks added to + # controllers will return an array of results. + # + # Several objects are automatically added to the call context: + # + # * project => current project + # * request => Request instance + # * controller => current Controller instance + # * hook_caller => object that called the hook + # + module Helper + def call_hook(hook, context={}) + if is_a?(ActionController::Base) + default_context = {:controller => self, :project => @project, :request => request, :hook_caller => self} + Redmine::Hook.call_hook(hook, default_context.merge(context)) + else + default_context = { :project => @project, :hook_caller => self } + default_context[:controller] = controller if respond_to?(:controller) + default_context[:request] = request if respond_to?(:request) + Redmine::Hook.call_hook(hook, default_context.merge(context)).join(' ').html_safe + end + end + end + end +end + +ApplicationHelper.send(:include, Redmine::Hook::Helper) +ActionController::Base.send(:include, Redmine::Hook::Helper) diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ce/cea51a8cc1d29add5095e952d8b4a92de6169347.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/cea51a8cc1d29add5095e952d8b4a92de6169347.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,242 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'redmine/scm/adapters/abstract_adapter' +require 'rexml/document' + +module Redmine + module Scm + module Adapters + class DarcsAdapter < AbstractAdapter + # Darcs executable name + DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs" + + class << self + def client_command + @@bin ||= DARCS_BIN + end + + def sq_bin + @@sq_bin ||= shell_quote_command + end + + def client_version + @@client_version ||= (darcs_binary_version || []) + end + + def client_available + !client_version.empty? + end + + def darcs_binary_version + darcsversion = darcs_binary_version_from_command_line.dup + if darcsversion.respond_to?(:force_encoding) + darcsversion.force_encoding('ASCII-8BIT') + end + if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)}) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def darcs_binary_version_from_command_line + shellout("#{sq_bin} --version") { |io| io.read }.to_s + end + end + + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) + @url = url + @root_url = url + end + + def supports_cat? + # cat supported in darcs 2.0.0 and higher + self.class.client_version_above?([2, 0, 0]) + end + + # Get info about the darcs repository + def info + rev = revisions(nil,nil,nil,{:limit => 1}) + rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + def entries(path=nil, identifier=nil, options={}) + path_prefix = (path.blank? ? '' : "#{path}/") + if path.blank? + path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' ) + end + entries = Entries.new + cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --xml-output" + cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier + cmd << " #{shell_quote path}" + shellout(cmd) do |io| + begin + doc = REXML::Document.new(io) + if doc.root.name == 'directory' + doc.elements.each('directory/*') do |element| + next unless ['file', 'directory'].include? element.name + entries << entry_from_xml(element, path_prefix) + end + elsif doc.root.name == 'file' + entries << entry_from_xml(doc.root, path_prefix) + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + entries.compact! + entries.sort_by_name + end + + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + path = '.' if path.blank? + revisions = Revisions.new + cmd = "#{self.class.sq_bin} changes --repodir #{shell_quote @url} --xml-output" + cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from + cmd << " --last #{options[:limit].to_i}" if options[:limit] + shellout(cmd) do |io| + begin + doc = REXML::Document.new(io) + doc.elements.each("changelog/patch") do |patch| + message = patch.elements['name'].text + message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment'] + revisions << Revision.new({:identifier => nil, + :author => patch.attributes['author'], + :scmid => patch.attributes['hash'], + :time => Time.parse(patch.attributes['local_date']), + :message => message, + :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil) + }) + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + revisions + end + + def diff(path, identifier_from, identifier_to=nil) + path = '*' if path.blank? + cmd = "#{self.class.sq_bin} diff --repodir #{shell_quote @url}" + if identifier_to.nil? + cmd << " --match #{shell_quote("hash #{identifier_from}")}" + else + cmd << " --to-match #{shell_quote("hash #{identifier_from}")}" + cmd << " --from-match #{shell_quote("hash #{identifier_to}")}" + end + cmd << " -u #{shell_quote path}" + diff = [] + shellout(cmd) do |io| + io.each_line do |line| + diff << line + end + end + return nil if $? && $?.exitstatus != 0 + diff + end + + def cat(path, identifier=nil) + cmd = "#{self.class.sq_bin} show content --repodir #{shell_quote @url}" + cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier + cmd << " #{shell_quote path}" + cat = nil + shellout(cmd) do |io| + io.binmode + cat = io.read + end + return nil if $? && $?.exitstatus != 0 + cat + end + + private + + # Returns an Entry from the given XML element + # or nil if the entry was deleted + def entry_from_xml(element, path_prefix) + modified_element = element.elements['modified'] + if modified_element.elements['modified_how'].text.match(/removed/) + return nil + end + + Entry.new({:name => element.attributes['name'], + :path => path_prefix + element.attributes['name'], + :kind => element.name == 'file' ? 'file' : 'dir', + :size => nil, + :lastrev => Revision.new({ + :identifier => nil, + :scmid => modified_element.elements['patch'].attributes['hash'] + }) + }) + end + + def get_paths_for_patch(hash) + paths = get_paths_for_patch_raw(hash) + if self.class.client_version_above?([2, 4]) + orig_paths = paths + paths = [] + add_paths = [] + add_paths_name = [] + mod_paths = [] + other_paths = [] + orig_paths.each do |path| + if path[:action] == 'A' + add_paths << path + add_paths_name << path[:path] + elsif path[:action] == 'M' + mod_paths << path + else + other_paths << path + end + end + add_paths_name.each do |add_path| + mod_paths.delete_if { |m| m[:path] == add_path } + end + paths.concat add_paths + paths.concat mod_paths + paths.concat other_paths + end + paths + end + + # Retrieve changed paths for a single patch + def get_paths_for_patch_raw(hash) + cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --summary --xml-output" + cmd << " --match #{shell_quote("hash #{hash}")} " + paths = [] + shellout(cmd) do |io| + begin + # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7) + # A root element is added so that REXML doesn't raise an error + doc = REXML::Document.new("" + io.read + "") + doc.elements.each('fake_root/summary/*') do |modif| + paths << {:action => modif.name[0,1].upcase, + :path => "/" + modif.text.chomp.gsub(/^\s*/, '') + } + end + rescue + end + end + paths + rescue CommandFailed + paths + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ce/ceab695f77fe3ebbfee36cd481f3a4c77d758f4f.svn-base --- a/.svn/pristine/ce/ceab695f77fe3ebbfee36cd481f3a4c77d758f4f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -<%= form_tag({:action => 'edit', :tab => 'projects'}) do %> - -
    -

    <%= setting_check_box :default_projects_public %>

    - -

    <%= setting_multiselect(:default_projects_modules, - Redmine::AccessControl.available_project_modules.collect {|m| [l_or_humanize(m, :prefix => "project_module_"), m.to_s]}) %>

    - -

    <%= setting_check_box :sequential_project_identifiers %>

    - -

    <%= setting_select :new_project_user_role_id, - Role.find_all_givable.collect {|r| [r.name, r.id.to_s]}, - :blank => "--- #{l(:actionview_instancetag_blank_option)} ---" %>

    -
    - -<%= submit_tag l(:button_save) %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ce/cebffaae753a7cc849c7601935577fcde31c4695.svn-base --- a/.svn/pristine/ce/cebffaae753a7cc849c7601935577fcde31c4695.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -
    - <%= link_to l(:label_version_new), new_project_version_path(@project), :class => 'icon icon-add' if User.current.allowed_to?(:manage_versions, @project) %> -
    - -

    <%=l(:label_roadmap)%>

    - -<% if @versions.empty? %> -

    <%= l(:label_no_data) %>

    -<% else %> -
    -<% @versions.each do |version| %> -

    <%= link_to_version version, :name => version_anchor(version) %>

    - <%= render :partial => 'versions/overview', :locals => {:version => version} %> - <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %> - - <% if (issues = @issues_by_version[version]) && issues.size > 0 %> - <%= form_tag({}) do -%> - - - <% issues.each do |issue| -%> - - - - - <% end -%> - - <% end %> - <% end %> - <%= call_hook :view_projects_roadmap_version_bottom, :version => version %> -<% end %> -
    -<% end %> - -<% content_for :sidebar do %> -<%= form_tag({}, :method => :get) do %> -

    <%= l(:label_roadmap) %>

    -<% @trackers.each do |tracker| %> -
    -<% end %> -
    - -<% if @project.descendants.active.any? %> - <%= hidden_field_tag 'with_subprojects', 0 %> -
    -<% end %> -

    <%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %>

    -<% end %> - -

    <%= l(:label_version_plural) %>

    -<% @versions.each do |version| %> -<%= link_to format_version_name(version), "##{version_anchor(version)}" %>
    -<% end %> -<% if @completed_versions.present? %> -

    - <%= link_to_function l(:label_completed_versions), - '$("#toggle-completed-versions").toggleClass("collapsed"); $("#completed-versions").toggle()', - :id => 'toggle-completed-versions', :class => 'collapsible collapsed' %>
    - -

    -<% end %> -<% end %> - -<% html_title(l(:label_roadmap)) %> - -<%= context_menu issues_context_menu_path %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ce/cef18514271092169d5d166f4163b28499491884.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/cef18514271092169d5d166f4163b28499491884.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,178 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::Hook::ManagerTest < ActionView::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, + :groups_users, + :trackers, :projects_trackers, + :enabled_modules, + :versions, + :issue_statuses, :issue_categories, :issue_relations, + :enumerations, + :issues + + # Some hooks that are manually registered in these tests + class TestHook < Redmine::Hook::ViewListener; end + + class TestHook1 < TestHook + def view_layouts_base_html_head(context) + 'Test hook 1 listener.' + end + end + + class TestHook2 < TestHook + def view_layouts_base_html_head(context) + 'Test hook 2 listener.' + end + end + + class TestHook3 < TestHook + def view_layouts_base_html_head(context) + "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}." + end + end + + class TestLinkToHook < TestHook + def view_layouts_base_html_head(context) + link_to('Issues', :controller => 'issues') + end + end + + class TestHookHelperController < ActionController::Base + include Redmine::Hook::Helper + end + + class TestHookHelperView < ActionView::Base + include Redmine::Hook::Helper + end + + Redmine::Hook.clear_listeners + + def setup + @hook_module = Redmine::Hook + end + + def teardown + @hook_module.clear_listeners + end + + def test_clear_listeners + assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size + @hook_module.add_listener(TestHook1) + @hook_module.add_listener(TestHook2) + assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size + + @hook_module.clear_listeners + assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size + end + + def test_add_listener + assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size + @hook_module.add_listener(TestHook1) + assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size + end + + def test_call_hook + @hook_module.add_listener(TestHook1) + assert_equal ['Test hook 1 listener.'], hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_with_context + @hook_module.add_listener(TestHook3) + assert_equal ['Context keys: bar, controller, foo, hook_caller, project, request.'], + hook_helper.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a') + end + + def test_call_hook_with_multiple_listeners + @hook_module.add_listener(TestHook1) + @hook_module.add_listener(TestHook2) + assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], hook_helper.call_hook(:view_layouts_base_html_head) + end + + # Context: Redmine::Hook::Helper.call_hook default_url + def test_call_hook_default_url_options + @hook_module.add_listener(TestLinkToHook) + + assert_equal ['Issues'], hook_helper.call_hook(:view_layouts_base_html_head) + end + + # Context: Redmine::Hook::Helper.call_hook + def test_call_hook_with_project_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /project/i, hook_helper.call_hook(:view_layouts_base_html_head)[0] + end + + def test_call_hook_from_controller_with_controller_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /controller/i, hook_helper.call_hook(:view_layouts_base_html_head)[0] + end + + def test_call_hook_from_controller_with_request_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /request/i, hook_helper.call_hook(:view_layouts_base_html_head)[0] + end + + def test_call_hook_from_view_with_project_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /project/i, view_hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_from_view_with_controller_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /controller/i, view_hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_from_view_with_request_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /request/i, view_hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_from_view_should_join_responses_with_a_space + @hook_module.add_listener(TestHook1) + @hook_module.add_listener(TestHook2) + assert_equal 'Test hook 1 listener. Test hook 2 listener.', + view_hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_should_not_change_the_default_url_for_email_notifications + issue = Issue.find(1) + + ActionMailer::Base.deliveries.clear + Mailer.deliver_issue_add(issue) + mail = ActionMailer::Base.deliveries.last + + @hook_module.add_listener(TestLinkToHook) + hook_helper.call_hook(:view_layouts_base_html_head) + + ActionMailer::Base.deliveries.clear + Mailer.deliver_issue_add(issue) + mail2 = ActionMailer::Base.deliveries.last + + assert_equal mail_body(mail), mail_body(mail2) + end + + def hook_helper + @hook_helper ||= TestHookHelperController.new + end + + def view_hook_helper + @view_hook_helper ||= TestHookHelperView.new(Rails.root.to_s + '/app/views') + end +end + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cf0bca266fbc21a03db30e949b23e76b71b26da6.svn-base --- a/.svn/pristine/cf/cf0bca266fbc21a03db30e949b23e76b71b26da6.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::TokenAuthenticationTest < ActionController::IntegrationTest - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows - - def setup - Setting.rest_api_enabled = '1' - Setting.login_required = '1' - end - - def teardown - Setting.rest_api_enabled = '0' - Setting.login_required = '0' - end - - # Using the NewsController because it's a simple API. - context "get /news" do - context "in :xml format" do - should_allow_key_based_auth(:get, "/news.xml") - end - - context "in :json format" do - should_allow_key_based_auth(:get, "/news.json") - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cf164880642b001e32b75655aabc9764c7865713.svn-base --- a/.svn/pristine/cf/cf164880642b001e32b75655aabc9764c7865713.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -require 'test/unit' - -unless defined?(ActiveRecord) - plugin_root = File.join(File.dirname(__FILE__), '..') - - # first look for a symlink to a copy of the framework - if framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p } - puts "found framework root: #{framework_root}" - # this allows for a plugin to be tested outside an app - $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib" - else - # is the plugin installed in an application? - app_root = plugin_root + '/../../..' - - if File.directory? app_root + '/config' - puts 'using config/boot.rb' - ENV['RAILS_ENV'] = 'test' - require File.expand_path(app_root + '/config/boot') - else - # simply use installed gems if available - puts 'using rubygems' - require 'rubygems' - gem 'actionpack'; gem 'activerecord' - end - end - - %w(action_pack active_record action_controller active_record/fixtures action_controller/test_process).each {|f| require f} - - Dependencies.load_paths.unshift "#{plugin_root}/lib" -end - -# Define the connector -class ActiveRecordTestConnector - cattr_accessor :able_to_connect - cattr_accessor :connected - - # Set our defaults - self.connected = false - self.able_to_connect = true - - class << self - def setup - unless self.connected || !self.able_to_connect - setup_connection - load_schema - require_fixture_models - self.connected = true - end - rescue Exception => e # errors from ActiveRecord setup - $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}" - #$stderr.puts " #{e.backtrace.join("\n ")}\n" - self.able_to_connect = false - end - - private - - def setup_connection - if Object.const_defined?(:ActiveRecord) - defaults = { :database => ':memory:' } - begin - options = defaults.merge :adapter => 'sqlite3', :timeout => 500 - ActiveRecord::Base.establish_connection(options) - ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options } - ActiveRecord::Base.connection - rescue Exception # errors from establishing a connection - $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.' - options = defaults.merge :adapter => 'sqlite' - ActiveRecord::Base.establish_connection(options) - ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options } - ActiveRecord::Base.connection - end - - Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE) - else - raise "Can't setup connection since ActiveRecord isn't loaded." - end - end - - # Load actionpack sqlite tables - def load_schema - File.read(File.dirname(__FILE__) + "/fixtures/schema.sql").split(';').each do |sql| - ActiveRecord::Base.connection.execute(sql) unless sql.blank? - end - end - - def require_fixture_models - Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f} - end - end -end - -# Test case for inheritance -class ActiveRecordTestCase < Test::Unit::TestCase - # Set our fixture path - if ActiveRecordTestConnector.able_to_connect - self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/" - self.use_transactional_fixtures = false - end - - def self.fixtures(*args) - super if ActiveRecordTestConnector.connected - end - - def run(*args) - super if ActiveRecordTestConnector.connected - end - - # Default so Test::Unit::TestCase doesn't complain - def test_truth - end -end - -ActiveRecordTestConnector.setup -ActionController::Routing::Routes.reload rescue nil -ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cf1f7ec64f7ca44a3fcc2b553d180407341c51aa.svn-base --- a/.svn/pristine/cf/cf1f7ec64f7ca44a3fcc2b553d180407341c51aa.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -

    <%=l(:label_spent_time)%> (<%= l(:label_last_n_days, 7) %>)

    -<% -entries = TimeEntry.find(:all, - :conditions => ["#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", @user.id, Date.today - 6, Date.today], - :include => [:activity, :project, {:issue => [:tracker, :status]}], - :order => "#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC") -entries_by_day = entries.group_by(&:spent_on) -%> - -
    -

    <%= l(:label_total) %>: <%= html_hours("%.2f" % entries.sum(&:hours).to_f) %>

    -
    - -<% if entries.any? %> - - - - - - - - - -<% entries_by_day.keys.sort.reverse.each do |day| %> - - - - - - - <% entries_by_day[day].each do |entry| -%> - - - - - - - - <% end -%> -<% end -%> - -
    <%= l(:label_activity) %><%= l(:label_project) %><%= l(:field_comments) %><%= l(:field_hours) %>
    <%= day == Date.today ? l(:label_today).titleize : format_date(day) %><%= html_hours("%.2f" % entries_by_day[day].sum(&:hours).to_f) %>
    <%=h entry.activity %><%=h entry.project %> <%= h(' - ') + link_to_issue(entry.issue, :truncate => 50) if entry.issue %><%=h entry.comments %><%= html_hours("%.2f" % entry.hours) %> - <% if entry.editable_by?(@user) -%> - <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry}, - :title => l(:button_edit) %> - <%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => entry}, - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:button_delete) %> - <% end -%> -
    -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cf33cf57a3e921509ff6e96342de19ecb5f2697c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cf/cf33cf57a3e921509ff6e96342de19ecb5f2697c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,202 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class GroupsControllerTest < ActionController::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, :groups_users + + def setup + @request.session[:user_id] = 1 + end + + def test_index + get :index + assert_response :success + assert_template 'index' + end + + def test_show + get :show, :id => 10 + assert_response :success + assert_template 'show' + end + + def test_show_invalid_should_return_404 + get :show, :id => 99 + assert_response 404 + end + + def test_new + get :new + assert_response :success + assert_template 'new' + assert_select 'input[name=?]', 'group[name]' + end + + def test_create + assert_difference 'Group.count' do + post :create, :group => {:name => 'New group'} + end + assert_redirected_to '/groups' + group = Group.first(:order => 'id DESC') + assert_equal 'New group', group.name + assert_equal [], group.users + end + + def test_create_and_continue + assert_difference 'Group.count' do + post :create, :group => {:name => 'New group'}, :continue => 'Create and continue' + end + assert_redirected_to '/groups/new' + group = Group.first(:order => 'id DESC') + assert_equal 'New group', group.name + end + + def test_create_with_failure + assert_no_difference 'Group.count' do + post :create, :group => {:name => ''} + end + assert_response :success + assert_template 'new' + end + + def test_edit + get :edit, :id => 10 + assert_response :success + assert_template 'edit' + + assert_select 'div#tab-content-users' + assert_select 'div#tab-content-memberships' do + assert_select 'a', :text => 'Private child of eCookbook' + end + end + + def test_update + new_name = 'New name' + put :update, :id => 10, :group => {:name => new_name} + assert_redirected_to '/groups' + group = Group.find(10) + assert_equal new_name, group.name + end + + def test_update_with_failure + put :update, :id => 10, :group => {:name => ''} + assert_response :success + assert_template 'edit' + end + + def test_destroy + assert_difference 'Group.count', -1 do + post :destroy, :id => 10 + end + assert_redirected_to '/groups' + end + + def test_add_users + assert_difference 'Group.find(10).users.count', 2 do + post :add_users, :id => 10, :user_ids => ['2', '3'] + end + end + + def test_xhr_add_users + assert_difference 'Group.find(10).users.count', 2 do + xhr :post, :add_users, :id => 10, :user_ids => ['2', '3'] + assert_response :success + assert_template 'add_users' + assert_equal 'text/javascript', response.content_type + end + assert_match /John Smith/, response.body + end + + def test_remove_user + assert_difference 'Group.find(10).users.count', -1 do + delete :remove_user, :id => 10, :user_id => '8' + end + end + + def test_xhr_remove_user + assert_difference 'Group.find(10).users.count', -1 do + xhr :delete, :remove_user, :id => 10, :user_id => '8' + assert_response :success + assert_template 'remove_user' + assert_equal 'text/javascript', response.content_type + end + end + + def test_new_membership + assert_difference 'Group.find(10).members.count' do + post :edit_membership, :id => 10, :membership => { :project_id => 2, :role_ids => ['1', '2']} + end + end + + def test_xhr_new_membership + assert_difference 'Group.find(10).members.count' do + xhr :post, :edit_membership, :id => 10, :membership => { :project_id => 2, :role_ids => ['1', '2']} + assert_response :success + assert_template 'edit_membership' + assert_equal 'text/javascript', response.content_type + end + assert_match /OnlineStore/, response.body + end + + def test_xhr_new_membership_with_failure + assert_no_difference 'Group.find(10).members.count' do + xhr :post, :edit_membership, :id => 10, :membership => { :project_id => 999, :role_ids => ['1', '2']} + assert_response :success + assert_template 'edit_membership' + assert_equal 'text/javascript', response.content_type + end + assert_match /alert/, response.body, "Alert message not sent" + end + + def test_edit_membership + assert_no_difference 'Group.find(10).members.count' do + post :edit_membership, :id => 10, :membership_id => 6, :membership => { :role_ids => ['1', '3']} + end + end + + def test_xhr_edit_membership + assert_no_difference 'Group.find(10).members.count' do + xhr :post, :edit_membership, :id => 10, :membership_id => 6, :membership => { :role_ids => ['1', '3']} + assert_response :success + assert_template 'edit_membership' + assert_equal 'text/javascript', response.content_type + end + end + + def test_destroy_membership + assert_difference 'Group.find(10).members.count', -1 do + post :destroy_membership, :id => 10, :membership_id => 6 + end + end + + def test_xhr_destroy_membership + assert_difference 'Group.find(10).members.count', -1 do + xhr :post, :destroy_membership, :id => 10, :membership_id => 6 + assert_response :success + assert_template 'destroy_membership' + assert_equal 'text/javascript', response.content_type + end + end + + def test_autocomplete_for_user + get :autocomplete_for_user, :id => 10, :q => 'smi', :format => 'js' + assert_response :success + assert_include 'John Smith', response.body + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cf46c38f878a3a8e8d2ecb6560d5ca30550356b4.svn-base --- a/.svn/pristine/cf/cf46c38f878a3a8e8d2ecb6560d5ca30550356b4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,511 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -desc 'Mantis migration script' - -require 'active_record' -require 'iconv' -require 'pp' - -namespace :redmine do -task :migrate_from_mantis => :environment do - - module MantisMigrate - - DEFAULT_STATUS = IssueStatus.default - assigned_status = IssueStatus.find_by_position(2) - resolved_status = IssueStatus.find_by_position(3) - feedback_status = IssueStatus.find_by_position(4) - closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } - STATUS_MAPPING = {10 => DEFAULT_STATUS, # new - 20 => feedback_status, # feedback - 30 => DEFAULT_STATUS, # acknowledged - 40 => DEFAULT_STATUS, # confirmed - 50 => assigned_status, # assigned - 80 => resolved_status, # resolved - 90 => closed_status # closed - } - - priorities = IssuePriority.all - DEFAULT_PRIORITY = priorities[2] - PRIORITY_MAPPING = {10 => priorities[1], # none - 20 => priorities[1], # low - 30 => priorities[2], # normal - 40 => priorities[3], # high - 50 => priorities[4], # urgent - 60 => priorities[5] # immediate - } - - TRACKER_BUG = Tracker.find_by_position(1) - TRACKER_FEATURE = Tracker.find_by_position(2) - - roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') - manager_role = roles[0] - developer_role = roles[1] - DEFAULT_ROLE = roles.last - ROLE_MAPPING = {10 => DEFAULT_ROLE, # viewer - 25 => DEFAULT_ROLE, # reporter - 40 => DEFAULT_ROLE, # updater - 55 => developer_role, # developer - 70 => manager_role, # manager - 90 => manager_role # administrator - } - - CUSTOM_FIELD_TYPE_MAPPING = {0 => 'string', # String - 1 => 'int', # Numeric - 2 => 'int', # Float - 3 => 'list', # Enumeration - 4 => 'string', # Email - 5 => 'bool', # Checkbox - 6 => 'list', # List - 7 => 'list', # Multiselection list - 8 => 'date', # Date - } - - RELATION_TYPE_MAPPING = {1 => IssueRelation::TYPE_RELATES, # related to - 2 => IssueRelation::TYPE_RELATES, # parent of - 3 => IssueRelation::TYPE_RELATES, # child of - 0 => IssueRelation::TYPE_DUPLICATES, # duplicate of - 4 => IssueRelation::TYPE_DUPLICATES # has duplicate - } - - class MantisUser < ActiveRecord::Base - self.table_name = :mantis_user_table - - def firstname - @firstname = realname.blank? ? username : realname.split.first[0..29] - @firstname - end - - def lastname - @lastname = realname.blank? ? '-' : realname.split[1..-1].join(' ')[0..29] - @lastname = '-' if @lastname.blank? - @lastname - end - - def email - if read_attribute(:email).match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) && - !User.find_by_mail(read_attribute(:email)) - @email = read_attribute(:email) - else - @email = "#{username}@foo.bar" - end - end - - def username - read_attribute(:username)[0..29].gsub(/[^a-zA-Z0-9_\-@\.]/, '-') - end - end - - class MantisProject < ActiveRecord::Base - self.table_name = :mantis_project_table - has_many :versions, :class_name => "MantisVersion", :foreign_key => :project_id - has_many :categories, :class_name => "MantisCategory", :foreign_key => :project_id - has_many :news, :class_name => "MantisNews", :foreign_key => :project_id - has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id - - def identifier - read_attribute(:name).gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH) - end - end - - class MantisVersion < ActiveRecord::Base - self.table_name = :mantis_project_version_table - - def version - read_attribute(:version)[0..29] - end - - def description - read_attribute(:description)[0..254] - end - end - - class MantisCategory < ActiveRecord::Base - self.table_name = :mantis_project_category_table - end - - class MantisProjectUser < ActiveRecord::Base - self.table_name = :mantis_project_user_list_table - end - - class MantisBug < ActiveRecord::Base - self.table_name = :mantis_bug_table - belongs_to :bug_text, :class_name => "MantisBugText", :foreign_key => :bug_text_id - has_many :bug_notes, :class_name => "MantisBugNote", :foreign_key => :bug_id - has_many :bug_files, :class_name => "MantisBugFile", :foreign_key => :bug_id - has_many :bug_monitors, :class_name => "MantisBugMonitor", :foreign_key => :bug_id - end - - class MantisBugText < ActiveRecord::Base - self.table_name = :mantis_bug_text_table - - # Adds Mantis steps_to_reproduce and additional_information fields - # to description if any - def full_description - full_description = description - full_description += "\n\n*Steps to reproduce:*\n\n#{steps_to_reproduce}" unless steps_to_reproduce.blank? - full_description += "\n\n*Additional information:*\n\n#{additional_information}" unless additional_information.blank? - full_description - end - end - - class MantisBugNote < ActiveRecord::Base - self.table_name = :mantis_bugnote_table - belongs_to :bug, :class_name => "MantisBug", :foreign_key => :bug_id - belongs_to :bug_note_text, :class_name => "MantisBugNoteText", :foreign_key => :bugnote_text_id - end - - class MantisBugNoteText < ActiveRecord::Base - self.table_name = :mantis_bugnote_text_table - end - - class MantisBugFile < ActiveRecord::Base - self.table_name = :mantis_bug_file_table - - def size - filesize - end - - def original_filename - MantisMigrate.encode(filename) - end - - def content_type - file_type - end - - def read(*args) - if @read_finished - nil - else - @read_finished = true - content - end - end - end - - class MantisBugRelationship < ActiveRecord::Base - self.table_name = :mantis_bug_relationship_table - end - - class MantisBugMonitor < ActiveRecord::Base - self.table_name = :mantis_bug_monitor_table - end - - class MantisNews < ActiveRecord::Base - self.table_name = :mantis_news_table - end - - class MantisCustomField < ActiveRecord::Base - self.table_name = :mantis_custom_field_table - set_inheritance_column :none - has_many :values, :class_name => "MantisCustomFieldString", :foreign_key => :field_id - has_many :projects, :class_name => "MantisCustomFieldProject", :foreign_key => :field_id - - def format - read_attribute :type - end - - def name - read_attribute(:name)[0..29] - end - end - - class MantisCustomFieldProject < ActiveRecord::Base - self.table_name = :mantis_custom_field_project_table - end - - class MantisCustomFieldString < ActiveRecord::Base - self.table_name = :mantis_custom_field_string_table - end - - def self.migrate - - # Users - print "Migrating users" - User.delete_all "login <> 'admin'" - users_map = {} - users_migrated = 0 - MantisUser.find(:all).each do |user| - u = User.new :firstname => encode(user.firstname), - :lastname => encode(user.lastname), - :mail => user.email, - :last_login_on => user.last_visit - u.login = user.username - u.password = 'mantis' - u.status = User::STATUS_LOCKED if user.enabled != 1 - u.admin = true if user.access_level == 90 - next unless u.save! - users_migrated += 1 - users_map[user.id] = u.id - print '.' - end - puts - - # Projects - print "Migrating projects" - Project.destroy_all - projects_map = {} - versions_map = {} - categories_map = {} - MantisProject.find(:all).each do |project| - p = Project.new :name => encode(project.name), - :description => encode(project.description) - p.identifier = project.identifier - next unless p.save - projects_map[project.id] = p.id - p.enabled_module_names = ['issue_tracking', 'news', 'wiki'] - p.trackers << TRACKER_BUG unless p.trackers.include?(TRACKER_BUG) - p.trackers << TRACKER_FEATURE unless p.trackers.include?(TRACKER_FEATURE) - print '.' - - # Project members - project.members.each do |member| - m = Member.new :user => User.find_by_id(users_map[member.user_id]), - :roles => [ROLE_MAPPING[member.access_level] || DEFAULT_ROLE] - m.project = p - m.save - end - - # Project versions - project.versions.each do |version| - v = Version.new :name => encode(version.version), - :description => encode(version.description), - :effective_date => (version.date_order ? version.date_order.to_date : nil) - v.project = p - v.save - versions_map[version.id] = v.id - end - - # Project categories - project.categories.each do |category| - g = IssueCategory.new :name => category.category[0,30] - g.project = p - g.save - categories_map[category.category] = g.id - end - end - puts - - # Bugs - print "Migrating bugs" - Issue.destroy_all - issues_map = {} - keep_bug_ids = (Issue.count == 0) - MantisBug.find_each(:batch_size => 200) do |bug| - next unless projects_map[bug.project_id] && users_map[bug.reporter_id] - i = Issue.new :project_id => projects_map[bug.project_id], - :subject => encode(bug.summary), - :description => encode(bug.bug_text.full_description), - :priority => PRIORITY_MAPPING[bug.priority] || DEFAULT_PRIORITY, - :created_on => bug.date_submitted, - :updated_on => bug.last_updated - i.author = User.find_by_id(users_map[bug.reporter_id]) - i.category = IssueCategory.find_by_project_id_and_name(i.project_id, bug.category[0,30]) unless bug.category.blank? - i.fixed_version = Version.find_by_project_id_and_name(i.project_id, bug.fixed_in_version) unless bug.fixed_in_version.blank? - i.status = STATUS_MAPPING[bug.status] || DEFAULT_STATUS - i.tracker = (bug.severity == 10 ? TRACKER_FEATURE : TRACKER_BUG) - i.id = bug.id if keep_bug_ids - next unless i.save - issues_map[bug.id] = i.id - print '.' - STDOUT.flush - - # Assignee - # Redmine checks that the assignee is a project member - if (bug.handler_id && users_map[bug.handler_id]) - i.assigned_to = User.find_by_id(users_map[bug.handler_id]) - i.save(:validate => false) - end - - # Bug notes - bug.bug_notes.each do |note| - next unless users_map[note.reporter_id] - n = Journal.new :notes => encode(note.bug_note_text.note), - :created_on => note.date_submitted - n.user = User.find_by_id(users_map[note.reporter_id]) - n.journalized = i - n.save - end - - # Bug files - bug.bug_files.each do |file| - a = Attachment.new :created_on => file.date_added - a.file = file - a.author = User.find :first - a.container = i - a.save - end - - # Bug monitors - bug.bug_monitors.each do |monitor| - next unless users_map[monitor.user_id] - i.add_watcher(User.find_by_id(users_map[monitor.user_id])) - end - end - - # update issue id sequence if needed (postgresql) - Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!') - puts - - # Bug relationships - print "Migrating bug relations" - MantisBugRelationship.find(:all).each do |relation| - next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id] - r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type] - r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id]) - r.issue_to = Issue.find_by_id(issues_map[relation.destination_bug_id]) - pp r unless r.save - print '.' - STDOUT.flush - end - puts - - # News - print "Migrating news" - News.destroy_all - MantisNews.find(:all, :conditions => 'project_id > 0').each do |news| - next unless projects_map[news.project_id] - n = News.new :project_id => projects_map[news.project_id], - :title => encode(news.headline[0..59]), - :description => encode(news.body), - :created_on => news.date_posted - n.author = User.find_by_id(users_map[news.poster_id]) - n.save - print '.' - STDOUT.flush - end - puts - - # Custom fields - print "Migrating custom fields" - IssueCustomField.destroy_all - MantisCustomField.find(:all).each do |field| - f = IssueCustomField.new :name => field.name[0..29], - :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format], - :min_length => field.length_min, - :max_length => field.length_max, - :regexp => field.valid_regexp, - :possible_values => field.possible_values.split('|'), - :is_required => field.require_report? - next unless f.save - print '.' - STDOUT.flush - # Trackers association - f.trackers = Tracker.find :all - - # Projects association - field.projects.each do |project| - f.projects << Project.find_by_id(projects_map[project.project_id]) if projects_map[project.project_id] - end - - # Values - field.values.each do |value| - v = CustomValue.new :custom_field_id => f.id, - :value => value.value - v.customized = Issue.find_by_id(issues_map[value.bug_id]) if issues_map[value.bug_id] - v.save - end unless f.new_record? - end - puts - - puts - puts "Users: #{users_migrated}/#{MantisUser.count}" - puts "Projects: #{Project.count}/#{MantisProject.count}" - puts "Memberships: #{Member.count}/#{MantisProjectUser.count}" - puts "Versions: #{Version.count}/#{MantisVersion.count}" - puts "Categories: #{IssueCategory.count}/#{MantisCategory.count}" - puts "Bugs: #{Issue.count}/#{MantisBug.count}" - puts "Bug notes: #{Journal.count}/#{MantisBugNote.count}" - puts "Bug files: #{Attachment.count}/#{MantisBugFile.count}" - puts "Bug relations: #{IssueRelation.count}/#{MantisBugRelationship.count}" - puts "Bug monitors: #{Watcher.count}/#{MantisBugMonitor.count}" - puts "News: #{News.count}/#{MantisNews.count}" - puts "Custom fields: #{IssueCustomField.count}/#{MantisCustomField.count}" - end - - def self.encoding(charset) - @ic = Iconv.new('UTF-8', charset) - rescue Iconv::InvalidEncoding - return false - end - - def self.establish_connection(params) - constants.each do |const| - klass = const_get(const) - next unless klass.respond_to? 'establish_connection' - klass.establish_connection params - end - end - - def self.encode(text) - @ic.iconv text - rescue - text - end - end - - puts - if Redmine::DefaultData::Loader.no_data? - puts "Redmine configuration need to be loaded before importing data." - puts "Please, run this first:" - puts - puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" - exit - end - - puts "WARNING: Your Redmine data will be deleted during this process." - print "Are you sure you want to continue ? [y/N] " - STDOUT.flush - break unless STDIN.gets.match(/^y$/i) - - # Default Mantis database settings - db_params = {:adapter => 'mysql2', - :database => 'bugtracker', - :host => 'localhost', - :username => 'root', - :password => '' } - - puts - puts "Please enter settings for your Mantis database" - [:adapter, :host, :database, :username, :password].each do |param| - print "#{param} [#{db_params[param]}]: " - value = STDIN.gets.chomp! - db_params[param] = value unless value.blank? - end - - while true - print "encoding [UTF-8]: " - STDOUT.flush - encoding = STDIN.gets.chomp! - encoding = 'UTF-8' if encoding.blank? - break if MantisMigrate.encoding encoding - puts "Invalid encoding!" - end - puts - - # Make sure bugs can refer bugs in other projects - Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations' - - # Turn off email notifications - Setting.notified_events = [] - - MantisMigrate.establish_connection db_params - MantisMigrate.migrate -end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cf7e5c248f11cae1d5c9a55fa82d93afc2bc7271.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cf/cf7e5c248f11cae1d5c9a55fa82d93afc2bc7271.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,31 @@ +
    +<%= link_to l(:label_auth_source_new), {:action => 'new'}, :class => 'icon icon-add' %> +
    + +<%= title l(:label_auth_source_plural) %> + + + + + + + + + + +<% for source in @auth_sources %> + "> + + + + + + +<% end %> + +
    <%=l(:field_name)%><%=l(:field_type)%><%=l(:field_host)%><%=l(:label_user_plural)%>
    <%= link_to(h(source.name), :action => 'edit', :id => source)%><%= h source.auth_method_name %><%= h source.host %><%= h source.users.count %> + <%= link_to l(:button_test), try_connection_auth_source_path(source), :class => 'icon icon-test' %> + <%= delete_link auth_source_path(source) %> +
    + +

    <%= pagination_links_full @auth_source_pages %>

    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cf93cd1a4b64064925ed170459c106faad562781.svn-base --- a/.svn/pristine/cf/cf93cd1a4b64064925ed170459c106faad562781.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingIssueCategoriesTest < ActionController::IntegrationTest - def test_issue_categories_scoped_under_project - assert_routing( - { :method => 'get', :path => "/projects/foo/issue_categories" }, - { :controller => 'issue_categories', :action => 'index', - :project_id => 'foo' } - ) - assert_routing( - { :method => 'get', :path => "/projects/foo/issue_categories.xml" }, - { :controller => 'issue_categories', :action => 'index', - :project_id => 'foo', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/projects/foo/issue_categories.json" }, - { :controller => 'issue_categories', :action => 'index', - :project_id => 'foo', :format => 'json' } - ) - assert_routing( - { :method => 'get', :path => "/projects/foo/issue_categories/new" }, - { :controller => 'issue_categories', :action => 'new', - :project_id => 'foo' } - ) - assert_routing( - { :method => 'post', :path => "/projects/foo/issue_categories" }, - { :controller => 'issue_categories', :action => 'create', - :project_id => 'foo' } - ) - assert_routing( - { :method => 'post', :path => "/projects/foo/issue_categories.xml" }, - { :controller => 'issue_categories', :action => 'create', - :project_id => 'foo', :format => 'xml' } - ) - assert_routing( - { :method => 'post', :path => "/projects/foo/issue_categories.json" }, - { :controller => 'issue_categories', :action => 'create', - :project_id => 'foo', :format => 'json' } - ) - end - - def test_issue_categories - assert_routing( - { :method => 'get', :path => "/issue_categories/1" }, - { :controller => 'issue_categories', :action => 'show', :id => '1' } - ) - assert_routing( - { :method => 'get', :path => "/issue_categories/1.xml" }, - { :controller => 'issue_categories', :action => 'show', :id => '1', - :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/issue_categories/1.json" }, - { :controller => 'issue_categories', :action => 'show', :id => '1', - :format => 'json' } - ) - assert_routing( - { :method => 'get', :path => "/issue_categories/1/edit" }, - { :controller => 'issue_categories', :action => 'edit', :id => '1' } - ) - assert_routing( - { :method => 'put', :path => "/issue_categories/1" }, - { :controller => 'issue_categories', :action => 'update', :id => '1' } - ) - assert_routing( - { :method => 'put', :path => "/issue_categories/1.xml" }, - { :controller => 'issue_categories', :action => 'update', :id => '1', - :format => 'xml' } - ) - assert_routing( - { :method => 'put', :path => "/issue_categories/1.json" }, - { :controller => 'issue_categories', :action => 'update', :id => '1', - :format => 'json' } - ) - assert_routing( - { :method => 'delete', :path => "/issue_categories/1" }, - { :controller => 'issue_categories', :action => 'destroy', :id => '1' } - ) - assert_routing( - { :method => 'delete', :path => "/issue_categories/1.xml" }, - { :controller => 'issue_categories', :action => 'destroy', :id => '1', - :format => 'xml' } - ) - assert_routing( - { :method => 'delete', :path => "/issue_categories/1.json" }, - { :controller => 'issue_categories', :action => 'destroy', :id => '1', - :format => 'json' } - ) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cf9ad7baf9e113a573f644f31dff4cb65997570b.svn-base --- a/.svn/pristine/cf/cf9ad7baf9e113a573f644f31dff4cb65997570b.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -module GanttHelper - - def gantt_zoom_link(gantt, in_or_out) - case in_or_out - when :in - if gantt.zoom < 4 - link_to_content_update l(:text_zoom_in), - params.merge(gantt.params.merge(:zoom => (gantt.zoom+1))), - :class => 'icon icon-zoom-in' - else - content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe - end - - when :out - if gantt.zoom > 1 - link_to_content_update l(:text_zoom_out), - params.merge(gantt.params.merge(:zoom => (gantt.zoom-1))), - :class => 'icon icon-zoom-out' - else - content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cfb6c51d7c06f79137cb3baca89fe82b05917998.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cf/cfb6c51d7c06f79137cb3baca89fe82b05917998.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,97 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../test_case', __FILE__) +require 'tmpdir' + +class RedminePmTest::RepositoryGitTest < RedminePmTest::TestCase + fixtures :projects, :users, :members, :roles, :member_roles + + GIT_BIN = Redmine::Configuration['scm_git_command'] || "git" + + def test_anonymous_read_on_public_repo_with_permission_should_succeed + assert_success "ls-remote", git_url + end + + def test_anonymous_read_on_public_repo_without_permission_should_fail + Role.anonymous.remove_permission! :browse_repository + assert_failure "ls-remote", git_url + end + + def test_invalid_credentials_should_fail + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "ls-remote", git_url + end + with_credentials "dlopper", "wrong" do + assert_failure "ls-remote", git_url + end + end + + def test_clone + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + assert_success "clone", git_url + end + end + end + + def test_write_commands + Role.find(2).add_permission! :commit_access + filename = random_filename + + Dir.mktmpdir do |dir| + assert_success "clone", git_url, dir + Dir.chdir(dir) do + f = File.new(File.join(dir, filename), "w") + f.write "test file content" + f.close + + with_credentials "dlopper", "foo" do + assert_success "add", filename + assert_success "commit -a --message Committing_a_file" + assert_success "push", git_url, "--all" + end + end + end + + Dir.mktmpdir do |dir| + assert_success "clone", git_url, dir + Dir.chdir(dir) do + assert File.exists?(File.join(dir, "#{filename}")) + end + end + end + + protected + + def execute(*args) + a = [GIT_BIN] + super a, *args + end + + def git_url(path=nil) + host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1' + credentials = nil + if username && password + credentials = "#{username}:#{password}" + end + url = "http://#{credentials}@#{host}/git/ecookbook" + url << "/#{path}" if path + url + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/cf/cfc60690251a0006237e1c8a62505c246f4de941.svn-base --- a/.svn/pristine/cf/cfc60690251a0006237e1c8a62505c246f4de941.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -api.array :queries, api_meta(:total_count => @query_count, :offset => @offset, :limit => @limit) do - @queries.each do |query| - api.query do - api.id query.id - api.name query.name - api.is_public query.is_public - api.project_id query.project_id - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/d0/d03051bd71eb75793300dcbd48f633085578296c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d0/d03051bd71eb75793300dcbd48f633085578296c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,111 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class MembersControllerTest < ActionController::TestCase + fixtures :projects, :members, :member_roles, :roles, :users + + def setup + User.current = nil + @request.session[:user_id] = 2 + end + + def test_create + assert_difference 'Member.count' do + post :create, :project_id => 1, :membership => {:role_ids => [1], :user_id => 7} + end + assert_redirected_to '/projects/ecookbook/settings/members' + assert User.find(7).member_of?(Project.find(1)) + end + + def test_create_multiple + assert_difference 'Member.count', 3 do + post :create, :project_id => 1, :membership => {:role_ids => [1], :user_ids => [7, 8, 9]} + end + assert_redirected_to '/projects/ecookbook/settings/members' + assert User.find(7).member_of?(Project.find(1)) + end + + def test_xhr_create + assert_difference 'Member.count', 3 do + xhr :post, :create, :project_id => 1, :membership => {:role_ids => [1], :user_ids => [7, 8, 9]} + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + end + assert User.find(7).member_of?(Project.find(1)) + assert User.find(8).member_of?(Project.find(1)) + assert User.find(9).member_of?(Project.find(1)) + assert_include 'tab-content-members', response.body + end + + def test_xhr_create_with_failure + assert_no_difference 'Member.count' do + xhr :post, :create, :project_id => 1, :membership => {:role_ids => [], :user_ids => [7, 8, 9]} + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + end + assert_match /alert/, response.body, "Alert message not sent" + end + + def test_edit + assert_no_difference 'Member.count' do + put :update, :id => 2, :membership => {:role_ids => [1], :user_id => 3} + end + assert_redirected_to '/projects/ecookbook/settings/members' + end + + def test_xhr_edit + assert_no_difference 'Member.count' do + xhr :put, :update, :id => 2, :membership => {:role_ids => [1], :user_id => 3} + assert_response :success + assert_template 'update' + assert_equal 'text/javascript', response.content_type + end + member = Member.find(2) + assert_equal [1], member.role_ids + assert_equal 3, member.user_id + assert_include 'tab-content-members', response.body + end + + def test_destroy + assert_difference 'Member.count', -1 do + delete :destroy, :id => 2 + end + assert_redirected_to '/projects/ecookbook/settings/members' + assert !User.find(3).member_of?(Project.find(1)) + end + + def test_xhr_destroy + assert_difference 'Member.count', -1 do + xhr :delete, :destroy, :id => 2 + assert_response :success + assert_template 'destroy' + assert_equal 'text/javascript', response.content_type + end + assert_nil Member.find_by_id(2) + assert_include 'tab-content-members', response.body + end + + def test_autocomplete + get :autocomplete, :project_id => 1, :q => 'mis', :format => 'js' + assert_response :success + assert_include 'User Misc', response.body + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/d0/d04345ff059a488f9272187f15a843b5609d167c.svn-base --- a/.svn/pristine/d0/d04345ff059a488f9272187f15a843b5609d167c.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1099 +0,0 @@ -# German translations for Ruby on Rails -# by Clemens Kofler (clemens@railway.at) -# additions for Redmine 1.2 by Jens Martsch (jmartsch@gmail.com) - -de: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%d.%m.%Y" - short: "%e. %b" - long: "%e. %B %Y" - - day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag] - abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember] - abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%d.%m.%Y %H:%M" - time: "%H:%M" - short: "%e. %b %H:%M" - long: "%A, %e. %B %Y, %H:%M Uhr" - am: "vormittags" - pm: "nachmittags" - - datetime: - distance_in_words: - half_a_minute: 'eine halbe Minute' - less_than_x_seconds: - one: 'weniger als 1 Sekunde' - other: 'weniger als %{count} Sekunden' - x_seconds: - one: '1 Sekunde' - other: '%{count} Sekunden' - less_than_x_minutes: - one: 'weniger als 1 Minute' - other: 'weniger als %{count} Minuten' - x_minutes: - one: '1 Minute' - other: '%{count} Minuten' - about_x_hours: - one: 'etwa 1 Stunde' - other: 'etwa %{count} Stunden' - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: '1 Tag' - other: '%{count} Tagen' - about_x_months: - one: 'etwa 1 Monat' - other: 'etwa %{count} Monaten' - x_months: - one: '1 Monat' - other: '%{count} Monaten' - about_x_years: - one: 'etwa 1 Jahr' - other: 'etwa %{count} Jahren' - over_x_years: - one: 'mehr als 1 Jahr' - other: 'mehr als %{count} Jahren' - almost_x_years: - one: "fast 1 Jahr" - other: "fast %{count} Jahren" - - number: - # Default format for numbers - format: - separator: ',' - delimiter: '.' - precision: 2 - currency: - format: - unit: '€' - format: '%n %u' - delimiter: '' - percentage: - format: - delimiter: "" - precision: - format: - delimiter: "" - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "und" - skip_last_comma: true - - activerecord: - errors: - template: - header: - one: "Dieses %{model}-Objekt konnte nicht gespeichert werden: %{count} Fehler." - other: "Dieses %{model}-Objekt konnte nicht gespeichert werden: %{count} Fehler." - body: "Bitte überprüfen Sie die folgenden Felder:" - - messages: - inclusion: "ist kein gültiger Wert" - exclusion: "ist nicht verfügbar" - invalid: "ist nicht gültig" - confirmation: "stimmt nicht mit der Bestätigung überein" - accepted: "muss akzeptiert werden" - empty: "muss ausgefüllt werden" - blank: "muss ausgefüllt werden" - too_long: "ist zu lang (nicht mehr als %{count} Zeichen)" - too_short: "ist zu kurz (nicht weniger als %{count} Zeichen)" - wrong_length: "hat die falsche Länge (muss genau %{count} Zeichen haben)" - taken: "ist bereits vergeben" - not_a_number: "ist keine Zahl" - not_a_date: "is kein gültiges Datum" - greater_than: "muss größer als %{count} sein" - greater_than_or_equal_to: "muss größer oder gleich %{count} sein" - equal_to: "muss genau %{count} sein" - less_than: "muss kleiner als %{count} sein" - less_than_or_equal_to: "muss kleiner oder gleich %{count} sein" - odd: "muss ungerade sein" - even: "muss gerade sein" - greater_than_start_date: "muss größer als Anfangsdatum sein" - not_same_project: "gehört nicht zum selben Projekt" - circular_dependency: "Diese Beziehung würde eine zyklische Abhängigkeit erzeugen" - cant_link_an_issue_with_a_descendant: "Ein Ticket kann nicht mit einer ihrer Unteraufgaben verlinkt werden" - - actionview_instancetag_blank_option: Bitte auswählen - - general_text_No: 'Nein' - general_text_Yes: 'Ja' - general_text_no: 'nein' - general_text_yes: 'ja' - general_lang_name: 'Deutsch' - general_csv_separator: ';' - general_csv_decimal_separator: ',' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Konto wurde erfolgreich aktualisiert. - notice_account_invalid_creditentials: Benutzer oder Kennwort ist ungültig. - notice_account_password_updated: Kennwort wurde erfolgreich aktualisiert. - notice_account_wrong_password: Falsches Kennwort. - notice_account_register_done: Konto wurde erfolgreich angelegt. - notice_account_unknown_email: Unbekannter Benutzer. - notice_can_t_change_password: Dieses Konto verwendet eine externe Authentifizierungs-Quelle. Unmöglich, das Kennwort zu ändern. - notice_account_lost_email_sent: Eine E-Mail mit Anweisungen, ein neues Kennwort zu wählen, wurde Ihnen geschickt. - notice_account_activated: Ihr Konto ist aktiviert. Sie können sich jetzt anmelden. - notice_successful_create: Erfolgreich angelegt - notice_successful_update: Erfolgreich aktualisiert. - notice_successful_delete: Erfolgreich gelöscht. - notice_successful_connection: Verbindung erfolgreich. - notice_file_not_found: Anhang existiert nicht oder ist gelöscht worden. - notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert. - notice_not_authorized: Sie sind nicht berechtigt, auf diese Seite zuzugreifen. - notice_email_sent: "Eine E-Mail wurde an %{value} gesendet." - notice_email_error: "Beim Senden einer E-Mail ist ein Fehler aufgetreten (%{value})." - notice_feeds_access_key_reseted: Ihr Atom-Zugriffsschlüssel wurde zurückgesetzt. - notice_api_access_key_reseted: Ihr API-Zugriffsschlüssel wurde zurückgesetzt. - notice_failed_to_save_issues: "%{count} von %{total} ausgewählten Tickets konnte(n) nicht gespeichert werden: %{ids}." - notice_failed_to_save_members: "Benutzer konnte nicht gespeichert werden: %{errors}." - notice_no_issue_selected: "Kein Ticket ausgewählt! Bitte wählen Sie die Tickets, die Sie bearbeiten möchten." - notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Genehmigung des Administrators." - notice_default_data_loaded: Die Standard-Konfiguration wurde erfolgreich geladen. - notice_unable_delete_version: Die Version konnte nicht gelöscht werden. - notice_unable_delete_time_entry: Der Zeiterfassungseintrag konnte nicht gelöscht werden. - notice_issue_done_ratios_updated: Der Ticket-Fortschritt wurde aktualisiert. - - error_can_t_load_default_data: "Die Standard-Konfiguration konnte nicht geladen werden: %{value}" - error_scm_not_found: Eintrag und/oder Revision existiert nicht im Projektarchiv. - error_scm_command_failed: "Beim Zugriff auf das Projektarchiv ist ein Fehler aufgetreten: %{value}" - error_scm_annotate: "Der Eintrag existiert nicht oder kann nicht annotiert werden." - error_issue_not_found_in_project: 'Das Ticket wurde nicht gefunden oder gehört nicht zu diesem Projekt.' - error_no_tracker_in_project: Diesem Projekt ist kein Tracker zugeordnet. Bitte überprüfen Sie die Projekteinstellungen. - error_no_default_issue_status: Es ist kein Status als Standard definiert. Bitte überprüfen Sie Ihre Konfiguration (unter "Administration -> Ticket-Status"). - error_can_not_delete_custom_field: Kann das benutzerdefinierte Feld nicht löschen. - error_can_not_delete_tracker: Dieser Tracker enthält Tickets und kann nicht gelöscht werden. - error_can_not_remove_role: Diese Rolle wird verwendet und kann nicht gelöscht werden. - error_can_not_reopen_issue_on_closed_version: Das Ticket ist einer abgeschlossenen Version zugeordnet und kann daher nicht wieder geöffnet werden. - error_can_not_archive_project: Dieses Projekt kann nicht archiviert werden. - error_issue_done_ratios_not_updated: Der Ticket-Fortschritt wurde nicht aktualisiert. - error_workflow_copy_source: Bitte wählen Sie einen Quell-Tracker und eine Quell-Rolle. - error_workflow_copy_target: Bitte wählen Sie die Ziel-Tracker und -Rollen. - error_unable_delete_issue_status: "Der Ticket-Status konnte nicht gelöscht werden." - error_unable_to_connect: Fehler beim Verbinden (%{value}) - warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden." - - mail_subject_lost_password: "Ihr %{value} Kennwort" - mail_body_lost_password: 'Benutzen Sie den folgenden Link, um Ihr Kennwort zu ändern:' - mail_subject_register: "%{value} Kontoaktivierung" - mail_body_register: 'Um Ihr Konto zu aktivieren, benutzen Sie folgenden Link:' - mail_body_account_information_external: "Sie können sich mit Ihrem Konto %{value} anmelden." - mail_body_account_information: Ihre Konto-Informationen - mail_subject_account_activation_request: "Antrag auf %{value} Kontoaktivierung" - mail_body_account_activation_request: "Ein neuer Benutzer (%{value}) hat sich registriert. Sein Konto wartet auf Ihre Genehmigung:" - mail_subject_reminder: "%{count} Tickets müssen in den nächsten %{days} Tagen abgegeben werden" - mail_body_reminder: "%{count} Tickets, die Ihnen zugewiesen sind, müssen in den nächsten %{days} Tagen abgegeben werden:" - mail_subject_wiki_content_added: "Wiki-Seite '%{id}' hinzugefügt" - mail_body_wiki_content_added: "Die Wiki-Seite '%{id}' wurde von %{author} hinzugefügt." - mail_subject_wiki_content_updated: "Wiki-Seite '%{id}' erfolgreich aktualisiert" - mail_body_wiki_content_updated: "Die Wiki-Seite '%{id}' wurde von %{author} aktualisiert." - - gui_validation_error: 1 Fehler - gui_validation_error_plural: "%{count} Fehler" - - field_name: Name - field_description: Beschreibung - field_summary: Zusammenfassung - field_is_required: Erforderlich - field_firstname: Vorname - field_lastname: Nachname - field_mail: E-Mail - field_filename: Datei - field_filesize: Größe - field_downloads: Downloads - field_author: Autor - field_created_on: Angelegt - field_updated_on: Aktualisiert - field_field_format: Format - field_is_for_all: Für alle Projekte - field_possible_values: Mögliche Werte - field_regexp: Regulärer Ausdruck - field_min_length: Minimale Länge - field_max_length: Maximale Länge - field_value: Wert - field_category: Kategorie - field_title: Titel - field_project: Projekt - field_issue: Ticket - field_status: Status - field_notes: Kommentare - field_is_closed: Ticket geschlossen - field_is_default: Standardeinstellung - field_tracker: Tracker - field_subject: Thema - field_due_date: Abgabedatum - field_assigned_to: Zugewiesen an - field_priority: Priorität - field_fixed_version: Zielversion - field_user: Benutzer - field_principal: Auftraggeber - field_role: Rolle - field_homepage: Projekt-Homepage - field_is_public: Öffentlich - field_parent: Unterprojekt von - field_is_in_roadmap: In der Roadmap anzeigen - field_login: Mitgliedsname - field_mail_notification: Mailbenachrichtigung - field_admin: Administrator - field_last_login_on: Letzte Anmeldung - field_language: Sprache - field_effective_date: Datum - field_password: Kennwort - field_new_password: Neues Kennwort - field_password_confirmation: Bestätigung - field_version: Version - field_type: Typ - field_host: Host - field_port: Port - field_account: Konto - field_base_dn: Base DN - field_attr_login: Mitgliedsname-Attribut - field_attr_firstname: Vorname-Attribut - field_attr_lastname: Name-Attribut - field_attr_mail: E-Mail-Attribut - field_onthefly: On-the-fly-Benutzererstellung - field_start_date: Beginn - field_done_ratio: "% erledigt" - field_auth_source: Authentifizierungs-Modus - field_hide_mail: E-Mail-Adresse nicht anzeigen - field_comments: Kommentar - field_url: URL - field_start_page: Hauptseite - field_subproject: Unterprojekt von - field_hours: Stunden - field_activity: Aktivität - field_spent_on: Datum - field_identifier: Kennung - field_is_filter: Als Filter benutzen - field_issue_to: Zugehöriges Ticket - field_delay: Pufferzeit - field_assignable: Tickets können dieser Rolle zugewiesen werden - field_redirect_existing_links: Existierende Links umleiten - field_estimated_hours: Geschätzter Aufwand - field_column_names: Spalten - field_time_entries: Logzeit - field_time_zone: Zeitzone - field_searchable: Durchsuchbar - field_default_value: Standardwert - field_comments_sorting: Kommentare anzeigen - field_parent_title: Übergeordnete Seite - field_editable: Bearbeitbar - field_watcher: Beobachter - field_identity_url: OpenID-URL - field_content: Inhalt - field_group_by: Gruppiere Ergebnisse nach - field_sharing: Gemeinsame Verwendung - field_parent_issue: Übergeordnete Aufgabe - - setting_app_title: Applikations-Titel - setting_app_subtitle: Applikations-Untertitel - setting_welcome_text: Willkommenstext - setting_default_language: Standardsprache - setting_login_required: Authentifizierung erforderlich - setting_self_registration: Anmeldung ermöglicht - setting_attachment_max_size: Max. Dateigröße - setting_issues_export_limit: Max. Anzahl Tickets bei CSV/PDF-Export - setting_mail_from: E-Mail-Absender - setting_bcc_recipients: E-Mails als Blindkopie (BCC) senden - setting_plain_text_mail: Nur reinen Text (kein HTML) senden - setting_host_name: Hostname - setting_text_formatting: Textformatierung - setting_wiki_compression: Wiki-Historie komprimieren - setting_feeds_limit: Max. Anzahl Einträge pro Atom-Feed - setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich - setting_autofetch_changesets: Changesets automatisch abrufen - setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen - setting_commit_ref_keywords: Schlüsselwörter (Beziehungen) - setting_commit_fix_keywords: Schlüsselwörter (Status) - setting_autologin: Automatische Anmeldung - setting_date_format: Datumsformat - setting_time_format: Zeitformat - setting_cross_project_issue_relations: Ticket-Beziehungen zwischen Projekten erlauben - setting_issue_list_default_columns: Standard-Spalten in der Ticket-Auflistung - setting_emails_footer: E-Mail-Fußzeile - setting_protocol: Protokoll - setting_per_page_options: Objekte pro Seite - setting_user_format: Benutzer-Anzeigeformat - setting_activity_days_default: Anzahl Tage pro Seite der Projekt-Aktivität - setting_display_subprojects_issues: Tickets von Unterprojekten im Hauptprojekt anzeigen - setting_enabled_scm: Aktivierte Versionskontrollsysteme - setting_mail_handler_body_delimiters: "Schneide E-Mails nach einer dieser Zeilen ab" - setting_mail_handler_api_enabled: Abruf eingehender E-Mails aktivieren - setting_mail_handler_api_key: API-Schlüssel - setting_sequential_project_identifiers: Fortlaufende Projektkennungen generieren - setting_gravatar_enabled: Gravatar-Benutzerbilder benutzen - setting_gravatar_default: Standard-Gravatar-Bild - setting_diff_max_lines_displayed: Maximale Anzahl anzuzeigender Diff-Zeilen - setting_file_max_size_displayed: Maximale Größe inline angezeigter Textdateien - setting_repository_log_display_limit: Maximale Anzahl anzuzeigender Revisionen in der Historie einer Datei - setting_openid: Erlaube OpenID-Anmeldung und -Registrierung - setting_password_min_length: Mindestlänge des Kennworts - setting_new_project_user_role_id: Rolle, die einem Nicht-Administrator zugeordnet wird, der ein Projekt erstellt - setting_default_projects_modules: Standardmäßig aktivierte Module für neue Projekte - setting_issue_done_ratio: Berechne den Ticket-Fortschritt mittels - setting_issue_done_ratio_issue_field: Ticket-Feld %-erledigt - setting_issue_done_ratio_issue_status: Ticket-Status - setting_start_of_week: Wochenanfang - setting_rest_api_enabled: REST-Schnittstelle aktivieren - setting_cache_formatted_text: Formatierten Text im Cache speichern - - permission_add_project: Projekt erstellen - permission_add_subprojects: Unterprojekte erstellen - permission_edit_project: Projekt bearbeiten - permission_select_project_modules: Projektmodule auswählen - permission_manage_members: Mitglieder verwalten - permission_manage_project_activities: Aktivitäten (Zeiterfassung) verwalten - permission_manage_versions: Versionen verwalten - permission_manage_categories: Ticket-Kategorien verwalten - permission_view_issues: Tickets anzeigen - permission_add_issues: Tickets hinzufügen - permission_edit_issues: Tickets bearbeiten - permission_manage_issue_relations: Ticket-Beziehungen verwalten - permission_add_issue_notes: Kommentare hinzufügen - permission_edit_issue_notes: Kommentare bearbeiten - permission_edit_own_issue_notes: Eigene Kommentare bearbeiten - permission_move_issues: Tickets verschieben - permission_delete_issues: Tickets löschen - permission_manage_public_queries: Öffentliche Filter verwalten - permission_save_queries: Filter speichern - permission_view_gantt: Gantt-Diagramm ansehen - permission_view_calendar: Kalender ansehen - permission_view_issue_watchers: Liste der Beobachter ansehen - permission_add_issue_watchers: Beobachter hinzufügen - permission_delete_issue_watchers: Beobachter löschen - permission_log_time: Aufwände buchen - permission_view_time_entries: Gebuchte Aufwände ansehen - permission_edit_time_entries: Gebuchte Aufwände bearbeiten - permission_edit_own_time_entries: Selbst gebuchte Aufwände bearbeiten - permission_manage_news: News verwalten - permission_comment_news: News kommentieren - permission_manage_documents: Dokumente verwalten - permission_view_documents: Dokumente ansehen - permission_manage_files: Dateien verwalten - permission_view_files: Dateien ansehen - permission_manage_wiki: Wiki verwalten - permission_rename_wiki_pages: Wiki-Seiten umbenennen - permission_delete_wiki_pages: Wiki-Seiten löschen - permission_view_wiki_pages: Wiki ansehen - permission_view_wiki_edits: Wiki-Versionsgeschichte ansehen - permission_edit_wiki_pages: Wiki-Seiten bearbeiten - permission_delete_wiki_pages_attachments: Anhänge löschen - permission_protect_wiki_pages: Wiki-Seiten schützen - permission_manage_repository: Projektarchiv verwalten - permission_browse_repository: Projektarchiv ansehen - permission_view_changesets: Changesets ansehen - permission_commit_access: Commit-Zugriff (über WebDAV) - permission_manage_boards: Foren verwalten - permission_view_messages: Forenbeiträge ansehen - permission_add_messages: Forenbeiträge hinzufügen - permission_edit_messages: Forenbeiträge bearbeiten - permission_edit_own_messages: Eigene Forenbeiträge bearbeiten - permission_delete_messages: Forenbeiträge löschen - permission_delete_own_messages: Eigene Forenbeiträge löschen - permission_export_wiki_pages: Wiki-Seiten exportieren - permission_manage_subtasks: Unteraufgaben verwalten - - project_module_issue_tracking: Ticket-Verfolgung - project_module_time_tracking: Zeiterfassung - project_module_news: News - project_module_documents: Dokumente - project_module_files: Dateien - project_module_wiki: Wiki - project_module_repository: Projektarchiv - project_module_boards: Foren - project_module_calendar: Kalender - project_module_gantt: Gantt - - label_user: Benutzer - label_user_plural: Benutzer - label_user_new: Neuer Benutzer - label_user_anonymous: Anonym - label_project: Projekt - label_project_new: Neues Projekt - label_project_plural: Projekte - label_x_projects: - zero: keine Projekte - one: 1 Projekt - other: "%{count} Projekte" - label_project_all: Alle Projekte - label_project_latest: Neueste Projekte - label_issue: Ticket - label_issue_new: Neues Ticket - label_issue_plural: Tickets - label_issue_view_all: Alle Tickets anzeigen - label_issues_by: "Tickets von %{value}" - label_issue_added: Ticket hinzugefügt - label_issue_updated: Ticket aktualisiert - label_document: Dokument - label_document_new: Neues Dokument - label_document_plural: Dokumente - label_document_added: Dokument hinzugefügt - label_role: Rolle - label_role_plural: Rollen - label_role_new: Neue Rolle - label_role_and_permissions: Rollen und Rechte - label_member: Mitglied - label_member_new: Neues Mitglied - label_member_plural: Mitglieder - label_tracker: Tracker - label_tracker_plural: Tracker - label_tracker_new: Neuer Tracker - label_workflow: Workflow - label_issue_status: Ticket-Status - label_issue_status_plural: Ticket-Status - label_issue_status_new: Neuer Status - label_issue_category: Ticket-Kategorie - label_issue_category_plural: Ticket-Kategorien - label_issue_category_new: Neue Kategorie - label_custom_field: Benutzerdefiniertes Feld - label_custom_field_plural: Benutzerdefinierte Felder - label_custom_field_new: Neues Feld - label_enumerations: Aufzählungen - label_enumeration_new: Neuer Wert - label_information: Information - label_information_plural: Informationen - label_please_login: Anmelden - label_register: Registrieren - label_login_with_open_id_option: oder mit OpenID anmelden - label_password_lost: Kennwort vergessen - label_home: Hauptseite - label_my_page: Meine Seite - label_my_account: Mein Konto - label_my_projects: Meine Projekte - label_my_page_block: Bereich "Meine Seite" - label_administration: Administration - label_login: Anmelden - label_logout: Abmelden - label_help: Hilfe - label_reported_issues: Gemeldete Tickets - label_assigned_to_me_issues: Mir zugewiesen - label_last_login: Letzte Anmeldung - label_registered_on: Angemeldet am - label_activity: Aktivität - label_overall_activity: Aktivitäten aller Projekte anzeigen - label_user_activity: "Aktivität von %{value}" - label_new: Neu - label_logged_as: Angemeldet als - label_environment: Umgebung - label_authentication: Authentifizierung - label_auth_source: Authentifizierungs-Modus - label_auth_source_new: Neuer Authentifizierungs-Modus - label_auth_source_plural: Authentifizierungs-Arten - label_subproject_plural: Unterprojekte - label_subproject_new: Neues Unterprojekt - label_and_its_subprojects: "%{value} und dessen Unterprojekte" - label_min_max_length: Länge (Min. - Max.) - label_list: Liste - label_date: Datum - label_integer: Zahl - label_float: Fließkommazahl - label_boolean: Boolean - label_string: Text - label_text: Langer Text - label_attribute: Attribut - label_attribute_plural: Attribute - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - label_no_data: Nichts anzuzeigen - label_change_status: Statuswechsel - label_history: Historie - label_attachment: Datei - label_attachment_new: Neue Datei - label_attachment_delete: Anhang löschen - label_attachment_plural: Dateien - label_file_added: Datei hinzugefügt - label_report: Bericht - label_report_plural: Berichte - label_news: News - label_news_new: News hinzufügen - label_news_plural: News - label_news_latest: Letzte News - label_news_view_all: Alle News anzeigen - label_news_added: News hinzugefügt - label_settings: Konfiguration - label_overview: Übersicht - label_version: Version - label_version_new: Neue Version - label_version_plural: Versionen - label_close_versions: Vollständige Versionen schließen - label_confirmation: Bestätigung - label_export_to: "Auch abrufbar als:" - label_read: Lesen... - label_public_projects: Öffentliche Projekte - label_open_issues: offen - label_open_issues_plural: offen - label_closed_issues: geschlossen - label_closed_issues_plural: geschlossen - label_x_open_issues_abbr_on_total: - zero: 0 offen / %{total} - one: 1 offen / %{total} - other: "%{count} offen / %{total}" - label_x_open_issues_abbr: - zero: 0 offen - one: 1 offen - other: "%{count} offen" - label_x_closed_issues_abbr: - zero: 0 geschlossen - one: 1 geschlossen - other: "%{count} geschlossen" - label_total: Gesamtzahl - label_permissions: Berechtigungen - label_current_status: Gegenwärtiger Status - label_new_statuses_allowed: Neue Berechtigungen - label_all: alle - label_none: kein - label_nobody: Niemand - label_next: Weiter - label_previous: Zurück - label_used_by: Benutzt von - label_details: Details - label_add_note: Kommentar hinzufügen - label_per_page: Pro Seite - label_calendar: Kalender - label_months_from: Monate ab - label_gantt: Gantt-Diagramm - label_internal: Intern - label_last_changes: "%{count} letzte Änderungen" - label_change_view_all: Alle Änderungen anzeigen - label_personalize_page: Diese Seite anpassen - label_comment: Kommentar - label_comment_plural: Kommentare - label_x_comments: - zero: keine Kommentare - one: 1 Kommentar - other: "%{count} Kommentare" - label_comment_add: Kommentar hinzufügen - label_comment_added: Kommentar hinzugefügt - label_comment_delete: Kommentar löschen - label_query: Benutzerdefinierte Abfrage - label_query_plural: Benutzerdefinierte Berichte - label_query_new: Neuer Bericht - label_filter_add: Filter hinzufügen - label_filter_plural: Filter - label_equals: ist - label_not_equals: ist nicht - label_in_less_than: in weniger als - label_in_more_than: in mehr als - label_greater_or_equal: ">=" - label_less_or_equal: "<=" - label_in: an - label_today: heute - label_all_time: gesamter Zeitraum - label_yesterday: gestern - label_this_week: aktuelle Woche - label_last_week: vorige Woche - label_last_n_days: "die letzten %{count} Tage" - label_this_month: aktueller Monat - label_last_month: voriger Monat - label_this_year: aktuelles Jahr - label_date_range: Zeitraum - label_less_than_ago: vor weniger als - label_more_than_ago: vor mehr als - label_ago: vor - label_contains: enthält - label_not_contains: enthält nicht - label_day_plural: Tage - label_repository: Projektarchiv - label_repository_plural: Projektarchive - label_browse: Codebrowser - label_modification: "%{count} Änderung" - label_modification_plural: "%{count} Änderungen" - label_branch: Zweig - label_tag: Markierung - label_revision: Revision - label_revision_plural: Revisionen - label_revision_id: Revision %{value} - label_associated_revisions: Zugehörige Revisionen - label_added: hinzugefügt - label_modified: geändert - label_copied: kopiert - label_renamed: umbenannt - label_deleted: gelöscht - label_latest_revision: Aktuellste Revision - label_latest_revision_plural: Aktuellste Revisionen - label_view_revisions: Revisionen anzeigen - label_view_all_revisions: Alle Revisionen anzeigen - label_max_size: Maximale Größe - label_sort_highest: An den Anfang - label_sort_higher: Eins höher - label_sort_lower: Eins tiefer - label_sort_lowest: Ans Ende - label_roadmap: Roadmap - label_roadmap_due_in: "Fällig in %{value}" - label_roadmap_overdue: "%{value} verspätet" - label_roadmap_no_issues: Keine Tickets für diese Version - label_search: Suche - label_result_plural: Resultate - label_all_words: Alle Wörter - label_wiki: Wiki - label_wiki_edit: Wiki-Bearbeitung - label_wiki_edit_plural: Wiki-Bearbeitungen - label_wiki_page: Wiki-Seite - label_wiki_page_plural: Wiki-Seiten - label_index_by_title: Seiten nach Titel sortiert - label_index_by_date: Seiten nach Datum sortiert - label_current_version: Gegenwärtige Version - label_preview: Vorschau - label_feed_plural: Feeds - label_changes_details: Details aller Änderungen - label_issue_tracking: Tickets - label_spent_time: Aufgewendete Zeit - label_overall_spent_time: Aufgewendete Zeit aller Projekte anzeigen - label_f_hour: "%{value} Stunde" - label_f_hour_plural: "%{value} Stunden" - label_time_tracking: Zeiterfassung - label_change_plural: Änderungen - label_statistics: Statistiken - label_commits_per_month: Übertragungen pro Monat - label_commits_per_author: Übertragungen pro Autor - label_view_diff: Unterschiede anzeigen - label_diff_inline: einspaltig - label_diff_side_by_side: nebeneinander - label_options: Optionen - label_copy_workflow_from: Workflow kopieren von - label_permissions_report: Berechtigungsübersicht - label_watched_issues: Beobachtete Tickets - label_related_issues: Zugehörige Tickets - label_applied_status: Zugewiesener Status - label_loading: Lade... - label_relation_new: Neue Beziehung - label_relation_delete: Beziehung löschen - label_relates_to: Beziehung mit - label_duplicates: Duplikat von - label_duplicated_by: Dupliziert durch - label_blocks: Blockiert - label_blocked_by: Blockiert durch - label_precedes: Vorgänger von - label_follows: Folgt - label_end_to_start: Ende - Anfang - label_end_to_end: Ende - Ende - label_start_to_start: Anfang - Anfang - label_start_to_end: Anfang - Ende - label_stay_logged_in: Angemeldet bleiben - label_disabled: gesperrt - label_show_completed_versions: Abgeschlossene Versionen anzeigen - label_me: ich - label_board: Forum - label_board_new: Neues Forum - label_board_plural: Foren - label_board_locked: Gesperrt - label_board_sticky: Wichtig (immer oben) - label_topic_plural: Themen - label_message_plural: Forenbeiträge - label_message_last: Letzter Forenbeitrag - label_message_new: Neues Thema - label_message_posted: Forenbeitrag hinzugefügt - label_reply_plural: Antworten - label_send_information: Sende Kontoinformationen an Benutzer - label_year: Jahr - label_month: Monat - label_week: Woche - label_date_from: Von - label_date_to: Bis - label_language_based: Sprachabhängig - label_sort_by: "Sortiert nach %{value}" - label_send_test_email: Test-E-Mail senden - label_feeds_access_key: RSS-Zugriffsschlüssel - label_missing_feeds_access_key: Der RSS-Zugriffsschlüssel fehlt. - label_feeds_access_key_created_on: "Atom-Zugriffsschlüssel vor %{value} erstellt" - label_module_plural: Module - label_added_time_by: "Von %{author} vor %{age} hinzugefügt" - label_updated_time_by: "Von %{author} vor %{age} aktualisiert" - label_updated_time: "Vor %{value} aktualisiert" - label_jump_to_a_project: Zu einem Projekt springen... - label_file_plural: Dateien - label_changeset_plural: Changesets - label_default_columns: Standard-Spalten - label_no_change_option: (Keine Änderung) - label_bulk_edit_selected_issues: Alle ausgewählten Tickets bearbeiten - label_theme: Stil - label_default: Standard - label_search_titles_only: Nur Titel durchsuchen - label_user_mail_option_all: "Für alle Ereignisse in all meinen Projekten" - label_user_mail_option_selected: "Für alle Ereignisse in den ausgewählten Projekten..." - label_user_mail_no_self_notified: "Ich möchte nicht über Änderungen benachrichtigt werden, die ich selbst durchführe." - label_registration_activation_by_email: Kontoaktivierung durch E-Mail - label_registration_manual_activation: Manuelle Kontoaktivierung - label_registration_automatic_activation: Automatische Kontoaktivierung - label_display_per_page: "Pro Seite: %{value}" - label_age: Geändert vor - label_change_properties: Eigenschaften ändern - label_general: Allgemein - label_more: Mehr - label_scm: Versionskontrollsystem - label_plugins: Plugins - label_ldap_authentication: LDAP-Authentifizierung - label_downloads_abbr: D/L - label_optional_description: Beschreibung (optional) - label_add_another_file: Eine weitere Datei hinzufügen - label_preferences: Präferenzen - label_chronological_order: in zeitlicher Reihenfolge - label_reverse_chronological_order: in umgekehrter zeitlicher Reihenfolge - label_planning: Terminplanung - label_incoming_emails: Eingehende E-Mails - label_generate_key: Generieren - label_issue_watchers: Beobachter - label_example: Beispiel - label_display: Anzeige - label_sort: Sortierung - label_ascending: Aufsteigend - label_descending: Absteigend - label_date_from_to: von %{start} bis %{end} - label_wiki_content_added: Die Wiki-Seite wurde erfolgreich hinzugefügt. - label_wiki_content_updated: Die Wiki-Seite wurde erfolgreich aktualisiert. - label_group: Gruppe - label_group_plural: Gruppen - label_group_new: Neue Gruppe - label_time_entry_plural: Benötigte Zeit - label_version_sharing_none: Nicht gemeinsam verwenden - label_version_sharing_descendants: Mit Unterprojekten - label_version_sharing_hierarchy: Mit Projekthierarchie - label_version_sharing_tree: Mit Projektbaum - label_version_sharing_system: Mit allen Projekten - label_update_issue_done_ratios: Ticket-Fortschritt aktualisieren - label_copy_source: Quelle - label_copy_target: Ziel - label_copy_same_as_target: So wie das Ziel - label_display_used_statuses_only: Zeige nur Status an, die von diesem Tracker verwendet werden - label_api_access_key: API-Zugriffsschlüssel - label_missing_api_access_key: Der API-Zugriffsschlüssel fehlt. - label_api_access_key_created_on: Der API-Zugriffsschlüssel wurde vor %{value} erstellt - label_profile: Profil - label_subtask_plural: Unteraufgaben - label_project_copy_notifications: Sende Mailbenachrichtigungen beim Kopieren des Projekts. - label_principal_search: "Nach Benutzer oder Gruppe suchen:" - label_user_search: "Nach Benutzer suchen:" - - button_login: Anmelden - button_submit: OK - button_save: Speichern - button_check_all: Alles auswählen - button_uncheck_all: Alles abwählen - button_delete: Löschen - button_create: Anlegen - button_create_and_continue: Anlegen und weiter - button_test: Testen - button_edit: Bearbeiten - button_edit_associated_wikipage: "Zugehörige Wikiseite bearbeiten: %{page_title}" - button_add: Hinzufügen - button_change: Wechseln - button_apply: Anwenden - button_clear: Zurücksetzen - button_lock: Sperren - button_unlock: Entsperren - button_download: Download - button_list: Liste - button_view: Anzeigen - button_move: Verschieben - button_move_and_follow: Verschieben und Ticket anzeigen - button_back: Zurück - button_cancel: Abbrechen - button_activate: Aktivieren - button_sort: Sortieren - button_log_time: Aufwand buchen - button_rollback: Auf diese Version zurücksetzen - button_watch: Beobachten - button_unwatch: Nicht beobachten - button_reply: Antworten - button_archive: Archivieren - button_unarchive: Entarchivieren - button_reset: Zurücksetzen - button_rename: Umbenennen - button_change_password: Kennwort ändern - button_copy: Kopieren - button_copy_and_follow: Kopieren und Ticket anzeigen - button_annotate: Annotieren - button_update: Bearbeiten - button_configure: Konfigurieren - button_quote: Zitieren - button_duplicate: Duplizieren - button_show: Anzeigen - - status_active: aktiv - status_registered: nicht aktivierte - status_locked: gesperrt - - version_status_open: offen - version_status_locked: gesperrt - version_status_closed: abgeschlossen - - field_active: Aktiv - - text_select_mail_notifications: Bitte wählen Sie die Aktionen aus, für die eine Mailbenachrichtigung gesendet werden soll. - text_regexp_info: z. B. ^[A-Z0-9]+$ - text_min_max_length_info: 0 heißt keine Beschränkung - text_project_destroy_confirmation: Sind Sie sicher, dass sie das Projekt löschen wollen? - text_subprojects_destroy_warning: "Dessen Unterprojekte (%{value}) werden ebenfalls gelöscht." - text_workflow_edit: Workflow zum Bearbeiten auswählen - text_are_you_sure: Sind Sie sicher? - text_journal_changed: "%{label} wurde von %{old} zu %{new} geändert" - text_journal_set_to: "%{label} wurde auf %{value} gesetzt" - text_journal_deleted: "%{label} %{old} wurde gelöscht" - text_journal_added: "%{label} %{value} wurde hinzugefügt" - text_tip_issue_begin_day: Aufgabe, die an diesem Tag beginnt - text_tip_issue_end_day: Aufgabe, die an diesem Tag endet - text_tip_issue_begin_end_day: Aufgabe, die an diesem Tag beginnt und endet - text_project_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
    Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' - text_caracters_maximum: "Max. %{count} Zeichen." - text_caracters_minimum: "Muss mindestens %{count} Zeichen lang sein." - text_length_between: "Länge zwischen %{min} und %{max} Zeichen." - text_tracker_no_workflow: Kein Workflow für diesen Tracker definiert. - text_unallowed_characters: Nicht erlaubte Zeichen - text_comma_separated: Mehrere Werte erlaubt (durch Komma getrennt). - text_line_separated: Mehrere Werte sind erlaubt (eine Zeile pro Wert). - text_issues_ref_in_commit_messages: Ticket-Beziehungen und -Status in Commit-Log-Meldungen - text_issue_added: "Ticket %{id} wurde erstellt von %{author}." - text_issue_updated: "Ticket %{id} wurde aktualisiert von %{author}." - text_wiki_destroy_confirmation: Sind Sie sicher, dass Sie dieses Wiki mit sämtlichem Inhalt löschen möchten? - text_issue_category_destroy_question: "Einige Tickets (%{count}) sind dieser Kategorie zugeodnet. Was möchten Sie tun?" - text_issue_category_destroy_assignments: Kategorie-Zuordnung entfernen - text_issue_category_reassign_to: Tickets dieser Kategorie zuordnen - text_user_mail_option: "Für nicht ausgewählte Projekte werden Sie nur Benachrichtigungen für Dinge erhalten, die Sie beobachten oder an denen Sie beteiligt sind (z. B. Tickets, deren Autor Sie sind oder die Ihnen zugewiesen sind)." - text_no_configuration_data: "Rollen, Tracker, Ticket-Status und Workflows wurden noch nicht konfiguriert.\nEs ist sehr zu empfehlen, die Standard-Konfiguration zu laden. Sobald sie geladen ist, können Sie sie abändern." - text_load_default_configuration: Standard-Konfiguration laden - text_status_changed_by_changeset: "Status geändert durch Changeset %{value}." - text_issues_destroy_confirmation: 'Sind Sie sicher, dass Sie die ausgewählten Tickets löschen möchten?' - text_select_project_modules: 'Bitte wählen Sie die Module aus, die in diesem Projekt aktiviert sein sollen:' - text_default_administrator_account_changed: Administrator-Kennwort geändert - text_file_repository_writable: Verzeichnis für Dateien beschreibbar - text_plugin_assets_writable: Verzeichnis für Plugin-Assets beschreibbar - text_rmagick_available: RMagick verfügbar (optional) - text_destroy_time_entries_question: Es wurden bereits %{hours} Stunden auf dieses Ticket gebucht. Was soll mit den Aufwänden geschehen? - text_destroy_time_entries: Gebuchte Aufwände löschen - text_assign_time_entries_to_project: Gebuchte Aufwände dem Projekt zuweisen - text_reassign_time_entries: 'Gebuchte Aufwände diesem Ticket zuweisen:' - text_user_wrote: "%{value} schrieb:" - text_enumeration_destroy_question: "%{count} Objekt(e) sind diesem Wert zugeordnet." - text_enumeration_category_reassign_to: 'Die Objekte stattdessen diesem Wert zuordnen:' - text_email_delivery_not_configured: "Der SMTP-Server ist nicht konfiguriert und Mailbenachrichtigungen sind ausgeschaltet.\nNehmen Sie die Einstellungen für Ihren SMTP-Server in config/configuration.yml vor und starten Sie die Applikation neu." - text_repository_usernames_mapping: "Bitte legen Sie die Zuordnung der Redmine-Benutzer zu den Benutzernamen der Commit-Log-Meldungen des Projektarchivs fest.\nBenutzer mit identischen Redmine- und Projektarchiv-Benutzernamen oder -E-Mail-Adressen werden automatisch zugeordnet." - text_diff_truncated: '... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.' - text_custom_field_possible_values_info: 'Eine Zeile pro Wert' - text_wiki_page_destroy_question: "Diese Seite hat %{descendants} Unterseite(n). Was möchten Sie tun?" - text_wiki_page_nullify_children: Verschiebe die Unterseiten auf die oberste Ebene - text_wiki_page_destroy_children: Lösche alle Unterseiten - text_wiki_page_reassign_children: Ordne die Unterseiten dieser Seite zu - text_own_membership_delete_confirmation: "Sie sind dabei, einige oder alle Ihre Berechtigungen zu entfernen. Es ist möglich, dass Sie danach das Projekt nicht mehr ansehen oder bearbeiten dürfen.\nSind Sie sicher, dass Sie dies tun möchten?" - text_zoom_in: Zoom in - text_zoom_out: Zoom out - - default_role_manager: Manager - default_role_developer: Entwickler - default_role_reporter: Reporter - default_tracker_bug: Fehler - default_tracker_feature: Feature - default_tracker_support: Unterstützung - default_issue_status_new: Neu - default_issue_status_in_progress: In Bearbeitung - default_issue_status_resolved: Gelöst - default_issue_status_feedback: Feedback - default_issue_status_closed: Erledigt - default_issue_status_rejected: Abgewiesen - default_doc_category_user: Benutzerdokumentation - default_doc_category_tech: Technische Dokumentation - default_priority_low: Niedrig - default_priority_normal: Normal - default_priority_high: Hoch - default_priority_urgent: Dringend - default_priority_immediate: Sofort - default_activity_design: Design - default_activity_development: Entwicklung - - enumeration_issue_priorities: Ticket-Prioritäten - enumeration_doc_categories: Dokumentenkategorien - enumeration_activities: Aktivitäten (Zeiterfassung) - enumeration_system_activity: System-Aktivität - - field_text: Textfeld - label_user_mail_option_only_owner: Nur für Aufgaben die ich angelegt habe - setting_default_notification_option: Standard Benachrichtigungsoptionen - label_user_mail_option_only_my_events: Nur für Aufgaben die ich beobachte oder an welchen ich mitarbeite - label_user_mail_option_only_assigned: Nur für Aufgaben für die ich zuständig bin. - notice_not_authorized_archived_project: Das Projekt wurde archiviert und ist daher nicht nicht verfügbar. - label_user_mail_option_none: keine Ereignisse - field_member_of_group: Zuständigkeitsgruppe - field_assigned_to_role: Zuständigkeitsrolle - field_visible: Sichtbar - setting_emails_header: E-Mail Betreffzeile - setting_commit_logtime_activity_id: Aktivität für die Zeiterfassung - text_time_logged_by_changeset: Angewendet in Changeset %{value}. - setting_commit_logtime_enabled: Aktiviere Zeitlogging - notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max}) - setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden. - field_warn_on_leaving_unsaved: vor dem Verlassen einer Seite mit ungesichertem Text im Editor warnen - text_warn_on_leaving_unsaved: Die aktuellen Änderungen gehen verloren, wenn Sie diese Seite verlassen. - label_my_queries: Meine eigenen Abfragen - text_journal_changed_no_detail: "%{label} aktualisiert" - label_news_comment_added: Kommentar zu einer News hinzugefügt - button_expand_all: Alle ausklappen - button_collapse_all: Alle einklappen - label_additional_workflow_transitions_for_assignee: Zusätzliche Berechtigungen wenn der Benutzer der Zugewiesene ist - label_additional_workflow_transitions_for_author: Zusätzliche Berechtigungen wenn der Benutzer der Autor ist - label_bulk_edit_selected_time_entries: Ausgewählte Zeitaufwände bearbeiten - text_time_entries_destroy_confirmation: Sind Sie sicher, dass Sie die ausgewählten Zeitaufwände löschen möchten? - label_role_anonymous: Anonymous - label_role_non_member: Nichtmitglied - label_issue_note_added: Notiz hinzugefügt - label_issue_status_updated: Status aktualisiert - label_issue_priority_updated: Priorität aktualisiert - label_issues_visibility_own: Tickets die folgender User erstellt hat oder die ihm zugewiesen sind - field_issues_visibility: Ticket Sichtbarkeit - label_issues_visibility_all: Alle Tickets - permission_set_own_issues_private: Eigene Tickets privat oder öffentlich markieren - field_is_private: Privat - permission_set_issues_private: Tickets privat oder öffentlich markieren - label_issues_visibility_public: Alle öffentlichen Tickets - text_issues_destroy_descendants_confirmation: Dies wird auch %{count} Unteraufgabe/n löschen. - field_commit_logs_encoding: Kodierung der Commit-Log-Meldungen - field_scm_path_encoding: Pfad Kodierung - text_scm_path_encoding_note: "Standard: UTF-8" - field_path_to_repository: Pfad zum repository - field_root_directory: Wurzelverzeichnis - field_cvs_module: Modul - field_cvsroot: CVSROOT - text_mercurial_repository_note: Lokales repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Kommando - text_scm_command_version: Version - label_git_report_last_commit: Bericht des letzten Commits für Dateien und Verzeichnisse - text_scm_config: Die SCM-Kommandos können in der in config/configuration.yml konfiguriert werden. Redmine muss anschließend neu gestartet werden. - text_scm_command_not_available: Scm Kommando ist nicht verfügbar. Bitte prüfen Sie die Einstellungen im Administrationspanel. - - notice_issue_successful_create: Ticket %{id} erstellt. - label_between: zwischen - setting_issue_group_assignment: Ticketzuweisung an Gruppen erlauben - label_diff: diff - text_git_repository_note: Repository steht für sich alleine (bare) und liegt lokal (z.B. /gitrepo, c:\gitrepo) - - description_filter: Filter - description_search: Suchfeld - description_choose_project: Projekte - description_project_scope: Suchbereich - description_notes: Kommentare - description_message_content: Nachrichteninhalt - description_query_sort_criteria_attribute: Sortierattribut - description_query_sort_criteria_direction: Sortierrichtung - description_user_mail_notification: Mailbenachrichtigungseinstellung - description_available_columns: Verfügbare Spalten - description_selected_columns: Ausgewählte Spalten - description_issue_category_reassign: Neue Kategorie wählen - description_wiki_subpages_reassign: Neue Elternseite wählen - description_date_range_list: Zeitraum aus einer Liste wählen - description_date_range_interval: Zeitraum durch Start- und Enddatum festlegen - description_date_from: Startdatum eintragen - description_date_to: Enddatum eintragen - - label_parent_revision: Vorgänger - label_child_revision: Nachfolger - error_scm_annotate_big_text_file: Der Eintrag kann nicht umgesetzt werden, da er die maximale Textlänge überschreitet. - setting_default_issue_start_date_to_creation_date: Aktuelles Datum als Beginn für neue Tickets verwenden - button_edit_section: Diesen Bereich bearbeiten - setting_repositories_encodings: Encoding von Anhängen und Repositories - description_all_columns: Alle Spalten - button_export: Exportieren - label_export_options: "%{export_format} Export-Eigenschaften" - error_attachment_too_big: Diese Datei kann nicht hochgeladen werden, da sie die maximale Dateigröße von (%{max_size}) überschreitet. - notice_failed_to_save_time_entries: "Gescheitert %{count} Zeiteinträge für %{total} von ausgewählten: %{ids} zu speichern." - label_x_issues: - zero: 0 Tickets - one: 1 Ticket - other: "%{count} Tickets" - label_repository_new: Neues Repository - field_repository_is_default: Haupt-Repository - label_copy_attachments: Anhänge Kopieren - label_item_position: "%{position}/%{count}" - label_completed_versions: Abgeschlossene Versionen - field_multiple: Mehrere Werte - setting_commit_cross_project_ref: Erlauben auf Tickets aller anderen Projekte zu referenzieren - text_issue_conflict_resolution_add_notes: Meine Änderungen übernehmen und alle anderen Änderungen verwerfen - text_issue_conflict_resolution_overwrite: Meine Änderungen trotzdem übernehmen (vorherige Notizen bleiben erhalten aber manche können überschrieben werden) - notice_issue_update_conflict: Das Ticket wurde von einem anderen Nutzer überarbeitet während Ihrer Bearbeitung. - text_issue_conflict_resolution_cancel: Meine Änderungen verwerfen und %{link} neu anzeigen - permission_manage_related_issues: Zugehörige Tickets verwalten - field_auth_source_ldap_filter: LDAP Filter - label_search_for_watchers: Nach hinzufügbaren Beobachtern suchen - notice_account_deleted: Ihr Benutzerkonto wurde unwiderruflich gelöscht. - setting_unsubscribe: Erlaubt Benutzern das eigene Benutzerkonto zu löschen - button_delete_my_account: Mein Benutzerkonto löschen - text_account_destroy_confirmation: Möchten Sie wirklich fortfahren?\nIhr Benutzerkonto wird für immer gelöscht und kann nicht wiederhergestellt werden. - error_session_expired: Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an. - text_session_expiration_settings: "Achtung: Änderungen können aktuelle Sitzungen beenden, Ihre eingeschlossen!" - setting_session_lifetime: Längste Dauer einer Sitzung - setting_session_timeout: Zeitüberschreitung bei Inaktivität - label_session_expiration: Ende einer Sitzung - permission_close_project: Schließen / erneutes Öffnen eines Projekts - label_show_closed_projects: Geschlossene Projekte anzeigen - button_close: Schließen - button_reopen: Öffnen - project_status_active: aktiv - project_status_closed: geschlossen - project_status_archived: archiviert - text_project_closed: Dieses Projekt ist geschlossen und kann nicht bearbeitet werden. - notice_user_successful_create: Benutzer %{id} angelegt. - field_core_fields: Standardwerte - field_timeout: Auszeit (in Sekunden) - setting_thumbnails_enabled: Vorschaubilder von Dateianhängen anzeigen - setting_thumbnails_size: Größe der Vorschaubilder (in Pixel) - label_status_transitions: Statusänderungen - label_fields_permissions: Feldberechtigungen - label_readonly: Nur-Lese-Zugriff - label_required: Erforderlich - text_repository_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
    Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' - field_board_parent: Übergeordnetes Forum - label_attribute_of_project: "%{name} des Projekts" - label_attribute_of_author: "%{name} des Autors" - label_attribute_of_assigned_to: "%{name} des Bearbeiters" - label_attribute_of_fixed_version: "%{name} der Zielversion" - label_copy_subtasks: Unteraufgaben kopieren - label_copied_to: Kopiert nach - label_copied_from: Kopiert von - label_any_issues_in_project: irgendein Ticket im Projekt - label_any_issues_not_in_project: irgendein Ticket nicht im Projekt - field_private_notes: Privater Kommentar - permission_view_private_notes: Private Kommentare sehen - permission_set_notes_private: Kommentar als privat markieren - label_no_issues_in_project: keine Tickets im Projekt - label_any: alle - label_last_n_weeks: letzte %{count} Wochen - setting_cross_project_subtasks: Projektübergreifende Unteraufgaben erlauben - label_cross_project_descendants: Mit Unterprojekten - label_cross_project_tree: Mit Projektbaum - label_cross_project_hierarchy: Mit Projekthierarchie - label_cross_project_system: Mit allen Projekten - button_hide: Verstecken - setting_non_working_week_days: Arbeitsfreie Tage - label_in_the_next_days: in den nächsten - label_in_the_past_days: in den letzten diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/d0/d04e674c0740817a528515c15ffb33465b7523c4.svn-base --- a/.svn/pristine/d0/d04e674c0740817a528515c15ffb33465b7523c4.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -== Redmine installation - -Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang -http://www.redmine.org/ - - -== Requirements - -* Ruby 1.8.7, 1.9.2 or 1.9.3 -* RubyGems -* Bundler >= 1.0.21 - -* A database: - * MySQL (tested with MySQL 5.1) - * PostgreSQL (tested with PostgreSQL 9.1) - * SQLite3 (tested with SQLite 3.6) - -Optional: -* SCM binaries (e.g. svn, git...), for repository browsing (must be available in PATH) -* ImageMagick (to enable Gantt export to png images) - -== Installation - -1. Uncompress the program archive - -2. Install the required gems by running: - bundle install --without development test - - If ImageMagick is not installed on your system, you should skip the installation - of the rmagick gem using: - bundle install --without development test rmagick - - If you need to load some gems that are not required by Redmine core (eg. fcgi), - you can create a file named Gemfile.local at the root of your redmine directory. - It will be loaded automatically when running `bundle install`. - -3. Create an empty utf8 encoded database: "redmine" for example - -4. Configure the database parameters in config/database.yml - for the "production" environment (default database is MySQL and ruby1.8) - - If you're running Redmine with MySQL and ruby1.9, replace the adapter name - with `mysql2` - -5. Generate a session store secret - - Redmine stores session data in cookies by default, which requires - a secret to be generated. Under the application main directory run: - rake generate_secret_token - -6. Create the database structure - - Under the application main directory run: - rake db:migrate RAILS_ENV="production" - - It will create all the tables and an administrator account. - -7. Setting up permissions (Windows users have to skip this section) - - The user who runs Redmine must have write permission on the following - subdirectories: files, log, tmp & public/plugin_assets. - - Assuming you run Redmine with a user named "redmine": - sudo chown -R redmine:redmine files log tmp public/plugin_assets - sudo chmod -R 755 files log tmp public/plugin_assets - -8. Test the installation by running the WEBrick web server - - Under the main application directory run: - ruby script/rails server -e production - - Once WEBrick has started, point your browser to http://localhost:3000/ - You should now see the application welcome page. - -9. Use the default administrator account to log in: - login: admin - password: admin - - Go to "Administration" to load the default configuration data (roles, - trackers, statuses, workflow) and to adjust the application settings - -== SMTP server Configuration - -Copy config/configuration.yml.example to config/configuration.yml and -edit this file to adjust your SMTP settings. -Do not forget to restart the application after any change to this file. - -Please do not enter your SMTP settings in environment.rb. - -== References - -* http://www.redmine.org/wiki/redmine/RedmineInstall -* http://www.redmine.org/wiki/redmine/EmailConfiguration -* http://www.redmine.org/wiki/redmine/RedmineSettings -* http://www.redmine.org/wiki/redmine/RedmineRepositories -* http://www.redmine.org/wiki/redmine/RedmineReceivingEmails -* http://www.redmine.org/wiki/redmine/RedmineReminderEmails -* http://www.redmine.org/wiki/redmine/RedmineLDAP diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/d0/d081bf55a7c6d79029b3f1816e9116ad715f9752.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d0/d081bf55a7c6d79029b3f1816e9116ad715f9752.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,168 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Acts + module Customizable + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def acts_as_customizable(options = {}) + return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods) + cattr_accessor :customizable_options + self.customizable_options = options + has_many :custom_values, :as => :customized, + :include => :custom_field, + :order => "#{CustomField.table_name}.position", + :dependent => :delete_all, + :validate => false + + send :include, Redmine::Acts::Customizable::InstanceMethods + validate :validate_custom_field_values + after_save :save_custom_field_values + end + end + + module InstanceMethods + def self.included(base) + base.extend ClassMethods + base.send :alias_method_chain, :reload, :custom_fields + end + + def available_custom_fields + CustomField.where("type = '#{self.class.name}CustomField'").sorted.all + end + + # Sets the values of the object's custom fields + # values is an array like [{'id' => 1, 'value' => 'foo'}, {'id' => 2, 'value' => 'bar'}] + def custom_fields=(values) + values_to_hash = values.inject({}) do |hash, v| + v = v.stringify_keys + if v['id'] && v.has_key?('value') + hash[v['id']] = v['value'] + end + hash + end + self.custom_field_values = values_to_hash + end + + # Sets the values of the object's custom fields + # values is a hash like {'1' => 'foo', 2 => 'bar'} + def custom_field_values=(values) + values = values.stringify_keys + + custom_field_values.each do |custom_field_value| + key = custom_field_value.custom_field_id.to_s + if values.has_key?(key) + value = values[key] + if value.is_a?(Array) + value = value.reject(&:blank?).uniq + if value.empty? + value << '' + end + end + custom_field_value.value = value + end + end + @custom_field_values_changed = true + end + + def custom_field_values + @custom_field_values ||= available_custom_fields.collect do |field| + x = CustomFieldValue.new + x.custom_field = field + x.customized = self + if field.multiple? + values = custom_values.select { |v| v.custom_field == field } + if values.empty? + values << custom_values.build(:customized => self, :custom_field => field, :value => nil) + end + x.value = values.map(&:value) + else + cv = custom_values.detect { |v| v.custom_field == field } + cv ||= custom_values.build(:customized => self, :custom_field => field, :value => nil) + x.value = cv.value + end + x + end + end + + def visible_custom_field_values + custom_field_values.select(&:visible?) + end + + def custom_field_values_changed? + @custom_field_values_changed == true + end + + def custom_value_for(c) + field_id = (c.is_a?(CustomField) ? c.id : c.to_i) + custom_values.detect {|v| v.custom_field_id == field_id } + end + + def custom_field_value(c) + field_id = (c.is_a?(CustomField) ? c.id : c.to_i) + custom_field_values.detect {|v| v.custom_field_id == field_id }.try(:value) + end + + def validate_custom_field_values + if new_record? || custom_field_values_changed? + custom_field_values.each(&:validate_value) + end + end + + def save_custom_field_values + target_custom_values = [] + custom_field_values.each do |custom_field_value| + if custom_field_value.value.is_a?(Array) + custom_field_value.value.each do |v| + target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field && cv.value == v} + target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field, :value => v) + target_custom_values << target + end + else + target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field} + target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field) + target.value = custom_field_value.value + target_custom_values << target + end + end + self.custom_values = target_custom_values + custom_values.each(&:save) + @custom_field_values_changed = false + true + end + + def reset_custom_values! + @custom_field_values = nil + @custom_field_values_changed = true + end + + def reload_with_custom_fields(*args) + @custom_field_values = nil + @custom_field_values_changed = false + reload_without_custom_fields(*args) + end + + module ClassMethods + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/d0/d09adc22c24263d1cc302440c2cd187a5c123446.svn-base --- a/.svn/pristine/d0/d09adc22c24263d1cc302440c2cd187a5c123446.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::IssueStatusesTest < ActionController::IntegrationTest - fixtures :issue_statuses - - def setup - Setting.rest_api_enabled = '1' - end - - context "/issue_statuses" do - context "GET" do - - should "return issue statuses" do - get '/issue_statuses.xml' - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'issue_statuses', - :attributes => {:type => 'array'}, - :child => { - :tag => 'issue_status', - :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'name', - :content => 'Assigned' - } - } - } - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/d0/d0cb7ff899061c806d3cee6fe597edd800fee259.svn-base --- a/.svn/pristine/d0/d0cb7ff899061c806d3cee6fe597edd800fee259.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -require 'iconv' - -module Redmine - module CodesetUtil - - def self.replace_invalid_utf8(str) - return str if str.nil? - if str.respond_to?(:force_encoding) - str.force_encoding('UTF-8') - if ! str.valid_encoding? - str = str.encode("US-ASCII", :invalid => :replace, - :undef => :replace, :replace => '?').encode("UTF-8") - end - elsif RUBY_PLATFORM == 'java' - begin - ic = Iconv.new('UTF-8', 'UTF-8') - str = ic.iconv(str) - rescue - str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') - end - else - ic = Iconv.new('UTF-8', 'UTF-8') - txtar = "" - begin - txtar += ic.iconv(str) - rescue Iconv::IllegalSequence - txtar += $!.success - str = '?' + $!.failed[1,$!.failed.length] - retry - rescue - txtar += $!.success - end - str = txtar - end - str - end - - def self.to_utf8(str, encoding) - return str if str.nil? - str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding) - if str.empty? - str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) - return str - end - enc = encoding.blank? ? "UTF-8" : encoding - if str.respond_to?(:force_encoding) - if enc.upcase != "UTF-8" - str.force_encoding(enc) - str = str.encode("UTF-8", :invalid => :replace, - :undef => :replace, :replace => '?') - else - str.force_encoding("UTF-8") - if ! str.valid_encoding? - str = str.encode("US-ASCII", :invalid => :replace, - :undef => :replace, :replace => '?').encode("UTF-8") - end - end - elsif RUBY_PLATFORM == 'java' - begin - ic = Iconv.new('UTF-8', enc) - str = ic.iconv(str) - rescue - str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') - end - else - ic = Iconv.new('UTF-8', enc) - txtar = "" - begin - txtar += ic.iconv(str) - rescue Iconv::IllegalSequence - txtar += $!.success - str = '?' + $!.failed[1,$!.failed.length] - retry - rescue - txtar += $!.success - end - str = txtar - end - str - end - - def self.to_utf8_by_setting(str) - return str if str.nil? - str = self.to_utf8_by_setting_internal(str) - if str.respond_to?(:force_encoding) - str.force_encoding('UTF-8') - end - str - end - - def self.to_utf8_by_setting_internal(str) - return str if str.nil? - if str.respond_to?(:force_encoding) - str.force_encoding('ASCII-8BIT') - end - return str if str.empty? - return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii - if str.respond_to?(:force_encoding) - str.force_encoding('UTF-8') - end - encodings = Setting.repositories_encodings.split(',').collect(&:strip) - encodings.each do |encoding| - begin - return Iconv.conv('UTF-8', encoding, str) - rescue Iconv::Failure - # do nothing here and try the next encoding - end - end - str = self.replace_invalid_utf8(str) - if str.respond_to?(:force_encoding) - str.force_encoding('UTF-8') - end - str - end - - def self.from_utf8(str, encoding) - str ||= '' - if str.respond_to?(:force_encoding) - str.force_encoding('UTF-8') - if encoding.upcase != 'UTF-8' - str = str.encode(encoding, :invalid => :replace, - :undef => :replace, :replace => '?') - else - str = self.replace_invalid_utf8(str) - end - elsif RUBY_PLATFORM == 'java' - begin - ic = Iconv.new(encoding, 'UTF-8') - str = ic.iconv(str) - rescue - str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') - end - else - ic = Iconv.new(encoding, 'UTF-8') - txtar = "" - begin - txtar += ic.iconv(str) - rescue Iconv::IllegalSequence - txtar += $!.success - str = '?' + $!.failed[1, $!.failed.length] - retry - rescue - txtar += $!.success - end - str = txtar - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/d0/d0ef4d83b348265867fc9cc9e1a5ce1317d548e2.svn-base --- a/.svn/pristine/d0/d0ef4d83b348265867fc9cc9e1a5ce1317d548e2.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class FilesController < ApplicationController - menu_item :files - - before_filter :find_project_by_project_id - before_filter :authorize - - helper :sort - include SortHelper - - def index - sort_init 'filename', 'asc' - sort_update 'filename' => "#{Attachment.table_name}.filename", - 'created_on' => "#{Attachment.table_name}.created_on", - 'size' => "#{Attachment.table_name}.filesize", - 'downloads' => "#{Attachment.table_name}.downloads" - - @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)] - @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse - render :layout => !request.xhr? - end - - def new - @versions = @project.versions.sort - end - - def create - container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) - attachments = Attachment.attach_files(container, params[:attachments]) - render_attachment_warning_if_needed(container) - - if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') - Mailer.attachments_added(attachments[:files]).deliver - end - redirect_to project_files_path(@project) - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/d1/d118d601fbdc765e4b097715615b532bc2112521.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d1/d118d601fbdc765e4b097715615b532bc2112521.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,2682 @@ +== Redmine changelog + +Redmine - project management software +Copyright (C) 2006-2014 Jean-Philippe Lang +http://www.redmine.org/ + +== 2014-07-06 v2.4.6 + +* Defect #13932: File upload does not work with Safari +* Defect #16467: back_url redirection does not work for '/' +* Defect #16511: Potentiel data leak in "Invalid form authenticity token" error screen +* Defect #16530: back_url redirection work for non sub uri +* Defect #16711: Non-ascii attachment file name get corrupted in IE11 +* Defect #17151: File upload broken on Chrome 36 +* Defect #17235: use test_email report can't modify frozen String +* Defect #17391: Upgrade to Rails 3.2.19 +* Patch #17206: Fix Invalid CSS on Error-Pages + +== 2014-03-29 v2.4.5 + +* Defect #16466: Fixed back url verification + +== 2014-03-02 v2.4.4 + +* Defect #16081: Export CSV - Custom field true/false not using translation +* Defect #16161: Parent task search and datepicker not available after changing status +* Defect #16169: Wrong validation when updating integer custom field with spaces +* Defect #16177: Mercurial 2.9 compatibility + +== 2014-02-08 v2.4.3 + +* Defect #13544: Commit reference: autogenerated issue note has wrong commit link syntax in multi-repo or cross-project context +* Defect #15664: Unable to upload attachments without add_issues, edit_issues or add_issue_notes permission +* Defect #15756: 500 on admin info/settings page on development environment +* Defect #15781: Customfields have a noticable impact on search performance due to slow database COUNT +* Defect #15849: Redmine:Fetch_Changesets Single-inheritance issue in subclass "Repository:Git" +* Defect #15870: Parent task completion is 104% after update of subtasks +* Defect #16032: Repository.fetch_changesets > app/models/repository/git.rb:137:in `[]=': string not matched (IndexError) +* Defect #16038: Issue#css_classes corrupts user.groups association cache +* Patch #15960: pt-BR translation for 2.4-stable + +Additional note: + +#15781 was forgotten to merge to v2.4.3. +It is in v2.5.0. + +== 2013-12-23 v2.4.2 + +* Defect #15398: HTML 5 invalid
    tag +* Defect #15523: CSS class for done ratio is not properly generated +* Defect #15623: Timelog filtering by activity field does not handle project activity overrides +* Defect #15677: Links for relations in notifications do not include hostname +* Defect #15684: MailHandler : text/plain attachments are added to description +* Defect #15714: Notification on loosing assignment does not work +* Defect #15735: OpenID login fails due to CSRF verification +* Defect #15741: Multiple scrollbars in project selection tree +* Patch #9442: Russian wiki syntax help translations +* Patch #15524: Japanese translation update (r12278) +* Patch #15601: Turkish translation update +* Patch #15688: Spanish translation updated +* Patch #15696: Russian translation update + +== 2013-11-23 v2.4.1 + +* Defect #15401: Wiki syntax "bold italic" is incorrect +* Defect #15414: Empty sidebar should not be displayed in project overview +* Defect #15427: REST API POST and PUT broken +* Patch #15376: Traditional Chinese translation (to r12295) +* Patch #15395: German "ImageMagick convert available" translation +* Patch #15400: Czech Wiki syntax traslation +* Patch #15402: Czech translation for 2.4-stable + +== 2013-11-17 v2.4.0 + +* Defect #1983: statistics get rather cramped with more than 15 or so contributers +* Defect #7335: Sorting issues in gantt by date, not by id +* Defect #12681: Treat group assignments as assigned to me +* Defect #12824: Useless "edit" link in workflow menu +* Defect #13260: JQuery Datepicker popup is missing multiple month/year modifiers +* Defect #13537: Filters will show issues with unused custom fields. +* Defect #13829: Favicon bug in IE8 +* Defect #13949: Handling of attachment uploads when 'Maximum attachment size' is set to 0 +* Defect #13989: Trac and Mantis importers reset global notification settings +* Defect #13990: Trac importer breaks on exotic filenames and ruby 1.9+ +* Defect #14028: Plugins Gemfiles loading breaks __FILE__ +* Defect #14086: Better handling of issue start date validation +* Defect #14206: Synchronize the lang attribute of the HTML with the display language +* Defect #14403: No error message if notification mail could not delivered +* Defect #14516: Missing Sort Column Label and Center Align on Admin-Enumerations +* Defect #14517: Missing Html Tile on Admin (Groups, LDAP and Plugins) +* Defect #14598: Wrong test with logger.info in model mail_handler +* Defect #14615: Warn me when leaving a page with unsaved text doesn't work when editing an update note +* Defect #14621: AJAX call on the issue form resets data entered during the request +* Defect #14657: Wrong German translation for member inheritance +* Defect #14773: ActiveRecord::Acts::Versioned::ActMethods#next_version Generates ArgumentError +* Defect #14819: Newlines in attachment filename causes crash +* Defect #14986: 500 error when viewing a wiki page without WikiContent +* Defect #14995: Japanese "notice_not_authorized" translation is incorrect +* Defect #15044: Patch for giving controller_issues_edit_after_save api hook the correct context +* Defect #15050: redmine:migrate_from_mantis fails to migrate projects with all upper case name +* Defect #15058: Project authorization EnabledModule N+1 queries +* Defect #15113: The mail method should return a Mail::Message +* Defect #15135: Issue#update_nested_set_attributes comparing nil with empty string +* Defect #15191: HTML 5 validation failures +* Defect #15227: Custom fields in issue form - splitting is incorrect +* Defect #15307: HTML 5 deprecates width and align attributes +* Feature #1005: Add the addition/removal/change of related issues to the history +* Feature #1019: Role based custom queries +* Feature #1391: Ability to force user to change password +* Feature #2199: Ability to clear dates and text fields when bulk editing issues +* Feature #2427: Document horizontal rule syntax +* Feature #2795: Add a "Cancel" button to the "Delete" project page when deleting a project. +* Feature #2865: One click filter in search view +* Feature #3413: Exclude attachments from incoming emails based on file name +* Feature #3872: New user password - better functionality +* Feature #4911: Multiple issue update rules with different keywords in commit messages +* Feature #5037: Role-based issue custom field visibility +* Feature #7590: Different commit Keywords for each tracker +* Feature #7836: Ability to save Gantt query filters +* Feature #8253: Update CodeRay to 1.1 final +* Feature #11159: REST API for getting CustomField definitions +* Feature #12293: Add links to attachments in new issue email notification +* Feature #12912: Issue-notes Redmine links: append actual note reference to rendered links +* Feature #13157: Link on "My Page" to view all my spent time +* Feature #13746: Highlighting of source link target line +* Feature #13943: Better handling of validation errors when bulk editing issues +* Feature #13945: Disable autofetching of repository changesets if projects are closed +* Feature #14024: Default of issue start and due date +* Feature #14060: Enable configuration of OpenIdAuthentication.store +* Feature #14228: Registered users should have a way to get a new action email +* Feature #14614: View hooks for user preferences +* Feature #14630: wiki_syntax.html per language (wiki help localization mechanism) +* Feature #15136: Activate Custom Fields on a selection of projects directly from Custom fields page +* Feature #15182: Return to section anchor after wiki section edit +* Feature #15218: Update Rails 3.2.15 +* Feature #15311: Add an indication to admin/info whether or not ImageMagick convert is available +* Patch #6689: Document project-links in parse_redmine_links +* Patch #13460: All translations: RSS -> Atom +* Patch #13482: Do not add empty header/footer to notification emails +* Patch #13528: Traditional Chinese "label_total_time" translation +* Patch #13551: update Dutch translations - March 2013 +* Patch #13577: Japanese translation improvement ("done ratio") +* Patch #13646: Fix handling multiple text parts in email +* Patch #13674: Lithuanian translation +* Patch #13687: Favicon bug in opera browser +* Patch #13697: Back-button on diff page is not working when I'm directed from email +* Patch #13745: Correct translation for member save button +* Patch #13808: Changed Bulgarian "label_statistics" translation +* Patch #13825: German translation: jquery.ui.datepicker-de.js +* Patch #13900: Update URL when changing tab +* Patch #13931: Error and inconsistencies in Croatian translation +* Patch #13948: REST API should return user.status +* Patch #13988: Enhanced Arabic translation +* Patch #14138: Output changeset comment in html title +* Patch #14180: Improve pt-BR translation +* Patch #14222: German translation: grammar + spelling +* Patch #14223: Fix icon transparency issues +* Patch #14360: Slovene language translation +* Patch #14767: More CSS classes on various fields +* Patch #14901: Slovak translation +* Patch #14920: Russian numeric translation +* Patch #14981: Italian translation +* Patch #15072: Optimization of issues journal custom fields display +* Patch #15073: list custom fields : multiple select filter wider +* Patch #15075: Fix typo in the Dutch "label_user_mail_option_all" translation +* Patch #15277: Accept custom field format added at runtime +* Patch #15295: Log error messages when moving attachements in sub-directories +* Patch #15369: Bulgarian translation (r12278) + +== 2013-11-17 v2.3.4 + +* Defect #13348: Repository tree can't handle two loading at once +* Defect #13632: Empty page attached when exporting PDF +* Defect #14590: migrate_from_trac.rake does not import Trac users, uses too short password +* Defect #14656: JRuby: Encoding error when creating issues +* Defect #14883: Update activerecord-jdbc-adapter +* Defect #14902: Potential invalid SQL error with invalid group_ids +* Defect #14931: SCM annotate with non ASCII author +* Defect #14960: migrate_from_mantis.rake does not import Mantis users, uses too short password +* Defect #14977: Internal Server Error while uploading file +* Defect #15190: JS-error while using a global custom query w/ project filter in a project context +* Defect #15235: Wiki Pages REST API with version returns wrong comments +* Defect #15344: Default status always inserted to allowed statuses when changing status +* Feature #14919: Update ruby-openid version above 2.3.0 +* Patch #14592: migrate_from_trac.rake does not properly parse First Name and Last Name +* Patch #14886: Norweigan - label_copied_to and label_copied_from translated +* Patch #15185: Simplified Chinese translation for 2.3-stable + +== 2013-09-14 v2.3.3 + +* Defect #13008: Usage of attribute_present? in UserPreference +* Defect #14340: Autocomplete fields rendering issue with alternate theme +* Defect #14366: Spent Time report sorting on custom fields causes error +* Defect #14369: Open/closed issue counts on issues summary are not displayed with SQLServer +* Defect #14401: Filtering issues on "related to" may ignore other filters +* Defect #14415: Spent time details and report should ignore 'Setting.display_subprojects_issues?' when 'Subproject' filter is enabled. +* Defect #14422: CVS root_url not recognized when connection string does not include port +* Defect #14447: Additional status transitions for assignees do not work if assigned to a group +* Defect #14511: warning: class variable access from toplevel on Ruby 2.0 +* Defect #14562: diff of CJK (Chinese/Japanese/Korean) is broken on Ruby 1.8 +* Defect #14584: Standard fields disabled for certain trackers still appear in email notifications +* Defect #14607: rake redmine:load_default_data Error +* Defect #14697: Wrong Russian translation in close project message +* Defect #14798: Wrong done_ratio calculation for parent with subtask having estimated_hours=0 +* Patch #14485: Traditional Chinese translation for 2.3-stable +* Patch #14502: Russian translation for 2.3-stable +* Patch #14531: Spanish translations for 2.3.x +* Patch #14686: Portuguese translation for 2.3-stable + +== 2013-07-14 v2.3.2 + +* Defect #9996: configuration.yml in documentation , but redmine ask me to create email.yml +* Defect #13692: warning: already initialized constant on Ruby 1.8.7 +* Defect #13783: Internal error on time tracking activity enumeration deletion +* Defect #13821: "obj" parameter is not defined for macros used in description of documents +* Defect #13850: Unable to set custom fields for versions using the REST API +* Defect #13910: Values of custom fields are not kept in issues when copying a project +* Defect #13950: Duplicate Lithuanian "error_attachment_too_big" translation keys +* Defect #14015: Ruby hangs when adding a subtask +* Defect #14020: Locking and unlocking a user resets the email notification checkbox +* Defect #14023: Can't delete relation when Redmine runs in a subpath +* Defect #14051: Filtering issues with custom field in date format with NULL(empty) value +* Defect #14178: PDF API broken in version 2.3.1 +* Defect #14186: Project name is not properly escaped in issue filters JSON +* Defect #14242: Project auto generation fails when projects created in the same time +* Defect #14245: Gem::InstallError: nokogiri requires Ruby version >= 1.9.2. +* Defect #14346: Latvian translation for "Log time" +* Feature #12888: Adding markings to emails generated by Private comments +* Feature #14419: Include RUBY_PATCHLEVEL and RUBY_RELEASE_DATE in info.rb +* Patch #14005: Swedish Translation for 2.3-stable +* Patch #14101: Receive IMAP by uid's +* Patch #14103: Disconnect and logout from IMAP after mail receive +* Patch #14145: German translation of x_hours +* Patch #14182: pt-BR translation for 2.3-stable +* Patch #14196: Italian translation for 2.3-stable +* Patch #14221: Translation of x_hours for many languages + +== 2013-05-01 v2.3.1 + +* Defect #12650: Lost text after selection in issue list with IE +* Defect #12684: Hotkey for Issue-Edit doesn't work as expected +* Defect #13405: Commit link title is escaped twice when using "commit:" prefix +* Defect #13541: Can't access SCM when log/production.scm.stderr.log is not writable +* Defect #13579: Datepicker uses Simplified Chinese in Traditional Chinese locale +* Defect #13584: Missing Portuguese jQuery UI date picker +* Defect #13586: Circular loop testing prevents precedes/follows relation between subtasks +* Defect #13618: CSV export of spent time ignores filters and columns selection +* Defect #13630: PDF export generates the issue id twice +* Defect #13644: Diff - Internal Error +* Defect #13712: Fix email rake tasks to also support no_account_notice and default_group options +* Defect #13811: Broken javascript in IE7 ; recurrence of #12195 +* Defect #13823: Trailing comma in javascript files +* Patch #13531: Traditional Chinese translation for 2.3-stable +* Patch #13552: Dutch translations for 2.3-stable +* Patch #13678: Lithuanian translation for 2.3-stable + +== 2013-03-19 v2.3.0 + +* Defect #3107: Issue with two digit year on Logtime +* Defect #3371: Autologin does not work when using openid +* Defect #3676: www. generates broken link in formatted text +* Defect #4700: Adding news does not send notification to all project members +* Defect #5329: Time entries report broken on first week of year +* Defect #8794: Circular loop when using relations and subtasks +* Defect #9475: German Translation "My custom queries" and "Custom queries" +* Defect #9549: Only 100 users are displayed when adding new project members +* Defect #10277: Redmine wikitext URL-into-link creation with hyphen is wrong +* Defect #10364: Custom field float separator in CSV export +* Defect #10930: rake redmine:load_default_data error in 2.0 with SQLServer +* Defect #10977: Redmine shouldn't require all database gems +* Defect #12528: Handle temporary failures gracefully in the external mail handler script +* Defect #12629: Wrong German "label_issues_by" translation +* Defect #12641: Diff outputs become ??? in some non ASCII words. +* Defect #12707: Typo in app/models/tracker.rb +* Defect #12716: Attachment description lost when issue validation fails +* Defect #12735: Negative duration allowed +* Defect #12736: Negative start/due dates allowed +* Defect #12968: Subtasks don't resepect following/precedes +* Defect #13006: Filter "Assignee's group" doesn't work with group assignments +* Defect #13022: Image pointing towards /logout signs out user +* Defect #13059: Custom fields are listed two times in workflow/Fields permission +* Defect #13076: Project overview page shows trackers from subprojects with disabled issue module +* Defect #13119: custom_field_values are not reloaded on #reload +* Defect #13154: After upgrade to 2.2.2 ticket list on some projects fails +* Defect #13188: Forms are not updated after changing the status field without "Add issues" permission +* Defect #13251: Adding a "follows" relation may not refresh relations list +* Defect #13272: translation missing: setting_default_projects_tracker_ids +* Defect #13328: Copying an issue as a child of itself creates an extra issue +* Defect #13335: Autologin does not work with custom autologin cookie name +* Defect #13350: Japanese mistranslation fix +* Feature #824: Add "closed_on" issue field (storing time of last closing) & add it as a column and filter on the issue list. +* Feature #1766: Custom fields should become addable to Spent Time list/report +* Feature #3436: Show relations in Gantt diagram +* Feature #3957: Ajax file upload with progress bar +* Feature #5298: Store attachments in sub directories +* Feature #5605: Subprojects should (optionally) inherit Members from their parent +* Feature #6727: Add/remove issue watchers via REST API +* Feature #7159: Bulk watch/unwatch issues from the context menu +* Feature #8529: Get the API key of the user through REST API +* Feature #8579: Multiple file upload with HTML5 / Drag-and-Drop +* Feature #10191: Add Filters For Spent time's Details and Report +* Feature #10286: Auto-populate fields while creating a new user with LDAP +* Feature #10352: Preview should already display the freshly attached images +* Feature #11498: Add --no-account-notice option for the mail handler script +* Feature #12122: Gantt progress lines (html only) +* Feature #12228: JRuby 1.7.2 support +* Feature #12251: Custom fields: 'Multiple values' should be able to be checked and then unchecked +* Feature #12401: Split "Manage documents" permission into create, edit and delete permissions +* Feature #12542: Group events in the activity view +* Feature #12665: Link to a file in a repository branch +* Feature #12713: Microsoft SQLServer support +* Feature #12787: Remove "Warning - iconv will be deprecated in the future, use String#encode instead." +* Feature #12843: Add links to projects in Group projects list +* Feature #12898: Handle GET /issues/context_menu parameters nicely to prevent returning error 500 to crawlers +* Feature #12992: Make JSONP support optional and disabled by default +* Feature #13174: Raise group name maximum length to 255 characters +* Feature #13175: Possibility to define the default enable trackers when creating a project +* Feature #13329: Ruby 2.0 support +* Feature #13337: Split translation "label_total" +* Feature #13340: Mail handler: option to add created user to default group +* Feature #13341: Mail handler: --no-notification option to disable notifications to the created user +* Patch #7202: Polish translation for v1.0.4 +* Patch #7851: Italian translation for 'issue' +* Patch #9225: Generate project identifier automatically with JavaScript +* Patch #10916: Optimisation in issues relations display +* Patch #12485: Don't force english language for default admin account +* Patch #12499: Use lambda in model scopes +* Patch #12611: Login link unexpected logs you out +* Patch #12626: Updated Japanese translations for button_view and permission_commit_access +* Patch #12640: Russian "about_x_hours" translation change +* Patch #12645: Russian numeric translation +* Patch #12660: Consistent German translation for my page +* Patch #12708: Restructured german translation (Cleanup) +* Patch #12721: Optimize MenuManager a bit +* Patch #12725: Change pourcent to percent (#12724) +* Patch #12754: Updated Japanese translation for notice_account_register_done +* Patch #12788: Copyright for 2013 +* Patch #12806: Serbian translation change +* Patch #12810: Swedish Translation change +* Patch #12910: Plugin settings div should perhaps have 'settings' CSS class +* Patch #12911: Fix 500 error for requests to the settings path for non-configurable plugins +* Patch #12926: Bulgarian translation (r11218) +* Patch #12927: Swedish Translation for r11244 +* Patch #12967: Change Spanish login/logout translations +* Patch #12988: Russian translation for trunk +* Patch #13080: German translation of label_in +* Patch #13098: Small datepicker improvements +* Patch #13152: Locale file for Azerbaijanian language +* Patch #13155: Add login to /users/:id API for current user +* Patch #13173: Put source :rubygems url HTTP secure +* Patch #13190: Bulgarian translation (r11404) +* Patch #13198: Traditional Chinese language file (to r11426) +* Patch #13203: German translation change for follow and precedes is inconsitent +* Patch #13206: Portuguese translation file +* Patch #13246: Some german translation patches +* Patch #13280: German translation (r11478) +* Patch #13301: Performance: avoid querying all memberships in User#roles_for_project +* Patch #13309: Add "tracker-[id]" CSS class to issues +* Patch #13324: fixing some pt-br locales +* Patch #13339: Complete language Vietnamese file +* Patch #13391: Czech translation update +* Patch #13399: Fixed some wrong or confusing translation in Korean locale +* Patch #13414: Bulgarian translation (r11567) +* Patch #13420: Korean translation for 2.3 (r11583) +* Patch #13437: German translation of setting_emails_header +* Patch #13438: English translation +* Patch #13447: German translation - some patches +* Patch #13450: Czech translation +* Patch #13475: fixing some pt-br locales +* Patch #13514: fixing some pt-br locales + +== 2013-03-19 v2.2.4 + +* Upgrade to Rails 3.2.13 +* Defect #12243: Ordering forum replies by last reply date is broken +* Defect #13127: h1 multiple lined titles breaks into main menu +* Defect #13138: Generating PDF of issue causes UndefinedConversionError with htmlentities gem +* Defect #13165: rdm-mailhandler.rb: initialize_http_header override basic auth +* Defect #13232: Link to topic in nonexistent forum causes error 500 +* Patch #13181: Bulgarian translation of jstoolbar-bg.js +* Patch #13207: Portuguese translation for 2.2-stable +* Patch #13310: pt-BR label_last_n_weeks translation +* Patch #13325: pt-BR translation for 2.2-stable +* Patch #13343: Vietnamese translation for 2.2-stable +* Patch #13398: Czech translation for 2.2-stable + +== 2013-02-12 v2.2.3 + +* Upgrade to Rails 3.2.12 +* Defect #11987: pdf: Broken new line in table +* Defect #12930: 404 Error when referencing different project source files in the wiki syntax +* Defect #12979: Wiki link syntax commit:repo_a:abcd doesn't work +* Defect #13075: Can't clear custom field value through context menu in the issue list +* Defect #13097: Project copy fails when wiki module is disabled +* Defect #13126: Issue view: estimated time vs. spent time +* Patch #12922: Update Spanish translation +* Patch #12928: Bulgarian translation for 2.2-stable +* Patch #12987: Russian translation for 2.2-stable + +== 2013-01-20 v2.2.2 + +* Defect #7510: Link to attachment should return latest attachment +* Defect #9842: {{toc}} is not replaced by table of content when exporting wiki page to pdf +* Defect #12749: Plugins cannot route wiki page sub-path +* Defect #12799: Cannot edit a wiki section which title starts with a tab +* Defect #12801: Viewing the history of a wiki page with attachments raises an error +* Defect #12833: Input fields restricted on length should have maxlength parameter set +* Defect #12838: Blank page when clicking Add with no block selected on my page layout +* Defect #12851: "Parent task is invalid" while editing child issues by Role with restricted Issues Visibility +* Patch #12800: Serbian Latin translation patch (sr-YU.yml) +* Patch #12809: Swedish Translation for r11162 +* Patch #12818: Minor swedish translation fix + +== 2013-01-09 v2.2.1 + +* Upgrade to Rails 3.2.11 +* Defect #12652: "Copy ticket" selects "new ticket" +* Defect #12691: Textile Homepage Dead? +* Defect #12711: incorrect fix of lib/SVG/Graph/TimeSeries.rb +* Defect #12744: Unable to call a macro with a name that contains uppercase letters +* Defect #12776: Security vulnerability in Rails 3.2.10 (CVE-2013-0156) +* Patch #12630: Russian "x_hours" translation + +== 2012-12-18 v2.2.0 + +* Defect #4787: Gannt to PNG - CJK (Chinese, Japanese and Korean) characters appear as ? +* Defect #8106: Issues by Category should show tasks without category +* Defect #8373: i18n string text_are_you_sure_with_children no longer used +* Defect #11426: Filtering with Due Date in less than N days should show overdue issues +* Defect #11834: Bazaar: "???" instead of non ASCII character in paths on non UTF-8 locale +* Defect #11868: Git and Mercurial diff displays deleted files as /dev/null +* Defect #11979: No validation errors when entering an invalid "Parent task" +* Defect #12012: Redmine::VERSION.revision method does not work on Subversion 1.7 working copy +* Defect #12018: Issue filter select box order changes randomly +* Defect #12090: email recipients not written to action_mailer log if BCC recipients setting is checked +* Defect #12092: Issue "start date" validation does not work correctly +* Defect #12285: Some unit and functional tests miss fixtures and break when run alone +* Defect #12286: Emails of private notes are sent to watcher users regardless of viewing permissions +* Defect #12310: Attachments may not be displayed in the order they were selected +* Defect #12356: Issue "Update" link broken focus +* Defect #12397: Error in Textile conversion of HTTP links, containing russian letters +* Defect #12434: Respond with 404 instead of 500 when requesting a wiki diff with invalid versions +* Feature #1554: Private comments in tickets +* Feature #2161: Time tracking code should respect weekends as "no work" days +* Feature #3239: Show related issues on the Issues Listing +* Feature #3265: Filter on issue relations +* Feature #3447: Option to display the issue descriptions on the issues list +* Feature #3511: Ability to sort issues by grouped column +* Feature #4590: Precede-Follow relation should move following issues when rescheduling issue earlier +* Feature #5487: Allow subtasks to cross projects +* Feature #6899: Add a relation between the original and copied issue +* Feature #7082: Rest API for wiki +* Feature #9835: REST API - List priorities +* Feature #10789: Macros {{child_pages}} with depth parameter +* Feature #10852: Ability to delete a version from a wiki page history +* Feature #10937: new user format #{lastname} +* Feature #11502: Expose roles details via REST API +* Feature #11755: Impersonate user through REST API auth +* Feature #12085: New user name format: firstname + first letter of lastname +* Feature #12125: Set filename used to store attachment updloaded via the REST API +* Feature #12167: Macro for inserting collapsible block of text +* Feature #12211: Wrap issue description and its contextual menu in a div +* Feature #12216: Textual CSS class for priorities +* Feature #12299: Redmine version requirement improvements (in plugins) +* Feature #12393: Upgrade to Rails 3.2.9 +* Feature #12475: Lazy loading of translation files for faster startup +* Patch #11846: Fill username when authentification failed +* Patch #11862: Add "last 2 weeks" preset to time entries reporting +* Patch #11992: Japanese translation about issue relations improved +* Patch #12027: Incorrect Spanish "September" month name +* Patch #12061: Japanese translation improvement (permission names) +* Patch #12078: User#allowed_to? should return true or false +* Patch #12117: Change Japanese translation of "admin" +* Patch #12142: Updated translation in Lithuanian +* Patch #12232: German translation enhancements +* Patch #12316: Fix Lithuanian numeral translation +* Patch #12494: Bulgarian "button_submit" translation change +* Patch #12514: Updated translation in Lithuanian +* Patch #12602: Korean translation update for 2.2-stable +* Patch #12608: Norwegian translation changed +* Patch #12619: Russian translation change + +== 2012-12-18 v2.1.5 + +* Defect #12400: Validation fails when receiving an email with list custom fields +* Defect #12451: Macros.rb extract_macro_options should use lazy search +* Defect #12513: Grouping of issues by custom fields not correct in PDF export +* Defect #12566: Issue history notes previews are broken +* Defect #12568: Clicking "edit" on a journal multiple times shows multiple forms +* Patch #12605: Norwegian translation for 1.4-stable update +* Patch #12614: Dutch translation +* Patch #12615: Russian translation + +== 2012-11-24 v2.1.4 + +* Defect #12274: Wiki export from Index by title is truncated +* Defect #12298: Right-click context menu unable to batch/bulk update (IE8) +* Defect #12332: Repository identifier does not display on Project/Settings/Repositories +* Defect #12396: Error when receiving an email without subject header +* Defect #12399: Non ASCII attachment filename encoding broken (MOJIBAKE) in receiving mail on Ruby 1.8 +* Defect #12409: Git: changesets aren't read after clear_changesets call +* Defect #12431: Project.rebuild! sorts root projects by id instead of name + +== 2012-11-17 v2.1.3 + +* Defect #12050: :export links to repository files lead to a 404 error +* Defect #12189: Missing tmp/pdf directory +* Defect #12195: Javascript error with IE7 / IE8 on new issue form +* Defect #12196: "Page not found" on OK button in SCM "View all revisions" page +* Defect #12199: Confirmation message displayed when clicking a disabled delete link in the context menu +* Defect #12231: Hardcoded "Back" in Repository +* Defect #12294: Incorrect german translation for "registered" users filter +* Defect #12349: Watchers auto-complete search on non-latin chars +* Defect #12358: 'None' grouped issue list section should be translated +* Defect #12359: Version date field regex validation accepts invalid date +* Defect #12375: Receiving mail subject encoding broken (MOJIBAKE) in some cases on Ruby 1.8 +* Patch #9732: German translations +* Patch #12021: Russian locale translations +* Patch #12188: Simplified Chinese translation with zh.yml file based on Rev:10681 +* Patch #12235: German translation for 2.1-stable +* Patch #12237: Added German Translation + +== 2012-09-30 v2.1.2 + +* Defect #11929: XSS vulnerability in Redmine 2.1.x + +== 2012-09-30 v2.1.1 + +* Defect #11290: ParseDate missing in Ruby 1.9x +* Defect #11844: "load_default_data" rake task fails to print the error message if one occurs +* Defect #11850: Can't create a user from ldap by on-the-fly on the redmine server using URI prefix +* Defect #11872: Private issue visible to anonymous users after its author is deleted +* Defect #11885: Filter misses Selectionfield on IE8 +* Defect #11893: New relation form Cancel link is broken with Chrome 21 +* Defect #11905: Potential "can't dup NilClass" error in UserPreference +* Defect #11909: Autocomplete results not reset after clearing search field +* Defect #11922: bs.yml and de.yml lead to error by number_to_currency() +* Defect #11945: rake task prints "can't convert Errno::EACCES into String" in case of no permission of public/plugin_assets +* Defect #11975: Undefined status transitions allowed in workflow (author of issue changes when selecting a new status) +* Defect #11982: SCM diff view generates extra parameter for switching mode +* Patch #11897: Traditional Chinese language file (to r10433) + +== 2012-09-16 v2.1.0 + +* Defect #2071: Reordering priority-enumerations breaks alternate-theme's issue-colouring +* Defect #2190: Month names not translated to german +* Defect #8978: LDAP timeout if an LDAP auth provider is unreachable +* Defect #9839: Gantt abbr of weekday should not be necessarily the first letter of the long day name +* Defect #10928: Documentation about generating a plugin is not up-to-date +* Defect #11034: TLS configuration documentation for Rails 3 +* Defect #11073: UserCustomField order_statement returns wrong output +* Defect #11153: Default sorting for target version is DESC instead of ASC +* Defect #11207: Issues associated with a locked version are not copied when copying a project +* Defect #11304: Issue-class: status-1, status-2 etc. refer to status position instead of status id +* Defect #11331: Openid registration form should not require user to enter password +* Defect #11345: Context menu should show shared versions when editing issues from different projects +* Defect #11355: Plain text notification emails content is HTML escaped +* Defect #11388: Updating a version through rest API returns invalid JSON +* Defect #11389: Warning in awesome_nested_set.rb +* Defect #11503: Accessing /projects/:project/wiki/something.png fails with error 500 +* Defect #11506: Versions that are not shared should not be assignable when selecting another project +* Defect #11508: Projects not ordered alphabetically after renaming project +* Defect #11540: Roadmap anchor links can be ambigous +* Defect #11545: Overwriting existing method Issue.open +* Defect #11552: MailHandler does not match assignee name with spaces +* Defect #11571: Custom fields of type version not proper handled in receiving e-mails +* Defect #11577: Can't use non-latin anchor in wiki +* Defect #11612: Revision graph sometimes broken due to raphael.js error +* Defect #11621: Redmine MIME Detection Of Javascript Files Non-Standard +* Defect #11633: Macro arguments should not be parsed by text formatters +* Defect #11662: Invalid query returned from Issues.visible scope after accessing User#projects_by_role with a role that is not present +* Defect #11691: 404 response when deleting a user from the edit page +* Defect #11723: redmine:send_reminders notification misses if assignee is a group +* Defect #11738: Batch update of issues clears project path +* Defect #11749: Redmine.pm: HEAD is not considered as a read-only method +* Defect #11814: Date picker does not respect week start setting +* Feature #703: Configurable required fields per tracker/status/role +* Feature #1006: Display thumbnails of attached images +* Feature #1091: Disabling default ticket fields per tracker +* Feature #1360: Permission for adding an issue to a version. +* Feature #3061: Let macros optionally match over multiple lines and ignore single curly braces +* Feature #3510: Inserting image thumbnails inside the wiki +* Feature #3521: Permissions for roles to change fields per tracker/status +* Feature #3640: Freeze / Close Projects +* Feature #3831: Support for subforums +* Feature #6597: Configurable session lifetime and timeout +* Feature #6965: Option to Copy Subtasks when copying an issue +* Feature #8161: Ability to filter issues on project custom fields +* Feature #8577: "Private" column and filter on the issue list +* Feature #8981: REST Api for Groups +* Feature #9258: Create role by copy +* Feature #9419: Group/sort the issue list by user/version-format custom fields +* Feature #10362: Show images in repositories inline when clicking the 'View' link +* Feature #10419: Upgrade raphael.js (2.1.0) +* Feature #11068: Ability to set default column order in issue list +* Feature #11102: Add autocomplete to "Related issue" field on revision +* Feature #11109: Repository Identifier should be frozen +* Feature #11181: Additional "Log time" link on project overview +* Feature #11205: Reversed order of priorities on the issue summary page +* Feature #11445: Switch from Prototype to JQuery +* Feature #11469: JSONP support +* Feature #11475: Redmine.pm: Allow fallback to other Apache auth providers +* Feature #11494: Don't turn #nnn with leading zeros into links +* Feature #11539: Display a projects tree instead of a flat list in notification preferences +* Feature #11578: Option to pass whole arguments to a macro without splitting them +* Feature #11595: Missing mime type for svg files +* Feature #11758: Upgrade to Rails 3.2.8 +* Patch #4905: Redmine.pm: add support for Git's smart HTTP protocol +* Patch #10988: New Korean translation patch +* Patch #11201: Korean translation special update +* Patch #11401: Fix Japanese mistranslation for "button_submit" +* Patch #11402: Japanese translation added for default role names +* Patch #11411: Fix disordered use of long sound in Japanese "user" translation +* Patch #11412: Unnatural Japanese message when users failed to login +* Patch #11419: Fix wrong Japanese "label_attachment" translation +* Patch #11496: Make labels clickable in Adminstration/Settings +* Patch #11704: Avoid the use of tag("...", "...", true) in layout +* Patch #11818: Redmine.pm fails when permissions are NULL + +== 2012-09-16 v2.0.4 + +* Defect #10818: Running rake in test environment causes exception +* Defect #11209: Wiki diff may generate broken HTML +* Defect #11217: Project names in drop-down are escaped twice +* Defect #11262: Link is escaped in wiki added/updated notification email +* Defect #11307: Can't filter for negative numeric custom fields +* Defect #11325: Unified diff link broken on specific file/revision diff view +* Defect #11341: Escaped link in conflict resolution form +* Defect #11365: Attachment description length is not validated +* Defect #11511: Confirmation page has broken HTML when a project folding sub project is deleted +* Defect #11533: rake redmine:plugins:test doesn't run tests in subdirectories +* Defect #11541: Version sharing is missing in the REST API +* Defect #11550: Issue reminder doesn't work when using asynchronous delivery +* Defect #11776: Can't override mailer views inside redmine plugin. +* Defect #11789: Edit section links broken with h5/h6 headings +* Feature #11338: Exclude emails with auto-submitted => auto-generated +* Patch #11299: redmine:plugins:migrate should update db/schema.rb +* Patch #11328: Fix Japanese mistranslation for 'label_language_based' +* Patch #11448: Russian translation for 1.4-stable and 2.0-stable +* Patch #11600: Fix plural form of the abbreviation for hours in Brazilian Portuguese + +== 2012-06-18 v2.0.3 + +* Defect #10688: PDF export from Wiki - Problems with tables +* Defect #11061: Cannot choose commit versions to view differences in Git/Mercurial repository view +* Defect #11065: E-Mail submitted tickets: German umlauts in 'Subject' get malformed (ruby 1.8) +* Defect #11098: Default priorities have the same position and can't be reordered +* Defect #11105: <% content_for :header_tags do %> doesn't work inside hook +* Defect #11112: REST API - custom fields in POST/PUT ignored for time_entries +* Defect #11118: "Maximum file size" displayed on upload forms is incorrect +* Defect #11124: Link to user is escaped in activity title +* Defect #11133: Wiki-page section edit link can point to incorrect section +* Defect #11160: SQL Error on time report if a custom field has multiple values for an entry +* Defect #11170: Topics sort order is broken in Redmine 2.x +* Defect #11178: Spent time sorted by date-descending order lists same-date entries in physical order (not-reverse) +* Defect #11185: Redmine fails to delete a project with parent/child task +* Feature #11162: Upgrade to Rails 3.2.6 +* Patch #11113: Small glitch in German localization + +== 2012-06-05 v2.0.2 + +* Defect #11032: Project list is not shown when "For any event on the selected projects only..." is selected on user edit panel +* Defect #11038: "Create and continue" should preserve project, issue and activity when logging time +* Defect #11046: Redmine.pm does not support "bind as user" ldap authentication +* Defect #11051: reposman.rb fails in 1.4.2 because of missing require for rubygems +* Defect #11085: Wiki start page can't be changed +* Feature #11084: Update Rails to 3.2.5 + +== 2012-05-28 v2.0.1 + +* Defect #10923: After creating a new Version Redmine jumps back to "Information" +* Defect #10932: Links to delete watchers are escaped when gravatars are enabled +* Defect #10964: Updated column doesn't get updated on issues +* Defect #10965: rake yard does not work for generating documentation. +* Defect #10972: Columns selection not displayed on the custom query form +* Defect #10991: My page > Spent time 'project' column is html-encoded +* Defect #10996: Time zones lost when upgrading from Redmine 1.4 to 2.0 +* Defect #11013: Fetching Email from IMAP/POP3 - uninitialized constant RAILS_DEFAULT_LOGGER error +* Defect #11024: redmine_plugin_model generator does not create the migration +* Defect #11027: Saving new query without name causes escaping of input field +* Defect #11028: Project identifier can be updated + +== 2012-05-15 v2.0.0 + +* Feature #4796: Rails 3 support +* Feature #7720: Limit the pagination-limit when max-results is fewer than max-pagination-limit +* Feature #9034: Add an id to the flash messages +* Patch #10782: Better translation for Estonian language + +== 2012-05-13 v1.4.2 + +* Defect #10744: rake task redmine:email:test broken +* Defect #10787: "Allow users to unsubscribe" option is confusing +* Defect #10827: Cannot access Repositories page and Settings in a Project - Error 500 +* Defect #10829: db:migrate fails 0.8.2 -> 1.4.1 +* Defect #10832: REST Uploads fail with fastcgi +* Defect #10837: reposman and rdm-mailhandler not working with ruby 1.9.x +* Defect #10856: can not load translations from hr.yml with ruby1.9.3-p194 +* Defect #10865: Filter reset when deleting locked user +* Feature #9790: Allow filtering text custom fields on "is null" and "is not null" +* Feature #10778: svn:ignore for config/additional_environment.rb +* Feature #10875: Partial Albanian Translations +* Feature #10888: Bring back List-Id to help aid Gmail filtering +* Patch #10733: Traditional Chinese language file (to r9502) +* Patch #10745: Japanese translation update (r9519) +* Patch #10750: Swedish Translation for r9522 +* Patch #10785: Bulgarian translation (jstoolbar) +* Patch #10800: Simplified Chinese translation + +== 2012-04-20 v1.4.1 + +* Defect #8574: Time report: date range fields not enabled when using the calendar popup +* Defect #10642: Nested textile ol/ul lists generate invalid HTML +* Defect #10668: RSS key is generated twice when user is not reloaded +* Defect #10669: Token.destroy_expired should not delete API tokens +* Defect #10675: "Submit and continue" is broken +* Defect #10711: User cannot change account details with "Login has already been taken" error +* Feature #10664: Unsubscribe Own User Account +* Patch #10693: German Translation Update + +== 2012-04-14 v1.4.0 + +* Defect #2719: Increase username length limit from 30 to 60 +* Defect #3087: Revision referring to issues across all projects +* Defect #4824: Unable to connect (can't convert Net::LDAP::LdapError into String) +* Defect #5058: reminder mails are not sent when delivery_method is :async_smtp +* Defect #6859: Moving issues to a tracker with different custom fields should let fill these fields +* Defect #7398: Error when trying to quick create a version with required custom field +* Defect #7495: Python multiline comments highlighting problem in Repository browser +* Defect #7826: bigdecimal-segfault-fix.rb must be removed for Oracle +* Defect #7920: Attempted to update a stale object when copying a project +* Defect #8857: Git: Too long in fetching repositories after upgrade from 1.1 or new branch at first time +* Defect #9472: The git scm module causes an excess amount of DB traffic. +* Defect #9685: Adding multiple times the same related issue relation is possible +* Defect #9798: Release 1.3.0 does not detect rubytree under ruby 1.9.3p0 / rails 2.3.14 +* Defect #9978: Japanese "permission_add_issue_watchers" is wrong +* Defect #10006: Email reminders are sent for closed issues +* Defect #10150: CSV export and spent time: rounding issue +* Defect #10168: CSV export breaks custom columns +* Defect #10181: Issue context menu and bulk edit form show irrelevant statuses +* Defect #10198: message_id regex in pop3.rb only recognizes Message-ID header (not Message-Id) +* Defect #10251: Description diff link in note details is relative when received by email +* Defect #10272: Ruby 1.9.3: "incompatible character encoding" with LDAP auth +* Defect #10275: Message object not passed to wiki macros for head topic and in preview edit mode +* Defect #10334: Full name is not unquoted when creating users from emails +* Defect #10410: [Localization] Grammar issue of Simplified Chinese in zh.yml +* Defect #10442: Ruby 1.9.3 Time Zone setting Internal error. +* Defect #10467: Confusing behavior while moving issue to a project with disabled Issues module +* Defect #10575: Uploading of attachments which filename contains non-ASCII chars fails with Ruby 1.9 +* Defect #10590: WikiContent::Version#text return string with # when uncompressed +* Defect #10593: Error: 'incompatible character encodings: UTF-8 and ASCII-8BIT' (old annoing issue) on ruby-1.9.3 +* Defect #10600: Watchers search generates an Internal error +* Defect #10605: Bulk edit selected issues does not allow selection of blank values for custom fields +* Defect #10619: When changing status before tracker, it shows improper status +* Feature #779: Multiple SCM per project +* Feature #971: Add "Spent time" column to query +* Feature #1060: Add a LDAP-filter using external auth sources +* Feature #1102: Shortcut for assigning an issue to me +* Feature #1189: Multiselect custom fields +* Feature #1363: Allow underscores in project identifiers +* Feature #1913: LDAP - authenticate as user +* Feature #1972: Attachments for News +* Feature #2009: Manually add related revisions +* Feature #2323: Workflow permissions for administrators +* Feature #2416: {background:color} doesn't work in text formatting +* Feature #2694: Notification on loosing assignment +* Feature #2715: "Magic links" to notes +* Feature #2850: Add next/previous navigation to issue +* Feature #3055: Option to copy attachments when copying an issue +* Feature #3108: set parent automatically for new pages +* Feature #3463: Export all wiki pages to PDF +* Feature #4050: Ruby 1.9 support +* Feature #4769: Ability to move an issue to a different project from the update form +* Feature #4774: Change the hyperlink for file attachment to view and download +* Feature #5159: Ability to add Non-Member watchers to the watch list +* Feature #5638: Use Bundler (Gemfile) for gem management +* Feature #5643: Add X-Redmine-Sender header to email notifications +* Feature #6296: Bulk-edit custom fields through context menu +* Feature #6386: Issue mail should render the HTML version of the issue details +* Feature #6449: Edit a wiki page's parent on the edit page +* Feature #6555: Double-click on "Submit" and "Save" buttons should not send two requests to server +* Feature #7361: Highlight active query in the side bar +* Feature #7420: Rest API for projects members +* Feature #7603: Please make editing issues more obvious than "Change properties (More)" +* Feature #8171: Adding attachments through the REST API +* Feature #8691: Better handling of issue update conflict +* Feature #9803: Change project through REST API issue update +* Feature #9923: User type custom fields should be filterable by "Me". +* Feature #9985: Group time report by the Status field +* Feature #9995: Time entries insertion, "Create and continue" button +* Feature #10020: Enable global time logging at /time_entries/new +* Feature #10042: Bulk change private flag +* Feature #10126: Add members of subprojects in the assignee and author filters +* Feature #10131: Include custom fiels in time entries API responses +* Feature #10207: Git: use default branch from HEAD +* Feature #10208: Estonian translation +* Feature #10253: Better handling of attachments when validation fails +* Feature #10350: Bulk copy should allow for changing the target version +* Feature #10607: Ignore out-of-office incoming emails +* Feature #10635: Adding time like "123 Min" is invalid +* Patch #9998: Make attachement "Optional Description" less wide +* Patch #10066: i18n not working with russian gem +* Patch #10128: Disable IE 8 compatibility mode to fix wrong div.autoscroll scroll bar behaviour +* Patch #10155: Russian translation changed +* Patch #10464: Enhanced PDF output for Issues list +* Patch #10470: Efficiently process new git revisions in a single batch +* Patch #10513: Dutch translation improvement + +== 2012-04-14 v1.3.3 + +* Defect #10505: Error when exporting to PDF with NoMethodError (undefined method `downcase' for nil:NilClass) +* Defect #10554: Defect symbols when exporting tasks in pdf +* Defect #10564: Unable to change locked, sticky flags and board when editing a message +* Defect #10591: Dutch "label_file_added" translation is wrong +* Defect #10622: "Default administrator account changed" is always true +* Patch #10555: rake redmine:send_reminders aborted if issue assigned to group +* Patch #10611: Simplified Chinese translations for 1.3-stable + +== 2012-03-11 v1.3.2 + +* Defect #8194: {{toc}} uses identical anchors for subsections with the same name +* Defect #9143: Partial diff comparison should be done on actual code, not on html +* Defect #9523: {{toc}} does not display headers with @ code markup +* Defect #9815: Release 1.3.0 does not detect rubytree with rubgems 1.8 +* Defect #10053: undefined method `<=>' for nil:NilClass when accessing the settings of a project +* Defect #10135: ActionView::TemplateError (can't convert Fixnum into String) +* Defect #10193: Unappropriate icons in highlighted code block +* Defect #10199: No wiki section edit when title contains code +* Defect #10218: Error when creating a project with a version custom field +* Defect #10241: "get version by ID" fails with "401 not authorized" error when using API access key +* Defect #10284: Note added by commit from a subproject does not contain project identifier +* Defect #10374: User list is empty when adding users to project / group if remaining users are added late +* Defect #10390: Mass assignment security vulnerability +* Patch #8413: Confirmation message before deleting a relationship +* Patch #10160: Bulgarian translation (r8777) +* Patch #10242: Migrate Redmine.pm from Digest::Sha1 to Digest::Sha +* Patch #10258: Italian translation for 1.3-stable + +== 2012-02-06 v1.3.1 + +* Defect #9775: app/views/repository/_revision_graph.html.erb sets window.onload directly.. +* Defect #9792: Ruby 1.9: [v1.3.0] Error: incompatible character encodings for it translation on Calendar page +* Defect #9793: Bad spacing between numbered list and heading (recently broken). +* Defect #9795: Unrelated error message when creating a group with an invalid name +* Defect #9832: Revision graph height should depend on height of rows in revisions table +* Defect #9937: Repository settings are not saved when all SCM are disabled +* Defect #9961: Ukrainian "default_tracker_bug" is wrong +* Defect #10013: Rest API - Create Version -> Internal server error 500 +* Defect #10115: Javascript error - Can't attach more than 1 file on IE 6 and 7 +* Defect #10130: Broken italic text style in edited comment preview +* Defect #10152: Attachment diff type is not saved in user preference +* Feature #9943: Arabic translation +* Patch #9874: pt-BR translation updates +* Patch #9922: Spanish translation updated +* Patch #10137: Korean language file ko.yml updated to Redmine 1.3.0 + +== 2011-12-10 v1.3.0 + +* Defect #2109: Context menu is being submitted twice per right click +* Defect #7717: MailHandler user creation for unknown_user impossible due to diverging length-limits of login and email fields +* Defect #7917: Creating users via email fails if user real name containes special chars +* Defect #7966: MailHandler does not include JournalDetail for attached files +* Defect #8368: Bad decimal separator in time entry CSV +* Defect #8371: MySQL error when filtering a custom field using the REST api +* Defect #8549: Export CSV has character encoding error +* Defect #8573: Do not show inactive Enumerations where not needed +* Defect #8611: rake/rdoctask is deprecated +* Defect #8751: Email notification: bug, when number of recipients more then 8 +* Defect #8894: Private issues - make it more obvious in the UI? +* Defect #8994: Hardcoded French string "anonyme" +* Defect #9043: Hardcoded string "diff" in Wiki#show and Repositories_Helper +* Defect #9051: wrong "text_issue_added" in russian translation. +* Defect #9108: Custom query not saving status filter +* Defect #9252: Regression: application title escaped 2 times +* Defect #9264: Bad Portuguese translation +* Defect #9470: News list is missing Avatars +* Defect #9471: Inline markup broken in Wiki link labels +* Defect #9489: Label all input field and control tags +* Defect #9534: Precedence: bulk email header is non standard and discouraged +* Defect #9540: Issue filter by assigned_to_role is not project specific +* Defect #9619: Time zone ignored when logging time while editing ticket +* Defect #9638: Inconsistent image filename extensions +* Defect #9669: Issue list doesn't sort assignees/authors regarding user display format +* Defect #9672: Message-quoting in forums module broken +* Defect #9719: Filtering by numeric custom field types broken after update to master +* Defect #9724: Can't remote add new categories +* Defect #9738: Setting of cross-project custom query is not remembered inside project +* Defect #9748: Error about configuration.yml validness should mention file path +* Feature #69: Textilized description in PDF +* Feature #401: Add pdf export for WIKI page +* Feature #1567: Make author column sortable and groupable +* Feature #2222: Single section edit. +* Feature #2269: Default issue start date should become configurable. +* Feature #2371: character encoding for attachment file +* Feature #2964: Ability to assign issues to groups +* Feature #3033: Bug Reporting: Using "Create and continue" should show bug id of saved bug +* Feature #3261: support attachment images in PDF export +* Feature #4264: Update CodeRay to 1.0 final +* Feature #4324: Redmine renames my files, it shouldn't. +* Feature #4729: Add Date-Based Filters for Issues List +* Feature #4742: CSV export: option to export selected or all columns +* Feature #4976: Allow rdm-mailhandler to read the API key from a file +* Feature #5501: Git: Mercurial: Adding visual merge/branch history to repository view +* Feature #5634: Export issue to PDF does not include Subtasks and Related Issues +* Feature #5670: Cancel option for file upload +* Feature #5737: Custom Queries available through the REST Api +* Feature #6180: Searchable custom fields do not provide adequate operators +* Feature #6954: Filter from date to date +* Feature #7180: List of statuses in REST API +* Feature #7181: List of trackers in REST API +* Feature #7366: REST API for Issue Relations +* Feature #7403: REST API for Versions +* Feature #7671: REST API for reading attachments +* Feature #7832: Ability to assign issue categories to groups +* Feature #8420: Consider removing #7013 workaround +* Feature #9196: Improve logging in MailHandler when user creation fails +* Feature #9496: Adds an option in mailhandler to disable server certificate verification +* Feature #9553: CRUD operations for "Issue categories" in REST API +* Feature #9593: HTML title should be reordered +* Feature #9600: Wiki links for news and forums +* Feature #9607: Filter for issues without start date (or any another field based on date type) +* Feature #9609: Upgrade to Rails 2.3.14 +* Feature #9612: "side by side" and "inline" patch view for attachments +* Feature #9667: Check attachment size before upload +* Feature #9690: Link in notification pointing to the actual update +* Feature #9720: Add note number for single issue's PDF +* Patch #8617: Indent subject of subtask ticket in exported issues PDF +* Patch #8778: Traditional Chinese 'issue' translation change +* Patch #9053: Fix up Russian translation +* Patch #9129: Improve wording of Git repository note at project setting +* Patch #9148: Better handling of field_due_date italian translation +* Patch #9273: Fix typos in russian localization +* Patch #9484: Limit SCM annotate to text files under the maximum file size for viewing +* Patch #9659: Indexing rows in auth_sources/index view +* Patch #9692: Fix Textilized description in PDF for CodeRay + +== 2011-12-10 v1.2.3 + +* Defect #8707: Reposman: wrong constant name +* Defect #8809: Table in timelog report overflows +* Defect #9055: Version files in Files module cannot be downloaded if issue tracking is disabled +* Defect #9137: db:encrypt fails to handle repositories with blank password +* Defect #9394: Custom date field only validating on regex and not a valid date +* Defect #9405: Any user with :log_time permission can edit time entries via context menu +* Defect #9448: The attached images are not shown in documents +* Defect #9520: Copied private query not visible after project copy +* Defect #9552: Error when reading ciphered text from the database without cipher key configured +* Defect #9566: Redmine.pm considers all projects private when login_required is enabled +* Defect #9567: Redmine.pm potential security issue with cache credential enabled and subversion +* Defect #9577: Deleting a subtasks doesn't update parent's rgt & lft values +* Defect #9597: Broken version links in wiki annotate history +* Defect #9682: Wiki HTML Export only useful when Access history is accessible +* Defect #9737: Custom values deleted before issue submit +* Defect #9741: calendar-hr.js (Croatian) is not UTF-8 +* Patch #9558: Simplified Chinese translation for 1.2.2 updated +* Patch #9695: Bulgarian translation (r7942) + +== 2011-11-11 v1.2.2 + +* Defect #3276: Incorrect handling of anchors in Wiki to HTML export +* Defect #7215: Wiki formatting mangles links to internal headers +* Defect #7613: Generated test instances may share the same attribute value object +* Defect #8411: Can't remove "Project" column on custom query +* Defect #8615: Custom 'version' fields don't show shared versions +* Defect #8633: Pagination counts non visible issues +* Defect #8651: Email attachments are not added to issues any more in v1.2 +* Defect #8825: JRuby + Windows: SCMs do not work on Redmine 1.2 +* Defect #8836: Additional workflow transitions not available when set to both author and assignee +* Defect #8865: Custom field regular expression is not validated +* Defect #8880: Error deleting issue with grandchild +* Defect #8884: Assignee is cleared when updating issue with locked assignee +* Defect #8892: Unused fonts in rfpdf plugin folder +* Defect #9161: pt-BR field_warn_on_leaving_unsaved has a small gramatical error +* Defect #9308: Search fails when a role haven't "view wiki" permission +* Defect #9465: Mercurial: can't browse named branch below Mercurial 1.5 + +== 2011-07-11 v1.2.1 + +* Defect #5089: i18N error on truncated revision diff view +* Defect #7501: Search options get lost after clicking on a specific result type +* Defect #8229: "project.xml" response does not include the parent ID +* Defect #8449: Wiki annotated page does not display author of version 1 +* Defect #8467: Missing german translation - Warn me when leaving a page with unsaved text +* Defect #8468: No warning when leaving page with unsaved text that has not lost focus +* Defect #8472: Private checkbox ignored on issue creation with "Set own issues public or private" permission +* Defect #8510: JRuby: Can't open administrator panel if scm command is not available +* Defect #8512: Syntax highlighter on Welcome page +* Defect #8554: Translation missing error on custom field validation +* Defect #8565: JRuby: Japanese PDF export error +* Defect #8566: Exported PDF UTF-8 Vietnamese not correct +* Defect #8569: JRuby: PDF export error with TypeError +* Defect #8576: Missing german translation - different things +* Defect #8616: Circular relations +* Defect #8646: Russian translation "label_follows" and "label_follows" are wrong +* Defect #8712: False 'Description updated' journal details messages +* Defect #8729: Not-public queries are not private +* Defect #8737: Broken line of long issue description on issue PDF. +* Defect #8738: Missing revision number/id of associated revisions on issue PDF +* Defect #8739: Workflow copy does not copy advanced workflow settings +* Defect #8759: Setting issue attributes from mail should be case-insensitive +* Defect #8777: Mercurial: Not able to Resetting Redmine project respository + +== 2011-05-30 v1.2.0 + +* Defect #61: Broken character encoding in pdf export +* Defect #1965: Redmine is not Tab Safe +* Defect #2274: Filesystem Repository path encoding of non UTF-8 characters +* Defect #2664: Mercurial: Repository path encoding of non UTF-8 characters +* Defect #3421: Mercurial reads files from working dir instead of changesets +* Defect #3462: CVS: Repository path encoding of non UTF-8 characters +* Defect #3715: Login page should not show projects link and search box if authentication is required +* Defect #3724: Mercurial repositories display revision ID instead of changeset ID +* Defect #3761: Most recent CVS revisions are missing in "revisions" view +* Defect #4270: CVS Repository view in Project doesn't show Author, Revision, Comment +* Defect #5138: Don't use Ajax for pagination +* Defect #5152: Cannot use certain characters for user and role names. +* Defect #5251: Git: Repository path encoding of non UTF-8 characters +* Defect #5373: Translation missing when adding invalid watchers +* Defect #5817: Shared versions not shown in subproject's gantt chart +* Defect #6013: git tab,browsing, very slow -- even after first time +* Defect #6148: Quoting, newlines, and nightmares... +* Defect #6256: Redmine considers non ASCII and UTF-16 text files as binary in SCM +* Defect #6476: Subproject's issues are not shown in the subproject's gantt +* Defect #6496: Remove i18n 0.3.x/0.4.x hack for Rails 2.3.5 +* Defect #6562: Context-menu deletion of issues deletes all subtasks too without explicit prompt +* Defect #6604: Issues targeted at parent project versions' are not shown on gantt chart +* Defect #6706: Resolving issues with the commit message produces the wrong comment with CVS +* Defect #6901: Copy/Move an issue does not give any history of who actually did the action. +* Defect #6905: Specific heading-content breaks CSS +* Defect #7000: Project filter not applied on versions in Gantt chart +* Defect #7097: Starting day of week cannot be set to Saturday +* Defect #7114: New gantt doesn't display some projects +* Defect #7146: Git adapter lost commits before 7 days from database latest changeset +* Defect #7218: Date range error on issue query +* Defect #7257: "Issues by" version links bad criterias +* Defect #7279: CSS class ".icon-home" is not used. +* Defect #7320: circular dependency >2 issues +* Defect #7352: Filters not working in Gantt charts +* Defect #7367: Receiving pop3 email should not output debug messages +* Defect #7373: Error with PDF output and ruby 1.9.2 +* Defect #7379: Remove extraneous hidden_field on wiki history +* Defect #7516: Redmine does not work with RubyGems 1.5.0 +* Defect #7518: Mercurial diff can be wrong if the previous changeset isn't the parent +* Defect #7581: Not including a spent time value on the main issue update screen causes silent data loss +* Defect #7582: hiding form pages from search engines +* Defect #7597: Subversion and Mercurial log have the possibility to miss encoding +* Defect #7604: ActionView::TemplateError (undefined method `name' for nil:NilClass) +* Defect #7605: Using custom queries always redirects to "Issues" tab +* Defect #7615: CVS diffs do not handle new files properly +* Defect #7618: SCM diffs do not handle one line new files properly +* Defect #7639: Some date fields do not have requested format. +* Defect #7657: Wrong commit range in git log command on Windows +* Defect #7818: Wiki pages don't use the local timezone to display the "Updated ? hours ago" mouseover +* Defect #7821: Git "previous" and "next" revisions are incorrect +* Defect #7827: CVS: Age column on repository view is off by timezone delta +* Defect #7843: Add a relation between issues = explicit login window ! (basic authentication popup is prompted on AJAX request) +* Defect #8011: {{toc}} does not display headlines with inline code markup +* Defect #8029: List of users for adding to a group may be empty if 100 first users have been added +* Defect #8064: Text custom fields do not wrap on the issue list +* Defect #8071: Watching a subtask from the context menu updates main issue watch link +* Defect #8072: Two untranslatable default role names +* Defect #8075: Some "notifiable" names are not i18n-enabled +* Defect #8081: GIT: Commits missing when user has the "decorate" git option enabled +* Defect #8088: Colorful indentation of subprojects must be on right in RTL locales +* Defect #8239: notes field is not propagated during issue copy +* Defect #8356: GET /time_entries.xml ignores limit/offset parameters +* Defect #8432: Private issues information shows up on Activity page for unauthorized users +* Feature #746: Versioned issue descriptions +* Feature #1067: Differentiate public/private saved queries in the sidebar +* Feature #1236: Make destination folder for attachment uploads configurable +* Feature #1735: Per project repository log encoding setting +* Feature #1763: Autologin-cookie should be configurable +* Feature #1981: display mercurial tags +* Feature #2074: Sending email notifications when comments are added in the news section +* Feature #2096: Custom fields referencing system tables (users and versions) +* Feature #2732: Allow additional workflow transitions for author and assignee +* Feature #2910: Warning on leaving edited issue/wiki page without saving +* Feature #3396: Git: use --encoding=UTF-8 in "git log" +* Feature #4273: SCM command availability automatic check in administration panel +* Feature #4477: Use mime types in downloading from repository +* Feature #5518: Graceful fallback for "missing translation" needed +* Feature #5520: Text format buttons and preview link missing when editing comment +* Feature #5831: Parent Task to Issue Bulk Edit +* Feature #6887: Upgrade to Rails 2.3.11 +* Feature #7139: Highlight changes inside diff lines +* Feature #7236: Collapse All for Groups +* Feature #7246: Handle "named branch" for mercurial +* Feature #7296: Ability for admin to delete users +* Feature #7318: Add user agent to Redmine Mailhandler +* Feature #7408: Add an application configuration file +* Feature #7409: Cross project Redmine links +* Feature #7410: Add salt to user passwords +* Feature #7411: Option to cipher LDAP ans SCM passwords stored in the database +* Feature #7412: Add an issue visibility level to each role +* Feature #7414: Private issues +* Feature #7517: Configurable path of executable for scm adapters +* Feature #7640: Add "mystery man" gravatar to options +* Feature #7858: RubyGems 1.6 support +* Feature #7893: Group filter on the users list +* Feature #7899: Box for editing comments should open with the formatting toolbar +* Feature #7921: issues by pulldown should have 'status' option +* Feature #7996: Bulk edit and context menu for time entries +* Feature #8006: Right click context menu for Related Issues +* Feature #8209: I18n YAML files not parsable with psych yaml library +* Feature #8345: Link to user profile from account page +* Feature #8365: Git: per project setting to report last commit or not in repository tree +* Patch #5148: metaKey not handled in issues selection +* Patch #5629: Wrap text fields properly in PDF +* Patch #7418: Redmine Persian Translation +* Patch #8295: Wrap title fields properly in PDF +* Patch #8310: fixes automatic line break problem with TCPDF +* Patch #8312: Switch to TCPDF from FPDF for PDF export + +== 2011-04-29 v1.1.3 + +* Defect #5773: Email reminders are sent to locked users +* Defect #6590: Wrong file list link in email notification on new file upload +* Defect #7589: Wiki page with backslash in title can not be found +* Defect #7785: Mailhandler keywords are not removed when updating issues +* Defect #7794: Internal server error on formatting an issue as a PDF in Japanese +* Defect #7838: Gantt- Issues does not show up in green when start and end date are the same +* Defect #7846: Headers (h1, etc.) containing backslash followed by a digit are not displayed correctly +* Defect #7875: CSV export separators in polish locale (pl.yml) +* Defect #7890: Internal server error when referencing an issue without project in commit message +* Defect #7904: Subprojects not properly deleted when deleting a parent project +* Defect #7939: Simultaneous Wiki Updates Cause Internal Error +* Defect #7951: Atom links broken on wiki index +* Defect #7954: IE 9 can not select issues, does not display context menu +* Defect #7985: Trying to do a bulk edit results in "Internal Error" +* Defect #8003: Error raised by reposman.rb under Windows server 2003 +* Defect #8012: Wrong selection of modules when adding new project after validation error +* Defect #8038: Associated Revisions OL/LI items are not styled properly in issue view +* Defect #8067: CSV exporting in Italian locale +* Defect #8235: bulk edit issues and copy issues error in es, gl and ca locales +* Defect #8244: selected modules are not activated when copying a project +* Patch #7278: Update Simplified Chinese translation to 1.1 +* Patch #7390: Fixes in Czech localization +* Patch #7963: Reminder email: Link for show all issues does not sort + +== 2011-03-07 v1.1.2 + +* Defect #3132: Bulk editing menu non-functional in Opera browser +* Defect #6090: Most binary files become corrupted when downloading from CVS repository browser when Redmine is running on a Windows server +* Defect #7280: Issues subjects wrap in Gantt +* Defect #7288: Non ASCII filename downloaded from repo is broken on Internet Explorer. +* Defect #7317: Gantt tab gives internal error due to nil avatar icon +* Defect #7497: Aptana Studio .project file added to version 1.1.1-stable +* Defect #7611: Workflow summary shows X icon for workflow with exactly 1 status transition +* Defect #7625: Syntax highlighting unavailable from board new topic or topic edit preview +* Defect #7630: Spent time in commits not recognized +* Defect #7656: MySQL SQL Syntax Error when filtering issues by Assignee's Group +* Defect #7718: Minutes logged in commit message are converted to hours +* Defect #7763: Email notification are sent to watchers even if 'No events' setting is chosen +* Feature #7608: Add "retro" gravatars +* Patch #7598: Extensible MailHandler +* Patch #7795: Internal server error at journals#index with custom fields + +== 2011-01-30 v1.1.1 + +* Defect #4899: Redmine fails to list files for darcs repository +* Defect #7245: Wiki fails to find pages with cyrillic characters using postgresql +* Defect #7256: redmine/public/.htaccess must be moved for non-fastcgi installs/upgrades +* Defect #7258: Automatic spent time logging does not work properly with SQLite3 +* Defect #7259: Released 1.1.0 uses "devel" label inside admin information +* Defect #7265: "Loading..." icon does not disappear after add project member +* Defect #7266: Test test_due_date_distance_in_words fail due to undefined locale +* Defect #7274: CSV value separator in dutch locale +* Defect #7277: Enabling gravatas causes usernames to overlap first name field in user list +* Defect #7294: "Notifiy for only project I select" is not available anymore in 1.1.0 +* Defect #7307: HTTP 500 error on query for empty revision +* Defect #7313: Label not translated in french in Settings/Email Notification tab +* Defect #7329: with long strings may hang server +* Defect #7337: My page french translation +* Defect #7348: French Translation of "Connection" +* Defect #7385: Error when viewing an issue which was related to a deleted subtask +* Defect #7386: NoMethodError on pdf export +* Defect #7415: Darcs adapter recognizes new files as modified files above Darcs 2.4 +* Defect #7421: no email sent with 'Notifiy for any event on the selected projects only' +* Feature #5344: Update to latest CodeRay 0.9.x + +== 2011-01-09 v1.1.0 + +* Defect #2038: Italics in wiki headers show-up wrong in the toc +* Defect #3449: Redmine Takes Too Long On Large Mercurial Repository +* Defect #3567: Sorting for changesets might go wrong on Mercurial repos +* Defect #3707: {{toc}} doesn't work with {{include}} +* Defect #5096: Redmine hangs up while browsing Git repository +* Defect #6000: Safe Attributes prevents plugin extension of Issue model... +* Defect #6064: Modules not assigned to projects created via API +* Defect #6110: MailHandler should allow updating Issue Priority and Custom fields +* Defect #6136: JSON API holds less information than XML API +* Defect #6345: xml used by rest API is invalid +* Defect #6348: Gantt chart PDF rendering errors +* Defect #6403: Updating an issue with custom fields fails +* Defect #6467: "Member of role", "Member of group" filter not work correctly +* Defect #6473: New gantt broken after clearing issue filters +* Defect #6541: Email notifications send to everybody +* Defect #6549: Notification settings not migrated properly +* Defect #6591: Acronyms must have a minimum of three characters +* Defect #6674: Delete time log broken after changes to REST +* Defect #6681: Mercurial, Bazaar and Darcs auto close issue text should be commit id instead of revision number +* Defect #6724: Wiki uploads does not work anymore (SVN 4266) +* Defect #6746: Wiki links are broken on Activity page +* Defect #6747: Wiki diff does not work since r4265 +* Defect #6763: New gantt charts: subject displayed twice on issues +* Defect #6826: Clicking "Add" twice creates duplicate member record +* Defect #6844: Unchecking status filter on the issue list has no effect +* Defect #6895: Wrong Polish translation of "blocks" +* Defect #6943: Migration from boolean to varchar fails on PostgreSQL 8.1 +* Defect #7064: Mercurial adapter does not recognize non alphabetic nor numeric in UTF-8 copied files +* Defect #7128: New gantt chart does not render subtasks under parent task +* Defect #7135: paging mechanism returns the same last page forever +* Defect #7188: Activity page not refreshed when changing language +* Defect #7195: Apply CLI-supplied defaults for incoming mail only to new issues not replies +* Defect #7197: Tracker reset to default when replying to an issue email +* Defect #7213: Copy project does not copy all roles and permissions +* Defect #7225: Project settings: Trackers & Custom fields only relevant if module Issue tracking is active +* Feature #630: Allow non-unique names for projects +* Feature #1738: Add a "Visible" flag to project/user custom fields +* Feature #2803: Support for Javascript in Themes +* Feature #2852: Clean Incoming Email of quoted text "----- Reply above this line ------" +* Feature #2995: Improve error message when trying to access an archived project +* Feature #3170: Autocomplete issue relations on subject +* Feature #3503: Administrator Be Able To Modify Email settings Of Users +* Feature #4155: Automatic spent time logging from commit messages +* Feature #5136: Parent select on Wiki rename page +* Feature #5338: Descendants (subtasks) should be available via REST API +* Feature #5494: Wiki TOC should display heading from level 4 +* Feature #5594: Improve MailHandler's keyword handling +* Feature #5622: Allow version to be set via incoming email +* Feature #5712: Reload themes +* Feature #5869: Issue filters by Group and Role +* Feature #6092: Truncate Git revision labels in Activity page/feed and allow configurable length +* Feature #6112: Accept localized keywords when receiving emails +* Feature #6140: REST issues response with issue count limit and offset +* Feature #6260: REST API for Users +* Feature #6276: Gantt Chart rewrite +* Feature #6446: Remove length limits on project identifier and name +* Feature #6628: Improvements in truncate email +* Feature #6779: Project JSON API +* Feature #6823: REST API for time tracker. +* Feature #7072: REST API for news +* Feature #7111: Expose more detail on journal entries +* Feature #7141: REST API: get information about current user +* Patch #4807: Allow to set the done_ratio field with the incoming mail system +* Patch #5441: Initialize TimeEntry attributes with params[:time_entry] +* Patch #6762: Use GET instead of POST to retrieve context_menu +* Patch #7160: French translation ofr "not_a_date" is missing +* Patch #7212: Missing remove_index in AddUniqueIndexOnMembers down migration + + +== 2010-12-23 v1.0.5 + +* #6656: Mercurial adapter loses seconds of commit times +* #6996: Migration trac(sqlite3) -> redmine(postgresql) doesnt escape ' char +* #7013: v-1.0.4 trunk - see {{count}} in page display rather than value +* #7016: redundant 'field_start_date' in ja.yml +* #7018: 'undefined method `reschedule_after' for nil:NilClass' on new issues +* #7024: E-mail notifications about Wiki changes. +* #7033: 'class' attribute of
     tag shouldn't be truncate
    +* #7035: CSV value separator in russian
    +* #7122: Issue-description Quote-button missing
    +* #7144: custom queries making use of deleted custom fields cause a 500 error
    +* #7162: Multiply defined label in french translation
    +
    +== 2010-11-28 v1.0.4
    +
    +* #5324: Git not working if color.ui is enabled
    +* #6447: Issues API doesn't allow full key auth for all actions
    +* #6457: Edit User group problem
    +* #6575: start date being filled with current date even when blank value is submitted
    +* #6740: Max attachment size, incorrect usage of 'KB'
    +* #6760: Select box sorted by ID instead of name in Issue Category
    +* #6766: Changing target version name can cause an internal error
    +* #6784: Redmine not working with i18n gem 0.4.2
    +* #6839: Hardcoded absolute links in my/page_layout
    +* #6841: Projects API doesn't allow full key auth for all actions
    +* #6860: svn: Write error: Broken pipe when browsing repository
    +* #6874: API should return XML description when creating a project
    +* #6932: submitting wrong parent task input creates a 500 error
    +* #6966: Records of Forums are remained, deleting project
    +* #6990: Layout problem in workflow overview
    +* #5117: mercurial_adapter should ensure the right LANG environment variable
    +* #6782: Traditional Chinese language file (to r4352)
    +* #6783: Swedish Translation for r4352
    +* #6804: Bugfix: spelling fixes
    +* #6814: Japanese Translation for r4362
    +* #6948: Bulgarian translation
    +* #6973: Update es.yml
    +
    +== 2010-10-31 v1.0.3
    +
    +* #4065: Redmine.pm doesn't work with LDAPS and a non-standard port
    +* #4416: Link from version details page to edit the wiki.
    +* #5484: Add new issue as subtask to an existing ticket
    +* #5948: Update help/wiki_syntax_detailed.html with more link options
    +* #6494: Typo in pt_BR translation for 1.0.2
    +* #6508: Japanese translation update
    +* #6509: Localization pt-PT (new strings)
    +* #6511: Rake task to test email
    +* #6525: Traditional Chinese language file (to r4225)
    +* #6536: Patch for swedish translation
    +* #6548: Rake tasks to add/remove i18n strings
    +* #6569: Updated Hebrew translation
    +* #6570: Japanese Translation for r4231
    +* #6596: pt-BR translation updates
    +* #6629: Change field-name of issues start date
    +* #6669: Bulgarian translation
    +* #6731: Macedonian translation fix
    +* #6732: Japanese Translation for r4287
    +* #6735: Add user-agent to reposman
    +* #6736: Traditional Chinese language file (to r4288)
    +* #6739: Swedish Translation for r4288
    +* #6765: Traditional Chinese language file (to r4302)
    +* Fixed #5324: Git not working if color.ui is enabled
    +* Fixed #5652: Bad URL parsing in the wiki when it ends with right-angle-bracket(greater-than mark).
    +* Fixed #5803: Precedes/Follows Relationships Broke
    +* Fixed #6435: Links to wikipages bound to versions do not respect version-sharing in Settings -> Versions
    +* Fixed #6438: Autologin cannot be disabled again once it's enabled
    +* Fixed #6513: "Move" and "Copy" are not displayed when deployed in subdirectory
    +* Fixed #6521: Tooltip/label for user "search-refinment" field on group/project member list
    +* Fixed #6563: i18n-issues on calendar view
    +* Fixed #6598: Wrong caption for button_create_and_continue in German language file
    +* Fixed #6607: Unclear caption for german button_update
    +* Fixed #6612: SortHelper missing from CalendarsController
    +* Fixed #6740: Max attachment size, incorrect usage of 'KB'
    +* Fixed #6750: ActionView::TemplateError (undefined method `empty?' for nil:NilClass) on line #12 of app/views/context_menus/issues.html.erb:
    +
    +== 2010-09-26 v1.0.2
    +
    +* #2285: issue-refinement: pressing enter should result to an "apply"
    +* #3411: Allow mass status update trough context menu
    +* #5929: https-enabled gravatars when called over https
    +* #6189: Japanese Translation for r4011
    +* #6197: Traditional Chinese language file (to r4036)
    +* #6198: Updated german translation
    +* #6208: Macedonian translation
    +* #6210: Swedish Translation for r4039
    +* #6248: nl translation update for r4050
    +* #6263: Catalan translation update
    +* #6275: After submitting a related issue, the Issue field should be re-focused
    +* #6289: Checkboxes in issues list shouldn't be displayed when printing
    +* #6290: Make journals theming easier
    +* #6291: User#allowed_to? is not tested
    +* #6306: Traditional Chinese language file (to r4061)
    +* #6307: Korean translation update for 4066(4061)
    +* #6316: pt_BR update
    +* #6339: SERBIAN Updated
    +* #6358: Updated Polish translation
    +* #6363: Japanese Translation for r4080
    +* #6365: Traditional Chinese language file (to r4081)
    +* #6382: Issue PDF export variable usage
    +* #6428: Interim solution for i18n >= 0.4
    +* #6441: Japanese Translation for r4162
    +* #6451: Traditional Chinese language file (to r4167)
    +* #6465: Japanese Translation for r4171
    +* #6466: Traditional Chinese language file (to r4171)
    +* #6490: pt-BR translation for 1.0.2
    +* Fixed #3935: stylesheet_link_tag with plugin doesn't take into account relative_url_root
    +* Fixed #4998: Global issue list's context menu has enabled options for parent menus but there are no valid selections
    +* Fixed #5170: Done ratio can not revert to 0% if status is used for done ratio
    +* Fixed #5608: broken with i18n 0.4.0
    +* Fixed #6054: Error 500 on filenames with whitespace in git reposities
    +* Fixed #6135: Default logger configuration grows without bound.
    +* Fixed #6191: Deletion of a main task deletes all subtasks
    +* Fixed #6195: Missing move issues between projects
    +* Fixed #6242: can't switch between inline and side-by-side diff
    +* Fixed #6249: Create and continue returns 404
    +* Fixed #6267: changing the authentication mode from ldap to internal with setting the password
    +* Fixed #6270: diff coderay malformed in the "news" page
    +* Fixed #6278: missing "cant_link_an_issue_with_a_descendant"from locale files
    +* Fixed #6333: Create and continue results in a 404 Error
    +* Fixed #6346: Age column on repository view is skewed for git, probably CVS too
    +* Fixed #6351: Context menu on roadmap broken
    +* Fixed #6388: New Subproject leads to a 404
    +* Fixed #6392: Updated/Created links to activity broken
    +* Fixed #6413: Error in SQL
    +* Fixed #6443: Redirect to project settings after Copying a Project
    +* Fixed #6448: Saving a wiki page with no content has a translation missing
    +* Fixed #6452: Unhandled exception on creating File
    +* Fixed #6471: Typo in label_report in Czech translation
    +* Fixed #6479: Changing tracker type will lose watchers
    +* Fixed #6499: Files with leading or trailing whitespace are not shown in git.
    +
    +== 2010-08-22 v1.0.1
    +
    +* #819: Add a body ID and class to all pages
    +* #871: Commit new CSS styles!
    +* #3301: Add favicon to base layout
    +* #4656: On Issue#show page, clicking on “Add related issue� should focus on the input
    +* #4896: Project identifier should be a limited field
    +* #5084: Filter all isssues by projects
    +* #5477: Replace Test::Unit::TestCase with ActiveSupport::TestCase
    +* #5591: 'calendar' action is used with 'issue' controller in issue/sidebar
    +* #5735: Traditional Chinese language file (to r3810)
    +* #5740: Swedish Translation for r3810
    +* #5785: pt-BR translation update
    +* #5898: Projects should be displayed as links in users/memberships
    +* #5910: Chinese translation to redmine-1.0.0
    +* #5912: Translation update for french locale
    +* #5962: Hungarian translation update to r3892
    +* #5971: Remove falsly applied chrome on revision links
    +* #5972: Updated Hebrew translation for 1.0.0
    +* #5982: Updated german translation
    +* #6008: Move admin_menu to Redmine::MenuManager
    +* #6012: RTL layout
    +* #6021: Spanish translation 1.0.0-RC
    +* #6025: nl translation updated for r3905
    +* #6030: Japanese Translation for r3907
    +* #6074: sr-CY.yml contains DOS-type newlines (\r\n)
    +* #6087: SERBIAN translation updated
    +* #6093: Updated italian translation
    +* #6142: Swedish Translation for r3940
    +* #6153: Move view_calendar and view_gantt to own modules
    +* #6169: Add issue status to issue tooltip
    +* Fixed #3834: Add a warning when not choosing a member role
    +* Fixed #3922: Bad english arround "Assigned to" text in journal entries
    +* Fixed #5158: Simplified Chinese language file zh.yml updated to r3608
    +* Fixed #5162: translation missing: zh-TW, field_time_entrie
    +* Fixed #5297: openid not validated correctly
    +* Fixed #5628: Wrong commit range in git log command
    +* Fixed #5760: Assigned_to and author filters in "Projects>View all issues" should be based on user's project visibility
    +* Fixed #5771: Problem when importing git repository
    +* Fixed #5775: ldap authentication in admin menu should have an icon
    +* Fixed #5811: deleting statuses doesnt delete workflow entries
    +* Fixed #5834: Emails with trailing spaces incorrectly detected as invalid
    +* Fixed #5846: ChangeChangesPathLengthLimit does not remove default for MySQL
    +* Fixed #5861: Vertical scrollbar always visible in Wiki "code" blocks in Chrome.
    +* Fixed #5883: correct label_project_latest Chinese translation
    +* Fixed #5892: Changing status from contextual menu opens the ticket instead
    +* Fixed #5904: Global gantt PDF and PNG should display project names
    +* Fixed #5925: parent task's priority edit should be disabled through shortcut menu in issues list page
    +* Fixed #5935: Add Another file to ticket doesn't work in IE Internet Explorer
    +* Fixed #5937: Harmonize french locale "zero" translation with other locales
    +* Fixed #5945: Forum message permalinks don't take pagination into account
    +* Fixed #5978: Debug code still remains
    +* Fixed #6009: When using "English (British)", the repository browser (svn) shows files over 1000 bytes as floating point (2.334355)
    +* Fixed #6045: Repository file Diff view sometimes shows more than selected file
    +* Fixed #6079: German Translation error in TimeEntryActivity
    +* Fixed #6100: User's profile should display all visible projects
    +* Fixed #6132: Allow Key based authentication in the Boards atom feed
    +* Fixed #6163: Bad CSS class for calendar project menu_item
    +* Fixed #6172: Browsing to a missing user's page shows the admin sidebar
    +
    +== 2010-07-18 v1.0.0 (Release candidate)
    +
    +* #443: Adds context menu to the roadmap issue lists
    +* #443: Subtasking
    +* #741: Description preview while editing an issue
    +* #1131: Add support for alternate (non-LDAP) authentication
    +* #1214: REST API for Issues
    +* #1223: File upload on wiki edit form
    +* #1755: add "blocked by" as a related issues option
    +* #2420: Fetching emails from an POP server
    +* #2482: Named scopes in Issue and ActsAsWatchable plus some view refactoring (logic extraction).
    +* #2924: Make the right click menu more discoverable using a cursor property
    +* #2985: Make syntax highlighting pluggable
    +* #3201: Workflow Check/Uncheck All Rows/Columns
    +* #3359: Update CodeRay 0.9
    +* #3706: Allow assigned_to field configuration on Issue creation by email
    +* #3936: configurable list of models to include in search
    +* #4480: Create a link to the user profile from the administration interface
    +* #4482: Cache textile rendering
    +* #4572: Make it harder to ruin your database
    +* #4573: Move github gems to Gemcutter
    +* #4664: Add pagination to forum threads
    +* #4732: Make login case-insensitive also for PostgreSQL
    +* #4812: Create links to other projects
    +* #4819: Replace images with smushed ones for speed
    +* #4945: Allow custom fields attached to project to be searchable
    +* #5121: Fix issues list layout overflow
    +* #5169: Issue list view hook request
    +* #5208: Aibility to edit wiki sidebar
    +* #5281: Remove empty ul tags in the issue history
    +* #5291: Updated basque translations
    +* #5328: Automatically add "Repository" menu_item after repository creation
    +* #5415: Fewer SQL statements generated for watcher_recipients
    +* #5416: Exclude "fields_for" from overridden methods in TabularFormBuilder
    +* #5573: Allow issue assignment in email
    +* #5595: Allow start date and due dates to be set via incoming email
    +* #5752: The projects view (/projects) renders ul's wrong
    +* #5781: Allow to use more macros on the welcome page and project list
    +* Fixed #1288: Unable to past escaped wiki syntax in an issue description
    +* Fixed #1334: Wiki formatting character *_ and _*
    +* Fixed #1416: Inline code with less-then/greater-than produces @lt; and @gt; respectively
    +* Fixed #2473: Login and mail should not be case sensitive
    +* Fixed #2990: Ruby 1.9 - wrong number of arguments (1 for 0) on rake db:migrate
    +* Fixed #3089: Text formatting sometimes breaks when combined
    +* Fixed #3690: Status change info duplicates on the issue screen
    +* Fixed #3691: Redmine allows two files with the same file name to be uploaded to the same issue
    +* Fixed #3764: ApplicationHelperTest fails with JRuby
    +* Fixed #4265: Unclosed code tags in issue descriptions affects main UI
    +* Fixed #4745: Bug in index.xml.builder (issues)
    +* Fixed #4852: changing user/roles of project member not possible without javascript
    +* Fixed #4857: Week number calculation in date picker is wrong if a week starts with Sunday
    +* Fixed #4883: Bottom "contextual" placement in issue with associated changeset
    +* Fixed #4918: Revisions r3453 and r3454 broke On-the-fly user creation with LDAP
    +* Fixed #4935: Navigation to the Master Timesheet page (time_entries)
    +* Fixed #5043: Flash messages are not displayed after the project settings[module/activity] saved
    +* Fixed #5081: Broken links on public/help/wiki_syntax_detailed.html
    +* Fixed #5104: Description of document not wikified on documents index
    +* Fixed #5108: Issue linking fails inside of []s
    +* Fixed #5199: diff code coloring using coderay
    +* Fixed #5233: Add a hook to the issue report (Summary) view
    +* Fixed #5265: timetracking: subtasks time is added to the main task
    +* Fixed #5343: acts_as_event Doesn't Accept Outside URLs
    +* Fixed #5440: UI Inconsistency : Administration > Enumerations table row headers should be enclosed in 
    +* Fixed #5463: 0.9.4 INSTALL and/or UPGRADE, missing session_store.rb
    +* Fixed #5524: Update_parent_attributes doesn't work for the old parent issue when reparenting
    +* Fixed #5548: SVN Repository: Can not list content of a folder which includes square brackets.
    +* Fixed #5589: "with subproject" malfunction
    +* Fixed #5676: Search for Numeric Value
    +* Fixed #5696: Redmine + PostgreSQL 8.4.4 fails on _dir_list_content.rhtml
    +* Fixed #5698: redmine:email:receive_imap fails silently for mails with subject longer than 255 characters
    +* Fixed #5700: TimelogController#destroy assumes success
    +* Fixed #5751: developer role is mispelled
    +* Fixed #5769: Popup Calendar doesn't Advance in Chrome
    +* Fixed #5771: Problem when importing git repository
    +* Fixed #5823: Error in comments in plugin.rb
    +
    +
    +== 2010-07-07 v0.9.6
    +
    +* Fixed: Redmine.pm access by unauthorized users
    +
    +== 2010-06-24 v0.9.5
    +
    +* Linkify folder names on revision view
    +* "fiters" and "options" should be hidden in print view via css
    +* Fixed: NoMethodError when no issue params are submitted
    +* Fixed: projects.atom with required authentication
    +* Fixed: External links not correctly displayed in Wiki TOC
    +* Fixed: Member role forms in project settings are not hidden after member added
    +* Fixed: pre can't be inside p
    +* Fixed: session cookie path does not respect RAILS_RELATIVE_URL_ROOT
    +* Fixed: mail handler fails when the from address is empty
    +
    +
    +== 2010-05-01 v0.9.4
    +
    +* Filters collapsed by default on issues index page for a saved query
    +* Fixed: When categories list is too big the popup menu doesn't adjust (ex. in the issue list)
    +* Fixed: remove "main-menu" div when the menu is empty
    +* Fixed: Code syntax highlighting not working in Document page
    +* Fixed: Git blame/annotate fails on moved files
    +* Fixed: Failing test in test_show_atom
    +* Fixed: Migrate from trac - not displayed Wikis
    +* Fixed: Email notifications on file upload sent to empty recipient list
    +* Fixed: Migrating from trac is not possible, fails to allocate memory
    +* Fixed: Lost password no longer flashes a confirmation message
    +* Fixed: Crash while deleting in-use enumeration
    +* Fixed: Hard coded English string at the selection of issue watchers
    +* Fixed: Bazaar v2.1.0 changed behaviour
    +* Fixed: Roadmap display can raise an exception if no trackers are selected
    +* Fixed: Gravatar breaks layout of "logged in" page
    +* Fixed: Reposman.rb on Windows
    +* Fixed: Possible error 500 while moving an issue to another project with SQLite
    +* Fixed: backslashes in issue description/note should be escaped when quoted
    +* Fixed: Long text in 
     disrupts Associated revisions
    +* Fixed: Links to missing wiki pages not red on project overview page
    +* Fixed: Cannot delete a project with subprojects that shares versions
    +* Fixed: Update of Subversion changesets broken under Solaris
    +* Fixed: "Move issues" permission not working for Non member
    +* Fixed: Sidebar overlap on Users tab of Group editor
    +* Fixed: Error on db:migrate with table prefix set (hardcoded name in principal.rb)
    +* Fixed: Report shows sub-projects for non-members
    +* Fixed: 500 internal error when browsing any Redmine page in epiphany
    +* Fixed: Watchers selection lost when issue creation fails
    +* Fixed: When copying projects, redmine should not generate an email to people who created issues
    +* Fixed: Issue "#" table cells should have a class attribute to enable fine-grained CSS theme
    +* Fixed: Plugin generators should display help if no parameter is given
    +
    +
    +== 2010-02-28 v0.9.3
    +
    +* Adds filter for system shared versions on the cross project issue list
    +* Makes project identifiers searchable
    +* Remove invalid utf8 sequences from commit comments and author name
    +* Fixed: Wrong link when "http" not included in project "Homepage" link
    +* Fixed: Escaping in html email templates
    +* Fixed: Pound (#) followed by number with leading zero (0) removes leading zero when rendered in wiki
    +* Fixed: Deselecting textile text formatting causes interning empty string errors
    +* Fixed: error with postgres when entering a non-numeric id for an issue relation
    +* Fixed: div.task incorrectly wrapping on Gantt Chart
    +* Fixed: Project copy loses wiki pages hierarchy
    +* Fixed: parent project field doesn't include blank value when a member with 'add subproject' permission edits a child project
    +* Fixed: Repository.fetch_changesets tries to fetch changesets for archived projects
    +* Fixed: Duplicated project name for subproject version on gantt chart
    +* Fixed: roadmap shows subprojects issues even if subprojects is unchecked
    +* Fixed: IndexError if all the :last menu items are deleted from a menu
    +* Fixed: Very high CPU usage for a long time when fetching commits from a large Git repository
    +
    +
    +== 2010-02-07 v0.9.2
    +
    +* Fixed: Sub-project repository commits not displayed on parent project issues
    +* Fixed: Potential security leak on my page calendar
    +* Fixed: Project tree structure is broken by deleting the project with the subproject
    +* Fixed: Error message shown duplicated when creating a new group
    +* Fixed: Firefox cuts off large pages
    +* Fixed: Invalid format parameter returns a DoubleRenderError on issues index
    +* Fixed: Unnecessary Quote button on locked forum message
    +* Fixed: Error raised when trying to view the gantt or calendar with a grouped query
    +* Fixed: PDF support for Korean locale
    +* Fixed: Deprecation warning in extra/svn/reposman.rb
    +
    +
    +== 2010-01-30 v0.9.1
    +
    +* Vertical alignment for inline images in formatted text set to 'middle'
    +* Fixed: Redmine.pm error "closing dbh with active statement handles at /usr/lib/perl5/Apache/Redmine.pm"
    +* Fixed: copyright year in footer set to 2010
    +* Fixed: Trac migration script may not output query lines
    +* Fixed: Email notifications may affect language of notice messages on the UI
    +* Fixed: Can not search for 2 letters word
    +* Fixed: Attachments get saved on issue update even if validation fails
    +* Fixed: Tab's 'border-bottom' not absent when selected
    +* Fixed: Issue summary tables that list by user are not sorted
    +* Fixed: Issue pdf export fails if target version is set
    +* Fixed: Issue list export to PDF breaks when issues are sorted by a custom field
    +* Fixed: SQL error when adding a group
    +* Fixes: Min password length during password reset always displays as 4 chars
    +
    +
    +== 2010-01-09 v0.9.0 (Release candidate)
    +
    +* Unlimited subproject nesting
    +* Multiple roles per user per project
    +* User groups
    +* Inheritence of versions
    +* OpenID login
    +* "Watched by me" issue filter
    +* Project copy
    +* Project creation by non admin users
    +* Accept emails from anyone on a private project
    +* Add email notification on Wiki changes
    +* Make issue description non-required field
    +* Custom fields for Versions
    +* Being able to sort the issue list by custom fields
    +* Ability to close versions
    +* User display/editing of custom fields attached to their user profile
    +* Add "follows" issue relation
    +* Copy workflows between trackers and roles
    +* Defaults enabled modules list for project creation
    +* Weighted version completion percentage on the roadmap
    +* Autocreate user account when user submits email that creates new issue
    +* CSS class on overdue issues on the issue list
    +* Enable tracker update on issue edit form
    +* Remove issue watchers
    +* Ability to move threads between project forums
    +* Changed custom field "Possible values" to a textarea
    +* Adds projects association on tracker form
    +* Set session store to cookie store by default
    +* Set a default wiki page on project creation
    +* Roadmap for main project should see Roadmaps for sub projects
    +* Ticket grouping on the issue list
    +* Hierarchical Project links in the page header
    +* Allow My Page blocks to be added to from a plugin
    +* Sort issues by multiple columns
    +* Filters of saved query are now visible and be adjusted without editing the query
    +* Saving "sort order" in custom queries
    +* Url to fetch changesets for a repository
    +* Managers able to create subprojects
    +* Issue Totals on My Page Modules
    +* Convert Enumerations to single table inheritance (STI)
    +* Allow custom my_page blocks to define drop-down names
    +* "View Issues" user permission added
    +* Ask user what to do with child pages when deleting a parent wiki page
    +* Contextual quick search
    +* Allow resending of password by email
    +* Change reply subject to be a link to the reply itself
    +* Include Logged Time as part of the project's Activity history
    +* REST API for authentication
    +* Browse through Git branches
    +* Setup Object Daddy to replace test fixtures
    +* Setup shoulda to make it easier to test
    +* Custom fields and overrides on Enumerations
    +* Add or remove columns from the issue list
    +* Ability to add new version from issues screen
    +* Setting to choose which day calendars start
    +* Asynchronous email delivery method
    +* RESTful URLs for (almost) everything
    +* Include issue status in search results and activity pages
    +* Add email to admin user search filter
    +* Proper content type for plain text mails
    +* Default value of project jump box
    +* Tree based menus
    +* Ability to use issue status to update percent done
    +* Second set of issue "Action Links" at the bottom of an issue page
    +* Proper exist status code for rdm-mailhandler.rb
    +* Remove incoming email body via a delimiter
    +* Fixed: Custom querry 'Export to PDF' ignores field selection
    +* Fixed: Related e-mail notifications aren't threaded
    +* Fixed: No warning when the creation of a categories from the issue form fails
    +* Fixed: Actually block issues from closing when relation 'blocked by' isn't closed
    +* Fixed: Include both first and last name when sorting by users
    +* Fixed: Table cell with multiple line text
    +* Fixed: Project overview page shows disabled trackers
    +* Fixed: Cross project issue relations and user permissions
    +* Fixed: My page shows tickets the user doesn't have access to
    +* Fixed: TOC does not parse wiki page reference links with description
    +* Fixed: Target version-list on bulk edit form is incorrectly sorted
    +* Fixed: Cannot modify/delete project named "Documents"
    +* Fixed: Email address in brackets breaks html
    +* Fixed: Timelog detail loose issue filter passing to report tab
    +* Fixed: Inform about custom field's name maximum length
    +* Fixed: Activity page and Atom feed links contain project id instead of identifier
    +* Fixed: no Atom key for forums with only 1 forum
    +* Fixed: When reading RSS feed in MS Outlook, the inline links are broken.
    +* Fixed: Sometimes new posts don't show up in the topic list of a forum.
    +* Fixed: The all/active filter selection in the project view does not stick.
    +* Fixed: Login box has Different width
    +* Fixed: User removed from project - still getting project update emails
    +* Fixed: Project with the identifier of 'new' cannot be viewed
    +* Fixed: Artefacts in search view (Cyrillic)
    +* Fixed: Allow [#id] as subject to reply by email
    +* Fixed: Wrong language used when closing an issue via a commit message
    +* Fixed: email handler drops emails for new issues with no subject
    +* Fixed: Calendar misspelled under Roles/Permissions
    +* Fixed: Emails from no-reply redmine's address hell cycle
    +* Fixed: child_pages macro fails on wiki page history
    +* Fixed: Pre-filled time tracking date ignores timezone
    +* Fixed: Links on locked users lead to 404 page
    +* Fixed: Page changes in issue-list when using context menu
    +* Fixed: diff parser removes lines starting with multiple dashes
    +* Fixed: Quoting in forums resets message subject
    +* Fixed: Editing issue comment removes quote link
    +* Fixed: Redmine.pm ignore browse_repository permission
    +* Fixed: text formatting breaks on [msg1][msg2]
    +* Fixed: Spent Time Default Value of 0.0
    +* Fixed: Wiki pages in search results are referenced by project number, not by project identifier.
    +* Fixed: When logging in via an autologin cookie the user's last_login_on should be updated
    +* Fixed: 50k users cause problems in project->settings->members screen
    +* Fixed: Document timestamp needs to show updated timestamps
    +* Fixed: Users getting notifications for issues they are no longer allowed to view
    +* Fixed: issue summary counts should link to the issue list without subprojects
    +* Fixed: 'Delete' link on LDAP list has no effect
    +
    +
    +== 2009-11-15 v0.8.7
    +
    +* Fixed: Hide paragraph terminator at the end of headings on html export
    +* Fixed: pre tags containing "#{ after }", false )
    -        end
    -    end
    -
    -    def lT( text ) 
    -        text =~ /\#$/ ? 'o' : 'u'
    -    end
    -
    -    def hard_break( text )
    -        text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks - end - - BLOCKS_GROUP_RE = /\n{2,}(?! )/m - - def blocks( text, deep_code = false ) - text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk| - plain = blk !~ /\A[#*> ]/ - - # skip blocks that are complex HTML - if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1 - blk - else - # search for indentation levels - blk.strip! - if blk.empty? - blk - else - code_blk = nil - blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk| - flush_left iblk - blocks iblk, plain - iblk.gsub( /^(\S)/, "\t\\1" ) - if plain - code_blk = iblk; "" - else - iblk - end - end - - block_applied = 0 - @rules.each do |rule_name| - block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) ) - end - if block_applied.zero? - if deep_code - blk = "\t
    #{ blk }
    " - else - blk = "\t

    #{ blk }

    " - end - end - # hard_break blk - blk + "\n#{ code_blk }" - end - end - - end.join( "\n\n" ) ) - end - - def textile_bq( tag, atts, cite, content ) - cite, cite_title = check_refs( cite ) - cite = " cite=\"#{ cite }\"" if cite - atts = shelve( atts ) if atts - "\t\n\t\t#{ content }

    \n\t" - end - - def textile_p( tag, atts, cite, content ) - atts = shelve( atts ) if atts - "\t<#{ tag }#{ atts }>#{ content }" - end - - alias textile_h1 textile_p - alias textile_h2 textile_p - alias textile_h3 textile_p - alias textile_h4 textile_p - alias textile_h5 textile_p - alias textile_h6 textile_p - - def textile_fn_( tag, num, atts, cite, content ) - atts << " id=\"fn#{ num }\" class=\"footnote\"" - content = "#{ num } #{ content }" - atts = shelve( atts ) if atts - "\t#{ content }

    " - end - - BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m - - def block_textile_prefix( text ) - if text =~ BLOCK_RE - tag,tagpre,num,atts,cite,content = $~[1..6] - atts = pba( atts ) - - # pass to prefix handler - replacement = nil - if respond_to? "textile_#{ tag }", true - replacement = method( "textile_#{ tag }" ).call( tag, atts, cite, content ) - elsif respond_to? "textile_#{ tagpre }_", true - replacement = method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) - end - text.gsub!( $& ) { replacement } if replacement - end - end - - SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m - def block_markdown_setext( text ) - if text =~ SETEXT_RE - tag = if $2 == "="; "h1"; else; "h2"; end - blk, cont = "<#{ tag }>#{ $1 }", $' - blocks cont - text.replace( blk + cont ) - end - end - - ATX_RE = /\A(\#{1,6}) # $1 = string of #'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #'s (not counted) - $/x - def block_markdown_atx( text ) - if text =~ ATX_RE - tag = "h#{ $1.length }" - blk, cont = "<#{ tag }>#{ $2 }\n\n", $' - blocks cont - text.replace( blk + cont ) - end - end - - MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m - - def block_markdown_bq( text ) - text.gsub!( MARKDOWN_BQ_RE ) do |blk| - blk.gsub!( /^ *> ?/, '' ) - flush_left blk - blocks blk - blk.gsub!( /^(\S)/, "\t\\1" ) - "
    \n#{ blk }\n
    \n\n" - end - end - - MARKDOWN_RULE_RE = /^(#{ - ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' ) - })$/ - - def block_markdown_rule( text ) - text.gsub!( MARKDOWN_RULE_RE ) do |blk| - "
    " - end - end - - # XXX TODO XXX - def block_markdown_lists( text ) - end - - def inline_textile_span( text ) - QTAGS.each do |qtag_rc, ht, qtag_re, rtype| - text.gsub!( qtag_re ) do |m| - - case rtype - when :limit - sta,oqs,qtag,content,oqa = $~[1..6] - atts = nil - if content =~ /^(#{C})(.+)$/ - atts, content = $~[1..2] - end - else - qtag,atts,cite,content = $~[1..4] - sta = '' - end - atts = pba( atts ) - atts = shelve( atts ) if atts - - "#{ sta }#{ oqs }<#{ ht }#{ atts }>#{ content }#{ oqa }" - - end - end - end - - LINK_RE = / - ( - ([\s\[{(]|[#{PUNCT}])? # $pre - " # start - (#{C}) # $atts - ([^"\n]+?) # $text - \s? - (?:\(([^)]+?)\)(?="))? # $title - ": - ( # $url - (\/|[a-zA-Z]+:\/\/|www\.|mailto:) # $proto - [[:alnum:]_\/]\S+? - ) - (\/)? # $slash - ([^[:alnum:]_\=\/;\(\)]*?) # $post - ) - (?=<|\s|$) - /x -#" - def inline_textile_link( text ) - text.gsub!( LINK_RE ) do |m| - all,pre,atts,text,title,url,proto,slash,post = $~[1..9] - if text.include?('
    ') - all - else - url, url_title = check_refs( url ) - title ||= url_title - - # Idea below : an URL with unbalanced parethesis and - # ending by ')' is put into external parenthesis - if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) ) - url=url[0..-2] # discard closing parenth from url - post = ")"+post # add closing parenth to post - end - atts = pba( atts ) - atts = " href=\"#{ htmlesc url }#{ slash }\"#{ atts }" - atts << " title=\"#{ htmlesc title }\"" if title - atts = shelve( atts ) if atts - - external = (url =~ /^https?:\/\//) ? ' class="external"' : '' - - "#{ pre }#{ text }#{ post }" - end - end - end - - MARKDOWN_REFLINK_RE = / - \[([^\[\]]+)\] # $text - [ ]? # opt. space - (?:\n[ ]*)? # one optional newline followed by spaces - \[(.*?)\] # $id - /x - - def inline_markdown_reflink( text ) - text.gsub!( MARKDOWN_REFLINK_RE ) do |m| - text, id = $~[1..2] - - if id.empty? - url, title = check_refs( text ) - else - url, title = check_refs( id ) - end - - atts = " href=\"#{ url }\"" - atts << " title=\"#{ title }\"" if title - atts = shelve( atts ) - - "#{ text }" - end - end - - MARKDOWN_LINK_RE = / - \[([^\[\]]+)\] # $text - \( # open paren - [ \t]* # opt space - ? # $href - [ \t]* # opt space - (?: # whole title - (['"]) # $quote - (.*?) # $title - \3 # matching quote - )? # title is optional - \) - /x - - def inline_markdown_link( text ) - text.gsub!( MARKDOWN_LINK_RE ) do |m| - text, url, quote, title = $~[1..4] - - atts = " href=\"#{ url }\"" - atts << " title=\"#{ title }\"" if title - atts = shelve( atts ) - - "#{ text }" - end - end - - TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/ - MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m - - def refs( text ) - @rules.each do |rule_name| - method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/ - end - end - - def refs_textile( text ) - text.gsub!( TEXTILE_REFS_RE ) do |m| - flag, url = $~[2..3] - @urlrefs[flag.downcase] = [url, nil] - nil - end - end - - def refs_markdown( text ) - text.gsub!( MARKDOWN_REFS_RE ) do |m| - flag, url = $~[2..3] - title = $~[6] - @urlrefs[flag.downcase] = [url, title] - nil - end - end - - def check_refs( text ) - ret = @urlrefs[text.downcase] if text - ret || [text, nil] - end - - IMAGE_RE = / - (>|\s|^) # start of line? - \! # opening - (\<|\=|\>)? # optional alignment atts - (#{C}) # optional style,class atts - (?:\. )? # optional dot-space - ([^\s(!]+?) # presume this is the src - \s? # optional space - (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title - \! # closing - (?::#{ HYPERLINK })? # optional href - /x - - def inline_textile_image( text ) - text.gsub!( IMAGE_RE ) do |m| - stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8] - htmlesc title - atts = pba( atts ) - atts = " src=\"#{ htmlesc url.dup }\"#{ atts }" - atts << " title=\"#{ title }\"" if title - atts << " alt=\"#{ title }\"" - # size = @getimagesize($url); - # if($size) $atts.= " $size[3]"; - - href, alt_title = check_refs( href ) if href - url, url_title = check_refs( url ) - - out = '' - out << "" if href - out << "" - out << "#{ href_a1 }#{ href_a2 }" if href - - if algn - algn = h_align( algn ) - if stln == "

    " - out = "

    #{ out }" - else - out = "#{ stln }

    #{ out }
    " - end - else - out = stln + out - end - - out - end - end - - def shelve( val ) - @shelf << val - " :redsh##{ @shelf.length }:" - end - - def retrieve( text ) - @shelf.each_with_index do |r, i| - text.gsub!( " :redsh##{ i + 1 }:", r ) - end - end - - def incoming_entities( text ) - ## turn any incoming ampersands into a dummy character for now. - ## This uses a negative lookahead for alphanumerics followed by a semicolon, - ## implying an incoming html entity, to be skipped - - text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) - end - - def no_textile( text ) - text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/, - '\1\2\3' ) - text.gsub!( /^ *==([^=]+.*?)==/m, - '\1\2\3' ) - end - - def clean_white_space( text ) - # normalize line breaks - text.gsub!( /\r\n/, "\n" ) - text.gsub!( /\r/, "\n" ) - text.gsub!( /\t/, ' ' ) - text.gsub!( /^ +$/, '' ) - text.gsub!( /\n{3,}/, "\n\n" ) - text.gsub!( /"$/, "\" " ) - - # if entire document is indented, flush - # to the left side - flush_left text - end - - def flush_left( text ) - indt = 0 - if text =~ /^ / - while text !~ /^ {#{indt}}\S/ - indt += 1 - end unless text.empty? - if indt.nonzero? - text.gsub!( /^ {#{indt}}/, '' ) - end - end - end - - def footnote_ref( text ) - text.gsub!( /\b\[([0-9]+?)\](\s)?/, - '\1\2' ) - end - - OFFTAGS = /(code|pre|kbd|notextile)/ - OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }\W|\Z)/mi - OFFTAG_OPEN = /<#{ OFFTAGS }/ - OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/ - HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m - ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m - - def glyphs_textile( text, level = 0 ) - if text !~ HASTAG_MATCH - pgl text - footnote_ref text - else - codepre = 0 - text.gsub!( ALLTAG_MATCH ) do |line| - ## matches are off if we're between ,
     etc.
    -                if $1
    -                    if line =~ OFFTAG_OPEN
    -                        codepre += 1
    -                    elsif line =~ OFFTAG_CLOSE
    -                        codepre -= 1
    -                        codepre = 0 if codepre < 0
    -                    end 
    -                elsif codepre.zero?
    -                    glyphs_textile( line, level + 1 )
    -                else
    -                    htmlesc( line, :NoQuotes )
    -                end
    -                # p [level, codepre, line]
    -
    -                line
    -            end
    -        end
    -    end
    -
    -    def rip_offtags( text, escape_aftertag=true, escape_line=true )
    -        if text =~ /<.*>/
    -            ## strip and encode 
     content
    -            codepre, used_offtags = 0, {}
    -            text.gsub!( OFFTAG_MATCH ) do |line|
    -                if $3
    -                    first, offtag, aftertag = $3, $4, $5
    -                    codepre += 1
    -                    used_offtags[offtag] = true
    -                    if codepre - used_offtags.length > 0
    -                        htmlesc( line, :NoQuotes ) if escape_line
    -                        @pre_list.last << line
    -                        line = ""
    -                    else
    -                        ### htmlesc is disabled between CODE tags which will be parsed with highlighter
    -                        ### Regexp in formatter.rb is : /\s?(.+)/m
    -                        ### NB: some changes were made not to use $N variables, because we use "match"
    -                        ###   and it breaks following lines
    -                        htmlesc( aftertag, :NoQuotes ) if aftertag && escape_aftertag && !first.match(//)
    -                        line = ""
    -                        first.match(/<#{ OFFTAGS }([^>]*)>/)
    -                        tag = $1
    -                        $2.to_s.match(/(class\=("[^"]+"|'[^']+'))/i)
    -                        tag << " #{$1}" if $1
    -                        @pre_list << "<#{ tag }>#{ aftertag }"
    -                    end
    -                elsif $1 and codepre > 0
    -                    if codepre - used_offtags.length > 0
    -                        htmlesc( line, :NoQuotes ) if escape_line
    -                        @pre_list.last << line
    -                        line = ""
    -                    end
    -                    codepre -= 1 unless codepre.zero?
    -                    used_offtags = {} if codepre.zero?
    -                end 
    -                line
    -            end
    -        end
    -        text
    -    end
    -
    -    def smooth_offtags( text )
    -        unless @pre_list.empty?
    -            ## replace 
     content
    -            text.gsub!( // ) { @pre_list[$1.to_i] }
    -        end
    -    end
    -
    -    def inline( text ) 
    -        [/^inline_/, /^glyphs_/].each do |meth_re|
    -            @rules.each do |rule_name|
    -                method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
    -            end
    -        end
    -    end
    -
    -    def h_align( text ) 
    -        H_ALGN_VALS[text]
    -    end
    -
    -    def v_align( text ) 
    -        V_ALGN_VALS[text]
    -    end
    -
    -    def textile_popup_help( name, windowW, windowH )
    -        ' ' + name + '
    ' - end - - # HTML cleansing stuff - BASIC_TAGS = { - 'a' => ['href', 'title'], - 'img' => ['src', 'alt', 'title'], - 'br' => [], - 'i' => nil, - 'u' => nil, - 'b' => nil, - 'pre' => nil, - 'kbd' => nil, - 'code' => ['lang'], - 'cite' => nil, - 'strong' => nil, - 'em' => nil, - 'ins' => nil, - 'sup' => nil, - 'sub' => nil, - 'del' => nil, - 'table' => nil, - 'tr' => nil, - 'td' => ['colspan', 'rowspan'], - 'th' => nil, - 'ol' => nil, - 'ul' => nil, - 'li' => nil, - 'p' => nil, - 'h1' => nil, - 'h2' => nil, - 'h3' => nil, - 'h4' => nil, - 'h5' => nil, - 'h6' => nil, - 'blockquote' => ['cite'] - } - - def clean_html( text, tags = BASIC_TAGS ) - text.gsub!( /]*)>/ ) do - raw = $~ - tag = raw[2].downcase - if tags.has_key? tag - pcs = [tag] - tags[tag].each do |prop| - ['"', "'", ''].each do |q| - q2 = ( q != '' ? q : '\s' ) - if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i - attrv = $1 - next if prop == 'src' and attrv =~ %r{^(?!http)\w+:} - pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\"" - break - end - end - end if tags[tag] - "<#{raw[1]}#{pcs.join " "}>" - else - " " - end - end - end - - ALLOWED_TAGS = %w(redpre pre code notextile) - - def escape_html_tags(text) - text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' unless $3.blank?}" } - end -end - diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fa/fa3b4d93c72c3ec159cca814b171bb7f0448ff0f.svn-base --- a/.svn/pristine/fa/fa3b4d93c72c3ec159cca814b171bb7f0448ff0f.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class Topic < ActiveRecord::Base - has_many :replies, :include => [:user], :dependent => :destroy -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fa/fa4c6495ebff9e1214dc90dd2c21d6f78f2cf2e7.svn-base --- a/.svn/pristine/fa/fa4c6495ebff9e1214dc90dd2c21d6f78f2cf2e7.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -api.issue do - api.id @issue.id - api.project(:id => @issue.project_id, :name => @issue.project.name) unless @issue.project.nil? - api.tracker(:id => @issue.tracker_id, :name => @issue.tracker.name) unless @issue.tracker.nil? - api.status(:id => @issue.status_id, :name => @issue.status.name) unless @issue.status.nil? - api.priority(:id => @issue.priority_id, :name => @issue.priority.name) unless @issue.priority.nil? - api.author(:id => @issue.author_id, :name => @issue.author.name) unless @issue.author.nil? - api.assigned_to(:id => @issue.assigned_to_id, :name => @issue.assigned_to.name) unless @issue.assigned_to.nil? - api.category(:id => @issue.category_id, :name => @issue.category.name) unless @issue.category.nil? - api.fixed_version(:id => @issue.fixed_version_id, :name => @issue.fixed_version.name) unless @issue.fixed_version.nil? - api.parent(:id => @issue.parent_id) unless @issue.parent.nil? - - api.subject @issue.subject - api.description @issue.description - api.start_date @issue.start_date - api.due_date @issue.due_date - api.done_ratio @issue.done_ratio - api.estimated_hours @issue.estimated_hours - api.spent_hours(@issue.spent_hours) if User.current.allowed_to?(:view_time_entries, @project) - - render_api_custom_values @issue.custom_field_values, api - - api.created_on @issue.created_on - api.updated_on @issue.updated_on - - render_api_issue_children(@issue, api) if include_in_api_response?('children') - - api.array :attachments do - @issue.attachments.each do |attachment| - render_api_attachment(attachment, api) - end - end if include_in_api_response?('attachments') - - api.array :relations do - @relations.each do |relation| - api.relation(:id => relation.id, :issue_id => relation.issue_from_id, :issue_to_id => relation.issue_to_id, :relation_type => relation.relation_type, :delay => relation.delay) - end - end if include_in_api_response?('relations') && @relations.present? - - api.array :changesets do - @issue.changesets.each do |changeset| - api.changeset :revision => changeset.revision do - api.user(:id => changeset.user_id, :name => changeset.user.name) unless changeset.user.nil? - api.comments changeset.comments - api.committed_on changeset.committed_on - end - end - end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project) - - api.array :journals do - @journals.each do |journal| - api.journal :id => journal.id do - api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil? - api.notes journal.notes - api.created_on journal.created_on - api.array :details do - journal.details.each do |detail| - api.detail :property => detail.property, :name => detail.prop_key do - api.old_value detail.old_value - api.new_value detail.value - end - end - end - end - end - end if include_in_api_response?('journals') -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fa/fa4e79285c00e0ec28562fe13c5b5fad98f92052.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fa/fa4e79285c00e0ec28562fe13c5b5fad98f92052.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,95 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class ReportsController < ApplicationController + menu_item :issues + before_filter :find_project, :authorize, :find_issue_statuses + + def issue_report + @trackers = @project.trackers + @versions = @project.shared_versions.sort + @priorities = IssuePriority.all.reverse + @categories = @project.issue_categories + @assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort + @authors = @project.users.sort + @subprojects = @project.descendants.visible + + @issues_by_tracker = Issue.by_tracker(@project) + @issues_by_version = Issue.by_version(@project) + @issues_by_priority = Issue.by_priority(@project) + @issues_by_category = Issue.by_category(@project) + @issues_by_assigned_to = Issue.by_assigned_to(@project) + @issues_by_author = Issue.by_author(@project) + @issues_by_subproject = Issue.by_subproject(@project) || [] + + render :template => "reports/issue_report" + end + + def issue_report_details + case params[:detail] + when "tracker" + @field = "tracker_id" + @rows = @project.trackers + @data = Issue.by_tracker(@project) + @report_title = l(:field_tracker) + when "version" + @field = "fixed_version_id" + @rows = @project.shared_versions.sort + @data = Issue.by_version(@project) + @report_title = l(:field_version) + when "priority" + @field = "priority_id" + @rows = IssuePriority.all.reverse + @data = Issue.by_priority(@project) + @report_title = l(:field_priority) + when "category" + @field = "category_id" + @rows = @project.issue_categories + @data = Issue.by_category(@project) + @report_title = l(:field_category) + when "assigned_to" + @field = "assigned_to_id" + @rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort + @data = Issue.by_assigned_to(@project) + @report_title = l(:field_assigned_to) + when "author" + @field = "author_id" + @rows = @project.users.sort + @data = Issue.by_author(@project) + @report_title = l(:field_author) + when "subproject" + @field = "project_id" + @rows = @project.descendants.visible + @data = Issue.by_subproject(@project) || [] + @report_title = l(:field_subproject) + end + + respond_to do |format| + if @field + format.html {} + else + format.html { redirect_to :action => 'issue_report', :id => @project } + end + end + end + + private + + def find_issue_statuses + @statuses = IssueStatus.sorted.all + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fa/fae038378d362215d4026bc12ab909502d936808.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fa/fae038378d362215d4026bc12ab909502d936808.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class WelcomeController < ApplicationController + caches_action :robots + + def index + @news = News.latest User.current + @projects = Project.latest User.current + end + + def robots + @projects = Project.all_public.active + render :layout => false, :content_type => 'text/plain' + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fb/fb006bff613cd3967737c6ae62ce3ae28c040bbd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fb/fb006bff613cd3967737c6ae62ce3ae28c040bbd.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,462 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# 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. + +require 'redmine/scm/adapters/abstract_adapter' + +module Redmine + module Scm + module Adapters + class CvsAdapter < AbstractAdapter + + # CVS executable name + CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs" + + class << self + def client_command + @@bin ||= CVS_BIN + end + + def sq_bin + @@sq_bin ||= shell_quote_command + end + + def client_version + @@client_version ||= (scm_command_version || []) + end + + def client_available + client_version_above?([1, 12]) + end + + def scm_command_version + scm_version = scm_version_from_command_line.dup + if scm_version.respond_to?(:force_encoding) + scm_version.force_encoding('ASCII-8BIT') + end + if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}m) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def scm_version_from_command_line + shellout("#{sq_bin} --version") { |io| io.read }.to_s + end + end + + # Guidelines for the input: + # url -> the project-path, relative to the cvsroot (eg. module name) + # root_url -> the good old, sometimes damned, CVSROOT + # login -> unnecessary + # password -> unnecessary too + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) + @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding + @url = url + # TODO: better Exception here (IllegalArgumentException) + raise CommandFailed if root_url.blank? + @root_url = root_url + + # These are unused. + @login = login if login && !login.empty? + @password = (password || "") if @login + end + + def path_encoding + @path_encoding + end + + def info + logger.debug " info" + Info.new({:root_url => @root_url, :lastrev => nil}) + end + + def get_previous_revision(revision) + CvsRevisionHelper.new(revision).prevRev + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + # this method is used by the repository-browser (aka LIST) + def entries(path=nil, identifier=nil, options={}) + logger.debug " entries '#{path}' with identifier '#{identifier}'" + path_locale = scm_iconv(@path_encoding, 'UTF-8', path) + path_locale.force_encoding("ASCII-8BIT") if path_locale.respond_to?(:force_encoding) + entries = Entries.new + cmd_args = %w|-q rls -e| + cmd_args << "-D" << time_to_cvstime_rlog(identifier) if identifier + cmd_args << path_with_proj(path) + scm_cmd(*cmd_args) do |io| + io.each_line() do |line| + fields = line.chop.split('/',-1) + logger.debug(">>InspectLine #{fields.inspect}") + if fields[0]!="D" + time = nil + # Thu Dec 13 16:27:22 2007 + time_l = fields[-3].split(' ') + if time_l.size == 5 && time_l[4].length == 4 + begin + time = Time.parse( + "#{time_l[1]} #{time_l[2]} #{time_l[3]} GMT #{time_l[4]}") + rescue + end + end + entries << Entry.new( + { + :name => scm_iconv('UTF-8', @path_encoding, fields[-5]), + #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]), + :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[-5]}"), + :kind => 'file', + :size => nil, + :lastrev => Revision.new( + { + :revision => fields[-4], + :name => scm_iconv('UTF-8', @path_encoding, fields[-4]), + :time => time, + :author => '' + }) + }) + else + entries << Entry.new( + { + :name => scm_iconv('UTF-8', @path_encoding, fields[1]), + :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[1]}"), + :kind => 'dir', + :size => nil, + :lastrev => nil + }) + end + end + end + entries.sort_by_name + rescue ScmCommandAborted + nil + end + + STARTLOG="----------------------------" + ENDLOG ="=============================================================================" + + # Returns all revisions found between identifier_from and identifier_to + # in the repository. both identifier have to be dates or nil. + # these method returns nothing but yield every result in block + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block) + path_with_project_utf8 = path_with_proj(path) + path_with_project_locale = scm_iconv(@path_encoding, 'UTF-8', path_with_project_utf8) + logger.debug " revisions path:" + + "'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" + cmd_args = %w|-q rlog| + cmd_args << "-d" << ">#{time_to_cvstime_rlog(identifier_from)}" if identifier_from + cmd_args << path_with_project_utf8 + scm_cmd(*cmd_args) do |io| + state = "entry_start" + commit_log = String.new + revision = nil + date = nil + author = nil + entry_path = nil + entry_name = nil + file_state = nil + branch_map = nil + io.each_line() do |line| + if state != "revision" && /^#{ENDLOG}/ =~ line + commit_log = String.new + revision = nil + state = "entry_start" + end + if state == "entry_start" + branch_map = Hash.new + if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project_locale)}(.+),v$/ =~ line + entry_path = normalize_cvs_path($1) + entry_name = normalize_path(File.basename($1)) + logger.debug("Path #{entry_path} <=> Name #{entry_name}") + elsif /^head: (.+)$/ =~ line + entry_headRev = $1 #unless entry.nil? + elsif /^symbolic names:/ =~ line + state = "symbolic" #unless entry.nil? + elsif /^#{STARTLOG}/ =~ line + commit_log = String.new + state = "revision" + end + next + elsif state == "symbolic" + if /^(.*):\s(.*)/ =~ (line.strip) + branch_map[$1] = $2 + else + state = "tags" + next + end + elsif state == "tags" + if /^#{STARTLOG}/ =~ line + commit_log = "" + state = "revision" + elsif /^#{ENDLOG}/ =~ line + state = "head" + end + next + elsif state == "revision" + if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line + if revision + revHelper = CvsRevisionHelper.new(revision) + revBranch = "HEAD" + branch_map.each() do |branch_name, branch_point| + if revHelper.is_in_branch_with_symbol(branch_point) + revBranch = branch_name + end + end + logger.debug("********** YIELD Revision #{revision}::#{revBranch}") + yield Revision.new({ + :time => date, + :author => author, + :message => commit_log.chomp, + :paths => [{ + :revision => revision.dup, + :branch => revBranch.dup, + :path => scm_iconv('UTF-8', @path_encoding, entry_path), + :name => scm_iconv('UTF-8', @path_encoding, entry_name), + :kind => 'file', + :action => file_state + }] + }) + end + commit_log = String.new + revision = nil + if /^#{ENDLOG}/ =~ line + state = "entry_start" + end + next + end + + if /^branches: (.+)$/ =~ line + # TODO: version.branch = $1 + elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line + revision = $1 + elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line + date = Time.parse($1) + line_utf8 = scm_iconv('UTF-8', options[:log_encoding], line) + author_utf8 = /author: ([^;]+)/.match(line_utf8)[1] + author = scm_iconv(options[:log_encoding], 'UTF-8', author_utf8) + file_state = /state: ([^;]+)/.match(line)[1] + # TODO: + # linechanges only available in CVS.... + # maybe a feature our SVN implementation. + # I'm sure, they are useful for stats or something else + # linechanges =/lines: \+(\d+) -(\d+)/.match(line) + # unless linechanges.nil? + # version.line_plus = linechanges[1] + # version.line_minus = linechanges[2] + # else + # version.line_plus = 0 + # version.line_minus = 0 + # end + else + commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/ + end + end + end + end + rescue ScmCommandAborted + Revisions.new + end + + def diff(path, identifier_from, identifier_to=nil) + logger.debug " diff path:'#{path}'" + + ",identifier_from #{identifier_from}, identifier_to #{identifier_to}" + cmd_args = %w|rdiff -u| + cmd_args << "-r#{identifier_to}" + cmd_args << "-r#{identifier_from}" + cmd_args << path_with_proj(path) + diff = [] + scm_cmd(*cmd_args) do |io| + io.each_line do |line| + diff << line + end + end + diff + rescue ScmCommandAborted + nil + end + + def cat(path, identifier=nil) + identifier = (identifier) ? identifier : "HEAD" + logger.debug " cat path:'#{path}',identifier #{identifier}" + cmd_args = %w|-q co| + cmd_args << "-D" << time_to_cvstime(identifier) if identifier + cmd_args << "-p" << path_with_proj(path) + cat = nil + scm_cmd(*cmd_args) do |io| + io.binmode + cat = io.read + end + cat + rescue ScmCommandAborted + nil + end + + def annotate(path, identifier=nil) + identifier = (identifier) ? identifier : "HEAD" + logger.debug " annotate path:'#{path}',identifier #{identifier}" + cmd_args = %w|rannotate| + cmd_args << "-D" << time_to_cvstime(identifier) if identifier + cmd_args << path_with_proj(path) + blame = Annotate.new + scm_cmd(*cmd_args) do |io| + io.each_line do |line| + next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$} + blame.add_line( + $3.rstrip, + Revision.new( + :revision => $1, + :identifier => nil, + :author => $2.strip + )) + end + end + blame + rescue ScmCommandAborted + Annotate.new + end + + private + + # Returns the root url without the connexion string + # :pserver:anonymous@foo.bar:/path => /path + # :ext:cvsservername:/path => /path + def root_url_path + root_url.to_s.gsub(%r{^:.+?(?=/)}, '') + end + + # convert a date/time into the CVS-format + def time_to_cvstime(time) + return nil if time.nil? + time = Time.now if time == 'HEAD' + + unless time.kind_of? Time + time = Time.parse(time) + end + return time_to_cvstime_rlog(time) + end + + def time_to_cvstime_rlog(time) + return nil if time.nil? + t1 = time.clone.localtime + return t1.strftime("%Y-%m-%d %H:%M:%S") + end + + def normalize_cvs_path(path) + normalize_path(path.gsub(/Attic\//,'')) + end + + def normalize_path(path) + path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1') + end + + def path_with_proj(path) + "#{url}#{with_leading_slash(path)}" + end + private :path_with_proj + + class Revision < Redmine::Scm::Adapters::Revision + # Returns the readable identifier + def format_identifier + revision.to_s + end + end + + def scm_cmd(*args, &block) + full_args = ['-d', root_url] + full_args += args + full_args_locale = [] + full_args.map do |e| + full_args_locale << scm_iconv(@path_encoding, 'UTF-8', e) + end + ret = shellout( + self.class.sq_bin + ' ' + full_args_locale.map { |e| shell_quote e.to_s }.join(' '), + &block + ) + if $? && $?.exitstatus != 0 + raise ScmCommandAborted, "cvs exited with non-zero status: #{$?.exitstatus}" + end + ret + end + private :scm_cmd + end + + class CvsRevisionHelper + attr_accessor :complete_rev, :revision, :base, :branchid + + def initialize(complete_rev) + @complete_rev = complete_rev + parseRevision() + end + + def branchPoint + return @base + end + + def branchVersion + if isBranchRevision + return @base+"."+@branchid + end + return @base + end + + def isBranchRevision + !@branchid.nil? + end + + def prevRev + unless @revision == 0 + return buildRevision( @revision - 1 ) + end + return buildRevision( @revision ) + end + + def is_in_branch_with_symbol(branch_symbol) + bpieces = branch_symbol.split(".") + branch_start = "#{bpieces[0..-3].join(".")}.#{bpieces[-1]}" + return ( branchVersion == branch_start ) + end + + private + def buildRevision(rev) + if rev == 0 + if @branchid.nil? + @base + ".0" + else + @base + end + elsif @branchid.nil? + @base + "." + rev.to_s + else + @base + "." + @branchid + "." + rev.to_s + end + end + + # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15 + def parseRevision() + pieces = @complete_rev.split(".") + @revision = pieces.last.to_i + baseSize = 1 + baseSize += (pieces.size / 2) + @base = pieces[0..-baseSize].join(".") + if baseSize > 2 + @branchid = pieces[-2] + end + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fb/fb831ceb031f77a301df0cd90da94f30274b8153.svn-base --- a/.svn/pristine/fb/fb831ceb031f77a301df0cd90da94f30274b8153.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class AdminControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles - - def setup - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_no_tag :tag => 'div', - :attributes => { :class => /nodata/ } - end - - def test_index_with_no_configuration_data - delete_configuration_data - get :index - assert_tag :tag => 'div', - :attributes => { :class => /nodata/ } - end - - def test_projects - get :projects - assert_response :success - assert_template 'projects' - assert_not_nil assigns(:projects) - # active projects only - assert_nil assigns(:projects).detect {|u| !u.active?} - end - - def test_projects_with_status_filter - get :projects, :status => 1 - assert_response :success - assert_template 'projects' - assert_not_nil assigns(:projects) - # active projects only - assert_nil assigns(:projects).detect {|u| !u.active?} - end - - def test_projects_with_name_filter - get :projects, :name => 'store', :status => '' - assert_response :success - assert_template 'projects' - projects = assigns(:projects) - assert_not_nil projects - assert_equal 1, projects.size - assert_equal 'OnlineStore', projects.first.name - end - - def test_load_default_configuration_data - delete_configuration_data - post :default_configuration, :lang => 'fr' - assert_response :redirect - assert_nil flash[:error] - assert IssueStatus.find_by_name('Nouveau') - end - - def test_load_default_configuration_data_should_rescue_error - delete_configuration_data - Redmine::DefaultData::Loader.stubs(:load).raises(Exception.new("Something went wrong")) - post :default_configuration, :lang => 'fr' - assert_response :redirect - assert_not_nil flash[:error] - assert_match /Something went wrong/, flash[:error] - end - - def test_test_email - user = User.find(1) - user.pref[:no_self_notified] = '1' - user.pref.save! - ActionMailer::Base.deliveries.clear - - get :test_email - assert_redirected_to '/settings/edit?tab=notifications' - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - user = User.find(1) - assert_equal [user.mail], mail.bcc - end - - def test_test_email_failure_should_display_the_error - Mailer.stubs(:test_email).raises(Exception, 'Some error message') - get :test_email - assert_redirected_to '/settings/edit?tab=notifications' - assert_match /Some error message/, flash[:error] - end - - def test_no_plugins - Redmine::Plugin.clear - - get :plugins - assert_response :success - assert_template 'plugins' - end - - def test_plugins - # Register a few plugins - Redmine::Plugin.register :foo do - name 'Foo plugin' - author 'John Smith' - description 'This is a test plugin' - version '0.0.1' - settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings' - end - Redmine::Plugin.register :bar do - end - - get :plugins - assert_response :success - assert_template 'plugins' - - assert_tag :td, :child => { :tag => 'span', :content => 'Foo plugin' } - assert_tag :td, :child => { :tag => 'span', :content => 'Bar' } - end - - def test_info - get :info - assert_response :success - assert_template 'info' - end - - def test_admin_menu_plugin_extension - Redmine::MenuManager.map :admin_menu do |menu| - menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test' - end - - get :index - assert_response :success - assert_tag :a, :attributes => { :href => '/foo/bar' }, - :content => 'Test' - - Redmine::MenuManager.map :admin_menu do |menu| - menu.delete :test_admin_menu_plugin_extension - end - end - - private - - def delete_configuration_data - Role.delete_all('builtin = 0') - Tracker.delete_all - IssueStatus.delete_all - Enumeration.delete_all - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fb/fb9aed3d609c2f59022b54e8ad9664c84c259e08.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fb/fb9aed3d609c2f59022b54e8ad9664c84c259e08.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,106 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class IssuePriorityTest < ActiveSupport::TestCase + fixtures :enumerations, :issues + + def test_named_scope + assert_equal Enumeration.find_by_name('Normal'), Enumeration.named('normal').first + end + + def test_default_should_return_the_default_priority + assert_equal Enumeration.find_by_name('Normal'), IssuePriority.default + end + + def test_default_should_return_nil_when_no_default_priority + IssuePriority.update_all :is_default => false + assert_nil IssuePriority.default + end + + def test_should_be_an_enumeration + assert IssuePriority.ancestors.include?(Enumeration) + end + + def test_objects_count + # low priority + assert_equal 6, IssuePriority.find(4).objects_count + # urgent + assert_equal 0, IssuePriority.find(7).objects_count + end + + def test_option_name + assert_equal :enumeration_issue_priorities, IssuePriority.new.option_name + end + + def test_should_be_created_at_last_position + IssuePriority.delete_all + + priorities = [1, 2, 3].map {|i| IssuePriority.create!(:name => "P#{i}")} + assert_equal [1, 2, 3], priorities.map(&:position) + end + + def test_reset_positions_in_list_should_set_sequential_positions + IssuePriority.delete_all + + priorities = [1, 2, 3].map {|i| IssuePriority.create!(:name => "P#{i}")} + priorities[0].update_attribute :position, 4 + priorities[1].update_attribute :position, 2 + priorities[2].update_attribute :position, 7 + assert_equal [4, 2, 7], priorities.map(&:reload).map(&:position) + + priorities[0].reset_positions_in_list + assert_equal [2, 1, 3], priorities.map(&:reload).map(&:position) + end + + def test_moving_in_list_should_reset_positions + priority = IssuePriority.first + priority.expects(:reset_positions_in_list).once + priority.move_to = 'higher' + end + + def test_clear_position_names_should_set_position_names_to_nil + IssuePriority.clear_position_names + assert IssuePriority.all.all? {|priority| priority.position_name.nil?} + end + + def test_compute_position_names_with_default_priority + IssuePriority.clear_position_names + + IssuePriority.compute_position_names + assert_equal %w(lowest default high3 high2 highest), IssuePriority.active.all.sort.map(&:position_name) + end + + def test_compute_position_names_without_default_priority_should_split_priorities + IssuePriority.clear_position_names + IssuePriority.update_all :is_default => false + + IssuePriority.compute_position_names + assert_equal %w(lowest low2 default high2 highest), IssuePriority.active.all.sort.map(&:position_name) + end + + def test_adding_a_priority_should_update_position_names + priority = IssuePriority.create!(:name => 'New') + assert_equal %w(lowest default high4 high3 high2 highest), IssuePriority.active.all.sort.map(&:position_name) + end + + def test_destroying_a_priority_should_update_position_names + IssuePriority.find_by_position_name('highest').destroy + assert_equal %w(lowest default high2 highest), IssuePriority.active.all.sort.map(&:position_name) + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fb/fba522b2ee4403afb33ab25e55c53d41892d5e4b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fb/fba522b2ee4403afb33ab25e55c53d41892d5e4b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,124 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'redmine/scm/adapters/bazaar_adapter' + +class Repository::Bazaar < Repository + attr_protected :root_url + validates_presence_of :url, :log_encoding + + def self.human_attribute_name(attribute_key_name, *args) + attr_name = attribute_key_name.to_s + if attr_name == "url" + attr_name = "path_to_repository" + end + super(attr_name, *args) + end + + def self.scm_adapter_class + Redmine::Scm::Adapters::BazaarAdapter + end + + def self.scm_name + 'Bazaar' + end + + def entry(path=nil, identifier=nil) + scm.bzr_path_encodig = log_encoding + scm.entry(path, identifier) + end + + def cat(path, identifier=nil) + scm.bzr_path_encodig = log_encoding + scm.cat(path, identifier) + end + + def annotate(path, identifier=nil) + scm.bzr_path_encodig = log_encoding + scm.annotate(path, identifier) + end + + def diff(path, rev, rev_to) + scm.bzr_path_encodig = log_encoding + scm.diff(path, rev, rev_to) + end + + def entries(path=nil, identifier=nil) + scm.bzr_path_encodig = log_encoding + entries = scm.entries(path, identifier) + if entries + entries.each do |e| + next if e.lastrev.revision.blank? + # Set the filesize unless browsing a specific revision + if identifier.nil? && e.is_file? + full_path = File.join(root_url, e.path) + e.size = File.stat(full_path).size if File.file?(full_path) + end + c = Change. + includes(:changeset). + where("#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id). + order("#{Changeset.table_name}.revision DESC"). + first + if c + e.lastrev.identifier = c.changeset.revision + e.lastrev.name = c.changeset.revision + e.lastrev.author = c.changeset.committer + end + end + end + load_entries_changesets(entries) + entries + end + + def fetch_changesets + scm.bzr_path_encodig = log_encoding + scm_info = scm.info + if scm_info + # latest revision found in database + db_revision = latest_changeset ? latest_changeset.revision.to_i : 0 + # latest revision in the repository + scm_revision = scm_info.lastrev.identifier.to_i + if db_revision < scm_revision + logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug? + identifier_from = db_revision + 1 + while (identifier_from <= scm_revision) + # loads changesets by batches of 200 + identifier_to = [identifier_from + 199, scm_revision].min + revisions = scm.revisions('', identifier_to, identifier_from) + transaction do + revisions.reverse_each do |revision| + changeset = Changeset.create(:repository => self, + :revision => revision.identifier, + :committer => revision.author, + :committed_on => revision.time, + :scmid => revision.scmid, + :comments => revision.message) + + revision.paths.each do |change| + Change.create(:changeset => changeset, + :action => change[:action], + :path => change[:path], + :revision => change[:revision]) + end + end + end unless revisions.nil? + identifier_from = identifier_to + 1 + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fb/fbc68f3692f8e2cc3c5089591e7d79fb0fb3bac7.svn-base --- a/.svn/pristine/fb/fbc68f3692f8e2cc3c5089591e7d79fb0fb3bac7.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -<%= render :partial => 'action_menu' %> - -

    <%=l(:label_workflow)%>

    - -
    -
      -
    • <%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %>
    • -
    • <%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker} %>
    • -
    -
    - -

    <%=l(:text_workflow_edit)%>:

    - -<%= form_tag({}, :method => 'get') do %> -

    - - - - - <%= submit_tag l(:button_edit), :name => nil %> - - <%= hidden_field_tag 'used_statuses_only', '0' %> - - -

    -<% end %> - -<% if @tracker && @role && @statuses.any? %> - <%= form_tag({}, :id => 'workflow_form' ) do %> - <%= hidden_field_tag 'tracker_id', @tracker.id %> - <%= hidden_field_tag 'role_id', @role.id %> - <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %> -
    - <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %> - -
    - <%= l(:label_additional_workflow_transitions_for_author) %> -
    - <%= render :partial => 'form', :locals => {:name => 'author', :workflows => @workflows['author']} %> -
    -
    - <%= javascript_tag "hideFieldset($('#author_workflows'))" unless @workflows['author'].present? %> - -
    - <%= l(:label_additional_workflow_transitions_for_assignee) %> -
    - <%= render :partial => 'form', :locals => {:name => 'assignee', :workflows => @workflows['assignee']} %> -
    -
    - <%= javascript_tag "hideFieldset($('#assignee_workflows'))" unless @workflows['assignee'].present? %> -
    - <%= submit_tag l(:button_save) %> - <% end %> -<% end %> - -<% html_title(l(:label_workflow)) -%> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fb/fbd9312384b9108653ac87420225c6ac2b8787f0.svn-base --- a/.svn/pristine/fb/fbd9312384b9108653ac87420225c6ac2b8787f0.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../../../test_helper', __FILE__) - -class MenuManagerTest < ActionController::IntegrationTest - include Redmine::I18n - - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows - - def test_project_menu_with_specific_locale - get 'projects/ecookbook/issues', { }, 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' - - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => ll('fr', :label_activity), - :attributes => { :href => '/projects/ecookbook/activity', - :class => 'activity' } } } - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => ll('fr', :label_issue_plural), - :attributes => { :href => '/projects/ecookbook/issues', - :class => 'issues selected' } } } - end - - def test_project_menu_with_additional_menu_items - Setting.default_language = 'en' - assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do - Redmine::MenuManager.map :project_menu do |menu| - menu.push :foo, { :controller => 'projects', :action => 'show' }, :caption => 'Foo' - menu.push :bar, { :controller => 'projects', :action => 'show' }, :before => :activity - menu.push :hello, { :controller => 'projects', :action => 'show' }, :caption => Proc.new {|p| p.name.upcase }, :after => :bar - end - - get 'projects/ecookbook' - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo', - :attributes => { :class => 'foo' } } } - - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar', - :attributes => { :class => 'bar' } }, - :before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } } - - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK', - :attributes => { :class => 'hello' } }, - :before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } } - - # Remove the menu items - Redmine::MenuManager.map :project_menu do |menu| - menu.delete :foo - menu.delete :bar - menu.delete :hello - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fb/fbf038fb26f312343fb26d2a73a682ce24193c01.svn-base --- a/.svn/pristine/fb/fbf038fb26f312343fb26d2a73a682ce24193c01.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -
    <%= l(:label_preview) %> -<%= textilizable @text, :attachments => @attachements, :object => @previewed %> -
    diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fc/fc01cb7f3c8f3eb9966701ee1b49005a54a9c291.svn-base --- a/.svn/pristine/fc/fc01cb7f3c8f3eb9966701ee1b49005a54a9c291.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1081 +0,0 @@ -# Bulgarian translation by Nikolay Solakov and Ivan Cenov -bg: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%d-%m-%Y" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [ÐеделÑ, Понеделник, Вторник, СрÑда, Четвъртък, Петък, Събота] - abbr_day_names: [Ðед, Пон, Вто, СрÑ, Чет, Пет, Съб] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Януари, Февруари, Март, Ðприл, Май, Юни, Юли, ÐвгуÑÑ‚, Септември, Октомври, Ðоември, Декември] - abbr_month_names: [~, Яну, Фев, Мар, Ðпр, Май, Юни, Юли, Ðвг, Сеп, Окт, Ðое, Дек] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%a, %d %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "по-малко от 1 Ñекунда" - other: "по-малко от %{count} Ñекунди" - x_seconds: - one: "1 Ñекунда" - other: "%{count} Ñекунди" - less_than_x_minutes: - one: "по-малко от 1 минута" - other: "по-малко от %{count} минути" - x_minutes: - one: "1 минута" - other: "%{count} минути" - about_x_hours: - one: "около 1 чаÑ" - other: "около %{count} чаÑа" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 ден" - other: "%{count} дена" - about_x_months: - one: "около 1 меÑец" - other: "около %{count} меÑеца" - x_months: - one: "1 меÑец" - other: "%{count} меÑеца" - about_x_years: - one: "около 1 година" - other: "около %{count} години" - over_x_years: - one: "над 1 година" - other: "над %{count} години" - almost_x_years: - one: "почти 1 година" - other: "почти %{count} години" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: байт - other: байта - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "и" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 грешка попречи този %{model} да бъде запиÑан" - other: "%{count} грешки попречиха този %{model} да бъде запиÑан" - messages: - inclusion: "не ÑъщеÑтвува в ÑпиÑъка" - exclusion: "е запазено" - invalid: "е невалидно" - confirmation: "липÑва одобрение" - accepted: "трÑбва да Ñе приеме" - empty: "не може да е празно" - blank: "не може да е празно" - too_long: "е прекалено дълго" - too_short: "е прекалено къÑо" - wrong_length: "е Ñ Ð³Ñ€ÐµÑˆÐ½Ð° дължина" - taken: "вече ÑъщеÑтвува" - not_a_number: "не е чиÑло" - not_a_date: "е невалидна дата" - greater_than: "трÑбва да бъде по-голÑм[a/о] от %{count}" - greater_than_or_equal_to: "трÑбва да бъде по-голÑм[a/о] от или равен[a/o] на %{count}" - equal_to: "трÑбва да бъде равен[a/o] на %{count}" - less_than: "трÑбва да бъде по-малък[a/o] от %{count}" - less_than_or_equal_to: "трÑбва да бъде по-малък[a/o] от или равен[a/o] на %{count}" - odd: "трÑбва да бъде нечетен[a/o]" - even: "трÑбва да бъде четен[a/o]" - greater_than_start_date: "трÑбва да е Ñлед началната дата" - not_same_project: "не е от ÑÑŠÑ‰Ð¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚" - circular_dependency: "Тази Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ Ñ‰Ðµ доведе до безкрайна завиÑимоÑÑ‚" - cant_link_an_issue_with_a_descendant: "Една задача не може да бъде Ñвързвана към ÑÐ²Ð¾Ñ Ð¿Ð¾Ð´Ð·Ð°Ð´Ð°Ñ‡Ð°" - - actionview_instancetag_blank_option: Изберете - - general_text_No: 'Ðе' - general_text_Yes: 'Да' - general_text_no: 'не' - general_text_yes: 'да' - general_lang_name: 'Bulgarian (БългарÑки)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Профилът е обновен уÑпешно. - notice_account_invalid_creditentials: Ðевалиден потребител или парола. - notice_account_password_updated: Паролата е уÑпешно променена. - notice_account_wrong_password: Грешна парола - notice_account_register_done: Профилът е Ñъздаден уÑпешно. - notice_account_unknown_email: Ðепознат e-mail. - notice_can_t_change_password: Този профил е Ñ Ð²ÑŠÐ½ÑˆÐµÐ½ метод за оторизациÑ. Ðевъзможна ÑмÑна на паролата. - notice_account_lost_email_sent: Изпратен ви е e-mail Ñ Ð¸Ð½Ñтрукции за избор на нова парола. - notice_account_activated: Профилът ви е активиран. Вече може да влезете в ÑиÑтемата. - notice_successful_create: УÑпешно Ñъздаване. - notice_successful_update: УÑпешно обновÑване. - notice_successful_delete: УÑпешно изтриване. - notice_successful_connection: УÑпешно Ñвързване. - notice_file_not_found: ÐеÑъщеÑтвуваща или премеÑтена Ñтраница. - notice_locking_conflict: Друг потребител Ð¿Ñ€Ð¾Ð¼ÐµÐ½Ñ Ñ‚ÐµÐ·Ð¸ данни в момента. - notice_not_authorized: ÐÑмате право на доÑтъп до тази Ñтраница. - notice_not_authorized_archived_project: Проектът, който Ñе опитвате да видите е архивиран. Ðко ÑмÑтате, че това не е правилно, обърнете Ñе към админиÑтратора за разархивиране. - notice_email_sent: "Изпратен e-mail на %{value}" - notice_email_error: "Грешка при изпращане на e-mail (%{value})" - notice_feeds_access_key_reseted: Ð’Ð°ÑˆÐ¸Ñ ÐºÐ»ÑŽÑ‡ за RSS доÑтъп беше променен. - notice_api_access_key_reseted: ВашиÑÑ‚ API ключ за доÑтъп беше изчиÑтен. - notice_failed_to_save_issues: "ÐеуÑпешен Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° %{count} задачи от %{total} избрани: %{ids}." - notice_failed_to_save_time_entries: "ÐевъзможноÑÑ‚ за Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° %{count} запиÑа за използвано време от %{total} избрани: %{ids}." - notice_failed_to_save_members: "ÐевъзможноÑÑ‚ за Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° член(ове): %{errors}." - notice_no_issue_selected: "ÐÑма избрани задачи." - notice_account_pending: "Профилът Ви е Ñъздаден и очаква одобрение от админиÑтратор." - notice_default_data_loaded: Примерната Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ðµ заредена уÑпешно. - notice_unable_delete_version: ÐевъзможноÑÑ‚ за изтриване на верÑÐ¸Ñ - notice_unable_delete_time_entry: ÐевъзможноÑÑ‚ за изтриване на Ð·Ð°Ð¿Ð¸Ñ Ð·Ð° използвано време. - notice_issue_done_ratios_updated: Обновен процент на завършените задачи. - notice_gantt_chart_truncated: МрежовиÑÑ‚ график е Ñъкратен, понеже броÑÑ‚ на обектите, които могат да бъдат показани е твърде голÑм (%{max}) - notice_issue_successful_create: Задача %{id} е Ñъздадена. - notice_issue_update_conflict: Задачата е била променена от друг потребител, докато вие Ñте Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð°Ð»Ð¸. - notice_account_deleted: ВашиÑÑ‚ профил беше премахнат без възможноÑÑ‚ за възÑтановÑване. - notice_user_successful_create: Потребител %{id} е Ñъздаден. - - error_can_t_load_default_data: "Грешка при зареждане на примерната информациÑ: %{value}" - error_scm_not_found: ÐеÑъщеÑтвуващ обект в хранилището. - error_scm_command_failed: "Грешка при опит за ÐºÐ¾Ð¼ÑƒÐ½Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ðµ: %{value}" - error_scm_annotate: "Обектът не ÑъщеÑтвува или не може да бъде анотиран." - error_scm_annotate_big_text_file: "Файлът не може да бъде анотиран, понеже Ð½Ð°Ð´Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¼Ð°ÐºÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€ за текÑтови файлове." - error_issue_not_found_in_project: 'Задачата не е намерена или не принадлежи на този проект' - error_no_tracker_in_project: ÐÑма аÑоциирани тракери Ñ Ñ‚Ð¾Ð·Ð¸ проект. Проверете наÑтройките на проекта. - error_no_default_issue_status: ÐÑма уÑтановено подразбиращо Ñе ÑÑŠÑтоÑние за задачите. ÐœÐ¾Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐµÑ‚Ðµ вашата ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ (Вижте "ÐдминиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ -> СъÑтоÑÐ½Ð¸Ñ Ð½Ð° задачи"). - error_can_not_delete_custom_field: ÐевъзможноÑÑ‚ за изтриване на потребителÑко поле - error_can_not_delete_tracker: Този тракер Ñъдържа задачи и не може да бъде изтрит. - error_can_not_remove_role: Тази Ñ€Ð¾Ð»Ñ Ñе използва и не може да бъде изтрита. - error_can_not_reopen_issue_on_closed_version: Задача, аÑоциирана ÑÑŠÑ Ð·Ð°Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð° верÑÐ¸Ñ Ð½Ðµ може да бъде отворена отново - error_can_not_archive_project: Този проект не може да бъде архивиран - error_issue_done_ratios_not_updated: Процентът на завършените задачи не е обновен. - error_workflow_copy_source: ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ source тракер или Ñ€Ð¾Ð»Ñ - error_workflow_copy_target: ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ тракер(и) и Ñ€Ð¾Ð»Ñ (роли). - error_unable_delete_issue_status: ÐевъзможноÑÑ‚ за изтриване на ÑÑŠÑтоÑние на задача - error_unable_to_connect: ÐевъзможноÑÑ‚ за Ñвързване Ñ (%{value}) - error_attachment_too_big: Този файл не може да бъде качен, понеже Ð½Ð°Ð´Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¼Ð°ÐºÑималната възможна големина (%{max_size}) - error_session_expired: Вашата ÑеÑÐ¸Ñ Ðµ изтекла. ÐœÐ¾Ð»Ñ Ð²Ð»ÐµÐ·ÐµÑ‚Ðµ в Redmine отново. - warning_attachments_not_saved: "%{count} файла не бÑха запиÑани." - - mail_subject_lost_password: "Вашата парола (%{value})" - mail_body_lost_password: 'За да Ñмените паролата Ñи, използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð»Ð¸Ð½Ðº:' - mail_subject_register: "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð½Ð° профил (%{value})" - mail_body_register: 'За да активирате профила Ñи използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð»Ð¸Ð½Ðº:' - mail_body_account_information_external: "Можете да използвате Ð²Ð°ÑˆÐ¸Ñ %{value} профил за вход." - mail_body_account_information: ИнформациÑта за профила ви - mail_subject_account_activation_request: "ЗаÑвка за активиране на профил в %{value}" - mail_body_account_activation_request: "Има новорегиÑтриран потребител (%{value}), очакващ вашето одобрение:" - mail_subject_reminder: "%{count} задачи Ñ ÐºÑ€Ð°ÐµÐ½ Ñрок Ñ Ñледващите %{days} дни" - mail_body_reminder: "%{count} задачи, назначени на Ð²Ð°Ñ Ñа Ñ ÐºÑ€Ð°ÐµÐ½ Ñрок в Ñледващите %{days} дни:" - mail_subject_wiki_content_added: "Wiki Ñтраницата '%{id}' беше добавена" - mail_body_wiki_content_added: Wiki Ñтраницата '%{id}' беше добавена от %{author}. - mail_subject_wiki_content_updated: "Wiki Ñтраницата '%{id}' беше обновена" - mail_body_wiki_content_updated: Wiki Ñтраницата '%{id}' беше обновена от %{author}. - - gui_validation_error: 1 грешка - gui_validation_error_plural: "%{count} грешки" - - field_name: Име - field_description: ОпиÑание - field_summary: ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ - field_is_required: Задължително - field_firstname: Име - field_lastname: Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ - field_mail: Email - field_filename: Файл - field_filesize: Големина - field_downloads: Изтеглени файлове - field_author: Ðвтор - field_created_on: От дата - field_updated_on: Обновена - field_field_format: Тип - field_is_for_all: За вÑички проекти - field_possible_values: Възможни ÑтойноÑти - field_regexp: РегулÑрен израз - field_min_length: Мин. дължина - field_max_length: МакÑ. дължина - field_value: СтойноÑÑ‚ - field_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - field_title: Заглавие - field_project: Проект - field_issue: Задача - field_status: СъÑтоÑние - field_notes: Бележка - field_is_closed: Затворена задача - field_is_default: СъÑтоÑние по подразбиране - field_tracker: Тракер - field_subject: ОтноÑно - field_due_date: Крайна дата - field_assigned_to: Възложена на - field_priority: Приоритет - field_fixed_version: Планувана верÑÐ¸Ñ - field_user: Потребител - field_principal: Principal - field_role: Ð Ð¾Ð»Ñ - field_homepage: Ðачална Ñтраница - field_is_public: Публичен - field_parent: Подпроект на - field_is_in_roadmap: Да Ñе вижда ли в Пътна карта - field_login: Потребител - field_mail_notification: ИзвеÑÑ‚Ð¸Ñ Ð¿Ð¾ пощата - field_admin: ÐдминиÑтратор - field_last_login_on: ПоÑледно Ñвързване - field_language: Език - field_effective_date: Дата - field_password: Парола - field_new_password: Ðова парола - field_password_confirmation: Потвърждение - field_version: ВерÑÐ¸Ñ - field_type: Тип - field_host: ХоÑÑ‚ - field_port: Порт - field_account: Профил - field_base_dn: Base DN - field_attr_login: Ðтрибут Login - field_attr_firstname: Ðтрибут Първо име (Firstname) - field_attr_lastname: Ðтрибут Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ (Lastname) - field_attr_mail: Ðтрибут Email - field_onthefly: Динамично Ñъздаване на потребител - field_start_date: Ðачална дата - field_done_ratio: "% ПрогреÑ" - field_auth_source: Ðачин на Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - field_hide_mail: Скрий e-mail адреÑа ми - field_comments: Коментар - field_url: ÐÐ´Ñ€ÐµÑ - field_start_page: Ðачална Ñтраница - field_subproject: Подпроект - field_hours: ЧаÑове - field_activity: ДейноÑÑ‚ - field_spent_on: Дата - field_identifier: Идентификатор - field_is_filter: Използва Ñе за филтър - field_issue_to: Свързана задача - field_delay: ОтмеÑтване - field_assignable: Възможно е възлагане на задачи за тази Ñ€Ð¾Ð»Ñ - field_redirect_existing_links: ПренаÑочване на ÑъщеÑтвуващи линкове - field_estimated_hours: ИзчиÑлено време - field_column_names: Колони - field_time_entries: Log time - field_time_zone: ЧаÑова зона - field_searchable: С възможноÑÑ‚ за търÑене - field_default_value: СтойноÑÑ‚ по подразбиране - field_comments_sorting: Сортиране на коментарите - field_parent_title: РодителÑка Ñтраница - field_editable: Editable - field_watcher: Ðаблюдател - field_identity_url: OpenID URL - field_content: Съдържание - field_group_by: Групиране на резултатите по - field_sharing: Sharing - field_parent_issue: РодителÑка задача - field_member_of_group: Член на група - field_assigned_to_role: Assignee's role - field_text: ТекÑтово поле - field_visible: Видим - field_warn_on_leaving_unsaved: Предупреди ме, когато напуÑкам Ñтраница Ñ Ð½ÐµÐ·Ð°Ð¿Ð¸Ñано Ñъдържание - field_issues_visibility: ВидимоÑÑ‚ на задачите - field_is_private: Лична - field_commit_logs_encoding: Кодова таблица на ÑъобщениÑта при поверÑване - field_scm_path_encoding: Кодова таблица на пътищата (path) - field_path_to_repository: Път до хранилището - field_root_directory: Коренна Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ (папка) - field_cvsroot: CVSROOT - field_cvs_module: Модул - field_repository_is_default: Главно хранилище - field_multiple: Избор на повече от една ÑтойноÑÑ‚ - field_auth_source_ldap_filter: LDAP филтър - field_core_fields: Стандартни полета - field_timeout: Таймаут (в Ñекунди) - field_board_parent: РодителÑки форум - field_private_notes: Лични бележки - - setting_app_title: Заглавие - setting_app_subtitle: ОпиÑание - setting_welcome_text: Допълнителен текÑÑ‚ - setting_default_language: Език по подразбиране - setting_login_required: ИзиÑкване за вход в ÑиÑтемата - setting_self_registration: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¾Ñ‚ потребители - setting_attachment_max_size: МакÑимална големина на прикачен файл - setting_issues_export_limit: МакÑимален брой задачи за екÑпорт - setting_mail_from: E-mail Ð°Ð´Ñ€ÐµÑ Ð·Ð° емиÑии - setting_bcc_recipients: Получатели на Ñкрито копие (bcc) - setting_plain_text_mail: Ñамо чиÑÑ‚ текÑÑ‚ (без HTML) - setting_host_name: ХоÑÑ‚ - setting_text_formatting: Форматиране на текÑта - setting_wiki_compression: КомпреÑиране на Wiki иÑториÑта - setting_feeds_limit: МакÑимален брой запиÑи в ATOM емиÑии - setting_default_projects_public: Ðовите проекти Ñа публични по подразбиране - setting_autofetch_changesets: Ðвтоматично извличане на ревизиите - setting_sys_api_enabled: Разрешаване на WS за управление - setting_commit_ref_keywords: ОтбелÑзващи ключови думи - setting_commit_fix_keywords: Приключващи ключови думи - setting_autologin: Ðвтоматичен вход - setting_date_format: Формат на датата - setting_time_format: Формат на чаÑа - setting_cross_project_issue_relations: Релации на задачи между проекти - setting_cross_project_subtasks: Подзадачи от други проекти - setting_issue_list_default_columns: Показвани колони по подразбиране - setting_repositories_encodings: Кодова таблица на прикачените файлове и хранилищата - setting_emails_header: Emails header - setting_emails_footer: ПодтекÑÑ‚ за e-mail - setting_protocol: Протокол - setting_per_page_options: Опции за Ñтраниране - setting_user_format: ПотребителÑки формат - setting_activity_days_default: Брой дни показвани на таб ДейноÑÑ‚ - setting_display_subprojects_issues: Задачите от подпроектите по подразбиране Ñе показват в главните проекти - setting_enabled_scm: Разрешена SCM - setting_mail_handler_body_delimiters: ОтрÑзване на e-mail-ите Ñлед един от тези редове - setting_mail_handler_api_enabled: Разрешаване на WS за входÑщи e-mail-и - setting_mail_handler_api_key: API ключ - setting_sequential_project_identifiers: Генериране на поÑледователни проектни идентификатори - setting_gravatar_enabled: Използване на портребителÑки икони от Gravatar - setting_gravatar_default: Подразбиращо Ñе изображение от Gravatar - setting_diff_max_lines_displayed: МакÑимален брой показвани diff редове - setting_file_max_size_displayed: МакÑимален размер на текÑтовите файлове, показвани inline - setting_repository_log_display_limit: МакÑимален брой на показванете ревизии в лог файла - setting_openid: Рарешаване на OpenID вход и региÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - setting_password_min_length: Минимална дължина на парола - setting_new_project_user_role_id: РолÑ, давана на потребител, Ñъздаващ проекти, който не е админиÑтратор - setting_default_projects_modules: Ðктивирани модули по подразбиране за нов проект - setting_issue_done_ratio: ИзчиÑление на процента на готови задачи Ñ - setting_issue_done_ratio_issue_field: Използване на поле '% ПрогреÑ' - setting_issue_done_ratio_issue_status: Използване на ÑÑŠÑтоÑнието на задачите - setting_start_of_week: Първи ден на Ñедмицата - setting_rest_api_enabled: Разрешаване на REST web ÑÑŠÑ€Ð²Ð¸Ñ - setting_cache_formatted_text: Кеширане на форматираните текÑтове - setting_default_notification_option: Подразбиращ Ñе начин за извеÑÑ‚Ñване - setting_commit_logtime_enabled: Разрешаване на отчитането на работното време - setting_commit_logtime_activity_id: ДейноÑÑ‚ при отчитане на работното време - setting_gantt_items_limit: МакÑимален брой обекти, които да Ñе показват в мрежов график - setting_issue_group_assignment: Разрешено назначаването на задачи на групи - setting_default_issue_start_date_to_creation_date: Ðачална дата на новите задачи по подразбиране да бъде днешната дата - setting_commit_cross_project_ref: ОтбелÑзване и приключване на задачи от други проекти, неÑвързани Ñ ÐºÐ¾Ð½ÐºÑ€ÐµÑ‚Ð½Ð¾Ñ‚Ð¾ хранилище - setting_unsubscribe: Потребителите могат да премахват профилите Ñи - setting_session_lifetime: МакÑимален живот на ÑеÑиите - setting_session_timeout: Таймаут за неактивноÑÑ‚ преди прекратÑване на ÑеÑиите - setting_thumbnails_enabled: Показване на миниатюри на прикачените Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ - setting_thumbnails_size: Размер на миниатюрите (в пикÑели) - setting_non_working_week_days: Ðе работни дни - - permission_add_project: Създаване на проект - permission_add_subprojects: Създаване на подпроекти - permission_edit_project: Редактиране на проект - permission_close_project: ЗатварÑне / отварÑне на проект - permission_select_project_modules: Избор на проектни модули - permission_manage_members: Управление на членовете (на екип) - permission_manage_project_activities: Управление на дейноÑтите на проекта - permission_manage_versions: Управление на верÑиите - permission_manage_categories: Управление на категориите - permission_view_issues: Разглеждане на задачите - permission_add_issues: ДобавÑне на задачи - permission_edit_issues: Редактиране на задачи - permission_manage_issue_relations: Управление на връзките между задачите - permission_set_own_issues_private: УÑтановÑване на ÑобÑтвените задачи публични или лични - permission_set_issues_private: УÑтановÑване на задачите публични или лични - permission_add_issue_notes: ДобавÑне на бележки - permission_edit_issue_notes: Редактиране на бележки - permission_edit_own_issue_notes: Редактиране на ÑобÑтвени бележки - permission_view_private_notes: Разглеждане на лични бележки - permission_set_notes_private: УÑтановÑване на бележките лични - permission_move_issues: ПремеÑтване на задачи - permission_delete_issues: Изтриване на задачи - permission_manage_public_queries: Управление на публичните заÑвки - permission_save_queries: Ð—Ð°Ð¿Ð¸Ñ Ð½Ð° Ð·Ð°Ð¿Ð¸Ñ‚Ð²Ð°Ð½Ð¸Ñ (queries) - permission_view_gantt: Разглеждане на мрежов график - permission_view_calendar: Разглеждане на календари - permission_view_issue_watchers: Разглеждане на ÑпиÑък Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°Ñ‚ÐµÐ»Ð¸ - permission_add_issue_watchers: ДобавÑне на наблюдатели - permission_delete_issue_watchers: Изтриване на наблюдатели - permission_log_time: Log spent time - permission_view_time_entries: Разглеждане на изразходваното време - permission_edit_time_entries: Редактиране на time logs - permission_edit_own_time_entries: Редактиране на ÑобÑтвените time logs - permission_manage_news: Управление на новини - permission_comment_news: Коментиране на новини - permission_manage_documents: Управление на документи - permission_view_documents: Разглеждане на документи - permission_manage_files: Управление на файлове - permission_view_files: Разглеждане на файлове - permission_manage_wiki: Управление на wiki - permission_rename_wiki_pages: Преименуване на wiki Ñтраници - permission_delete_wiki_pages: Изтриване на wiki Ñтраници - permission_view_wiki_pages: Разглеждане на wiki - permission_view_wiki_edits: Разглеждане на wiki иÑÑ‚Ð¾Ñ€Ð¸Ñ - permission_edit_wiki_pages: Редактиране на wiki Ñтраници - permission_delete_wiki_pages_attachments: Изтриване на прикачени файлове към wiki Ñтраници - permission_protect_wiki_pages: Заключване на wiki Ñтраници - permission_manage_repository: Управление на хранилища - permission_browse_repository: Разглеждане на хранилища - permission_view_changesets: Разглеждане на changesets - permission_commit_access: ПоверÑване - permission_manage_boards: Управление на boards - permission_view_messages: Разглеждане на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_add_messages: Публикуване на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_edit_messages: Редактиране на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_edit_own_messages: Редактиране на ÑобÑтвени ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_delete_messages: Изтриване на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_delete_own_messages: Изтриване на ÑобÑтвени ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_export_wiki_pages: ЕкÑпорт на wiki Ñтраници - permission_manage_subtasks: Управление на подзадачите - permission_manage_related_issues: Управление на връзките между задачи и ревизии - - project_module_issue_tracking: Тракинг - project_module_time_tracking: ОтделÑне на време - project_module_news: Ðовини - project_module_documents: Документи - project_module_files: Файлове - project_module_wiki: Wiki - project_module_repository: Хранилище - project_module_boards: Форуми - project_module_calendar: Календар - project_module_gantt: Мрежов график - - label_user: Потребител - label_user_plural: Потребители - label_user_new: Ðов потребител - label_user_anonymous: Ðнонимен - label_project: Проект - label_project_new: Ðов проект - label_project_plural: Проекти - label_x_projects: - zero: 0 проекта - one: 1 проект - other: "%{count} проекта" - label_project_all: Ð’Ñички проекти - label_project_latest: ПоÑледни проекти - label_issue: Задача - label_issue_new: Ðова задача - label_issue_plural: Задачи - label_issue_view_all: Ð’Ñички задачи - label_issues_by: "Задачи по %{value}" - label_issue_added: Добавена задача - label_issue_updated: Обновена задача - label_issue_note_added: Добавена бележка - label_issue_status_updated: Обновено ÑÑŠÑтоÑние - label_issue_priority_updated: Обновен приоритет - label_document: Документ - label_document_new: Ðов документ - label_document_plural: Документи - label_document_added: Добавен документ - label_role: Ð Ð¾Ð»Ñ - label_role_plural: Роли - label_role_new: Ðова Ñ€Ð¾Ð»Ñ - label_role_and_permissions: Роли и права - label_role_anonymous: Ðнонимен - label_role_non_member: Ðе член - label_member: Член - label_member_new: Ðов член - label_member_plural: Членове - label_tracker: Тракер - label_tracker_plural: Тракери - label_tracker_new: Ðов тракер - label_workflow: Работен Ð¿Ñ€Ð¾Ñ†ÐµÑ - label_issue_status: СъÑтоÑние на задача - label_issue_status_plural: СъÑтоÑÐ½Ð¸Ñ Ð½Ð° задачи - label_issue_status_new: Ðово ÑÑŠÑтоÑние - label_issue_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° - label_issue_category_plural: Категории задачи - label_issue_category_new: Ðова ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - label_custom_field: ПотребителÑко поле - label_custom_field_plural: ПотребителÑки полета - label_custom_field_new: Ðово потребителÑко поле - label_enumerations: СпиÑъци - label_enumeration_new: Ðова ÑтойноÑÑ‚ - label_information: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ - label_information_plural: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ - label_please_login: Вход - label_register: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - label_login_with_open_id_option: или вход чрез OpenID - label_password_lost: Забравена парола - label_home: Ðачало - label_my_page: Лична Ñтраница - label_my_account: Профил - label_my_projects: Проекти, в които учаÑтвам - label_my_page_block: Блокове в личната Ñтраница - label_administration: ÐдминиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - label_login: Вход - label_logout: Изход - label_help: Помощ - label_reported_issues: Публикувани задачи - label_assigned_to_me_issues: Възложени на мен - label_last_login: ПоÑледно Ñвързване - label_registered_on: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - label_activity: ДейноÑÑ‚ - label_overall_activity: ЦÑлоÑтна дейноÑÑ‚ - label_user_activity: "ÐктивноÑÑ‚ на %{value}" - label_new: Ðов - label_logged_as: Здравейте, - label_environment: Среда - label_authentication: ÐžÑ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - label_auth_source: Ðачин на Ð¾Ñ‚Ð¾Ñ€Ð¾Ð·Ð°Ñ†Ð¸Ñ - label_auth_source_new: Ðов начин на Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - label_auth_source_plural: Ðачини на Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - label_subproject_plural: Подпроекти - label_subproject_new: Ðов подпроект - label_and_its_subprojects: "%{value} и неговите подпроекти" - label_min_max_length: Минимална - макÑимална дължина - label_list: СпиÑък - label_date: Дата - label_integer: ЦелочиÑлен - label_float: Дробно - label_boolean: Ð§ÐµÐºÐ±Ð¾ÐºÑ - label_string: ТекÑÑ‚ - label_text: Дълъг текÑÑ‚ - label_attribute: Ðтрибут - label_attribute_plural: Ðтрибути - label_download: "%{count} изтеглÑне" - label_download_plural: "%{count} изтеглÑниÑ" - label_no_data: ÐÑма изходни данни - label_change_status: ПромÑна на ÑÑŠÑтоÑнието - label_history: ИÑÑ‚Ð¾Ñ€Ð¸Ñ - label_attachment: Файл - label_attachment_new: Ðов файл - label_attachment_delete: Изтриване - label_attachment_plural: Файлове - label_file_added: Добавен файл - label_report: Справка - label_report_plural: Справки - label_news: Ðовини - label_news_new: Добави - label_news_plural: Ðовини - label_news_latest: ПоÑледни новини - label_news_view_all: Виж вÑички - label_news_added: Добавена новина - label_news_comment_added: Добавен коментар към новина - label_settings: ÐаÑтройки - label_overview: Общ изглед - label_version: ВерÑÐ¸Ñ - label_version_new: Ðова верÑÐ¸Ñ - label_version_plural: ВерÑии - label_close_versions: ЗатварÑне на завършените верÑии - label_confirmation: Одобрение - label_export_to: ЕкÑпорт към - label_read: Read... - label_public_projects: Публични проекти - label_open_issues: отворена - label_open_issues_plural: отворени - label_closed_issues: затворена - label_closed_issues_plural: затворени - label_x_open_issues_abbr_on_total: - zero: 0 отворени / %{total} - one: 1 отворена / %{total} - other: "%{count} отворени / %{total}" - label_x_open_issues_abbr: - zero: 0 отворени - one: 1 отворена - other: "%{count} отворени" - label_x_closed_issues_abbr: - zero: 0 затворени - one: 1 затворена - other: "%{count} затворени" - label_x_issues: - zero: 0 задачи - one: 1 задача - other: "%{count} задачи" - label_total: Общо - label_permissions: Права - label_current_status: Текущо ÑÑŠÑтоÑние - label_new_statuses_allowed: Позволени ÑÑŠÑтоÑÐ½Ð¸Ñ - label_all: вÑички - label_any: коÑто и да е - label_none: никакви - label_nobody: никой - label_next: Следващ - label_previous: Предишен - label_used_by: Използва Ñе от - label_details: Детайли - label_add_note: ДобавÑне на бележка - label_per_page: Ðа Ñтраница - label_calendar: Календар - label_months_from: меÑеца от - label_gantt: Мрежов график - label_internal: Вътрешен - label_last_changes: "поÑледни %{count} промени" - label_change_view_all: Виж вÑички промени - label_personalize_page: ПерÑонализиране - label_comment: Коментар - label_comment_plural: Коментари - label_x_comments: - zero: 0 коментара - one: 1 коментар - other: "%{count} коментара" - label_comment_add: ДобавÑне на коментар - label_comment_added: Добавен коментар - label_comment_delete: Изтриване на коментари - label_query: ПотребителÑка Ñправка - label_query_plural: ПотребителÑки Ñправки - label_query_new: Ðова заÑвка - label_my_queries: Моите заÑвки - label_filter_add: Добави филтър - label_filter_plural: Филтри - label_equals: е - label_not_equals: не е - label_in_less_than: Ñлед по-малко от - label_in_more_than: Ñлед повече от - label_in_the_next_days: в Ñледващите - label_in_the_past_days: в предишните - label_greater_or_equal: ">=" - label_less_or_equal: <= - label_between: между - label_in: в Ñледващите - label_today: Ð´Ð½ÐµÑ - label_all_time: вÑички - label_yesterday: вчера - label_this_week: тази Ñедмица - label_last_week: поÑледната Ñедмица - label_last_n_weeks: поÑледните %{count} Ñедмици - label_last_n_days: "поÑледните %{count} дни" - label_this_month: Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ð¼ÐµÑец - label_last_month: поÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð¼ÐµÑец - label_this_year: текущата година - label_date_range: Период - label_less_than_ago: преди по-малко от - label_more_than_ago: преди повече от - label_ago: преди - label_contains: Ñъдържа - label_not_contains: не Ñъдържа - label_any_issues_in_project: задачи от проект - label_any_issues_not_in_project: задачи, които не Ñа в проект - label_no_issues_in_project: никакви задачи в проект - label_day_plural: дни - label_repository: Хранилище - label_repository_new: Ðово хранилище - label_repository_plural: Хранилища - label_browse: Разглеждане - label_modification: "%{count} промÑна" - label_modification_plural: "%{count} промени" - label_branch: работен вариант - label_tag: ВерÑÐ¸Ñ - label_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ - label_revision_plural: Ревизии - label_revision_id: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ %{value} - label_associated_revisions: ÐÑоциирани ревизии - label_added: добавено - label_modified: променено - label_copied: копирано - label_renamed: преименувано - label_deleted: изтрито - label_latest_revision: ПоÑледна Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ - label_latest_revision_plural: ПоÑледни ревизии - label_view_revisions: Виж ревизиите - label_view_all_revisions: Разглеждане на вÑички ревизии - label_max_size: МакÑимална големина - label_sort_highest: ПремеÑти най-горе - label_sort_higher: ПремеÑти по-горе - label_sort_lower: ПремеÑти по-долу - label_sort_lowest: ПремеÑти най-долу - label_roadmap: Пътна карта - label_roadmap_due_in: "Излиза Ñлед %{value}" - label_roadmap_overdue: "%{value} закъÑнение" - label_roadmap_no_issues: ÐÑма задачи за тази верÑÐ¸Ñ - label_search: ТърÑене - label_result_plural: Pезултати - label_all_words: Ð’Ñички думи - label_wiki: Wiki - label_wiki_edit: Wiki Ñ€ÐµÐ´Ð°ÐºÑ†Ð¸Ñ - label_wiki_edit_plural: Wiki редакции - label_wiki_page: Wiki Ñтраница - label_wiki_page_plural: Wiki Ñтраници - label_index_by_title: Ð˜Ð½Ð´ÐµÐºÑ - label_index_by_date: Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ð¾ дата - label_current_version: Текуща верÑÐ¸Ñ - label_preview: Преглед - label_feed_plural: ЕмиÑии - label_changes_details: Подробни промени - label_issue_tracking: Тракинг - label_spent_time: Отделено време - label_overall_spent_time: Общо употребено време - label_f_hour: "%{value} чаÑ" - label_f_hour_plural: "%{value} чаÑа" - label_time_tracking: ОтделÑне на време - label_change_plural: Промени - label_statistics: СтатиÑтики - label_commits_per_month: Ревизии по меÑеци - label_commits_per_author: Ревизии по автор - label_diff: diff - label_view_diff: Виж разликите - label_diff_inline: хоризонтално - label_diff_side_by_side: вертикално - label_options: Опции - label_copy_workflow_from: Копирай Ñ€Ð°Ð±Ð¾Ñ‚Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð¾Ñ‚ - label_permissions_report: Справка за права - label_watched_issues: Ðаблюдавани задачи - label_related_issues: Свързани задачи - label_applied_status: УÑтановено ÑÑŠÑтоÑние - label_loading: Зареждане... - label_relation_new: Ðова Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ - label_relation_delete: Изтриване на Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ - label_relates_to: Ñвързана ÑÑŠÑ - label_duplicates: дублира - label_duplicated_by: дублирана от - label_blocks: блокира - label_blocked_by: блокирана от - label_precedes: предшеÑтва - label_follows: изпълнÑва Ñе Ñлед - label_copied_to: копирана в - label_copied_from: копирана от - label_end_to_start: край към начало - label_end_to_end: край към край - label_start_to_start: начало към начало - label_start_to_end: начало към край - label_stay_logged_in: Запомни ме - label_disabled: забранено - label_show_completed_versions: Показване на реализирани верÑии - label_me: аз - label_board: Форум - label_board_new: Ðов форум - label_board_plural: Форуми - label_board_locked: Заключена - label_board_sticky: Sticky - label_topic_plural: Теми - label_message_plural: Ð¡ÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - label_message_last: ПоÑледно Ñъобщение - label_message_new: Ðова тема - label_message_posted: Добавено Ñъобщение - label_reply_plural: Отговори - label_send_information: Изпращане на информациÑта до Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ - label_year: Година - label_month: МеÑец - label_week: Седмица - label_date_from: От - label_date_to: До - label_language_based: Ð’ завиÑимоÑÑ‚ от езика - label_sort_by: "Сортиране по %{value}" - label_send_test_email: Изпращане на теÑтов e-mail - label_feeds_access_key: RSS access ключ - label_missing_feeds_access_key: ЛипÑващ RSS ключ за доÑтъп - label_feeds_access_key_created_on: "%{value} от Ñъздаването на RSS ключа" - label_module_plural: Модули - label_added_time_by: "Публикувана от %{author} преди %{age}" - label_updated_time_by: "Обновена от %{author} преди %{age}" - label_updated_time: "Обновена преди %{value}" - label_jump_to_a_project: Проект... - label_file_plural: Файлове - label_changeset_plural: Ревизии - label_default_columns: По подразбиране - label_no_change_option: (Без промÑна) - label_bulk_edit_selected_issues: Групово редактиране на задачи - label_bulk_edit_selected_time_entries: Групово редактиране на запиÑи за използвано време - label_theme: Тема - label_default: По подразбиране - label_search_titles_only: Само в заглавиÑта - label_user_mail_option_all: "За вÑÑко Ñъбитие в проектите, в които учаÑтвам" - label_user_mail_option_selected: "За вÑички ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ñамо в избраните проекти..." - label_user_mail_option_none: "Само за наблюдавани или в които учаÑтвам (автор или назначени на мен)" - label_user_mail_option_only_my_events: Само за неща, в които Ñъм включен/а - label_user_mail_option_only_assigned: Само за неща, назначени на мен - label_user_mail_option_only_owner: Само за неща, на които аз Ñъм ÑобÑтвеник - label_user_mail_no_self_notified: "Ðе иÑкам извеÑÑ‚Ð¸Ñ Ð·Ð° извършени от мен промени" - label_registration_activation_by_email: активиране на профила по email - label_registration_manual_activation: ръчно активиране - label_registration_automatic_activation: автоматично активиране - label_display_per_page: "Ðа Ñтраница по: %{value}" - label_age: ВъзраÑÑ‚ - label_change_properties: ПромÑна на наÑтройки - label_general: ОÑновни - label_more: Още - label_scm: SCM (СиÑтема за контрол на верÑиите) - label_plugins: Плъгини - label_ldap_authentication: LDAP Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - label_downloads_abbr: D/L - label_optional_description: Ðезадължително опиÑание - label_add_another_file: ДобавÑне на друг файл - label_preferences: ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ð¸Ñ‚Ð°Ð½Ð¸Ñ - label_chronological_order: Хронологичен ред - label_reverse_chronological_order: Обратен хронологичен ред - label_planning: Планиране - label_incoming_emails: ВходÑщи e-mail-и - label_generate_key: Генериране на ключ - label_issue_watchers: Ðаблюдатели - label_example: Пример - label_display: Показване - label_sort: Сортиране - label_ascending: ÐараÑтващ - label_descending: ÐамалÑващ - label_date_from_to: От %{start} до %{end} - label_wiki_content_added: Wiki Ñтраница беше добавена - label_wiki_content_updated: Wiki Ñтраница беше обновена - label_group: Група - label_group_plural: Групи - label_group_new: Ðова група - label_time_entry_plural: Използвано време - label_version_sharing_none: Ðе Ñподелен - label_version_sharing_descendants: С подпроекти - label_version_sharing_hierarchy: С проектна Ð¹ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ - label_version_sharing_tree: С дърво на проектите - label_version_sharing_system: С вÑички проекти - label_update_issue_done_ratios: ОбновÑване на процента на завършените задачи - label_copy_source: Източник - label_copy_target: Цел - label_copy_same_as_target: Също като целта - label_display_used_statuses_only: Показване Ñамо на ÑÑŠÑтоÑниÑта, използвани от този тракер - label_api_access_key: API ключ за доÑтъп - label_missing_api_access_key: ЛипÑващ API ключ - label_api_access_key_created_on: API ключ за доÑтъп е Ñъздаден преди %{value} - label_profile: Профил - label_subtask_plural: Подзадачи - label_project_copy_notifications: Изпращане на Send e-mail извеÑÑ‚Ð¸Ñ Ð¿Ð¾ време на копирането на проекта - label_principal_search: "ТърÑене на потребител или група:" - label_user_search: "ТърÑене на потребител:" - label_additional_workflow_transitions_for_author: Позволени Ñа допълнителни преходи, когато потребителÑÑ‚ е авторът - label_additional_workflow_transitions_for_assignee: Позволени Ñа допълнителни преходи, когато потребителÑÑ‚ е назначениÑÑ‚ към задачата - label_issues_visibility_all: Ð’Ñички задачи - label_issues_visibility_public: Ð’Ñички не-лични задачи - label_issues_visibility_own: Задачи, Ñъздадени от или назначени на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ - label_git_report_last_commit: Извеждане на поÑледното поверÑване за файлове и папки - label_parent_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ» - label_child_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ Ð½Ð°Ñледник - label_export_options: "%{export_format} опции за екÑпорт" - label_copy_attachments: Копиране на прикачените файлове - label_copy_subtasks: Копиране на подзадачите - label_item_position: "%{position}/%{count}" - label_completed_versions: Завършени верÑии - label_search_for_watchers: ТърÑене на потребители за наблюдатели - label_session_expiration: Изтичане на ÑеÑиите - label_show_closed_projects: Разглеждане на затворени проекти - label_status_transitions: Преходи между ÑÑŠÑтоÑниÑта - label_fields_permissions: ВидимоÑÑ‚ на полетата - label_readonly: Само за четене - label_required: Задължително - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_cross_project_descendants: С подпроекти - label_cross_project_tree: С дърво на проектите - label_cross_project_hierarchy: С проектна Ð¹ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ - label_cross_project_system: С вÑички проекти - - button_login: Вход - button_submit: Изпращане - button_save: Ð—Ð°Ð¿Ð¸Ñ - button_check_all: Избор на вÑички - button_uncheck_all: ИзчиÑтване на вÑички - button_collapse_all: Скриване вÑички - button_expand_all: Разгъване вÑички - button_delete: Изтриване - button_create: Създаване - button_create_and_continue: Създаване и продължаване - button_test: ТеÑÑ‚ - button_edit: Ð ÐµÐ´Ð°ÐºÑ†Ð¸Ñ - button_edit_associated_wikipage: "Редактиране на аÑоциираната Wiki Ñтраница: %{page_title}" - button_add: ДобавÑне - button_change: ПромÑна - button_apply: Приложи - button_clear: ИзчиÑти - button_lock: Заключване - button_unlock: Отключване - button_download: ИзтеглÑне - button_list: СпиÑък - button_view: Преглед - button_move: ПремеÑтване - button_move_and_follow: ПремеÑтване и продължаване - button_back: Ðазад - button_cancel: Отказ - button_activate: ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ - button_sort: Сортиране - button_log_time: ОтделÑне на време - button_rollback: Върни Ñе към тази Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ - button_watch: Ðаблюдаване - button_unwatch: Край на наблюдението - button_reply: Отговор - button_archive: Ðрхивиране - button_unarchive: Разархивиране - button_reset: Генериране наново - button_rename: Преименуване - button_change_password: ПромÑна на парола - button_copy: Копиране - button_copy_and_follow: Копиране и продължаване - button_annotate: ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ - button_update: ОбновÑване - button_configure: Конфигуриране - button_quote: Цитат - button_duplicate: Дублиране - button_show: Показване - button_hide: Скриване - button_edit_section: Редактиране на тази ÑÐµÐºÑ†Ð¸Ñ - button_export: ЕкÑпорт - button_delete_my_account: Премахване на Ð¼Ð¾Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð» - button_close: ЗатварÑне - button_reopen: ОтварÑне - - status_active: активен - status_registered: региÑтриран - status_locked: заключен - - project_status_active: активен - project_status_closed: затворен - project_status_archived: архивиран - - version_status_open: отворена - version_status_locked: заключена - version_status_closed: затворена - - field_active: Ðктивен - - text_select_mail_notifications: Изберете ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð·Ð° изпращане на e-mail. - text_regexp_info: пр. ^[A-Z0-9]+$ - text_min_max_length_info: 0 - без Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ - text_project_destroy_confirmation: Сигурни ли Ñте, че иÑкате да изтриете проекта и данните в него? - text_subprojects_destroy_warning: "Ðеговите подпроекти: %{value} Ñъщо ще бъдат изтрити." - text_workflow_edit: Изберете Ñ€Ð¾Ð»Ñ Ð¸ тракер за да редактирате Ñ€Ð°Ð±Ð¾Ñ‚Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑ - text_are_you_sure: Сигурни ли Ñте? - text_journal_changed: "%{label} променен от %{old} на %{new}" - text_journal_changed_no_detail: "%{label} променен" - text_journal_set_to: "%{label} уÑтановен на %{value}" - text_journal_deleted: "%{label} изтрит (%{old})" - text_journal_added: "Добавено %{label} %{value}" - text_tip_issue_begin_day: задача, започваща този ден - text_tip_issue_end_day: задача, завършваща този ден - text_tip_issue_begin_end_day: задача, започваща и завършваща този ден - text_project_identifier_info: 'Позволени Ñа малки букви (a-z), цифри, тирета и _.
    ПромÑна Ñлед Ñъздаването му не е възможна.' - text_caracters_maximum: "До %{count} Ñимвола." - text_caracters_minimum: "Минимум %{count} Ñимвола." - text_length_between: "От %{min} до %{max} Ñимвола." - text_tracker_no_workflow: ÐÑма дефиниран работен Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð·Ð° този тракер - text_unallowed_characters: Ðепозволени Ñимволи - text_comma_separated: Позволено е изброÑване (Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð¸Ñ‚ÐµÐ» запетаÑ). - text_line_separated: Позволени Ñа много ÑтойноÑти (по едно на ред). - text_issues_ref_in_commit_messages: ОтбелÑзване и приключване на задачи от ревизии - text_issue_added: "Публикувана е нова задача Ñ Ð½Ð¾Ð¼ÐµÑ€ %{id} (от %{author})." - text_issue_updated: "Задача %{id} е обновена (от %{author})." - text_wiki_destroy_confirmation: Сигурни ли Ñте, че иÑкате да изтриете това Wiki и цÑлото му Ñъдържание? - text_issue_category_destroy_question: "Има задачи (%{count}) обвързани Ñ Ñ‚Ð°Ð·Ð¸ категориÑ. Какво ще изберете?" - text_issue_category_destroy_assignments: Премахване на връзките Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñта - text_issue_category_reassign_to: Преобвързване Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - text_user_mail_option: "За неизбраните проекти, ще получавате извеÑÑ‚Ð¸Ñ Ñамо за наблюдавани дейноÑти или в които учаÑтвате (Ñ‚.е. автор или назначени на мен)." - text_no_configuration_data: "Ð’Ñе още не Ñа конфигурирани Роли, тракери, ÑÑŠÑтоÑÐ½Ð¸Ñ Ð½Ð° задачи и работен процеÑ.\nСтрого Ñе препоръчва зареждането на примерната информациÑ. Веднъж заредена ще имате възможноÑÑ‚ да Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð°Ñ‚Ðµ." - text_load_default_configuration: Зареждане на примерна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ - text_status_changed_by_changeset: "Приложено Ñ Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ %{value}." - text_time_logged_by_changeset: Приложено в Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ %{value}. - text_issues_destroy_confirmation: 'Сигурни ли Ñте, че иÑкате да изтриете избраните задачи?' - text_issues_destroy_descendants_confirmation: Тази Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ñ‰Ðµ премахне и %{count} подзадача(и). - text_time_entries_destroy_confirmation: Сигурен ли Ñте, че изтриете избраните запиÑи за изразходвано време? - text_select_project_modules: 'Изберете активните модули за този проект:' - text_default_administrator_account_changed: Сменен Ñ„Ð°Ð±Ñ€Ð¸Ñ‡Ð½Ð¸Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸ÑтраторÑки профил - text_file_repository_writable: ВъзможноÑÑ‚ за пиÑане в хранилището Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ðµ - text_plugin_assets_writable: Папката на приÑтавките е разрешена за Ð·Ð°Ð¿Ð¸Ñ - text_rmagick_available: Ðаличен RMagick (по избор) - text_destroy_time_entries_question: "%{hours} чаÑа Ñа отделени на задачите, които иÑкате да изтриете. Какво избирате?" - text_destroy_time_entries: Изтриване на отделеното време - text_assign_time_entries_to_project: ПрехвърлÑне на отделеното време към проект - text_reassign_time_entries: 'ПрехвърлÑне на отделеното време към задача:' - text_user_wrote: "%{value} напиÑа:" - text_enumeration_destroy_question: "%{count} обекта Ñа Ñвързани Ñ Ñ‚Ð°Ð·Ð¸ ÑтойноÑÑ‚." - text_enumeration_category_reassign_to: 'ПреÑвържете ги към тази ÑтойноÑÑ‚:' - text_email_delivery_not_configured: "Изпращането на e-mail-и не е конфигурирано и извеÑтиÑта не Ñа разрешени.\nКонфигурирайте Ð²Ð°ÑˆÐ¸Ñ SMTP Ñървър в config/configuration.yml и реÑтартирайте Redmine, за да ги разрешите." - text_repository_usernames_mapping: "Изберете или променете потребителите в Redmine, ÑъответÑтващи на потребителите в дневника на хранилището (repository).\nПотребителите Ñ ÐµÐ´Ð½Ð°ÐºÐ²Ð¸ имена в Redmine и хранилищата Ñе ÑъвмеÑÑ‚Ñват автоматично." - text_diff_truncated: '... Този diff не е пълен, понеже е Ð½Ð°Ð´Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¼Ð°ÐºÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€, който може да бъде показан.' - text_custom_field_possible_values_info: 'Една ÑтойноÑÑ‚ на ред' - text_wiki_page_destroy_question: Тази Ñтраница има %{descendants} Ñтраници деца и descendant(s). Какво желаете да правите? - text_wiki_page_nullify_children: Запазване на тези Ñтраници като коренни Ñтраници - text_wiki_page_destroy_children: Изтриване на Ñтраниците деца и вÑички техни descendants - text_wiki_page_reassign_children: Преназначаване на Ñтраниците деца на тази родителÑка Ñтраница - text_own_membership_delete_confirmation: "Вие Ñте на път да премахнете нÑкои или вÑички ваши Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¸ е възможно Ñлед това да не можете да редактирате този проект.\nСигурен ли Ñте, че иÑкате да продължите?" - text_zoom_in: Увеличаване - text_zoom_out: ÐамалÑване - text_warn_on_leaving_unsaved: Страницата Ñъдържа незапиÑано Ñъдържание, което може да бъде загубено, ако Ñ Ð½Ð°Ð¿ÑƒÑнете. - text_scm_path_encoding_note: "По подразбиране: UTF-8" - text_git_repository_note: Празно и локално хранилище (например /gitrepo, c:\gitrepo) - text_mercurial_repository_note: Локално хранилище (например /hgrepo, c:\hgrepo) - text_scm_command: SCM команда - text_scm_command_version: ВерÑÐ¸Ñ - text_scm_config: Можете да конфигурирате SCM командите в config/configuration.yml. За да активирате промените, реÑтартирайте Redmine. - text_scm_command_not_available: SCM командата не е налична или доÑтъпна. Проверете конфигурациÑта в админиÑÑ‚Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ð¸Ñ Ð¿Ð°Ð½ÐµÐ». - text_issue_conflict_resolution_overwrite: Прилагане на моите промени (предишните коментари ще бъдат запазени, но нÑкои други промени може да бъдат презапиÑани) - text_issue_conflict_resolution_add_notes: ДобавÑне на моите коментари и отхвърлÑне на другите мои промени - text_issue_conflict_resolution_cancel: ОтхвърлÑне на вÑички мои промени и презареждане на %{link} - text_account_destroy_confirmation: "Сигурен/на ли Ñте, че желаете да продължите?\nВашиÑÑ‚ профил ще бъде премахнат без възможноÑÑ‚ за възÑтановÑване." - text_session_expiration_settings: "Внимание: промÑната на тези уÑтановÑÐ²Ð°Ð½Ð¾Ñ Ð¼Ð¾Ð¶Ðµ да прекрати вÑички активни ÑеÑии, включително и вашата." - text_project_closed: Този проект е затворен и е Ñамо за четене. - - default_role_manager: Мениджър - default_role_developer: Разработчик - default_role_reporter: Публикуващ - default_tracker_bug: Грешка - default_tracker_feature: ФункционалноÑÑ‚ - default_tracker_support: Поддръжка - default_issue_status_new: Ðова - default_issue_status_in_progress: Изпълнение - default_issue_status_resolved: Приключена - default_issue_status_feedback: Обратна връзка - default_issue_status_closed: Затворена - default_issue_status_rejected: Отхвърлена - default_doc_category_user: Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð·Ð° Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ - default_doc_category_tech: ТехничеÑка Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ - default_priority_low: ÐиÑък - default_priority_normal: Ðормален - default_priority_high: ВиÑок - default_priority_urgent: Спешен - default_priority_immediate: Веднага - default_activity_design: Дизайн - default_activity_development: Разработка - - enumeration_issue_priorities: Приоритети на задачи - enumeration_doc_categories: Категории документи - enumeration_activities: ДейноÑти (time tracking) - enumeration_system_activity: СиÑтемна активноÑÑ‚ - description_filter: Филтър - description_search: ТърÑене - description_choose_project: Проекти - description_project_scope: Обхват на търÑенето - description_notes: Бележки - description_message_content: Съдържание на Ñъобщението - description_query_sort_criteria_attribute: Ðтрибут на Ñортиране - description_query_sort_criteria_direction: ПоÑока на Ñортиране - description_user_mail_notification: ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¸Ð·Ð²ÐµÑтиÑта по пощата - description_available_columns: Ðалични колони - description_selected_columns: Избрани колони - description_issue_category_reassign: Изберете ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - description_wiki_subpages_reassign: Изберете нова родителÑка Ñтраница - description_all_columns: Ð’Ñички колони - description_date_range_list: Изберете диапазон от ÑпиÑъка - description_date_range_interval: Изберете диапазон чрез задаване на начална и крайна дати - description_date_from: Въведете начална дата - description_date_to: Въведете крайна дата - text_repository_identifier_info: 'Позволени Ñа малки букви (a-z), цифри, тирета и _.
    ПромÑна Ñлед Ñъздаването му не е възможна.' diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fc/fc0546309452598a33a3567fab81883ae02048af.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fc/fc0546309452598a33a3567fab81883ae02048af.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,6 @@ +<%= title [l(:label_issue_status_plural), issue_statuses_path], @issue_status.name %> + +<%= labelled_form_for @issue_status do |f| %> + <%= render :partial => 'form', :locals => {:f => f} %> + <%= submit_tag l(:button_save) %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fc/fc1e8e015812f86556df9e03ed455ae55a7dfc3a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fc/fc1e8e015812f86556df9e03ed455ae55a7dfc3a.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,174 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +namespace :redmine do + namespace :email do + + desc <<-END_DESC +Read an email from standard input. + +General options: + unknown_user=ACTION how to handle emails from an unknown user + ACTION can be one of the following values: + ignore: email is ignored (default) + accept: accept as anonymous user + create: create a user account + no_permission_check=1 disable permission checking when receiving + the email + no_account_notice=1 disable new user account notification + default_group=foo,bar adds created user to foo and bar groups + +Issue attributes control options: + project=PROJECT identifier of the target project + status=STATUS name of the target status + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + rake redmine:email:read RAILS_ENV="production" < raw_email + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + rake redmine:email:read RAILS_ENV="production" \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority < raw_email +END_DESC + + task :read => :environment do + MailHandler.receive(STDIN.read, MailHandler.extract_options_from_env(ENV)) + end + + desc <<-END_DESC +Read emails from an IMAP server. + +General options: + unknown_user=ACTION how to handle emails from an unknown user + ACTION can be one of the following values: + ignore: email is ignored (default) + accept: accept as anonymous user + create: create a user account + no_permission_check=1 disable permission checking when receiving + the email + no_account_notice=1 disable new user account notification + default_group=foo,bar adds created user to foo and bar groups + +Available IMAP options: + host=HOST IMAP server host (default: 127.0.0.1) + port=PORT IMAP server port (default: 143) + ssl=SSL Use SSL? (default: false) + username=USERNAME IMAP account + password=PASSWORD IMAP password + folder=FOLDER IMAP folder to read (default: INBOX) + +Issue attributes control options: + project=PROJECT identifier of the target project + status=STATUS name of the target status + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Processed emails control options: + move_on_success=MAILBOX move emails that were successfully received + to MAILBOX instead of deleting them + move_on_failure=MAILBOX move emails that were ignored to MAILBOX + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + + rake redmine:email:receive_imap RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@example.net password=xxx + + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + + rake redmine:email:receive_imap RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority +END_DESC + + task :receive_imap => :environment do + imap_options = {:host => ENV['host'], + :port => ENV['port'], + :ssl => ENV['ssl'], + :username => ENV['username'], + :password => ENV['password'], + :folder => ENV['folder'], + :move_on_success => ENV['move_on_success'], + :move_on_failure => ENV['move_on_failure']} + + Redmine::IMAP.check(imap_options, MailHandler.extract_options_from_env(ENV)) + end + + desc <<-END_DESC +Read emails from an POP3 server. + +Available POP3 options: + host=HOST POP3 server host (default: 127.0.0.1) + port=PORT POP3 server port (default: 110) + username=USERNAME POP3 account + password=PASSWORD POP3 password + apop=1 use APOP authentication (default: false) + delete_unprocessed=1 delete messages that could not be processed + successfully from the server (default + behaviour is to leave them on the server) + +See redmine:email:receive_imap for more options and examples. +END_DESC + + task :receive_pop3 => :environment do + pop_options = {:host => ENV['host'], + :port => ENV['port'], + :apop => ENV['apop'], + :username => ENV['username'], + :password => ENV['password'], + :delete_unprocessed => ENV['delete_unprocessed']} + + Redmine::POP3.check(pop_options, MailHandler.extract_options_from_env(ENV)) + end + + desc "Send a test email to the user with the provided login name" + task :test, [:login] => :environment do |task, args| + include Redmine::I18n + abort l(:notice_email_error, "Please include the user login to test with. Example: rake redmine:email:test[login]") if args[:login].blank? + + user = User.find_by_login(args[:login]) + abort l(:notice_email_error, "User #{args[:login]} not found") unless user && user.logged? + + ActionMailer::Base.raise_delivery_errors = true + begin + Mailer.with_synched_deliveries do + Mailer.test_email(user).deliver + end + puts l(:notice_email_sent, user.mail) + rescue Exception => e + abort l(:notice_email_error, e.message) + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fc/fc704247258255beec93055af5d732e51089fa26.svn-base --- a/.svn/pristine/fc/fc704247258255beec93055af5d732e51089fa26.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1080 +0,0 @@ -ro: - direction: ltr - date: - formats: - default: "%d-%m-%Y" - short: "%d %b" - long: "%d %B %Y" - only_day: "%e" - - day_names: [Duminică, Luni, Marti, Miercuri, Joi, Vineri, Sâmbătă] - abbr_day_names: [Dum, Lun, Mar, Mie, Joi, Vin, Sâm] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Ianuarie, Februarie, Martie, Aprilie, Mai, Iunie, Iulie, August, Septembrie, Octombrie, Noiembrie, Decembrie] - abbr_month_names: [~, Ian, Feb, Mar, Apr, Mai, Iun, Iul, Aug, Sep, Oct, Noi, Dec] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%m/%d/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "jumătate de minut" - less_than_x_seconds: - one: "mai puÈ›in de o secundă" - other: "mai puÈ›in de %{count} secunde" - x_seconds: - one: "o secundă" - other: "%{count} secunde" - less_than_x_minutes: - one: "mai puÈ›in de un minut" - other: "mai puÈ›in de %{count} minute" - x_minutes: - one: "un minut" - other: "%{count} minute" - about_x_hours: - one: "aproximativ o oră" - other: "aproximativ %{count} ore" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "o zi" - other: "%{count} zile" - about_x_months: - one: "aproximativ o lună" - other: "aproximativ %{count} luni" - x_months: - one: "o luna" - other: "%{count} luni" - about_x_years: - one: "aproximativ un an" - other: "aproximativ %{count} ani" - over_x_years: - one: "peste un an" - other: "peste %{count} ani" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - - human: - format: - precision: 3 - delimiter: "" - storage_units: - format: "%n %u" - units: - kb: KB - tb: TB - gb: GB - byte: - one: Byte - other: Bytes - mb: MB - -# Used in array.to_sentence. - support: - array: - sentence_connector: "È™i" - skip_last_comma: true - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "nu este inclus în listă" - exclusion: "este rezervat" - invalid: "nu este valid" - confirmation: "nu este identică" - accepted: "trebuie acceptat" - empty: "trebuie completat" - blank: "nu poate fi gol" - too_long: "este prea lung" - too_short: "este prea scurt" - wrong_length: "nu are lungimea corectă" - taken: "a fost luat deja" - not_a_number: "nu este un număr" - not_a_date: "nu este o dată validă" - greater_than: "trebuie să fie mai mare de %{count}" - greater_than_or_equal_to: "trebuie să fie mai mare sau egal cu %{count}" - equal_to: "trebuie să fie egal cu {count}}" - less_than: "trebuie să fie mai mic decat %{count}" - less_than_or_equal_to: "trebuie să fie mai mic sau egal cu %{count}" - odd: "trebuie să fie impar" - even: "trebuie să fie par" - greater_than_start_date: "trebuie să fie după data de început" - not_same_project: "trebuie să aparÈ›ină aceluiaÈ™i proiect" - circular_dependency: "Această relaÈ›ie ar crea o dependență circulară" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: SelectaÈ›i - - general_text_No: 'Nu' - general_text_Yes: 'Da' - general_text_no: 'nu' - general_text_yes: 'da' - general_lang_name: 'Română' - general_csv_separator: '.' - general_csv_decimal_separator: ',' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '2' - - notice_account_updated: Cont actualizat. - notice_account_invalid_creditentials: Utilizator sau parola nevalidă - notice_account_password_updated: Parolă actualizată. - notice_account_wrong_password: Parolă greÈ™ită - notice_account_register_done: Contul a fost creat. Pentru activare, urmaÈ›i legătura trimisă prin email. - notice_account_unknown_email: Utilizator necunoscut. - notice_can_t_change_password: Acest cont foloseÈ™te o sursă externă de autentificare. Nu se poate schimba parola. - notice_account_lost_email_sent: S-a trimis un email cu instrucÈ›iuni de schimbare a parolei. - notice_account_activated: Contul a fost activat. Vă puteÈ›i autentifica acum. - notice_successful_create: Creat. - notice_successful_update: Actualizat. - notice_successful_delete: Șters. - notice_successful_connection: Conectat. - notice_file_not_found: Pagina pe care doriÈ›i să o accesaÈ›i nu există sau a fost È™tearsă. - notice_locking_conflict: Datele au fost actualizate de alt utilizator. - notice_not_authorized: Nu sunteÈ›i autorizat sa accesaÈ›i această pagină. - notice_email_sent: "S-a trimis un email către %{value}" - notice_email_error: "A intervenit o eroare la trimiterea de email (%{value})" - notice_feeds_access_key_reseted: Cheia de acces RSS a fost resetată. - notice_failed_to_save_issues: "Nu s-au putut salva %{count} tichete din cele %{total} selectate: %{ids}." - notice_no_issue_selected: "Niciun tichet selectat! Vă rugăm să selectaÈ›i tichetele pe care doriÈ›i să le editaÈ›i." - notice_account_pending: "Contul dumneavoastră a fost creat È™i aÈ™teaptă aprobarea administratorului." - notice_default_data_loaded: S-a încărcat configuraÈ›ia implicită. - notice_unable_delete_version: Nu se poate È™terge versiunea. - - error_can_t_load_default_data: "Nu s-a putut încărca configuraÈ›ia implicită: %{value}" - error_scm_not_found: "Nu s-a găsit articolul sau revizia în depozit." - error_scm_command_failed: "A intervenit o eroare la accesarea depozitului: %{value}" - error_scm_annotate: "Nu există sau nu poate fi adnotată." - error_issue_not_found_in_project: 'Tichetul nu a fost găsit sau nu aparÈ›ine acestui proiect' - - warning_attachments_not_saved: "Nu s-au putut salva %{count} fiÈ™iere." - - mail_subject_lost_password: "Parola dumneavoastră: %{value}" - mail_body_lost_password: 'Pentru a schimba parola, accesaÈ›i:' - mail_subject_register: "Activarea contului %{value}" - mail_body_register: 'Pentru activarea contului, accesaÈ›i:' - mail_body_account_information_external: "PuteÈ›i folosi contul „{value}}†pentru a vă autentifica." - mail_body_account_information: InformaÈ›ii despre contul dumneavoastră - mail_subject_account_activation_request: "Cerere de activare a contului %{value}" - mail_body_account_activation_request: "S-a înregistrat un utilizator nou (%{value}). Contul aÈ™teaptă aprobarea dumneavoastră:" - mail_subject_reminder: "%{count} tichete trebuie rezolvate în următoarele %{days} zile" - mail_body_reminder: "%{count} tichete atribuite dumneavoastră trebuie rezolvate în următoarele %{days} zile:" - - gui_validation_error: o eroare - gui_validation_error_plural: "%{count} erori" - - field_name: Nume - field_description: Descriere - field_summary: Rezumat - field_is_required: Obligatoriu - field_firstname: Prenume - field_lastname: Nume - field_mail: Email - field_filename: FiÈ™ier - field_filesize: Mărime - field_downloads: Descărcări - field_author: Autor - field_created_on: Creat la - field_updated_on: Actualizat la - field_field_format: Format - field_is_for_all: Pentru toate proiectele - field_possible_values: Valori posibile - field_regexp: Expresie regulară - field_min_length: lungime minimă - field_max_length: lungime maximă - field_value: Valoare - field_category: Categorie - field_title: Titlu - field_project: Proiect - field_issue: Tichet - field_status: Stare - field_notes: Note - field_is_closed: Rezolvat - field_is_default: Implicit - field_tracker: Tip de tichet - field_subject: Subiect - field_due_date: Data finalizării - field_assigned_to: Atribuit - field_priority: Prioritate - field_fixed_version: Versiune È›intă - field_user: Utilizator - field_role: Rol - field_homepage: Pagina principală - field_is_public: Public - field_parent: Sub-proiect al - field_is_in_roadmap: Tichete afiÈ™ate în plan - field_login: Autentificare - field_mail_notification: Notificări prin e-mail - field_admin: Administrator - field_last_login_on: Ultima autentificare în - field_language: Limba - field_effective_date: Data - field_password: Parola - field_new_password: Parola nouă - field_password_confirmation: Confirmare - field_version: Versiune - field_type: Tip - field_host: Gazdă - field_port: Port - field_account: Cont - field_base_dn: Base DN - field_attr_login: Atribut autentificare - field_attr_firstname: Atribut prenume - field_attr_lastname: Atribut nume - field_attr_mail: Atribut email - field_onthefly: Creare utilizator pe loc - field_start_date: Data începerii - field_done_ratio: Realizat (%) - field_auth_source: Mod autentificare - field_hide_mail: Nu se afiÈ™ează adresa de email - field_comments: Comentariu - field_url: URL - field_start_page: Pagina de start - field_subproject: Subproiect - field_hours: Ore - field_activity: Activitate - field_spent_on: Data - field_identifier: Identificator - field_is_filter: Filtru - field_issue_to: Tichet asociat - field_delay: ÃŽntârziere - field_assignable: Se pot atribui tichete acestui rol - field_redirect_existing_links: RedirecÈ›ionează legăturile existente - field_estimated_hours: Timp estimat - field_column_names: Coloane - field_time_zone: Fus orar - field_searchable: Căutare - field_default_value: Valoare implicita - field_comments_sorting: AfiÈ™ează comentarii - field_parent_title: Pagina superioara - field_editable: Modificabil - field_watcher: UrmăreÈ™te - field_identity_url: URL OpenID - field_content: ConÈ›inut - - setting_app_title: Titlu aplicaÈ›ie - setting_app_subtitle: Subtitlu aplicaÈ›ie - setting_welcome_text: Text de întâmpinare - setting_default_language: Limba implicita - setting_login_required: Necesita autentificare - setting_self_registration: ÃŽnregistrare automată - setting_attachment_max_size: Mărime maxima ataÈ™ament - setting_issues_export_limit: Limită de tichete exportate - setting_mail_from: Adresa de email a expeditorului - setting_bcc_recipients: AlÈ›i destinatari pentru email (BCC) - setting_plain_text_mail: Mesaje text (fără HTML) - setting_host_name: Numele gazdei È™i calea - setting_text_formatting: Formatare text - setting_wiki_compression: Comprimare istoric Wiki - setting_feeds_limit: Limita de actualizări din feed - setting_default_projects_public: Proiectele noi sunt implicit publice - setting_autofetch_changesets: Preluare automată a modificărilor din depozit - setting_sys_api_enabled: Activare WS pentru gestionat depozitul - setting_commit_ref_keywords: Cuvinte cheie pt. referire tichet - setting_commit_fix_keywords: Cuvinte cheie pt. rezolvare tichet - setting_autologin: Autentificare automată - setting_date_format: Format dată - setting_time_format: Format oră - setting_cross_project_issue_relations: Permite legături de tichete între proiecte - setting_issue_list_default_columns: Coloane implicite afiÈ™ate în lista de tichete - setting_emails_footer: Subsol email - setting_protocol: Protocol - setting_per_page_options: Număr de obiecte pe pagină - setting_user_format: Stil de afiÈ™are pentru utilizator - setting_activity_days_default: Se afiÈ™ează zile în jurnalul proiectului - setting_display_subprojects_issues: AfiÈ™ează implicit tichetele sub-proiectelor în proiectele principale - setting_enabled_scm: SCM activat - setting_mail_handler_api_enabled: Activare WS pentru email primit - setting_mail_handler_api_key: cheie API - setting_sequential_project_identifiers: Generează secvenÈ›ial identificatoarele de proiect - setting_gravatar_enabled: FoloseÈ™te poze Gravatar pentru utilizatori - setting_diff_max_lines_displayed: Număr maxim de linii de diferență afiÈ™ate - setting_file_max_size_displayed: Număr maxim de fiÈ™iere text afiÈ™ate în pagină (inline) - setting_repository_log_display_limit: Număr maxim de revizii afiÈ™ate în istoricul fiÈ™ierului - setting_openid: Permite înregistrare È™i autentificare cu OpenID - - permission_edit_project: Editează proiectul - permission_select_project_modules: Alege module pentru proiect - permission_manage_members: Editează membri - permission_manage_versions: Editează versiuni - permission_manage_categories: Editează categorii - permission_add_issues: Adaugă tichete - permission_edit_issues: Editează tichete - permission_manage_issue_relations: Editează relaÈ›ii tichete - permission_add_issue_notes: Adaugă note - permission_edit_issue_notes: Editează note - permission_edit_own_issue_notes: Editează notele proprii - permission_move_issues: Mută tichete - permission_delete_issues: Șterge tichete - permission_manage_public_queries: Editează căutările implicite - permission_save_queries: Salvează căutările - permission_view_gantt: AfiÈ™ează Gantt - permission_view_calendar: AfiÈ™ează calendarul - permission_view_issue_watchers: AfiÈ™ează lista de persoane interesate - permission_add_issue_watchers: Adaugă persoane interesate - permission_log_time: ÃŽnregistrează timpul de lucru - permission_view_time_entries: AfiÈ™ează timpul de lucru - permission_edit_time_entries: Editează jurnalele cu timp de lucru - permission_edit_own_time_entries: Editează jurnalele proprii cu timpul de lucru - permission_manage_news: Editează È™tiri - permission_comment_news: Comentează È™tirile - permission_manage_documents: Editează documente - permission_view_documents: AfiÈ™ează documente - permission_manage_files: Editează fiÈ™iere - permission_view_files: AfiÈ™ează fiÈ™iere - permission_manage_wiki: Editează wiki - permission_rename_wiki_pages: RedenumeÈ™te pagini wiki - permission_delete_wiki_pages: Șterge pagini wiki - permission_view_wiki_pages: AfiÈ™ează wiki - permission_view_wiki_edits: AfiÈ™ează istoricul wiki - permission_edit_wiki_pages: Editează pagini wiki - permission_delete_wiki_pages_attachments: Șterge ataÈ™amente - permission_protect_wiki_pages: Blochează pagini wiki - permission_manage_repository: Gestionează depozitul - permission_browse_repository: RăsfoieÈ™te depozitul - permission_view_changesets: AfiÈ™ează modificările din depozit - permission_commit_access: Acces commit - permission_manage_boards: Editează forum - permission_view_messages: AfiÈ™ează mesaje - permission_add_messages: Scrie mesaje - permission_edit_messages: Editează mesaje - permission_edit_own_messages: Editează mesajele proprii - permission_delete_messages: Șterge mesaje - permission_delete_own_messages: Șterge mesajele proprii - - project_module_issue_tracking: Tichete - project_module_time_tracking: Timp de lucru - project_module_news: Știri - project_module_documents: Documente - project_module_files: FiÈ™iere - project_module_wiki: Wiki - project_module_repository: Depozit - project_module_boards: Forum - - label_user: Utilizator - label_user_plural: Utilizatori - label_user_new: Utilizator nou - label_project: Proiect - label_project_new: Proiect nou - label_project_plural: Proiecte - label_x_projects: - zero: niciun proiect - one: un proiect - other: "%{count} proiecte" - label_project_all: Toate proiectele - label_project_latest: Proiecte noi - label_issue: Tichet - label_issue_new: Tichet nou - label_issue_plural: Tichete - label_issue_view_all: AfiÈ™ează toate tichetele - label_issues_by: "Sortează după %{value}" - label_issue_added: Adaugat - label_issue_updated: Actualizat - label_document: Document - label_document_new: Document nou - label_document_plural: Documente - label_document_added: Adăugat - label_role: Rol - label_role_plural: Roluri - label_role_new: Rol nou - label_role_and_permissions: Roluri È™i permisiuni - label_member: Membru - label_member_new: membru nou - label_member_plural: Membri - label_tracker: Tip de tichet - label_tracker_plural: Tipuri de tichete - label_tracker_new: Tip nou de tichet - label_workflow: Mod de lucru - label_issue_status: Stare tichet - label_issue_status_plural: Stare tichete - label_issue_status_new: Stare nouă - label_issue_category: Categorie de tichet - label_issue_category_plural: Categorii de tichete - label_issue_category_new: Categorie nouă - label_custom_field: Câmp personalizat - label_custom_field_plural: Câmpuri personalizate - label_custom_field_new: Câmp nou personalizat - label_enumerations: Enumerări - label_enumeration_new: Valoare nouă - label_information: InformaÈ›ie - label_information_plural: InformaÈ›ii - label_please_login: Vă rugăm să vă autentificaÈ›i - label_register: ÃŽnregistrare - label_login_with_open_id_option: sau autentificare cu OpenID - label_password_lost: Parolă uitată - label_home: Acasă - label_my_page: Pagina mea - label_my_account: Contul meu - label_my_projects: Proiectele mele - label_administration: Administrare - label_login: Autentificare - label_logout: IeÈ™ire din cont - label_help: Ajutor - label_reported_issues: Tichete - label_assigned_to_me_issues: Tichetele mele - label_last_login: Ultima conectare - label_registered_on: ÃŽnregistrat la - label_activity: Activitate - label_overall_activity: Activitate - vedere de ansamblu - label_user_activity: "Activitate %{value}" - label_new: Nou - label_logged_as: Autentificat ca - label_environment: Mediu - label_authentication: Autentificare - label_auth_source: Mod de autentificare - label_auth_source_new: Nou - label_auth_source_plural: Moduri de autentificare - label_subproject_plural: Sub-proiecte - label_and_its_subprojects: "%{value} È™i sub-proiecte" - label_min_max_length: lungime min - max - label_list: Listă - label_date: Dată - label_integer: ÃŽntreg - label_float: Zecimal - label_boolean: Valoare logică - label_string: Text - label_text: Text lung - label_attribute: Atribut - label_attribute_plural: Atribute - label_download: "%{count} descărcare" - label_download_plural: "%{count} descărcări" - label_no_data: Nu există date de afiÈ™at - label_change_status: Schimbă starea - label_history: Istoric - label_attachment: FiÈ™ier - label_attachment_new: FiÈ™ier nou - label_attachment_delete: Șterge fiÈ™ier - label_attachment_plural: FiÈ™iere - label_file_added: Adăugat - label_report: Raport - label_report_plural: Rapoarte - label_news: Știri - label_news_new: Adaugă È™tire - label_news_plural: Știri - label_news_latest: Ultimele È™tiri - label_news_view_all: AfiÈ™ează toate È™tirile - label_news_added: Adăugat - label_settings: Setări - label_overview: Pagină proiect - label_version: Versiune - label_version_new: Versiune nouă - label_version_plural: Versiuni - label_confirmation: Confirmare - label_export_to: 'Disponibil È™i în:' - label_read: CiteÈ™te... - label_public_projects: Proiecte publice - label_open_issues: deschis - label_open_issues_plural: deschise - label_closed_issues: închis - label_closed_issues_plural: închise - label_x_open_issues_abbr_on_total: - zero: 0 deschise / %{total} - one: 1 deschis / %{total} - other: "%{count} deschise / %{total}" - label_x_open_issues_abbr: - zero: 0 deschise - one: 1 deschis - other: "%{count} deschise" - label_x_closed_issues_abbr: - zero: 0 închise - one: 1 închis - other: "%{count} închise" - label_total: Total - label_permissions: Permisiuni - label_current_status: Stare curentă - label_new_statuses_allowed: Stări noi permise - label_all: toate - label_none: niciunul - label_nobody: nimeni - label_next: ÃŽnainte - label_previous: ÃŽnapoi - label_used_by: Folosit de - label_details: Detalii - label_add_note: Adaugă o notă - label_per_page: pe pagină - label_calendar: Calendar - label_months_from: luni de la - label_gantt: Gantt - label_internal: Intern - label_last_changes: "ultimele %{count} schimbări" - label_change_view_all: AfiÈ™ează toate schimbările - label_personalize_page: Personalizează aceasta pagina - label_comment: Comentariu - label_comment_plural: Comentarii - label_x_comments: - zero: fara comentarii - one: 1 comentariu - other: "%{count} comentarii" - label_comment_add: Adaugă un comentariu - label_comment_added: Adăugat - label_comment_delete: Șterge comentariul - label_query: Cautare personalizata - label_query_plural: Căutări personalizate - label_query_new: Căutare nouă - label_filter_add: Adaugă filtru - label_filter_plural: Filtre - label_equals: este - label_not_equals: nu este - label_in_less_than: în mai puÈ›in de - label_in_more_than: în mai mult de - label_in: în - label_today: astăzi - label_all_time: oricând - label_yesterday: ieri - label_this_week: săptămâna aceasta - label_last_week: săptămâna trecută - label_last_n_days: "ultimele %{count} zile" - label_this_month: luna aceasta - label_last_month: luna trecută - label_this_year: anul acesta - label_date_range: Perioada - label_less_than_ago: mai puÈ›in de ... zile - label_more_than_ago: mai mult de ... zile - label_ago: în urma - label_contains: conÈ›ine - label_not_contains: nu conÈ›ine - label_day_plural: zile - label_repository: Depozit - label_repository_plural: Depozite - label_browse: AfiÈ™ează - label_modification: "%{count} schimbare" - label_modification_plural: "%{count} schimbări" - label_revision: Revizie - label_revision_plural: Revizii - label_associated_revisions: Revizii asociate - label_added: adaugată - label_modified: modificată - label_copied: copiată - label_renamed: redenumită - label_deleted: È™tearsă - label_latest_revision: Ultima revizie - label_latest_revision_plural: Ultimele revizii - label_view_revisions: AfiÈ™ează revizii - label_max_size: Mărime maximă - label_sort_highest: Prima - label_sort_higher: ÃŽn sus - label_sort_lower: ÃŽn jos - label_sort_lowest: Ultima - label_roadmap: Planificare - label_roadmap_due_in: "De terminat în %{value}" - label_roadmap_overdue: "ÃŽntârziat cu %{value}" - label_roadmap_no_issues: Nu există tichete pentru această versiune - label_search: Caută - label_result_plural: Rezultate - label_all_words: toate cuvintele - label_wiki: Wiki - label_wiki_edit: Editare Wiki - label_wiki_edit_plural: Editări Wiki - label_wiki_page: Pagină Wiki - label_wiki_page_plural: Pagini Wiki - label_index_by_title: Sortează după titlu - label_index_by_date: Sortează după dată - label_current_version: Versiunea curentă - label_preview: Previzualizare - label_feed_plural: Feed-uri - label_changes_details: Detaliile tuturor schimbărilor - label_issue_tracking: Urmărire tichete - label_spent_time: Timp alocat - label_f_hour: "%{value} oră" - label_f_hour_plural: "%{value} ore" - label_time_tracking: Urmărire timp de lucru - label_change_plural: Schimbări - label_statistics: Statistici - label_commits_per_month: Commit pe luna - label_commits_per_author: Commit per autor - label_view_diff: AfiÈ™ează diferenÈ›ele - label_diff_inline: în linie - label_diff_side_by_side: una lângă alta - label_options: OpÈ›iuni - label_copy_workflow_from: Copiază modul de lucru de la - label_permissions_report: Permisiuni - label_watched_issues: Tichete urmărite - label_related_issues: Tichete asociate - label_applied_status: Stare aplicată - label_loading: ÃŽncarcă... - label_relation_new: Asociere nouă - label_relation_delete: Șterge asocierea - label_relates_to: asociat cu - label_duplicates: duplicate - label_duplicated_by: la fel ca - label_blocks: blocări - label_blocked_by: blocat de - label_precedes: precede - label_follows: urmează - label_end_to_start: de la sfârÈ™it la început - label_end_to_end: de la sfârÈ™it la sfârÈ™it - label_start_to_start: de la început la început - label_start_to_end: de la început la sfârÈ™it - label_stay_logged_in: Păstrează autentificarea - label_disabled: dezactivat - label_show_completed_versions: Arată versiunile terminate - label_me: eu - label_board: Forum - label_board_new: Forum nou - label_board_plural: Forumuri - label_topic_plural: Subiecte - label_message_plural: Mesaje - label_message_last: Ultimul mesaj - label_message_new: Mesaj nou - label_message_posted: Adăugat - label_reply_plural: Răspunsuri - label_send_information: Trimite utilizatorului informaÈ›iile despre cont - label_year: An - label_month: Lună - label_week: Săptămână - label_date_from: De la - label_date_to: La - label_language_based: Un funcÈ›ie de limba de afiÈ™are a utilizatorului - label_sort_by: "Sortează după %{value}" - label_send_test_email: Trimite email de test - label_feeds_access_key_created_on: "Cheie de acces creată acum %{value}" - label_module_plural: Module - label_added_time_by: "Adăugat de %{author} acum %{age}" - label_updated_time_by: "Actualizat de %{author} acum %{age}" - label_updated_time: "Actualizat acum %{value}" - label_jump_to_a_project: Alege proiectul... - label_file_plural: FiÈ™iere - label_changeset_plural: Schimbări - label_default_columns: Coloane implicite - label_no_change_option: (fără schimbări) - label_bulk_edit_selected_issues: Editează toate tichetele selectate - label_theme: Tema - label_default: Implicită - label_search_titles_only: Caută numai în titluri - label_user_mail_option_all: "Pentru orice eveniment, în toate proiectele mele" - label_user_mail_option_selected: " Pentru orice eveniment, în proiectele selectate..." - label_user_mail_no_self_notified: "Nu trimite notificări pentru modificările mele" - label_registration_activation_by_email: activare cont prin email - label_registration_manual_activation: activare manuală a contului - label_registration_automatic_activation: activare automată a contului - label_display_per_page: "pe pagină: %{value}" - label_age: vechime - label_change_properties: Schimbă proprietățile - label_general: General - label_more: Mai mult - label_scm: SCM - label_plugins: Plugin-uri - label_ldap_authentication: autentificare LDAP - label_downloads_abbr: D/L - label_optional_description: Descriere (opÈ›ională) - label_add_another_file: Adaugă alt fiÈ™ier - label_preferences: PreferinÈ›e - label_chronological_order: în ordine cronologică - label_reverse_chronological_order: ÃŽn ordine invers cronologică - label_planning: Planificare - label_incoming_emails: Mesaje primite - label_generate_key: Generează o cheie - label_issue_watchers: Cine urmăreÈ™te - label_example: Exemplu - label_display: AfiÈ™ează - - label_sort: Sortează - label_ascending: Crescător - label_descending: Descrescător - label_date_from_to: De la %{start} la %{end} - - button_login: Autentificare - button_submit: Trimite - button_save: Salvează - button_check_all: Bifează tot - button_uncheck_all: Debifează tot - button_delete: Șterge - button_create: Creează - button_create_and_continue: Creează È™i continua - button_test: Testează - button_edit: Editează - button_add: Adaugă - button_change: Modifică - button_apply: Aplică - button_clear: Șterge - button_lock: Blochează - button_unlock: Deblochează - button_download: Descarcă - button_list: Listează - button_view: AfiÈ™ează - button_move: Mută - button_back: ÃŽnapoi - button_cancel: Anulează - button_activate: Activează - button_sort: Sortează - button_log_time: ÃŽnregistrează timpul de lucru - button_rollback: Revenire la această versiune - button_watch: Urmăresc - button_unwatch: Nu urmăresc - button_reply: Răspunde - button_archive: Arhivează - button_unarchive: Dezarhivează - button_reset: Resetează - button_rename: RedenumeÈ™te - button_change_password: Schimbare parolă - button_copy: Copiază - button_annotate: Adnotează - button_update: Actualizează - button_configure: Configurează - button_quote: Citează - - status_active: activ - status_registered: înregistrat - status_locked: blocat - - text_select_mail_notifications: SelectaÈ›i acÈ›iunile notificate prin email. - text_regexp_info: ex. ^[A-Z0-9]+$ - text_min_max_length_info: 0 înseamnă fără restricÈ›ii - text_project_destroy_confirmation: Sigur doriÈ›i să È™tergeÈ›i proiectul È™i toate datele asociate? - text_subprojects_destroy_warning: "Se vor È™terge È™i sub-proiectele: %{value}." - text_workflow_edit: SelectaÈ›i un rol È™i un tip de tichet pentru a edita modul de lucru - text_are_you_sure: SunteÈ›i sigur(ă)? - text_tip_issue_begin_day: sarcină care începe în această zi - text_tip_issue_end_day: sarcină care se termină în această zi - text_tip_issue_begin_end_day: sarcină care începe È™i se termină în această zi - text_caracters_maximum: "maxim %{count} caractere." - text_caracters_minimum: "Trebuie să fie minim %{count} caractere." - text_length_between: "Lungime între %{min} È™i %{max} caractere." - text_tracker_no_workflow: Nu sunt moduri de lucru pentru acest tip de tichet - text_unallowed_characters: Caractere nepermise - text_comma_separated: Sunt permise mai multe valori (separate cu virgulă). - text_issues_ref_in_commit_messages: Referire la tichete È™i rezolvare în textul mesajului - text_issue_added: "Tichetul %{id} a fost adăugat de %{author}." - text_issue_updated: "Tichetul %{id} a fost actualizat de %{author}." - text_wiki_destroy_confirmation: Sigur doriÈ›i È™tergerea Wiki È™i a conÈ›inutului asociat? - text_issue_category_destroy_question: "Această categorie conÈ›ine (%{count}) tichete. Ce doriÈ›i să faceÈ›i?" - text_issue_category_destroy_assignments: Șterge apartenenÈ›a la categorie. - text_issue_category_reassign_to: Atribuie tichetele la această categorie - text_user_mail_option: "Pentru proiectele care nu sunt selectate, veÈ›i primi notificări doar pentru ceea ce urmăriÈ›i sau în ce sunteÈ›i implicat (ex: tichete create de dumneavoastră sau care vă sunt atribuite)." - text_no_configuration_data: "Nu s-au configurat încă rolurile, stările tichetelor È™i modurile de lucru.\nEste recomandat să încărcaÈ›i configuraÈ›ia implicită. O veÈ›i putea modifica ulterior." - text_load_default_configuration: ÃŽncarcă configuraÈ›ia implicită - text_status_changed_by_changeset: "Aplicat în setul %{value}." - text_issues_destroy_confirmation: 'Sigur doriÈ›i să È™tergeÈ›i tichetele selectate?' - text_select_project_modules: 'SelectaÈ›i modulele active pentru acest proiect:' - text_default_administrator_account_changed: S-a schimbat contul administratorului implicit - text_file_repository_writable: Se poate scrie în directorul de ataÈ™amente - text_plugin_assets_writable: Se poate scrie în directorul de plugin-uri - text_rmagick_available: Este disponibil RMagick (opÈ›ional) - text_destroy_time_entries_question: "%{hours} ore sunt înregistrate la tichetele pe care doriÈ›i să le È™tergeÈ›i. Ce doriÈ›i sa faceÈ›i?" - text_destroy_time_entries: Șterge orele înregistrate - text_assign_time_entries_to_project: Atribuie orele la proiect - text_reassign_time_entries: 'Atribuie orele înregistrate la tichetul:' - text_user_wrote: "%{value} a scris:" - text_enumeration_destroy_question: "Această valoare are %{count} obiecte." - text_enumeration_category_reassign_to: 'Atribuie la această valoare:' - text_email_delivery_not_configured: "Trimiterea de emailuri nu este configurată È™i ca urmare, notificările sunt dezactivate.\nConfiguraÈ›i serverul SMTP în config/configuration.yml È™i reporniÈ›i aplicaÈ›ia pentru a le activa." - text_repository_usernames_mapping: "SelectaÈ›i sau modificaÈ›i contul Redmine echivalent contului din istoricul depozitului.\nUtilizatorii cu un cont (sau e-mail) identic în Redmine È™i depozit sunt echivalate automat." - text_diff_truncated: '... ComparaÈ›ia a fost trunchiată pentru ca depășeÈ™te lungimea maximă de text care poate fi afiÈ™at.' - text_custom_field_possible_values_info: 'O linie pentru fiecare valoare' - - default_role_manager: Manager - default_role_developer: Dezvoltator - default_role_reporter: Creator de rapoarte - default_tracker_bug: Defect - default_tracker_feature: FuncÈ›ie - default_tracker_support: Suport - default_issue_status_new: Nou - default_issue_status_in_progress: In Progress - default_issue_status_resolved: Rezolvat - default_issue_status_feedback: AÈ™teaptă reacÈ›ii - default_issue_status_closed: ÃŽnchis - default_issue_status_rejected: Respins - default_doc_category_user: DocumentaÈ›ie - default_doc_category_tech: DocumentaÈ›ie tehnică - default_priority_low: mică - default_priority_normal: normală - default_priority_high: mare - default_priority_urgent: urgentă - default_priority_immediate: imediată - default_activity_design: Design - default_activity_development: Dezvoltare - - enumeration_issue_priorities: Priorități tichete - enumeration_doc_categories: Categorii documente - enumeration_activities: Activități (timp de lucru) - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Această pagină are %{descendants} pagini anterioare È™i descendenÈ›i. Ce doriÈ›i să faceÈ›i? - text_wiki_page_reassign_children: Atribuie paginile la această pagină - text_wiki_page_nullify_children: MenÈ›ine paginile ca È™i pagini iniÈ›iale (root) - text_wiki_page_destroy_children: Șterge paginile È™i descendenÈ›ii - setting_password_min_length: Lungime minimă parolă - field_group_by: Grupează după - mail_subject_wiki_content_updated: "Pagina wiki '%{id}' a fost actualizată" - label_wiki_content_added: Adăugat - mail_subject_wiki_content_added: "Pagina wiki '%{id}' a fost adăugată" - mail_body_wiki_content_added: Pagina wiki '%{id}' a fost adăugată de %{author}. - label_wiki_content_updated: Actualizat - mail_body_wiki_content_updated: Pagina wiki '%{id}' a fost actualizată de %{author}. - permission_add_project: Crează proiect - setting_new_project_user_role_id: Rol atribuit utilizatorului non-admin care crează un proiect. - label_view_all_revisions: Arată toate reviziile - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: Nu există un tracker asociat cu proiectul. VerificaÈ›i vă rog setările proiectului. - error_no_default_issue_status: Nu există un status implicit al tichetelor. VerificaÈ›i vă rog configuraÈ›ia (MergeÈ›i la "Administrare -> Stări tichete"). - text_journal_changed: "%{label} schimbat din %{old} în %{new}" - text_journal_set_to: "%{label} setat ca %{value}" - text_journal_deleted: "%{label} È™ters (%{old})" - label_group_plural: Grupuri - label_group: Grup - label_group_new: Grup nou - label_time_entry_plural: Timp alocat - text_journal_added: "%{label} %{value} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Codare pentru mesaje - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 tichet - one: 1 tichet - other: "%{count} tichete" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: toate - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: With subprojects - label_cross_project_tree: With project tree - label_cross_project_hierarchy: With project hierarchy - label_cross_project_system: With all projects - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fc/fcea6055312718c79b19d49925ca680df3e4895d.svn-base --- a/.svn/pristine/fc/fcea6055312718c79b19d49925ca680df3e4895d.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -witty_retort: - id: 1 - topic_id: 1 - content: Birdman is better! - created_at: <%= 6.hours.ago.to_s(:db) %> - updated_at: nil - -another: - id: 2 - topic_id: 2 - content: Nuh uh! - created_at: <%= 1.hour.ago.to_s(:db) %> - updated_at: nil \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fd/fd3c31711fb05e7466806c7da438af9f8c1e0a15.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fd/fd3c31711fb05e7466806c7da438af9f8c1e0a15.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,205 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require 'redmine/scm/adapters/cvs_adapter' +require 'digest/sha1' + +class Repository::Cvs < Repository + validates_presence_of :url, :root_url, :log_encoding + + safe_attributes 'root_url', + :if => lambda {|repository, user| repository.new_record?} + + def self.human_attribute_name(attribute_key_name, *args) + attr_name = attribute_key_name.to_s + if attr_name == "root_url" + attr_name = "cvsroot" + elsif attr_name == "url" + attr_name = "cvs_module" + end + super(attr_name, *args) + end + + def self.scm_adapter_class + Redmine::Scm::Adapters::CvsAdapter + end + + def self.scm_name + 'CVS' + end + + def entry(path=nil, identifier=nil) + rev = identifier.nil? ? nil : changesets.find_by_revision(identifier) + scm.entry(path, rev.nil? ? nil : rev.committed_on) + end + + def entries(path=nil, identifier=nil) + rev = nil + if ! identifier.nil? + rev = changesets.find_by_revision(identifier) + return nil if rev.nil? + end + entries = scm.entries(path, rev.nil? ? nil : rev.committed_on) + if entries + entries.each() do |entry| + if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? ) + change = filechanges.find_by_revision_and_path( + entry.lastrev.revision, + scm.with_leading_slash(entry.path) ) + if change + entry.lastrev.identifier = change.changeset.revision + entry.lastrev.revision = change.changeset.revision + entry.lastrev.author = change.changeset.committer + # entry.lastrev.branch = change.branch + end + end + end + end + load_entries_changesets(entries) + entries + end + + def cat(path, identifier=nil) + rev = nil + if ! identifier.nil? + rev = changesets.find_by_revision(identifier) + return nil if rev.nil? + end + scm.cat(path, rev.nil? ? nil : rev.committed_on) + end + + def annotate(path, identifier=nil) + rev = nil + if ! identifier.nil? + rev = changesets.find_by_revision(identifier) + return nil if rev.nil? + end + scm.annotate(path, rev.nil? ? nil : rev.committed_on) + end + + def diff(path, rev, rev_to) + # convert rev to revision. CVS can't handle changesets here + diff=[] + changeset_from = changesets.find_by_revision(rev) + if rev_to.to_i > 0 + changeset_to = changesets.find_by_revision(rev_to) + end + changeset_from.filechanges.each() do |change_from| + revision_from = nil + revision_to = nil + if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path)) + revision_from = change_from.revision + end + if revision_from + if changeset_to + changeset_to.filechanges.each() do |change_to| + revision_to = change_to.revision if change_to.path == change_from.path + end + end + unless revision_to + revision_to = scm.get_previous_revision(revision_from) + end + file_diff = scm.diff(change_from.path, revision_from, revision_to) + diff = diff + file_diff unless file_diff.nil? + end + end + return diff + end + + def fetch_changesets + # some nifty bits to introduce a commit-id with cvs + # natively cvs doesn't provide any kind of changesets, + # there is only a revision per file. + # we now take a guess using the author, the commitlog and the commit-date. + + # last one is the next step to take. the commit-date is not equal for all + # commits in one changeset. cvs update the commit-date when the *,v file was touched. so + # we use a small delta here, to merge all changes belonging to _one_ changeset + time_delta = 10.seconds + fetch_since = latest_changeset ? latest_changeset.committed_on : nil + transaction do + tmp_rev_num = 1 + scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision| + # only add the change to the database, if it doen't exists. the cvs log + # is not exclusive at all. + tmp_time = revision.time.clone + unless filechanges.find_by_path_and_revision( + scm.with_leading_slash(revision.paths[0][:path]), + revision.paths[0][:revision] + ) + cmt = Changeset.normalize_comments(revision.message, repo_log_encoding) + author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding) + cs = changesets.where( + :committed_on => tmp_time - time_delta .. tmp_time + time_delta, + :committer => author_utf8, + :comments => cmt + ).first + # create a new changeset.... + unless cs + # we use a temporaray revision number here (just for inserting) + # later on, we calculate a continous positive number + tmp_time2 = tmp_time.clone.gmtime + branch = revision.paths[0][:branch] + scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S") + cs = Changeset.create(:repository => self, + :revision => "tmp#{tmp_rev_num}", + :scmid => scmid, + :committer => revision.author, + :committed_on => tmp_time, + :comments => revision.message) + tmp_rev_num += 1 + end + # convert CVS-File-States to internal Action-abbrevations + # default action is (M)odified + action = "M" + if revision.paths[0][:action] == "Exp" && revision.paths[0][:revision] == "1.1" + action = "A" # add-action always at first revision (= 1.1) + elsif revision.paths[0][:action] == "dead" + action = "D" # dead-state is similar to Delete + end + Change.create( + :changeset => cs, + :action => action, + :path => scm.with_leading_slash(revision.paths[0][:path]), + :revision => revision.paths[0][:revision], + :branch => revision.paths[0][:branch] + ) + end + end + + # Renumber new changesets in chronological order + Changeset. + order('committed_on ASC, id ASC'). + where("repository_id = ? AND revision LIKE 'tmp%'", id). + each do |changeset| + changeset.update_attribute :revision, next_revision_number + end + end # transaction + @current_revision_number = nil + end + + private + + # Returns the next revision number to assign to a CVS changeset + def next_revision_number + # Need to retrieve existing revision numbers to sort them as integers + sql = "SELECT revision FROM #{Changeset.table_name} " + sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'" + @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0) + @current_revision_number += 1 + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fd/fd3ede0a5e12e3d96f4d011a2d4e4835e31d6383.svn-base --- a/.svn/pristine/fd/fd3ede0a5e12e3d96f4d011a2d4e4835e31d6383.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class AutoCompletesControllerTest < ActionController::TestCase - fixtures :projects, :issues, :issue_statuses, - :enumerations, :users, :issue_categories, - :trackers, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :journals, :journal_details - - def test_issues_should_not_be_case_sensitive - get :issues, :project_id => 'ecookbook', :q => 'ReCiPe' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).detect {|issue| issue.subject.match /recipe/} - end - - def test_issues_should_accept_term_param - get :issues, :project_id => 'ecookbook', :term => 'ReCiPe' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).detect {|issue| issue.subject.match /recipe/} - end - - def test_issues_should_return_issue_with_given_id - get :issues, :project_id => 'subproject1', :q => '13' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).include?(Issue.find(13)) - end - - def test_auto_complete_with_scope_all_should_search_other_projects - get :issues, :project_id => 'ecookbook', :q => '13', :scope => 'all' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).include?(Issue.find(13)) - end - - def test_auto_complete_without_project_should_search_all_projects - get :issues, :q => '13' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).include?(Issue.find(13)) - end - - def test_auto_complete_without_scope_all_should_not_search_other_projects - get :issues, :project_id => 'ecookbook', :q => '13' - assert_response :success - assert_equal [], assigns(:issues) - end - - def test_issues_should_return_json - get :issues, :project_id => 'subproject1', :q => '13' - assert_response :success - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Array, json - issue = json.first - assert_kind_of Hash, issue - assert_equal 13, issue['id'] - assert_equal 13, issue['value'] - assert_equal 'Bug #13: Subproject issue two', issue['label'] - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fd/fd62d84703695db56636faed9a7f858cfe7bf11b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fd/fd62d84703695db56636faed9a7f858cfe7bf11b.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,46 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::TrackersTest < Redmine::ApiTest::Base + fixtures :trackers + + def setup + Setting.rest_api_enabled = '1' + end + + test "GET /trackers.xml should return trackers" do + get '/trackers.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'trackers', + :attributes => {:type => 'array'}, + :child => { + :tag => 'tracker', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => 'Feature request' + } + } + } + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fd/fd672f720e53aab0a0184fd9c83dcdbc26d28825.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fd/fd672f720e53aab0a0184fd9c83dcdbc26d28825.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,601 @@ +module CollectiveIdea #:nodoc: + module Acts #:nodoc: + module NestedSet #:nodoc: + + # This acts provides Nested Set functionality. Nested Set is a smart way to implement + # an _ordered_ tree, with the added feature that you can select the children and all of their + # descendants with a single query. The drawback is that insertion or move need some complex + # sql queries. But everything is done here by this module! + # + # Nested sets are appropriate each time you want either an orderd tree (menus, + # commercial categories) or an efficient way of querying big trees (threaded posts). + # + # == API + # + # Methods names are aligned with acts_as_tree as much as possible to make replacment from one + # by another easier. + # + # item.children.create(:name => "child1") + # + + # Configuration options are: + # + # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id) + # * +:left_column+ - column name for left boundry data, default "lft" + # * +:right_column+ - column name for right boundry data, default "rgt" + # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id" + # (if it hasn't been already) and use that as the foreign key restriction. You + # can also pass an array to scope by multiple attributes. + # Example: acts_as_nested_set :scope => [:notable_id, :notable_type] + # * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the + # child objects are destroyed alongside this object by calling their destroy + # method. If set to :delete_all (default), all the child objects are deleted + # without calling their destroy method. + # * +:counter_cache+ adds a counter cache for the number of children. + # defaults to false. + # Example: acts_as_nested_set :counter_cache => :children_count + # + # See CollectiveIdea::Acts::NestedSet::Model::ClassMethods for a list of class methods and + # CollectiveIdea::Acts::NestedSet::Model for a list of instance methods added + # to acts_as_nested_set models + def acts_as_nested_set(options = {}) + options = { + :parent_column => 'parent_id', + :left_column => 'lft', + :right_column => 'rgt', + :dependent => :delete_all, # or :destroy + :counter_cache => false, + :order => 'id' + }.merge(options) + + if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/ + options[:scope] = "#{options[:scope]}_id".intern + end + + class_attribute :acts_as_nested_set_options + self.acts_as_nested_set_options = options + + include CollectiveIdea::Acts::NestedSet::Model + include Columns + extend Columns + + belongs_to :parent, :class_name => self.base_class.to_s, + :foreign_key => parent_column_name, + :counter_cache => options[:counter_cache], + :inverse_of => :children + has_many :children, :class_name => self.base_class.to_s, + :foreign_key => parent_column_name, :order => left_column_name, + :inverse_of => :parent, + :before_add => options[:before_add], + :after_add => options[:after_add], + :before_remove => options[:before_remove], + :after_remove => options[:after_remove] + + attr_accessor :skip_before_destroy + + before_create :set_default_left_and_right + before_save :store_new_parent + after_save :move_to_new_parent + before_destroy :destroy_descendants + + # no assignment to structure fields + [left_column_name, right_column_name].each do |column| + module_eval <<-"end_eval", __FILE__, __LINE__ + def #{column}=(x) + raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead." + end + end_eval + end + + define_model_callbacks :move + end + + module Model + extend ActiveSupport::Concern + + module ClassMethods + # Returns the first root + def root + roots.first + end + + def roots + where(parent_column_name => nil).order(quoted_left_column_name) + end + + def leaves + where("#{quoted_right_column_name} - #{quoted_left_column_name} = 1").order(quoted_left_column_name) + end + + def valid? + left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid? + end + + def left_and_rights_valid? + joins("LEFT OUTER JOIN #{quoted_table_name} AS parent ON " + + "#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}"). + where( + "#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " + + "#{quoted_table_name}.#{quoted_right_column_name} IS NULL OR " + + "#{quoted_table_name}.#{quoted_left_column_name} >= " + + "#{quoted_table_name}.#{quoted_right_column_name} OR " + + "(#{quoted_table_name}.#{quoted_parent_column_name} IS NOT NULL AND " + + "(#{quoted_table_name}.#{quoted_left_column_name} <= parent.#{quoted_left_column_name} OR " + + "#{quoted_table_name}.#{quoted_right_column_name} >= parent.#{quoted_right_column_name}))" + ).count == 0 + end + + def no_duplicates_for_columns? + scope_string = Array(acts_as_nested_set_options[:scope]).map do |c| + connection.quote_column_name(c) + end.push(nil).join(", ") + [quoted_left_column_name, quoted_right_column_name].all? do |column| + # No duplicates + select("#{scope_string}#{column}, COUNT(#{column})"). + group("#{scope_string}#{column}"). + having("COUNT(#{column}) > 1"). + first.nil? + end + end + + # Wrapper for each_root_valid? that can deal with scope. + def all_roots_valid? + if acts_as_nested_set_options[:scope] + roots.group(scope_column_names).group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots| + each_root_valid?(grouped_roots) + end + else + each_root_valid?(roots) + end + end + + def each_root_valid?(roots_to_validate) + left = right = 0 + roots_to_validate.all? do |root| + (root.left > left && root.right > right).tap do + left = root.left + right = root.right + end + end + end + + # Rebuilds the left & rights if unset or invalid. + # Also very useful for converting from acts_as_tree. + def rebuild!(validate_nodes = true) + # Don't rebuild a valid tree. + return true if valid? + + scope = lambda{|node|} + if acts_as_nested_set_options[:scope] + scope = lambda{|node| + scope_column_names.inject(""){|str, column_name| + str << "AND #{connection.quote_column_name(column_name)} = #{connection.quote(node.send(column_name.to_sym))} " + } + } + end + indices = {} + + set_left_and_rights = lambda do |node| + # set left + node[left_column_name] = indices[scope.call(node)] += 1 + # find + where(["#{quoted_parent_column_name} = ? #{scope.call(node)}", node]).order(acts_as_nested_set_options[:order]).each{|n| set_left_and_rights.call(n) } + # set right + node[right_column_name] = indices[scope.call(node)] += 1 + node.save!(:validate => validate_nodes) + end + + # Find root node(s) + root_nodes = where("#{quoted_parent_column_name} IS NULL").order(acts_as_nested_set_options[:order]).each do |root_node| + # setup index for this scope + indices[scope.call(root_node)] ||= 0 + set_left_and_rights.call(root_node) + end + end + + # Iterates over tree elements and determines the current level in the tree. + # Only accepts default ordering, odering by an other column than lft + # does not work. This method is much more efficent than calling level + # because it doesn't require any additional database queries. + # + # Example: + # Category.each_with_level(Category.root.self_and_descendants) do |o, level| + # + def each_with_level(objects) + path = [nil] + objects.each do |o| + if o.parent_id != path.last + # we are on a new level, did we decent or ascent? + if path.include?(o.parent_id) + # remove wrong wrong tailing paths elements + path.pop while path.last != o.parent_id + else + path << o.parent_id + end + end + yield(o, path.length - 1) + end + end + end + + # Any instance method that returns a collection makes use of Rails 2.1's named_scope (which is bundled for Rails 2.0), so it can be treated as a finder. + # + # category.self_and_descendants.count + # category.ancestors.find(:all, :conditions => "name like '%foo%'") + + # Value of the parent column + def parent_id + self[parent_column_name] + end + + # Value of the left column + def left + self[left_column_name] + end + + # Value of the right column + def right + self[right_column_name] + end + + # Returns true if this is a root node. + def root? + parent_id.nil? + end + + def leaf? + new_record? || (right - left == 1) + end + + # Returns true is this is a child node + def child? + !parent_id.nil? + end + + # Returns root + def root + self_and_ancestors.where(parent_column_name => nil).first + end + + # Returns the array of all parents and self + def self_and_ancestors + nested_set_scope.where([ + "#{self.class.quoted_table_name}.#{quoted_left_column_name} <= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} >= ?", left, right + ]) + end + + # Returns an array of all parents + def ancestors + without_self self_and_ancestors + end + + # Returns the array of all children of the parent, including self + def self_and_siblings + nested_set_scope.where(parent_column_name => parent_id) + end + + # Returns the array of all children of the parent, except self + def siblings + without_self self_and_siblings + end + + # Returns a set of all of its nested children which do not have children + def leaves + descendants.where("#{self.class.quoted_table_name}.#{quoted_right_column_name} - #{self.class.quoted_table_name}.#{quoted_left_column_name} = 1") + end + + # Returns the level of this object in the tree + # root level is 0 + def level + parent_id.nil? ? 0 : ancestors.count + end + + # Returns a set of itself and all of its nested children + def self_and_descendants + nested_set_scope.where([ + "#{self.class.quoted_table_name}.#{quoted_left_column_name} >= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} <= ?", left, right + ]) + end + + # Returns a set of all of its children and nested children + def descendants + without_self self_and_descendants + end + + def is_descendant_of?(other) + other.left < self.left && self.left < other.right && same_scope?(other) + end + + def is_or_is_descendant_of?(other) + other.left <= self.left && self.left < other.right && same_scope?(other) + end + + def is_ancestor_of?(other) + self.left < other.left && other.left < self.right && same_scope?(other) + end + + def is_or_is_ancestor_of?(other) + self.left <= other.left && other.left < self.right && same_scope?(other) + end + + # Check if other model is in the same scope + def same_scope?(other) + Array(acts_as_nested_set_options[:scope]).all? do |attr| + self.send(attr) == other.send(attr) + end + end + + # Find the first sibling to the left + def left_sibling + siblings.where(["#{self.class.quoted_table_name}.#{quoted_left_column_name} < ?", left]). + order("#{self.class.quoted_table_name}.#{quoted_left_column_name} DESC").last + end + + # Find the first sibling to the right + def right_sibling + siblings.where(["#{self.class.quoted_table_name}.#{quoted_left_column_name} > ?", left]).first + end + + # Shorthand method for finding the left sibling and moving to the left of it. + def move_left + move_to_left_of left_sibling + end + + # Shorthand method for finding the right sibling and moving to the right of it. + def move_right + move_to_right_of right_sibling + end + + # Move the node to the left of another node (you can pass id only) + def move_to_left_of(node) + move_to node, :left + end + + # Move the node to the left of another node (you can pass id only) + def move_to_right_of(node) + move_to node, :right + end + + # Move the node to the child of another node (you can pass id only) + def move_to_child_of(node) + move_to node, :child + end + + # Move the node to root nodes + def move_to_root + move_to nil, :root + end + + def move_possible?(target) + self != target && # Can't target self + same_scope?(target) && # can't be in different scopes + # !(left..right).include?(target.left..target.right) # this needs tested more + # detect impossible move + !((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right)) + end + + def to_text + self_and_descendants.map do |node| + "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})" + end.join("\n") + end + + protected + + def without_self(scope) + scope.where(["#{self.class.quoted_table_name}.#{self.class.primary_key} != ?", self]) + end + + # All nested set queries should use this nested_set_scope, which performs finds on + # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set + # declaration. + def nested_set_scope(options = {}) + options = {:order => "#{self.class.quoted_table_name}.#{quoted_left_column_name}"}.merge(options) + scopes = Array(acts_as_nested_set_options[:scope]) + options[:conditions] = scopes.inject({}) do |conditions,attr| + conditions.merge attr => self[attr] + end unless scopes.empty? + self.class.base_class.scoped options + end + + def store_new_parent + @move_to_new_parent_id = send("#{parent_column_name}_changed?") ? parent_id : false + true # force callback to return true + end + + def move_to_new_parent + if @move_to_new_parent_id.nil? + move_to_root + elsif @move_to_new_parent_id + move_to_child_of(@move_to_new_parent_id) + end + end + + # on creation, set automatically lft and rgt to the end of the tree + def set_default_left_and_right + highest_right_row = nested_set_scope(:order => "#{quoted_right_column_name} desc").limit(1).lock(true).first + maxright = highest_right_row ? (highest_right_row[right_column_name] || 0) : 0 + # adds the new node to the right of all existing nodes + self[left_column_name] = maxright + 1 + self[right_column_name] = maxright + 2 + end + + def in_tenacious_transaction(&block) + retry_count = 0 + begin + transaction(&block) + rescue ActiveRecord::StatementInvalid => error + raise unless connection.open_transactions.zero? + raise unless error.message =~ /Deadlock found when trying to get lock|Lock wait timeout exceeded/ + raise unless retry_count < 10 + retry_count += 1 + logger.info "Deadlock detected on retry #{retry_count}, restarting transaction" + sleep(rand(retry_count)*0.1) # Aloha protocol + retry + end + end + + # Prunes a branch off of the tree, shifting all of the elements on the right + # back to the left so the counts still work. + def destroy_descendants + return if right.nil? || left.nil? || skip_before_destroy + + in_tenacious_transaction do + reload_nested_set + # select the rows in the model that extend past the deletion point and apply a lock + self.class.base_class. + select("id"). + where("#{quoted_left_column_name} >= ?", left). + lock(true). + all + + if acts_as_nested_set_options[:dependent] == :destroy + descendants.each do |model| + model.skip_before_destroy = true + model.destroy + end + else + nested_set_scope.delete_all( + ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?", + left, right] + ) + end + + # update lefts and rights for remaining nodes + diff = right - left + 1 + nested_set_scope.update_all( + ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff], + ["#{quoted_left_column_name} > ?", right] + ) + nested_set_scope.update_all( + ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff], + ["#{quoted_right_column_name} > ?", right] + ) + +reload + # Don't allow multiple calls to destroy to corrupt the set + self.skip_before_destroy = true + end + end + + # reload left, right, and parent + def reload_nested_set + reload( + :select => "#{quoted_left_column_name}, #{quoted_right_column_name}, #{quoted_parent_column_name}", + :lock => true + ) + end + + def move_to(target, position) + raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if self.new_record? + run_callbacks :move do + in_tenacious_transaction do + if target.is_a? self.class.base_class + target.reload_nested_set + elsif position != :root + # load object if node is not an object + target = nested_set_scope.find(target) + end + self.reload_nested_set + + unless position == :root || move_possible?(target) + raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree." + end + + bound = case position + when :child; target[right_column_name] + when :left; target[left_column_name] + when :right; target[right_column_name] + 1 + when :root; 1 + else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)." + end + + if bound > self[right_column_name] + bound = bound - 1 + other_bound = self[right_column_name] + 1 + else + other_bound = self[left_column_name] - 1 + end + + # there would be no change + return if bound == self[right_column_name] || bound == self[left_column_name] + + # we have defined the boundaries of two non-overlapping intervals, + # so sorting puts both the intervals and their boundaries in order + a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort + + # select the rows in the model between a and d, and apply a lock + self.class.base_class.select('id').lock(true).where( + ["#{quoted_left_column_name} >= :a and #{quoted_right_column_name} <= :d", {:a => a, :d => d}] + ) + + new_parent = case position + when :child; target.id + when :root; nil + else target[parent_column_name] + end + + self.nested_set_scope.update_all([ + "#{quoted_left_column_name} = CASE " + + "WHEN #{quoted_left_column_name} BETWEEN :a AND :b " + + "THEN #{quoted_left_column_name} + :d - :b " + + "WHEN #{quoted_left_column_name} BETWEEN :c AND :d " + + "THEN #{quoted_left_column_name} + :a - :c " + + "ELSE #{quoted_left_column_name} END, " + + "#{quoted_right_column_name} = CASE " + + "WHEN #{quoted_right_column_name} BETWEEN :a AND :b " + + "THEN #{quoted_right_column_name} + :d - :b " + + "WHEN #{quoted_right_column_name} BETWEEN :c AND :d " + + "THEN #{quoted_right_column_name} + :a - :c " + + "ELSE #{quoted_right_column_name} END, " + + "#{quoted_parent_column_name} = CASE " + + "WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " + + "ELSE #{quoted_parent_column_name} END", + {:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent} + ]) + end + target.reload_nested_set if target + self.reload_nested_set + end + end + + end + + # Mixed into both classes and instances to provide easy access to the column names + module Columns + def left_column_name + acts_as_nested_set_options[:left_column] + end + + def right_column_name + acts_as_nested_set_options[:right_column] + end + + def parent_column_name + acts_as_nested_set_options[:parent_column] + end + + def scope_column_names + Array(acts_as_nested_set_options[:scope]) + end + + def quoted_left_column_name + connection.quote_column_name(left_column_name) + end + + def quoted_right_column_name + connection.quote_column_name(right_column_name) + end + + def quoted_parent_column_name + connection.quote_column_name(parent_column_name) + end + + def quoted_scope_column_names + scope_column_names.collect {|column_name| connection.quote_column_name(column_name) } + end + end + + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fd/fda750af1905cd4d70c559db0d1becde41e7e9ca.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fd/fda750af1905cd4d70c559db0d1becde41e7e9ca.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,50 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class CustomFieldValue + attr_accessor :custom_field, :customized, :value + + def custom_field_id + custom_field.id + end + + def true? + self.value == '1' + end + + def editable? + custom_field.editable? + end + + def visible? + custom_field.visible? + end + + def required? + custom_field.is_required? + end + + def to_s + value.to_s + end + + def validate_value + custom_field.validate_field_value(value).each do |message| + customized.errors.add(:base, custom_field.name + ' ' + message) + end + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fe/fe0e2a7971f5615a462d500c0c1966b4ca458e24.svn-base --- a/.svn/pristine/fe/fe0e2a7971f5615a462d500c0c1966b4ca458e24.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -<%= board_breadcrumb(@board) %> - -
    -<%= link_to_if_authorized l(:label_message_new), - {:controller => 'messages', :action => 'new', :board_id => @board}, - :class => 'icon icon-add', - :onclick => 'showAndScrollTo("add-message", "message_subject"); return false;' %> -<%= watcher_tag(@board, User.current) %> -
    - - - -

    <%=h @board.name %>

    -

    <%=h @board.description %>

    - -<% if @topics.any? %> - - - - - <%= sort_header_tag('created_on', :caption => l(:field_created_on)) %> - <%= sort_header_tag('replies', :caption => l(:label_reply_plural)) %> - <%= sort_header_tag('updated_on', :caption => l(:label_message_last)) %> - - - <% @topics.each do |topic| %> - - - - - - - - <% end %> - -
    <%= l(:field_subject) %><%= l(:field_author) %>
    <%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => topic } %><%= link_to_user(topic.author) %><%= format_time(topic.created_on) %><%= topic.replies_count %> - <% if topic.last_reply %> - <%= authoring topic.last_reply.created_on, topic.last_reply.author %>
    - <%= link_to_message topic.last_reply %> - <% end %> -
    -

    <%= pagination_links_full @topic_pages, @topic_count %>

    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> - -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> -<% end %> - -<% html_title @board.name %> - -<% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %> -<% end %> diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fe/fe2e55a5ec8942da65be048ad7fee177c1286f80.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fe/fe2e55a5ec8942da65be048ad7fee177c1286f80.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddProjectsInheritMembers < ActiveRecord::Migration + def up + add_column :projects, :inherit_members, :boolean, :default => false, :null => false + end + + def down + remove_column :projects, :inherit_members + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/fe/fe4a48627f341dfca44eecc242e7bc59f020ac70.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fe/fe4a48627f341dfca44eecc242e7bc59f020ac70.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,69 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../../test_helper', __FILE__) +begin + require 'mocha/setup' + + class DarcsAdapterTest < ActiveSupport::TestCase + REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s + + if File.directory?(REPOSITORY_PATH) + def setup + @adapter = Redmine::Scm::Adapters::DarcsAdapter.new(REPOSITORY_PATH) + end + + def test_darcsversion + to_test = { "1.0.9 (release)\n" => [1,0,9] , + "2.2.0 (release)\n" => [2,2,0] } + to_test.each do |s, v| + test_darcsversion_for(s, v) + end + end + + def test_revisions + id1 = '20080308225258-98289-761f654d669045eabee90b91b53a21ce5593cadf.gz' + revs = @adapter.revisions('', nil, nil, {:with_path => true}) + assert_equal 6, revs.size + assert_equal id1, revs[5].scmid + paths = revs[5].paths + assert_equal 5, paths.size + assert_equal 'A', paths[0][:action] + assert_equal '/README', paths[0][:path] + assert_equal 'A', paths[1][:action] + assert_equal '/images', paths[1][:path] + end + + private + + def test_darcsversion_for(darcsversion, version) + @adapter.class.expects(:darcs_binary_version_from_command_line).returns(darcsversion) + assert_equal version, @adapter.class.darcs_binary_version + end + + else + puts "Darcs test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end + end + +rescue LoadError + class DarcsMochaFake < ActiveSupport::TestCase + def test_fake; assert(false, "Requires mocha to run those tests") end + end +end + diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ff/ff4ade207165498e6822191c7d47b12871a75858.svn-base --- a/.svn/pristine/ff/ff4ade207165498e6822191c7d47b12871a75858.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,157 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -require File.expand_path('../../test_helper', __FILE__) - -class JournalTest < ActiveSupport::TestCase - fixtures :projects, :issues, :issue_statuses, :journals, :journal_details, - :users, :members, :member_roles, :roles, :enabled_modules, - :projects_trackers, :trackers - - def setup - @journal = Journal.find 1 - end - - def test_journalized_is_an_issue - issue = @journal.issue - assert_kind_of Issue, issue - assert_equal 1, issue.id - end - - def test_new_status - status = @journal.new_status - assert_not_nil status - assert_kind_of IssueStatus, status - assert_equal 2, status.id - end - - def test_create_should_send_email_notification - ActionMailer::Base.deliveries.clear - issue = Issue.find(:first) - user = User.find(:first) - journal = issue.init_journal(user, issue) - - assert journal.save - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_should_not_save_journal_with_blank_notes_and_no_details - journal = Journal.new(:journalized => Issue.first, :user => User.first) - - assert_no_difference 'Journal.count' do - assert_equal false, journal.save - end - end - - def test_create_should_not_split_non_private_notes - assert_difference 'Journal.count' do - assert_no_difference 'JournalDetail.count' do - journal = Journal.generate!(:notes => 'Notes') - end - end - - assert_difference 'Journal.count' do - assert_difference 'JournalDetail.count' do - journal = Journal.generate!(:notes => 'Notes', :details => [JournalDetail.new]) - end - end - - assert_difference 'Journal.count' do - assert_difference 'JournalDetail.count' do - journal = Journal.generate!(:notes => '', :details => [JournalDetail.new]) - end - end - end - - def test_create_should_split_private_notes - assert_difference 'Journal.count' do - assert_no_difference 'JournalDetail.count' do - journal = Journal.generate!(:notes => 'Notes', :private_notes => true) - journal.reload - assert_equal true, journal.private_notes - assert_equal 'Notes', journal.notes - end - end - - assert_difference 'Journal.count', 2 do - assert_difference 'JournalDetail.count' do - journal = Journal.generate!(:notes => 'Notes', :private_notes => true, :details => [JournalDetail.new]) - journal.reload - assert_equal true, journal.private_notes - assert_equal 'Notes', journal.notes - assert_equal 0, journal.details.size - - journal_with_changes = Journal.order('id DESC').offset(1).first - assert_equal false, journal_with_changes.private_notes - assert_nil journal_with_changes.notes - assert_equal 1, journal_with_changes.details.size - assert_equal journal.created_on, journal_with_changes.created_on - end - end - - assert_difference 'Journal.count' do - assert_difference 'JournalDetail.count' do - journal = Journal.generate!(:notes => '', :private_notes => true, :details => [JournalDetail.new]) - journal.reload - assert_equal false, journal.private_notes - assert_equal '', journal.notes - assert_equal 1, journal.details.size - end - end - end - - def test_visible_scope_for_anonymous - # Anonymous user should see issues of public projects only - journals = Journal.visible(User.anonymous).all - assert journals.any? - assert_nil journals.detect {|journal| !journal.issue.project.is_public?} - # Anonymous user should not see issues without permission - Role.anonymous.remove_permission!(:view_issues) - journals = Journal.visible(User.anonymous).all - assert journals.empty? - end - - def test_visible_scope_for_user - user = User.find(9) - assert user.projects.empty? - # Non member user should see issues of public projects only - journals = Journal.visible(user).all - assert journals.any? - assert_nil journals.detect {|journal| !journal.issue.project.is_public?} - # Non member user should not see issues without permission - Role.non_member.remove_permission!(:view_issues) - user.reload - journals = Journal.visible(user).all - assert journals.empty? - # User should see issues of projects for which he has view_issues permissions only - Member.create!(:principal => user, :project_id => 1, :role_ids => [1]) - user.reload - journals = Journal.visible(user).all - assert journals.any? - assert_nil journals.detect {|journal| journal.issue.project_id != 1} - end - - def test_visible_scope_for_admin - user = User.find(1) - user.members.each(&:destroy) - assert user.projects.empty? - journals = Journal.visible(user).all - assert journals.any? - # Admin should see issues on private projects that he does not belong to - assert journals.detect {|journal| !journal.issue.project.is_public?} - end -end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ff/ffca1425f1aa4e1091d7e6036ce643ffe45832ab.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ff/ffca1425f1aa4e1091d7e6036ce643ffe45832ab.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,19 @@ +class CreateProjectsTrackers < ActiveRecord::Migration + def self.up + create_table :projects_trackers, :id => false do |t| + t.column :project_id, :integer, :default => 0, :null => false + t.column :tracker_id, :integer, :default => 0, :null => false + end + add_index :projects_trackers, :project_id, :name => :projects_trackers_project_id + + # Associates all trackers to all projects (as it was before) + tracker_ids = Tracker.all.collect(&:id) + Project.all.each do |project| + project.tracker_ids = tracker_ids + end + end + + def self.down + drop_table :projects_trackers + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ff/ffdc6711dc145662e4ade30a72d7c03a4e4d8769.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ff/ffdc6711dc145662e4ade30a72d7c03a4e4d8769.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,141 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class GroupsController < ApplicationController + layout 'admin' + + before_filter :require_admin + before_filter :find_group, :except => [:index, :new, :create] + accept_api_auth :index, :show, :create, :update, :destroy, :add_users, :remove_user + + helper :custom_fields + + def index + @groups = Group.sorted.all + + respond_to do |format| + format.html + format.api + end + end + + def show + respond_to do |format| + format.html + format.api + end + end + + def new + @group = Group.new + end + + def create + @group = Group.new + @group.safe_attributes = params[:group] + + respond_to do |format| + if @group.save + format.html { + flash[:notice] = l(:notice_successful_create) + redirect_to(params[:continue] ? new_group_path : groups_path) + } + format.api { render :action => 'show', :status => :created, :location => group_url(@group) } + else + format.html { render :action => "new" } + format.api { render_validation_errors(@group) } + end + end + end + + def edit + end + + def update + @group.safe_attributes = params[:group] + + respond_to do |format| + if @group.save + flash[:notice] = l(:notice_successful_update) + format.html { redirect_to(groups_path) } + format.api { render_api_ok } + else + format.html { render :action => "edit" } + format.api { render_validation_errors(@group) } + end + end + end + + def destroy + @group.destroy + + respond_to do |format| + format.html { redirect_to(groups_path) } + format.api { render_api_ok } + end + end + + def add_users + @users = User.find_all_by_id(params[:user_id] || params[:user_ids]) + @group.users << @users if request.post? + respond_to do |format| + format.html { redirect_to edit_group_path(@group, :tab => 'users') } + format.js + format.api { render_api_ok } + end + end + + def remove_user + @group.users.delete(User.find(params[:user_id])) if request.delete? + respond_to do |format| + format.html { redirect_to edit_group_path(@group, :tab => 'users') } + format.js + format.api { render_api_ok } + end + end + + def autocomplete_for_user + respond_to do |format| + format.js + end + end + + def edit_membership + @membership = Member.edit_membership(params[:membership_id], params[:membership], @group) + @membership.save if request.post? + respond_to do |format| + format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } + format.js + end + end + + def destroy_membership + Member.find(params[:membership_id]).destroy if request.post? + respond_to do |format| + format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } + format.js + end + end + + private + + def find_group + @group = Group.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r d98d22a98252 -r afce8026aaeb .svn/pristine/ff/fffb2fa25ea9e4635cf131e9207a44d185b5131c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ff/fffb2fa25ea9e4635cf131e9207a44d185b5131c.svn-base Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,38 @@ + + + + + +<% if Setting.emails_header.present? -%> +<%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_header).html_safe %> +<% end -%> +<%= yield %> +
    +<% if Setting.emails_footer.present? -%> +<%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_footer).html_safe %> +<% end -%> + + diff -r d98d22a98252 -r afce8026aaeb .svn/text-base/.hgignore.svn-base --- a/.svn/text-base/.hgignore.svn-base Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -syntax: glob - -.project -.loadpath -config/additional_environment.rb -config/configuration.yml -config/database.yml -config/email.yml -config/initializers/session_store.rb -coverage -db/*.db -db/*.sqlite3 -db/schema.rb -files/* -lib/redmine/scm/adapters/mercurial/redminehelper.pyc -lib/redmine/scm/adapters/mercurial/redminehelper.pyo -log/*.log* -log/mongrel_debug -public/dispatch.* -public/plugin_assets -tmp/* -tmp/cache/* -tmp/sessions/* -tmp/sockets/* -tmp/test/* -vendor/rails -*.rbc -.svn/ -.git/ diff -r d98d22a98252 -r afce8026aaeb .svn/wc.db Binary file .svn/wc.db has changed diff -r d98d22a98252 -r afce8026aaeb .travis.yml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.travis.yml Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,38 @@ +# Redmine runs tests on own continuous integration server. +# http://www.redmine.org/projects/redmine/wiki/Continuous_integration +# You can also run tests on your environment. +language: ruby +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 + - 2.0 + - jruby +matrix: + allow_failures: + # SCM tests fail randomly due to IO.popen(). + # https://github.com/jruby/jruby/issues/779 + - rvm: jruby +env: + - "TEST_SUITE=units DATABASE_ADAPTER=postgresql" + - "TEST_SUITE=functionals DATABASE_ADAPTER=postgresql" + - "TEST_SUITE=integration DATABASE_ADAPTER=postgresql" + - "TEST_SUITE=units DATABASE_ADAPTER=mysql" + - "TEST_SUITE=functionals DATABASE_ADAPTER=mysql" + - "TEST_SUITE=integration DATABASE_ADAPTER=mysql" + - "TEST_SUITE=units DATABASE_ADAPTER=sqlite3" + - "TEST_SUITE=functionals DATABASE_ADAPTER=sqlite3" + - "TEST_SUITE=integration DATABASE_ADAPTER=sqlite3" +before_install: + - "sudo apt-get update -qq" + - "sudo apt-get --no-install-recommends install bzr cvs git mercurial subversion" +script: + - "SCMS=bazaar,cvs,subversion,git,mercurial,filesystem" + - "export SCMS" + - "git --version" + - "bundle install" + - "RUN_ON_NOT_OFFICIAL='' RUBY_VER=1.9 BRANCH=trunk bundle exec rake config/database.yml" + - "bundle install" + - "JRUBY_OPTS=-J-Xmx1024m bundle exec rake ci" +notifications: + email: false diff -r d98d22a98252 -r afce8026aaeb CONTRIBUTING.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CONTRIBUTING.md Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,8 @@ + +**Do not send a pull requst to this github repository**. + +For more detail, please see [official website] wiki [Contribute]. + +[official website]: http://www.redmine.org +[Contribute]: http://www.redmine.org/projects/redmine/wiki/Contribute + diff -r d98d22a98252 -r afce8026aaeb Gemfile --- a/Gemfile Wed May 07 14:15:02 2014 +0100 +++ b/Gemfile Tue Sep 09 09:34:53 2014 +0100 @@ -1,9 +1,9 @@ -source 'http://rubygems.org' +source 'https://rubygems.org' -gem "rails", "3.2.18" +gem "rails", "3.2.19" +gem "rake", "~> 10.1.1" gem "jquery-rails", "~> 2.0.2" -gem "i18n", "~> 0.6.0" -gem "coderay", "~> 1.0.6" +gem "coderay", "~> 1.1.0" gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] gem "builder", "3.0.0" @@ -18,7 +18,7 @@ # Optional gem for OpenID authentication group :openid do - gem "ruby-openid", "~> 2.1.4", :require => "openid" + gem "ruby-openid", "~> 2.3.0", :require => "openid" gem "rack-openid" end @@ -32,43 +32,47 @@ end end -# Database gems -platforms :mri, :mingw do - group :postgresql do - gem "pg", ">= 0.11.0" - end - - group :sqlite do - gem "sqlite3" - end +platforms :jruby do + # jruby-openssl is bundled with JRuby 1.7.0 + gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0' + gem "activerecord-jdbc-adapter", "~> 1.3.2" end -platforms :mri_18, :mingw_18 do - group :mysql do - gem "mysql", "~> 2.8.1" +# Include database gems for the adapters found in the database +# configuration file +require 'erb' +require 'yaml' +database_file = File.join(File.dirname(__FILE__), "config/database.yml") +if File.exist?(database_file) + database_config = YAML::load(ERB.new(IO.read(database_file)).result) + adapters = database_config.values.map {|c| c['adapter']}.compact.uniq + if adapters.any? + adapters.each do |adapter| + case adapter + when 'mysql2' + gem "mysql2", "~> 0.3.11", :platforms => [:mri, :mingw] + gem "activerecord-jdbcmysql-adapter", :platforms => :jruby + when 'mysql' + gem "mysql", "~> 2.8.1", :platforms => [:mri, :mingw] + gem "activerecord-jdbcmysql-adapter", :platforms => :jruby + when /postgresql/ + gem "pg", ">= 0.11.0", :platforms => [:mri, :mingw] + gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby + when /sqlite3/ + gem "sqlite3", :platforms => [:mri, :mingw] + gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby + when /sqlserver/ + gem "tiny_tds", "~> 0.5.1", :platforms => [:mri, :mingw] + gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw] + else + warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems") + end + end + else + warn("No adapter found in config/database.yml, please configure it first") end -end - -platforms :mri_19, :mingw_19 do - group :mysql do - gem "mysql2", "~> 0.3.11" - end -end - -platforms :jruby do - gem "jruby-openssl" - - group :mysql do - gem "activerecord-jdbcmysql-adapter" - end - - group :postgresql do - gem "activerecord-jdbcpostgresql-adapter" - end - - group :sqlite do - gem "activerecord-jdbcsqlite3-adapter" - end +else + warn("Please configure your config/database.yml first") end group :development do @@ -77,13 +81,13 @@ end group :test do - gem "shoulda", "~> 2.11" - # Shoulda does not work nice on Ruby 1.9.3 and JRuby 1.7. - # It seems to need test-unit explicitely. - platforms = [:mri_19] - platforms << :jruby if defined?(JRUBY_VERSION) && JRUBY_VERSION >= "1.7" - gem "test-unit", :platforms => platforms - gem "mocha", "~> 0.13.3" + gem "shoulda", "~> 3.3.2" + gem "mocha", "~> 1.0.0", :require => 'mocha/api' + if RUBY_VERSION >= '1.9.3' + gem "capybara", "~> 2.1.0" + gem "selenium-webdriver" + gem "database_cleaner" + end end local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") @@ -95,5 +99,6 @@ # Load plugins' Gemfiles Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file| puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v` - instance_eval File.read(file) + #TODO: switch to "eval_gemfile file" when bundler >= 1.2.0 will be required (rails 4) + instance_eval File.read(file), file end diff -r d98d22a98252 -r afce8026aaeb app/controllers/account_controller.rb --- a/app/controllers/account_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/account_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,12 +20,22 @@ include CustomFieldsHelper # prevents login action to be filtered by check_if_login_required application scope filter - skip_before_filter :check_if_login_required + skip_before_filter :check_if_login_required, :check_password_change + + # Overrides ApplicationController#verify_authenticity_token to disable + # token verification on openid callbacks + def verify_authenticity_token + unless using_open_id? + super + end + end # Login request and validation def login if request.get? - logout_user + if User.current.logged? + redirect_to home_url + end else authenticate_user end @@ -36,15 +46,20 @@ # Log out current user and redirect to welcome page def logout - logout_user - redirect_to home_url + if User.current.anonymous? + redirect_to home_url + elsif request.post? + logout_user + redirect_to home_url + end + # display the logout form end # Lets user choose a new password def lost_password - redirect_to(home_url) && return unless Setting.lost_password? + (redirect_to(home_url); return) unless Setting.lost_password? if params[:token] - @token = Token.find_by_action_and_value("recovery", params[:token].to_s) + @token = Token.find_token("recovery", params[:token].to_s) if @token.nil? || @token.expired? redirect_to home_url return @@ -68,11 +83,15 @@ else if request.post? user = User.find_by_mail(params[:mail].to_s) - # user not found or not active - unless user && user.active? + # user not found + unless user flash.now[:error] = l(:notice_account_unknown_email) return end + unless user.active? + handle_inactive_user(user, lost_password_path) + return + end # user cannot change its password unless user.change_password_allowed? flash.now[:error] = l(:notice_can_t_change_password) @@ -92,7 +111,7 @@ # User self-registration def register - redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration] + (redirect_to(home_url); return) unless Setting.self_registration? || session[:auth_source_registration] if request.get? session[:auth_source_registration] = nil @@ -116,7 +135,7 @@ session[:auth_source_registration] = nil self.logged_user = @user flash[:notice] = l(:notice_account_activated) - redirect_to :controller => 'my', :action => 'account' + redirect_to my_account_path end else @user.login = params[:user][:login] @@ -145,11 +164,11 @@ # Token based account activation def activate - redirect_to(home_url) && return unless Setting.self_registration? && params[:token] - token = Token.find_by_action_and_value('register', params[:token]) - redirect_to(home_url) && return unless token and !token.expired? + (redirect_to(home_url); return) unless Setting.self_registration? && params[:token].present? + token = Token.find_token('register', params[:token].to_s) + (redirect_to(home_url); return) unless token and !token.expired? user = token.user - redirect_to(home_url) && return unless user.registered? + (redirect_to(home_url); return) unless user.registered? user.activate if user.save token.destroy @@ -158,6 +177,19 @@ redirect_to signin_path end + # Sends a new account activation email + def activation_email + if session[:registered_user_id] && Setting.self_registration == '1' + user_id = session.delete(:registered_user_id).to_i + user = User.find_by_id(user_id) + if user && user.registered? + register_by_email_activation(user) + return + end + end + redirect_to(home_url) + end + private def authenticate_user @@ -169,7 +201,7 @@ end def password_authentication - user = User.try_to_login(params[:username], params[:password]) + user = User.try_to_login(params[:username], params[:password], false) if user.nil? invalid_credentials @@ -177,25 +209,31 @@ onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id }) else # Valid user - successful_authentication(user) + if user.active? + successful_authentication(user) + else + handle_inactive_user(user) + end end end def open_id_authenticate(openid_url) - authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url, :method => :post) do |result, identity_url, registration| + back_url = signin_url(:autologin => params[:autologin]) + authenticate_with_open_id( + openid_url, :required => [:nickname, :fullname, :email], + :return_to => back_url, :method => :post + ) do |result, identity_url, registration| if result.successful? user = User.find_or_initialize_by_identity_url(identity_url) if user.new_record? # Self-registration off - redirect_to(home_url) && return unless Setting.self_registration? - + (redirect_to(home_url); return) unless Setting.self_registration? # Create on the fly user.login = registration['nickname'] unless registration['nickname'].nil? user.mail = registration['email'] unless registration['email'].nil? user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil? user.random_password user.register - case Setting.self_registration when '1' register_by_email_activation(user) do @@ -215,7 +253,7 @@ if user.active? successful_authentication(user) else - account_pending + handle_inactive_user(user) end end end @@ -231,12 +269,11 @@ set_autologin_cookie(user) end call_hook(:controller_account_success_authentication_after, {:user => user }) - redirect_back_or_default :controller => 'my', :action => 'page' + redirect_back_or_default my_page_path end def set_autologin_cookie(user) token = Token.create(:user => user, :action => 'autologin') - cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin' cookie_options = { :value => token.value, :expires => 1.year.from_now, @@ -244,7 +281,7 @@ :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), :httponly => true } - cookies[cookie_name] = cookie_options + cookies[autologin_cookie_name] = cookie_options end # Onthefly creation failed, display the registration form to fill/fix attributes @@ -266,7 +303,7 @@ token = Token.new(:user => user, :action => "register") if user.save and token.save Mailer.register(token).deliver - flash[:notice] = l(:notice_account_register_done) + flash[:notice] = l(:notice_account_register_done, :email => user.mail) redirect_to signin_path else yield if block_given? @@ -283,7 +320,7 @@ if user.save self.logged_user = user flash[:notice] = l(:notice_account_activated) - redirect_to :controller => 'my', :action => 'account' + redirect_to my_account_path else yield if block_given? end @@ -299,14 +336,32 @@ # Sends an email to the administrators Mailer.account_activation_request(user).deliver - account_pending + account_pending(user) else yield if block_given? end end - def account_pending - flash[:notice] = l(:notice_account_pending) - redirect_to signin_path + def handle_inactive_user(user, redirect_path=signin_path) + if user.registered? + account_pending(user, redirect_path) + else + account_locked(user, redirect_path) + end + end + + def account_pending(user, redirect_path=signin_path) + if Setting.self_registration == '1' + flash[:error] = l(:notice_account_not_activated_yet, :url => activation_email_path) + session[:registered_user_id] = user.id + else + flash[:error] = l(:notice_account_pending) + end + redirect_to redirect_path + end + + def account_locked(user, redirect_path=signin_path) + flash[:error] = l(:notice_account_locked) + redirect_to redirect_path end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/activities_controller.rb --- a/app/controllers/activities_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/activities_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/controllers/admin_controller.rb --- a/app/controllers/admin_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/admin_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,19 +23,18 @@ before_filter :require_admin helper :sort - include SortHelper + include SortHelper def index @no_configuration_data = Redmine::DefaultData::Loader::no_data? end - + def projects @status = params[:status] || 1 - scope = Project.status(@status) + scope = Project.status(@status).order('lft') scope = scope.like(params[:name]) if params[:name].present? - - @projects = scope.all(:order => 'lft') + @projects = scope.all render :action => "projects", :layout => false if request.xhr? end @@ -55,7 +54,7 @@ flash[:error] = l(:error_can_t_load_default_data, e.message) end end - redirect_to :action => 'index' + redirect_to admin_path end def test_email @@ -66,10 +65,10 @@ @test = Mailer.test_email(User.current).deliver flash[:notice] = l(:notice_email_sent, User.current.mail) rescue Exception => e - flash[:error] = l(:notice_email_error, e.message) + flash[:error] = l(:notice_email_error, Redmine::CodesetUtil.replace_invalid_utf8(e.message.dup)) end ActionMailer::Base.raise_delivery_errors = raise_delivery_errors - redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications' + redirect_to settings_path(:tab => 'notifications') end def info @@ -78,7 +77,8 @@ [:text_default_administrator_account_changed, User.default_admin_account_changed?], [:text_file_repository_writable, File.writable?(Attachment.storage_path)], [:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)], - [:text_rmagick_available, Object.const_defined?(:Magick)] + [:text_rmagick_available, Object.const_defined?(:Magick)], + [:text_convert_available, Redmine::Thumbnail.convert_available?] ] end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/application_controller.rb --- a/app/controllers/application_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/application_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -22,6 +22,9 @@ class ApplicationController < ActionController::Base include Redmine::I18n + include Redmine::Pagination + include RoutesHelper + helper :routes class_attribute :accept_api_auth_actions class_attribute :accept_rss_auth_actions @@ -30,14 +33,24 @@ layout 'base' protect_from_forgery - def handle_unverified_request - super - cookies.delete(:autologin) + + def verify_authenticity_token + unless api_request? + super + end end - before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization + def handle_unverified_request + unless api_request? + super + cookies.delete(autologin_cookie_name) + self.logged_user = nil + render_error :status => 422, :message => "Invalid form authenticity token." + end + end - rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token + before_filter :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization + rescue_from ::Unauthorized, :with => :deny_access rescue_from ::ActionView::MissingTemplate, :with => :missing_template @@ -75,6 +88,9 @@ session[:user_id] = user.id session[:ctime] = Time.now.utc.to_i session[:atime] = Time.now.utc.to_i + if user.must_change_password? + session[:pwd] = '1' + end end def user_setup @@ -109,6 +125,10 @@ authenticate_with_http_basic do |username, password| user = User.try_to_login(username, password) || User.find_by_api_key(username) end + if user && user.must_change_password? + render_error :message => 'You must change your password', :status => 403 + return + end end # Switch user if requested by an admin user if user && user.admin? && (username = api_switch_user_from_request) @@ -124,10 +144,14 @@ user end + def autologin_cookie_name + Redmine::Configuration['autologin_cookie_name'].presence || 'autologin' + end + def try_to_autologin - if cookies[:autologin] && Setting.autologin? + if cookies[autologin_cookie_name] && Setting.autologin? # auto-login feature starts a new session - user = User.try_to_autologin(cookies[:autologin]) + user = User.try_to_autologin(cookies[autologin_cookie_name]) if user reset_session start_user_session(user) @@ -150,7 +174,7 @@ # Logs out current user def logout_user if User.current.logged? - cookies.delete :autologin + cookies.delete(autologin_cookie_name) Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) self.logged_user = nil end @@ -163,6 +187,16 @@ require_login if Setting.login_required? end + def check_password_change + if session[:pwd] + if User.current.must_change_password? + redirect_to my_password_path + else + session.delete(:pwd) + end + end + end + def set_localization lang = nil if User.current.logged? @@ -188,7 +222,13 @@ url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id]) end respond_to do |format| - format.html { redirect_to :controller => "account", :action => "login", :back_url => url } + format.html { + if request.xhr? + head :unauthorized + else + redirect_to :controller => "account", :action => "login", :back_url => url + end + } format.atom { redirect_to :controller => "account", :action => "login", :back_url => url } format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } @@ -291,7 +331,7 @@ # Find issues with a single :id param or :ids array param # Raises a Unauthorized exception if one of the issues is not visible def find_issues - @issues = Issue.find_all_by_id(params[:id] || params[:ids]) + @issues = Issue.where(:id => (params[:id] || params[:ids])).preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to).to_a raise ActiveRecord::RecordNotFound if @issues.empty? raise Unauthorized unless @issues.all?(&:visible?) @projects = @issues.collect(&:project).compact.uniq @@ -300,6 +340,16 @@ render_404 end + def find_attachments + if (attachments = params[:attachments]).present? + att = attachments.values.collect do |attachment| + Attachment.find_by_token( attachment[:token] ) if attachment[:token].present? + end + att.compact! + end + @attachments = att || [] + end + # make sure that the user is a member of the project (or admin) if project is private # used as a before_filter for actions that do not require any particular permission on the project def check_project_privacy @@ -326,11 +376,7 @@ def redirect_back_or_default(default) back_url = params[:back_url].to_s - if back_url.present? - begin - uri = URI.parse(back_url) - # do not redirect user to another host or to the login or register page - if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)}) + if back_url.present? && valid_back_url?(back_url) # soundsoftware: if back_url is the home page, # change it to My Page (#125) if (uri.path == home_path) @@ -346,18 +392,41 @@ uri.scheme = "https" end back_url = uri.to_s - redirect_to(back_url) - return - end - rescue URI::InvalidURIError - logger.warn("Could not redirect to invalid URL #{back_url}") - # redirect to default - end + redirect_to(back_url) + return end redirect_to default false end + # Returns true if back_url is a valid url for redirection, otherwise false + def valid_back_url?(back_url) + if CGI.unescape(back_url).include?('..') + return false + end + + begin + uri = URI.parse(back_url) + rescue URI::InvalidURIError + return false + end + + if uri.host.present? && uri.host != request.host + return false + end + + if uri.path.match(%r{/(login|account/register)}) + return false + end + + if relative_url_root.present? && !uri.path.starts_with?(relative_url_root) + return false + end + + return true + end + private :valid_back_url? + # Redirects to the request referer if present, redirects to args or call block otherwise. def redirect_to_referer_or(*args, &block) redirect_to :back @@ -425,13 +494,6 @@ request.xhr? ? false : 'base' end - def invalid_authenticity_token - if api_request? - logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)." - end - render_error "Invalid form authenticity token. Perhaps your session has timed out; try reloading the form and entering your details again." - end - def render_feed(items, options={}) @items = items || [] @items.sort! {|x,y| y.event_datetime <=> x.event_datetime } @@ -527,7 +589,7 @@ # Returns a string that can be used as filename value in Content-Disposition header def filename_for_content_disposition(name) - request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name + request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident)} ? ERB::Util.url_encode(name) : name end def api_request? @@ -553,21 +615,6 @@ flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present? end - # Sets the `flash` notice or error based the number of issues that did not save - # - # @param [Array, Issue] issues all of the saved and unsaved Issues - # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved - def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids) - if unsaved_issue_ids.empty? - flash[:notice] = l(:notice_successful_update) unless issues.empty? - else - flash[:error] = l(:notice_failed_to_save_issues, - :count => unsaved_issue_ids.size, - :total => issues.size, - :ids => '#' + unsaved_issue_ids.join(', #')) - end - end - # Rescues an invalid query statement. Just in case... def query_statement_invalid(exception) logger.error "Query::StatementInvalid: #{exception.message}" if logger diff -r d98d22a98252 -r afce8026aaeb app/controllers/attachments_controller.rb --- a/app/controllers/attachments_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/attachments_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -91,15 +91,17 @@ @attachment = Attachment.new(:file => request.raw_post) @attachment.author = User.current @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16) + saved = @attachment.save - if @attachment.save - respond_to do |format| - format.api { render :action => 'upload', :status => :created } - end - else - respond_to do |format| - format.api { render_validation_errors(@attachment) } - end + respond_to do |format| + format.js + format.api { + if saved + render :action => 'upload', :status => :created + else + render_validation_errors(@attachment) + end + } end end @@ -107,9 +109,17 @@ if @attachment.container.respond_to?(:init_journal) @attachment.container.init_journal(User.current) end - # Make sure association callbacks are called - @attachment.container.attachments.delete(@attachment) - redirect_to_referer_or project_path(@project) + if @attachment.container + # Make sure association callbacks are called + @attachment.container.attachments.delete(@attachment) + else + @attachment.destroy + end + + respond_to do |format| + format.html { redirect_to_referer_or project_path(@project) } + format.js + end end def toggle_active @@ -132,7 +142,12 @@ # Checks that the file exists and is readable def file_readable - @attachment.readable? ? true : render_404 + if @attachment.readable? + true + else + logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable." + render_404 + end end def read_authorize diff -r d98d22a98252 -r afce8026aaeb app/controllers/auth_sources_controller.rb --- a/app/controllers/auth_sources_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/auth_sources_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,57 +20,77 @@ menu_item :ldap_authentication before_filter :require_admin + before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy] def index - @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10 + @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25 end def new klass_name = params[:type] || 'AuthSourceLdap' @auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) + render_404 unless @auth_source end def create @auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) if @auth_source.save flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to auth_sources_path else render :action => 'new' end end def edit - @auth_source = AuthSource.find(params[:id]) end def update - @auth_source = AuthSource.find(params[:id]) if @auth_source.update_attributes(params[:auth_source]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to auth_sources_path else render :action => 'edit' end end def test_connection - @auth_source = AuthSource.find(params[:id]) begin @auth_source.test_connection flash[:notice] = l(:notice_successful_connection) rescue Exception => e flash[:error] = l(:error_unable_to_connect, e.message) end - redirect_to :action => 'index' + redirect_to auth_sources_path end def destroy - @auth_source = AuthSource.find(params[:id]) - unless @auth_source.users.find(:first) + unless @auth_source.users.exists? @auth_source.destroy flash[:notice] = l(:notice_successful_delete) end - redirect_to :action => 'index' + redirect_to auth_sources_path + end + + def autocomplete_for_new_user + results = AuthSource.search(params[:term]) + + render :json => results.map {|result| { + 'value' => result[:login], + 'label' => "#{result[:login]} (#{result[:firstname]} #{result[:lastname]})", + 'login' => result[:login].to_s, + 'firstname' => result[:firstname].to_s, + 'lastname' => result[:lastname].to_s, + 'mail' => result[:mail].to_s, + 'auth_source_id' => result[:auth_source_id].to_s + }} + end + + private + + def find_auth_source + @auth_source = AuthSource.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/auto_completes_controller.rb --- a/app/controllers/auto_completes_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/auto_completes_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,10 +23,10 @@ q = (params[:q] || params[:term]).to_s.strip if q.present? scope = (params[:scope] == "all" || @project.nil? ? Issue : @project.issues).visible - if q.match(/^\d+$/) - @issues << scope.find_by_id(q.to_i) + if q.match(/\A#?(\d+)\z/) + @issues << scope.find_by_id($1.to_i) end - @issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%").order("#{Issue.table_name}.id DESC").limit(10).all + @issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE LOWER(?)", "%#{q}%").order("#{Issue.table_name}.id DESC").limit(10).all @issues.compact! end render :layout => false diff -r d98d22a98252 -r afce8026aaeb app/controllers/boards_controller.rb --- a/app/controllers/boards_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/boards_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,7 +25,7 @@ helper :watchers def index - @boards = @project.boards.includes(:last_message => :author).all + @boards = @project.boards.includes(:project, :last_message => :author).all # show the board if there is only one if @boards.size == 1 @board = @boards.first @@ -37,17 +37,17 @@ respond_to do |format| format.html { sort_init 'updated_on', 'desc' - sort_update 'created_on' => "#{Message.table_name}.created_on", + sort_update 'created_on' => "#{Message.table_name}.created_on", 'replies' => "#{Message.table_name}.replies_count", 'updated_on' => "COALESCE(last_replies_messages.created_on, #{Message.table_name}.created_on)" @topic_count = @board.topics.count - @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page'] + @topic_pages = Paginator.new @topic_count, per_page_option, params['page'] @topics = @board.topics. reorder("#{Message.table_name}.sticky DESC"). includes(:last_reply). - limit(@topic_pages.items_per_page). - offset(@topic_pages.current.offset). + limit(@topic_pages.per_page). + offset(@topic_pages.offset). order(sort_clause). preload(:author, {:last_reply => :author}). all @@ -55,9 +55,11 @@ render :action => 'show', :layout => !request.xhr? } format.atom { - @messages = @board.messages.find :all, :order => 'created_on DESC', - :include => [:author, :board], - :limit => Setting.feeds_limit.to_i + @messages = @board.messages. + reorder('created_on DESC'). + includes(:author, :board). + limit(Setting.feeds_limit.to_i). + all render_feed(@messages, :title => "#{@project}: #{@board}") } end @@ -98,7 +100,7 @@ private def redirect_to_settings_in_projects - redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards' + redirect_to settings_project_path(@project, :tab => 'boards') end def find_board_if_available diff -r d98d22a98252 -r afce8026aaeb app/controllers/calendars_controller.rb --- a/app/controllers/calendars_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/calendars_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/controllers/comments_controller.rb --- a/app/controllers/comments_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/comments_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -32,12 +32,12 @@ flash[:notice] = l(:label_comment_added) end - redirect_to :controller => 'news', :action => 'show', :id => @news + redirect_to news_path(@news) end def destroy @news.comments.find(params[:comment_id]).destroy - redirect_to :controller => 'news', :action => 'show', :id => @news + redirect_to news_path(@news) end private diff -r d98d22a98252 -r afce8026aaeb app/controllers/context_menus_controller.rb --- a/app/controllers/context_menus_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/context_menus_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,16 +19,15 @@ helper :watchers helper :issues + before_filter :find_issues, :only => :issues + def issues - @issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project) if (@issues.size == 1) @issue = @issues.first end @issue_ids = @issues.map(&:id).sort @allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) - @projects = @issues.collect(&:project).compact.uniq - @project = @projects.first if @projects.size == 1 @can = {:edit => User.current.allowed_to?(:edit_issues, @projects), :log_time => (@project && User.current.allowed_to?(:log_time, @project)), @@ -72,8 +71,9 @@ end def time_entries - @time_entries = TimeEntry.all( - :conditions => {:id => params[:ids]}, :include => :project) + @time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a + (render_404; return) unless @time_entries.present? + @projects = @time_entries.collect(&:project).compact.uniq @project = @projects.first if @projects.size == 1 @activities = TimeEntryActivity.shared.active diff -r d98d22a98252 -r afce8026aaeb app/controllers/custom_fields_controller.rb --- a/app/controllers/custom_fields_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/custom_fields_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -21,20 +21,28 @@ before_filter :require_admin before_filter :build_new_custom_field, :only => [:new, :create] before_filter :find_custom_field, :only => [:edit, :update, :destroy] + accept_api_auth :index def index - @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name } - @tab = params[:tab] || 'IssueCustomField' + respond_to do |format| + format.html { + @custom_fields_by_type = CustomField.all.group_by {|f| f.class.name } + @tab = params[:tab] || 'IssueCustomField' + } + format.api { + @custom_fields = CustomField.all + } + end end def new end def create - if request.post? and @custom_field.save + if @custom_field.save flash[:notice] = l(:notice_successful_create) call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field) - redirect_to :action => 'index', :tab => @custom_field.class.name + redirect_to custom_fields_path(:tab => @custom_field.class.name) else render :action => 'new' end @@ -44,21 +52,22 @@ end def update - if request.put? and @custom_field.update_attributes(params[:custom_field]) + if @custom_field.update_attributes(params[:custom_field]) flash[:notice] = l(:notice_successful_update) call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field) - redirect_to :action => 'index', :tab => @custom_field.class.name + redirect_to custom_fields_path(:tab => @custom_field.class.name) else render :action => 'edit' end end def destroy - @custom_field.destroy - redirect_to :action => 'index', :tab => @custom_field.class.name - rescue - flash[:error] = l(:error_can_not_delete_custom_field) - redirect_to :action => 'index' + begin + @custom_field.destroy + rescue + flash[:error] = l(:error_can_not_delete_custom_field) + end + redirect_to custom_fields_path(:tab => @custom_field.class.name) end private @@ -67,6 +76,8 @@ @custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field]) if @custom_field.nil? render_404 + else + @custom_field.default_value = nil end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/documents_controller.rb --- a/app/controllers/documents_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/documents_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -27,7 +27,7 @@ def index @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category' - documents = @project.documents.find :all, :include => [:attachments, :category] + documents = @project.documents.includes(:attachments, :category).all case @sort_by when 'date' @grouped = documents.group_by {|d| d.updated_on.to_date } @@ -43,7 +43,7 @@ end def show - @attachments = @document.attachments.find(:all, :order => "created_on DESC") + @attachments = @document.attachments.all end def new @@ -58,7 +58,7 @@ if @document.save render_attachment_warning_if_needed(@document) flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index', :project_id => @project + redirect_to project_documents_path(@project) else render :action => 'new' end @@ -71,7 +71,7 @@ @document.safe_attributes = params[:document] if request.put? and @document.save flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'show', :id => @document + redirect_to document_path(@document) else render :action => 'edit' end @@ -79,7 +79,7 @@ def destroy @document.destroy if request.delete? - redirect_to :controller => 'documents', :action => 'index', :project_id => @project + redirect_to project_documents_path(@project) end def add_attachment @@ -89,6 +89,6 @@ if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') Mailer.attachments_added(attachments[:files]).deliver end - redirect_to :action => 'show', :id => @document + redirect_to document_path(@document) end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/enumerations_controller.rb --- a/app/controllers/enumerations_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/enumerations_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -46,7 +46,7 @@ def create if request.post? && @enumeration.save flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to enumerations_path else render :action => 'new' end @@ -58,7 +58,7 @@ def update if request.put? && @enumeration.update_attributes(params[:enumeration]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to enumerations_path else render :action => 'edit' end @@ -68,16 +68,14 @@ if !@enumeration.in_use? # No associated objects @enumeration.destroy - redirect_to :action => 'index' + redirect_to enumerations_path return - elsif params[:reassign_to_id] - if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id]) - @enumeration.destroy(reassign_to) - redirect_to :action => 'index' - return - end + elsif params[:reassign_to_id].present? && (reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id].to_i)) + @enumeration.destroy(reassign_to) + redirect_to enumerations_path + return end - @enumerations = @enumeration.class.all - [@enumeration] + @enumerations = @enumeration.class.system.all - [@enumeration] end private diff -r d98d22a98252 -r afce8026aaeb app/controllers/files_controller.rb --- a/app/controllers/files_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/files_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -32,8 +32,8 @@ 'size' => "#{Attachment.table_name}.filesize", 'downloads' => "#{Attachment.table_name}.downloads" - @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)] - @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse + @containers = [ Project.includes(:attachments).reorder(sort_clause).find(@project.id)] + @containers += @project.versions.includes(:attachments).reorder(sort_clause).all.sort.reverse render :layout => !request.xhr? end diff -r d98d22a98252 -r afce8026aaeb app/controllers/gantts_controller.rb --- a/app/controllers/gantts_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/gantts_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/controllers/groups_controller.rb --- a/app/controllers/groups_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/groups_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -84,7 +84,7 @@ @group.destroy respond_to do |format| - format.html { redirect_to(groups_url) } + format.html { redirect_to(groups_path) } format.api { render_api_ok } end end @@ -93,7 +93,7 @@ @users = User.find_all_by_id(params[:user_id] || params[:user_ids]) @group.users << @users if request.post? respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } + format.html { redirect_to edit_group_path(@group, :tab => 'users') } format.js format.api { render_api_ok } end @@ -102,22 +102,23 @@ def remove_user @group.users.delete(User.find(params[:user_id])) if request.delete? respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } + format.html { redirect_to edit_group_path(@group, :tab => 'users') } format.js format.api { render_api_ok } end end def autocomplete_for_user - @users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100) - render :layout => false + respond_to do |format| + format.js + end end def edit_membership @membership = Member.edit_membership(params[:membership_id], params[:membership], @group) @membership.save if request.post? respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } + format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } format.js end end @@ -125,7 +126,7 @@ def destroy_membership Member.find(params[:membership_id]).destroy if request.post? respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } + format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } format.js end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/issue_categories_controller.rb --- a/app/controllers/issue_categories_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/issue_categories_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,14 +26,14 @@ def index respond_to do |format| - format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project } + format.html { redirect_to_settings_in_projects } format.api { @categories = @project.issue_categories.all } end end def show respond_to do |format| - format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project } + format.html { redirect_to_settings_in_projects } format.api end end @@ -55,7 +55,7 @@ respond_to do |format| format.html do flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project + redirect_to_settings_in_projects end format.js format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) } @@ -78,7 +78,7 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project + redirect_to_settings_in_projects } format.api { render_api_ok } end @@ -99,7 +99,7 @@ end @category.destroy(reassign_to) respond_to do |format| - format.html { redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' } + format.html { redirect_to_settings_in_projects } format.api { render_api_ok } end return @@ -107,7 +107,12 @@ @categories = @project.issue_categories - [@category] end -private + private + + def redirect_to_settings_in_projects + redirect_to settings_project_path(@project, :tab => 'categories') + end + # Wrap ApplicationController's find_model_object method to set # @category instead of just @issue_category def find_model_object diff -r d98d22a98252 -r afce8026aaeb app/controllers/issue_relations_controller.rb --- a/app/controllers/issue_relations_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/issue_relations_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -48,9 +48,9 @@ saved = @relation.save respond_to do |format| - format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } + format.html { redirect_to issue_path(@issue) } format.js { - @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } + @relations = @issue.reload.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } } format.api { if saved @@ -67,7 +67,7 @@ @relation.destroy respond_to do |format| - format.html { redirect_to issue_path } # TODO : does this really work since @issue is always nil? What is it useful to? + format.html { redirect_to issue_path(@relation.issue_from) } format.js format.api { render_api_ok } end diff -r d98d22a98252 -r afce8026aaeb app/controllers/issue_statuses_controller.rb --- a/app/controllers/issue_statuses_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/issue_statuses_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,7 +25,7 @@ def index respond_to do |format| format.html { - @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position" + @issue_status_pages, @issue_statuses = paginate IssueStatus.sorted, :per_page => 25 render :action => "index", :layout => false if request.xhr? } format.api { @@ -40,9 +40,9 @@ def create @issue_status = IssueStatus.new(params[:issue_status]) - if request.post? && @issue_status.save + if @issue_status.save flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to issue_statuses_path else render :action => 'new' end @@ -54,9 +54,9 @@ def update @issue_status = IssueStatus.find(params[:id]) - if request.put? && @issue_status.update_attributes(params[:issue_status]) + if @issue_status.update_attributes(params[:issue_status]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to issue_statuses_path else render :action => 'edit' end @@ -64,11 +64,11 @@ def destroy IssueStatus.find(params[:id]).destroy - redirect_to :action => 'index' + redirect_to issue_statuses_path rescue flash[:error] = l(:error_unable_delete_issue_status) - redirect_to :action => 'index' - end + redirect_to issue_statuses_path + end def update_issue_done_ratio if request.post? && IssueStatus.update_issue_done_ratios @@ -76,6 +76,6 @@ else flash[:error] = l(:error_issue_done_ratios_not_updated) end - redirect_to :action => 'index' + redirect_to issue_statuses_path end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/issues_controller.rb --- a/app/controllers/issues_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/issues_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -21,11 +21,11 @@ before_filter :find_issue, :only => [:show, :edit, :update] before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] - before_filter :find_project, :only => [:new, :create] + before_filter :find_project, :only => [:new, :create, :update_form] before_filter :authorize, :except => [:index] before_filter :find_optional_project, :only => [:index] before_filter :check_for_default_issue_status, :only => [:new, :create] - before_filter :build_new_issue_from_params, :only => [:new, :create] + before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form] accept_rss_auth :index, :show accept_api_auth :index, :show, :create, :update, :destroy @@ -71,8 +71,8 @@ end @issue_count = @query.issue_count - @issue_pages = Paginator.new self, @issue_count, @limit, params['page'] - @offset ||= @issue_pages.current.offset + @issue_pages = Paginator.new @issue_count, @limit, params['page'] + @offset ||= @issue_pages.offset @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], :order => sort_clause, :offset => @offset, @@ -85,8 +85,8 @@ Issue.load_visible_relations(@issues) if include_in_api_response?('relations') } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } - format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') } - format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') } + format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') } + format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') } end else respond_to do |format| @@ -103,6 +103,9 @@ @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all @journals.each_with_index {|j,i| j.indice = i+1} @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project) + Journal.preload_journals_details_custom_fields(@journals) + # TODO: use #select! when ruby1.8 support is dropped + @journals.reject! {|journal| !journal.notes? && journal.visible_details.empty?} @journals.reverse! if User.current.wants_comments_in_reverse_order? @changesets = @issue.changesets.visible.all @@ -113,6 +116,8 @@ @edit_allowed = User.current.allowed_to?(:edit_issues, @project) @priorities = IssuePriority.active @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) + @relation = IssueRelation.new + respond_to do |format| format.html { retrieve_previous_and_next_issue_ids @@ -132,7 +137,6 @@ def new respond_to do |format| format.html { render :action => 'new', :layout => !request.xhr? } - format.js { render :partial => 'update_form' } end end @@ -154,8 +158,12 @@ format.html { render_attachment_warning_if_needed(@issue) flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject)) - redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } : - { :action => 'show', :id => @issue }) + if params[:continue] + attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} + redirect_to new_project_issue_path(@issue.project, :issue => attrs) + else + redirect_to issue_path(@issue) + end } format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } end @@ -182,7 +190,7 @@ @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads])) saved = false begin - saved = @issue.save_issue_with_child_records(params, @time_entry) + saved = save_issue_with_child_records rescue ActiveRecord::StaleObjectError @conflict = true if params[:last_journal_id] @@ -196,7 +204,7 @@ flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? respond_to do |format| - format.html { redirect_back_or_default({:action => 'show', :id => @issue}) } + format.html { redirect_back_or_default issue_path(@issue) } format.api { render_api_ok } end else @@ -207,6 +215,11 @@ end end + # Updates the issue form when changing the project, status or tracker + # on issue creation/update + def update_form + end + # Bulk edit/copy a set of issues def bulk_edit @issues.sort! @@ -229,7 +242,7 @@ else @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) end - @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&) + @custom_fields = target_projects.map{|p|p.all_issue_custom_fields.visible}.reduce(:&) @assignables = target_projects.map(&:assignable_users).reduce(:&) @trackers = target_projects.map(&:trackers).reduce(:&) @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&) @@ -240,7 +253,9 @@ end @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&) - render :layout => false if request.xhr? + + @issue_params = params[:issue] || {} + @issue_params[:custom_field_values] ||= {} end def bulk_update @@ -248,8 +263,8 @@ @copy = params[:copy].present? attributes = parse_params_for_bulk_issue_attributes(params) - unsaved_issue_ids = [] - moved_issues = [] + unsaved_issues = [] + saved_issues = [] if @copy && params[:copy_subtasks].present? # Descendant issues will be copied with the parent task @@ -257,39 +272,48 @@ @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}} end - @issues.each do |issue| - issue.reload + @issues.each do |orig_issue| + orig_issue.reload if @copy - issue = issue.copy({}, + issue = orig_issue.copy({}, :attachments => params[:copy_attachments].present?, :subtasks => params[:copy_subtasks].present? ) + else + issue = orig_issue end journal = issue.init_journal(User.current, params[:notes]) issue.safe_attributes = attributes call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) if issue.save - moved_issues << issue + saved_issues << issue else - # Keep unsaved issue ids to display them in flash error - unsaved_issue_ids << issue.id + unsaved_issues << orig_issue end end - set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) - if params[:follow] - if @issues.size == 1 && moved_issues.size == 1 - redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first - elsif moved_issues.map(&:project).uniq.size == 1 - redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first + if unsaved_issues.empty? + flash[:notice] = l(:notice_successful_update) unless saved_issues.empty? + if params[:follow] + if @issues.size == 1 && saved_issues.size == 1 + redirect_to issue_path(saved_issues.first) + elsif saved_issues.map(&:project).uniq.size == 1 + redirect_to project_issues_path(saved_issues.map(&:project).first) + end + else + redirect_back_or_default _project_issues_path(@project) end else - redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project}) + @saved_issues = @issues + @unsaved_issues = unsaved_issues + @issues = Issue.visible.find_all_by_id(@unsaved_issues.map(&:id)) + bulk_edit + render :action => 'bulk_edit' end end def destroy - @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f + @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f if @hours > 0 case params[:todo] when 'destroy' @@ -317,7 +341,7 @@ end end respond_to do |format| - format.html { redirect_back_or_default(:action => 'index', :project_id => @project) } + format.html { redirect_back_or_default _project_issues_path(@project) } format.api { render_api_ok } end end @@ -423,7 +447,7 @@ @issue.safe_attributes = params[:issue] @priorities = IssuePriority.active - @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) + @allowed_statuses = @issue.new_statuses_allowed_to(User.current, @issue.new_record?) @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq end @@ -449,4 +473,26 @@ end attributes end + + # Saves @issue and a time_entry from the parameters + def save_issue_with_child_records + Issue.transaction do + if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project) + time_entry = @time_entry || TimeEntry.new + time_entry.project = @issue.project + time_entry.issue = @issue + time_entry.user = User.current + time_entry.spent_on = User.current.today + time_entry.attributes = params[:time_entry] + @issue.time_entries << time_entry + end + + call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal}) + if @issue.save + call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal}) + else + raise ActiveRecord::Rollback + end + end + end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/journals_controller.rb --- a/app/controllers/journals_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/journals_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -80,7 +80,7 @@ @journal.destroy if @journal.details.empty? && @journal.notes.blank? call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params}) respond_to do |format| - format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id } + format.html { redirect_to issue_path(@journal.journalized) } format.js { render :action => 'update' } end else diff -r d98d22a98252 -r afce8026aaeb app/controllers/mail_handler_controller.rb --- a/app/controllers/mail_handler_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/mail_handler_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/controllers/members_controller.rb --- a/app/controllers/members_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/members_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,21 +25,19 @@ accept_api_auth :index, :show, :create, :update, :destroy def index + @offset, @limit = api_offset_and_limit + @member_count = @project.member_principals.count + @member_pages = Paginator.new @member_count, @limit, params['page'] + @offset ||= @member_pages.offset + @members = @project.member_principals.all( + :order => "#{Member.table_name}.id", + :limit => @limit, + :offset => @offset + ) + respond_to do |format| - format.html { - render :layout => false if request.xhr? - } - format.api { - @offset, @limit = api_offset_and_limit - @member_count = @project.member_principals.count - @member_pages = Paginator.new self, @member_count, @limit, params['page'] - @offset ||= @member_pages.current.offset - @members = @project.member_principals.all( - :order => "#{Member.table_name}.id", - :limit => @limit, - :offset => @offset - ) - } + format.html { head 406 } + format.api end end @@ -76,7 +74,7 @@ end respond_to do |format| - format.html { redirect_to :action => 'index', :project_id => @project } + format.html { redirect_to_settings_in_projects } format.js { @members = members } format.api { @member = members.first @@ -95,7 +93,7 @@ end saved = @member.save respond_to do |format| - format.html { redirect_to :action => 'index', :project_id => @project } + format.html { redirect_to_settings_in_projects } format.js format.api { if saved @@ -112,7 +110,7 @@ @member.destroy end respond_to do |format| - format.html { redirect_to :action => 'index', :project_id => @project } + format.html { redirect_to_settings_in_projects } format.js format.api { if @member.destroyed? @@ -129,4 +127,10 @@ logger.debug "Query for #{params[:q]} returned #{@principals.size} results" render :layout => false end + + private + + def redirect_to_settings_in_projects + redirect_to settings_project_path(@project, :tab => 'members') + end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/messages_controller.rb --- a/app/controllers/messages_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/messages_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,6 +19,7 @@ menu_item :boards default_search_scope :messages before_filter :find_board, :only => [:new, :preview] + before_filter :find_attachments, :only => [:preview] before_filter :find_message, :except => [:new, :preview] before_filter :authorize, :except => [:preview, :edit, :destroy] @@ -34,16 +35,18 @@ page = params[:page] # Find the page of the requested reply if params[:r] && page.nil? - offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i]) + offset = @topic.children.where("#{Message.table_name}.id < ?", params[:r].to_i).count page = 1 + offset / REPLIES_PER_PAGE end @reply_count = @topic.children.count - @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page - @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}], - :order => "#{Message.table_name}.created_on ASC", - :limit => @reply_pages.items_per_page, - :offset => @reply_pages.current.offset) + @reply_pages = Paginator.new @reply_count, REPLIES_PER_PAGE, page + @replies = @topic.children. + includes(:author, :attachments, {:board => :project}). + reorder("#{Message.table_name}.created_on ASC"). + limit(@reply_pages.per_page). + offset(@reply_pages.offset). + all @reply = Message.new(:subject => "RE: #{@message.subject}") render :action => "show", :layout => false if request.xhr? @@ -115,7 +118,6 @@ def preview message = @board.messages.find_by_id(params[:id]) - @attachements = message.attachments if message @text = (params[:message] || params[:reply])[:content] @previewed = message render :partial => 'common/preview' diff -r d98d22a98252 -r afce8026aaeb app/controllers/my_controller.rb --- a/app/controllers/my_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/my_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,6 +17,8 @@ class MyController < ApplicationController before_filter :require_login + # let user change user's password when user has to + skip_before_filter :check_password_change, :only => :password helper :issues helper :users @@ -68,6 +70,7 @@ if request.post? @user.safe_attributes = params[:user] @user.pref.attributes = params[:pref] + @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') if @user.ssamr_user_detail == nil @@ -91,10 +94,9 @@ if @user.save @user.pref.save - @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) set_language_if_valid @user.language flash[:notice] = l(:notice_account_updated) - redirect_to :action => 'account' + redirect_to my_account_path return end end @@ -104,7 +106,7 @@ def destroy @user = User.current unless @user.own_account_deletable? - redirect_to :action => 'account' + redirect_to my_account_path return end @@ -123,18 +125,21 @@ @user = User.current unless @user.change_password_allowed? flash[:error] = l(:notice_can_t_change_password) - redirect_to :action => 'account' + redirect_to my_account_path return end if request.post? - if @user.check_password?(params[:password]) + if !@user.check_password?(params[:password]) + flash.now[:error] = l(:notice_account_wrong_password) + elsif params[:password] == params[:new_password] + flash.now[:error] = l(:notice_new_password_must_be_different) + else @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] + @user.must_change_passwd = false if @user.save flash[:notice] = l(:notice_account_password_updated) - redirect_to :action => 'account' + redirect_to my_account_path end - else - flash[:error] = l(:notice_account_wrong_password) end end end @@ -149,7 +154,7 @@ User.current.rss_key flash[:notice] = l(:notice_feeds_access_key_reseted) end - redirect_to :action => 'account' + redirect_to my_account_path end # Create a new API key @@ -162,7 +167,7 @@ User.current.api_key flash[:notice] = l(:notice_api_access_key_reseted) end - redirect_to :action => 'account' + redirect_to my_account_path end # User's page layout configuration @@ -192,7 +197,7 @@ @user.pref[:my_page_layout] = layout @user.pref.save end - redirect_to :action => 'page_layout' + redirect_to my_page_layout_path end # Remove a block to user's page @@ -205,7 +210,7 @@ %w(top left right).each {|f| (layout[f] ||= []).delete block } @user.pref[:my_page_layout] = layout @user.pref.save - redirect_to :action => 'page_layout' + redirect_to my_page_layout_path end # Change blocks order on user's page diff -r d98d22a98252 -r afce8026aaeb app/controllers/news_controller.rb --- a/app/controllers/news_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/news_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -40,8 +40,8 @@ scope = @project ? @project.news.visible : News.visible @news_count = scope.count - @news_pages = Paginator.new self, @news_count, @limit, params['page'] - @offset ||= @news_pages.current.offset + @news_pages = Paginator.new @news_count, @limit, params['page'] + @offset ||= @news_pages.offset @newss = scope.all(:include => [:author, :project], :order => "#{News.table_name}.created_on DESC", :offset => @offset, @@ -73,7 +73,7 @@ if @news.save render_attachment_warning_if_needed(@news) flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'news', :action => 'index', :project_id => @project + redirect_to project_news_index_path(@project) else render :action => 'new' end @@ -88,7 +88,7 @@ if @news.save render_attachment_warning_if_needed(@news) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'show', :id => @news + redirect_to news_path(@news) else render :action => 'edit' end @@ -96,7 +96,7 @@ def destroy @news.destroy - redirect_to :action => 'index', :project_id => @project + redirect_to project_news_index_path(@project) end private diff -r d98d22a98252 -r afce8026aaeb app/controllers/previews_controller.rb --- a/app/controllers/previews_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/previews_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,12 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class PreviewsController < ApplicationController - before_filter :find_project + before_filter :find_project, :find_attachments def issue @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? if @issue - @attachements = @issue.attachments @description = params[:issue] && params[:issue][:description] if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") @description = nil @@ -37,7 +36,6 @@ def news if params[:id].present? && news = News.visible.find_by_id(params[:id]) @previewed = news - @attachments = news.attachments end @text = (params[:news] ? params[:news][:description] : nil) render :partial => 'common/preview' diff -r d98d22a98252 -r afce8026aaeb app/controllers/project_enumerations_controller.rb --- a/app/controllers/project_enumerations_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/project_enumerations_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -29,7 +29,7 @@ flash[:notice] = l(:notice_successful_update) end - redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project + redirect_to settings_project_path(@project, :tab => 'activities') end def destroy @@ -37,7 +37,6 @@ time_entry_activity.destroy(time_entry_activity.parent) end flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project + redirect_to settings_project_path(@project, :tab => 'activities') end - end diff -r d98d22a98252 -r afce8026aaeb app/controllers/projects_controller.rb --- a/app/controllers/projects_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/projects_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -43,6 +43,7 @@ helper :repositories include RepositoriesHelper include ProjectsHelper + helper :members include ActivitiesHelper helper :activities @@ -70,11 +71,10 @@ format.api { @offset, @limit = api_offset_and_limit @project_count = Project.visible.count - @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft') + @projects = Project.visible.offset(@offset).limit(@limit).order('lft').all } format.atom { - projects = Project.visible.find(:all, :order => 'created_on DESC', - :limit => Setting.feeds_limit.to_i) + projects = Project.visible.order('created_on DESC').limit(Setting.feeds_limit.to_i).all render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}") } end @@ -91,21 +91,21 @@ end def new - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_custom_fields = IssueCustomField.sorted.all @trackers = Tracker.sorted.all @project = Project.new @project.safe_attributes = params[:project] end def create - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_custom_fields = IssueCustomField.sorted.all @trackers = Tracker.sorted.all @project = Project.new @project.safe_attributes = params[:project] if validate_is_public_key && validate_parent_id && @project.save @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') - # Add current user as a project member if he is not admin + # Add current user as a project member if current user is not admin unless User.current.admin? r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first m = Member.new(:user => User.current, :roles => [r]) @@ -114,10 +114,12 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_create) - redirect_to(params[:continue] ? - {:controller => 'projects', :action => 'new', :project => {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}} : - {:controller => 'projects', :action => 'settings', :id => @project} - ) + if params[:continue] + attrs = {:parent_id => @project.parent_id}.reject {|k,v| v.nil?} + redirect_to new_project_path(attrs) + else + redirect_to settings_project_path(@project) + end } format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } end @@ -127,15 +129,11 @@ format.api { render_validation_errors(@project) } end end - end def copy - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_custom_fields = IssueCustomField.sorted.all @trackers = Tracker.sorted.all - @root_projects = Project.find(:all, - :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}", - :order => 'name') @source_project = Project.find(params[:id]) if request.get? @project = Project.copy_from(@source_project) @@ -147,13 +145,13 @@ if validate_parent_id && @project.copy(@source_project, :only => params[:only]) @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'projects', :action => 'settings', :id => @project + redirect_to settings_project_path(@project) elsif !@project.new_record? # Project was created # But some objects were not copied due to validation failures # (eg. issues from disabled trackers) # TODO: inform about that - redirect_to :controller => 'projects', :action => 'settings', :id => @project + redirect_to settings_project_path(@project) end end end @@ -164,14 +162,14 @@ # Show @project def show - if params[:jump] - # try to redirect to the requested menu item - redirect_to_project_menu_item(@project, params[:jump]) && return + # try to redirect to the requested menu item + if params[:jump] && redirect_to_project_menu_item(@project, params[:jump]) + return end @users_by_role = @project.users_by_role @subprojects = @project.children.visible.all - @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") + @news = @project.news.limit(5).includes(:author, :project).reorder("#{News.table_name}.created_on DESC").all @trackers = @project.rolled_up_trackers cond = @project.project_condition(Setting.display_subprojects_issues?) @@ -180,7 +178,7 @@ @total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker) if User.current.allowed_to?(:view_time_entries, @project) - @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f + @total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f end @key = User.current.rss_key @@ -192,7 +190,7 @@ end def settings - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_custom_fields = IssueCustomField.sorted.all @issue_category ||= IssueCategory.new @member ||= @project.members.new @trackers = Tracker.sorted.all @@ -210,7 +208,7 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'settings', :id => @project + redirect_to settings_project_path(@project) } format.api { render_api_ok } end @@ -236,7 +234,7 @@ def modules @project.enabled_module_names = params[:enabled_module_names] flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'settings', :id => @project, :tab => 'modules' + redirect_to settings_project_path(@project, :tab => 'modules') end def archive @@ -245,12 +243,12 @@ flash[:error] = l(:error_can_not_archive_project) end end - redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status])) + redirect_to admin_projects_path(:status => params[:status]) end def unarchive @project.unarchive if request.post? && !@project.active? - redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status])) + redirect_to admin_projects_path(:status => params[:status]) end def close @@ -269,7 +267,7 @@ if api_request? || params[:confirm] @project_to_destroy.destroy respond_to do |format| - format.html { redirect_to :controller => 'admin', :action => 'projects' } + format.html { redirect_to admin_projects_path } format.api { render_api_ok } end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/queries_controller.rb --- a/app/controllers/queries_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/queries_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -32,35 +32,34 @@ @limit = per_page_option end - @query_count = Query.visible.count - @query_pages = Paginator.new self, @query_count, @limit, params['page'] - @queries = Query.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name") + @query_count = IssueQuery.visible.count + @query_pages = Paginator.new @query_count, @limit, params['page'] + @queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name") respond_to do |format| - format.html { render :nothing => true } format.api end end def new - @query = Query.new + @query = IssueQuery.new @query.user = User.current @query.project = @project - @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params + @query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? + @query.build_from_params(params) end def create - @query = Query.new(params[:query]) + @query = IssueQuery.new(params[:query]) @query.user = User.current @query.project = params[:query_is_for_all] ? nil : @project - @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params + @query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? + @query.build_from_params(params) @query.column_names = nil if params[:default_columns] if @query.save flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query + redirect_to_issues(:query_id => @query) else render :action => 'new', :layout => !request.xhr? end @@ -72,13 +71,13 @@ def update @query.attributes = params[:query] @query.project = nil if params[:query_is_for_all] - @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params + @query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? + @query.build_from_params(params) @query.column_names = nil if params[:default_columns] if @query.save flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query + redirect_to_issues(:query_id => @query) else render :action => 'edit' end @@ -86,12 +85,12 @@ def destroy @query.destroy - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 + redirect_to_issues(:set_filter => 1) end private def find_query - @query = Query.find(params[:id]) + @query = IssueQuery.find(params[:id]) @project = @query.project render_403 unless @query.editable_by?(User.current) rescue ActiveRecord::RecordNotFound @@ -104,4 +103,16 @@ rescue ActiveRecord::RecordNotFound render_404 end + + def redirect_to_issues(options) + if params[:gantt] + if @project + redirect_to project_gantt_path(@project, options) + else + redirect_to issues_gantt_path(options) + end + else + redirect_to _project_issues_path(@project, options) + end + end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/reports_controller.rb --- a/app/controllers/reports_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/reports_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -90,6 +90,6 @@ private def find_issue_statuses - @statuses = IssueStatus.find(:all, :order => 'position') + @statuses = IssueStatus.sorted.all end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/repositories_controller.rb --- a/app/controllers/repositories_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/repositories_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,7 +18,7 @@ require 'SVG/Graph/Bar' require 'SVG/Graph/BarHorizontal' require 'digest/sha1' -require 'redmine/scm/adapters/abstract_adapter' +require 'redmine/scm/adapters' class ChangesetNotFound < Exception; end class InvalidRevisionParam < Exception; end @@ -112,7 +112,7 @@ end def show - @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? + @repository.fetch_changesets if @project.active? && Setting.autofetch_changesets? && @path.empty? @entries = @repository.entries(@path, @rev) @changeset = @repository.find_changeset_by_name(@rev) @@ -139,13 +139,14 @@ def revisions @changeset_count = @repository.changesets.count - @changeset_pages = Paginator.new self, @changeset_count, + @changeset_pages = Paginator.new @changeset_count, per_page_option, params['page'] - @changesets = @repository.changesets.find(:all, - :limit => @changeset_pages.items_per_page, - :offset => @changeset_pages.current.offset, - :include => [:user, :repository, :parents]) + @changesets = @repository.changesets. + limit(@changeset_pages.per_page). + offset(@changeset_pages.offset). + includes(:user, :repository, :parents). + all respond_to do |format| format.html { render :layout => false if request.xhr? } @@ -228,7 +229,8 @@ # Adds a related issue to a changeset # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues def add_related_issue - @issue = @changeset.find_referenced_issue_by_id(params[:issue_id]) + issue_id = params[:issue_id].to_s.sub(/^#/,'') + @issue = @changeset.find_referenced_issue_by_id(issue_id) if @issue && (!@issue.visible? || @changeset.issues.include?(@issue)) @issue = nil end @@ -351,15 +353,18 @@ @date_to = Date.today @date_from = @date_to << 11 @date_from = Date.civil(@date_from.year, @date_from.month, 1) - commits_by_day = Changeset.count( - :all, :group => :commit_date, - :conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) + commits_by_day = Changeset. + where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to). + group(:commit_date). + count commits_by_month = [0] * 12 commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } - changes_by_day = Change.count( - :all, :group => :commit_date, :include => :changeset, - :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) + changes_by_day = Change. + joins(:changeset). + where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to). + group(:commit_date). + count changes_by_month = [0] * 12 changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } @@ -392,10 +397,10 @@ end def graph_commits_per_author(repository) - commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id]) + commits_by_author = Changeset.where("repository_id = ?", repository.id).group(:committer).count commits_by_author.to_a.sort! {|x, y| x.last <=> y.last} - changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id]) + changes_by_author = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", repository.id).group(:committer).count h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} fields = commits_by_author.collect {|r| r.first} @@ -410,7 +415,7 @@ fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } graph = SVG::Graph::BarHorizontal.new( - :height => 400, + :height => 30 * commits_data.length, :width => 800, :fields => fields, :stack => :side, diff -r d98d22a98252 -r afce8026aaeb app/controllers/roles_controller.rb --- a/app/controllers/roles_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/roles_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,7 +26,7 @@ def index respond_to do |format| format.html { - @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position' + @role_pages, @roles = paginate Role.sorted, :per_page => 25 render :action => "index", :layout => false if request.xhr? } format.api { @@ -58,7 +58,7 @@ @role.workflow_rules.copy(copy_from) end flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to roles_path else @roles = Role.sorted.all render :action => 'new' @@ -71,7 +71,7 @@ def update if request.put? and @role.update_attributes(params[:role]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to roles_path else render :action => 'edit' end @@ -79,10 +79,10 @@ def destroy @role.destroy - redirect_to :action => 'index' + redirect_to roles_path rescue flash[:error] = l(:error_can_not_remove_role) - redirect_to :action => 'index' + redirect_to roles_path end def permissions @@ -94,7 +94,7 @@ role.save end flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to roles_path end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/search_controller.rb --- a/app/controllers/search_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/search_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,9 +18,6 @@ class SearchController < ApplicationController before_filter :find_optional_project - helper :messages - include MessagesHelper - def index @question = params[:q] || "" @question.strip! @@ -43,8 +40,8 @@ begin; offset = params[:offset].to_time if params[:offset]; rescue; end # quick jump to an issue - if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i) - redirect_to :controller => "issues", :action => "show", :id => $1 + if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i)) + redirect_to issue_path(issue) return end diff -r d98d22a98252 -r afce8026aaeb app/controllers/settings_controller.rb --- a/app/controllers/settings_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/settings_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,6 +19,8 @@ layout 'admin' menu_item :plugins, :only => :plugin + helper :queries + before_filter :require_admin def index @@ -31,12 +33,10 @@ if request.post? && params[:settings] && params[:settings].is_a?(Hash) settings = (params[:settings] || {}).dup.symbolize_keys settings.each do |name, value| - # remove blank values in array settings - value.delete_if {|v| v.blank? } if value.is_a?(Array) - Setting[name] = value + Setting.set_from_params name, value end flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'edit', :tab => params[:tab] + redirect_to settings_path(:tab => params[:tab]) else @options = {} user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]} @@ -46,16 +46,24 @@ @guessed_host_and_path = request.host_with_port.dup @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? + @commit_update_keywords = Setting.commit_update_keywords.dup + @commit_update_keywords = [{}] unless @commit_update_keywords.is_a?(Array) && @commit_update_keywords.any? + Redmine::Themes.rescan end end def plugin @plugin = Redmine::Plugin.find(params[:id]) + unless @plugin.configurable? + render_404 + return + end + if request.post? Setting.send "plugin_#{@plugin.id}=", params[:settings] flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'plugin', :id => @plugin.id + redirect_to plugin_settings_path(@plugin) else @partial = @plugin.settings[:partial] @settings = Setting.send "plugin_#{@plugin.id}" diff -r d98d22a98252 -r afce8026aaeb app/controllers/sys_controller.rb --- a/app/controllers/sys_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/sys_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,11 +19,7 @@ before_filter :check_enabled def projects - p = Project.active.has_module(:repository).find( - :all, - :include => :repository, - :order => "#{Project.table_name}.identifier" - ) + p = Project.active.has_module(:repository).order("#{Project.table_name}.identifier").preload(:repository).all # extra_info attribute from repository breaks activeresource client render :xml => p.to_xml( :only => [:id, :identifier, :name, :is_public, :status], diff -r d98d22a98252 -r afce8026aaeb app/controllers/timelog_controller.rb --- a/app/controllers/timelog_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/timelog_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,42 +30,31 @@ accept_rss_auth :index accept_api_auth :index, :show, :create, :update, :destroy + rescue_from Query::StatementInvalid, :with => :query_statement_invalid + helper :sort include SortHelper helper :issues include TimelogHelper helper :custom_fields include CustomFieldsHelper + helper :queries + include QueriesHelper def index - sort_init 'spent_on', 'desc' - sort_update 'spent_on' => ['spent_on', "#{TimeEntry.table_name}.created_on"], - 'user' => 'user_id', - 'activity' => 'activity_id', - 'project' => "#{Project.table_name}.name", - 'issue' => 'issue_id', - 'hours' => 'hours' + @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') - retrieve_date_range - - scope = TimeEntry.visible.spent_between(@from, @to) - if @issue - scope = scope.on_issue(@issue) - elsif @project - scope = scope.on_project(@project, Setting.display_subprojects_issues?) - end + sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria) + sort_update(@query.sortable_columns) + scope = time_entry_scope(:order => sort_clause). + includes(:project, :user, :issue). + preload(:issue => [:project, :tracker, :status, :assigned_to, :priority]) respond_to do |format| format.html { - # Paginate results @entry_count = scope.count - @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] - @entries = scope.all( - :include => [:project, :activity, :user, {:issue => :tracker}], - :order => sort_clause, - :limit => @entry_pages.items_per_page, - :offset => @entry_pages.current.offset - ) + @entry_pages = Paginator.new @entry_count, per_page_option, params['page'] + @entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).all @total_hours = scope.sum(:hours).to_f render :layout => !request.xhr? @@ -73,35 +62,25 @@ format.api { @entry_count = scope.count @offset, @limit = api_offset_and_limit - @entries = scope.all( - :include => [:project, :activity, :user, {:issue => :tracker}], - :order => sort_clause, - :limit => @limit, - :offset => @offset - ) + @entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).all } format.atom { - entries = scope.all( - :include => [:project, :activity, :user, {:issue => :tracker}], - :order => "#{TimeEntry.table_name}.created_on DESC", - :limit => Setting.feeds_limit.to_i - ) + entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").all render_feed(entries, :title => l(:label_spent_time)) } format.csv { # Export all entries - @entries = scope.all( - :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], - :order => sort_clause - ) - send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv') + @entries = scope.all + send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv') } end end def report - retrieve_date_range - @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to) + @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') + scope = time_entry_scope + + @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope) respond_to do |format| format.html { render :layout => !request.xhr? } @@ -134,16 +113,24 @@ flash[:notice] = l(:notice_successful_create) if params[:continue] if params[:project_id] - redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue, + options = { :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, :back_url => params[:back_url] + } + if @time_entry.issue + redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options) + else + redirect_to new_project_time_entry_path(@time_entry.project, options) + end else - redirect_to :action => 'new', + options = { :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, :back_url => params[:back_url] + } + redirect_to new_time_entry_path(options) end else - redirect_back_or_default :action => 'index', :project_id => @time_entry.project + redirect_back_or_default project_time_entries_path(@time_entry.project) end } format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } @@ -169,7 +156,7 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) - redirect_back_or_default :action => 'index', :project_id => @time_entry.project + redirect_back_or_default project_time_entries_path(@time_entry.project) } format.api { render_api_ok } end @@ -195,12 +182,13 @@ time_entry.safe_attributes = attributes call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry }) unless time_entry.save + logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info # Keep unsaved time_entry ids to display them in flash error unsaved_time_entry_ids << time_entry.id end end set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids) - redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first}) + redirect_back_or_default project_time_entries_path(@projects.first) end def destroy @@ -219,7 +207,7 @@ else flash[:error] = l(:notice_unable_delete_time_entry) end - redirect_back_or_default(:action => 'index', :project_id => @projects.first) + redirect_back_or_default project_time_entries_path(@projects.first) } format.api { if destroyed @@ -291,51 +279,13 @@ end end - # Retrieves the date range based on predefined ranges or specific from/to param dates - def retrieve_date_range - @free_period = false - @from, @to = nil, nil - - if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?) - case params[:period].to_s - when 'today' - @from = @to = Date.today - when 'yesterday' - @from = @to = Date.today - 1 - when 'current_week' - @from = Date.today - (Date.today.cwday - 1)%7 - @to = @from + 6 - when 'last_week' - @from = Date.today - 7 - (Date.today.cwday - 1)%7 - @to = @from + 6 - when 'last_2_weeks' - @from = Date.today - 14 - (Date.today.cwday - 1)%7 - @to = @from + 13 - when '7_days' - @from = Date.today - 7 - @to = Date.today - when 'current_month' - @from = Date.civil(Date.today.year, Date.today.month, 1) - @to = (@from >> 1) - 1 - when 'last_month' - @from = Date.civil(Date.today.year, Date.today.month, 1) << 1 - @to = (@from >> 1) - 1 - when '30_days' - @from = Date.today - 30 - @to = Date.today - when 'current_year' - @from = Date.civil(Date.today.year, 1, 1) - @to = Date.civil(Date.today.year, 12, 31) - end - elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?)) - begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end - begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end - @free_period = true - else - # default + # Returns the TimeEntry scope for index and report actions + def time_entry_scope(options={}) + scope = @query.results_scope(options) + if @issue + scope = scope.on_issue(@issue) end - - @from, @to = @to, @from if @from && @to && @from > @to + scope end def parse_params_for_bulk_time_entry_attributes(params) diff -r d98d22a98252 -r afce8026aaeb app/controllers/trackers_controller.rb --- a/app/controllers/trackers_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/trackers_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,7 +25,7 @@ def index respond_to do |format| format.html { - @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position' + @tracker_pages, @trackers = paginate Tracker.sorted, :per_page => 25 render :action => "index", :layout => false if request.xhr? } format.api { @@ -36,19 +36,19 @@ def new @tracker ||= Tracker.new(params[:tracker]) - @trackers = Tracker.find :all, :order => 'position' - @projects = Project.find(:all) + @trackers = Tracker.sorted.all + @projects = Project.all end def create @tracker = Tracker.new(params[:tracker]) - if request.post? and @tracker.save + if @tracker.save # workflow copy if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from])) @tracker.workflow_rules.copy(copy_from) end flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to trackers_path return end new @@ -57,14 +57,14 @@ def edit @tracker ||= Tracker.find(params[:id]) - @projects = Project.find(:all) + @projects = Project.all end def update @tracker = Tracker.find(params[:id]) - if request.put? and @tracker.update_attributes(params[:tracker]) + if @tracker.update_attributes(params[:tracker]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to trackers_path return end edit @@ -78,7 +78,7 @@ else @tracker.destroy end - redirect_to :action => 'index' + redirect_to trackers_path end def fields @@ -92,7 +92,7 @@ end end flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'fields' + redirect_to fields_trackers_path return end @trackers = Tracker.sorted.all diff -r d98d22a98252 -r afce8026aaeb app/controllers/users_controller.rb --- a/app/controllers/users_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/users_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -45,12 +45,9 @@ scope = scope.in_group(params[:group_id]) if params[:group_id].present? @user_count = scope.count - @user_pages = Paginator.new self, @user_count, @limit, params['page'] - @offset ||= @user_pages.current.offset - @users = scope.find :all, - :order => sort_clause, - :limit => @limit, - :offset => @offset + @user_pages = Paginator.new @user_count, @limit, params['page'] + @offset ||= @user_pages.offset + @users = scope.order(sort_clause).limit(@limit).offset(@offset).all respond_to do |format| format.html { @@ -58,7 +55,7 @@ render :layout => !request.xhr? } format.api - end + end end def show @@ -69,7 +66,7 @@ end # show projects based on current user visibility - @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current)) + @memberships = @user.memberships.where(Project.visible_condition(User.current)).all events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10) @events_by_day = events.group_by(&:event_date) @@ -89,8 +86,8 @@ def new @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) - @auth_sources = AuthSource.find(:all) - + @user.safe_attributes = params[:user] + @auth_sources = AuthSource.all @ssamr_user_details = SsamrUserDetail.new end @@ -113,26 +110,26 @@ if @user.save @user.pref.attributes = params[:pref] - @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') @user.pref.save - + @ssamr_user_details.save! - - Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information] + Mailer.account_information(@user, @user.password).deliver if params[:send_information] respond_to do |format| format.html { flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user))) - redirect_to(params[:continue] ? - {:controller => 'users', :action => 'new'} : - {:controller => 'users', :action => 'edit', :id => @user} - ) + if params[:continue] + attrs = params[:user].slice(:generate_password) + redirect_to new_user_path(:user => attrs) + else + redirect_to edit_user_path(@user) + end } format.api { render :action => 'show', :status => :created, :location => user_url(@user) } end else - @auth_sources = AuthSource.find(:all) + @auth_sources = AuthSource.all # Clear password input @user.password = @user.password_confirmation = nil @@ -144,6 +141,7 @@ end def edit + @auth_sources = AuthSource.all @ssamr_user_details = @user.ssamr_user_detail @@ -153,7 +151,6 @@ @selected_institution_id = @user.ssamr_user_detail.institution_id.to_i end - @auth_sources = AuthSource.find(:all) @membership ||= Member.new end @@ -168,7 +165,6 @@ was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) # TODO: Similar to My#account @user.pref.attributes = params[:pref] - @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') if @user.ssamr_user_detail == nil @ssamr_user_details = SsamrUserDetail.new() @@ -192,12 +188,11 @@ if @user.save @user.pref.save - @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) if was_activated Mailer.account_activated(@user).deliver - elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil? - Mailer.account_information(@user, params[:user][:password]).deliver + elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? + Mailer.account_information(@user, @user.password).deliver end respond_to do |format| @@ -208,7 +203,7 @@ format.api { render_api_ok } end else - @auth_sources = AuthSource.find(:all) + @auth_sources = AuthSource.all @membership ||= Member.new # Clear password input @user.password = @user.password_confirmation = nil @@ -223,7 +218,7 @@ def destroy @user.destroy respond_to do |format| - format.html { redirect_back_or_default(users_url) } + format.html { redirect_back_or_default(users_path) } format.api { render_api_ok } end end @@ -232,7 +227,7 @@ @membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership.save respond_to do |format| - format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } + format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } format.js end end @@ -243,7 +238,7 @@ @membership.destroy end respond_to do |format| - format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } + format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } format.js end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/versions_controller.rb --- a/app/controllers/versions_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/versions_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -31,7 +31,7 @@ def index respond_to do |format| format.html { - @trackers = @project.trackers.find(:all, :order => 'position') + @trackers = @project.trackers.sorted.all retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?}) @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id] @@ -46,11 +46,11 @@ @issues_by_version = {} if @selected_tracker_ids.any? && @versions.any? - issues = Issue.visible.all( - :include => [:project, :status, :tracker, :priority, :fixed_version], - :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)}, - :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id" - ) + issues = Issue.visible. + includes(:project, :tracker). + preload(:status, :priority, :fixed_version). + where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)). + order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") @issues_by_version = issues.group_by(&:fixed_version) end @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} @@ -64,9 +64,10 @@ def show respond_to do |format| format.html { - @issues = @version.fixed_issues.visible.find(:all, - :include => [:status, :tracker, :priority], - :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") + @issues = @version.fixed_issues.visible. + includes(:status, :tracker, :priority). + reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id"). + all } format.api end @@ -95,7 +96,7 @@ respond_to do |format| format.html do flash[:notice] = l(:notice_successful_create) - redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project + redirect_back_or_default settings_project_path(@project, :tab => 'versions') end format.js format.api do @@ -124,7 +125,7 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) - redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project + redirect_back_or_default settings_project_path(@project, :tab => 'versions') } format.api { render_api_ok } end @@ -141,21 +142,21 @@ if request.put? @project.close_completed_versions end - redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project + redirect_to settings_project_path(@project, :tab => 'versions') end def destroy if @version.fixed_issues.empty? @version.destroy respond_to do |format| - format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project } + format.html { redirect_back_or_default settings_project_path(@project, :tab => 'versions') } format.api { render_api_ok } end else respond_to do |format| format.html { flash[:error] = l(:notice_unable_delete_version) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project + redirect_to settings_project_path(@project, :tab => 'versions') } format.api { head :unprocessable_entity } end diff -r d98d22a98252 -r afce8026aaeb app/controllers/watchers_controller.rb --- a/app/controllers/watchers_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/watchers_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,35 +16,36 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class WatchersController < ApplicationController - before_filter :find_project - before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] - before_filter :authorize, :only => [:new, :destroy] + before_filter :require_login, :find_watchables, :only => [:watch, :unwatch] def watch - if @watched.respond_to?(:visible?) && !@watched.visible?(User.current) - render_403 - else - set_watcher(User.current, true) - end + set_watcher(@watchables, User.current, true) end def unwatch - set_watcher(User.current, false) + set_watcher(@watchables, User.current, false) end + before_filter :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user] + accept_api_auth :create, :destroy + def new end def create - if params[:watcher].is_a?(Hash) && request.post? - user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]] - user_ids.each do |user_id| - Watcher.create(:watchable => @watched, :user_id => user_id) - end + user_ids = [] + if params[:watcher].is_a?(Hash) + user_ids << (params[:watcher][:user_ids] || params[:watcher][:user_id]) + else + user_ids << params[:user_id] + end + user_ids.flatten.compact.uniq.each do |user_id| + Watcher.create(:watchable => @watched, :user_id => user_id) end respond_to do |format| format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}} format.js + format.api { render_api_ok } end end @@ -56,22 +57,24 @@ end def destroy - @watched.set_watcher(User.find(params[:user_id]), false) if request.post? + @watched.set_watcher(User.find(params[:user_id]), false) respond_to do |format| format.html { redirect_to :back } format.js + format.api { render_api_ok } end end def autocomplete_for_user - @users = User.active.like(params[:q]).find(:all, :limit => 100) + @users = User.active.sorted.like(params[:q]).limit(100).all if @watched @users -= @watched.watcher_users end render :layout => false end -private + private + def find_project if params[:object_type] && params[:object_id] klass = Object.const_get(params[:object_type].camelcase) @@ -85,11 +88,22 @@ render_404 end - def set_watcher(user, watching) - @watched.set_watcher(user, watching) + def find_watchables + klass = Object.const_get(params[:object_type].camelcase) rescue nil + if klass && klass.respond_to?('watched_by') + @watchables = klass.find_all_by_id(Array.wrap(params[:object_id])) + raise Unauthorized if @watchables.any? {|w| w.respond_to?(:visible?) && !w.visible?} + end + render_404 unless @watchables.present? + end + + def set_watcher(watchables, user, watching) + watchables.each do |watchable| + watchable.set_watcher(user, watching) + end respond_to do |format| format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}} - format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => @watched} } + format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} } end end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/welcome_controller.rb --- a/app/controllers/welcome_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/welcome_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/controllers/wiki_controller.rb --- a/app/controllers/wiki_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/wiki_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -15,8 +15,6 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'diff' - # The WikiController follows the Rails REST controller pattern but with # a few differences # @@ -37,6 +35,7 @@ before_filter :find_existing_or_new_page, :only => [:show, :edit, :update] before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] accept_api_auth :index, :show, :update, :destroy + before_filter :find_attachments, :only => [:preview] helper :attachments include AttachmentsHelper @@ -63,7 +62,12 @@ # display a page (in editing mode if it doesn't exist) def show - if @page.new_record? + if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project) + deny_access + return + end + @content = @page.content_for_version(params[:version]) + if @content.nil? if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request? edit render :action => 'edit' @@ -72,11 +76,6 @@ end return end - if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project) - deny_access - return - end - @content = @page.content_for_version(params[:version]) if User.current.allowed_to?(:export_wiki_pages, @project) if params[:format] == 'pdf' send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf") @@ -105,19 +104,19 @@ def edit return render_403 unless editable? if @page.new_record? - @page.content = WikiContent.new(:page => @page) if params[:parent].present? @page.parent = @page.wiki.find_page(params[:parent].to_s) end end @content = @page.content_for_version(params[:version]) + @content ||= WikiContent.new(:page => @page) @content.text = initial_page_content(@page) if @content.text.blank? # don't keep previous comment @content.comments = nil # To prevent StaleObjectError exception when reverting to a previous version - @content.version = @page.content.version + @content.version = @page.content.version if @page.content @text = @content.text if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? @@ -131,10 +130,9 @@ def update return render_403 unless editable? was_new_page = @page.new_record? - @page.content = WikiContent.new(:page => @page) if @page.new_record? @page.safe_attributes = params[:wiki_page] - @content = @page.content + @content = @page.content || WikiContent.new(:page => @page) content_params = params[:content] if content_params.nil? && params[:wiki_page].is_a?(Hash) content_params = params[:wiki_page].slice(:text, :comments, :version) @@ -146,23 +144,26 @@ if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? @section = params[:section].to_i @section_hash = params[:section_hash] - @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash) + @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(@section, @text, @section_hash) else @content.version = content_params[:version] if content_params[:version] @content.text = @text end @content.author = User.current - if @page.save_with_content + if @page.save_with_content(@content) attachments = Attachment.attach_files(@page, params[:attachments]) render_attachment_warning_if_needed(@page) call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) respond_to do |format| - format.html { redirect_to :action => 'show', :project_id => @project, :id => @page.title } + format.html { + anchor = @section ? "section-#{@section}" : nil + redirect_to project_wiki_page_path(@project, @page.title, :anchor => anchor) + } format.api { if was_new_page - render :action => 'show', :status => :created, :location => url_for(:controller => 'wiki', :action => 'show', :project_id => @project, :id => @page.title) + render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title) else render_api_ok end @@ -199,25 +200,26 @@ @original_title = @page.pretty_title if request.post? && @page.update_attributes(params[:wiki_page]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'show', :project_id => @project, :id => @page.title + redirect_to project_wiki_page_path(@project, @page.title) end end def protect @page.update_attribute :protected, params[:protected] - redirect_to :action => 'show', :project_id => @project, :id => @page.title + redirect_to project_wiki_page_path(@project, @page.title) end # show page history def history @version_count = @page.content.versions.count - @version_pages = Paginator.new self, @version_count, per_page_option, params['page'] + @version_pages = Paginator.new @version_count, per_page_option, params['page'] # don't load text - @versions = @page.content.versions.find :all, - :select => "id, author_id, comments, updated_on, version", - :order => 'version DESC', - :limit => @version_pages.items_per_page + 1, - :offset => @version_pages.current.offset + @versions = @page.content.versions. + select("id, author_id, comments, updated_on, version"). + reorder('version DESC'). + limit(@version_pages.per_page + 1). + offset(@version_pages.offset). + all render :layout => false if request.xhr? end @@ -260,7 +262,7 @@ end @page.destroy respond_to do |format| - format.html { redirect_to :action => 'index', :project_id => @project } + format.html { redirect_to project_wiki_index_path(@project) } format.api { render_api_ok } end end @@ -270,7 +272,7 @@ @content = @page.content_for_version(params[:version]) @content.destroy - redirect_to_referer_or :action => 'history', :id => @page.title, :project_id => @project + redirect_to_referer_or history_project_wiki_page_path(@project, @page.title) end # Export wiki to a single pdf or html file @@ -292,7 +294,7 @@ # page is nil when previewing a new page return render_403 unless page.nil? || editable?(page) if page - @attachements = page.attachments + @attachments += page.attachments @previewed = page.content end @text = params[:content][:text] @@ -349,6 +351,6 @@ end def load_pages_for_index - @pages = @wiki.pages.with_updated_on.order("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all + @pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/wikis_controller.rb --- a/app/controllers/wikis_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/wikis_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,7 +30,7 @@ def destroy if request.post? && params[:confirm] && @project.wiki @project.wiki.destroy - redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki' + redirect_to settings_project_path(@project, :tab => 'wiki') end end end diff -r d98d22a98252 -r afce8026aaeb app/controllers/workflows_controller.rb --- a/app/controllers/workflows_controller.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/controllers/workflows_controller.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -38,7 +38,7 @@ } } if @role.save - redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only] + redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) return end end @@ -64,7 +64,7 @@ if request.post? && @role && @tracker WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {}) - redirect_to :action => 'permissions', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only] + redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) return end @@ -106,12 +106,12 @@ if request.post? if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) flash.now[:error] = l(:error_workflow_copy_source) - elsif @target_trackers.nil? || @target_roles.nil? + elsif @target_trackers.blank? || @target_roles.blank? flash.now[:error] = l(:error_workflow_copy_target) else WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role + redirect_to workflows_copy_path(:source_tracker_id => @source_tracker, :source_role_id => @source_role) end end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/account_helper.rb --- a/app/helpers/account_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/account_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/activities_helper.rb --- a/app/helpers/activities_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/activities_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,37 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. module ActivitiesHelper + def sort_activity_events(events) + events_by_group = events.group_by(&:event_group) + sorted_events = [] + events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event| + if group_events = events_by_group.delete(event.event_group) + group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i| + sorted_events << [e, i > 0] + end + end + end + sorted_events + end + def date_of_event(e) if e.respond_to? :updated_at e.updated_at @@ -39,7 +70,6 @@ end end end - projhash end diff -r d98d22a98252 -r afce8026aaeb app/helpers/admin_helper.rb --- a/app/helpers/admin_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/admin_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/application_helper.rb --- a/app/helpers/application_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/application_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,6 +24,7 @@ include Redmine::WikiFormatting::Macros::Definitions include Redmine::I18n include GravatarHelper::PublicMethods + include Redmine::Pagination::Helper extend Forwardable def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter @@ -78,7 +79,8 @@ subject = truncate(subject, :length => options[:truncate]) end end - s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title + only_path = options[:only_path].nil? ? true : options[:only_path] + s = link_to text, issue_path(issue, :only_path => only_path), :class => issue.css_classes, :title => title s << h(": #{subject}") if subject s = h("#{issue.project} - ") + s if options[:project] s @@ -90,14 +92,10 @@ # * :download - Force download (default: false) def link_to_attachment(attachment, options={}) text = options.delete(:text) || attachment.filename - action = options.delete(:download) ? 'download' : 'show' - opt_only_path = {} - opt_only_path[:only_path] = (options[:only_path] == false ? false : true) - options.delete(:only_path) - link_to(h(text), - {:controller => 'attachments', :action => action, - :id => attachment, :filename => attachment.filename}.merge(opt_only_path), - options) + route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path + html_options = options.slice!(:only_path) + url = send(route_method, attachment, attachment.filename, options) + link_to text, url, html_options end # Generates a link to a SCM revision @@ -119,13 +117,11 @@ # Generates a link to a message def link_to_message(message, options={}, html_options = nil) link_to( - h(truncate(message.subject, :length => 60)), - { :controller => 'messages', :action => 'show', - :board_id => message.board_id, - :id => (message.parent_id || message.id), + truncate(message.subject, :length => 60), + board_message_path(message.board_id, message.parent_id || message.id, { :r => (message.parent_id && message.id), :anchor => (message.parent_id ? "message-#{message.id}" : nil) - }.merge(options), + }.merge(options)), html_options ) end @@ -134,16 +130,29 @@ # Examples: # # link_to_project(project) # => link to the specified project overview - # link_to_project(project, :action=>'settings') # => link to project settings # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) # def link_to_project(project, options={}, html_options = nil) if project.archived? - h(project) + h(project.name) + elsif options.key?(:action) + ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0." + url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) + link_to project.name, url, html_options else - url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) - link_to(h(project), url, html_options) + link_to project.name, project_path(project, options), html_options + end + end + + # Generates a link to a project settings if active + def link_to_project_settings(project, options={}, html_options=nil) + if project.active? + link_to project.name, settings_project_path(project, options), html_options + elsif project.archived? + h(project.name) + else + link_to project.name, project_path(project, options), html_options end end @@ -152,8 +161,8 @@ end def thumbnail_tag(attachment) - link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)), - {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename}, + link_to image_tag(thumbnail_path(attachment)), + named_attachment_path(attachment, attachment.filename), :title => attachment.filename end @@ -187,7 +196,7 @@ def format_version_name(version) if version.project == @project - h(version) + h(version) else h("#{version.project} - #{version}") end @@ -308,12 +317,13 @@ def principals_check_box_tags(name, principals) s = '' + principals.sort.each do |principal| if principal.type == "User" - s << "\n" + s << "\n" else - s << "\n" + s << "\n" end end @@ -328,7 +338,7 @@ end groups = '' collection.sort.each do |element| - selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) + selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected (element.is_a?(Group) ? groups : s) << %() end unless groups.empty? @@ -341,11 +351,15 @@ def options_for_membership_project_select(principal, projects) options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") options << project_tree_options_for_select(projects) do |p| - {:disabled => principal.projects.include?(p)} + {:disabled => principal.projects.to_a.include?(p)} end options end + def option_tag(name, text, value, selected=nil, options={}) + content_tag 'option', value, options.merge(:value => value, :selected => (value == selected)) + end + # Truncates and returns the string as a single line def truncate_single_line(string, *args) truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') @@ -378,7 +392,7 @@ if @project link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) else - content_tag('acronym', text, :title => format_time(time)) + content_tag('abbr', text, :title => format_time(time)) end end @@ -397,59 +411,6 @@ str.blank? ? nil : str end - def pagination_links_full(paginator, count=nil, options={}) - page_param = options.delete(:page_param) || :page - per_page_links = options.delete(:per_page_links) - url_param = params.dup - - html = '' - if paginator.current.previous - # \xc2\xab(utf-8) = « - html << link_to_content_update( - "\xc2\xab " + l(:label_previous), - url_param.merge(page_param => paginator.current.previous)) + ' ' - end - - html << (pagination_links_each(paginator, options) do |n| - link_to_content_update(n.to_s, url_param.merge(page_param => n)) - end || '') - - if paginator.current.next - # \xc2\xbb(utf-8) = » - html << ' ' + link_to_content_update( - (l(:label_next) + " \xc2\xbb"), - url_param.merge(page_param => paginator.current.next)) - end - - unless count.nil? - html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" - if per_page_links != false && links = per_page_links(paginator.items_per_page, count) - html << " | #{links}" - end - end - - html.html_safe - end - - def per_page_links(selected=nil, item_count=nil) - values = Setting.per_page_options_array - if item_count && values.any? - if item_count > values.first - max = values.detect {|value| value >= item_count} || item_count - else - max = item_count - end - values = values.select {|value| value <= max || value == selected} - end - if values.empty? || (values.size == 1 && values.first == selected) - return nil - end - links = values.collect do |n| - n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) - end - l(:label_display_per_page, links.join(', ')) - end - def reorder_links(name, url, method = :post) link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), @@ -503,12 +464,31 @@ end end + # Returns a h2 tag and sets the html title with the given arguments + def title(*args) + strings = args.map do |arg| + if arg.is_a?(Array) && arg.size >= 2 + link_to(*arg) + else + h(arg.to_s) + end + end + html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s} + content_tag('h2', strings.join(' » ').html_safe) + end + + # Sets the html title + # Returns the html title when called without arguments + # Current project name and app_title and automatically appended + # Exemples: + # html_title 'Foo', 'Bar' + # html_title # => 'Foo - Bar - My Project - Redmine' def html_title(*args) if args.empty? title = @html_title || [] title << @project.name if @project title << Setting.app_title unless Setting.app_title == title.last - title.select {|t| !t.blank? }.join(' - ') + title.reject(&:blank?).join(' - ') else @html_title ||= [] @html_title += args @@ -523,13 +503,18 @@ css << 'theme-' + theme.name end + css << 'project-' + @project.identifier if @project && @project.identifier.present? css << 'controller-' + controller_name css << 'action-' + action_name css.join(' ') end def accesskey(s) - Redmine::AccessKeys.key_for s + @used_accesskeys ||= [] + key = Redmine::AccessKeys.key_for(s) + return nil if @used_accesskeys.include?(key) + @used_accesskeys << key + key end # Formats text according to system settings. @@ -617,8 +602,7 @@ filename, ext, alt, alttext = $1.downcase, $2, $3, $4 # search for the picture in attachments if found = Attachment.latest_attach(attachments, filename) - image_url = url_for :only_path => only_path, :controller => 'attachments', - :action => 'download', :id => found + image_url = download_named_attachment_path(found, found.filename, :only_path => only_path) desc = found.description.to_s.gsub('"', '') if !desc.blank? && alttext.blank? alt = " title=\"#{desc}\" alt=\"#{desc}\"" @@ -647,9 +631,9 @@ esc, all, page, title = $1, $2, $3, $5 if esc.nil? if page =~ /^([^\:]+)\:(.*)$/ - link_project = Project.find_by_identifier($1) || Project.find_by_name($1) - page = $2 - title ||= $1 if page.blank? + identifier, page = $1, $2 + link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier) + title ||= identifier if page.blank? end if link_project && link_project.wiki @@ -711,6 +695,9 @@ # export:some/file -> Force the download of the file # Forum messages: # message#1218 -> Link to message with id 1218 + # Projects: + # project:someproject -> Link to project named "someproject" + # project#3 -> Link to project with id 3 # # Links can refer other objects from other projects, using project identifier: # identifier:r52 @@ -747,7 +734,7 @@ when nil if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) anchor = comment_id ? "note-#{comment_id}" : nil - link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, + link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, :class => issue.css_classes, :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") end @@ -814,14 +801,14 @@ repository = project.repository end if prefix == 'commit' - if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"])) + if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first) link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, :class => 'changeset', - :title => truncate_single_line(h(changeset.comments), :length => 100) + :title => truncate_single_line(changeset.comments, :length => 100) end else if repository && User.current.allowed_to?(:browse_repository, project) - name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} + name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$} path, rev, anchor = $1, $3, $5 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, :path => to_path_param(path), @@ -835,11 +822,10 @@ when 'attachment' attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) if attachments && attachment = Attachment.latest_attach(attachments, name) - link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, - :class => 'attachment' + link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') end when 'project' - if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) + if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first link = link_to_project(p, {:only_path => only_path}, :class => 'project') end end @@ -860,7 +846,8 @@ content_tag('div', link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), :class => 'contextual', - :title => l(:button_edit_section)) + heading.html_safe + :title => l(:button_edit_section), + :id => "section-#{@current_section}") + heading.html_safe else heading end @@ -1091,8 +1078,8 @@ (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) - ), :class => 'progress', :style => "width: #{width};").html_safe + - content_tag('p', legend, :class => 'pourcent').html_safe + ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe + + content_tag('p', legend, :class => 'percent').html_safe end def checked_image(checked=true) @@ -1124,6 +1111,7 @@ def include_calendar_headers_tags unless @calendar_headers_tags_included + tags = javascript_include_tag("datepicker") @calendar_headers_tags_included = true content_for :header_tags do start_of_week = Setting.start_of_week @@ -1131,12 +1119,13 @@ # Redmine uses 1..7 (monday..sunday) in settings and locales # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 start_of_week = start_of_week.to_i % 7 - - tags = javascript_tag( + tags << javascript_tag( "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + "showOn: 'button', buttonImageOnly: true, buttonImage: '" + path_to_image('/images/calendar.png') + - "', showButtonPanel: true};") + "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " + + "selectOtherMonths: true, changeMonth: true, changeYear: true, " + + "beforeShow: beforeShowDatePicker};") jquery_locale = l('jquery.locale', :default => current_language.to_s) unless jquery_locale == 'en' tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") @@ -1199,18 +1188,13 @@ super sources, options end - def content_for(name, content = nil, &block) - @has_content ||= {} - @has_content[name] = true - super(name, content, &block) - end - + # TODO: remove this in 2.5.0 def has_content?(name) - (@has_content && @has_content[name]) || false + content_for?(name) end def sidebar_content? - has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present? + content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present? end def view_layouts_base_sidebar_hook_response @@ -1240,7 +1224,7 @@ def sanitize_anchor_name(anchor) if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' - anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') + anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-') else # TODO: remove when ruby1.8 is no longer supported anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') @@ -1249,7 +1233,7 @@ # Returns the javascript tags that are included in the html layout head def javascript_heads - tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application') + tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application') unless User.current.pref.warn_on_leaving_unsaved == '0' tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") end diff -r d98d22a98252 -r afce8026aaeb app/helpers/attachments_helper.rb --- a/app/helpers/attachments_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/attachments_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/auth_sources_helper.rb --- a/app/helpers/auth_sources_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/auth_sources_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/boards_helper.rb --- a/app/helpers/boards_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/boards_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/calendars_helper.rb --- a/app/helpers/calendars_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/calendars_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/context_menus_helper.rb --- a/app/helpers/context_menus_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/context_menus_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -36,7 +36,7 @@ def bulk_update_custom_field_context_menu_link(field, text, value) context_menu_link h(text), - {:controller => 'issues', :action => 'bulk_update', :ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back}, + bulk_update_issues_path(:ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back), :method => :post, :selected => (@issue && @issue.custom_field_value(field) == value) end diff -r d98d22a98252 -r afce8026aaeb app/helpers/custom_fields_helper.rb --- a/app/helpers/custom_fields_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/custom_fields_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,12 +19,33 @@ module CustomFieldsHelper + CUSTOM_FIELDS_TABS = [ + {:name => 'IssueCustomField', :partial => 'custom_fields/index', + :label => :label_issue_plural}, + {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', + :label => :label_spent_time}, + {:name => 'ProjectCustomField', :partial => 'custom_fields/index', + :label => :label_project_plural}, + {:name => 'VersionCustomField', :partial => 'custom_fields/index', + :label => :label_version_plural}, + {:name => 'UserCustomField', :partial => 'custom_fields/index', + :label => :label_user_plural}, + {:name => 'GroupCustomField', :partial => 'custom_fields/index', + :label => :label_group_plural}, + {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', + :label => TimeEntryActivity::OptionName}, + {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', + :label => IssuePriority::OptionName}, + {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', + :label => DocumentCategory::OptionName} + ] + def custom_fields_tabs - CustomField::CUSTOM_FIELDS_TABS + CUSTOM_FIELDS_TABS end # Return custom field html tag corresponding to its format - def custom_field_tag(name, custom_value) + def custom_field_tag(name, custom_value) custom_field = custom_value.custom_field field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name << "[]" if custom_field.multiple? @@ -77,32 +98,44 @@ custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value) end - def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil) + def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil, value='') field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name << "[]" if custom_field.multiple? field_id = "#{name}_custom_field_values_#{custom_field.id}" tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"} + unset_tag = '' + unless custom_field.is_required? + unset_tag = content_tag('label', + check_box_tag(field_name, '__none__', (value == '__none__'), :id => nil, :data => {:disables => "##{field_id}"}) + l(:button_clear), + :class => 'inline' + ) + end + field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) case field_format.try(:edit_as) when "date" - text_field_tag(field_name, '', tag_options.merge(:size => 10)) + - calendar_for(field_id) + text_field_tag(field_name, value, tag_options.merge(:size => 10)) + + calendar_for(field_id) + + unset_tag when "text" - text_area_tag(field_name, '', tag_options.merge(:rows => 3)) + text_area_tag(field_name, value, tag_options.merge(:rows => 3)) + + '
    '.html_safe + + unset_tag when "bool" select_tag(field_name, options_for_select([[l(:label_no_change_option), ''], [l(:general_text_yes), '1'], - [l(:general_text_no), '0']]), tag_options) + [l(:general_text_no), '0']], value), tag_options) when "list" options = [] options << [l(:label_no_change_option), ''] unless custom_field.multiple? options << [l(:label_none), '__none__'] unless custom_field.is_required? options += custom_field.possible_values_options(projects) - select_tag(field_name, options_for_select(options), tag_options.merge(:multiple => custom_field.multiple?)) + select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?)) else - text_field_tag(field_name, '', tag_options) + text_field_tag(field_name, value, tag_options) + + unset_tag end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/documents_helper.rb --- a/app/helpers/documents_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/documents_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/enumerations_helper.rb --- a/app/helpers/enumerations_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/enumerations_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/gantt_helper.rb --- a/app/helpers/gantt_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/gantt_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,19 +24,19 @@ when :in if gantt.zoom < 4 link_to_content_update l(:text_zoom_in), - params.merge(gantt.params.merge(:zoom => (gantt.zoom+1))), + params.merge(gantt.params.merge(:zoom => (gantt.zoom + 1))), :class => 'icon icon-zoom-in' else - content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe + content_tag(:span, l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe end when :out if gantt.zoom > 1 link_to_content_update l(:text_zoom_out), - params.merge(gantt.params.merge(:zoom => (gantt.zoom-1))), + params.merge(gantt.params.merge(:zoom => (gantt.zoom - 1))), :class => 'icon icon-zoom-out' else - content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe + content_tag(:span, l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe end end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/groups_helper.rb --- a/app/helpers/groups_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/groups_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,4 +24,19 @@ {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural} ] end + + def render_principals_for_new_group_users(group) + scope = User.active.sorted.not_in_group(group).like(params[:q]) + principal_count = scope.count + principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page'] + principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all + + s = content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'principals') + + links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| + link_to text, autocomplete_for_user_group_path(group, parameters.merge(:q => params[:q], :format => 'js')), :remote => true + } + + s + content_tag('p', links, :class => 'pagination') + end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/issue_categories_helper.rb --- a/app/helpers/issue_categories_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/issue_categories_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/issue_relations_helper.rb --- a/app/helpers/issue_relations_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/issue_relations_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/issue_statuses_helper.rb --- a/app/helpers/issue_statuses_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/issue_statuses_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/issues_helper.rb --- a/app/helpers/issues_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/issues_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -94,6 +94,20 @@ s.html_safe end + # Returns an array of error messages for bulk edited issues + def bulk_edit_error_messages(issues) + messages = {} + issues.each do |issue| + issue.errors.full_messages.each do |message| + messages[message] ||= [] + messages[message] << issue + end + end + messages.map { |message, issues| + "#{message}: " + issues.map {|i| "##{i.id}"}.join(', ') + } + end + # Returns a link for adding a new subtask to the given issue def link_to_new_subtask(issue) attrs = { @@ -146,12 +160,13 @@ end def render_custom_fields_rows(issue) - return if issue.custom_field_values.empty? + values = issue.visible_custom_field_values + return if values.empty? ordered_values = [] - half = (issue.custom_field_values.size / 2.0).ceil + half = (values.size / 2.0).ceil half.times do |i| - ordered_values << issue.custom_field_values[i] - ordered_values << issue.custom_field_values[i + half] + ordered_values << values[i] + ordered_values << values[i + half] end s = "\n" n = 0 @@ -184,36 +199,60 @@ def sidebar_queries unless @sidebar_queries - @sidebar_queries = Query.visible.all( - :order => "#{Query.table_name}.name ASC", + @sidebar_queries = IssueQuery.visible. + order("#{Query.table_name}.name ASC"). # Project specific queries and global queries - :conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]) - ) + where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]). + all end @sidebar_queries end def query_links(title, queries) + return '' if queries.empty? # links to #index on issues/show url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params - content_tag('h3', h(title)) + - queries.collect {|query| - css = 'query' - css << ' selected' if query == @query - link_to(h(query.name), url_params.merge(:query_id => query), :class => css) - }.join('
    ').html_safe + content_tag('h3', title) + "\n" + + content_tag('ul', + queries.collect {|query| + css = 'query' + css << ' selected' if query == @query + content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css)) + }.join("\n").html_safe, + :class => 'queries' + ) + "\n" end def render_sidebar_queries out = ''.html_safe - queries = sidebar_queries.select {|q| !q.is_public?} - out << query_links(l(:label_my_queries), queries) if queries.any? - queries = sidebar_queries.select {|q| q.is_public?} - out << query_links(l(:label_query_plural), queries) if queries.any? + out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?)) + out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?)) out end + def email_issue_attributes(issue, user) + items = [] + %w(author status priority assigned_to category fixed_version).each do |attribute| + unless issue.disabled_core_fields.include?(attribute+"_id") + items << "#{l("field_#{attribute}")}: #{issue.send attribute}" + end + end + issue.visible_custom_field_values(user).each do |value| + items << "#{value.custom_field.name}: #{show_value(value)}" + end + items + end + + def render_email_issue_attributes(issue, user, html=false) + items = email_issue_attributes(issue, user) + if html + content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe) + else + items.map{|s| "* #{s}"}.join("\n") + end + end + # Returns the textual representation of a journal details # as an array of strings def details_to_strings(details, no_html=false, options={}) @@ -222,23 +261,23 @@ values_by_field = {} details.each do |detail| if detail.property == 'cf' - field_id = detail.prop_key - field = CustomField.find_by_id(field_id) + field = detail.custom_field if field && field.multiple? - values_by_field[field_id] ||= {:added => [], :deleted => []} + values_by_field[field] ||= {:added => [], :deleted => []} if detail.old_value - values_by_field[field_id][:deleted] << detail.old_value + values_by_field[field][:deleted] << detail.old_value end if detail.value - values_by_field[field_id][:added] << detail.value + values_by_field[field][:added] << detail.value end next end end strings << show_detail(detail, no_html, options) end - values_by_field.each do |field_id, changes| - detail = JournalDetail.new(:property => 'cf', :prop_key => field_id) + values_by_field.each do |field, changes| + detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s) + detail.instance_variable_set "@custom_field", field if changes[:added].any? detail.value = changes[:added] strings << show_detail(detail, no_html, options) @@ -281,7 +320,7 @@ old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank? end when 'cf' - custom_field = CustomField.find_by_id(detail.prop_key) + custom_field = detail.custom_field if custom_field multiple = custom_field.multiple? label = custom_field.name @@ -290,6 +329,17 @@ end when 'attachment' label = l(:label_attachment) + when 'relation' + if detail.value && !detail.old_value + rel_issue = Issue.visible.find_by_id(detail.value) + value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" : + (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path])) + elsif detail.old_value && !detail.value + rel_issue = Issue.visible.find_by_id(detail.old_value) + old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" : + (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path])) + end + label = l(detail.prop_key.to_sym) end call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value }) @@ -301,7 +351,9 @@ unless no_html label = content_tag('strong', label) old_value = content_tag("i", h(old_value)) if detail.old_value - old_value = content_tag("del", old_value) if detail.old_value and detail.value.blank? + if detail.old_value && detail.value.blank? && detail.property != 'relation' + old_value = content_tag("del", old_value) + end if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key) # Link to the attachment if it has not been removed value = link_to_attachment(atta, :download => true, :only_path => options[:only_path]) @@ -337,7 +389,7 @@ else l(:text_journal_set_to, :label => label, :value => value).html_safe end - when 'attachment' + when 'attachment', 'relation' l(:text_journal_added, :label => label, :value => value).html_safe end else @@ -347,6 +399,9 @@ # Find the name of an associated record stored in the field attribute def find_name_by_reflection(field, id) + unless id.present? + return nil + end association = Issue.reflect_on_association(field.to_sym) if association record = association.class_name.constantize.find_by_id(id) @@ -370,44 +425,4 @@ end end end - - def issues_to_csv(issues, project, query, options={}) - decimal_separator = l(:general_csv_decimal_separator) - encoding = l(:general_csv_encoding) - columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns) - if options[:description] - if description = query.available_columns.detect {|q| q.name == :description} - columns << description - end - end - - export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| - # csv header fields - csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } - - # csv lines - issues.each do |issue| - col_values = columns.collect do |column| - s = if column.is_a?(QueryCustomFieldColumn) - cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} - show_value(cv) - else - value = column.value(issue) - if value.is_a?(Date) - format_date(value) - elsif value.is_a?(Time) - format_time(value) - elsif value.is_a?(Float) - ("%.2f" % value).gsub('.', decimal_separator) - else - value - end - end - s.to_s - end - csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) } - end - end - export - end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/journals_helper.rb --- a/app/helpers/journals_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/journals_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/mail_handler_helper.rb --- a/app/helpers/mail_handler_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/mail_handler_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/members_helper.rb --- a/app/helpers/members_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/members_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,4 +18,18 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module MembersHelper + def render_principals_for_new_members(project) + scope = Principal.active.sorted.not_member_of(project).like(params[:q]) + principal_count = scope.count + principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page'] + principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all + + s = content_tag('div', principals_check_box_tags('membership[user_ids][]', principals), :id => 'principals') + + links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| + link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true + } + + s + content_tag('p', links, :class => 'pagination') + end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/messages_helper.rb --- a/app/helpers/messages_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/messages_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/my_helper.rb --- a/app/helpers/my_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/my_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,10 +19,60 @@ module MyHelper + def calendar_items(startdt, enddt) + Issue.visible. + where(:project_id => User.current.projects.map(&:id)). + where("(start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)", startdt, enddt, startdt, enddt). + includes(:project, :tracker, :priority, :assigned_to). + all + end + def all_colleagues_of(user) # Return a list of all user ids who have worked with the given user # (on projects that are visible to the current user) user.projects.select { |p| p.visible? }.map { |p| p.members.map { |m| m.user_id } }.flatten.sort.uniq.reject { |i| user.id == i } end + def documents_items + Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).all + end + + def issuesassignedtome_items + Issue.visible.open. + where(:assigned_to_id => ([User.current.id] + User.current.group_ids)). + limit(10). + includes(:status, :project, :tracker, :priority). + order("#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC"). + all + end + + def issuesreportedbyme_items + Issue.visible. + where(:author_id => User.current.id). + limit(10). + includes(:status, :project, :tracker). + order("#{Issue.table_name}.updated_on DESC"). + all + end + + def issueswatched_items + Issue.visible.on_active_project.watched_by(User.current.id).recently_updated.limit(10).all + end + + def news_items + News.visible. + where(:project_id => User.current.projects.map(&:id)). + limit(10). + includes(:project, :author). + order("#{News.table_name}.created_on DESC"). + all + end + + def timelog_items + TimeEntry. + where("#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", User.current.id, Date.today - 6, Date.today). + includes(:activity, :project, {:issue => [:tracker, :status]}). + order("#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC"). + all + end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/news_helper.rb --- a/app/helpers/news_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/news_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/projects_helper.rb --- a/app/helpers/projects_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/projects_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -46,7 +46,7 @@ end options = '' - options << "" if project.allowed_parents.include?(nil) + options << "" if project.allowed_parents.include?(nil) options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected) content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id') end @@ -252,10 +252,11 @@ grouped[version.project.name] << [version.name, version.id] end + selected = selected.is_a?(Version) ? selected.id : selected if grouped.keys.size > 1 - grouped_options_for_select(grouped, selected && selected.id) + grouped_options_for_select(grouped, selected) else - options_for_select((grouped.values.first || []), selected && selected.id) + options_for_select((grouped.values.first || []), selected) end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/queries_helper.rb --- a/app/helpers/queries_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/queries_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,32 +24,35 @@ def filters_options(query) options = [[]] - sorted_options = query.available_filters.sort do |a, b| - ord = 0 - if !(a[1][:order] == 20 && b[1][:order] == 20) - ord = a[1][:order] <=> b[1][:order] - else - cn = (CustomField::CUSTOM_FIELDS_NAMES.index(a[1][:field].class.name) <=> - CustomField::CUSTOM_FIELDS_NAMES.index(b[1][:field].class.name)) - if cn != 0 - ord = cn - else - f = (a[1][:field] <=> b[1][:field]) - if f != 0 - ord = f - else - # assigned_to or author - ord = (a[0] <=> b[0]) - end - end - end - ord - end - options += sorted_options.map do |field, field_options| + options += query.available_filters.map do |field, field_options| [field_options[:name], field] end end + def query_filters_hidden_tags(query) + tags = ''.html_safe + query.filters.each do |field, options| + tags << hidden_field_tag("f[]", field, :id => nil) + tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil) + options[:values].each do |value| + tags << hidden_field_tag("v[#{field}][]", value, :id => nil) + end + end + tags + end + + def query_columns_hidden_tags(query) + tags = ''.html_safe + query.columns.each do |column| + tags << hidden_field_tag("c[]", column.name, :id => nil) + end + tags + end + + def query_hidden_tags(query) + query_filters_hidden_tags(query) + query_columns_hidden_tags(query) + end + def available_block_columns_tags(query) tags = ''.html_safe query.available_block_columns.each do |column| @@ -58,6 +61,19 @@ tags end + def query_available_inline_columns_options(query) + (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} + end + + def query_selected_inline_columns_options(query) + (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} + end + + def render_query_columns_selection(query, options={}) + tag_name = (options[:name] || 'c') + '[]' + render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} + end + def column_header(column) column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, :default_order => column.default_order) : @@ -88,7 +104,9 @@ when 'Date' format_date(value) when 'Fixnum' - if column.name == :done_ratio + if column.name == :id + link_to value, issue_path(issue) + elsif column.name == :done_ratio progress_bar(value, :width => '80px') else value.to_s @@ -106,7 +124,7 @@ when 'FalseClass' l(:general_text_No) when 'Issue' - link_to_issue(value, :subject => false) + value.visible? ? link_to_issue(value) : "##{value.id}" when 'IssueRelation' other = value.other_issue(issue) content_tag('span', @@ -117,26 +135,76 @@ end end + def csv_content(column, issue) + value = column.value(issue) + if value.is_a?(Array) + value.collect {|v| csv_value(column, issue, v)}.compact.join(', ') + else + csv_value(column, issue, value) + end + end + + def csv_value(column, issue, value) + case value.class.name + when 'Time' + format_time(value) + when 'Date' + format_date(value) + when 'Float' + sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator)) + when 'IssueRelation' + other = value.other_issue(issue) + l(value.label_for(issue)) + " ##{other.id}" + when 'TrueClass' + l(:general_text_Yes) + when 'FalseClass' + l(:general_text_No) + else + value.to_s + end + end + + def query_to_csv(items, query, options={}) + encoding = l(:general_csv_encoding) + columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns) + query.available_block_columns.each do |column| + if options[column.name].present? + columns << column + end + end + + export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| + # csv header fields + csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } + # csv lines + items.each do |item| + csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) } + end + end + export + end + # Retrieve query from session or build a new query def retrieve_query if !params[:query_id].blank? cond = "project_id IS NULL" cond << " OR project_id = #{@project.id}" if @project - @query = Query.find(params[:query_id], :conditions => cond) + @query = IssueQuery.where(cond).find(params[:query_id]) raise ::Unauthorized unless @query.visible? @query.project = @project session[:query] = {:id => @query.id, :project_id => @query.project_id} sort_clear elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil) # Give it a name, required to be valid - @query = Query.new(:name => "_") + @query = IssueQuery.new(:name => "_") @query.project = @project - build_query_from_params + @query.build_from_params(params) session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} else # retrieve from session - @query = Query.find_by_id(session[:query][:id]) if session[:query][:id] - @query ||= Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) + @query = nil + @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] + @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) @query.project = @project end end @@ -144,10 +212,10 @@ def retrieve_query_from_session if session[:query] if session[:query][:id] - @query = Query.find_by_id(session[:query][:id]) + @query = IssueQuery.find_by_id(session[:query][:id]) return unless @query else - @query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) + @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) end if session[:query].has_key?(:project_id) @query.project_id = session[:query][:project_id] @@ -157,17 +225,4 @@ @query end end - - def build_query_from_params - if params[:fields] || params[:f] - @query.filters = {} - @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) - else - @query.available_filters.keys.each do |field| - @query.add_short_filter(field, params[field]) if params[field] - end - end - @query.group_by = params[:group_by] || (params[:query] && params[:query][:group_by]) - @query.column_names = params[:c] || (params[:query] && params[:query][:column_names]) - end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/reports_helper.rb --- a/app/helpers/reports_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/reports_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,7 +24,7 @@ data.each { |row| match = 1 criteria.each { |k, v| - match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t")) + match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && (v == 0 ? ['f', false] : ['t', true]).include?(row[k])) } unless criteria.nil? a = a + row["total"].to_i if match == 1 } unless data.nil? @@ -35,4 +35,9 @@ a = aggregate data, criteria a > 0 ? link_to(h(a), *args) : '-' end + + def aggregate_path(project, field, row, options={}) + parameters = {:set_filter => 1, :subproject_id => '!*', field => row.id}.merge(options) + project_issues_path(row.is_a?(Project) ? row : project, parameters) + end end diff -r d98d22a98252 -r afce8026aaeb app/helpers/repositories_helper.rb --- a/app/helpers/repositories_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/repositories_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -43,7 +43,7 @@ end def render_changeset_changes - changes = @changeset.filechanges.find(:all, :limit => 1000, :order => 'path').collect do |change| + changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change| case change.action when 'A' # Detects moved/copied files diff -r d98d22a98252 -r afce8026aaeb app/helpers/roles_helper.rb --- a/app/helpers/roles_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/roles_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/routes_helper.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/helpers/routes_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,39 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module RoutesHelper + + # Returns the path to project issues or to the cross-project + # issue list if project is nil + def _project_issues_path(project, *args) + if project + project_issues_path(project, *args) + else + issues_path(*args) + end + end + + def _project_calendar_path(project, *args) + project ? project_calendar_path(project, *args) : issues_calendar_path(*args) + end + + def _project_gantt_path(project, *args) + project ? project_gantt_path(project, *args) : issues_gantt_path(*args) + end +end diff -r d98d22a98252 -r afce8026aaeb app/helpers/search_helper.rb --- a/app/helpers/search_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/search_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/settings_helper.rb --- a/app/helpers/settings_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/settings_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -53,7 +53,7 @@ check_box_tag( "settings[#{setting}][]", value, - Setting.send(setting).include?(value), + setting_values.include?(value), :id => nil ) + text.to_s, :class => (options[:inline] ? 'inline' : 'block') diff -r d98d22a98252 -r afce8026aaeb app/helpers/sort_helper.rb --- a/app/helpers/sort_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/sort_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -80,12 +80,13 @@ @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') end + # Returns an array of SQL fragments used to sort the list def to_sql sql = @criteria.collect do |k,o| if s = @available_criteria[k] - (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}).join(', ') + (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}) end - end.compact.join(', ') + end.flatten.compact sql.blank? ? nil : sql end diff -r d98d22a98252 -r afce8026aaeb app/helpers/timelog_helper.rb --- a/app/helpers/timelog_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/timelog_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -56,10 +56,10 @@ end def select_hours(data, criteria, value) - if value.to_s.empty? - data.select {|row| row[criteria].blank? } + if value.to_s.empty? + data.select {|row| row[criteria].blank? } else - data.select {|row| row[criteria].to_s == value.to_s} + data.select {|row| row[criteria].to_s == value.to_s} end end @@ -86,49 +86,6 @@ value) end - def entries_to_csv(entries) - decimal_separator = l(:general_csv_decimal_separator) - custom_fields = TimeEntryCustomField.find(:all) - export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| - # csv header fields - headers = [l(:field_spent_on), - l(:field_user), - l(:field_activity), - l(:field_project), - l(:field_issue), - l(:field_tracker), - l(:field_subject), - l(:field_hours), - l(:field_comments) - ] - # Export custom fields - headers += custom_fields.collect(&:name) - - csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( - c.to_s, - l(:general_csv_encoding) ) } - # csv lines - entries.each do |entry| - fields = [format_date(entry.spent_on), - entry.user, - entry.activity, - entry.project, - (entry.issue ? entry.issue.id : nil), - (entry.issue ? entry.issue.tracker : nil), - (entry.issue ? entry.issue.subject : nil), - entry.hours.to_s.gsub('.', decimal_separator), - entry.comments - ] - fields += custom_fields.collect {|f| show_value(entry.custom_field_values.detect {|v| v.custom_field_id == f.id}) } - - csv << fields.collect {|c| Redmine::CodesetUtil.from_utf8( - c.to_s, - l(:general_csv_encoding) ) } - end - end - export - end - def format_criteria_value(criteria_options, value) if value.blank? "[#{l(:label_none)}]" @@ -150,14 +107,14 @@ # Column headers headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) } headers += report.periods - headers << l(:label_total) + headers << l(:label_total_time) csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( c.to_s, l(:general_csv_encoding) ) } # Content report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours) # Total row - str_total = Redmine::CodesetUtil.from_utf8(l(:label_total), l(:general_csv_encoding)) + str_total = Redmine::CodesetUtil.from_utf8(l(:label_total_time), l(:general_csv_encoding)) row = [ str_total ] + [''] * (report.criteria.size - 1) total = 0 report.periods.each do |period| diff -r d98d22a98252 -r afce8026aaeb app/helpers/trackers_helper.rb --- a/app/helpers/trackers_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/trackers_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/users_helper.rb --- a/app/helpers/users_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/users_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/versions_helper.rb --- a/app/helpers/versions_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/versions_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -35,12 +35,9 @@ h = Hash.new {|k,v| k[v] = [0, 0]} begin # Total issue count - Issue.count(:group => criteria, - :conditions => ["#{Issue.table_name}.fixed_version_id = ?", version.id]).each {|c,s| h[c][0] = s} + Issue.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][0] = s} # Open issues count - Issue.count(:group => criteria, - :include => :status, - :conditions => ["#{Issue.table_name}.fixed_version_id = ? AND #{IssueStatus.table_name}.is_closed = ?", version.id, false]).each {|c,s| h[c][1] = s} + Issue.open.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][1] = s} rescue ActiveRecord::RecordNotFound # When grouping by an association, Rails throws this exception if there's no result (bug) end diff -r d98d22a98252 -r afce8026aaeb app/helpers/watchers_helper.rb --- a/app/helpers/watchers_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/watchers_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,24 +20,31 @@ module WatchersHelper def watcher_tag(object, user, options={}) - content_tag("span", watcher_link(object, user), :class => watcher_css(object)) + ActiveSupport::Deprecation.warn "#watcher_tag is deprecated and will be removed in Redmine 3.0. Use #watcher_link instead." + watcher_link(object, user) end - def watcher_link(object, user) - return '' unless user && user.logged? && object.respond_to?('watched_by?') - watched = object.watched_by?(user) - url = {:controller => 'watchers', - :action => (watched ? 'unwatch' : 'watch'), - :object_type => object.class.to_s.underscore, - :object_id => object.id} - link_to((watched ? l(:button_unwatch) : l(:button_watch)), url, - :remote => true, :method => 'post', :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off')) + def watcher_link(objects, user) + return '' unless user && user.logged? + objects = Array.wrap(objects) + watched = Watcher.any_watched?(objects, user) + css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ') + text = watched ? l(:button_unwatch) : l(:button_watch) + url = watch_path( + :object_type => objects.first.class.to_s.underscore, + :object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort) + ) + method = watched ? 'delete' : 'post' + + link_to text, url, :remote => true, :method => method, :class => css end # Returns the css class used to identify watch links for a given +object+ - def watcher_css(object) - "#{object.class.to_s.underscore}-#{object.id}-watcher" + def watcher_css(objects) + objects = Array.wrap(objects) + id = (objects.size == 1 ? objects.first.id : 'bulk') + "#{objects.first.class.to_s.underscore}-#{id}-watcher" end # Returns a comma separated list of users watching the given object @@ -56,11 +63,11 @@ :user_id => user} s << ' ' s << link_to(image_tag('delete.png'), url, - :remote => true, :method => 'post', :style => "vertical-align: middle", :class => "delete") + :remote => true, :method => 'delete', :class => "delete") end - content << content_tag('li', s) + content << content_tag('li', s, :class => "user-#{user.id}") end - content.present? ? content_tag('ul', content) : content + content.present? ? content_tag('ul', content, :class => 'watchers') : content end def watchers_checkboxes(object, users, checked=nil) diff -r d98d22a98252 -r afce8026aaeb app/helpers/welcome_helper.rb --- a/app/helpers/welcome_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/welcome_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/wiki_helper.rb --- a/app/helpers/wiki_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/wiki_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/helpers/workflows_helper.rb --- a/app/helpers/workflows_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/helpers/workflows_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -22,11 +22,20 @@ field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field) end - def field_permission_tag(permissions, status, field) + def field_permission_tag(permissions, status, field, role) name = field.is_a?(CustomField) ? field.id.to_s : field options = [["", ""], [l(:label_readonly), "readonly"]] options << [l(:label_required), "required"] unless field_required?(field) + html_options = {} + selected = permissions[status.id][name] - select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, permissions[status.id][name])) + hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field) + if hidden + options[0][0] = l(:label_hidden) + selected = '' + html_options[:disabled] = true + end + + select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options) end end diff -r d98d22a98252 -r afce8026aaeb app/models/attachment.rb --- a/app/models/attachment.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/attachment.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,6 +16,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require "digest/md5" +require "fileutils" class Attachment < ActiveRecord::Base belongs_to :container, :polymorphic => true @@ -92,9 +93,6 @@ def filename=(arg) write_attribute :filename, sanitize_filename(arg.to_s) - if new_record? && disk_filename.blank? - self.disk_filename = Attachment.disk_filename(filename) - end filename end @@ -102,7 +100,13 @@ # and computes its MD5 hash def files_to_final_location if @temp_file && (@temp_file.size > 0) - logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") + self.disk_directory = target_directory + self.disk_filename = Attachment.disk_filename(filename, disk_directory) + logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") if logger + path = File.dirname(diskfile) + unless File.directory?(path) + FileUtils.mkdir_p(path) + end md5 = Digest::MD5.new File.open(diskfile, "wb") do |f| if @temp_file.respond_to?(:read) @@ -134,7 +138,7 @@ # Returns file's location on disk def diskfile - File.join(self.class.storage_path, disk_filename.to_s) + File.join(self.class.storage_path, disk_directory.to_s, disk_filename.to_s) end def title @@ -154,11 +158,19 @@ end def visible?(user=User.current) - container && container.attachments_visible?(user) + if container_id + container && container.attachments_visible?(user) + else + author == user + end end def deletable?(user=User.current) - container && container.attachments_deletable?(user) + if container_id + container && container.attachments_deletable?(user) + else + author == user + end end def image? @@ -251,6 +263,37 @@ Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all end + # Moves an existing attachment to its target directory + def move_to_target_directory! + return unless !new_record? & readable? + + src = diskfile + self.disk_directory = target_directory + dest = diskfile + + return if src == dest + + if !FileUtils.mkdir_p(File.dirname(dest)) + logger.error "Could not create directory #{File.dirname(dest)}" if logger + return + end + + if !FileUtils.mv(src, dest) + logger.error "Could not move attachment from #{src} to #{dest}" if logger + return + end + + update_column :disk_directory, disk_directory + end + + # Moves existing attachments that are stored at the root of the files + # directory (ie. created before Redmine 2.3) to their target subdirectories + def self.move_from_root_to_target_directory + Attachment.where("disk_directory IS NULL OR disk_directory = ''").find_each do |attachment| + attachment.move_to_target_directory! + end + end + private # Physically deletes the file from the file system @@ -262,14 +305,21 @@ def sanitize_filename(value) # get only the filename, not the whole path - just_filename = value.gsub(/^.*(\\|\/)/, '') + just_filename = value.gsub(/\A.*(\\|\/)/m, '') # Finally, replace invalid characters with underscore - @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_') + @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>\n\r]+/, '_') end - # Returns an ASCII or hashed filename - def self.disk_filename(filename) + # Returns the subdirectory in which the attachment will be saved + def target_directory + time = created_on || DateTime.now + time.strftime("%Y/%m") + end + + # Returns an ASCII or hashed filename that do not + # exists yet in the given subdirectory + def self.disk_filename(filename, directory=nil) timestamp = DateTime.now.strftime("%y%m%d%H%M%S") ascii = '' if filename =~ %r{^[a-zA-Z0-9_\.\-]*$} @@ -279,7 +329,7 @@ # keep the extension if any ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$} end - while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}")) + while File.exist?(File.join(storage_path, directory.to_s, "#{timestamp}_#{ascii}")) timestamp.succ! end "#{timestamp}_#{ascii}" diff -r d98d22a98252 -r afce8026aaeb app/models/auth_source.rb --- a/app/models/auth_source.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/auth_source.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -48,6 +48,24 @@ write_ciphered_attribute(:account_password, arg) end + def searchable? + false + end + + def self.search(q) + results = [] + AuthSource.all.each do |source| + begin + if source.searchable? + results += source.search(q) + end + rescue AuthSourceException => e + logger.error "Error while searching users in #{source.name}: #{e.message}" + end + end + results + end + def allow_password_changes? self.class.allow_password_changes? end diff -r d98d22a98252 -r afce8026aaeb app/models/auth_source_ldap.rb --- a/app/models/auth_source_ldap.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/auth_source_ldap.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -15,7 +15,6 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'iconv' require 'net/ldap' require 'net/ldap/dn' require 'timeout' @@ -64,6 +63,32 @@ "LDAP" end + # Returns true if this source can be searched for users + def searchable? + !account.to_s.include?("$login") && %w(login firstname lastname mail).all? {|a| send("attr_#{a}?")} + end + + # Searches the source for users and returns an array of results + def search(q) + q = q.to_s.strip + return [] unless searchable? && q.present? + + results = [] + search_filter = base_filter & Net::LDAP::Filter.begins(self.attr_login, q) + ldap_con = initialize_ldap_con(self.account, self.account_password) + ldap_con.search(:base => self.base_dn, + :filter => search_filter, + :attributes => ['dn', self.attr_login, self.attr_firstname, self.attr_lastname, self.attr_mail], + :size => 10) do |entry| + attrs = get_user_attributes_from_ldap_entry(entry) + attrs[:login] = AuthSourceLdap.get_attr(entry, self.attr_login) + results << attrs + end + results + rescue Net::LDAP::LdapError => e + raise AuthSourceException.new(e.message) + end + private def with_timeout(&block) @@ -84,6 +109,14 @@ nil end + def base_filter + filter = Net::LDAP::Filter.eq("objectClass", "*") + if f = ldap_filter + filter = filter & f + end + filter + end + def validate_filter if filter.present? && ldap_filter.nil? errors.add(:filter, :invalid) @@ -140,14 +173,8 @@ else ldap_con = initialize_ldap_con(self.account, self.account_password) end - login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) - object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) attrs = {} - - search_filter = object_filter & login_filter - if f = ldap_filter - search_filter = search_filter & f - end + search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login) ldap_con.search( :base => self.base_dn, :filter => search_filter, diff -r d98d22a98252 -r afce8026aaeb app/models/board.rb --- a/app/models/board.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/board.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,8 +30,9 @@ validates_length_of :description, :maximum => 255 validate :validate_board - scope :visible, lambda {|*args| { :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } } + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + } safe_attributes 'name', 'description', 'parent_id', 'move_to' diff -r d98d22a98252 -r afce8026aaeb app/models/change.rb --- a/app/models/change.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/change.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/changeset.rb --- a/app/models/changeset.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/changeset.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -15,8 +15,6 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'iconv' - class Changeset < ActiveRecord::Base belongs_to :repository belongs_to :user @@ -49,9 +47,9 @@ validates_uniqueness_of :revision, :scope => :repository_id validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true - scope :visible, - lambda {|*args| { :include => {:repository => :project}, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } } + scope :visible, lambda {|*args| + includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args)) + } after_create :scan_for_issues before_create :before_create_cs @@ -120,22 +118,25 @@ ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip) ref_keywords_any = ref_keywords.delete('*') # keywords used to fix issues - fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip) + fix_keywords = Setting.commit_update_keywords_array.map {|r| r['keywords']}.flatten.compact kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|") referenced_issues = [] comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match| - action, refs = match[2], match[3] + action, refs = match[2].to_s.downcase, match[3] next unless action.present? || ref_keywords_any refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m| issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2] if issue referenced_issues << issue - fix_issue(issue) if fix_keywords.include?(action.to_s.downcase) - log_time(issue, hours) if hours && Setting.commit_logtime_enabled? + # Don't update issues or log time when importing old commits + unless repository.created_on && committed_on && committed_on < repository.created_on + fix_issue(issue, action) if fix_keywords.include?(action) + log_time(issue, hours) if hours && Setting.commit_logtime_enabled? + end end end end @@ -153,13 +154,14 @@ end def text_tag(ref_project=nil) + repo = "" + if repository && repository.identifier.present? + repo = "#{repository.identifier}|" + end tag = if scmid? - "commit:#{scmid}" + "commit:#{repo}#{scmid}" else - "r#{revision}" - end - if repository && repository.identifier.present? - tag = "#{repository.identifier}|#{tag}" + "#{repo}r#{revision}" end if ref_project && project && ref_project != project tag = "#{project.identifier}:#{tag}" @@ -212,25 +214,26 @@ private - def fix_issue(issue) - status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i) - if status.nil? - logger.warn("No status matches commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger - return issue - end - + # Updates the +issue+ according to +action+ + def fix_issue(issue, action) # the issue may have been updated by the closure of another one (eg. duplicate) issue.reload # don't change the status is the issue is closed return if issue.status && issue.status.is_closed? - journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project))) - issue.status = status - unless Setting.commit_fix_done_ratio.blank? - issue.done_ratio = Setting.commit_fix_done_ratio.to_i + journal = issue.init_journal(user || User.anonymous, + ll(Setting.default_language, + :text_status_changed_by_changeset, + text_tag(issue.project))) + rule = Setting.commit_update_keywords_array.detect do |rule| + rule['keywords'].include?(action) && + (rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s) + end + if rule + issue.assign_attributes rule.slice(*Issue.attribute_names) end Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update, - { :changeset => self, :issue => issue }) + { :changeset => self, :issue => issue, :action => action }) unless issue.save logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger end diff -r d98d22a98252 -r afce8026aaeb app/models/comment.rb --- a/app/models/comment.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/comment.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -22,5 +22,16 @@ validates_presence_of :commented, :author, :comments + after_create :send_notification + safe_attributes 'comments' + + private + + def send_notification + mailer_method = "#{commented.class.name.underscore}_comment_added" + if Setting.notified_events.include?(mailer_method) + Mailer.send(mailer_method, self).deliver + end + end end diff -r d98d22a98252 -r afce8026aaeb app/models/comment_observer.rb --- a/app/models/comment_observer.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class CommentObserver < ActiveRecord::Observer - def after_create(comment) - if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added') - Mailer.news_comment_added(comment).deliver - end - end -end diff -r d98d22a98252 -r afce8026aaeb app/models/custom_field.rb --- a/app/models/custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,39 +19,43 @@ include Redmine::SubclassFactory has_many :custom_values, :dependent => :delete_all + has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id" acts_as_list :scope => 'type = \'#{self.class}\'' serialize :possible_values validates_presence_of :name, :field_format validates_uniqueness_of :name, :scope => :type validates_length_of :name, :maximum => 30 - validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats + validates_inclusion_of :field_format, :in => Proc.new { Redmine::CustomFieldFormat.available_formats } + validate :validate_custom_field - validate :validate_custom_field before_validation :set_searchable + after_save :handle_multiplicity_change + after_save do |field| + if field.visible_changed? && field.visible + field.roles.clear + end + end - CUSTOM_FIELDS_TABS = [ - {:name => 'IssueCustomField', :partial => 'custom_fields/index', - :label => :label_issue_plural}, - {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', - :label => :label_spent_time}, - {:name => 'ProjectCustomField', :partial => 'custom_fields/index', - :label => :label_project_plural}, - {:name => 'VersionCustomField', :partial => 'custom_fields/index', - :label => :label_version_plural}, - {:name => 'UserCustomField', :partial => 'custom_fields/index', - :label => :label_user_plural}, - {:name => 'GroupCustomField', :partial => 'custom_fields/index', - :label => :label_group_plural}, - {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', - :label => TimeEntryActivity::OptionName}, - {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', - :label => IssuePriority::OptionName}, - {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', - :label => DocumentCategory::OptionName} - ] + scope :sorted, lambda { order("#{table_name}.position ASC") } + scope :visible, lambda {|*args| + user = args.shift || User.current + if user.admin? + # nop + elsif user.memberships.any? + where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" + + " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" + + " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" + + " WHERE m.user_id = ?)", + true, user.id) + else + where(:visible => true) + end + } - CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]} + def visible_by?(project, user=User.current) + visible? || user.admin? + end def field_format=(arg) # cannot change format of a saved custom field @@ -119,8 +123,10 @@ values.each do |value| value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) end + values + else + [] end - values || [] end end @@ -169,7 +175,7 @@ keyword end end - + # Returns a ORDER BY clause that can used to sort customized # objects by their value of the custom field. # Returns nil if the custom field can not be used for sorting. @@ -178,18 +184,12 @@ case field_format when 'string', 'text', 'list', 'date', 'bool' # COALESCE is here to make sure that blank and NULL values are sorted equally - "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" + - " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + - " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + - " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" + "COALESCE(#{join_alias}.value, '')" when 'int', 'float' # Make the database cast values into numeric # Postgresql will raise an error if a value can not be casted! # CustomValue validations should ensure that it doesn't occur - "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" + - " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + - " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + - " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)" + "CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))" when 'user', 'version' value_class.fields_for_order_statement(value_join_alias) else @@ -199,16 +199,13 @@ # Returns a GROUP BY clause that can used to group by custom value # Returns nil if the custom field can not be used for grouping. - def group_statement + def group_statement return nil if multiple? case field_format when 'list', 'date', 'bool', 'int' order_statement when 'user', 'version' - "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" + - " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + - " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + - " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" + "COALESCE(#{join_alias}.value, '')" else nil end @@ -221,13 +218,35 @@ " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + " AND #{join_alias}.custom_field_id = #{id}" + + " AND (#{visibility_by_project_condition})" + " AND #{join_alias}.value <> ''" + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" + - " ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id" + " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id" + when 'int', 'float' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND (#{visibility_by_project_condition})" + + " AND #{join_alias}.value <> ''" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + when 'string', 'text', 'list', 'date', 'bool' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND (#{visibility_by_project_condition})" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" else nil end @@ -241,6 +260,33 @@ join_alias + "_" + field_format end + def visibility_by_project_condition(project_key=nil, user=User.current) + if visible? || user.admin? + "1=1" + elsif user.anonymous? + "1=0" + else + project_key ||= "#{self.class.customized_class.table_name}.project_id" + "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" + + " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" + + " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" + + " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})" + end + end + + def self.visibility_condition + if user.admin? + "1=1" + elsif user.anonymous? + "#{table_name}.visible" + else + "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" + + " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" + + " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" + + " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})" + end + end + def <=>(field) position <=> field.position end @@ -257,12 +303,12 @@ def self.customized_class self.name =~ /^(.+)CustomField$/ - begin; $1.constantize; rescue nil; end + $1.constantize rescue nil end # to move in project_custom_field def self.for_all - find(:all, :conditions => ["is_for_all=?", true], :order => 'position') + where(:is_for_all => true).order('position').all end def type_name @@ -304,10 +350,10 @@ # Returns the error message for the given value regarding its format def validate_field_value_format(value) errs = [] - if value.present? + unless value.to_s == '' errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp) - errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length - errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length + errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length && min_length > 0 && value.length < min_length + errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length && max_length > 0 && value.length > max_length # Format specific validations case field_format @@ -323,4 +369,20 @@ end errs end + + # Removes multiple values for the custom field after setting the multiple attribute to false + # We kepp the value with the highest id for each customized object + def handle_multiplicity_change + if !new_record? && multiple_was && !multiple + ids = custom_values. + where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" + + " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" + + " AND cve.id > #{CustomValue.table_name}.id)"). + pluck(:id) + + if ids.any? + custom_values.where(:id => ids).delete_all + end + end + end end diff -r d98d22a98252 -r afce8026aaeb app/models/custom_field_value.rb --- a/app/models/custom_field_value.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/custom_field_value.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/custom_value.rb --- a/app/models/custom_value.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/custom_value.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/document.rb --- a/app/models/document.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/document.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,19 +19,22 @@ include Redmine::SafeAttributes belongs_to :project belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id" - acts_as_attachable :delete_permission => :manage_documents + acts_as_attachable :delete_permission => :delete_documents acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"}, - :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil }, + :author => Proc.new {|o| o.attachments.reorder("#{Attachment.table_name}.created_on ASC").first.try(:author) }, :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}} acts_as_activity_provider :find_options => {:include => :project} validates_presence_of :project, :title, :category validates_length_of :title, :maximum => 60 - scope :visible, lambda {|*args| { :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_documents, *args) } } + after_create :send_notification + + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_documents, *args)) + } safe_attributes 'category_id', 'title', 'description' @@ -53,4 +56,12 @@ end @updated_on end + + private + + def send_notification + if Setting.notified_events.include?('document_added') + Mailer.document_added(self).deliver + end + end end diff -r d98d22a98252 -r afce8026aaeb app/models/document_category.rb --- a/app/models/document_category.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/document_category.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/document_category_custom_field.rb --- a/app/models/document_category_custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/document_category_custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/document_observer.rb --- a/app/models/document_observer.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class DocumentObserver < ActiveRecord::Observer - def after_create(document) - Mailer.document_added(document).deliver if Setting.notified_events.include?('document_added') - end -end diff -r d98d22a98252 -r afce8026aaeb app/models/enabled_module.rb --- a/app/models/enabled_module.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/enabled_module.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/enumeration.rb --- a/app/models/enumeration.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/enumeration.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,7 +24,7 @@ acts_as_list :scope => 'type = \'#{type}\'' acts_as_customizable - acts_as_tree :order => 'position ASC' + acts_as_tree :order => "#{Enumeration.table_name}.position ASC" before_destroy :check_integrity before_save :check_default @@ -35,9 +35,10 @@ validates_uniqueness_of :name, :scope => [:type, :project_id] validates_length_of :name, :maximum => 30 - scope :shared, where(:project_id => nil) - scope :sorted, order("#{table_name}.position ASC") - scope :active, where(:active => true) + scope :shared, lambda { where(:project_id => nil) } + scope :sorted, lambda { order("#{table_name}.position ASC") } + scope :active, lambda { where(:active => true) } + scope :system, lambda { where(:project_id => nil) } scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} def self.default diff -r d98d22a98252 -r afce8026aaeb app/models/group.rb --- a/app/models/group.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/group.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,11 +25,12 @@ validates_presence_of :lastname validates_uniqueness_of :lastname, :case_sensitive => false - validates_length_of :lastname, :maximum => 30 + validates_length_of :lastname, :maximum => 255 before_destroy :remove_references_before_destroy - scope :sorted, order("#{table_name}.lastname ASC") + scope :sorted, lambda { order("#{table_name}.lastname ASC") } + scope :named, lambda {|arg| where("LOWER(#{table_name}.lastname) = LOWER(?)", arg.to_s.strip)} safe_attributes 'name', 'user_ids', @@ -62,8 +63,11 @@ def user_removed(user) members.each do |member| - MemberRole.find(:all, :include => :member, - :conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy) + MemberRole. + includes(:member). + where("#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids). + all. + each(&:destroy) end end diff -r d98d22a98252 -r afce8026aaeb app/models/group_custom_field.rb --- a/app/models/group_custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/group_custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/issue.rb --- a/app/models/issue.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/issue.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,6 +18,7 @@ class Issue < ActiveRecord::Base include Redmine::SafeAttributes include Redmine::Utils::DateCalculation + include Redmine::I18n belongs_to :project belongs_to :tracker @@ -67,29 +68,41 @@ validates_length_of :subject, :maximum => 255 validates_inclusion_of :done_ratio, :in => 0..100 - validates_numericality_of :estimated_hours, :allow_nil => true + validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid} + validates :start_date, :date => true + validates :due_date, :date => true validate :validate_issue, :validate_required_fields - scope :visible, - lambda {|*args| { :include => :project, - :conditions => Issue.visible_condition(args.shift || User.current, *args) } } + scope :visible, lambda {|*args| + includes(:project).where(Issue.visible_condition(args.shift || User.current, *args)) + } scope :open, lambda {|*args| is_closed = args.size > 0 ? !args.first : false - {:conditions => ["#{IssueStatus.table_name}.is_closed = ?", is_closed], :include => :status} + includes(:status).where("#{IssueStatus.table_name}.is_closed = ?", is_closed) } - scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC" - scope :on_active_project, :include => [:status, :project, :tracker], - :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"] + scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") } + scope :on_active_project, lambda { + includes(:status, :project, :tracker).where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE) + } + scope :fixed_version, lambda {|versions| + ids = [versions].flatten.compact.map {|v| v.is_a?(Version) ? v.id : v} + ids.any? ? where(:fixed_version_id => ids) : where('1=0') + } before_create :default_assign - before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change + before_save :close_duplicates, :update_done_ratio_from_issue_status, + :force_updated_on_change, :update_closed_on, :set_assigned_to_was after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?} - after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal + after_save :reschedule_following_issues, :update_nested_set_attributes, + :update_parent_attributes, :create_journal # Should be after_create but would be called before previous after_save callbacks after_save :after_create_from_copy after_destroy :update_parent_attributes + after_create :send_notification + # Keep it at the end of after_save callbacks + after_save :clear_assigned_to_was # Returns a SQL conditions string used to find all issues visible by the specified user def self.visible_condition(user, options={}) @@ -99,10 +112,10 @@ when 'all' nil when 'default' - user_ids = [user.id] + user.groups.map(&:id) + user_ids = [user.id] + user.groups.map(&:id).compact "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))" when 'own' - user_ids = [user.id] + user.groups.map(&:id) + user_ids = [user.id] + user.groups.map(&:id).compact "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))" else '1=0' @@ -133,6 +146,11 @@ end end + # Returns true if user or current user is allowed to edit or add a note to the issue + def editable?(user=User.current) + user.allowed_to?(:edit_issues, project) || user.allowed_to?(:add_issue_notes, project) + end + def initialize(attributes=nil, *args) super if new_record? @@ -143,6 +161,13 @@ end end + def create_or_update + super + ensure + @status_was = nil + end + private :create_or_update + # AR#Persistence#destroy would raise and RecordNotFound exception # if the issue was already deleted or updated (non matching lock_version). # This is a problem when bulk deleting issues or deleting a project @@ -165,10 +190,12 @@ super end + alias :base_reload :reload def reload(*args) @workflow_rule_by_attribute = nil @assignable_versions = nil - super + @relations = nil + base_reload(*args) end # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields @@ -176,6 +203,13 @@ (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : [] end + def visible_custom_field_values(user=nil) + user_real = user || User.current + custom_field_values.select do |value| + value.custom_field.visible_by?(project, user_real) + end + end + # Copies attributes from another issue, arg can be an id or an Issue def copy_from(arg, options={}) issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg) @@ -326,8 +360,7 @@ if issue.new_record? issue.copy? elsif user.allowed_to?(:move_issues, issue.project) - projects = Issue.allowed_target_projects_on_move(user) - projects.include?(issue.project) && projects.size > 1 + Issue.allowed_target_projects_on_move.count > 1 end } @@ -394,7 +427,7 @@ # Project and Tracker must be set before since new_statuses_allowed_to depends on it. if (p = attrs.delete('project_id')) && safe_attribute?('project_id') - if allowed_target_projects(user).collect(&:id).include?(p.to_i) + if allowed_target_projects(user).where(:id => p.to_i).exists? self.project_id = p end end @@ -424,11 +457,15 @@ end if attrs['custom_field_values'].present? - attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| read_only_attribute_names(user).include? k.to_s} + editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s} + # TODO: use #select when ruby1.8 support is dropped + attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| !editable_custom_field_ids.include?(k.to_s)} end if attrs['custom_fields'].present? - attrs['custom_fields'] = attrs['custom_fields'].reject {|c| read_only_attribute_names(user).include? c['id'].to_s} + editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s} + # TODO: use #select when ruby1.8 support is dropped + attrs['custom_fields'] = attrs['custom_fields'].reject {|c| !editable_custom_field_ids.include?(c['id'].to_s)} end # mass-assignment security bypass @@ -441,7 +478,7 @@ # Returns the custom_field_values that can be edited by the given user def editable_custom_field_values(user=nil) - custom_field_values.reject do |value| + visible_custom_field_values(user).reject do |value| read_only_attribute_names(user).include?(value.custom_field_id.to_s) end end @@ -526,20 +563,12 @@ end def validate_issue - if due_date.nil? && @attributes['due_date'].present? - errors.add :due_date, :not_a_date - end - - if start_date.nil? && @attributes['start_date'].present? - errors.add :start_date, :not_a_date - end - - if due_date && start_date && due_date < start_date + if due_date && start_date && (start_date_changed? || due_date_changed?) && due_date < start_date errors.add :due_date, :greater_than_start_date end - if start_date && soonest_start && start_date < soonest_start - errors.add :start_date, :invalid + if start_date && start_date_changed? && soonest_start && start_date < soonest_start + errors.add :start_date, :earlier_than_minimum_start_date, :date => format_date(soonest_start) end if fixed_version @@ -563,6 +592,8 @@ elsif @parent_issue if !valid_parent_project?(@parent_issue) errors.add :parent_issue_id, :invalid + elsif (@parent_issue != parent) && (all_dependent_issues.include?(@parent_issue) || @parent_issue.all_dependent_issues.include?(self)) + errors.add :parent_issue_id, :invalid elsif !new_record? # moving an existing issue if @parent_issue.root_id != root_id @@ -633,6 +664,14 @@ scope end + # Returns the initial status of the issue + # Returns nil for a new issue + def status_was + if status_id_was && status_id_was.to_i > 0 + @status_was ||= IssueStatus.find_by_id(status_id_was) + end + end + # Return true if the issue is closed, otherwise false def closed? self.status.is_closed? @@ -653,9 +692,7 @@ # Return true if the issue is being closed def closing? if !new_record? && status_id_changed? - status_was = IssueStatus.find_by_id(status_id_was) - status_new = IssueStatus.find_by_id(status_id) - if status_was && status_new && !status_was.is_closed? && status_new.is_closed? + if status_was && status && !status_was.is_closed? && status.is_closed? return true end end @@ -670,7 +707,7 @@ # Is the amount of work done less than it should for the due date def behind_schedule? return false if start_date.nil? || due_date.nil? - done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor + done_date = start_date + ((due_date - start_date + 1) * done_ratio / 100).floor return done_date <= Date.today end @@ -723,12 +760,16 @@ initial_status = IssueStatus.find_by_id(status_id_was) end initial_status ||= status - + + initial_assigned_to_id = assigned_to_id_changed? ? assigned_to_id_was : assigned_to_id + assignee_transitions_allowed = initial_assigned_to_id.present? && + (user.id == initial_assigned_to_id || user.group_ids.include?(initial_assigned_to_id)) + statuses = initial_status.find_new_statuses_allowed_to( user.admin ? Role.all : user.roles_for_project(project), tracker, author == user, - assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id + assignee_transitions_allowed ) statuses << initial_status unless statuses.empty? statuses << IssueStatus.default if include_default @@ -737,9 +778,12 @@ end end + # Returns the previous assignee if changed def assigned_to_was - if assigned_to_id_changed? && assigned_to_id_was.present? - @assigned_to_was ||= User.find_by_id(assigned_to_id_was) + # assigned_to_id_was is reset before after_save callbacks + user_id = @previous_assigned_to_id || assigned_to_id_was + if user_id && user_id != assigned_to_id + @assigned_to_was ||= User.find_by_id(user_id) end end @@ -769,6 +813,21 @@ notified_users.collect(&:mail) end + def each_notification(users, &block) + if users.any? + if custom_field_values.detect {|value| !value.custom_field.visible?} + users_by_custom_field_visibility = users.group_by do |user| + visible_custom_field_values(user).map(&:custom_field_id).sort + end + users_by_custom_field_visibility.values.each do |users| + yield(users) + end + else + yield(users) + end + end + end + # Returns the number of hours spent on this issue def spent_hours @spent_hours ||= time_entries.sum(:hours) || 0 @@ -785,13 +844,13 @@ end def relations - @relations ||= IssueRelations.new(self, (relations_from + relations_to).sort) + @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort) end # Preloads relations for a collection of issues def self.load_relations(issues) if issues.any? - relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}]) + relations = IssueRelation.where("issue_from_id IN (:ids) OR issue_to_id IN (:ids)", :ids => issues.map(&:id)).all issues.each do |issue| issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id} end @@ -801,7 +860,7 @@ # Preloads visible spent time for a collection of issues def self.load_visible_spent_hours(issues, user=User.current) if issues.any? - hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id) + hours_by_issue_id = TimeEntry.visible(user).group(:issue_id).sum(:hours) issues.each do |issue| issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0) end @@ -822,25 +881,110 @@ relations_from.select {|relation| relation.issue_from_id == issue.id} + relations_to.select {|relation| relation.issue_to_id == issue.id} - issue.instance_variable_set "@relations", IssueRelations.new(issue, relations.sort) + issue.instance_variable_set "@relations", IssueRelation::Relations.new(issue, relations.sort) end end end # Finds an issue relation given its id. def find_relation(relation_id) - IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id]) + IssueRelation.where("issue_to_id = ? OR issue_from_id = ?", id, id).find(relation_id) end + # Returns all the other issues that depend on the issue + # The algorithm is a modified breadth first search (bfs) def all_dependent_issues(except=[]) - except << self + # The found dependencies dependencies = [] - relations_from.each do |relation| - if relation.issue_to && !except.include?(relation.issue_to) - dependencies << relation.issue_to - dependencies += relation.issue_to.all_dependent_issues(except) + + # The visited flag for every node (issue) used by the breadth first search + eNOT_DISCOVERED = 0 # The issue is "new" to the algorithm, it has not seen it before. + + ePROCESS_ALL = 1 # The issue is added to the queue. Process both children and relations of + # the issue when it is processed. + + ePROCESS_RELATIONS_ONLY = 2 # The issue was added to the queue and will be output as dependent issue, + # but its children will not be added to the queue when it is processed. + + eRELATIONS_PROCESSED = 3 # The related issues, the parent issue and the issue itself have been added to + # the queue, but its children have not been added. + + ePROCESS_CHILDREN_ONLY = 4 # The relations and the parent of the issue have been added to the queue, but + # the children still need to be processed. + + eALL_PROCESSED = 5 # The issue and all its children, its parent and its related issues have been + # added as dependent issues. It needs no further processing. + + issue_status = Hash.new(eNOT_DISCOVERED) + + # The queue + queue = [] + + # Initialize the bfs, add start node (self) to the queue + queue << self + issue_status[self] = ePROCESS_ALL + + while (!queue.empty?) do + current_issue = queue.shift + current_issue_status = issue_status[current_issue] + dependencies << current_issue + + # Add parent to queue, if not already in it. + parent = current_issue.parent + parent_status = issue_status[parent] + + if parent && (parent_status == eNOT_DISCOVERED) && !except.include?(parent) + queue << parent + issue_status[parent] = ePROCESS_RELATIONS_ONLY end - end + + # Add children to queue, but only if they are not already in it and + # the children of the current node need to be processed. + if (current_issue_status == ePROCESS_CHILDREN_ONLY || current_issue_status == ePROCESS_ALL) + current_issue.children.each do |child| + next if except.include?(child) + + if (issue_status[child] == eNOT_DISCOVERED) + queue << child + issue_status[child] = ePROCESS_ALL + elsif (issue_status[child] == eRELATIONS_PROCESSED) + queue << child + issue_status[child] = ePROCESS_CHILDREN_ONLY + elsif (issue_status[child] == ePROCESS_RELATIONS_ONLY) + queue << child + issue_status[child] = ePROCESS_ALL + end + end + end + + # Add related issues to the queue, if they are not already in it. + current_issue.relations_from.map(&:issue_to).each do |related_issue| + next if except.include?(related_issue) + + if (issue_status[related_issue] == eNOT_DISCOVERED) + queue << related_issue + issue_status[related_issue] = ePROCESS_ALL + elsif (issue_status[related_issue] == eRELATIONS_PROCESSED) + queue << related_issue + issue_status[related_issue] = ePROCESS_CHILDREN_ONLY + elsif (issue_status[related_issue] == ePROCESS_RELATIONS_ONLY) + queue << related_issue + issue_status[related_issue] = ePROCESS_ALL + end + end + + # Set new status for current issue + if (current_issue_status == ePROCESS_ALL) || (current_issue_status == ePROCESS_CHILDREN_ONLY) + issue_status[current_issue] = eALL_PROCESSED + elsif (current_issue_status == ePROCESS_RELATIONS_ONLY) + issue_status[current_issue] = eRELATIONS_PROCESSED + end + end # while + + # Remove the issues from the "except" parameter from the result array + dependencies -= except + dependencies.delete(self) + dependencies end @@ -873,7 +1017,7 @@ @soonest_start = nil if reload @soonest_start ||= ( relations_to(reload).collect{|relation| relation.successor_soonest_start} + - ancestors.collect(&:soonest_start) + [(@parent_issue || parent).try(:soonest_start)] ).compact.max end @@ -935,42 +1079,21 @@ end # Returns a string of css classes that apply to the issue - def css_classes - s = "issue status-#{status_id} #{priority.try(:css_classes)}" + def css_classes(user=User.current) + s = "issue tracker-#{tracker_id} status-#{status_id} #{priority.try(:css_classes)}" s << ' closed' if closed? s << ' overdue' if overdue? s << ' child' if child? s << ' parent' unless leaf? s << ' private' if is_private? - s << ' created-by-me' if User.current.logged? && author_id == User.current.id - s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id + if user.logged? + s << ' created-by-me' if author_id == user.id + s << ' assigned-to-me' if assigned_to_id == user.id + s << ' assigned-to-my-group' if user.groups.any? {|g| g.id == assigned_to_id} + end s end - # Saves an issue and a time_entry from the parameters - def save_issue_with_child_records(params, existing_time_entry=nil) - Issue.transaction do - if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project) - @time_entry = existing_time_entry || TimeEntry.new - @time_entry.project = project - @time_entry.issue = self - @time_entry.user = User.current - @time_entry.spent_on = User.current.today - @time_entry.attributes = params[:time_entry] - self.time_entries << @time_entry - end - - # TODO: Rename hook - Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) - if save - # TODO: Rename hook - Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) - else - raise ActiveRecord::Rollback - end - end - end - # Unassigns issues from +version+ if it's no longer shared with issue's project def self.update_versions_from_sharing_change(version) # Update issues assigned to the version @@ -989,6 +1112,10 @@ s = arg.to_s.strip.presence if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1])) @parent_issue.id + @invalid_parent_issue_id = nil + elsif s.blank? + @parent_issue = nil + @invalid_parent_issue_id = nil else @parent_issue = nil @invalid_parent_issue_id = arg @@ -1005,8 +1132,8 @@ end end - # Returns true if issue's project is a valid - # parent issue project + # Returns true if issue's project is a valid + # parent issue project def valid_parent_project?(issue=parent) return true if issue.nil? || issue.project_id == project_id @@ -1077,18 +1204,18 @@ end # End ReportsController extraction - # Returns an array of projects that user can assign the issue to + # Returns a scope of projects that user can assign the issue to def allowed_target_projects(user=User.current) if new_record? - Project.all(:conditions => Project.allowed_to_condition(user, :add_issues)) + Project.where(Project.allowed_to_condition(user, :add_issues)) else self.class.allowed_target_projects_on_move(user) end end - # Returns an array of projects that user can move issues to + # Returns a scope of projects that user can move issues to def self.allowed_target_projects_on_move(user=User.current) - Project.all(:conditions => Project.allowed_to_condition(user, :move_issues)) + Project.where(Project.allowed_to_condition(user, :move_issues)) end private @@ -1128,20 +1255,27 @@ end unless @copied_from.leaf? || @copy_options[:subtasks] == false - @copied_from.children.each do |child| + copy_options = (@copy_options || {}).merge(:subtasks => false) + copied_issue_ids = {@copied_from.id => self.id} + @copied_from.reload.descendants.reorder("#{Issue.table_name}.lft").each do |child| + # Do not copy self when copying an issue as a descendant of the copied issue + next if child == self + # Do not copy subtasks of issues that were not copied + next unless copied_issue_ids[child.parent_id] + # Do not copy subtasks that are not visible to avoid potential disclosure of private data unless child.visible? - # Do not copy subtasks that are not visible to avoid potential disclosure of private data logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger next end - copy = Issue.new.copy_from(child, @copy_options) + copy = Issue.new.copy_from(child, copy_options) copy.author = author copy.project = project - copy.parent_issue_id = id - # Children subtasks are copied recursively + copy.parent_issue_id = copied_issue_ids[child.parent_id] unless copy.save logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger + next end + copied_issue_ids[child.id] = copy.id end end @after_create_from_copy_handled = true @@ -1152,48 +1286,50 @@ # issue was just created self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id) set_default_left_and_right - Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id]) + Issue.update_all(["root_id = ?, lft = ?, rgt = ?", root_id, lft, rgt], ["id = ?", id]) if @parent_issue move_to_child_of(@parent_issue) end - reload elsif parent_issue_id != parent_id - former_parent_id = parent_id - # moving an existing issue - if @parent_issue && @parent_issue.root_id == root_id - # inside the same tree - move_to_child_of(@parent_issue) - else - # to another tree - unless root? - move_to_right_of(root) - reload - end - old_root_id = root_id - self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id ) - target_maxright = nested_set_scope.maximum(right_column_name) || 0 - offset = target_maxright + 1 - lft - Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}", - ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt]) - self[left_column_name] = lft + offset - self[right_column_name] = rgt + offset - if @parent_issue - move_to_child_of(@parent_issue) - end - end - reload - # delete invalid relations of all descendants - self_and_descendants.each do |issue| - issue.relations.each do |relation| - relation.destroy unless relation.valid? - end - end - # update former parent - recalculate_attributes_for(former_parent_id) if former_parent_id + update_nested_set_attributes_on_parent_change end remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue) end + # Updates the nested set for when an existing issue is moved + def update_nested_set_attributes_on_parent_change + former_parent_id = parent_id + # moving an existing issue + if @parent_issue && @parent_issue.root_id == root_id + # inside the same tree + move_to_child_of(@parent_issue) + else + # to another tree + unless root? + move_to_right_of(root) + end + old_root_id = root_id + self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id ) + target_maxright = nested_set_scope.maximum(right_column_name) || 0 + offset = target_maxright + 1 - lft + Issue.update_all(["root_id = ?, lft = lft + ?, rgt = rgt + ?", root_id, offset, offset], + ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt]) + self[left_column_name] = lft + offset + self[right_column_name] = rgt + offset + if @parent_issue + move_to_child_of(@parent_issue) + end + end + # delete invalid relations of all descendants + self_and_descendants.each do |issue| + issue.relations.each do |relation| + relation.destroy unless relation.valid? + end + end + # update former parent + recalculate_attributes_for(former_parent_id) if former_parent_id + end + def update_parent_attributes recalculate_attributes_for(parent_id) if parent_id end @@ -1216,11 +1352,12 @@ unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio leaves_count = p.leaves.count if leaves_count > 0 - average = p.leaves.average(:estimated_hours).to_f + average = p.leaves.where("estimated_hours > 0").average(:estimated_hours).to_f if average == 0 average = 1 end - done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f + done = p.leaves.sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " + + "* (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f progress = done / (average * leaves_count) p.done_ratio = progress.round end @@ -1240,12 +1377,11 @@ def self.update_versions(conditions=nil) # Only need to update issues with a fixed_version from # a different project and that is not systemwide shared - Issue.scoped(:conditions => conditions).all( - :conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" + + Issue.includes(:project, :fixed_version). + where("#{Issue.table_name}.fixed_version_id IS NOT NULL" + " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" + - " AND #{Version.table_name}.sharing <> 'system'", - :include => [:project, :fixed_version] - ).each do |issue| + " AND #{Version.table_name}.sharing <> 'system'"). + where(conditions).each do |issue| next if issue.project.nil? || issue.fixed_version.nil? unless issue.project.shared_versions.include?(issue.fixed_version) issue.init_journal(User.current) @@ -1303,10 +1439,23 @@ end end - # Make sure updated_on is updated when adding a note + # Make sure updated_on is updated when adding a note and set updated_on now + # so we can set closed_on with the same value on closing def force_updated_on_change - if @current_journal + if @current_journal || changed? self.updated_on = current_time_from_proper_timezone + if new_record? + self.created_on = updated_on + end + end + end + + # Callback for setting closed_on when the issue is closed. + # The closed_on attribute stores the time of the last closing + # and is preserved when the issue is reopened. + def update_closed_on + if closing? || (new_record? && closed?) + self.closed_on = updated_on end end @@ -1316,7 +1465,7 @@ if @current_journal # attributes changes if @attributes_before_change - (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c| + (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)).each {|c| before = @attributes_before_change[c] after = send(c) next if before == after || (before.blank? && after.blank?) @@ -1365,6 +1514,24 @@ end end + def send_notification + if Setting.notified_events.include?('issue_added') + Mailer.deliver_issue_add(self) + end + end + + # Stores the previous assignee so we can still have access + # to it during after_save callbacks (assigned_to_id_was is reset) + def set_assigned_to_was + @previous_assigned_to_id = assigned_to_id_was + end + + # Clears the previous assignee at the end of after_save callbacks + def clear_assigned_to_was + @assigned_to_was = nil + @previous_assigned_to_id = nil + end + # Query generator for selecting groups of issue counts for a project # based on specific criteria # diff -r d98d22a98252 -r afce8026aaeb app/models/issue_category.rb --- a/app/models/issue_category.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/issue_category.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,7 +24,7 @@ validates_presence_of :name validates_uniqueness_of :name, :scope => [:project_id] validates_length_of :name, :maximum => 30 - + safe_attributes 'name', 'assigned_to_id' scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} @@ -35,7 +35,7 @@ # If a category is specified, issues are reassigned to this category def destroy(reassign_to = nil) if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project - Issue.update_all("category_id = #{reassign_to.id}", "category_id = #{id}") + Issue.update_all({:category_id => reassign_to.id}, {:category_id => id}) end destroy_without_reassign end diff -r d98d22a98252 -r afce8026aaeb app/models/issue_custom_field.rb --- a/app/models/issue_custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/issue_custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,5 +23,22 @@ def type_name :label_issue_plural end + + def visible_by?(project, user=User.current) + super || (roles & user.roles_for_project(project)).present? + end + + def visibility_by_project_condition(*args) + sql = super + additional_sql = "#{Issue.table_name}.tracker_id IN (SELECT tracker_id FROM #{table_name_prefix}custom_fields_trackers#{table_name_suffix} WHERE custom_field_id = #{id})" + unless is_for_all? + additional_sql << " AND #{Issue.table_name}.project_id IN (SELECT project_id FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} WHERE custom_field_id = #{id})" + end + "((#{sql}) AND (#{additional_sql}))" + end + + def validate_custom_field + super + errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) unless visible? || roles.present? + end end - diff -r d98d22a98252 -r afce8026aaeb app/models/issue_observer.rb --- a/app/models/issue_observer.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class IssueObserver < ActiveRecord::Observer - def after_create(issue) - Mailer.issue_add(issue).deliver if Setting.notified_events.include?('issue_added') - end -end diff -r d98d22a98252 -r afce8026aaeb app/models/issue_priority.rb --- a/app/models/issue_priority.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/issue_priority.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/issue_priority_custom_field.rb --- a/app/models/issue_priority_custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/issue_priority_custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/issue_query.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/models/issue_query.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,483 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class IssueQuery < Query + + self.queried_class = Issue + + self.available_columns = [ + QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true), + QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), + QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), + QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue), + QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true), + QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true), + QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), + QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true), + QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), + QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), + QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), + QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), + QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), + QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), + QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), + QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), + QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), + QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'), + QueryColumn.new(:relations, :caption => :label_related_issues), + QueryColumn.new(:description, :inline => false) + ] + + scope :visible, lambda {|*args| + user = args.shift || User.current + base = Project.allowed_to_condition(user, :view_issues, *args) + scope = includes(:project).where("#{table_name}.project_id IS NULL OR (#{base})") + + if user.admin? + scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id) + elsif user.memberships.any? + scope.where("#{table_name}.visibility = ?" + + " OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" + + "SELECT DISTINCT q.id FROM #{table_name} q" + + " INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" + + " INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" + + " INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" + + " WHERE q.project_id IS NULL OR q.project_id = m.project_id))" + + " OR #{table_name}.user_id = ?", + VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id) + elsif user.logged? + scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id) + else + scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC) + end + } + + def initialize(attributes=nil, *args) + super attributes + self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } + end + + # Returns true if the query is visible to +user+ or the current user. + def visible?(user=User.current) + return true if user.admin? + return false unless project.nil? || user.allowed_to?(:view_issues, project) + case visibility + when VISIBILITY_PUBLIC + true + when VISIBILITY_ROLES + if project + (user.roles_for_project(project) & roles).any? + else + Member.where(:user_id => user.id).joins(:roles).where(:member_roles => {:role_id => roles.map(&:id)}).any? + end + else + user == self.user + end + end + + def is_private? + visibility == VISIBILITY_PRIVATE + end + + def is_public? + !is_private? + end + + def draw_relations + r = options[:draw_relations] + r.nil? || r == '1' + end + + def draw_relations=(arg) + options[:draw_relations] = (arg == '0' ? '0' : nil) + end + + def draw_progress_line + r = options[:draw_progress_line] + r == '1' + end + + def draw_progress_line=(arg) + options[:draw_progress_line] = (arg == '1' ? '1' : nil) + end + + def build_from_params(params) + super + self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations]) + self.draw_progress_line = params[:draw_progress_line] || (params[:query] && params[:query][:draw_progress_line]) + self + end + + def initialize_available_filters + principals = [] + subprojects = [] + versions = [] + categories = [] + issue_custom_fields = [] + + if project + principals += project.principals.sort + unless project.leaf? + subprojects = project.descendants.visible.all + principals += Principal.member_of(subprojects) + end + versions = project.shared_versions.all + categories = project.issue_categories.all + issue_custom_fields = project.all_issue_custom_fields + else + if all_projects.any? + principals += Principal.member_of(all_projects) + end + versions = Version.visible.find_all_by_sharing('system') + issue_custom_fields = IssueCustomField.where(:is_for_all => true) + end + principals.uniq! + principals.sort! + users = principals.select {|p| p.is_a?(User)} + + add_available_filter "status_id", + :type => :list_status, :values => IssueStatus.sorted.all.collect{|s| [s.name, s.id.to_s] } + + if project.nil? + project_values = [] + if User.current.logged? && User.current.memberships.any? + project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] + end + project_values += all_projects_values + add_available_filter("project_id", + :type => :list, :values => project_values + ) unless project_values.empty? + end + + add_available_filter "tracker_id", + :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] } + add_available_filter "priority_id", + :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } + + author_values = [] + author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + author_values += users.collect{|s| [s.name, s.id.to_s] } + add_available_filter("author_id", + :type => :list, :values => author_values + ) unless author_values.empty? + + assigned_to_values = [] + assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + assigned_to_values += (Setting.issue_group_assignment? ? + principals : users).collect{|s| [s.name, s.id.to_s] } + add_available_filter("assigned_to_id", + :type => :list_optional, :values => assigned_to_values + ) unless assigned_to_values.empty? + + group_values = Group.all.collect {|g| [g.name, g.id.to_s] } + add_available_filter("member_of_group", + :type => :list_optional, :values => group_values + ) unless group_values.empty? + + role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } + add_available_filter("assigned_to_role", + :type => :list_optional, :values => role_values + ) unless role_values.empty? + + if versions.any? + add_available_filter "fixed_version_id", + :type => :list_optional, + :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } + end + + if categories.any? + add_available_filter "category_id", + :type => :list_optional, + :values => categories.collect{|s| [s.name, s.id.to_s] } + end + + add_available_filter "subject", :type => :text + add_available_filter "created_on", :type => :date_past + add_available_filter "updated_on", :type => :date_past + add_available_filter "closed_on", :type => :date_past + add_available_filter "start_date", :type => :date + add_available_filter "due_date", :type => :date + add_available_filter "estimated_hours", :type => :float + add_available_filter "done_ratio", :type => :integer + + if User.current.allowed_to?(:set_issues_private, nil, :global => true) || + User.current.allowed_to?(:set_own_issues_private, nil, :global => true) + add_available_filter "is_private", + :type => :list, + :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] + end + + if User.current.logged? + add_available_filter "watcher_id", + :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]] + end + + if subprojects.any? + add_available_filter "subproject_id", + :type => :list_subprojects, + :values => subprojects.collect{|s| [s.name, s.id.to_s] } + end + + add_custom_fields_filters(issue_custom_fields) + + add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version + + IssueRelation::TYPES.each do |relation_type, options| + add_available_filter relation_type, :type => :relation, :label => options[:name] + end + + Tracker.disabled_core_fields(trackers).each {|field| + delete_available_filter field + } + end + + def available_columns + return @available_columns if @available_columns + @available_columns = self.class.available_columns.dup + @available_columns += (project ? + project.all_issue_custom_fields : + IssueCustomField + ).visible.collect {|cf| QueryCustomFieldColumn.new(cf) } + + if User.current.allowed_to?(:view_time_entries, project, :global => true) + index = nil + @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours} + index = (index ? index + 1 : -1) + # insert the column after estimated_hours or at the end + @available_columns.insert index, QueryColumn.new(:spent_hours, + :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)", + :default_order => 'desc', + :caption => :label_spent_time + ) + end + + if User.current.allowed_to?(:set_issues_private, nil, :global => true) || + User.current.allowed_to?(:set_own_issues_private, nil, :global => true) + @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private") + end + + disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')} + @available_columns.reject! {|column| + disabled_fields.include?(column.name.to_s) + } + + @available_columns + end + + def default_columns_names + @default_columns_names ||= begin + default_columns = Setting.issue_list_default_columns.map(&:to_sym) + + project.present? ? default_columns : [:project] | default_columns + end + end + + # Returns the issue count + def issue_count + Issue.visible.joins(:status, :project).where(statement).count + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the issue count by group or nil if query is not grouped + def issue_count_by_group + r = nil + if grouped? + begin + # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value + r = Issue.visible. + joins(:status, :project). + where(statement). + joins(joins_for_order_statement(group_by_statement)). + group(group_by_statement). + count + rescue ActiveRecord::RecordNotFound + r = {nil => issue_count} + end + c = group_by_column + if c.is_a?(QueryCustomFieldColumn) + r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} + end + end + r + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the issues + # Valid options are :order, :offset, :limit, :include, :conditions + def issues(options={}) + order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) + + scope = Issue.visible. + joins(:status, :project). + where(statement). + includes(([:status, :project] + (options[:include] || [])).uniq). + where(options[:conditions]). + order(order_option). + joins(joins_for_order_statement(order_option.join(','))). + limit(options[:limit]). + offset(options[:offset]) + + if has_custom_field_column? + scope = scope.preload(:custom_values) + end + + issues = scope.all + + if has_column?(:spent_hours) + Issue.load_visible_spent_hours(issues) + end + if has_column?(:relations) + Issue.load_visible_relations(issues) + end + issues + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the issues ids + def issue_ids(options={}) + order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) + + Issue.visible. + joins(:status, :project). + where(statement). + includes(([:status, :project] + (options[:include] || [])).uniq). + where(options[:conditions]). + order(order_option). + joins(joins_for_order_statement(order_option.join(','))). + limit(options[:limit]). + offset(options[:offset]). + find_ids + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the journals + # Valid options are :order, :offset, :limit + def journals(options={}) + Journal.visible. + joins(:issue => [:project, :status]). + where(statement). + order(options[:order]). + limit(options[:limit]). + offset(options[:offset]). + preload(:details, :user, {:issue => [:project, :author, :tracker, :status]}). + all + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the versions + # Valid options are :conditions + def versions(options={}) + Version.visible. + where(project_statement). + where(options[:conditions]). + includes(:project). + all + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + def sql_for_watcher_id_field(field, operator, value) + db_table = Watcher.table_name + "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + + sql_for_field(field, '=', value, db_table, 'user_id') + ')' + end + + def sql_for_member_of_group_field(field, operator, value) + if operator == '*' # Any group + groups = Group.all + operator = '=' # Override the operator since we want to find by assigned_to + elsif operator == "!*" + groups = Group.all + operator = '!' # Override the operator since we want to find by assigned_to + else + groups = Group.find_all_by_id(value) + end + groups ||= [] + + members_of_groups = groups.inject([]) {|user_ids, group| + user_ids + group.user_ids + [group.id] + }.uniq.compact.sort.collect(&:to_s) + + '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' + end + + def sql_for_assigned_to_role_field(field, operator, value) + case operator + when "*", "!*" # Member / Not member + sw = operator == "!*" ? 'NOT' : '' + nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' + "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + + " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" + when "=", "!" + role_cond = value.any? ? + "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : + "1=0" + + sw = operator == "!" ? 'NOT' : '' + nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' + "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + + " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" + end + end + + def sql_for_is_private_field(field, operator, value) + op = (operator == "=" ? 'IN' : 'NOT IN') + va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') + + "#{Issue.table_name}.is_private #{op} (#{va})" + end + + def sql_for_relations(field, operator, value, options={}) + relation_options = IssueRelation::TYPES[field] + return relation_options unless relation_options + + relation_type = field + join_column, target_join_column = "issue_from_id", "issue_to_id" + if relation_options[:reverse] || options[:reverse] + relation_type = relation_options[:reverse] || relation_type + join_column, target_join_column = target_join_column, join_column + end + + sql = case operator + when "*", "!*" + op = (operator == "*" ? 'IN' : 'NOT IN') + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" + when "=", "!" + op = (operator == "=" ? 'IN' : 'NOT IN') + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" + when "=p", "=!p", "!p" + op = (operator == "!p" ? 'NOT IN' : 'IN') + comp = (operator == "=!p" ? '<>' : '=') + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" + end + + if relation_options[:sym] == field && !options[:reverse] + sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] + sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ") + end + "(#{sql})" + end + + IssueRelation::TYPES.keys.each do |relation_type| + alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations + end +end diff -r d98d22a98252 -r afce8026aaeb app/models/issue_relation.rb --- a/app/models/issue_relation.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/issue_relation.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -15,21 +15,21 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# Class used to represent the relations of an issue -class IssueRelations < Array - include Redmine::I18n +class IssueRelation < ActiveRecord::Base + # Class used to represent the relations of an issue + class Relations < Array + include Redmine::I18n - def initialize(issue, *args) - @issue = issue - super(*args) + def initialize(issue, *args) + @issue = issue + super(*args) + end + + def to_s(*args) + map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ') + end end - def to_s(*args) - map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ') - end -end - -class IssueRelation < ActiveRecord::Base belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id' belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id' @@ -72,6 +72,8 @@ attr_protected :issue_from_id, :issue_to_id before_save :handle_issue_order + after_create :create_journal_after_create + after_destroy :create_journal_after_delete def visible?(user=User.current) (issue_from.nil? || issue_from.visible?(user)) && (issue_to.nil? || issue_to.visible?(user)) @@ -179,4 +181,30 @@ self.relation_type = TYPES[relation_type][:reverse] end end + + def create_journal_after_create + journal = issue_from.init_journal(User.current) + journal.details << JournalDetail.new(:property => 'relation', + :prop_key => label_for(issue_from).to_s, + :value => issue_to.id) + journal.save + journal = issue_to.init_journal(User.current) + journal.details << JournalDetail.new(:property => 'relation', + :prop_key => label_for(issue_to).to_s, + :value => issue_from.id) + journal.save + end + + def create_journal_after_delete + journal = issue_from.init_journal(User.current) + journal.details << JournalDetail.new(:property => 'relation', + :prop_key => label_for(issue_from).to_s, + :old_value => issue_to.id) + journal.save + journal = issue_to.init_journal(User.current) + journal.details << JournalDetail.new(:property => 'relation', + :prop_key => label_for(issue_to).to_s, + :old_value => issue_from.id) + journal.save + end end diff -r d98d22a98252 -r afce8026aaeb app/models/issue_status.rb --- a/app/models/issue_status.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/issue_status.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -28,7 +28,7 @@ validates_length_of :name, :maximum => 30 validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true - scope :sorted, order("#{table_name}.position ASC") + scope :sorted, lambda { order("#{table_name}.position ASC") } scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} def update_default diff -r d98d22a98252 -r afce8026aaeb app/models/journal.rb --- a/app/models/journal.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/journal.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -28,6 +28,7 @@ acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" }, :description => :notes, :author => :user, + :group => :issue, :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' }, :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} @@ -38,6 +39,7 @@ " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} before_create :split_private_notes + after_create :send_notification scope :visible, lambda {|*args| user = args.shift || User.current @@ -52,6 +54,32 @@ (details.empty? && notes.blank?) ? false : super end + # Returns journal details that are visible to user + def visible_details(user=User.current) + details.select do |detail| + if detail.property == 'cf' + detail.custom_field && detail.custom_field.visible_by?(project, user) + elsif detail.property == 'relation' + Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user) + else + true + end + end + end + + def each_notification(users, &block) + if users.any? + users_by_details_visibility = users.group_by do |user| + visible_details(user) + end + users_by_details_visibility.each do |visible_details, users| + if notes? || visible_details.any? + yield(users) + end + end + end + end + # Returns the new status if the journal contains a status change, otherwise nil def new_status c = details.detect {|detail| detail.prop_key == 'status_id'} @@ -92,20 +120,44 @@ @notify = arg end - def recipients + def notified_users notified = journalized.notified_users if private_notes? notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)} end - notified.map(&:mail) + notified end - def watcher_recipients + def recipients + notified_users.map(&:mail) + end + + def notified_watchers notified = journalized.notified_watchers if private_notes? notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)} end - notified.map(&:mail) + notified + end + + def watcher_recipients + notified_watchers.map(&:mail) + end + + # Sets @custom_field instance variable on journals details using a single query + def self.preload_journals_details_custom_fields(journals) + field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq + if field_ids.any? + fields_by_id = CustomField.find_all_by_id(field_ids).inject({}) {|h, f| h[f.id] = f; h} + journals.each do |journal| + journal.details.each do |detail| + if detail.property == 'cf' + detail.instance_variable_set "@custom_field", fields_by_id[detail.prop_key.to_i] + end + end + end + end + journals end private @@ -128,4 +180,14 @@ end true end + + def send_notification + if notify? && (Setting.notified_events.include?('issue_updated') || + (Setting.notified_events.include?('issue_note_added') && notes.present?) || + (Setting.notified_events.include?('issue_status_updated') && new_status.present?) || + (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?) + ) + Mailer.deliver_issue_edit(self) + end + end end diff -r d98d22a98252 -r afce8026aaeb app/models/journal_detail.rb --- a/app/models/journal_detail.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/journal_detail.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,6 +19,12 @@ belongs_to :journal before_save :normalize_values + def custom_field + if property == 'cf' + @custom_field ||= CustomField.find_by_id(prop_key) + end + end + private def normalize_values @@ -27,10 +33,13 @@ end def normalize(v) - if v == true + case v + when true "1" - elsif v == false + when false "0" + when Date + v.strftime("%Y-%m-%d") else v end diff -r d98d22a98252 -r afce8026aaeb app/models/journal_observer.rb --- a/app/models/journal_observer.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class JournalObserver < ActiveRecord::Observer - def after_create(journal) - if journal.notify? && - (Setting.notified_events.include?('issue_updated') || - (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) || - (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) || - (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?) - ) - Mailer.issue_edit(journal).deliver - end - end -end diff -r d98d22a98252 -r afce8026aaeb app/models/mail_handler.rb --- a/app/models/mail_handler.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/mail_handler.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -38,12 +38,27 @@ # Status overridable by default @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) - @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false) + @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1') + @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1') + @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1') email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding) super(email) end + # Extracts MailHandler options from environment variables + # Use when receiving emails with rake tasks + def self.extract_options_from_env(env) + options = {:issue => {}} + %w(project status tracker category priority).each do |option| + options[:issue][option.to_sym] = env[option] if env[option] + end + %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option| + options[option.to_sym] = env[option] if env[option] + end + options + end + def logger Rails.logger end @@ -61,7 +76,7 @@ sender_email = email.from.to_a.first.to_s.strip # Ignore emails received from the application emission address to avoid hell cycles if sender_email.downcase == Setting.mail_from.to_s.strip.downcase - if logger && logger.info + if logger logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" end return false @@ -72,7 +87,7 @@ if value value = value.to_s.downcase if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value - if logger && logger.info + if logger logger.info "MailHandler: ignoring email with #{key}:#{value} header" end return false @@ -81,7 +96,7 @@ end @user = User.find_by_mail(sender_email) if sender_email.present? if @user && !@user.active? - if logger && logger.info + if logger logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" end return false @@ -94,20 +109,23 @@ when 'create' @user = create_user_from_email if @user - if logger && logger.info + if logger logger.info "MailHandler: [#{@user.login}] account created" end - Mailer.account_information(@user, @user.password).deliver + add_user_to_group(@@handler_options[:default_group]) + unless @@handler_options[:no_account_notice] + Mailer.account_information(@user, @user.password).deliver + end else - if logger && logger.error + if logger logger.error "MailHandler: could not create account for [#{sender_email}]" end return false end else # Default behaviour, emails from unknown users are ignored - if logger && logger.info - logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" + if logger + logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" end return false end @@ -118,7 +136,7 @@ private - MESSAGE_ID_RE = %r{^ obj, :file => attachment.decoded, :filename => attachment.filename, @@ -258,13 +277,26 @@ end end + # Returns false if the +attachment+ of the incoming email should be ignored + def accept_attachment?(attachment) + @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?) + @excluded.each do |pattern| + regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i + if attachment.filename.to_s =~ regexp + logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}" + return false + end + end + true + end + # Adds To and Cc as watchers of the given object if the sender has the # appropriate permission def add_watchers(obj) if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project) addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase} unless addresses.empty? - watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses]) + watchers = User.active.where('LOWER(mail) IN (?)', addresses).all watchers.each {|w| obj.add_watcher(w)} end end @@ -315,6 +347,13 @@ # * parse the email To field # * specific project (eg. Setting.mail_handler_target_project) target = Project.find_by_identifier(get_keyword(:project)) + if target.nil? + # Invalid project keyword, use the project specified as the default one + default_project = @@handler_options[:issue][:project] + if default_project.present? + target = Project.find_by_identifier(default_project) + end + end raise MissingInformation.new('Unable to determine target project') if target.nil? target end @@ -338,7 +377,7 @@ }.delete_if {|k, v| v.blank? } if issue.new_record? && attrs['tracker_id'].nil? - attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id) + attrs['tracker_id'] = issue.project.trackers.first.try(:id) end attrs @@ -359,12 +398,26 @@ def plain_text_body return @plain_text_body unless @plain_text_body.nil? - part = email.text_part || email.html_part || email - @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset) + parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present? + text_parts + elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present? + html_parts + else + [email] + end + + parts.reject! do |part| + part.header[:content_disposition].try(:disposition_type) == 'attachment' + end + + @plain_text_body = parts.map {|p| Redmine::CodesetUtil.to_utf8(p.body.decoded, p.charset)}.join("\r\n") # strip html tags and remove doctype directive - @plain_text_body = strip_tags(@plain_text_body.strip) - @plain_text_body.sub! %r{^ Setting.host_name, :protocol => Setting.protocol } end - # Builds a Mail::Message object used to email the specified member - # that he was added to a project - # - # Example: - # member_added_to_project(member, project) => Mail::Message object - # Mailer.member_added_to_project(member, project) => sends an email to the registered member + # Builds a mail for notifying the specified member that they were + # added to a project def member_added_to_project(member, project) principal = Principal.find(member.user_id) @@ -55,34 +51,35 @@ end end - # Builds a Mail::Message object used to email recipients of the added issue. - # - # Example: - # issue_add(issue) => Mail::Message object - # Mailer.issue_add(issue).deliver => sends an email to issue recipients - def issue_add(issue) + # Builds a mail for notifying to_users and cc_users about a new issue + def issue_add(issue, to_users, cc_users) redmine_headers 'Project' => issue.project.identifier, 'Issue-Id' => issue.id, 'Issue-Author' => issue.author.login redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to message_id issue + references issue @author = issue.author @issue = issue + @users = to_users + cc_users @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue) - recipients = issue.recipients - cc = issue.watcher_recipients - recipients - mail :to => recipients, - :cc => cc, + mail :to => to_users.map(&:mail), + :cc => cc_users.map(&:mail), :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" end - # Builds a Mail::Message object used to email recipients of the edited issue. - # - # Example: - # issue_edit(journal) => Mail::Message object - # Mailer.issue_edit(journal).deliver => sends an email to issue recipients - def issue_edit(journal) - issue = journal.journalized.reload + # Notifies users about a new issue + def self.deliver_issue_add(issue) + to = issue.notified_users + cc = issue.notified_watchers - to + issue.each_notification(to + cc) do |users| + Mailer.issue_add(issue, to & users, cc & users).deliver + end + end + + # Builds a mail for notifying to_users and cc_users about an issue update + def issue_edit(journal, to_users, cc_users) + issue = journal.journalized redmine_headers 'Project' => issue.project.identifier, 'Issue-Id' => issue.id, 'Issue-Author' => issue.author.login @@ -90,20 +87,31 @@ message_id journal references issue @author = journal.user - recipients = journal.recipients - # Watchers in cc - cc = journal.watcher_recipients - recipients s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " s << "(#{issue.status.name}) " if journal.new_value_for('status_id') s << issue.subject @issue = issue + @users = to_users + cc_users @journal = journal + @journal_details = journal.visible_details(@users.first) @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}") - mail :to => recipients, - :cc => cc, + mail :to => to_users.map(&:mail), + :cc => cc_users.map(&:mail), :subject => s end + # Notifies users about an issue update + def self.deliver_issue_edit(journal) + issue = journal.journalized.reload + to = journal.notified_users + cc = journal.notified_watchers + journal.each_notification(to + cc) do |users| + issue.each_notification(users) do |users2| + Mailer.issue_edit(journal, to & users2, cc & users2).deliver + end + end + end + def reminder(user, issues, days) set_language_if_valid user.language @issues = issues @@ -170,6 +178,7 @@ redmine_headers 'Project' => news.project.identifier @author = news.author message_id news + references news @news = news @news_url = url_for(:controller => 'news', :action => 'show', :id => news) mail :to => news.recipients, @@ -186,6 +195,7 @@ redmine_headers 'Project' => news.project.identifier @author = comment.author message_id comment + references news @news = news @comment = comment @news_url = url_for(:controller => 'news', :action => 'show', :id => news) @@ -204,7 +214,7 @@ 'Topic-Id' => (message.parent_id || message.id) @author = message.author message_id message - references message.parent unless message.parent.nil? + references message.root recipients = message.recipients cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients) @message = message @@ -280,7 +290,7 @@ # Mailer.account_activation_request(user).deliver => sends an email to all active administrators def account_activation_request(user) # Send the email to all active administrators - recipients = User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact + recipients = User.active.where(:admin => true).all.collect { |u| u.mail }.compact @user = user @url = url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, @@ -325,31 +335,6 @@ :subject => 'Redmine test' end - # Overrides default deliver! method to prevent from sending an email - # with no recipient, cc or bcc - def deliver!(mail = @mail) - set_language_if_valid @initial_language - return false if (recipients.nil? || recipients.empty?) && - (cc.nil? || cc.empty?) && - (bcc.nil? || bcc.empty?) - - - # Log errors when raise_delivery_errors is set to false, Rails does not - raise_errors = self.class.raise_delivery_errors - self.class.raise_delivery_errors = true - begin - return super(mail) - rescue Exception => e - if raise_errors - raise e - elsif mylogger - mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml." - end - ensure - self.class.raise_delivery_errors = raise_errors - end - end - # Sends reminders to issue assignees # Available options: # * :days => how many days in the future to remind about (defaults to 7) @@ -407,7 +392,7 @@ ActionMailer::Base.delivery_method = saved_method end - def mail(headers={}) + def mail(headers={}, &block) headers.merge! 'X-Mailer' => 'Redmine', 'X-Redmine-Host' => Setting.host_name, 'X-Redmine-Site' => Setting.app_title, @@ -417,8 +402,9 @@ 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>" # Removes the author from the recipients and cc - # if he doesn't want to receive notifications about what he does - if @author && @author.logged? && @author.pref[:no_self_notified] + # if the author does not want to receive notifications + # about what the author do + if @author && @author.logged? && @author.pref.no_self_notified headers[:to].delete(@author.mail) if headers[:to].is_a?(Array) headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array) end @@ -438,15 +424,20 @@ headers[:message_id] = "<#{self.class.message_id_for(@message_id_object)}>" end if @references_objects - headers[:references] = @references_objects.collect {|o| "<#{self.class.message_id_for(o)}>"}.join(' ') + headers[:references] = @references_objects.collect {|o| "<#{self.class.references_for(o)}>"}.join(' ') end - super headers do |format| - format.text - format.html unless Setting.plain_text_mail? + m = if block_given? + super headers, &block + else + super headers do |format| + format.text + format.html unless Setting.plain_text_mail? + end end + set_language_if_valid @initial_language - set_language_if_valid @initial_language + m end def initialize(*args) @@ -454,10 +445,20 @@ set_language_if_valid Setting.default_language super end - + def self.deliver_mail(mail) return false if mail.to.blank? && mail.cc.blank? && mail.bcc.blank? - super + begin + # Log errors when raise_delivery_errors is set to false, Rails does not + mail.raise_delivery_errors = true + super + rescue Exception => e + if ActionMailer::Base.raise_delivery_errors + raise e + else + Rails.logger.error "Email delivery error: #{e.message}" + end + end end def self.method_missing(method, *args, &block) @@ -476,15 +477,30 @@ h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s } end - # Returns a predictable Message-Id for the given object - def self.message_id_for(object) - # id + timestamp should reduce the odds of a collision - # as far as we don't send multiple emails for the same object + def self.token_for(object, rand=true) timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on) - hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}" + hash = [ + "redmine", + "#{object.class.name.demodulize.underscore}-#{object.id}", + timestamp.strftime("%Y%m%d%H%M%S") + ] + if rand + hash << Redmine::Utils.random_hex(8) + end host = Setting.mail_from.to_s.gsub(%r{^.*@}, '') host = "#{::Socket.gethostname}.redmine" if host.empty? - "#{hash}@#{host}" + "#{hash.join('.')}@#{host}" + end + + # Returns a Message-Id for the given object + def self.message_id_for(object) + token_for(object, true) + end + + # Returns a uniq token for a given object referenced by all notifications + # related to this object + def self.references_for(object) + token_for(object, false) end def message_id(object) diff -r d98d22a98252 -r afce8026aaeb app/models/member.rb --- a/app/models/member.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/member.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -27,7 +27,6 @@ validate :validate_role before_destroy :set_issue_category_nil - after_destroy :unwatch_from_permission_change def role end @@ -52,7 +51,6 @@ member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)} if member_roles_to_destroy.any? member_roles_to_destroy.each(&:destroy) - unwatch_from_permission_change end end @@ -97,18 +95,19 @@ @membership end + # Finds or initilizes a Member for the given project and principal + def self.find_or_new(project, principal) + project_id = project.is_a?(Project) ? project.id : project + principal_id = principal.is_a?(Principal) ? principal.id : principal + + member = Member.find_by_project_id_and_user_id(project_id, principal_id) + member ||= Member.new(:project_id => project_id, :user_id => principal_id) + member + end + protected def validate_role errors.add_on_empty :role if member_roles.empty? && roles.empty? end - - private - - # Unwatch things that the user is no longer allowed to view inside project - def unwatch_from_permission_change - if user - Watcher.prune(:user => user, :project => project) - end - end end diff -r d98d22a98252 -r afce8026aaeb app/models/member_role.rb --- a/app/models/member_role.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/member_role.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -21,8 +21,8 @@ after_destroy :remove_member_if_empty - after_create :add_role_to_group_users - after_destroy :remove_role_from_group_users + after_create :add_role_to_group_users, :add_role_to_subprojects + after_destroy :remove_inherited_roles validates_presence_of :role validate :validate_role_member @@ -44,21 +44,28 @@ end def add_role_to_group_users - if member.principal.is_a?(Group) + if member.principal.is_a?(Group) && !inherited? member.principal.users.each do |user| - user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id) + user_member = Member.find_or_new(member.project_id, user.id) user_member.member_roles << MemberRole.new(:role => role, :inherited_from => id) user_member.save! end end end - def remove_role_from_group_users - MemberRole.find(:all, :conditions => { :inherited_from => id }).group_by(&:member).each do |member, member_roles| - member_roles.each(&:destroy) - if member && member.user - Watcher.prune(:user => member.user, :project => member.project) + def add_role_to_subprojects + member.project.children.each do |subproject| + if subproject.inherit_members? + child_member = Member.find_or_new(subproject.id, member.user_id) + child_member.member_roles << MemberRole.new(:role => role, :inherited_from => id) + child_member.save! end end end + + def remove_inherited_roles + MemberRole.where(:inherited_from => id).all.group_by(&:member).each do |member, member_roles| + member_roles.each(&:destroy) + end + end end diff -r d98d22a98252 -r afce8026aaeb app/models/message.rb --- a/app/models/message.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/message.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -29,6 +29,7 @@ :date_column => "#{table_name}.created_on" acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, :description => :content, + :group => :parent, :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : {:id => o.parent_id, :r => o.id, :anchor => "message-#{o.id}"})} @@ -44,9 +45,11 @@ after_create :add_author_as_watcher, :reset_counters! after_update :update_messages_board after_destroy :reset_counters! + after_create :send_notification - scope :visible, lambda {|*args| { :include => {:board => :project}, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } } + scope :visible, lambda {|*args| + includes(:board => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + } safe_attributes 'subject', 'content' safe_attributes 'locked', 'sticky', 'board_id', @@ -65,7 +68,7 @@ def update_messages_board if board_id_changed? - Message.update_all("board_id = #{board_id}", ["id = ? OR parent_id = ?", root.id, root.id]) + Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id]) Board.reset_counters!(board_id_was) Board.reset_counters!(board_id) end @@ -103,4 +106,10 @@ def add_author_as_watcher Watcher.create(:watchable => self.root, :user => author) end + + def send_notification + if Setting.notified_events.include?('message_posted') + Mailer.message_posted(self).deliver + end + end end diff -r d98d22a98252 -r afce8026aaeb app/models/message_observer.rb --- a/app/models/message_observer.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class MessageObserver < ActiveRecord::Observer - def after_create(message) - Mailer.message_posted(message).deliver if Setting.notified_events.include?('message_posted') - end -end diff -r d98d22a98252 -r afce8026aaeb app/models/news.rb --- a/app/models/news.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/news.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -33,11 +33,11 @@ acts_as_watchable after_create :add_author_as_watcher + after_create :send_notification - scope :visible, lambda {|*args| { - :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_news, *args) - }} + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args)) + } safe_attributes 'title', 'summary', 'description' @@ -50,6 +50,10 @@ user.allowed_to?(:comment_news, project) end + def recipients + project.users.select {|user| user.notify_about?(self)}.map(&:mail) + end + # returns latest news for projects visible by user def self.latest(user = User.current, count = 5) visible(user).includes([:author, :project]).order("#{News.table_name}.created_on DESC").limit(count).all @@ -61,6 +65,12 @@ Watcher.create(:watchable => self, :user => author) end + def send_notification + if Setting.notified_events.include?('news_added') + Mailer.news_added(self).deliver + end + end + # returns latest news for a specific project def self.latest_for(project, count = 5) find(:all, :limit => count, :conditions => [ "#{News.table_name}.project_id = #{project.id}", Project.allowed_to_condition(User.current, :view_news) ], :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") diff -r d98d22a98252 -r afce8026aaeb app/models/news_observer.rb --- a/app/models/news_observer.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class NewsObserver < ActiveRecord::Observer - def after_create(news) - Mailer.news_added(news).deliver if Setting.notified_events.include?('news_added') - end -end diff -r d98d22a98252 -r afce8026aaeb app/models/principal.rb --- a/app/models/principal.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/principal.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,13 +18,19 @@ class Principal < ActiveRecord::Base self.table_name = "#{table_name_prefix}users#{table_name_suffix}" + # Account statuses + STATUS_ANONYMOUS = 0 + STATUS_ACTIVE = 1 + STATUS_REGISTERED = 2 + STATUS_LOCKED = 3 + has_many :members, :foreign_key => 'user_id', :dependent => :destroy has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}", :order => "#{Project.table_name}.name" has_many :projects, :through => :memberships has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify # Groups and active users - scope :active, :conditions => "#{Principal.table_name}.status = 1" + scope :active, lambda { where(:status => STATUS_ACTIVE) } scope :like, lambda {|q| q = q.to_s @@ -51,7 +57,7 @@ where("1=0") else ids = projects.map(&:id) - where("#{Principal.table_name}.status = 1 AND #{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids) + active.uniq.joins(:members).where("#{Member.table_name}.project_id IN (?)", ids) end } # Principals that are not members of projects @@ -64,6 +70,7 @@ where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids) end } + scope :sorted, lambda { order(*Principal.fields_for_order_statement)} before_create :set_default_empty_values @@ -82,6 +89,15 @@ end end + # Returns an array of fields names than can be used to make an order statement for principals. + # Users are sorted before Groups. + # Examples: + def self.fields_for_order_statement(table=nil) + table ||= table_name + columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id'] + columns.uniq.map {|field| "#{table}.#{field}"} + end + protected # Make sure we don't try to insert NULL values (see #4632) diff -r d98d22a98252 -r afce8026aaeb app/models/project.rb --- a/app/models/project.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/project.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -28,13 +28,11 @@ # Specific overidden Activities has_many :time_entry_activities - has_many :members, :include => [:principal, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}" + has_many :members, :include => [:principal, :roles], :conditions => "#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}" has_many :memberships, :class_name => 'Member' has_many :member_principals, :class_name => 'Member', :include => :principal, - :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})" - has_many :users, :through => :members - has_many :principals, :through => :member_principals, :source => :principal + :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE})" has_many :enabled_modules, :dependent => :delete_all has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" @@ -42,7 +40,7 @@ has_many :issue_changes, :through => :issues, :source => :journals has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" has_many :time_entries, :dependent => :delete_all - has_many :queries, :dependent => :delete_all + has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all has_many :documents, :dependent => :destroy has_many :news, :dependent => :destroy, :include => :author has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" @@ -77,20 +75,23 @@ validates_length_of :homepage, :maximum => 255 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH # donwcase letters, digits, dashes but not digits only - validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-_]*$/, :if => Proc.new { |p| p.identifier_changed? } + validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? } # reserved words validates_exclusion_of :identifier, :in => %w( new ) after_save :update_position_under_parent, :if => Proc.new {|project| project.name_changed?} + after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?} before_destroy :delete_all_members - scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } } - scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} - scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} } - scope :all_public, { :conditions => { :is_public => true } } - scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }} - scope :visible_roots, lambda {|*args| { :conditions => Project.root_visible_by(args.shift || User.current, *args) } } - scope :allowed_to, lambda {|*args| + scope :has_module, lambda {|mod| + where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s) + } + scope :active, lambda { where(:status => STATUS_ACTIVE) } + scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } + scope :all_public, lambda { where(:is_public => true) } + scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) } + scope :visible_roots, lambda {|*args| where(Project.root_visible_by(args.shift || User.current, *args)) } + scope :allowed_to, lambda {|*args| user = User.current permission = nil if args.first.is_a?(Symbol) @@ -99,14 +100,14 @@ user = args.shift permission = args.shift end - { :conditions => Project.allowed_to_condition(user, permission, *args) } + where(Project.allowed_to_condition(user, permission, *args)) } scope :like, lambda {|arg| if arg.blank? - {} + where(nil) else pattern = "%#{arg.to_s.strip.downcase}%" - {:conditions => ["LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", {:p => pattern}]} + where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern) end } @@ -124,7 +125,12 @@ self.enabled_module_names = Setting.default_projects_modules end if !initialized.key?('trackers') && !initialized.key?('tracker_ids') - self.trackers = Tracker.sorted.all + default = Setting.default_projects_tracker_ids + if default.is_a?(Array) + self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.all + else + self.trackers = Tracker.sorted.all + end end end @@ -139,8 +145,8 @@ # returns latest created projects # non public projects will be returned only if user is a member of those def self.latest(user=nil, count=5) - visible(user).find(:all, :limit => count, :order => "created_on DESC") - end + visible(user).limit(count).order("created_on DESC").all + end # Returns true if the project is visible to +user+ or to the current user. def visible?(user=User.current) @@ -185,7 +191,7 @@ else statement_by_role = {} unless options[:member] - role = user.logged? ? Role.non_member : Role.anonymous + role = user.builtin_role if role.allowed_to?(permission) statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}" end @@ -212,6 +218,14 @@ end end + def principals + @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq + end + + def users + @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq + end + # Returns the Systemwide and project specific activities def activities(include_inactive=false) if include_inactive @@ -282,7 +296,10 @@ self.find(*args) end + alias :base_reload :reload def reload(*args) + @principals = nil + @users = nil @shared_versions = nil @rolled_up_versions = nil @rolled_up_trackers = nil @@ -292,7 +309,9 @@ @allowed_parents = nil @allowed_permissions = nil @actions_allowed = nil - super + @start_date = nil + @due_date = nil + base_reload(*args) end def to_param @@ -313,9 +332,12 @@ # Check that there is no issue of a non descendant project that is assigned # to one of the project or descendant versions v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten - if v_ids.any? && Issue.find(:first, :include => :project, - :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" + - " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids]) + if v_ids.any? && + Issue. + includes(:project). + where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt). + where("#{Issue.table_name}.fixed_version_id IN (?)", v_ids). + exists? return false end Project.transaction do @@ -343,7 +365,7 @@ # by the current user def allowed_parents return @allowed_parents if @allowed_parents - @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects)) + @allowed_parents = Project.where(Project.allowed_to_condition(User.current, :add_subprojects)).all @allowed_parents = @allowed_parents - self_and_descendants if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?) @allowed_parents << nil @@ -411,16 +433,19 @@ # Returns an array of the trackers used by the project and its active sub projects def rolled_up_trackers @rolled_up_trackers ||= - Tracker.find(:all, :joins => :projects, - :select => "DISTINCT #{Tracker.table_name}.*", - :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt], - :order => "#{Tracker.table_name}.position") + Tracker. + joins(:projects). + joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'"). + select("DISTINCT #{Tracker.table_name}.*"). + where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt). + sorted. + all end # Closes open and locked project versions that are completed def close_completed_versions Version.transaction do - versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version| + versions.where(:status => %w(open locked)).all.each do |version| if version.completed? version.update_attribute(:status, 'closed') end @@ -431,33 +456,36 @@ # Returns a scope of the Versions on subprojects def rolled_up_versions @rolled_up_versions ||= - Version.scoped(:include => :project, - :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt]) + Version. + includes(:project). + where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED) end # Returns a scope of the Versions used by the project def shared_versions if new_record? - Version.scoped(:include => :project, - :conditions => "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND #{Version.table_name}.sharing = 'system'") + Version. + includes(:project). + where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED) else @shared_versions ||= begin r = root? ? self : root - Version.scoped(:include => :project, - :conditions => "#{Project.table_name}.id = #{id}" + - " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" + - " #{Version.table_name}.sharing = 'system'" + - " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" + - " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + - " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + - "))") + Version. + includes(:project). + where("#{Project.table_name}.id = #{id}" + + " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" + + " #{Version.table_name}.sharing = 'system'" + + " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" + + " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + + " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + + "))") end end end # Returns a hash of project users grouped by role def users_by_role - members.find(:all, :include => [:user, :roles]).inject({}) do |h, m| + members.includes(:user, :roles).all.inject({}) do |h, m| m.roles.each do |r| h[r] ||= [] h[r] << m.user @@ -490,10 +518,14 @@ members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal} end - # Returns an array of all custom fields enabled for project issues + # Returns a scope of all custom fields enabled for project issues # (explictly associated custom fields and custom fields enabled for all projects) def all_issue_custom_fields - @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort + @all_issue_custom_fields ||= IssueCustomField. + sorted. + where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" + + " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" + + " WHERE cfp.project_id = ?)", true, id) end # Returns an array of all custom fields enabled for project time entries @@ -550,20 +582,20 @@ # The earliest start date of a project, based on it's issues and versions def start_date - [ + @start_date ||= [ issues.minimum('start_date'), - shared_versions.collect(&:effective_date), - shared_versions.collect(&:start_date) - ].flatten.compact.min + shared_versions.minimum('effective_date'), + Issue.fixed_version(shared_versions).minimum('start_date') + ].compact.min end # The latest due date of an issue or version def due_date - [ + @due_date ||= [ issues.maximum('due_date'), - shared_versions.collect(&:effective_date), - shared_versions.collect {|v| v.fixed_issues.maximum('due_date')} - ].flatten.compact.max + shared_versions.maximum('effective_date'), + Issue.fixed_version(shared_versions).maximum('due_date') + ].compact.max end def overdue? @@ -579,7 +611,7 @@ total / self_and_descendants.count else if versions.count > 0 - total = versions.collect(&:completed_pourcent).sum + total = versions.collect(&:completed_percent).sum total / versions.count else @@ -662,6 +694,9 @@ safe_attributes 'enabled_module_names', :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) } + safe_attributes 'inherit_members', + :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)} + # Returns an array of projects that are in this project's hierarchy # # Example: parents, children, siblings @@ -673,7 +708,7 @@ # Returns an auto-generated project identifier based on the last identifier used def self.next_identifier - p = Project.find(:first, :order => 'created_on DESC') + p = Project.order('id DESC').first p.nil? ? nil : p.identifier.to_s.succ end @@ -710,27 +745,17 @@ end end - - # Copies +project+ and returns the new instance. This will not save - # the copy + # Returns a new unsaved Project instance with attributes copied from +project+ def self.copy_from(project) - begin - project = project.is_a?(Project) ? project : Project.find(project) - if project - # clear unique attributes - attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt') - copy = Project.new(attributes) - copy.enabled_modules = project.enabled_modules - copy.trackers = project.trackers - copy.custom_values = project.custom_values.collect {|v| v.clone} - copy.issue_custom_fields = project.issue_custom_fields - return copy - else - return nil - end - rescue ActiveRecord::RecordNotFound - return nil - end + project = project.is_a?(Project) ? project : Project.find(project) + # clear unique attributes + attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt') + copy = Project.new(attributes) + copy.enabled_modules = project.enabled_modules + copy.trackers = project.trackers + copy.custom_values = project.custom_values.collect {|v| v.clone} + copy.issue_custom_fields = project.issue_custom_fields + copy end # Yields the given block for each project with its level in the tree @@ -747,6 +772,44 @@ private + def after_parent_changed(parent_was) + remove_inherited_member_roles + add_inherited_member_roles + end + + def update_inherited_members + if parent + if inherit_members? && !inherit_members_was + remove_inherited_member_roles + add_inherited_member_roles + elsif !inherit_members? && inherit_members_was + remove_inherited_member_roles + end + end + end + + def remove_inherited_member_roles + member_roles = memberships.map(&:member_roles).flatten + member_role_ids = member_roles.map(&:id) + member_roles.each do |member_role| + if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from) + member_role.destroy + end + end + end + + def add_inherited_member_roles + if inherit_members? && parent + parent.memberships.each do |parent_member| + member = Member.find_or_new(self.id, parent_member.user_id) + parent_member.member_roles.each do |parent_member_role| + member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id) + end + member.save! + end + end + end + # Copies wiki from +project+ def copy_wiki(project) # Check that the source project has a wiki first @@ -808,10 +871,13 @@ # Get issues sorted by root_id, lft so that parent issues # get copied before their children - project.issues.find(:all, :order => 'root_id, lft').each do |issue| + project.issues.reorder('root_id, lft').all.each do |issue| new_issue = Issue.new new_issue.copy_from(issue, :subtasks => false, :link => false) new_issue.project = self + # Changing project resets the custom field values + # TODO: handle this in Issue#project= + new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} # Reassign fixed_versions by name, since names are unique per project if issue.fixed_version && issue.fixed_version.project == project new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name} @@ -894,7 +960,7 @@ # Copies queries from +project+ def copy_queries(project) project.queries.each do |query| - new_query = ::Query.new + new_query = IssueQuery.new new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria") new_query.sort_criteria = query.sort_criteria if query.sort_criteria new_query.project = self @@ -915,7 +981,7 @@ def allowed_permissions @allowed_permissions ||= begin - module_names = enabled_modules.all(:select => :name).collect {|m| m.name} + module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name) Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name} end end @@ -951,13 +1017,11 @@ def system_activities_and_project_overrides(include_inactive=false) if include_inactive return TimeEntryActivity.shared. - find(:all, - :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) + + where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all + self.time_entry_activities else return TimeEntryActivity.shared.active. - find(:all, - :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) + + where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all + self.time_entry_activities.active end end @@ -976,6 +1040,7 @@ # Inserts/moves the project so that target's children or root projects stay alphabetically sorted def set_or_update_position_under(target_parent) + parent_was = parent sibs = (target_parent.nil? ? self.class.roots : target_parent.children) to_be_inserted_before = sibs.sort_by {|c| c.name.to_s.downcase}.detect {|c| c.name.to_s.downcase > name.to_s.downcase } @@ -992,5 +1057,8 @@ # move_to_child_of adds the project in last (ie.right) position move_to_child_of(target_parent) end + if parent_was != target_parent + after_parent_changed(parent_was) + end end end diff -r d98d22a98252 -r afce8026aaeb app/models/project_custom_field.rb --- a/app/models/project_custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/project_custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/query.rb --- a/app/models/query.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/query.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -28,11 +28,12 @@ end self.default_order = options[:default_order] @inline = options.key?(:inline) ? options[:inline] : true - @caption_key = options[:caption] || "field_#{name}" + @caption_key = options[:caption] || "field_#{name}".to_sym + @frozen = options[:frozen] end def caption - l(@caption_key) + @caption_key.is_a?(Symbol) ? l(@caption_key) : @caption_key end # Returns true if the column is sortable, otherwise false @@ -48,8 +49,12 @@ @inline end - def value(issue) - issue.send name + def frozen? + @frozen + end + + def value(object) + object.send name end def css_classes @@ -75,9 +80,13 @@ @cf end - def value(issue) - cv = issue.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)} - cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first + def value(object) + if custom_field.visible_by?(object.project, User.current) + cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)} + cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first + else + nil + end end def css_classes @@ -85,101 +94,143 @@ end end +class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn + + def initialize(association, custom_field) + super(custom_field) + self.name = "#{association}.cf_#{custom_field.id}".to_sym + # TODO: support sorting/grouping by association custom field + self.sortable = false + self.groupable = false + @association = association + end + + def value(object) + if assoc = object.send(@association) + super(assoc) + end + end + + def css_classes + @css_classes ||= "#{@association}_cf_#{@cf.id} #{@cf.field_format}" + end +end + class Query < ActiveRecord::Base class StatementInvalid < ::ActiveRecord::StatementInvalid end + VISIBILITY_PRIVATE = 0 + VISIBILITY_ROLES = 1 + VISIBILITY_PUBLIC = 2 + belongs_to :project belongs_to :user + has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "query_id" serialize :filters serialize :column_names serialize :sort_criteria, Array + serialize :options, Hash attr_protected :project_id, :user_id validates_presence_of :name validates_length_of :name, :maximum => 255 + validates :visibility, :inclusion => { :in => [VISIBILITY_PUBLIC, VISIBILITY_ROLES, VISIBILITY_PRIVATE] } validate :validate_query_filters + validate do |query| + errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) if query.visibility == VISIBILITY_ROLES && roles.blank? + end - @@operators = { "=" => :label_equals, - "!" => :label_not_equals, - "o" => :label_open_issues, - "c" => :label_closed_issues, - "!*" => :label_none, - "*" => :label_any, - ">=" => :label_greater_or_equal, - "<=" => :label_less_or_equal, - "><" => :label_between, - " :label_in_less_than, - ">t+" => :label_in_more_than, - "> :label_in_the_next_days, - "t+" => :label_in, - "t" => :label_today, - "w" => :label_this_week, - ">t-" => :label_less_than_ago, - " :label_more_than_ago, - "> :label_in_the_past_days, - "t-" => :label_ago, - "~" => :label_contains, - "!~" => :label_not_contains, - "=p" => :label_any_issues_in_project, - "=!p" => :label_any_issues_not_in_project, - "!p" => :label_no_issues_in_project} + after_save do |query| + if query.visibility_changed? && query.visibility != VISIBILITY_ROLES + query.roles.clear + end + end - cattr_reader :operators + class_attribute :operators + self.operators = { + "=" => :label_equals, + "!" => :label_not_equals, + "o" => :label_open_issues, + "c" => :label_closed_issues, + "!*" => :label_none, + "*" => :label_any, + ">=" => :label_greater_or_equal, + "<=" => :label_less_or_equal, + "><" => :label_between, + " :label_in_less_than, + ">t+" => :label_in_more_than, + "> :label_in_the_next_days, + "t+" => :label_in, + "t" => :label_today, + "ld" => :label_yesterday, + "w" => :label_this_week, + "lw" => :label_last_week, + "l2w" => [:label_last_n_weeks, {:count => 2}], + "m" => :label_this_month, + "lm" => :label_last_month, + "y" => :label_this_year, + ">t-" => :label_less_than_ago, + " :label_more_than_ago, + "> :label_in_the_past_days, + "t-" => :label_ago, + "~" => :label_contains, + "!~" => :label_not_contains, + "=p" => :label_any_issues_in_project, + "=!p" => :label_any_issues_not_in_project, + "!p" => :label_no_issues_in_project + } - @@operators_by_filter_type = { :list => [ "=", "!" ], - :list_status => [ "o", "=", "!", "c", "*" ], - :list_optional => [ "=", "!", "!*", "*" ], - :list_subprojects => [ "*", "!*", "=" ], - :date => [ "=", ">=", "<=", "><", "t+", ">t-", " [ "=", ">=", "<=", "><", ">t-", " [ "=", "~", "!", "!~", "!*", "*" ], - :text => [ "~", "!~", "!*", "*" ], - :integer => [ "=", ">=", "<=", "><", "!*", "*" ], - :float => [ "=", ">=", "<=", "><", "!*", "*" ], - :relation => ["=", "=p", "=!p", "!p", "!*", "*"]} + class_attribute :operators_by_filter_type + self.operators_by_filter_type = { + :list => [ "=", "!" ], + :list_status => [ "o", "=", "!", "c", "*" ], + :list_optional => [ "=", "!", "!*", "*" ], + :list_subprojects => [ "*", "!*", "=" ], + :date => [ "=", ">=", "<=", "><", "t+", ">t-", " [ "=", ">=", "<=", "><", ">t-", " [ "=", "~", "!", "!~", "!*", "*" ], + :text => [ "~", "!~", "!*", "*" ], + :integer => [ "=", ">=", "<=", "><", "!*", "*" ], + :float => [ "=", ">=", "<=", "><", "!*", "*" ], + :relation => ["=", "=p", "=!p", "!p", "!*", "*"] + } - cattr_reader :operators_by_filter_type + class_attribute :available_columns + self.available_columns = [] - @@available_columns = [ - QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), - QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), - QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue), - QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true), - QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true), - QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), - QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true), - QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), - QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), - QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), - QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), - QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), - QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), - QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), - QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), - QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), - QueryColumn.new(:relations, :caption => :label_related_issues), - QueryColumn.new(:description, :inline => false) - ] - cattr_reader :available_columns + class_attribute :queried_class - scope :visible, lambda {|*args| - user = args.shift || User.current - base = Project.allowed_to_condition(user, :view_issues, *args) - user_id = user.logged? ? user.id : 0 - { - :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id], - :include => :project - } - } + def queried_table_name + @queried_table_name ||= self.class.queried_class.table_name + end def initialize(attributes=nil, *args) super attributes - self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } @is_for_all = project.nil? end + # Builds the query from the given params + def build_from_params(params) + if params[:fields] || params[:f] + self.filters = {} + add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) + else + available_filters.keys.each do |field| + add_short_filter(field, params[field]) if params[field] + end + end + self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by]) + self.column_names = params[:c] || (params[:query] && params[:query][:column_names]) + self + end + + # Builds a new query from the given params and attributes + def self.build_from_params(params, attributes={}) + new(attributes).build_from_params(params) + end + def validate_query_filters filters.each_key do |field| if values_for(field) @@ -202,7 +253,7 @@ # filter requires one or more values (values_for(field) and !values_for(field).first.blank?) or # filter doesn't require any value - ["o", "c", "!*", "*", "t", "w"].include? operator_for(field) + ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y"].include? operator_for(field) end if filters end @@ -211,168 +262,21 @@ errors.add(:base, m) end - # Returns true if the query is visible to +user+ or the current user. - def visible?(user=User.current) - (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id) - end - def editable_by?(user) return false unless user # Admin can edit them all and regular users can edit their private queries - return true if user.admin? || (!is_public && self.user_id == user.id) + return true if user.admin? || (is_private? && self.user_id == user.id) # Members can not edit public queries that are for all project (only admin is allowed to) - is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) + is_public? && !@is_for_all && user.allowed_to?(:manage_public_queries, project) end def trackers - @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers + @trackers ||= project.nil? ? Tracker.sorted.all : project.rolled_up_trackers end # Returns a hash of localized labels for all filter operators def self.operators_labels - operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h} - end - - def available_filters - return @available_filters if @available_filters - @available_filters = { - "status_id" => { - :type => :list_status, :order => 0, - :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } - }, - "tracker_id" => { - :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } - }, - "priority_id" => { - :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } - }, - "subject" => { :type => :text, :order => 8 }, - "created_on" => { :type => :date_past, :order => 9 }, - "updated_on" => { :type => :date_past, :order => 10 }, - "start_date" => { :type => :date, :order => 11 }, - "due_date" => { :type => :date, :order => 12 }, - "estimated_hours" => { :type => :float, :order => 13 }, - "done_ratio" => { :type => :integer, :order => 14 } - } - IssueRelation::TYPES.each do |relation_type, options| - @available_filters[relation_type] = { - :type => :relation, :order => @available_filters.size + 100, - :label => options[:name] - } - end - principals = [] - if project - principals += project.principals.sort - unless project.leaf? - subprojects = project.descendants.visible.all - if subprojects.any? - @available_filters["subproject_id"] = { - :type => :list_subprojects, :order => 13, - :values => subprojects.collect{|s| [s.name, s.id.to_s] } - } - principals += Principal.member_of(subprojects) - end - end - else - if all_projects.any? - # members of visible projects - principals += Principal.member_of(all_projects) - # project filter - project_values = [] - if User.current.logged? && User.current.memberships.any? - project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] - end - project_values += all_projects_values - @available_filters["project_id"] = { - :type => :list, :order => 1, :values => project_values - } unless project_values.empty? - end - end - principals.uniq! - principals.sort! - users = principals.select {|p| p.is_a?(User)} - - assigned_to_values = [] - assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? - assigned_to_values += (Setting.issue_group_assignment? ? - principals : users).collect{|s| [s.name, s.id.to_s] } - @available_filters["assigned_to_id"] = { - :type => :list_optional, :order => 4, :values => assigned_to_values - } unless assigned_to_values.empty? - - author_values = [] - author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? - author_values += users.collect{|s| [s.name, s.id.to_s] } - @available_filters["author_id"] = { - :type => :list, :order => 5, :values => author_values - } unless author_values.empty? - - group_values = Group.all.collect {|g| [g.name, g.id.to_s] } - @available_filters["member_of_group"] = { - :type => :list_optional, :order => 6, :values => group_values - } unless group_values.empty? - - role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } - @available_filters["assigned_to_role"] = { - :type => :list_optional, :order => 7, :values => role_values - } unless role_values.empty? - - if User.current.logged? - @available_filters["watcher_id"] = { - :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] - } - end - - if project - # project specific filters - categories = project.issue_categories.all - unless categories.empty? - @available_filters["category_id"] = { - :type => :list_optional, :order => 6, - :values => categories.collect{|s| [s.name, s.id.to_s] } - } - end - versions = project.shared_versions.all - unless versions.empty? - @available_filters["fixed_version_id"] = { - :type => :list_optional, :order => 7, - :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } - } - end - add_custom_fields_filters(project.all_issue_custom_fields) - else - # global filters for cross project issue list - system_shared_versions = Version.visible.find_all_by_sharing('system') - unless system_shared_versions.empty? - @available_filters["fixed_version_id"] = { - :type => :list_optional, :order => 7, - :values => system_shared_versions.sort.collect{|s| - ["#{s.project.name} - #{s.name}", s.id.to_s] - } - } - end - add_custom_fields_filters( - IssueCustomField.find(:all, - :conditions => { - :is_filter => true, - :is_for_all => true - })) - end - add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version - if User.current.allowed_to?(:set_issues_private, nil, :global => true) || - User.current.allowed_to?(:set_own_issues_private, nil, :global => true) - @available_filters["is_private"] = { - :type => :list, :order => 16, - :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] - } - end - Tracker.disabled_core_fields(trackers).each {|field| - @available_filters.delete field - } - @available_filters.each do |field, options| - options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, '')) - end - @available_filters + operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h} end # Returns a representation of the available filters for JSON serialization @@ -399,17 +303,43 @@ @all_projects_values = values end - def add_filter(field, operator, values) + # Adds available filters + def initialize_available_filters + # implemented by sub-classes + end + protected :initialize_available_filters + + # Adds an available filter + def add_available_filter(field, options) + @available_filters ||= ActiveSupport::OrderedHash.new + @available_filters[field] = options + @available_filters + end + + # Removes an available filter + def delete_available_filter(field) + if @available_filters + @available_filters.delete(field) + end + end + + # Return a hash of available filters + def available_filters + unless @available_filters + initialize_available_filters + @available_filters.each do |field, options| + options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, '')) + end + end + @available_filters + end + + def add_filter(field, operator, values=nil) # values must be an array return unless values.nil? || values.is_a?(Array) # check if field is defined as an available filter if available_filters.has_key? field filter_options = available_filters[field] - # check if operator is allowed for that filter - #if @@operators_by_filter_type[filter_options[:type]].include? operator - # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]}) - # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator - #end filters[field] = {:operator => operator, :values => (values || [''])} end end @@ -417,9 +347,10 @@ def add_short_filter(field, expression) return unless expression && available_filters.has_key?(field) field_type = available_filters[field][:type] - @@operators_by_filter_type[field_type].sort.reverse.detect do |operator| + operators_by_filter_type[field_type].sort.reverse.detect do |operator| next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/ - add_filter field, operator, $1.present? ? $1.split('|') : [''] + values = $1 + add_filter field, operator, values.present? ? values.split('|') : [''] end || add_filter(field, '=', expression.split('|')) end @@ -457,43 +388,6 @@ label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field) end - def available_columns - return @available_columns if @available_columns - @available_columns = ::Query.available_columns.dup - @available_columns += (project ? - project.all_issue_custom_fields : - IssueCustomField.find(:all) - ).collect {|cf| QueryCustomFieldColumn.new(cf) } - - if User.current.allowed_to?(:view_time_entries, project, :global => true) - index = nil - @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours} - index = (index ? index + 1 : -1) - # insert the column after estimated_hours or at the end - @available_columns.insert index, QueryColumn.new(:spent_hours, - :sortable => "(SELECT COALESCE(SUM(hours), 0) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)", - :default_order => 'desc', - :caption => :label_spent_time - ) - end - - if User.current.allowed_to?(:set_issues_private, nil, :global => true) || - User.current.allowed_to?(:set_own_issues_private, nil, :global => true) - @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private") - end - - disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')} - @available_columns.reject! {|column| - disabled_fields.include?(column.name.to_s) - } - - @available_columns - end - - def self.available_columns=(v) - self.available_columns = (v) - end - def self.add_available_column(column) self.available_columns << (column) if column.is_a?(QueryColumn) end @@ -505,17 +399,18 @@ # Returns a Hash of columns and the key for sorting def sortable_columns - {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column| - h[column.name.to_s] = column.sortable - h - }) + available_columns.inject({}) {|h, column| + h[column.name.to_s] = column.sortable + h + } end def columns # preserve the column_names order - (has_default_columns? ? default_columns_names : column_names).collect do |name| + cols = (has_default_columns? ? default_columns_names : column_names).collect do |name| available_columns.find { |col| col.name == name } end.compact + available_columns.select(&:frozen?) | cols end def inline_columns @@ -535,11 +430,7 @@ end def default_columns_names - @default_columns_names ||= begin - default_columns = Setting.issue_list_default_columns.map(&:to_sym) - - project.present? ? default_columns : [:project] | default_columns - end + [] end def column_names=(names) @@ -558,6 +449,10 @@ column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column) end + def has_custom_field_column? + columns.any? {|column| column.is_a? QueryCustomFieldColumn} + end + def has_default_columns? column_names.nil? || column_names.empty? end @@ -645,7 +540,7 @@ operator = operator_for(field) # "me" value subsitution - if %w(assigned_to_id author_id watcher_id).include?(field) + if %w(assigned_to_id author_id user_id watcher_id).include?(field) if v.delete("me") if User.current.logged? v.push(User.current.id.to_s) @@ -670,192 +565,21 @@ filters_clauses << send("sql_for_#{field}_field", field, operator, v) else # regular field - filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')' + filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')' end end if filters and valid? + if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn) + # Excludes results for which the grouped custom field is not visible + filters_clauses << c.custom_field.visibility_by_project_condition + end + filters_clauses << project_statement filters_clauses.reject!(&:blank?) filters_clauses.any? ? filters_clauses.join(' AND ') : nil end - # Returns the issue count - def issue_count - Issue.visible.count(:include => [:status, :project], :conditions => statement) - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the issue count by group or nil if query is not grouped - def issue_count_by_group - r = nil - if grouped? - begin - # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value - r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) - rescue ActiveRecord::RecordNotFound - r = {nil => issue_count} - end - c = group_by_column - if c.is_a?(QueryCustomFieldColumn) - r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} - end - end - r - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the issues - # Valid options are :order, :offset, :limit, :include, :conditions - def issues(options={}) - order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') - order_option = nil if order_option.blank? - - issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, - :conditions => statement, - :order => order_option, - :joins => joins_for_order_statement(order_option), - :limit => options[:limit], - :offset => options[:offset] - - if has_column?(:spent_hours) - Issue.load_visible_spent_hours(issues) - end - if has_column?(:relations) - Issue.load_visible_relations(issues) - end - issues - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the issues ids - def issue_ids(options={}) - order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') - order_option = nil if order_option.blank? - - Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq, - :conditions => statement, - :order => order_option, - :joins => joins_for_order_statement(order_option), - :limit => options[:limit], - :offset => options[:offset]).find_ids - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the journals - # Valid options are :order, :offset, :limit - def journals(options={}) - Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], - :conditions => statement, - :order => options[:order], - :limit => options[:limit], - :offset => options[:offset] - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the versions - # Valid options are :conditions - def versions(options={}) - Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - def sql_for_watcher_id_field(field, operator, value) - db_table = Watcher.table_name - "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + - sql_for_field(field, '=', value, db_table, 'user_id') + ')' - end - - def sql_for_member_of_group_field(field, operator, value) - if operator == '*' # Any group - groups = Group.all - operator = '=' # Override the operator since we want to find by assigned_to - elsif operator == "!*" - groups = Group.all - operator = '!' # Override the operator since we want to find by assigned_to - else - groups = Group.find_all_by_id(value) - end - groups ||= [] - - members_of_groups = groups.inject([]) {|user_ids, group| - if group && group.user_ids.present? - user_ids << group.user_ids - end - user_ids.flatten.uniq.compact - }.sort.collect(&:to_s) - - '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' - end - - def sql_for_assigned_to_role_field(field, operator, value) - case operator - when "*", "!*" # Member / Not member - sw = operator == "!*" ? 'NOT' : '' - nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' - "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + - " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" - when "=", "!" - role_cond = value.any? ? - "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : - "1=0" - - sw = operator == "!" ? 'NOT' : '' - nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' - "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + - " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" - end - end - - def sql_for_is_private_field(field, operator, value) - op = (operator == "=" ? 'IN' : 'NOT IN') - va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') - - "#{Issue.table_name}.is_private #{op} (#{va})" - end - - def sql_for_relations(field, operator, value, options={}) - relation_options = IssueRelation::TYPES[field] - return relation_options unless relation_options - - relation_type = field - join_column, target_join_column = "issue_from_id", "issue_to_id" - if relation_options[:reverse] || options[:reverse] - relation_type = relation_options[:reverse] || relation_type - join_column, target_join_column = target_join_column, join_column - end - - sql = case operator - when "*", "!*" - op = (operator == "*" ? 'IN' : 'NOT IN') - "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" - when "=", "!" - op = (operator == "=" ? 'IN' : 'NOT IN') - "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" - when "=p", "=!p", "!p" - op = (operator == "!p" ? 'NOT IN' : 'IN') - comp = (operator == "=!p" ? '<>' : '=') - "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" - end - - if relation_options[:sym] == field && !options[:reverse] - sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] - sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ") - else - sql - end - end - - IssueRelation::TYPES.keys.each do |relation_type| - alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations - end - private def sql_for_custom_field(field, operator, value, custom_field_id) @@ -875,15 +599,21 @@ not_in = 'NOT' end customized_key = "id" - customized_class = Issue + customized_class = queried_class if field =~ /^(.+)\.cf_/ assoc = $1 customized_key = "#{assoc}_id" - customized_class = Issue.reflect_on_association(assoc.to_sym).klass.base_class rescue nil - raise "Unknown Issue association #{assoc}" unless customized_class + customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil + raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class end - "#{Issue.table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " + - sql_for_field(field, operator, value, db_table, db_field, true) + ')' + where = sql_for_field(field, operator, value, db_table, db_field, true) + if operator =~ /[<>]/ + where = "(#{where}) AND #{db_table}.#{db_field} <> ''" + end + "#{queried_table_name}.#{customized_key} #{not_in} IN (" + + "SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name}" + + " LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id}" + + " WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))" end # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ @@ -897,13 +627,13 @@ sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil)) when :integer if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) = #{value.first.to_i})" else sql = "#{db_table}.#{db_field} = #{value.first.to_i}" end when :float if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})" else sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}" end @@ -932,7 +662,7 @@ sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil) else if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})" else sql = "#{db_table}.#{db_field} >= #{value.first.to_f}" end @@ -942,7 +672,7 @@ sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil)) else if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})" else sql = "#{db_table}.#{db_field} <= #{value.first.to_f}" end @@ -952,15 +682,15 @@ sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil)) else if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})" else sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}" end end when "o" - sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id" + sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id" when "c" - sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" + sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" when ">= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6) + when "lw" + # = last week + first_day_of_week = l(:general_first_day_of_week).to_i + day_of_week = Date.today.cwday + days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) + sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1) + when "l2w" + # = last 2 weeks + first_day_of_week = l(:general_first_day_of_week).to_i + day_of_week = Date.today.cwday + days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) + sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1) + when "m" + # = this month + date = Date.today + sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month) + when "lm" + # = last month + date = Date.today.prev_month + sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month) + when "y" + # = this year + date = Date.today + sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year) when "~" sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" when "!~" @@ -1005,55 +762,61 @@ return sql end - def add_custom_fields_filters(custom_fields, assoc=nil) - return unless custom_fields.present? - @available_filters ||= {} + # Adds a filter for the given custom field + def add_custom_field_filter(field, assoc=nil) + case field.field_format + when "text" + options = { :type => :text } + when "list" + options = { :type => :list_optional, :values => field.possible_values } + when "date" + options = { :type => :date } + when "bool" + options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] } + when "int" + options = { :type => :integer } + when "float" + options = { :type => :float } + when "user", "version" + return unless project + values = field.possible_values_options(project) + if User.current.logged? && field.field_format == 'user' + values.unshift ["<< #{l(:label_me)} >>", "me"] + end + options = { :type => :list_optional, :values => values } + else + options = { :type => :string } + end + filter_id = "cf_#{field.id}" + filter_name = field.name + if assoc.present? + filter_id = "#{assoc}.#{filter_id}" + filter_name = l("label_attribute_of_#{assoc}", :name => filter_name) + end + add_available_filter filter_id, options.merge({ + :name => filter_name, + :format => field.field_format, + :field => field + }) + end - custom_fields.select(&:is_filter?).each do |field| - case field.field_format - when "text" - options = { :type => :text, :order => 20 } - when "list" - options = { :type => :list_optional, :values => field.possible_values, :order => 20} - when "date" - options = { :type => :date, :order => 20 } - when "bool" - options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } - when "int" - options = { :type => :integer, :order => 20 } - when "float" - options = { :type => :float, :order => 20 } - when "user", "version" - next unless project - values = field.possible_values_options(project) - if User.current.logged? && field.field_format == 'user' - values.unshift ["<< #{l(:label_me)} >>", "me"] - end - options = { :type => :list_optional, :values => values, :order => 20} - else - options = { :type => :string, :order => 20 } - end - filter_id = "cf_#{field.id}" - filter_name = field.name - if assoc.present? - filter_id = "#{assoc}.#{filter_id}" - filter_name = l("label_attribute_of_#{assoc}", :name => filter_name) - end - @available_filters[filter_id] = options.merge({ - :name => filter_name, - :format => field.field_format, - :field => field - }) + # Adds filters for the given custom fields scope + def add_custom_fields_filters(scope, assoc=nil) + scope.visible.where(:is_filter => true).sorted.each do |field| + add_custom_field_filter(field, assoc) end end + # Adds filters for the given associations custom fields def add_associations_custom_fields_filters(*associations) - fields_by_class = CustomField.where(:is_filter => true).group_by(&:class) + fields_by_class = CustomField.visible.where(:is_filter => true).group_by(&:class) associations.each do |assoc| - association_klass = Issue.reflect_on_association(assoc).klass + association_klass = queried_class.reflect_on_association(assoc).klass fields_by_class.each do |field_class, fields| if field_class.customized_class <= association_klass - add_custom_fields_filters(fields, assoc) + fields.sort.each do |field| + add_custom_field_filter(field, assoc) + end end end end @@ -1091,7 +854,7 @@ if order_options if order_options.include?('authors') - joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id" + joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id" end order_options.scan(/cf_\d+/).uniq.each do |name| column = available_columns.detect {|c| c.name.to_s == name} diff -r d98d22a98252 -r afce8026aaeb app/models/repository.rb --- a/app/models/repository.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/repository.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -42,7 +42,7 @@ validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph) # donwcase letters, digits, dashes, underscores but not digits only - validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-_]*$/, :allow_blank => true + validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true # Checks if the SCM is enabled when creating a repository validate :repo_create_validation, :on => :create @@ -155,6 +155,12 @@ end end + # TODO: should return an empty hash instead of nil to avoid many ||{} + def extra_info + h = read_attribute(:extra_info) + h.is_a?(Hash) ? h : nil + end + def merge_extra_info(arg) h = extra_info || {} return h if arg.nil? @@ -236,31 +242,33 @@ def find_changeset_by_name(name) return nil if name.blank? s = name.to_s - changesets.find(:first, :conditions => (s.match(/^\d*$/) ? - ["revision = ?", s] : ["revision LIKE ?", s + '%'])) + if s.match(/^\d*$/) + changesets.where("revision = ?", s).first + else + changesets.where("revision LIKE ?", s + '%').first + end end def latest_changeset - @latest_changeset ||= changesets.find(:first) + @latest_changeset ||= changesets.first end # Returns the latest changesets for +path+ # Default behaviour is to search in cached changesets def latest_changesets(path, rev, limit=10) if path.blank? - changesets.find( - :all, - :include => :user, - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", - :limit => limit) + changesets. + reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"). + limit(limit). + preload(:user). + all else - filechanges.find( - :all, - :include => {:changeset => :user}, - :conditions => ["path = ?", path.with_leading_slash], - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", - :limit => limit - ).collect(&:changeset) + filechanges. + where("path = ?", path.with_leading_slash). + reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"). + limit(limit). + preload(:changeset => :user). + collect(&:changeset) end end @@ -303,7 +311,7 @@ return @found_committer_users[committer] if @found_committer_users.has_key?(committer) user = nil - c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user) + c = changesets.where(:committer => committer).includes(:user).first if c && c.user user = c.user elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/ @@ -339,7 +347,7 @@ # scan changeset comments to find related and fixed issues for all repositories def self.scan_changesets_for_issue_ids - find(:all).each(&:scan_changesets_for_issue_ids) + all.each(&:scan_changesets_for_issue_ids) end def self.scm_name @@ -396,7 +404,7 @@ end def set_as_default? - new_record? && project && !Repository.first(:conditions => {:project_id => project.id}) + new_record? && project && Repository.where(:project_id => project.id).empty? end protected diff -r d98d22a98252 -r afce8026aaeb app/models/repository/bazaar.rb --- a/app/models/repository/bazaar.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/repository/bazaar.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -68,15 +68,11 @@ full_path = File.join(root_url, e.path) e.size = File.stat(full_path).size if File.file?(full_path) end - c = Change.find( - :first, - :include => :changeset, - :conditions => [ - "#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", - e.lastrev.revision, - id - ], - :order => "#{Changeset.table_name}.revision DESC") + c = Change. + includes(:changeset). + where("#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id). + order("#{Changeset.table_name}.revision DESC"). + first if c e.lastrev.identifier = c.changeset.revision e.lastrev.name = c.changeset.revision diff -r d98d22a98252 -r afce8026aaeb app/models/repository/cvs.rb --- a/app/models/repository/cvs.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/repository/cvs.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -143,14 +143,11 @@ ) cmt = Changeset.normalize_comments(revision.message, repo_log_encoding) author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding) - cs = changesets.find( - :first, - :conditions => { - :committed_on => tmp_time - time_delta .. tmp_time + time_delta, - :committer => author_utf8, - :comments => cmt - } - ) + cs = changesets.where( + :committed_on => tmp_time - time_delta .. tmp_time + time_delta, + :committer => author_utf8, + :comments => cmt + ).first # create a new changeset.... unless cs # we use a temporaray revision number here (just for inserting) @@ -185,10 +182,10 @@ end # Renumber new changesets in chronological order - Changeset.all( - :order => 'committed_on ASC, id ASC', - :conditions => ["repository_id = ? AND revision LIKE 'tmp%'", id] - ).each do |changeset| + Changeset. + order('committed_on ASC, id ASC'). + where("repository_id = ? AND revision LIKE 'tmp%'", id). + each do |changeset| changeset.update_attribute :revision, next_revision_number end end # transaction diff -r d98d22a98252 -r afce8026aaeb app/models/repository/darcs.rb --- a/app/models/repository/darcs.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/repository/darcs.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/repository/filesystem.rb --- a/app/models/repository/filesystem.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/repository/filesystem.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # FileSystem adapter # File written by Paul Rivier, at Demotera. diff -r d98d22a98252 -r afce8026aaeb app/models/repository/git.rb --- a/app/models/repository/git.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/repository/git.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com # # This program is free software; you can redistribute it and/or @@ -191,13 +191,8 @@ offset = 0 revisions_copy = revisions.clone # revisions will change while offset < revisions_copy.size - recent_changesets_slice = changesets.find( - :all, - :conditions => [ - 'scmid IN (?)', - revisions_copy.slice(offset, limit).map{|x| x.scmid} - ] - ) + scmids = revisions_copy.slice(offset, limit).map{|x| x.scmid} + recent_changesets_slice = changesets.where(:scmid => scmids).all # Subtract revisions that redmine already knows about recent_revisions = recent_changesets_slice.map{|c| c.scmid} revisions.reject!{|r| recent_revisions.include?(r.scmid)} @@ -246,14 +241,7 @@ revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) return [] if revisions.nil? || revisions.empty? - changesets.find( - :all, - :conditions => [ - "scmid IN (?)", - revisions.map!{|c| c.scmid} - ], - :order => 'committed_on DESC' - ) + changesets.where(:scmid => revisions.map {|c| c.scmid}).all end def clear_extra_info_of_changesets diff -r d98d22a98252 -r afce8026aaeb app/models/repository/mercurial.rb --- a/app/models/repository/mercurial.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/repository/mercurial.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -92,11 +92,12 @@ # Sqlite3 and PostgreSQL pass. # Is this MySQL bug? def latest_changesets(path, rev, limit=10) - changesets.find(:all, - :include => :user, - :conditions => latest_changesets_cond(path, rev, limit), - :limit => limit, - :order => "#{Changeset.table_name}.id DESC") + changesets. + includes(:user). + where(latest_changesets_cond(path, rev, limit)). + limit(limit). + order("#{Changeset.table_name}.id DESC"). + all end def latest_changesets_cond(path, rev, limit) diff -r d98d22a98252 -r afce8026aaeb app/models/repository/subversion.rb --- a/app/models/repository/subversion.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/repository/subversion.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,7 +20,7 @@ class Repository::Subversion < Repository attr_protected :root_url validates_presence_of :url - validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i + validates_format_of :url, :with => %r{\A(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+}i def self.scm_adapter_class Redmine::Scm::Adapters::SubversionAdapter diff -r d98d22a98252 -r afce8026aaeb app/models/role.rb --- a/app/models/role.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/role.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -39,8 +39,8 @@ ['own', :label_issues_visibility_own] ] - scope :sorted, order("#{table_name}.builtin ASC, #{table_name}.position ASC") - scope :givable, order("#{table_name}.position ASC").where(:builtin => 0) + scope :sorted, lambda { order("#{table_name}.builtin ASC, #{table_name}.position ASC") } + scope :givable, lambda { order("#{table_name}.position ASC").where(:builtin => 0) } scope :builtin, lambda { |*args| compare = (args.first == true ? 'not' : '') where("#{compare} builtin = 0") @@ -52,6 +52,7 @@ WorkflowRule.copy(nil, source_role, nil, proxy_association.owner) end end + has_and_belongs_to_many :custom_fields, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "role_id" has_many :member_roles, :dependent => :destroy has_many :members, :through => :member_roles @@ -137,7 +138,7 @@ def anonymous? builtin == 2 end - + # Return true if the role is a project member role def member? !self.builtin? diff -r d98d22a98252 -r afce8026aaeb app/models/setting.rb --- a/app/models/setting.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/setting.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,15 +18,15 @@ class Setting < ActiveRecord::Base DATE_FORMATS = [ - '%Y-%m-%d', - '%d/%m/%Y', - '%d.%m.%Y', - '%d-%m-%Y', - '%m/%d/%Y', - '%d %b %Y', - '%d %B %Y', - '%b %d, %Y', - '%B %d, %Y' + '%Y-%m-%d', + '%d/%m/%Y', + '%d.%m.%Y', + '%d-%m-%Y', + '%m/%d/%Y', + '%d %b %Y', + '%d %B %Y', + '%b %d, %Y', + '%B %d, %Y' ] TIME_FORMATS = [ @@ -132,15 +132,87 @@ def self.#{name}=(value) self[:#{name}] = value end - END_SRC +END_SRC class_eval src, __FILE__, __LINE__ end + # Sets a setting value from params + def self.set_from_params(name, params) + params = params.dup + params.delete_if {|v| v.blank? } if params.is_a?(Array) + + m = "#{name}_from_params" + if respond_to? m + self[name.to_sym] = send m, params + else + self[name.to_sym] = params + end + end + + # Returns a hash suitable for commit_update_keywords setting + # + # Example: + # params = {:keywords => ['fixes', 'closes'], :status_id => ["3", "5"], :done_ratio => ["", "100"]} + # Setting.commit_update_keywords_from_params(params) + # # => [{'keywords => 'fixes', 'status_id' => "3"}, {'keywords => 'closes', 'status_id' => "5", 'done_ratio' => "100"}] + def self.commit_update_keywords_from_params(params) + s = [] + if params.is_a?(Hash) && params.key?(:keywords) && params.values.all? {|v| v.is_a? Array} + attributes = params.except(:keywords).keys + params[:keywords].each_with_index do |keywords, i| + next if keywords.blank? + s << attributes.inject({}) {|h, a| + value = params[a][i].to_s + h[a.to_s] = value if value.present? + h + }.merge('keywords' => keywords) + end + end + s + end + # Helper that returns an array based on per_page_options setting def self.per_page_options_array per_page_options.split(%r{[\s,]}).collect(&:to_i).select {|n| n > 0}.sort end + # Helper that returns a Hash with single update keywords as keys + def self.commit_update_keywords_array + a = [] + if commit_update_keywords.is_a?(Array) + commit_update_keywords.each do |rule| + next unless rule.is_a?(Hash) + rule = rule.dup + rule.delete_if {|k, v| v.blank?} + keywords = rule['keywords'].to_s.downcase.split(",").map(&:strip).reject(&:blank?) + next if keywords.empty? + a << rule.merge('keywords' => keywords) + end + end + a + end + + def self.commit_fix_keywords + ActiveSupport::Deprecation.warn "Setting.commit_fix_keywords is deprecated and will be removed in Redmine 3" + if commit_update_keywords.is_a?(Array) + commit_update_keywords.first && commit_update_keywords.first['keywords'] + end + end + + def self.commit_fix_status_id + ActiveSupport::Deprecation.warn "Setting.commit_fix_status_id is deprecated and will be removed in Redmine 3" + if commit_update_keywords.is_a?(Array) + commit_update_keywords.first && commit_update_keywords.first['status_id'] + end + end + + def self.commit_fix_done_ratio + ActiveSupport::Deprecation.warn "Setting.commit_fix_done_ratio is deprecated and will be removed in Redmine 3" + if commit_update_keywords.is_a?(Array) + commit_update_keywords.first && commit_update_keywords.first['done_ratio'] + end + end + def self.openid? Object.const_defined?(:OpenID) && self[:openid].to_i > 0 end @@ -154,7 +226,7 @@ clear_cache end end - + # Clears the settings cache def self.clear_cache @cached_settings.clear diff -r d98d22a98252 -r afce8026aaeb app/models/time_entry.rb --- a/app/models/time_entry.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/time_entry.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,6 +30,7 @@ acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"}, :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}}, :author => :user, + :group => :issue, :description => :comments acts_as_activity_provider :timestamp => "#{table_name}.created_on", @@ -39,30 +40,28 @@ validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on validates_numericality_of :hours, :allow_nil => true, :message => :invalid validates_length_of :comments, :maximum => 255, :allow_nil => true + validates :spent_on, :date => true before_validation :set_project_if_nil validate :validate_time_entry - scope :visible, lambda {|*args| { - :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args) - }} - scope :on_issue, lambda {|issue| { - :include => :issue, - :conditions => "#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}" - }} - scope :on_project, lambda {|project, include_subprojects| { - :include => :project, - :conditions => project.project_condition(include_subprojects) - }} + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)) + } + scope :on_issue, lambda {|issue| + includes(:issue).where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}") + } + scope :on_project, lambda {|project, include_subprojects| + includes(:project).where(project.project_condition(include_subprojects)) + } scope :spent_between, lambda {|from, to| if from && to - {:conditions => ["#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to]} + where("#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to) elsif from - {:conditions => ["#{TimeEntry.table_name}.spent_on >= ?", from]} + where("#{TimeEntry.table_name}.spent_on >= ?", from) elsif to - {:conditions => ["#{TimeEntry.table_name}.spent_on <= ?", to]} + where("#{TimeEntry.table_name}.spent_on <= ?", to) else - {} + where(nil) end } diff -r d98d22a98252 -r afce8026aaeb app/models/time_entry_activity.rb --- a/app/models/time_entry_activity.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/time_entry_activity.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,11 +24,15 @@ OptionName end + def objects + TimeEntry.where(:activity_id => self_and_descendants(1).map(&:id)) + end + def objects_count - time_entries.count + objects.count end def transfer_relations(to) - time_entries.update_all("activity_id = #{to.id}") + objects.update_all(:activity_id => to.id) end end diff -r d98d22a98252 -r afce8026aaeb app/models/time_entry_activity_custom_field.rb --- a/app/models/time_entry_activity_custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/time_entry_activity_custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/time_entry_custom_field.rb --- a/app/models/time_entry_custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/time_entry_custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/time_entry_query.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/models/time_entry_query.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,137 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +class TimeEntryQuery < Query + + self.queried_class = TimeEntry + + self.available_columns = [ + QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), + QueryColumn.new(:spent_on, :sortable => ["#{TimeEntry.table_name}.spent_on", "#{TimeEntry.table_name}.created_on"], :default_order => 'desc', :groupable => true), + QueryColumn.new(:user, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), + QueryColumn.new(:activity, :sortable => "#{TimeEntryActivity.table_name}.position", :groupable => true), + QueryColumn.new(:issue, :sortable => "#{Issue.table_name}.id"), + QueryColumn.new(:comments), + QueryColumn.new(:hours, :sortable => "#{TimeEntry.table_name}.hours"), + ] + + def initialize(attributes=nil, *args) + super attributes + self.filters ||= {} + add_filter('spent_on', '*') unless filters.present? + end + + def initialize_available_filters + add_available_filter "spent_on", :type => :date_past + + principals = [] + if project + principals += project.principals.sort + unless project.leaf? + subprojects = project.descendants.visible.all + if subprojects.any? + add_available_filter "subproject_id", + :type => :list_subprojects, + :values => subprojects.collect{|s| [s.name, s.id.to_s] } + principals += Principal.member_of(subprojects) + end + end + else + if all_projects.any? + # members of visible projects + principals += Principal.member_of(all_projects) + # project filter + project_values = [] + if User.current.logged? && User.current.memberships.any? + project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] + end + project_values += all_projects_values + add_available_filter("project_id", + :type => :list, :values => project_values + ) unless project_values.empty? + end + end + principals.uniq! + principals.sort! + users = principals.select {|p| p.is_a?(User)} + + users_values = [] + users_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + users_values += users.collect{|s| [s.name, s.id.to_s] } + add_available_filter("user_id", + :type => :list_optional, :values => users_values + ) unless users_values.empty? + + activities = (project ? project.activities : TimeEntryActivity.shared.active) + add_available_filter("activity_id", + :type => :list, :values => activities.map {|a| [a.name, a.id.to_s]} + ) unless activities.empty? + + add_available_filter "comments", :type => :text + add_available_filter "hours", :type => :float + + add_custom_fields_filters(TimeEntryCustomField) + add_associations_custom_fields_filters :project, :issue, :user + end + + def available_columns + return @available_columns if @available_columns + @available_columns = self.class.available_columns.dup + @available_columns += TimeEntryCustomField.visible.all.map {|cf| QueryCustomFieldColumn.new(cf) } + @available_columns += IssueCustomField.visible.all.map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf) } + @available_columns + end + + def default_columns_names + @default_columns_names ||= [:project, :spent_on, :user, :activity, :issue, :comments, :hours] + end + + def results_scope(options={}) + order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) + + TimeEntry.visible. + where(statement). + order(order_option). + joins(joins_for_order_statement(order_option.join(','))). + includes(:activity) + end + + def sql_for_activity_id_field(field, operator, value) + condition_on_id = sql_for_field(field, operator, value, Enumeration.table_name, 'id') + condition_on_parent_id = sql_for_field(field, operator, value, Enumeration.table_name, 'parent_id') + ids = value.map(&:to_i).join(',') + table_name = Enumeration.table_name + if operator == '=' + "(#{table_name}.id IN (#{ids}) OR #{table_name}.parent_id IN (#{ids}))" + else + "(#{table_name}.id NOT IN (#{ids}) AND (#{table_name}.parent_id IS NULL OR #{table_name}.parent_id NOT IN (#{ids})))" + end + end + + # Accepts :from/:to params as shortcut filters + def build_from_params(params) + super + if params[:from].present? && params[:to].present? + add_filter('spent_on', '><', [params[:from], params[:to]]) + elsif params[:from].present? + add_filter('spent_on', '>=', [params[:from]]) + elsif params[:to].present? + add_filter('spent_on', '<=', [params[:to]]) + end + self + end +end diff -r d98d22a98252 -r afce8026aaeb app/models/token.rb --- a/app/models/token.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/token.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -37,11 +37,43 @@ Token.delete_all ["action NOT IN (?) AND created_on < ?", ['feeds', 'api'], Time.now - @@validity_time] end -private + # Returns the active user who owns the key for the given action + def self.find_active_user(action, key, validity_days=nil) + user = find_user(action, key, validity_days) + if user && user.active? + user + end + end + + # Returns the user who owns the key for the given action + def self.find_user(action, key, validity_days=nil) + token = find_token(action, key, validity_days) + if token + token.user + end + end + + # Returns the token for action and key with an optional + # validity duration (in number of days) + def self.find_token(action, key, validity_days=nil) + action = action.to_s + key = key.to_s + return nil unless action.present? && key =~ /\A[a-z0-9]+\z/i + + token = Token.where(:action => action, :value => key).first + if token && (token.action == action) && (token.value == key) && token.user + if validity_days.nil? || (token.created_on > validity_days.days.ago) + token + end + end + end + def self.generate_token_value Redmine::Utils.random_hex(20) end + private + # Removes obsolete tokens (same user and action) def delete_previous_tokens if user diff -r d98d22a98252 -r afce8026aaeb app/models/tracker.rb --- a/app/models/tracker.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/tracker.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -35,13 +35,13 @@ has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id' acts_as_list - attr_protected :field_bits + attr_protected :fields_bits validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 30 - scope :sorted, order("#{table_name}.position ASC") + scope :sorted, lambda { order("#{table_name}.position ASC") } scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} def to_s; name end diff -r d98d22a98252 -r afce8026aaeb app/models/user.rb --- a/app/models/user.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/user.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,12 +20,6 @@ class User < Principal include Redmine::SafeAttributes - # Account statuses - STATUS_ANONYMOUS = 0 - STATUS_ACTIVE = 1 - STATUS_REGISTERED = 2 - STATUS_LOCKED = 3 - # Different ways of displaying/sorting users USER_FORMATS = { :firstname_lastname => { @@ -82,8 +76,8 @@ has_one :api_token, :class_name => 'Token', :conditions => "action='api'" belongs_to :auth_source - scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}" - scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} } + scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") } + scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } has_one :ssamr_user_detail, :dependent => :destroy, :class_name => 'SsamrUserDetail' accepts_nested_attributes_for :ssamr_user_detail @@ -92,7 +86,7 @@ acts_as_customizable - attr_accessor :password, :password_confirmation + attr_accessor :password, :password_confirmation, :generate_password attr_accessor :last_before_login_on # Prevents unauthorized assignments attr_protected :login, :admin, :password, :password_confirmation, :hashed_password @@ -103,20 +97,21 @@ validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false - - # Login must contain lettres, numbers, underscores only - validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i + + # Login must contain letters, numbers, underscores only + validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT validates_length_of :firstname, :lastname, :maximum => 30 - validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true + validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true validates_confirmation_of :password, :allow_nil => true validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true validate :validate_password_length before_create :set_mail_notification - before_save :update_hashed_password + before_save :generate_password_if_needed, :update_hashed_password before_destroy :remove_references_before_destroy + after_save :update_notified_project_ids validates_acceptance_of :terms_and_conditions, :on => :create, :message => :must_accept_terms_and_conditions @@ -128,6 +123,7 @@ group_id = group.is_a?(Group) ? group.id : group.to_i where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) } + scope :sorted, lambda { order(*User.fields_for_order_statement)} def set_mail_notification self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? @@ -141,10 +137,15 @@ end end + alias :base_reload :reload def reload(*args) @name = nil @projects_by_role = nil - super + @membership_by_project_id = nil + @notified_projects_ids = nil + @notified_projects_ids_changed = false + @builtin_role = nil + base_reload(*args) end def mail=(arg) @@ -162,30 +163,24 @@ begin write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url)) rescue OpenIdAuthentication::InvalidOpenId - # Invlaid url, don't save + # Invalid url, don't save end end self.read_attribute(:identity_url) end # Returns the user that matches provided login and password, or nil - def self.try_to_login(login, password) + def self.try_to_login(login, password, active_only=true) login = login.to_s password = password.to_s - # Make sure no one can sign in with an empty password - return nil if password.empty? + # Make sure no one can sign in with an empty login or password + return nil if login.empty? || password.empty? user = find_by_login(login) if user # user is already in local database - return nil if !user.active? - if user.auth_source - # user has an external authentication method - return nil unless user.auth_source.authenticate(login, password) - else - # authentication with local password - return nil unless user.check_password?(password) - end + return nil unless user.check_password?(password) + return nil if !user.active? && active_only else # user is not yet registered, try to authenticate with available sources attrs = AuthSource.authenticate(login, password) @@ -199,7 +194,7 @@ end end end - user.update_attribute(:last_login_on, Time.now) if user && !user.new_record? + user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active? user rescue => text raise text @@ -207,14 +202,10 @@ # Returns the user who matches the given autologin +key+ or nil def self.try_to_autologin(key) - tokens = Token.find_all_by_action_and_value('autologin', key.to_s) - # Make sure there's only 1 token that matches the key - if tokens.size == 1 - token = tokens.first - if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active? - token.user.update_attribute(:last_login_on, Time.now) - token.user - end + user = Token.find_active_user('autologin', key, Setting.autologin.to_i) + if user + user.update_column(:last_login_on, Time.now) + user end end @@ -301,13 +292,20 @@ return auth_source.allow_password_changes? end - # Generate and set a random password. Useful for automated user creation - # Based on Token#generate_token_value - # - def random_password + def must_change_password? + must_change_passwd? && change_password_allowed? + end + + def generate_password? + generate_password == '1' || generate_password == true + end + + # Generate and set a random password on given length + def random_password(length=40) chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + chars -= %w(0 O 1 l) password = '' - 40.times { |i| password << chars[rand(chars.size-1)] } + length.times {|i| password << chars[SecureRandom.random_number(chars.size)] } self.password = password self.password_confirmation = password self @@ -347,12 +345,20 @@ end def notified_project_ids=(ids) - Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id]) - Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty? - @notified_projects_ids = nil - notified_projects_ids + @notified_projects_ids_changed = true + @notified_projects_ids = ids end + # Updates per project notifications (after_save callback) + def update_notified_project_ids + if @notified_projects_ids_changed + ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : []) + members.update_all(:mail_notification => false) + members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any? + end + end + private :update_notified_project_ids + def valid_notification_options self.class.valid_notification_options(self) end @@ -371,23 +377,24 @@ # Find a user account by matching the exact login and then a case-insensitive # version. Exact matches will be given priority. def self.find_by_login(login) - # First look for an exact match - user = where(:login => login).all.detect {|u| u.login == login} - unless user - # Fail over to case-insensitive if none was found - user = where("LOWER(login) = ?", login.to_s.downcase).first + if login.present? + login = login.to_s + # First look for an exact match + user = where(:login => login).all.detect {|u| u.login == login} + unless user + # Fail over to case-insensitive if none was found + user = where("LOWER(login) = ?", login.downcase).first + end + user end - user end def self.find_by_rss_key(key) - token = Token.find_by_action_and_value('feeds', key.to_s) - token && token.user.active? ? token.user : nil + Token.find_active_user('feeds', key) end def self.find_by_api_key(key) - token = Token.find_by_action_and_value('api', key.to_s) - token && token.user.active? ? token.user : nil + Token.find_active_user('api', key) end # Makes find_by_mail case-insensitive @@ -441,30 +448,38 @@ !logged? end + # Returns user's membership for the given project + # or nil if the user is not a member of project + def membership(project) + project_id = project.is_a?(Project) ? project.id : project + + @membership_by_project_id ||= Hash.new {|h, project_id| + h[project_id] = memberships.where(:project_id => project_id).first + } + @membership_by_project_id[project_id] + end + + # Returns the user's bult-in role + def builtin_role + @builtin_role ||= Role.non_member + end + # Return user's roles for project def roles_for_project(project) roles = [] # No role on archived projects return roles if project.nil? || project.archived? - if logged? - # Find project membership - membership = memberships.detect {|m| m.project_id == project.id} - if membership - roles = membership.roles - else - @role_non_member ||= Role.non_member - roles << @role_non_member - end + if membership = membership(project) + roles = membership.roles else - @role_anonymous ||= Role.anonymous - roles << @role_anonymous + roles << builtin_role end roles end # Return true if the user is a member of project def member_of?(project) - !roles_for_project(project).detect {|role| role.member?}.nil? + projects.to_a.include?(project) end # Returns a hash of user's projects grouped by roles @@ -549,7 +564,7 @@ allowed_to?(action, nil, options.reverse_merge(:global => true), &block) end - # Returns true if the user is allowed to delete his own account + # Returns true if the user is allowed to delete the user's own account def own_account_deletable? Setting.unsubscribe? && (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?) @@ -560,6 +575,7 @@ 'lastname', 'mail', 'mail_notification', + 'notified_project_ids', 'language', 'custom_field_values', 'custom_fields', @@ -567,6 +583,8 @@ safe_attributes 'status', 'auth_source_id', + 'generate_password', + 'must_change_passwd', :if => lambda {|user, current_user| current_user.admin?} safe_attributes 'group_ids', @@ -577,47 +595,35 @@ # # TODO: only supports Issue events currently def notify_about?(object) - case mail_notification - when 'all' + if mail_notification == 'all' true - when 'selected' - # user receives notifications for created/assigned issues on unselected projects - if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)) + elsif mail_notification.blank? || mail_notification == 'none' + false + else + case object + when Issue + case mail_notification + when 'selected', 'only_my_events' + # user receives notifications for created/assigned issues on unselected projects + object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_assigned' + is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_owner' + object.author == self + end + when News + # always send to project members except when mail_notification is set to 'none' true - else - false end - when 'none' - false - when 'only_my_events' - if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)) - true - else - false - end - when 'only_assigned' - if object.is_a?(Issue) && (is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)) - true - else - false - end - when 'only_owner' - if object.is_a?(Issue) && object.author == self - true - else - false - end - else - false end end def self.current=(user) - @current_user = user + Thread.current[:current_user] = user end def self.current - @current_user ||= User.anonymous + Thread.current[:current_user] ||= User.anonymous end # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only @@ -648,6 +654,7 @@ protected def validate_password_length + return if password.blank? && generate_password? # Password length validation based on setting if !password.nil? && password.size < Setting.password_min_length.to_i errors.add(:password, :too_short, :count => Setting.password_min_length.to_i) @@ -656,6 +663,13 @@ private + def generate_password_if_needed + if generate_password? && auth_source.nil? + length = [Setting.password_min_length.to_i + 2, 10].max + random_password(length) + end + end + # Removes references that are not handled by associations # Things that are not deleted are reassociated with the anonymous user def remove_references_before_destroy @@ -672,7 +686,7 @@ Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] # Remove private queries and keep public ones - ::Query.delete_all ['user_id = ? AND is_public = ?', id, false] + ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE] ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] Token.delete_all ['user_id = ?', id] @@ -698,7 +712,7 @@ def validate_anonymous_uniqueness # There should be only one AnonymousUser in the database - errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first) + errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists? end def available_custom_fields @@ -717,6 +731,19 @@ UserPreference.new(:user => self) end + # Returns the user's bult-in role + def builtin_role + @builtin_role ||= Role.anonymous + end + + def membership(*args) + nil + end + + def member_of?(*args) + false + end + # Anonymous user can not be destroyed def destroy false diff -r d98d22a98252 -r afce8026aaeb app/models/user_custom_field.rb --- a/app/models/user_custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/user_custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/user_preference.rb --- a/app/models/user_preference.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/user_preference.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -22,7 +22,7 @@ attr_protected :others, :user_id before_save :set_others_hash - + def initialize(attributes=nil, *args) super self.others ||= {} @@ -33,7 +33,7 @@ end def [](attr_name) - if attribute_present? attr_name + if has_attribute? attr_name super else others ? others[attr_name] : nil @@ -41,7 +41,7 @@ end def []=(attr_name, value) - if attribute_present? attr_name + if has_attribute? attr_name super else h = (read_attribute(:others) || {}).dup @@ -56,4 +56,7 @@ def warn_on_leaving_unsaved; self[:warn_on_leaving_unsaved] || '1'; end def warn_on_leaving_unsaved=(value); self[:warn_on_leaving_unsaved]=value; end + + def no_self_notified; (self[:no_self_notified] == true || self[:no_self_notified] == '1'); end + def no_self_notified=(value); self[:no_self_notified]=value; end end diff -r d98d22a98252 -r afce8026aaeb app/models/version.rb --- a/app/models/version.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/version.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,25 +30,25 @@ validates_presence_of :name validates_uniqueness_of :name, :scope => [:project_id] validates_length_of :name, :maximum => 60 - validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true + validates :effective_date, :date => true validates_inclusion_of :status, :in => VERSION_STATUSES validates_inclusion_of :sharing, :in => VERSION_SHARINGS - validate :validate_version scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} - scope :open, where(:status => 'open') + scope :open, lambda { where(:status => 'open') } scope :visible, lambda {|*args| includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues)) } - safe_attributes 'name', + safe_attributes 'name', 'description', 'effective_date', 'due_date', 'wiki_page_title', 'status', 'sharing', - 'custom_field_values' + 'custom_field_values', + 'custom_fields' # Returns true if +user+ or current user is allowed to view the version def visible?(user=User.current) @@ -97,10 +97,10 @@ end def behind_schedule? - if completed_pourcent == 100 + if completed_percent == 100 return false elsif due_date && start_date - done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor + done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor return done_date <= Date.today else false # No issues so it's not late @@ -109,7 +109,7 @@ # Returns the completion percentage of this version based on the amount of open/closed issues # and the time spent on the open issues. - def completed_pourcent + def completed_percent if issues_count == 0 0 elsif open_issues_count == 0 @@ -119,8 +119,14 @@ end end + # TODO: remove in Redmine 3.0 + def completed_pourcent + ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead." + completed_percent + end + # Returns the percentage of issues that have been marked as 'closed'. - def closed_pourcent + def closed_percent if issues_count == 0 0 else @@ -128,6 +134,12 @@ end end + # TODO: remove in Redmine 3.0 + def closed_pourcent + ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead." + closed_percent + end + # Returns true if the version is overdue: due date reached and some open issues def overdue? effective_date && (effective_date < Date.today) && (open_issues_count > 0) @@ -275,10 +287,4 @@ progress end end - - def validate_version - if effective_date.nil? && @attributes['effective_date'].present? - errors.add :effective_date, :not_a_date - end - end end diff -r d98d22a98252 -r afce8026aaeb app/models/version_custom_field.rb --- a/app/models/version_custom_field.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/version_custom_field.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/watcher.rb --- a/app/models/watcher.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/watcher.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,13 +23,26 @@ validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] validate :validate_user + # Returns true if at least one object among objects is watched by user + def self.any_watched?(objects, user) + objects = objects.reject(&:new_record?) + if objects.any? + objects.group_by {|object| object.class.base_class}.each do |base_class, objects| + if Watcher.where(:watchable_type => base_class.name, :watchable_id => objects.map(&:id), :user_id => user.id).exists? + return true + end + end + end + false + end + # Unwatch things that users are no longer allowed to view def self.prune(options={}) if options.has_key?(:user) prune_single_user(options[:user], options) else pruned = 0 - User.find(:all, :conditions => "id IN (SELECT DISTINCT user_id FROM #{table_name})").each do |user| + User.where("id IN (SELECT DISTINCT user_id FROM #{table_name})").all.each do |user| pruned += prune_single_user(user, options) end pruned @@ -47,7 +60,7 @@ def self.prune_single_user(user, options={}) return unless user.is_a?(User) pruned = 0 - find(:all, :conditions => {:user_id => user.id}).each do |watcher| + where(:user_id => user.id).all.each do |watcher| next if watcher.watchable.nil? if options.has_key?(:project) diff -r d98d22a98252 -r afce8026aaeb app/models/wiki.rb --- a/app/models/wiki.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/wiki.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,7 +24,7 @@ acts_as_watchable validates_presence_of :start_page - validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/ + validates_format_of :start_page, :with => /\A[^,\.\/\?\;\|\:]*\z/ safe_attributes 'start_page' @@ -50,10 +50,10 @@ @page_found_with_redirect = false title = start_page if title.blank? title = Wiki.titleize(title) - page = pages.first(:conditions => ["LOWER(title) = LOWER(?)", title]) + page = pages.where("LOWER(title) = LOWER(?)", title).first if !page && !(options[:with_redirect] == false) # search for a redirect - redirect = redirects.first(:conditions => ["LOWER(title) = LOWER(?)", title]) + redirect = redirects.where("LOWER(title) = LOWER(?)", title).first if redirect page = find_page(redirect.redirects_to, :with_redirect => false) @page_found_with_redirect = true diff -r d98d22a98252 -r afce8026aaeb app/models/wiki_content.rb --- a/app/models/wiki_content.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/wiki_content.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,6 +26,8 @@ acts_as_versioned + after_save :send_notification + def visible?(user=User.current) page.visible?(user) end @@ -59,6 +61,7 @@ :description => :comments, :datetime => :updated_on, :type => 'wiki-page', + :group => :page, :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.page.wiki.project, :id => o.page.title, :version => o.version}} acts_as_activity_provider :type => 'wiki_edits', @@ -144,4 +147,19 @@ end end end + + private + + def send_notification + # new_record? returns false in after_save callbacks + if id_changed? + if Setting.notified_events.include?('wiki_content_added') + Mailer.wiki_content_added(self).deliver + end + elsif text_changed? + if Setting.notified_events.include?('wiki_content_updated') + Mailer.wiki_content_updated(self).deliver + end + end + end end diff -r d98d22a98252 -r afce8026aaeb app/models/wiki_content_observer.rb --- a/app/models/wiki_content_observer.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# 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. - -class WikiContentObserver < ActiveRecord::Observer - def after_create(wiki_content) - Mailer.wiki_content_added(wiki_content).deliver if Setting.notified_events.include?('wiki_content_added') - end - - def after_update(wiki_content) - if wiki_content.text_changed? - Mailer.wiki_content_updated(wiki_content).deliver if Setting.notified_events.include?('wiki_content_updated') - end - end -end diff -r d98d22a98252 -r afce8026aaeb app/models/wiki_page.rb --- a/app/models/wiki_page.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/wiki_page.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -40,7 +40,7 @@ attr_accessor :redirect_existing_links validates_presence_of :title - validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/ + validates_format_of :title, :with => /\A[^,\.\/\?\;\|\s]*\z/ validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false validates_associated :content @@ -49,9 +49,9 @@ before_save :handle_redirects # eager load information about last updates, without loading text - scope :with_updated_on, { - :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version", - :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id" + scope :with_updated_on, lambda { + select("#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version"). + joins("LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id") } # Wiki pages that are protected by default @@ -175,9 +175,10 @@ end # Saves the page and its content if text was changed - def save_with_content + def save_with_content(content) ret = nil transaction do + self.content = content if new_record? # Rails automatically saves associated content ret = save diff -r d98d22a98252 -r afce8026aaeb app/models/wiki_redirect.rb --- a/app/models/wiki_redirect.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/wiki_redirect.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/workflow_permission.rb --- a/app/models/workflow_permission.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/workflow_permission.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/models/workflow_rule.rb --- a/app/models/workflow_rule.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/workflow_rule.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -62,8 +62,8 @@ else transaction do delete_all :tracker_id => target_tracker.id, :role_id => target_role.id - connection.insert "INSERT INTO #{WorkflowRule.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee, field_name, rule, type)" + - " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee, field_name, rule, type" + + connection.insert "INSERT INTO #{WorkflowRule.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type)" + + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type" + " FROM #{WorkflowRule.table_name}" + " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" end diff -r d98d22a98252 -r afce8026aaeb app/models/workflow_transition.rb --- a/app/models/workflow_transition.rb Wed May 07 14:15:02 2014 +0100 +++ b/app/models/workflow_transition.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb app/views/account/login.html.erb --- a/app/views/account/login.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/account/login.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -4,34 +4,34 @@ <%= back_url_hidden_field_tag %> - - + + - - + + <% if Setting.openid? %> - - + + <% end %> - - - diff -r d98d22a98252 -r afce8026aaeb app/views/account/logout.html.erb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/account/logout.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,3 @@ +<%= form_tag(signout_path) do %> +

    <%= submit_tag l(:label_logout) %>

    +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/activities/index.html.erb --- a/app/views/activities/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/activities/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -15,13 +15,14 @@ <% @events_by_day.keys.sort.reverse.each do |day| %>

    <%= format_activity_day(day) %>

    -<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> -
    +<% sort_activity_events(@events_by_day[day]).each do |e, in_group| -%> +
    <%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>"> <%= avatar(e.event_author, :size => "24") if e.respond_to?(:event_author) %> <%= format_time(e.event_datetime, false) %> <%= content_tag('span', h(e.project), :class => 'project') if @project.nil? || @project != e.project %> - <%= link_to format_activity_title(e.event_title), e.event_url %>
    -
    <%= format_activity_description(e.event_description) %> + <%= link_to format_activity_title(e.event_title), e.event_url %> + +
    "><%= format_activity_description(e.event_description) %> <%= link_to_user(e.event_author) if e.respond_to?(:event_author) %>
    <% end -%>
    @@ -52,11 +53,17 @@ <% content_for :sidebar do %> <%= form_tag({}, :method => :get) do %>

    <%= l(:label_activity) %>

    -

    <% @activity.event_types.each do |t| %> -<%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> - -
    -<% end %>

    +
      +<% @activity.event_types.each do |t| %> +
    • + <%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> + +
    • +<% end %> +
    <% if @project && @project.descendants.active.any? %> <%= hidden_field_tag 'with_subprojects', 0 %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/admin/info.html.erb --- a/app/views/admin/info.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/admin/info.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -5,8 +5,8 @@
    <%= text_field_tag 'username', params[:username], :tabindex => '1' %><%= text_field_tag 'username', params[:username], :tabindex => '1' %>
    <%= password_field_tag 'password', nil, :tabindex => '2' %><%= password_field_tag 'password', nil, :tabindex => '2' %>
    <%= text_field_tag "openid_url", nil, :tabindex => '3' %><%= text_field_tag "openid_url", nil, :tabindex => '3' %>
    + <% if Setting.autologin? %> <% end %>
    + <% if Setting.lost_password? %> <%= link_to l(:label_password_lost), lost_password_path %> <% end %> +
    <% @checklist.each do |label, result| %> - - + <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/admin/plugins.html.erb --- a/app/views/admin/plugins.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/admin/plugins.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,16 +1,16 @@ -

    <%= l(:label_plugins) %>

    +<%= title l(:label_plugins) %> <% if @plugins.any? %>
    <%= l(label) %><%= image_tag((result ? 'true.png' : 'exclamation.png'), + <%= l(label) %><%= image_tag((result ? 'true.png' : 'exclamation.png'), :style => "vertical-align:bottom;") %>
    <% @plugins.each do |plugin| %> - - + - + <% end %>
    <%=h plugin.name %> +
    <%=h plugin.name %> <%= content_tag('span', h(plugin.description), :class => 'description') unless plugin.description.blank? %> <%= content_tag('span', link_to(h(plugin.url), plugin.url), :class => 'url') unless plugin.url.blank? %> <%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %> <%=h plugin.version %><%= link_to(l(:button_configure), :controller => 'settings', :action => 'plugin', :id => plugin.id) if plugin.configurable? %><%= link_to(l(:button_configure), plugin_settings_path(plugin)) if plugin.configurable? %>
    diff -r d98d22a98252 -r afce8026aaeb app/views/admin/projects.html.erb --- a/app/views/admin/projects.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/admin/projects.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -2,7 +2,7 @@ <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add' %>
    -

    <%=l(:label_project_plural)%>

    +<%= title l(:label_project_plural) %> <%= form_tag({}, :method => :get) do %>
    <%= l(:label_filter_plural) %> @@ -27,9 +27,9 @@ <% project_tree(@projects) do |project, level| %> <%= project.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> - <%= link_to_project(project, {:action => (project.active? ? 'settings' : 'show')}, :title => project.short_description) %> - <%= checked_image project.is_public? %> - <%= format_date(project.created_on) %> + <%= link_to_project_settings(project, {}, :title => project.short_description) %> + <%= checked_image project.is_public? %> + <%= format_date(project.created_on) %> <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :data => {:confirm => l(:text_are_you_sure)}, :method => :post, :class => 'icon icon-lock') unless project.archived? %> <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if project.archived? && (project.parent.nil? || !project.parent.archived?) %> @@ -41,5 +41,3 @@
    - -<% html_title(l(:label_project_plural)) -%> diff -r d98d22a98252 -r afce8026aaeb app/views/attachments/_form.html.erb --- a/app/views/attachments/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/attachments/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,19 +1,33 @@ + <% if defined?(container) && container && container.saved_attachments %> <% container.saved_attachments.each_with_index do |attachment, i| %> - - <%= h(attachment.filename) %> (<%= number_to_human_size(attachment.filesize) %>) - <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.id}.#{attachment.digest}" %> + + <%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename') + + text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') + + link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %> + <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %> <% end %> <% end %> - - - <%= file_field_tag 'attachments[1][file]', :id => nil, :class => 'file', - :onchange => "checkFileSize(this, #{Setting.attachment_max_size.to_i.kilobytes}, '#{escape_javascript(l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)))}');" -%> - <%= text_field_tag 'attachments[1][description]', '', :id => nil, :class => 'description', :maxlength => 255, :placeholder => l(:label_optional_description) %> - <%= link_to_function(image_tag('delete.png'), 'removeFileField(this)', :title => (l(:button_delete))) %> - + +<%= file_field_tag 'attachments[dummy][file]', + :id => nil, + :class => 'file_selector', + :multiple => true, + :data => { + :max_file_size => Setting.attachment_max_size.to_i.kilobytes, + :max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)), + :max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i, + :upload_path => uploads_path(:format => 'js'), + :description_placeholder => l(:label_optional_description) + } %> +(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) + +<%= javascript_tag do %> + $('input.file_selector').on('change', function(){addInputFiles(this);}); +<% end %> -<%= link_to l(:label_add_another_file), '#', :onclick => 'addFileField(); return false;', :class => 'add_attachment' %> -(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) +<% content_for :header_tags do %> + <%= javascript_include_tag 'attachments' %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/attachments/destroy.js.erb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/attachments/destroy.js.erb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1 @@ +$('#attachments_<%= j params[:attachment_id] %>').remove(); diff -r d98d22a98252 -r afce8026aaeb app/views/attachments/upload.js.erb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/attachments/upload.js.erb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,14 @@ +var fileSpan = $('#attachments_<%= j params[:attachment_id] %>'); +<% if @attachment.new_record? %> + fileSpan.hide(); + alert("<%= escape_javascript @attachment.errors.full_messages.join(', ') %>"); +<% else %> +$('', { type: 'hidden', name: 'attachments[<%= j params[:attachment_id] %>][token]' } ).val('<%= j @attachment.token %>').appendTo(fileSpan); +fileSpan.find('a.remove-upload') + .attr({ + "data-remote": true, + "data-method": 'delete', + href: '<%= j attachment_path(@attachment, :attachment_id => params[:attachment_id], :format => 'js') %>' + }) + .off('click'); +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/auth_sources/_form.html.erb --- a/app/views/auth_sources/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/auth_sources/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,13 +1,6 @@ <%= error_messages_for 'auth_source' %> -
    - -

    -<%= text_field 'auth_source', 'name' %>

    - -

    -<%= check_box 'auth_source', 'onthefly_register' %>

    +
    +

    <%= f.text_field :name, :required => true %>

    +

    <%= f.check_box :onthefly_register, :label => :field_onthefly %>

    - - - diff -r d98d22a98252 -r afce8026aaeb app/views/auth_sources/_form_auth_source_ldap.html.erb --- a/app/views/auth_sources/_form_auth_source_ldap.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/auth_sources/_form_auth_source_ldap.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,50 +1,24 @@ <%= error_messages_for 'auth_source' %> -
    - -

    -<%= text_field 'auth_source', 'name' %>

    - -

    -<%= text_field 'auth_source', 'host' %>

    - -

    -<%= text_field 'auth_source', 'port', :size => 6 %> <%= check_box 'auth_source', 'tls' %> LDAPS

    - -

    -<%= text_field 'auth_source', 'account' %>

    - -

    -<%= password_field 'auth_source', 'account_password', :name => 'ignore', - :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), - :onfocus => "this.value=''; this.name='auth_source[account_password]';", - :onchange => "this.name='auth_source[account_password]';" %>

    - -

    -<%= text_field 'auth_source', 'base_dn', :size => 60 %>

    - -

    -<%= text_field 'auth_source', 'filter', :size => 60 %>

    - -

    -<%= text_field 'auth_source', 'timeout', :size => 4 %>

    - -

    -<%= check_box 'auth_source', 'onthefly_register' %>

    +
    +

    <%= f.text_field :name, :required => true %>

    +

    <%= f.text_field :host, :required => true %>

    +

    <%= f.text_field :port, :required => true, :size => 6 %> <%= f.check_box :tls, :no_label => true %> LDAPS

    +

    <%= f.text_field :account %>

    +

    <%= f.password_field :account_password, :label => :field_password, + :name => 'dummy_password', + :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), + :onfocus => "this.value=''; this.name='auth_source[account_password]';", + :onchange => "this.name='auth_source[account_password]';" %>

    +

    <%= f.text_field :base_dn, :required => true, :size => 60 %>

    +

    <%= f.text_field :filter, :size => 60, :label => :field_auth_source_ldap_filter %>

    +

    <%= f.text_field :timeout, :size => 4 %>

    +

    <%= f.check_box :onthefly_register, :label => :field_onthefly %>

    -
    <%=l(:label_attribute_plural)%> -

    -<%= text_field 'auth_source', 'attr_login', :size => 20 %>

    - -

    -<%= text_field 'auth_source', 'attr_firstname', :size => 20 %>

    - -

    -<%= text_field 'auth_source', 'attr_lastname', :size => 20 %>

    - -

    -<%= text_field 'auth_source', 'attr_mail', :size => 20 %>

    +
    <%=l(:label_attribute_plural)%> +

    <%= f.text_field :attr_login, :required => true, :size => 20 %>

    +

    <%= f.text_field :attr_firstname, :size => 20 %>

    +

    <%= f.text_field :attr_lastname, :size => 20 %>

    +

    <%= f.text_field :attr_mail, :size => 20 %>

    - - diff -r d98d22a98252 -r afce8026aaeb app/views/auth_sources/edit.html.erb --- a/app/views/auth_sources/edit.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/auth_sources/edit.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@ -

    <%=l(:label_auth_source)%> (<%= h(@auth_source.auth_method_name) %>)

    +<%= title [l(:label_auth_source_plural), auth_sources_path], @auth_source.name %> -<%= form_tag({:action => 'update', :id => @auth_source}, :method => :put, :class => "tabular") do %> - <%= render :partial => auth_source_partial_name(@auth_source) %> +<%= labelled_form_for @auth_source, :as => :auth_source, :url => auth_source_path(@auth_source), :html => {:id => 'auth_source_form'} do |f| %> + <%= render :partial => auth_source_partial_name(@auth_source), :locals => { :f => f } %> <%= submit_tag l(:button_save) %> <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/auth_sources/index.html.erb --- a/app/views/auth_sources/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/auth_sources/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -2,7 +2,7 @@ <%= link_to l(:label_auth_source_new), {:action => 'new'}, :class => 'icon icon-add' %>
    -

    <%=l(:label_auth_source_plural)%>

    +<%= title l(:label_auth_source_plural) %> @@ -15,12 +15,12 @@ <% for source in @auth_sources %> "> - - - - + + + + diff -r d98d22a98252 -r afce8026aaeb app/views/auth_sources/new.html.erb --- a/app/views/auth_sources/new.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/auth_sources/new.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ -

    <%=l(:label_auth_source_new)%> (<%= h(@auth_source.auth_method_name) %>)

    +<%= title [l(:label_auth_source_plural), auth_sources_path], "#{l(:label_auth_source_new)} (#{@auth_source.auth_method_name})" %> -<%= form_tag({:action => 'create'}, :class => "tabular") do %> +<%= labelled_form_for @auth_source, :as => :auth_source, :url => auth_sources_path, :html => {:id => 'auth_source_form'} do |f| %> <%= hidden_field_tag 'type', @auth_source.type %> - <%= render :partial => auth_source_partial_name(@auth_source) %> + <%= render :partial => auth_source_partial_name(@auth_source), :locals => { :f => f } %> <%= submit_tag l(:button_create) %> <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/boards/index.html.erb --- a/app/views/boards/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/boards/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -10,8 +10,8 @@ <% Board.board_tree(@boards) do |board, level| %> - diff -r d98d22a98252 -r afce8026aaeb app/views/boards/show.html.erb --- a/app/views/boards/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/boards/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,20 +1,20 @@ <%= board_breadcrumb(@board) %>
    -<%= link_to_if_authorized l(:label_message_new), - {:controller => 'messages', :action => 'new', :board_id => @board}, - :class => 'icon icon-add', - :onclick => 'showAndScrollTo("add-message", "message_subject"); return false;' %> -<%= watcher_tag(@board, User.current) %> +<%= link_to l(:label_message_new), + new_board_message_path(@board), + :class => 'icon icon-add', + :onclick => 'showAndScrollTo("add-message", "message_subject"); return false;' if User.current.allowed_to?(:add_messages, @board.project) %> +<%= watcher_link(@board, User.current) %>
    <% @topics.each do |topic| %> - + diff -r d98d22a98252 -r afce8026aaeb app/views/common/_diff.html.erb --- a/app/views/common/_diff.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/common/_diff.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -10,7 +10,7 @@ @@ -24,11 +24,11 @@ <% end -%> @@ -40,7 +40,7 @@ @@ -55,7 +55,7 @@ <% end -%> diff -r d98d22a98252 -r afce8026aaeb app/views/common/_file.html.erb --- a/app/views/common/_file.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/common/_file.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -3,8 +3,8 @@ <% line_num = 1 %> <% syntax_highlight_lines(filename, Redmine::CodesetUtil.to_utf8_by_setting(content)).each do |line| %> - - + - @@ -54,7 +54,7 @@ - diff -r d98d22a98252 -r afce8026aaeb app/views/gantts/show.html.erb --- a/app/views/gantts/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/gantts/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,11 @@ <% @gantt.view = self %> +
    +<% if !@query.new_record? && @query.editable_by?(User.current) %> + <%= link_to l(:button_edit), edit_query_path(@query, :gantt => 1), :class => 'icon icon-edit' %> + <%= delete_link query_path(@query, :gantt => 1) %> +<% end %> +
    +

    <%= @query.new_record? ? l(:label_gantt) : h(@query.name) %>

    <%= form_tag({:controller => 'gantts', :action => 'show', @@ -6,12 +13,46 @@ :year => params[:year], :months => params[:months]}, :method => :get, :id => 'query_form') do %> <%= hidden_field_tag 'set_filter', '1' %> +<%= hidden_field_tag 'gantt', '1' %>
    "> <%= l(:label_filter_plural) %>
    "> <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
    +
    <%= link_to(h(source.name), :action => 'edit', :id => source)%><%= h source.auth_method_name %><%= h source.host %><%= h source.users.count %><%= link_to(h(source.name), :action => 'edit', :id => source)%><%= h source.auth_method_name %><%= h source.host %><%= h source.users.count %> - <%= link_to l(:button_test), {:action => 'test_connection', :id => source}, :class => 'icon icon-test' %> + <%= link_to l(:button_test), try_connection_auth_source_path(source), :class => 'icon icon-test' %> <%= delete_link auth_source_path(source) %>
    - <%= link_to h(board.name), {:action => 'show', :id => board}, :class => "board" %>
    +
    + <%= link_to h(board.name), project_board_path(board.project, board), :class => "board" %>
    <%=h board.description %>
    <%= board.topics_count %>
    <%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => topic } %><%= link_to h(topic.subject), board_message_path(@board, topic) %> <%= link_to_user(topic.author) %> <%= format_time(topic.created_on) %> <%= topic.replies_count %>
    - <%= h(Redmine::CodesetUtil.to_utf8_by_setting(table_file.file_name)) %> + <%= table_file.file_name %>
    <%= line.nb_line_left %> -
    <%= Redmine::CodesetUtil.to_utf8_by_setting(line.html_line_left).html_safe %>
    +
    <%= line.html_line_left.html_safe %>
    <%= line.nb_line_right %> -
    <%= Redmine::CodesetUtil.to_utf8_by_setting(line.html_line_right).html_safe %>
    +
    <%= line.html_line_right.html_safe %>
    - <%= h(Redmine::CodesetUtil.to_utf8_by_setting(table_file.file_name)) %> + <%= table_file.file_name %>
    <%= line.nb_line_left %> <%= line.nb_line_right %> -
    <%= Redmine::CodesetUtil.to_utf8_by_setting(line.html_line).html_safe %>
    +
    <%= line.html_line.html_safe %>
    +
    <%= line_num %> diff -r d98d22a98252 -r afce8026aaeb app/views/common/_preview.html.erb --- a/app/views/common/_preview.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/common/_preview.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,3 +1,3 @@
    <%= l(:label_preview) %> -<%= textilizable @text, :attachments => @attachements, :object => @previewed %> +<%= textilizable @text, :attachments => @attachments, :object => @previewed %>
    diff -r d98d22a98252 -r afce8026aaeb app/views/common/_tabs.html.erb --- a/app/views/common/_tabs.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/common/_tabs.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -6,7 +6,7 @@
  • <%= link_to l(tab[:label]), { :tab => tab[:name] }, :id => "tab-#{tab[:name]}", :class => (tab[:name] != selected_tab ? nil : 'selected'), - :onclick => "showTab('#{tab[:name]}'); this.blur(); return false;" %>
  • + :onclick => "showTab('#{tab[:name]}', this.href); this.blur(); return false;" %> <% end -%>
    + <%= link_to(h(container), {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %>
    <%= number_to_human_size(file.filesize) %> <%= file.downloads %> <%= file.digest %> + <%= link_to(image_tag('delete.png'), attachment_path(file), :data => {:confirm => l(:text_are_you_sure)}, :method => :delete) if delete_allowed %>
    + + + + +
    +
    + <%= l(:label_related_issues) %> + +
    +
    +
    + <%= l(:label_gantt_progress_line) %> + +
    +
    +
    +

    <%= gantt_zoom_link(@gantt, :in) %> @@ -29,6 +70,11 @@ :class => 'icon icon-checked' %> <%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, :class => 'icon icon-reload' %> +<% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %> + <%= link_to_function l(:button_save), + "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit();", + :class => 'icon icon-save' %> +<% end %>

    <% end %> @@ -39,18 +85,18 @@ @gantt.zoom.times { zoom = zoom * 2 } subject_width = 330 - header_heigth = 18 + header_height = 18 - headers_height = header_heigth + headers_height = header_height show_weeks = false show_days = false if @gantt.zoom > 1 show_weeks = true - headers_height = 2 * header_heigth + headers_height = 2 * header_height if @gantt.zoom > 2 show_days = true - headers_height = 3 * header_heigth + headers_height = 3 * header_height end end @@ -102,7 +148,7 @@ -
    +
    <% style = "" style += "width: #{g_width - 1}px;" @@ -115,7 +161,7 @@ <% month_f = @gantt.date_from left = 0 - height = (show_weeks ? header_heigth : header_heigth + g_height) + height = (show_weeks ? header_height : header_height + g_height) %> <% @gantt.months.times do %> <% @@ -140,7 +186,7 @@ <% if show_weeks %> <% left = 0 - height = (show_days ? header_heigth - 1 : header_heigth - 1 + g_height) + height = (show_days ? header_height - 1 : header_height - 1 + g_height) %> <% if @gantt.date_from.cwday == 1 %> <% @@ -189,7 +235,7 @@ <% if show_days %> <% left = 0 - height = g_height + header_heigth - 1 + height = g_height + header_height - 1 wday = @gantt.date_from.cwday %> <% (@gantt.date_to - @gantt.date_from + 1).to_i.times do %> @@ -229,9 +275,17 @@ style += "width:10px;" style += "border-left: 1px dashed red;" %> - <%= content_tag(:div, ' '.html_safe, :style => style) %> + <%= content_tag(:div, ' '.html_safe, :style => style, :id => 'today_line') %> <% end %> - +<% + style = "" + style += "position: absolute;" + style += "height: #{g_height}px;" + style += "top: #{headers_height + 1}px;" + style += "left: 0px;" + style += "width: #{g_width - 1}px;" +%> +<%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %>
    @@ -239,11 +293,11 @@ - - @@ -261,3 +315,18 @@ <% end %> <% html_title(l(:label_gantt)) -%> + +<% content_for :header_tags do %> + <%= javascript_include_tag 'raphael' %> + <%= javascript_include_tag 'gantt' %> +<% end %> + +<%= javascript_tag do %> + var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>; + $(document).ready(drawGanttHandler); + $(window).resize(drawGanttHandler); + $(function() { + $("#draw_relations").change(drawGanttHandler); + $("#draw_progress_line").change(drawGanttHandler); + }); +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/groups/_form.html.erb --- a/app/views/groups/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/groups/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ <%= error_messages_for @group %>
    -

    <%= f.text_field :name %>

    +

    <%= f.text_field :name, :required => true, :size => 60 %>

    <% @group.custom_field_values.each do |value| %>

    <%= custom_field_tag_with_label :group, value %>

    <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/groups/_memberships.html.erb --- a/app/views/groups/_memberships.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/groups/_memberships.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ <% roles = Role.find_all_givable %> -<% projects = Project.active.find(:all, :order => 'lft') %> +<% projects = Project.active.all %>
    <% if @group.memberships.any? %> @@ -13,7 +13,7 @@ <% @group.memberships.each do |membership| %> <% next if membership.new_record? %>
    - + <% for status in @issue_statuses %> "> - + <% if Issue.use_status_for_done_ratio? %> - + <% end %> - - - + + + diff -r d98d22a98252 -r afce8026aaeb app/views/issue_statuses/new.html.erb --- a/app/views/issue_statuses/new.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issue_statuses/new.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%= link_to l(:label_issue_status_plural), issue_statuses_path %> » <%=l(:label_issue_status_new)%>

    +<%= title [l(:label_issue_status_plural), issue_statuses_path], l(:label_issue_status_new) %> <%= labelled_form_for @issue_status do |f| %> <%= render :partial => 'form', :locals => {:f => f} %> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_action_menu.html.erb --- a/app/views/issues/_action_menu.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/_action_menu.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@
    -<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "issue_notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> +<%= link_to l(:button_update), edit_issue_path(@issue), :onclick => 'showAndScrollTo("update", "issue_notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit) if @issue.editable? %> <%= link_to l(:button_log_time), new_issue_time_entry_path(@issue), :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project) %> -<%= watcher_tag(@issue, User.current) %> -<%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, :class => 'icon icon-copy' %> +<%= watcher_link(@issue, User.current) %> +<%= link_to l(:button_copy), project_copy_issue_path(@project, @issue), :class => 'icon icon-copy' if User.current.allowed_to?(:add_issues, @project) %> <%= link_to l(:button_delete), issue_path(@issue), :data => {:confirm => issues_destroy_confirmation_message(@issue)}, :method => :delete, :class => 'icon icon-del' if User.current.allowed_to?(:delete_issues, @project) %>
    diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_attributes.html.erb --- a/app/views/issues/_attributes.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/_attributes.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -47,11 +47,19 @@ <% end %> <% if @issue.safe_attribute? 'start_date' %> -

    <%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('start_date') %><%= calendar_for('issue_start_date') if @issue.leaf? %>

    +

    + <%= f.text_field(:start_date, :size => 10, :disabled => !@issue.leaf?, + :required => @issue.required_attribute?('start_date')) %> + <%= calendar_for('issue_start_date') if @issue.leaf? %> +

    <% end %> <% if @issue.safe_attribute? 'due_date' %> -

    <%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('due_date') %><%= calendar_for('issue_due_date') if @issue.leaf? %>

    +

    + <%= f.text_field(:due_date, :size => 10, :disabled => !@issue.leaf?, + :required => @issue.required_attribute?('due_date')) %> + <%= calendar_for('issue_due_date') if @issue.leaf? %> +

    <% end %> <% if @issue.safe_attribute? 'estimated_hours' %> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_form.html.erb --- a/app/views/issues/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -43,3 +43,5 @@ <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %> <% end %> + +<% heads_for_wiki_formatter %> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_form_custom_fields.html.erb --- a/app/views/issues/_form_custom_fields.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/_form_custom_fields.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,8 +1,9 @@ +<% custom_field_values = @issue.editable_custom_field_values %>
    <% i = 0 %> -<% split_on = (@issue.custom_field_values.size / 2.0).ceil - 1 %> -<% @issue.editable_custom_field_values.each do |value| %> +<% split_on = (custom_field_values.size / 2.0).ceil - 1 %> +<% custom_field_values.each do |value| %>

    <%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %>

    <% if i == split_on -%>
    diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_history.html.erb --- a/app/views/issues/_history.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/_history.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -8,7 +8,7 @@ <% if journal.details.any? %>
      - <% details_to_strings(journal.details).each do |string| %> + <% details_to_strings(journal.visible_details).each do |string| %>
    • <%= string %>
    • <% end %>
    diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_list.html.erb --- a/app/views/issues/_list.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/_list.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -9,7 +9,6 @@ :onclick => 'toggleIssuesSelection(this); return false;', :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> - <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %> <% query.inline_columns.each do |column| %> <%= column_header(column) %> <% end %> @@ -32,13 +31,12 @@ <% end %>
    "> - <%= raw query.inline_columns.map {|column| ""}.join %> <% @query.block_columns.each do |column| if (text = column_content(column, issue)) && text.present? -%> - + <% end -%> <% end -%> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_list_simple.html.erb --- a/app/views/issues/_list_simple.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/_list_simple.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -12,12 +12,12 @@ <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_relations.html.erb --- a/app/views/issues/_relations.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/_relations.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -10,22 +10,25 @@
    + <%= link_to_content_update("\xc2\xab " + l(:label_previous), params.merge(@gantt.params_previous)) %> + <%= link_to_content_update(l(:label_next) + " \xc2\xbb", params.merge(@gantt.params_next)) %>
    <%=h membership.project %><%= link_to_project membership.project %> <%=h membership.roles.sort.collect(&:to_s).join(', ') %> <%= form_for(:membership, :remote => true, diff -r d98d22a98252 -r afce8026aaeb app/views/groups/_users.html.erb --- a/app/views/groups/_users.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/groups/_users.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -22,22 +22,18 @@
    -<% users = User.active.not_in_group(@group).all(:limit => 100) %> -<% if users.any? %> <%= form_for(@group, :remote => true, :url => group_users_path(@group), :html => {:method => :post}) do |f| %>
    <%=l(:label_user_new)%>

    <%= label_tag "user_search", l(:label_user_search) %><%= text_field_tag 'user_search', nil %>

    - <%= javascript_tag "observeSearchfield('user_search', 'users', '#{ escape_javascript autocomplete_for_user_group_path(@group) }')" %> + <%= javascript_tag "observeSearchfield('user_search', null, '#{ escape_javascript autocomplete_for_user_group_path(@group) }')" %>
    - <%= principals_check_box_tags 'user_ids[]', users %> + <%= render_principals_for_new_group_users(@group) %>

    <%= submit_tag l(:button_add) %>

    <% end %> -<% end %> -
    diff -r d98d22a98252 -r afce8026aaeb app/views/groups/autocomplete_for_user.html.erb --- a/app/views/groups/autocomplete_for_user.html.erb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= principals_check_box_tags 'user_ids[]', @users %> diff -r d98d22a98252 -r afce8026aaeb app/views/groups/autocomplete_for_user.js.erb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/groups/autocomplete_for_user.js.erb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1 @@ +$('#users').html('<%= escape_javascript(render_principals_for_new_group_users(@group)) %>'); diff -r d98d22a98252 -r afce8026aaeb app/views/groups/edit.html.erb --- a/app/views/groups/edit.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/groups/edit.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,3 @@ -

    <%= link_to l(:label_group_plural), groups_path %> » <%= h(@group) %>

    +<%= title [l(:label_group_plural), groups_path], @group.name %> <%= render_tabs group_settings_tabs %> - -<% html_title(l(:label_group), @group, l(:label_administration)) -%> diff -r d98d22a98252 -r afce8026aaeb app/views/groups/index.html.erb --- a/app/views/groups/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/groups/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -2,7 +2,7 @@ <%= link_to l(:label_group_new), new_group_path, :class => 'icon icon-add' %> -

    <%= l(:label_group_plural) %>

    +<%= title l(:label_group_plural) %> <% if @groups.any? %> @@ -14,11 +14,12 @@ <% @groups.each do |group| %> - - + + <% end %> +
    <%= link_to h(group), edit_group_path(group) %><%= group.users.size %><%= link_to h(group), edit_group_path(group) %><%= group.users.size %> <%= delete_link group %>
    <% else %>

    <%= l(:label_no_data) %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/groups/new.html.erb --- a/app/views/groups/new.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/groups/new.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%= link_to l(:label_group_plural), groups_path %> » <%= l(:label_group_new) %>

    +<%= title [l(:label_group_plural), groups_path], l(:label_group_new) %> <%= labelled_form_for @group do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff -r d98d22a98252 -r afce8026aaeb app/views/groups/show.html.erb --- a/app/views/groups/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/groups/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%= link_to l(:label_group_plural), groups_path %> » <%=h @group %>

    +<%= title [l(:label_group_plural), groups_path], @group.name %>
      <% @group.users.each do |user| %> diff -r d98d22a98252 -r afce8026aaeb app/views/issue_relations/create.js.erb --- a/app/views/issue_relations/create.js.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issue_relations/create.js.erb Tue Sep 09 09:34:53 2014 +0100 @@ -4,3 +4,4 @@ $('#relation_issue_to_id').val(''); $('#relation_issue_to_id').focus(); <% end %> +$('#new-relation-form').show(); diff -r d98d22a98252 -r afce8026aaeb app/views/issue_statuses/edit.html.erb --- a/app/views/issue_statuses/edit.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issue_statuses/edit.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

      <%= link_to l(:label_issue_status_plural), issue_statuses_path %> » <%=h @issue_status %>

      +<%= title [l(:label_issue_status_plural), issue_statuses_path], @issue_status.name %> <%= labelled_form_for @issue_status do |f| %> <%= render :partial => 'form', :locals => {:f => f} %> diff -r d98d22a98252 -r afce8026aaeb app/views/issue_statuses/index.html.erb --- a/app/views/issue_statuses/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issue_statuses/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -19,13 +19,13 @@
    <%= link_to h(status.name), edit_issue_status_path(status) %><%= link_to h(status.name), edit_issue_status_path(status) %><%= h status.default_done_ratio %><%= h status.default_done_ratio %><%= checked_image status.is_default? %><%= checked_image status.is_closed? %><%= reorder_links('issue_status', {:action => 'update', :id => status}, :put) %><%= checked_image status.is_default? %><%= checked_image status.is_closed? %><%= reorder_links('issue_status', {:action => 'update', :id => status}, :put) %> <%= delete_link issue_status_path(status) %>
    <%= check_box_tag("ids[]", issue.id, false, :id => nil) %><%= link_to issue.id, issue_path(issue) %>#{column_content(column, issue)}
    <%= text %><%= text %>
    <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;', :id => nil) %> - <%= link_to(h(issue.id), :controller => 'issues', :action => 'show', :id => issue) %> + <%= link_to issue.id, issue_path(issue) %> <%= link_to_project(issue.project) %> <%=h issue.tracker %> - <%= link_to h(truncate(issue.subject, :length => 60)), :controller => 'issues', :action => 'show', :id => issue %> (<%=h issue.status %>) + <%= link_to truncate(issue.subject, :length => 60), issue_path(issue) %> (<%=h issue.status %>)
    <% @relations.each do |relation| %> - - - - - - - - + <% other_issue = relation.other_issue(@issue) -%> + + + + + + + + <% end %>
    <%= check_box_tag("ids[]", relation.other_issue(@issue).id, false, :id => nil) %><%= l(relation.label_for(@issue)) %> <%= "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)})" if relation.delay && relation.delay != 0 %> - <%= h(relation.other_issue(@issue).project) + ' - ' if Setting.cross_project_issue_relations? %> - <%= link_to_issue(relation.other_issue(@issue), :truncate => 60) %> -<%=h relation.other_issue(@issue).status.name %><%= format_date(relation.other_issue(@issue).start_date) %><%= format_date(relation.other_issue(@issue).due_date) %><%= link_to image_tag('link_break.png'), - {:controller => 'issue_relations', :action => 'destroy', :id => relation}, - :remote => true, - :method => :delete, - :data => {:confirm => l(:text_are_you_sure)}, - :title => l(:label_relation_delete) if User.current.allowed_to?(:manage_issue_relations, @project) %>
    <%= check_box_tag("ids[]", other_issue.id, false, :id => nil) %> + <%= l(relation.label_for(@issue)) %> + <%= "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)})" if relation.delay && relation.delay != 0 %> + <%= h(other_issue.project) + ' - ' if Setting.cross_project_issue_relations? %> + <%= link_to_issue(other_issue, :truncate => 60) %> + <%=h other_issue.status.name %><%= format_date(other_issue.start_date) %><%= format_date(other_issue.due_date) %><%= link_to image_tag('link_break.png'), + relation_path(relation), + :remote => true, + :method => :delete, + :data => {:confirm => l(:text_are_you_sure)}, + :title => l(:label_relation_delete) if User.current.allowed_to?(:manage_issue_relations, @project) %>
    @@ -33,9 +36,9 @@ <%= form_for @relation, { :as => :relation, :remote => true, - :url => {:controller => 'issue_relations', :action => 'create', :issue_id => @issue}, + :url => issue_relations_path(@issue), :method => :post, - :html => {:id => 'new-relation-form', :style => (@relation ? '' : 'display: none;')} + :html => {:id => 'new-relation-form', :style => 'display: none;'} } do |f| %> <%= render :partial => 'issue_relations/form', :locals => {:f => f}%> <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_sidebar.html.erb --- a/app/views/issues/_sidebar.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/_sidebar.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,16 +1,20 @@

    <%= l(:label_issue_plural) %>

    -<%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %>
    + +
      +
    • <%= link_to l(:label_issue_view_all), _project_issues_path(@project, :set_filter => 1) %>
    • <% if @project %> -<%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %>
      +
    • <%= link_to l(:field_summary), project_issues_report_path(@project) %>
    • <% end %> -<%= call_hook(:view_issues_sidebar_issues_bottom) %> <% if User.current.allowed_to?(:view_calendar, @project, :global => true) %> - <%= link_to(l(:label_calendar), :controller => 'calendars', :action => 'show', :project_id => @project) %>
      +
    • <%= link_to l(:label_calendar), _project_calendar_path(@project) %>
    • <% end %> <% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> - <%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %>
      +
    • <%= link_to l(:label_gantt), _project_gantt_path(@project) %>
    • <% end %> +
    + +<%= call_hook(:view_issues_sidebar_issues_bottom) %> <%= call_hook(:view_issues_sidebar_planning_bottom) %> <%= render_sidebar_queries %> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/_update_form.js.erb --- a/app/views/issues/_update_form.js.erb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -$('#all_attributes').html('<%= escape_javascript(render :partial => 'form') %>'); - -<% if User.current.allowed_to?(:log_time, @issue.project) %> - $('#log_time').show(); -<% else %> - $('#log_time').hide(); -<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/bulk_edit.html.erb --- a/app/views/issues/bulk_edit.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/bulk_edit.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,13 +1,28 @@

    <%= @copy ? l(:button_copy) : l(:label_bulk_edit_selected_issues) %>

    -
      <%= @issues.collect {|i| - content_tag('li', - link_to(h("#{i.tracker} ##{i.id}"), - { :action => 'show', :id => i } - ) + h(": #{i.subject}")) - }.join("\n").html_safe %>
    +<% if @saved_issues && @unsaved_issues.present? %> +
    + + <%= l(:notice_failed_to_save_issues, + :count => @unsaved_issues.size, + :total => @saved_issues.size, + :ids => @unsaved_issues.map {|i| "##{i.id}"}.join(', ')) %> + +
      + <% bulk_edit_error_messages(@unsaved_issues).each do |message| %> +
    • <%= message %>
    • + <% end %> +
    +
    +<% end %> -<%= form_tag({:action => 'bulk_update'}, :id => 'bulk_edit_form') do %> +
      +<% @issues.each do |issue| %> + <%= content_tag 'li', link_to_issue(issue) %> +<% end %> +
    + +<%= form_tag(bulk_update_issues_path, :id => 'bulk_edit_form') do %> <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join("\n").html_safe %>
    @@ -17,34 +32,43 @@ <% if @allowed_projects.present? %>

    - <%= select_tag('issue[project_id]', content_tag('option', l(:label_no_change_option), :value => '') + project_tree_options_for_select(@allowed_projects, :selected => @target_project), + <%= select_tag('issue[project_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + project_tree_options_for_select(@allowed_projects, :selected => @target_project), :onchange => "updateBulkEditFrom('#{escape_javascript url_for(:action => 'bulk_edit', :format => 'js')}')") %>

    <% end %>

    - <%= select_tag('issue[tracker_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@trackers, :id, :name)) %> + <%= select_tag('issue[tracker_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + options_from_collection_for_select(@trackers, :id, :name, @issue_params[:tracker_id])) %>

    <% if @available_statuses.any? %>

    - <%= select_tag('issue[status_id]',content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@available_statuses, :id, :name)) %> + <%= select_tag('issue[status_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + options_from_collection_for_select(@available_statuses, :id, :name, @issue_params[:status_id])) %>

    <% end %> <% if @safe_attributes.include?('priority_id') -%>

    - <%= select_tag('issue[priority_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(IssuePriority.active, :id, :name)) %> + <%= select_tag('issue[priority_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + options_from_collection_for_select(IssuePriority.active, :id, :name, @issue_params[:priority_id])) %>

    <% end %> <% if @safe_attributes.include?('assigned_to_id') -%>

    - <%= select_tag('issue[assigned_to_id]', content_tag('option', l(:label_no_change_option), :value => '') + - content_tag('option', l(:label_nobody), :value => 'none') + - principals_options_for_select(@assignables)) %> + <%= select_tag('issue[assigned_to_id]', + content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:label_nobody), :value => 'none', :selected => (@issue_params[:assigned_to_id] == 'none')) + + principals_options_for_select(@assignables, @issue_params[:assigned_to_id])) %>

    <% end %> @@ -52,8 +76,8 @@

    <%= select_tag('issue[category_id]', content_tag('option', l(:label_no_change_option), :value => '') + - content_tag('option', l(:label_none), :value => 'none') + - options_from_collection_for_select(@categories, :id, :name)) %> + content_tag('option', l(:label_none), :value => 'none', :selected => (@issue_params[:category_id] == 'none')) + + options_from_collection_for_select(@categories, :id, :name, @issue_params[:category_id])) %>

    <% end %> @@ -61,26 +85,31 @@

    <%= select_tag('issue[fixed_version_id]', content_tag('option', l(:label_no_change_option), :value => '') + - content_tag('option', l(:label_none), :value => 'none') + - version_options_for_select(@versions.sort)) %> + content_tag('option', l(:label_none), :value => 'none', :selected => (@issue_params[:fixed_version_id] == 'none')) + + version_options_for_select(@versions.sort, @issue_params[:fixed_version_id])) %>

    <% end %> <% @custom_fields.each do |custom_field| %> -

    <%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects) %>

    +

    + + <%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects, @issue_params[:custom_field_values][custom_field.id.to_s]) %> +

    <% end %> <% if @copy && @attachments_present %> +<%= hidden_field_tag 'copy_attachments', '0' %>

    - <%= check_box_tag 'copy_attachments', '1', true %> + <%= check_box_tag 'copy_attachments', '1', params[:copy_attachments] != '0' %>

    <% end %> <% if @copy && @subtasks_present %> +<%= hidden_field_tag 'copy_subtasks', '0' %>

    - <%= check_box_tag 'copy_subtasks', '1', true %> + <%= check_box_tag 'copy_subtasks', '1', params[:copy_subtasks] != '0' %>

    <% end %> @@ -92,15 +121,16 @@

    <%= select_tag('issue[is_private]', content_tag('option', l(:label_no_change_option), :value => '') + - content_tag('option', l(:general_text_Yes), :value => '1') + - content_tag('option', l(:general_text_No), :value => '0')) %> + content_tag('option', l(:general_text_Yes), :value => '1', :selected => (@issue_params[:is_private] == '1')) + + content_tag('option', l(:general_text_No), :value => '0', :selected => (@issue_params[:is_private] == '0'))) %>

    <% end %> <% if @safe_attributes.include?('parent_issue_id') && @project %>

    - <%= text_field_tag 'issue[parent_issue_id]', '', :size => 10 %> + <%= text_field_tag 'issue[parent_issue_id]', '', :size => 10, :value => @issue_params[:parent_issue_id] %> +

    <%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @project)}')" %> <% end %> @@ -108,44 +138,61 @@ <% if @safe_attributes.include?('start_date') %>

    - <%= text_field_tag 'issue[start_date]', '', :size => 10 %><%= calendar_for('issue_start_date') %> + <%= text_field_tag 'issue[start_date]', '', :value => @issue_params[:start_date], :size => 10 %><%= calendar_for('issue_start_date') %> +

    <% end %> <% if @safe_attributes.include?('due_date') %>

    - <%= text_field_tag 'issue[due_date]', '', :size => 10 %><%= calendar_for('issue_due_date') %> + <%= text_field_tag 'issue[due_date]', '', :value => @issue_params[:due_date], :size => 10 %><%= calendar_for('issue_due_date') %> +

    <% end %> <% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>

    - <%= select_tag 'issue[done_ratio]', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %> + <%= select_tag 'issue[done_ratio]', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }, @issue_params[:done_ratio]) %>

    <% end %>
    - -
    <%= l(:field_notes) %> +
    +<%= l(:field_notes) %> <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %> <%= wikitoolbar_for 'notes' %>

    - <% if @copy %> - <%= hidden_field_tag 'copy', '1' %> - <%= submit_tag l(:button_copy) %> - <%= submit_tag l(:button_copy_and_follow), :name => 'follow' %> - <% elsif @target_project %> - <%= submit_tag l(:button_move) %> - <%= submit_tag l(:button_move_and_follow), :name => 'follow' %> - <% else %> - <%= submit_tag l(:button_submit) %> - <% end %> + <% if @copy %> + <%= hidden_field_tag 'copy', '1' %> + <%= submit_tag l(:button_copy) %> + <%= submit_tag l(:button_copy_and_follow), :name => 'follow' %> + <% elsif @target_project %> + <%= submit_tag l(:button_move) %> + <%= submit_tag l(:button_move_and_follow), :name => 'follow' %> + <% else %> + <%= submit_tag l(:button_submit) %> + <% end %>

    <% end %> + +<%= javascript_tag do %> +$(window).load(function(){ + $(document).on('change', 'input[data-disables]', function(){ + if ($(this).attr('checked')){ + $($(this).data('disables')).attr('disabled', true).val(''); + } else { + $($(this).data('disables')).attr('disabled', false); + } + }); +}); +$(document).ready(function(){ + $('input[data-disables]').trigger('change'); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/index.api.rsb --- a/app/views/issues/index.api.rsb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/index.api.rsb Tue Sep 09 09:34:53 2014 +0100 @@ -19,10 +19,11 @@ api.done_ratio issue.done_ratio api.estimated_hours issue.estimated_hours - render_api_custom_values issue.custom_field_values, api + render_api_custom_values issue.visible_custom_field_values, api api.created_on issue.created_on api.updated_on issue.updated_on + api.closed_on issue.closed_on api.array :relations do issue.relations.each do |relation| diff -r d98d22a98252 -r afce8026aaeb app/views/issues/index.html.erb --- a/app/views/issues/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -24,7 +24,7 @@ - + diff -r d98d22a98252 -r afce8026aaeb app/views/issues/new.html.erb --- a/app/views/issues/new.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/new.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%=l(:label_issue_new)%>

    +<%= title l(:label_issue_new) %> <%= call_hook(:view_issues_new_top, {:issue => @issue}) %> diff -r d98d22a98252 -r afce8026aaeb app/views/issues/show.api.rsb --- a/app/views/issues/show.api.rsb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/show.api.rsb Tue Sep 09 09:34:53 2014 +0100 @@ -18,10 +18,11 @@ api.estimated_hours @issue.estimated_hours api.spent_hours(@issue.spent_hours) if User.current.allowed_to?(:view_time_entries, @project) - render_api_custom_values @issue.custom_field_values, api + render_api_custom_values @issue.visible_custom_field_values, api api.created_on @issue.created_on api.updated_on @issue.updated_on + api.closed_on @issue.closed_on render_api_issue_children(@issue, api) if include_in_api_response?('children') @@ -54,7 +55,7 @@ api.notes journal.notes api.created_on journal.created_on api.array :details do - journal.details.each do |detail| + journal.visible_details.each do |detail| api.detail :property => detail.property, :name => detail.prop_key do api.old_value detail.old_value api.new_value detail.value @@ -64,4 +65,10 @@ end end end if include_in_api_response?('journals') + + api.array :watchers do + @issue.watcher_users.each do |user| + api.user :id => user.id, :name => user.name + end + end if include_in_api_response?('watchers') && User.current.allowed_to?(:view_issue_watchers, @issue.project) end diff -r d98d22a98252 -r afce8026aaeb app/views/issues/show.html.erb --- a/app/views/issues/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/issues/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -61,7 +61,7 @@ end end if User.current.allowed_to?(:view_time_entries, @project) - rows.right l(:label_spent_time), (@issue.total_spent_hours > 0 ? (link_to l_hours(@issue.total_spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-"), :class => 'spent-time' + rows.right l(:label_spent_time), (@issue.total_spent_hours > 0 ? link_to(l_hours(@issue.total_spent_hours), project_issue_time_entries_path(@project, @issue)) : "-"), :class => 'spent-time' end end %> <%= render_custom_fields_rows(@issue) %> @@ -73,11 +73,7 @@ <% if @issue.description? %>
    - <%= link_to l(:button_quote), - {:controller => 'journals', :action => 'new', :id => @issue}, - :remote => true, - :method => 'post', - :class => 'icon icon-comment' if authorize_for('issues', 'edit') %> + <%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if authorize_for('issues', 'edit') %>

    <%=l(:field_description)%>

    @@ -130,7 +126,7 @@ <%= render :partial => 'action_menu' %>
    -<% if authorize_for('issues', 'edit') %> +<% if @issue.editable? %>
    - <% end %> - + <% @project.activities(true).each do |enumeration| %> <%= fields_for "enumerations[#{enumeration.id}]", enumeration do |ff| %> - - + <% enumeration.custom_field_values.each do |value| %> - <% end %> - diff -r d98d22a98252 -r afce8026aaeb app/views/projects/settings/_boards.html.erb --- a/app/views/projects/settings/_boards.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/projects/settings/_boards.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -10,9 +10,9 @@ <% Board.board_tree(@project.boards) do |board, level| next if board.new_record? %> - - - + + <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/projects/settings/_issue_categories.html.erb --- a/app/views/projects/settings/_issue_categories.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/projects/settings/_issue_categories.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -9,13 +9,13 @@ <% for category in @project.issue_categories %> <% unless category.new_record? %> - + <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/projects/settings/_members.html.erb --- a/app/views/projects/settings/_members.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/projects/settings/_members.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@ <%= error_messages_for 'member' %> <% roles = Role.find_all_givable - members = @project.member_principals.find(:all, :include => [:roles, :principal]).sort %> + members = @project.member_principals.includes(:member_roles, :roles, :principal).all.sort %>
    <% if members.any? %> @@ -15,22 +15,32 @@ <% members.each do |member| %> <% next if member.new_record? %>
    - + @@ -17,10 +17,10 @@ <%= ("" * level).html_safe %> - + <%= ("" * (criterias.length - level - 1)).html_safe -%> <% total = 0 -%> <% @report.periods.each do |period| -%> diff -r d98d22a98252 -r afce8026aaeb app/views/timelog/bulk_edit.html.erb --- a/app/views/timelog/bulk_edit.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/timelog/bulk_edit.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,13 +1,13 @@

    <%= l(:label_bulk_edit_selected_time_entries) %>

    -
      -<%= @time_entries.collect {|i| content_tag('li', - link_to(h("#{i.spent_on.strftime("%Y-%m-%d")} - #{i.project}: #{l(:label_f_hour_plural, :value => i.hours)}"), - { :action => 'edit', :id => i }) - )}.join("\n").html_safe %> +
        +<% @time_entries.each do |entry| %> + <%= content_tag 'li', + link_to("#{format_date(entry.spent_on)} - #{entry.project}: #{l(:label_f_hour_plural, :value => entry.hours)}", edit_time_entry_path(entry)) %> +<% end %>
      -<%= form_tag(:action => 'bulk_update') do %> +<%= form_tag(bulk_update_time_entries_path, :id => 'bulk_edit_form') do %> <%= @time_entries.collect {|i| hidden_field_tag('ids[]', i.id)}.join.html_safe %>
      diff -r d98d22a98252 -r afce8026aaeb app/views/timelog/index.html.erb --- a/app/views/timelog/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/timelog/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -13,7 +13,7 @@ <% end %>
      -

      <%= l(:label_total) %>: <%= html_hours(l_hours(@total_hours)) %>

      +

      <%= l(:label_total_time) %>: <%= html_hours(l_hours(@total_hours)) %>

      <% unless @entries.empty? %> @@ -22,8 +22,23 @@ <% other_formats_links do |f| %> <%= f.link_to 'Atom', :url => params.merge({:issue_id => @issue, :key => User.current.rss_key}) %> - <%= f.link_to 'CSV', :url => params %> + <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '330px'); return false;" %> <% end %> + + <% end %> <% html_title l(:label_spent_time), l(:label_details) %> diff -r d98d22a98252 -r afce8026aaeb app/views/timelog/report.html.erb --- a/app/views/timelog/report.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/timelog/report.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -30,7 +30,7 @@ <% unless @report.criteria.empty? %>
      -

      <%= l(:label_total) %>: <%= html_hours(l_hours(@report.total_hours)) %>

      +

      <%= l(:label_total_time) %>: <%= html_hours(l_hours(@report.total_hours)) %>

      <% unless @report.hours.empty? %> @@ -43,15 +43,15 @@ <% end %> <% columns_width = (40 / (@report.periods.length+1)).to_i %> <% @report.periods.each do |period| %> -
    + <% end %> - + <%= render :partial => 'report_criteria', :locals => {:criterias => @report.criteria, :hours => @report.hours, :level => 0} %> - + <%= ('' * (@report.criteria.size - 1)).html_safe %> <% total = 0 -%> <% @report.periods.each do |period| -%> diff -r d98d22a98252 -r afce8026aaeb app/views/trackers/_form.html.erb --- a/app/views/trackers/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/trackers/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -22,7 +22,7 @@ <% IssueCustomField.all.each do |field| %> <% end %> @@ -43,7 +43,7 @@ <% if @projects.any? %>
    <%= l(:label_project_plural) %> <%= render_project_nested_lists(@projects) do |p| - content_tag('label', check_box_tag('tracker[project_ids][]', p.id, @tracker.projects.include?(p), :id => nil) + ' ' + h(p)) + content_tag('label', check_box_tag('tracker[project_ids][]', p.id, @tracker.projects.to_a.include?(p), :id => nil) + ' ' + h(p)) end %> <%= hidden_field_tag('tracker[project_ids][]', '', :id => nil) %>

    <%= check_all_links 'tracker_project_ids' %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/trackers/edit.html.erb --- a/app/views/trackers/edit.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/trackers/edit.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%= link_to l(:label_tracker_plural), trackers_path %> » <%=h @tracker %>

    +<%= title [l(:label_tracker_plural), trackers_path], @tracker.name %> <%= labelled_form_for @tracker do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff -r d98d22a98252 -r afce8026aaeb app/views/trackers/fields.html.erb --- a/app/views/trackers/fields.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/trackers/fields.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ -

    <%= link_to l(:label_tracker_plural), trackers_path %> » <%= l(:field_summary) %>

    +<%= title [l(:label_tracker_plural), trackers_path], l(:field_summary) %> <% if @trackers.any? %> - <%= form_tag({}) do %> + <%= form_tag fields_trackers_path do %>
    <%= l(:field_column_names) %><%= render :partial => 'queries/columns', :locals => {:query => @query} %><%= render_query_columns_selection(@query) %>
    <%=h entry.project %> <%= h(' - ') + link_to_issue(entry.issue, :truncate => 50) if entry.issue %> <%=h entry.comments %> <%= html_hours("%.2f" % entry.hours) %> + <% if entry.editable_by?(@user) -%> <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry}, :title => l(:button_edit) %> diff -r d98d22a98252 -r afce8026aaeb app/views/my/destroy.html.erb --- a/app/views/my/destroy.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/my/destroy.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -2,10 +2,10 @@

    <%= simple_format l(:text_account_destroy_confirmation)%>

    - <%= form_tag({}) do %> + <%= form_tag({}) do %> <%= submit_tag l(:button_delete_my_account) %> | - <%= link_to l(:button_cancel), :action => 'account' %> - <% end %> + <%= link_to l(:button_cancel), :action => 'account' %> + <% end %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/my/password.html.erb --- a/app/views/my/password.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/my/password.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -17,6 +17,10 @@ <%= submit_tag l(:button_apply) %> <% end %> +<% unless @user.must_change_passwd? %> <% content_for :sidebar do %> <%= render :partial => 'sidebar' %> <% end %> +<% end %> + +<%= javascript_tag "$('#password').focus();" %> diff -r d98d22a98252 -r afce8026aaeb app/views/news/show.html.erb --- a/app/views/news/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/news/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@
    -<%= watcher_tag(@news, User.current) %> +<%= watcher_link(@news, User.current) %> <%= link_to(l(:button_edit), edit_news_path(@news), :class => 'icon icon-edit', diff -r d98d22a98252 -r afce8026aaeb app/views/previews/issue.html.erb --- a/app/views/previews/issue.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/previews/issue.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,11 +1,11 @@ <% if @notes %>
    <%= l(:field_notes) %> - <%= textilizable @notes, :attachments => @attachements, :object => @issue %> + <%= textilizable @notes, :attachments => @attachments, :object => @issue %>
    <% end %> <% if @description %>
    <%= l(:field_description) %> - <%= textilizable @description, :attachments => @attachements, :object => @issue %> + <%= textilizable @description, :attachments => @attachments, :object => @issue %>
    <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/projects/_form.html.erb --- a/app/views/projects/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/projects/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -7,16 +7,13 @@ <%= l(:text_project_name_info).html_safe %>

    -<% unless @project.allowed_parents.compact.empty? %> -

    <%= label(:project, :parent_id, l(:field_parent)) %><%= parent_project_select_tag(@project) %>

    -<% end %> - -

    <%= f.text_area :description, :rows => 5, :class => 'wiki-edit' %>

    -

    <%= f.text_field :identifier, :required => true, :size => 60, :disabled => @project.identifier_frozen? %> +

    <%= f.text_area :description, :rows => 8, :class => 'wiki-edit' %>

    +

    <%= f.text_field :identifier, :required => true, :size => 60, :disabled => @project.identifier_frozen?, :maxlength => Project::IDENTIFIER_MAX_LENGTH %> <% unless @project.identifier_frozen? %>
    <%= l(:text_length_between, :min => 1, :max => Project::IDENTIFIER_MAX_LENGTH) %> <%= l(:text_project_identifier_info).html_safe %> <% end %>

    +

    <%= f.text_field :homepage, :size => 60 %>
    <%= l(:text_project_homepage_info) %> @@ -36,6 +33,15 @@
    <%= l(:text_project_visibility_info) %>

    + +<% unless @project.allowed_parents.compact.empty? %> +

    <%= label(:project, :parent_id, l(:field_parent)) %><%= parent_project_select_tag(@project) %>

    +<% end %> + +<% if @project.safe_attribute? 'inherit_members' %> +

    <%= f.check_box :inherit_members %>

    +<% end %> + <%= wikitoolbar_for 'project_description' %> <% @project.custom_field_values.each do |value| %> @@ -53,7 +59,6 @@ <% end %> <%= hidden_field_tag 'project[enabled_module_names][]', '' %> -<%= javascript_tag 'observeProjectModules()' %> <% end %> @@ -88,3 +93,35 @@ <% end %> <% end %> + +<% unless @project.identifier_frozen? %> + <% content_for :header_tags do %> + <%= javascript_include_tag 'project_identifier' %> + <% end %> +<% end %> + +<% if !User.current.admin? && @project.inherit_members? && @project.parent && User.current.member_of?(@project.parent) %> + <%= javascript_tag do %> + $(document).ready(function() { + $("#project_inherit_members").change(function(){ + if (!$(this).is(':checked')) { + if (!confirm("<%= escape_javascript(l(:text_own_membership_delete_confirmation)) %>")) { + $("#project_inherit_members").attr("checked", true); + } + } + }); + }); + <% end %> +<% end %> + +<%= javascript_tag do %> +$(document).ready(function() { + $('#project_enabled_module_names_issue_tracking').on('change', function(){ + if ($(this).attr('checked')){ + $('#project_trackers, #project_issue_custom_fields').show(); + } else { + $('#project_trackers, #project_issue_custom_fields').hide(); + } + }).trigger('change'); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/projects/destroy.html.erb --- a/app/views/projects/destroy.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/projects/destroy.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,5 @@ -

    <%=l(:label_confirmation)%>

    +<%= title l(:label_confirmation) %> +

    <%=h @project_to_destroy %>
    <%=l(:text_project_destroy_confirmation)%> @@ -12,6 +13,7 @@ <%= form_tag(project_path(@project_to_destroy), :method => :delete) do %> <%= submit_tag l(:button_delete) %> + <%= link_to l(:button_cancel), :controller => 'admin', :action => 'projects' %> <% end %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/projects/new.html.erb --- a/app/views/projects/new.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/projects/new.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%=l(:label_project_new)%>

    +<%= title l(:label_project_new) %> <%= labelled_form_for @project do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff -r d98d22a98252 -r afce8026aaeb app/views/projects/settings/_activities.html.erb --- a/app/views/projects/settings/_activities.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/projects/settings/_activities.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -7,23 +7,23 @@ <% TimeEntryActivity.new.available_custom_fields.each do |value| %>
    <%= h value.name %><%= l(:field_active) %><%= l(:field_active) %>
    + <%= ff.hidden_field :parent_id, :value => enumeration.id unless enumeration.project %> <%= h(enumeration) %> <%= checked_image !enumeration.project %><%= checked_image !enumeration.project %> + <%= custom_field_tag "enumerations[#{enumeration.id}]", value %> + <%= ff.check_box :active %>
    <%= link_to board.name, project_board_path(@project, board) %><%=h board.description %> + <%= link_to board.name, project_board_path(@project, board) %><%=h board.description %> <% if authorize_for("boards", "edit") %> <%= reorder_links('board', {:controller => 'boards', :action => 'update', :project_id => @project, :id => board}, :put) %> <% end %> @@ -21,7 +21,7 @@ <% if User.current.allowed_to?(:manage_boards, @project) %> <%= link_to l(:button_edit), edit_project_board_path(@project, board), :class => 'icon icon-edit' %> <%= delete_link project_board_path(@project, board) %> - <% end %> + <% end %>
    <%=h(category.name) %><%=h(category.name) %> <%=h(category.assigned_to.name) if category.assigned_to %> - <% if User.current.allowed_to?(:manage_categories, @project) %> + <% if User.current.allowed_to?(:manage_categories, @project) %> <%= link_to l(:button_edit), edit_issue_category_path(category), :class => 'icon icon-edit' %> <%= delete_link issue_category_path(category) %> - <% end %> + <% end %>
    <%= link_to_user member.principal %><%= link_to_user member.principal %> - <%=h member.roles.sort.collect(&:to_s).join(', ') %> - <%= form_for(member, {:as => :membership, :remote => true, :url => membership_path(member), - :method => :put, - :html => { :id => "member-#{member.id}-roles-form", :class => 'hol' }} + <%= member.roles.sort.collect(&:to_s).join(', ') %> + <%= form_for(member, + {:as => :membership, :remote => true, + :url => membership_path(member), + :method => :put, + :html => { :id => "member-#{member.id}-roles-form", :class => 'hol' }} ) do |f| %> -

    <% roles.each do |role| %> -
    - <% end %>

    +

    + <% roles.each do |role| %> +
    + <% end %> +

    <%= hidden_field_tag 'membership[role_ids][]', '' %> -

    <%= submit_tag l(:button_change), :class => "small" %> - <%= link_to_function l(:button_cancel), - "$('#member-#{member.id}-roles').show(); $('#member-#{member.id}-roles-form').hide(); return false;" - %>

    +

    + <%= submit_tag l(:button_save), :class => "small" %> + <%= link_to_function(l(:button_cancel), + "$('#member-#{member.id}-roles').show(); $('#member-#{member.id}-roles-form').hide(); return false;") %> +

    <% end %>
    @@ -51,26 +61,28 @@ <% end %> -<% principals = Principal.active.not_member_of(@project).all(:limit => 100, :order => 'type, login, lastname ASC') %> -
    -<% if roles.any? && principals.any? %> - <%= form_for(@member, {:as => :membership, :url => project_memberships_path(@project), :remote => true, :method => :post}) do |f| %> -
    <%=l(:label_member_new)%> - -

    <%= label_tag "principal_search", l(:label_principal_search) %><%= text_field_tag 'principal_search', nil %>

    - <%= javascript_tag "observeSearchfield('principal_search', 'principals', '#{ escape_javascript autocomplete_project_memberships_path(@project) }')" %> - -
    - <%= principals_check_box_tags 'membership[user_ids][]', principals %> -
    - -

    <%= l(:label_role_plural) %>: - <% roles.each do |role| %> - - <% end %>

    - -

    <%= submit_tag l(:button_add), :id => 'member-add-submit' %>

    +<% if roles.any? %> + <%= form_for(@member, + {:as => :membership, :url => project_memberships_path(@project), + :remote => true, :method => :post}) do |f| %> +
    + <%=l(:label_member_new)%> +

    + <%= label_tag("principal_search", l(:label_principal_search)) %> + <%= text_field_tag('principal_search', nil) %> +

    + <%= javascript_tag "observeSearchfield('principal_search', null, '#{ escape_javascript autocomplete_project_memberships_path(@project, :format => 'js') }')" %> +
    + <%= render_principals_for_new_members(@project) %> +
    +

    + <%= l(:label_role_plural) %>: + <% roles.each do |role| %> + + <% end %> +

    +

    <%= submit_tag l(:button_add), :id => 'member-add-submit' %>

    <% end %> <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/projects/settings/_modules.html.erb --- a/app/views/projects/settings/_modules.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/projects/settings/_modules.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -3,8 +3,7 @@ :html => {:id => 'modules-form', :method => :post} do |f| %> -
    -
    +
    <%= l(:text_select_project_modules) %> <% Redmine::AccessControl.available_project_modules.each do |m| %> @@ -12,9 +11,8 @@ <%= l_or_humanize(m, :prefix => "project_module_") %>

    <% end %>
    -
    +

    <%= check_all_links 'modules-form' %>

    -

    <%= check_all_links 'modules-form' %>

    <%= submit_tag l(:button_save) %>

    <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/projects/show.html.erb --- a/app/views/projects/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/projects/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@
    <% if User.current.allowed_to?(:add_subprojects, @project) %> - <%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'new', :parent_id => @project}, :class => 'icon icon-add' %> + <%= link_to l(:label_subproject_new), new_project_path(:parent_id => @project), :class => 'icon icon-add' %> <% end %> <% if User.current.allowed_to?(:close_project, @project) %> <% if @project.active? %> @@ -57,7 +57,7 @@ <% end %> <% if @subprojects.any? %>
  • <%=l(:label_subproject_plural)%>: - <%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ").html_safe %>
  • + <%= @subprojects.collect{|p| link_to p, project_path(p)}.join(", ").html_safe %> <% end %> <% @project.visible_custom_field_values.each do |custom_value| %> <% if !custom_value.value.blank? %> @@ -72,21 +72,19 @@

    <%=l(:label_issue_tracking)%>

      <% for tracker in @trackers %> -
    • <%= link_to h(tracker.name), :controller => 'issues', :action => 'index', :project_id => @project, - :set_filter => 1, - "tracker_id" => tracker.id %>: +
    • <%= link_to h(tracker.name), project_issues_path(@project, :set_filter => 1, :tracker_id => tracker.id) %>: <%= l(:label_x_open_issues_abbr_on_total, :count => @open_issues_by_tracker[tracker].to_i, :total => @total_issues_by_tracker[tracker].to_i) %>
    • <% end %>

    - <%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %> + <%= link_to l(:label_issue_view_all), project_issues_path(@project, :set_filter => 1) %> <% if User.current.allowed_to?(:view_calendar, @project, :global => true) %> - | <%= link_to(l(:label_calendar), :controller => 'calendars', :action => 'show', :project_id => @project) %> + | <%= link_to l(:label_calendar), project_calendar_path(@project) %> <% end %> <% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> - | <%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %> + | <%= link_to l(:label_gantt), project_gantt_path(@project) %> <% end %>

    @@ -103,7 +101,7 @@

    <%=l(:label_news_latest)%>

    <%= render :partial => 'news/news', :collection => @news %> -

    <%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %>

    +

    <%= link_to l(:label_news_view_all), project_news_index_path(@project) %>

    <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/queries/_columns.html.erb --- a/app/views/queries/_columns.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/queries/_columns.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -4,7 +4,7 @@ <%= label_tag "available_columns", l(:description_available_columns) %>
    <%= select_tag 'available_columns', - options_for_select((query.available_inline_columns - query.columns).collect {|column| [column.caption, column.name]}), + options_for_select(query_available_inline_columns_options(query)), :multiple => true, :size => 10, :style => "width:150px", :ondblclick => "moveOptions(this.form.available_columns, this.form.selected_columns);" %>
    <%= label_tag "selected_columns", l(:description_selected_columns) %>
    - <%= select_tag((defined?(tag_name) ? tag_name : 'c[]'), - options_for_select(query.inline_columns.collect {|column| [column.caption, column.name]}), + <%= select_tag tag_name, + options_for_select(query_selected_inline_columns_options(query)), :id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px", - :ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);") %> + :ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);" %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/queries/_filters.html.erb --- a/app/views/queries/_filters.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/queries/_filters.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -3,7 +3,7 @@ var operatorByType = <%= raw_json Query.operators_by_filter_type %>; var availableFilters = <%= raw_json query.available_filters_as_json %>; var labelDayPlural = <%= raw_json l(:label_day_plural) %>; -var allProjects = <%= raw query.all_projects_values.to_json %>; +var allProjects = <%= raw_json query.all_projects_values %>; $(document).ready(function(){ initFilters(); <% query.filters.each do |field, options| %> diff -r d98d22a98252 -r afce8026aaeb app/views/queries/_form.html.erb --- a/app/views/queries/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/queries/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -2,19 +2,28 @@
    +<%= hidden_field_tag 'gantt', '1' if params[:gantt] %> +

    <%= text_field 'query', 'name', :size => 80 %>

    <% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @project) %> -

    -<%= check_box 'query', 'is_public', - :onchange => (User.current.admin? ? nil : 'if (this.checked) {$("#query_is_for_all").removeAttr("checked"); $("#query_is_for_all").attr("disabled", true);} else {$("#query_is_for_all").removeAttr("disabled");}') %>

    +

    + + + <% Role.givable.sorted.each do |role| %> + + <% end %> + + <%= hidden_field_tag 'query[role_ids][]', '' %> +

    <% end %>

    <%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %>

    +
    <%= l(:label_options) %>

    <%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', :onclick => 'if (this.checked) {$("#columns").hide();} else {$("#columns").show();}' %>

    @@ -24,6 +33,14 @@

    <%= available_block_columns_tags(@query) %>

    + +<% if params[:gantt] %> +

    + + +

    +<% end %> +
    <%= l(:label_filter_plural) %> @@ -49,7 +66,16 @@ <%= content_tag 'fieldset', :id => 'columns', :style => (query.has_default_columns? ? 'display:none;' : nil) do %> <%= l(:field_column_names) %> -<%= render :partial => 'queries/columns', :locals => {:query => query}%> +<%= render_query_columns_selection(query) %> <% end %>
    + +<%= javascript_tag do %> +$(document).ready(function(){ + $("input[name='query[visibility]']").change(function(){ + var checked = $('#query_visibility_1').is(':checked'); + $("input[name='query[role_ids][]'][type=checkbox]").attr('disabled', !checked); + }).trigger('change'); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/queries/index.api.rsb --- a/app/views/queries/index.api.rsb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/queries/index.api.rsb Tue Sep 09 09:34:53 2014 +0100 @@ -3,7 +3,7 @@ api.query do api.id query.id api.name query.name - api.is_public query.is_public + api.is_public query.is_public? api.project_id query.project_id end end diff -r d98d22a98252 -r afce8026aaeb app/views/queries/index.html.erb --- a/app/views/queries/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/queries/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -10,15 +10,13 @@ <% @queries.each do |query| %> - - diff -r d98d22a98252 -r afce8026aaeb app/views/reports/_details.html.erb --- a/app/views/reports/_details.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/reports/_details.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,50 +1,26 @@ <% if @statuses.empty? or rows.empty? %>

    <%=l(:label_no_data)%>

    <% else %> -<% col_width = 70 / (@statuses.length+3) %> -
    + <%= link_to h(query.name), :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %> - + <% if query.editable_by?(User.current) %> <%= link_to l(:button_edit), edit_query_path(query), :class => 'icon icon-edit' %> <%= delete_link query_path(query) %> - <% end %>
    +
    - + <% for status in @statuses %> - + <% end %> - - - + + + <% for row in rows %> "> - + <% for status in @statuses %> - + <% end %> - - - + + + <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/reports/_simple.html.erb --- a/app/views/reports/_simple.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/reports/_simple.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,38 +1,20 @@ <% if @statuses.empty? or rows.empty? %>

    <%=l(:label_no_data)%>

    <% else %> -
    <%=h status.name %><%=h status.name %><%=l(:label_open_issues_plural)%><%=l(:label_closed_issues_plural)%><%=l(:label_total)%><%=l(:label_open_issues_plural)%><%=l(:label_closed_issues_plural)%><%=l(:label_total)%>
    <%= link_to h(row.name), :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id %><%= link_to h(row.name), aggregate_path(@project, field_name, row) %><%= aggregate_link data, { field_name => row.id, "status_id" => status.id }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "status_id" => status.id, - "#{field_name}" => row.id %><%= aggregate_link data, { field_name => row.id, "status_id" => status.id }, aggregate_path(@project, field_name, row, :status_id => status.id) %><%= aggregate_link data, { field_name => row.id, "closed" => 0 }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "o" %><%= aggregate_link data, { field_name => row.id, "closed" => 1 }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "c" %><%= aggregate_link data, { field_name => row.id }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "*" %><%= aggregate_link data, { field_name => row.id, "closed" => 0 }, aggregate_path(@project, field_name, row, :status_id => "o") %><%= aggregate_link data, { field_name => row.id, "closed" => 1 }, aggregate_path(@project, field_name, row, :status_id => "c") %><%= aggregate_link data, { field_name => row.id }, aggregate_path(@project, field_name, row, :status_id => "*") %>
    +
    - - - - + + + + <% for row in rows %> "> - - - - + + + + <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/reports/issue_report.html.erb --- a/app/views/reports/issue_report.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/reports/issue_report.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,31 +1,31 @@

    <%=l(:label_report_plural)%>

    -

    <%=l(:field_tracker)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'tracker' %>

    +

    <%=l(:field_tracker)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'tracker') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %>
    -

    <%=l(:field_priority)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'priority' %>

    +

    <%=l(:field_priority)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'priority') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %>
    -

    <%=l(:field_assigned_to)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'assigned_to' %>

    +

    <%=l(:field_assigned_to)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'assigned_to') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_assigned_to, :field_name => "assigned_to_id", :rows => @assignees } %>
    -

    <%=l(:field_author)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'author' %>

    +

    <%=l(:field_author)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'author') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_author, :field_name => "author_id", :rows => @authors } %>
    <%= call_hook(:view_reports_issue_report_split_content_left, :project => @project) %>
    -

    <%=l(:field_version)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'version' %>

    +

    <%=l(:field_version)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'version') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_version, :field_name => "fixed_version_id", :rows => @versions } %>
    <% if @project.children.any? %> -

    <%=l(:field_subproject)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'subproject' %>

    +

    <%=l(:field_subproject)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'subproject') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_subproject, :field_name => "project_id", :rows => @subprojects } %>
    <% end %> -

    <%=l(:field_category)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'category' %>

    +

    <%=l(:field_category)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'category') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %>
    <%= call_hook(:view_reports_issue_report_split_content_right, :project => @project) %> diff -r d98d22a98252 -r afce8026aaeb app/views/reports/issue_report_details.html.erb --- a/app/views/reports/issue_report_details.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/reports/issue_report_details.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -3,5 +3,5 @@

    <%=@report_title%>

    <%= render :partial => 'details', :locals => { :data => @data, :field_name => @field, :rows => @rows } %>
    -<%= link_to l(:button_back), :action => 'issue_report' %> +<%= link_to l(:button_back), project_issues_report_path(@project) %> diff -r d98d22a98252 -r afce8026aaeb app/views/repositories/_breadcrumbs.html.erb --- a/app/views/repositories/_breadcrumbs.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/repositories/_breadcrumbs.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -<%= link_to(@repository.identifier.present? ? h(@repository.identifier) : 'root', +<%= link_to(@repository.identifier.present? ? h(@repository.identifier) : 'root', :action => 'show', :id => @project, :repository_id => @repository.identifier_param, :path => nil, :rev => @rev) %> diff -r d98d22a98252 -r afce8026aaeb app/views/repositories/_form.html.erb --- a/app/views/repositories/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/repositories/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -9,19 +9,23 @@

    <%= f.check_box :is_default, :label => :field_repository_is_default %>

    -

    <%= f.text_field :identifier, :disabled => @repository.identifier_frozen? %> +

    +<%= f.text_field :identifier, :disabled => @repository.identifier_frozen? %> <% unless @repository.identifier_frozen? %> - <%= l(:text_length_between, :min => 1, :max => Repository::IDENTIFIER_MAX_LENGTH) %> <%= l(:text_repository_identifier_info).html_safe %> -<% end %>

    + + <%= l(:text_length_between, :min => 1, :max => Repository::IDENTIFIER_MAX_LENGTH) %> <%= l(:text_repository_identifier_info).html_safe %> + +<% end %> +

    <% button_disabled = true %> <% if @repository %> -<% button_disabled = ! @repository.class.scm_available %> -<%= repository_field_tags(f, @repository)%> + <% button_disabled = ! @repository.class.scm_available %> + <%= repository_field_tags(f, @repository) %> <% end %>

    <%= submit_tag(@repository.new_record? ? l(:button_create) : l(:button_save), :disabled => button_disabled) %> - <%= link_to l(:button_cancel), settings_project_path(@project, :tab => 'repositories') %> + <%= link_to l(:button_cancel), settings_project_path(@project, :tab => 'repositories') %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/repositories/_navigation.html.erb --- a/app/views/repositories/_navigation.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/repositories/_navigation.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -32,7 +32,7 @@ <% if @repository.supports_all_revisions? %> | <%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 8 %> - <% end %> + <% end %> <% end -%> diff -r d98d22a98252 -r afce8026aaeb app/views/repositories/annotate.html.erb --- a/app/views/repositories/annotate.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/repositories/annotate.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -16,11 +16,21 @@ <% line_num = 1; previous_revision = nil %> <% syntax_highlight_lines(@path, Redmine::CodesetUtil.to_utf8_by_setting(@annotate.content)).each do |line| %> <% revision = @annotate.revisions[line_num - 1] %> - - + + - + <% if revision && revision != previous_revision %> + <%= revision.identifier ? + link_to_revision(revision, @repository) : format_revision(revision) %> + <% end %> + + <% line_num += 1; previous_revision = revision %> diff -r d98d22a98252 -r afce8026aaeb app/views/repositories/revision.html.erb --- a/app/views/repositories/revision.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/repositories/revision.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -99,4 +99,8 @@ <%= stylesheet_link_tag "scm" %> <% end %> -<% html_title("#{l(:label_revision)} #{format_revision(@changeset)}") -%> +<% + title = "#{l(:label_revision)} #{format_revision(@changeset)}" + title << " - #{truncate(@changeset.comments, :length => 80)}" + html_title(title) + -%> diff -r d98d22a98252 -r afce8026aaeb app/views/repositories/stats.html.erb --- a/app/views/repositories/stats.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/repositories/stats.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,10 +1,18 @@

    <%= l(:label_statistics) %>

    -<%= tag("embed", :width => 800, :height => 300, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :repository_id => @repository.identifier_param, :graph => "commits_per_month")) %> +<%= tag("embed", + :type => "image/svg+xml", :src => url_for(:controller => 'repositories', + :action => 'graph', :id => @project, + :repository_id => @repository.identifier_param, + :graph => "commits_per_month")) %>

    -<%= tag("embed", :width => 800, :height => 400, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :repository_id => @repository.identifier_param, :graph => "commits_per_author")) %> +<%= tag("embed", + :type => "image/svg+xml", :src => url_for(:controller => 'repositories', + :action => 'graph', :id => @project, + :repository_id => @repository.identifier_param, + :graph => "commits_per_author")) %>

    <%= link_to l(:button_back), :action => 'show', :id => @project %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/roles/edit.html.erb --- a/app/views/roles/edit.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/roles/edit.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%= link_to l(:label_role_plural), roles_path %> » <%=h @role.name %>

    +<%= title [l(:label_role_plural), roles_path], @role.name %> <%= labelled_form_for @role do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff -r d98d22a98252 -r afce8026aaeb app/views/roles/index.html.erb --- a/app/views/roles/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/roles/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@
    <%= link_to l(:label_role_new), new_role_path, :class => 'icon icon-add' %> -<%= link_to l(:label_permissions_report), {:action => 'permissions'}, :class => 'icon icon-summary' %> +<%= link_to l(:label_permissions_report), permissions_roles_path, :class => 'icon icon-summary' %>

    <%=l(:label_role_plural)%>

    @@ -14,8 +14,8 @@ <% for role in @roles %> "> - - + - <% @roles.each do |role| %> - - - + <% end %> + + + <% end %>
    <%=l(:label_open_issues_plural)%><%=l(:label_closed_issues_plural)%><%=l(:label_total)%><%=l(:label_open_issues_plural)%><%=l(:label_closed_issues_plural)%><%=l(:label_total)%>
    <%= link_to h(row.name), :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id %><%= aggregate_link data, { field_name => row.id, "closed" => 0 }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "o" %><%= aggregate_link data, { field_name => row.id, "closed" => 1 }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "c" %><%= aggregate_link data, { field_name => row.id }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "*" %><%= link_to h(row.name), aggregate_path(@project, field_name, row) %><%= aggregate_link data, { field_name => row.id, "closed" => 0 }, aggregate_path(@project, field_name, row, :status_id => "o") %><%= aggregate_link data, { field_name => row.id, "closed" => 1 }, aggregate_path(@project, field_name, row, :status_id => "c") %><%= aggregate_link data, { field_name => row.id }, aggregate_path(@project, field_name, row, :status_id => "*") %>
    <%= line_num %>
    <%= line_num %> - <%= (revision.identifier ? link_to_revision(revision, @repository) : format_revision(revision)) if revision && revision != previous_revision %><%= h(revision.author.to_s.split('<').first) if revision && revision != previous_revision %> + <% if revision && revision != previous_revision %> + <% author = Redmine::CodesetUtil.to_utf8(revision.author.to_s, + @repository.repo_log_encoding) %> + <%= author.split('<').first %> + <% end %> +
    <%= line.html_safe %>
    <%= content_tag(role.builtin? ? 'em' : 'span', link_to(h(role.name), edit_role_path(role))) %> + <%= content_tag(role.builtin? ? 'em' : 'span', link_to(h(role.name), edit_role_path(role))) %> <% unless role.builtin? %> <%= reorder_links('role', {:action => 'update', :id => role}, :put) %> <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/roles/new.html.erb --- a/app/views/roles/new.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/roles/new.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%= link_to l(:label_role_plural), roles_path %> » <%=l(:label_role_new)%>

    +<%= title [l(:label_role_plural), roles_path], l(:label_role_new) %> <%= labelled_form_for @role do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff -r d98d22a98252 -r afce8026aaeb app/views/roles/permissions.html.erb --- a/app/views/roles/permissions.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/roles/permissions.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%= link_to l(:label_role_plural), roles_path %> » <%=l(:label_permissions_report)%>

    +<%= title [l(:label_role_plural), roles_path], l(:label_permissions_report) %> <%= form_tag(permissions_roles_path, :id => 'permissions_form') do %> <%= hidden_field_tag 'permissions[0]', '', :id => nil %> @@ -32,13 +32,13 @@ <% end %> <% perms_by_module[mod].each do |permission| %>
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('.permission-#{permission.name} input')", :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> <%= l_or_humanize(permission.name, :prefix => 'permission_') %> + <% if role.setable_permissions.include? permission %> <%= check_box_tag "permissions[#{role.id}][]", permission.name, (role.permissions.include? permission.name), :id => nil, :class => "role-#{role.id}" %> <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/search/index.html.erb --- a/app/views/search/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/search/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@

    <%= l(:label_search) %>

    -<%= form_tag({}, :method => :get) do %> +<%= form_tag({}, :method => :get, :id => 'search-form') do %> <%= label_tag "search-input", l(:description_search), :class => "hidden-for-sighted" %>

    <%= text_field_tag 'q', @question, :size => 60, :id => 'search-input' %> <%= javascript_tag "$('#search-input').focus()" %> @@ -11,13 +11,14 @@ <%= hidden_field_tag 'titles_only', '', :id => nil %>

    -

    + +

    <% @object_types.each do |t| %> - + <% end %>

    -

    <%= submit_tag l(:button_submit), :name => 'submit' %>

    +

    <%= submit_tag l(:button_submit) %>

    <% end %>
    @@ -46,7 +47,7 @@ <% end %> -

    +

    <% if @pagination_previous_date %> <%= link_to_content_update("\xc2\xab " + l(:label_previous), params.merge(:previous => 1, @@ -57,6 +58,17 @@ params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %> <% end %> -

    +

    <% html_title(l(:label_search)) -%> + +<%= javascript_tag do %> +$("#search-types a").click(function(e){ + e.preventDefault(); + $("#search-types input[type=checkbox]").attr('checked', false); + $(this).siblings("input[type=checkbox]").attr('checked', true); + if ($("#search-input").val() != "") { + $("#search-form").submit(); + } +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/settings/_authentication.html.erb --- a/app/views/settings/_authentication.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/settings/_authentication.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -19,6 +19,8 @@

    <%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %>

    <%= setting_check_box :rest_api_enabled %>

    + +

    <%= setting_check_box :jsonp_enabled %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/settings/_issues.html.erb --- a/app/views/settings/_issues.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/settings/_issues.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -21,12 +21,10 @@
    - <%= l(:setting_issue_list_default_columns) %> - <%= render :partial => 'queries/columns', - :locals => { - :query => Query.new(:column_names => Setting.issue_list_default_columns), - :tag_name => 'settings[issue_list_default_columns][]' - } %> + <%= l(:setting_issue_list_default_columns) %> + <%= render_query_columns_selection( + IssueQuery.new(:column_names => Setting.issue_list_default_columns), + :name => 'settings[issue_list_default_columns]') %>
    <%= submit_tag l(:button_save) %> diff -r d98d22a98252 -r afce8026aaeb app/views/settings/_mail_handler.html.erb --- a/app/views/settings/_mail_handler.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/settings/_mail_handler.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -5,6 +5,11 @@ <%= setting_text_area :mail_handler_body_delimiters, :rows => 5 %> <%= l(:text_line_separated) %>

    +

    + <%= setting_text_field :mail_handler_excluded_filenames, :size => 60 %> + <%= l(:text_comma_separated) %> + <%= l(:label_example) %>: smime.p7s, *.vcf +

    diff -r d98d22a98252 -r afce8026aaeb app/views/settings/_projects.html.erb --- a/app/views/settings/_projects.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/settings/_projects.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -6,6 +6,9 @@

    <%= setting_multiselect(:default_projects_modules, Redmine::AccessControl.available_project_modules.collect {|m| [l_or_humanize(m, :prefix => "project_module_"), m.to_s]}) %>

    +

    <%= setting_multiselect(:default_projects_tracker_ids, + Tracker.sorted.all.collect {|t| [t.name, t.id.to_s]}) %>

    +

    <%= setting_check_box :sequential_project_identifiers %>

    <%= setting_select :new_project_user_role_id, diff -r d98d22a98252 -r afce8026aaeb app/views/settings/_repositories.html.erb --- a/app/views/settings/_repositories.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/settings/_repositories.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -13,29 +13,29 @@ <% scm_class = "Repository::#{choice}".constantize %> <% text, value = (choice.is_a?(Array) ? choice : [choice, choice]) %> <% setting = :enabled_scm %> - <% enabled = Setting.send(setting).include?(value) %> + <% enabled = Setting.send(setting).include?(value) %>

    - <% if enabled %> - <%= - image_tag( + <% if enabled %> + <%= + image_tag( (scm_class.scm_available ? 'true.png' : 'exclamation.png'), :style => "vertical-align:bottom;" - ) + ) %> <%= scm_class.scm_command %> - <% end %> - - <%= scm_class.scm_version_string if enabled %> -
    + <%= scm_class.scm_version_string if enabled %> +

    <%= l(:text_scm_config) %>

    @@ -65,19 +65,6 @@

    <%= setting_text_field :commit_ref_keywords, :size => 30 %> <%= l(:text_comma_separated) %>

    -

    <%= setting_text_field :commit_fix_keywords, :size => 30 %> - <%= l(:label_applied_status) %>: <%= setting_select :commit_fix_status_id, - [["", 0]] + - IssueStatus.find(:all).collect{ - |status| [status.name, status.id.to_s] - }, - :label => false %> - <%= l(:field_done_ratio) %>: <%= setting_select :commit_fix_done_ratio, - (0..10).to_a.collect {|r| ["#{r*10} %", "#{r*10}"] }, - :blank => :label_no_change_option, - :label => false %> -<%= l(:text_comma_separated) %>

    -

    <%= setting_check_box :commit_cross_project_ref %>

    <%= setting_check_box :commit_logtime_enabled, @@ -90,5 +77,51 @@ :disabled => !Setting.commit_logtime_enabled?%>

    -<%= submit_tag l(:button_save) %> + + + + + + + + + + + + <% @commit_update_keywords.each do |rule| %> + + + + + + + + <% end %> + + + + + + + + +
    <%= l(:label_tracker) %><%= l(:setting_commit_fix_keywords) %><%= l(:label_applied_status) %><%= l(:field_done_ratio) %>
    <%= select_tag "settings[commit_update_keywords][if_tracker_id][]", options_for_select([[l(:label_all), ""]] + Tracker.sorted.all.map {|t| [t.name, t.id.to_s]}, rule['if_tracker_id']) %><%= text_field_tag "settings[commit_update_keywords][keywords][]", rule['keywords'], :size => 30 %><%= select_tag "settings[commit_update_keywords][status_id][]", options_for_select([["", 0]] + IssueStatus.sorted.all.collect{|status| [status.name, status.id.to_s]}, rule['status_id']) %><%= select_tag "settings[commit_update_keywords][done_ratio][]", options_for_select([["", ""]] + (0..10).to_a.collect {|r| ["#{r*10} %", "#{r*10}"] }, rule['done_ratio']) %><%= link_to image_tag('delete.png'), '#', :class => 'delete-commit-keywords' %>
    <%= l(:text_comma_separated) %><%= link_to image_tag('add.png'), '#', :class => 'add-commit-keywords' %>
    + +

    <%= submit_tag l(:button_save) %>

    <% end %> + +<%= javascript_tag do %> +$('#commit-keywords').on('click', 'a.delete-commit-keywords', function(e){ + e.preventDefault(); + if ($('#commit-keywords tbody tr.commit-keywords').length > 1) { + $(this).parents('#commit-keywords tr').remove(); + } else { + $('#commit-keywords tbody tr.commit-keywords').find('input, select').val(''); + } +}); +$('#commit-keywords').on('click', 'a.add-commit-keywords', function(e){ + e.preventDefault(); + var row = $('#commit-keywords tr.commit-keywords:last'); + row.clone().insertAfter(row).find('input, select').val(''); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/settings/plugin.html.erb --- a/app/views/settings/plugin.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/settings/plugin.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,8 +1,8 @@ -

    <%= l(:label_settings) %>: <%=h @plugin.name %>

    +<%= title [l(:label_plugins), {:controller => 'admin', :action => 'plugins'}], @plugin.name %>
    <%= form_tag({:action => 'plugin'}) do %> -
    +
    <%= render :partial => @partial, :locals => {:settings => @settings}%>
    <%= submit_tag l(:button_apply) %> diff -r d98d22a98252 -r afce8026aaeb app/views/timelog/_date_range.html.erb --- a/app/views/timelog/_date_range.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/timelog/_date_range.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,42 +1,34 @@ -
    -<%= l(:label_date_range) %> -
    -

    -<%= label_tag "period_type_list", l(:description_date_range_list), :class => "hidden-for-sighted" %> -<%= radio_button_tag 'period_type', '1', !@free_period, :onclick => '$("#from,#to").attr("disabled", true);$("#period").removeAttr("disabled");', :id => "period_type_list"%> -<%= select_tag 'period', options_for_period_select(params[:period]), - :onchange => 'this.form.submit();', - :onfocus => '$("#period_type_1").attr("checked", true);', - :disabled => @free_period %> -

    -

    -<%= label_tag "period_type_interval", l(:description_date_range_interval), :class => "hidden-for-sighted" %> -<%= radio_button_tag 'period_type', '2', @free_period, :onclick => '$("#from,#to").removeAttr("disabled");$("#period").attr("disabled", true);', :id => "period_type_interval" %> -<%= l(:label_date_from_to, - :start => ((label_tag "from", l(:description_date_from), :class => "hidden-for-sighted") + - text_field_tag('from', @from, :size => 10, :disabled => !@free_period) + calendar_for('from')), - :end => ((label_tag "to", l(:description_date_to), :class => "hidden-for-sighted") + - text_field_tag('to', @to, :size => 10, :disabled => !@free_period) + calendar_for('to'))).html_safe %> -

    +
    +
    "> + <%= l(:label_filter_plural) %> +
    "> + <%= render :partial => 'queries/filters', :locals => {:query => @query} %> +
    +
    +
    -
    -

    - <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %> - <%= link_to l(:button_clear), {:controller => controller_name, :action => action_name, :project_id => @project, :issue_id => @issue}, :class => 'icon icon-reload' %> + +

    + <%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %> + <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue}, :class => 'icon icon-reload' %>

    -<% url_params = @free_period ? { :from => @from, :to => @to } : { :period => params[:period] } %> +<% query_params = params.slice(:f, :op, :v, :sort) %>
      -
    • <%= link_to(l(:label_details), url_params.merge({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue }), +
    • <%= link_to(l(:label_details), query_params.merge({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue }), :class => (action_name == 'index' ? 'selected' : nil)) %>
    • -
    • <%= link_to(l(:label_report), url_params.merge({:controller => 'timelog', :action => 'report', :project_id => @project, :issue_id => @issue}), +
    • <%= link_to(l(:label_report), query_params.merge({:controller => 'timelog', :action => 'report', :project_id => @project, :issue_id => @issue}), :class => (action_name == 'report' ? 'selected' : nil)) %>
    - -<%= javascript_tag do %> -$('#from, #to').change(function(){ - $('#period_type_interval').attr('checked', true); $('#from,#to').removeAttr('disabled'); $('#period').attr('disabled', true); -}); -<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/timelog/_form.html.erb --- a/app/views/timelog/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/timelog/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -2,22 +2,31 @@ <%= back_url_hidden_field_tag %>
    - <% if @time_entry.new_record? %> - <% if params[:project_id] || @time_entry.issue %> - <%= f.hidden_field :project_id %> - <% else %> -

    <%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).all, :selected => @time_entry.project), :required => true %>

    - <% end %> - <% end %> -

    <%= f.text_field :issue_id, :size => 6 %> <%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %>

    -

    <%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %>

    -

    <%= f.text_field :hours, :size => 6, :required => true %>

    -

    <%= f.text_field :comments, :size => 100, :maxlength => 255 %>

    -

    <%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %>

    - <% @time_entry.custom_field_values.each do |value| %> -

    <%= custom_field_tag_with_label :time_entry, value %>

    - <% end %> - <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %> + <% if @time_entry.new_record? %> + <% if params[:project_id] || @time_entry.issue %> + <%= f.hidden_field :project_id %> + <% else %> +

    <%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).all, :selected => @time_entry.project), :required => true %>

    + <% end %> + <% end %> +

    + <%= f.text_field :issue_id, :size => 6 %> + <%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %> +

    +

    <%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %>

    +

    <%= f.text_field :hours, :size => 6, :required => true %>

    +

    <%= f.text_field :comments, :size => 100, :maxlength => 255 %>

    +

    <%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %>

    + <% @time_entry.custom_field_values.each do |value| %> +

    <%= custom_field_tag_with_label :time_entry, value %>

    + <% end %> + <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %>
    -<%= javascript_tag "observeAutocompleteField('time_entry_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (@project ? nil : 'all'))}')" %> +<%= javascript_tag do %> + observeAutocompleteField('time_entry_issue_id', '<%= escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (@project ? nil : 'all'))%>', { + select: function(event, ui) { + $('#time_entry_issue').text(ui.item.label); + } + }); +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/timelog/_list.html.erb --- a/app/views/timelog/_list.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/timelog/_list.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -3,49 +3,35 @@
    - - -<%= sort_header_tag('spent_on', :caption => l(:label_date), :default_order => 'desc') %> -<%= sort_header_tag('user', :caption => l(:label_member)) %> -<%= sort_header_tag('activity', :caption => l(:label_activity)) %> -<%= sort_header_tag('project', :caption => l(:label_project)) %> -<%= sort_header_tag('issue', :caption => l(:label_issue), :default_order => 'desc') %> - -<%= sort_header_tag('hours', :caption => l(:field_hours)) %> - - + + + <% @query.inline_columns.each do |column| %> + <%= column_header(column) %> + <% end %> + + <% entries.each do |entry| -%> - hascontextmenu"> - - - - - - - - - - + hascontextmenu"> + + <%= raw @query.inline_columns.map {|column| ""}.join %> + + <% end -%>
    - <%= link_to image_tag('toggle_check.png'), - {}, - :onclick => 'toggleIssuesSelection(this); return false;', - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> -<%= l(:field_comments) %>
    + <%= link_to image_tag('toggle_check.png'), + {}, + :onclick => 'toggleIssuesSelection(this); return false;', + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> +
    <%= check_box_tag("ids[]", entry.id, false, :id => nil) %><%= format_date(entry.spent_on) %><%= link_to_user(entry.user) %><%=h entry.activity %><%= link_to_project(entry.project) %> -<% if entry.issue -%> -<%= entry.issue.visible? ? link_to_issue(entry.issue, :truncate => 50) : "##{entry.issue.id}" -%> -<% end -%> -<%=h entry.comments %><%= html_hours("%.2f" % entry.hours) %> -<% if entry.editable_by?(User.current) -%> - <%= link_to image_tag('edit.png'), edit_time_entry_path(entry), - :title => l(:button_edit) %> - <%= link_to image_tag('delete.png'), time_entry_path(entry), - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:button_delete) %> -<% end -%> -
    <%= check_box_tag("ids[]", entry.id, false, :id => nil) %>#{column_content(column, entry)} + <% if entry.editable_by?(User.current) -%> + <%= link_to image_tag('edit.png'), edit_time_entry_path(entry), + :title => l(:button_edit) %> + <%= link_to image_tag('delete.png'), time_entry_path(entry), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:button_delete) %> + <% end -%> +
    diff -r d98d22a98252 -r afce8026aaeb app/views/timelog/_report_criteria.html.erb --- a/app/views/timelog/_report_criteria.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/timelog/_report_criteria.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -3,7 +3,7 @@ <% next if hours_for_value.empty? -%>
    <%= h(format_criteria_value(@report.available_criteria[criterias[level]], value)) %><%= h(format_criteria_value(@report.available_criteria[criterias[level]], value)) %><%= period %><%= period %><%= l(:label_total) %><%= l(:label_total_time) %>
    <%= l(:label_total) %><%= l(:label_total_time) %>
    @@ -25,13 +25,13 @@ <% Tracker::CORE_FIELDS.each do |field| %> "> - <% @trackers.each do |tracker| %> - @@ -47,13 +47,13 @@ <% @custom_fields.each do |field| %> "> - <% @trackers.each do |tracker| %> - @@ -73,5 +73,3 @@ <% else %>

    <%= l(:label_no_data) %>

    <% end %> - -<% html_title l(:field_summary) %> diff -r d98d22a98252 -r afce8026aaeb app/views/trackers/index.html.erb --- a/app/views/trackers/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/trackers/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@
    <%= link_to l(:label_tracker_new), new_tracker_path, :class => 'icon icon-add' %> -<%= link_to l(:field_summary), {:action => 'fields'}, :class => 'icon icon-summary' %> +<%= link_to l(:field_summary), fields_trackers_path, :class => 'icon icon-summary' %>

    <%=l(:label_tracker_plural)%>

    @@ -15,9 +15,17 @@ <% for tracker in @trackers %> "> - - - + + + diff -r d98d22a98252 -r afce8026aaeb app/views/trackers/new.html.erb --- a/app/views/trackers/new.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/trackers/new.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%= link_to l(:label_tracker_plural), trackers_path %> » <%=l(:label_tracker_new)%>

    +<%= title [l(:label_tracker_plural), trackers_path], l(:label_tracker_new) %> <%= labelled_form_for @tracker do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff -r d98d22a98252 -r afce8026aaeb app/views/users/_form.html.erb --- a/app/views/users/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/users/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -62,6 +62,8 @@

    <%= f.password_field :password, :required => true, :size => 25 %> <%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

    <%= f.password_field :password_confirmation, :required => true, :size => 25 %>

    +

    <%= f.check_box :generate_password %>

    +

    <%= f.check_box :must_change_passwd %>

    @@ -75,8 +77,22 @@
    <%=l(:label_preferences)%> <%= render :partial => 'users/preferences' %> + <%= call_hook(:view_users_form_preferences, :user => @user, :form => f) %>
    + +<%= javascript_tag do %> +$(document).ready(function(){ + $('#user_generate_password').change(function(){ + var passwd = $('#user_password, #user_password_confirmation'); + if ($(this).is(':checked')){ + passwd.val('').attr('disabled', true); + }else{ + passwd.removeAttr('disabled'); + } + }).trigger('change'); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/users/_mail_notifications.html.erb --- a/app/views/users/_mail_notifications.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/users/_mail_notifications.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -11,17 +11,20 @@ <%= render_project_nested_lists(@user.projects) do |project| content_tag('label', check_box_tag( - 'notified_project_ids[]', + 'user[notified_project_ids][]', project.id, - @user.notified_projects_ids.include?(project.id) + @user.notified_projects_ids.include?(project.id), + :id => nil ) + ' ' + h(project.name) ) end %> + <%= hidden_field_tag 'user[notified_project_ids][]', '' %>

    <%= l(:text_user_mail_option) %>

    <% end %> + +<%= fields_for :pref, @user.pref do |pref_fields| %>

    - + <%= pref_fields.check_box :no_self_notified %> +

    +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/users/_memberships.html.erb --- a/app/views/users/_memberships.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/users/_memberships.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ <% roles = Role.find_all_givable %> -<% projects = Project.active.find(:all, :order => 'lft') %> +<% projects = Project.active.all %>
    <% if @user.memberships.any? %> diff -r d98d22a98252 -r afce8026aaeb app/views/users/edit.html.erb --- a/app/views/users/edit.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/users/edit.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -4,9 +4,6 @@ <%= delete_link user_path(@user) if User.current != @user %>
    -

    <%= link_to l(:label_user_plural), users_path %> » <%=h @user.login %>

    +<%= title [l(:label_user_plural), users_path], @user.login %> <%= render_tabs user_settings_tabs %> - -<% html_title(l(:label_user), @user.login, l(:label_administration)) -%> - diff -r d98d22a98252 -r afce8026aaeb app/views/users/index.html.erb --- a/app/views/users/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/users/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -4,7 +4,7 @@

    <%=l(:label_user_plural)%>

    -<%= form_tag({}, :method => :get) do %> +<%= form_tag(users_path, :method => :get) do %>
    <%= l(:label_filter_plural) %> <%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> @@ -41,9 +41,9 @@
    - - - + + + ]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.core-field-#{field}')", :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> <%= l("field_#{field}".sub(/_id$/, '')) %> + <%= check_box_tag "trackers[#{tracker.id}][core_fields][]", field, tracker.core_fields.include?(field), :class => "tracker-#{tracker.id} core-field-#{field}" %>
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.custom-field-#{field.id}')", :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> <%= field.name %> + <%= check_box_tag "trackers[#{tracker.id}][custom_field_ids][]", field.id, tracker.custom_fields.include?(field), :class => "tracker-#{tracker.id} custom-field-#{field.id}" %>
    <%= link_to h(tracker.name), edit_tracker_path(tracker) %><% unless tracker.workflow_rules.count > 0 %><%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), {:controller => 'workflows', :action => 'edit', :tracker_id => tracker} %>)<% end %><%= reorder_links('tracker', {:action => 'update', :id => tracker}, :put) %><%= link_to h(tracker.name), edit_tracker_path(tracker) %> + <% unless tracker.workflow_rules.count > 0 %> + + <%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), workflows_edit_path(:tracker_id => tracker) %>) + + <% end %> + + <%= reorder_links('tracker', {:action => 'update', :id => tracker}, :put) %> + <%= delete_link tracker_path(tracker) %> <%= h(user.firstname) %> <%= h(user.lastname) %> <%= checked_image user.admin? %><%= format_time(user.created_on) %><%= checked_image user.admin? %><%= format_time(user.created_on) %> <%= change_status_link(user) %> <%= delete_link user_path(user, :back_url => users_path(params)) unless User.current == user %> diff -r d98d22a98252 -r afce8026aaeb app/views/users/new.html.erb --- a/app/views/users/new.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/users/new.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -

    <%= link_to l(:label_user_plural), users_path %> » <%=l(:label_user_new)%>

    +<%= title [l(:label_user_plural), users_path], l(:label_user_new) %> <%= labelled_form_for @user do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> @@ -10,3 +10,21 @@ <%= submit_tag l(:button_create_and_continue), :name => 'continue' %>

    <% end %> + +<% if @auth_sources.present? && @auth_sources.any?(&:searchable?) %> + <%= javascript_tag do %> + observeAutocompleteField('user_login', '<%= escape_javascript autocomplete_for_new_user_auth_sources_path %>', { + select: function(event, ui) { + $('input#user_firstname').val(ui.item.firstname); + $('input#user_lastname').val(ui.item.lastname); + $('input#user_mail').val(ui.item.mail); + $('select#user_auth_source_id option').each(function(){ + if ($(this).attr('value') == ui.item.auth_source_id) { + $(this).attr('selected', true); + $('select#user_auth_source_id').trigger('change'); + } + }); + } + }); + <% end %> +<% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/users/show.api.rsb --- a/app/views/users/show.api.rsb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/users/show.api.rsb Tue Sep 09 09:34:53 2014 +0100 @@ -1,11 +1,13 @@ api.user do api.id @user.id - api.login @user.login if User.current.admin? + api.login @user.login if User.current.admin? || (User.current == @user) api.firstname @user.firstname api.lastname @user.lastname api.mail @user.mail if User.current.admin? || !@user.pref.hide_mail api.created_on @user.created_on api.last_login_on @user.last_login_on + api.api_key @user.api_key if User.current.admin? || (User.current == @user) + api.status @user.status if User.current.admin? render_api_custom_values @user.visible_custom_field_values, api diff -r d98d22a98252 -r afce8026aaeb app/views/users/show.html.erb --- a/app/views/users/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/users/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -47,7 +47,7 @@ :from => @events_by_day.keys.first %>

    -<%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %> +<%=l(:label_reported_issues)%>: <%= Issue.where(:author_id => @user.id).count %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/versions/_issue_counts.html.erb --- a/app/views/versions/_issue_counts.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/versions/_issue_counts.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -13,14 +13,14 @@ <% counts.each do |count| %> - - ]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
    ","
    "],thead:[1,"
    + <% if count[:group] -%> <%= link_to(h(count[:group]), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => count[:group])) %> <% else -%> <%= link_to(l(:label_none), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => "!*")) %> <% end %> + <%= progress_bar((count[:closed].to_f / count[:total])*100, :legend => "#{count[:closed]}/#{count[:total]}", :width => "#{(count[:total].to_f / max * 200).floor}px;") %> diff -r d98d22a98252 -r afce8026aaeb app/views/versions/_overview.html.erb --- a/app/views/versions/_overview.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/versions/_overview.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -16,16 +16,23 @@ <% end %> <% if version.issues_count > 0 %> - <%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %> + <%= progress_bar([version.closed_percent, version.completed_percent], + :width => '40em', :legend => ('%0.0f%' % version.completed_percent)) %>

    - <%= link_to(l(:label_x_issues, :count => version.issues_count), - project_issues_path(version.project, :status_id => '*', :fixed_version_id => version, :set_filter => 1)) %> -   - (<%= link_to_if(version.closed_issues_count > 0, l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), - project_issues_path(version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1)) %> + <%= link_to(l(:label_x_issues, :count => version.issues_count), + project_issues_path(version.project, + :status_id => '*', :fixed_version_id => version, + :set_filter => 1)) %> +   + (<%= link_to_if(version.closed_issues_count > 0, + l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), + project_issues_path(version.project, :status_id => 'c', + :fixed_version_id => version, :set_filter => 1)) %> — - <%= link_to_if(version.open_issues_count > 0, l(:label_x_open_issues_abbr, :count => version.open_issues_count), - project_issues_path(version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1)) %>) + <%= link_to_if(version.open_issues_count > 0, + l(:label_x_open_issues_abbr, :count => version.open_issues_count), + project_issues_path(version.project, :status_id => 'o', + :fixed_version_id => version, :set_filter => 1)) %>)

    <% else %>

    <%= l(:label_roadmap_no_issues) %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/versions/index.html.erb --- a/app/views/versions/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/versions/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,64 +1,89 @@
    - <%= link_to l(:label_version_new), new_project_version_path(@project), :class => 'icon icon-add' if User.current.allowed_to?(:manage_versions, @project) %> + <%= link_to(l(:label_version_new), new_project_version_path(@project), + :class => 'icon icon-add') if User.current.allowed_to?(:manage_versions, @project) %>

    <%=l(:label_roadmap)%>

    <% if @versions.empty? %> -

    <%= l(:label_no_data) %>

    +

    <%= l(:label_no_data) %>

    <% else %> -
    -<% @versions.each do |version| %> +
    + <% @versions.each do |version| %>

    <%= link_to_version version, :name => version_anchor(version) %>

    <%= render :partial => 'versions/overview', :locals => {:version => version} %> - <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %> - + <%= render(:partial => "wiki/content", + :locals => {:content => version.wiki_page.content}) if version.wiki_page %> <% if (issues = @issues_by_version[version]) && issues.size > 0 %> - <%= form_tag({}) do -%> - - - <% issues.each do |issue| -%> - - - - - <% end -%> - - <% end %> + <%= form_tag({}) do -%> + + + <% issues.each do |issue| -%> + + + + + <% end -%> + + <% end %> <% end %> <%= call_hook :view_projects_roadmap_version_bottom, :version => version %> -<% end %> -
    + <% end %> +
    <% end %> <% content_for :sidebar do %> <%= form_tag({}, :method => :get) do %>

    <%= l(:label_roadmap) %>

    +
      <% @trackers.each do |tracker| %> -
      +
    • + +
    • <% end %> -
      - -<% if @project.descendants.active.any? %> - <%= hidden_field_tag 'with_subprojects', 0 %> -
      -<% end %> +
    +

    +
      +
    • + +
    • + <% if @project.descendants.active.any? %> +
    • + <%= hidden_field_tag 'with_subprojects', 0 %> + +
    • + <% end %> +

    <%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %>

    <% end %>

    <%= l(:label_version_plural) %>

    +
      <% @versions.each do |version| %> -<%= link_to format_version_name(version), "##{version_anchor(version)}" %>
      +
    • + <%= link_to(format_version_name(version), "##{version_anchor(version)}") %> +
    • <% end %> +
    <% if @completed_versions.present? %>

    - <%= link_to_function l(:label_completed_versions), - '$("#toggle-completed-versions").toggleClass("collapsed"); $("#completed-versions").toggle()', - :id => 'toggle-completed-versions', :class => 'collapsible collapsed' %>
    - + <%= link_to_function l(:label_completed_versions), + '$("#toggle-completed-versions").toggleClass("collapsed"); $("#completed-versions").toggle()', + :id => 'toggle-completed-versions', :class => 'collapsible collapsed' %> +

    <% end %> <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/versions/show.html.erb --- a/app/views/versions/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/versions/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -39,9 +39,9 @@ <%- @issues.each do |issue| -%> - + - + <% end %> diff -r d98d22a98252 -r afce8026aaeb app/views/watchers/_new.html.erb --- a/app/views/watchers/_new.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/watchers/_new.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -2,8 +2,9 @@ <%= form_tag({:controller => 'watchers', :action => (watched ? 'create' : 'append'), - :object_type => watched.class.name.underscore, - :object_id => watched}, + :object_type => (watched && watched.class.name.underscore), + :object_id => watched, + :project_id => @project}, :remote => true, :method => :post, :id => 'new-watcher-form') do %> @@ -11,8 +12,9 @@

    <%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %>

    <%= javascript_tag "observeSearchfield('user_search', 'users_for_watcher', '#{ escape_javascript url_for(:controller => 'watchers', :action => 'autocomplete_for_user', - :object_type => watched.class.name.underscore, - :object_id => watched) }')" %> + :object_type => (watched && watched.class.name.underscore), + :object_id => watched, + :project_id => @project) }')" %>
    <%= principals_check_box_tags 'watcher[user_ids][]', (watched ? watched.addable_watcher_users : User.active.all(:limit => 100)) %> diff -r d98d22a98252 -r afce8026aaeb app/views/watchers/_set_watcher.js.erb --- a/app/views/watchers/_set_watcher.js.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/watchers/_set_watcher.js.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,2 +1,2 @@ <% selector = ".#{watcher_css(watched)}" %> -$("<%= selector %>").each(function(){$(this).html("<%= escape_javascript watcher_link(watched, user) %>")}); +$("<%= selector %>").each(function(){$(this).replaceWith("<%= escape_javascript watcher_link(watched, user) %>")}); diff -r d98d22a98252 -r afce8026aaeb app/views/wiki/_sidebar.html.erb --- a/app/views/wiki/_sidebar.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/wiki/_sidebar.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -3,7 +3,10 @@ <% end -%>

    <%= l(:label_wiki) %>

    - -<%= link_to l(:field_start_page), {:action => 'show', :id => nil} %>
    -<%= link_to l(:label_index_by_title), {:action => 'index'} %>
    -<%= link_to l(:label_index_by_date), {:controller => 'wiki', :project_id => @project, :action => 'date_index'} %>
    +
      +
    • <%= link_to(l(:field_start_page), {:action => 'show', :id => nil}) %>
    • +
    • <%= link_to(l(:label_index_by_title), {:action => 'index'}) %>
    • +
    • <%= link_to(l(:label_index_by_date), + {:controller => 'wiki', :project_id => @project, + :action => 'date_index'}) %>
    • +
    diff -r d98d22a98252 -r afce8026aaeb app/views/wiki/annotate.html.erb --- a/app/views/wiki/annotate.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/wiki/annotate.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -6,15 +6,14 @@ <%= wiki_page_breadcrumb(@page) %> -

    <%=h @page.pretty_title %>

    +<%= title [@page.pretty_title, project_wiki_page_path(@page.project, @page.title, :version => nil)], + [l(:label_history), history_project_wiki_page_path(@page.project, @page.title)], + "#{l(:label_version)} #{@annotate.content.version}" %>

    -<%= l(:label_version) %> <%= link_to h(@annotate.content.version), - :action => 'show', :project_id => @project, - :id => @page.title, :version => @annotate.content.version %> -(<%= h(@annotate.content.author ? - @annotate.content.author.name : l(:label_user_anonymous)) - %>, <%= format_time(@annotate.content.updated_on) %>) + <%= @annotate.content.author ? link_to_user(@annotate.content.author) : l(:label_user_anonymous) + %>, <%= format_time(@annotate.content.updated_on) %>
    + <%=h @annotate.content.comments %>

    <% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %> diff -r d98d22a98252 -r afce8026aaeb app/views/wiki/date_index.html.erb --- a/app/views/wiki/date_index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/wiki/date_index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@
    -<%= watcher_tag(@wiki, User.current) %> +<%= watcher_link(@wiki, User.current) %>

    <%= l(:label_index_by_date) %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/wiki/diff.html.erb --- a/app/views/wiki/diff.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/wiki/diff.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -5,7 +5,9 @@ <%= wiki_page_breadcrumb(@page) %> -

    <%= h(@page.pretty_title) %>

    +<%= title [@page.pretty_title, project_wiki_page_path(@page.project, @page.title, :version => nil)], + [l(:label_history), history_project_wiki_page_path(@page.project, @page.title)], + "#{l(:label_version)} #{@diff.content_to.version}" %>

    <%= l(:label_version) %> <%= link_to @diff.content_from.version, :action => 'show', :id => @page.title, :project_id => @page.project, :version => @diff.content_from.version %> diff -r d98d22a98252 -r afce8026aaeb app/views/wiki/history.html.erb --- a/app/views/wiki/history.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/wiki/history.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,8 +1,6 @@ <%= wiki_page_breadcrumb(@page) %> -

    <%= h(@page.pretty_title) %>

    - -

    <%= l(:label_history) %>

    +<%= title [@page.pretty_title, project_wiki_page_path(@page.project, @page.title, :version => nil)], l(:label_history) %> <%= form_tag({:controller => 'wiki', :action => 'diff', :project_id => @page.project, :id => @page.title}, diff -r d98d22a98252 -r afce8026aaeb app/views/wiki/index.html.erb --- a/app/views/wiki/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/wiki/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@
    -<%= watcher_tag(@wiki, User.current) %> +<%= watcher_link(@wiki, User.current) %>

    <%= l(:label_index_by_title) %>

    diff -r d98d22a98252 -r afce8026aaeb app/views/wiki/show.api.rsb --- a/app/views/wiki/show.api.rsb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/wiki/show.api.rsb Tue Sep 09 09:34:53 2014 +0100 @@ -6,7 +6,7 @@ api.text @content.text api.version @content.version api.author(:id => @content.author_id, :name => @content.author.name) - api.comments @page.content.comments + api.comments @content.comments api.created_on @page.created_on api.updated_on @content.updated_on diff -r d98d22a98252 -r afce8026aaeb app/views/wiki/show.html.erb --- a/app/views/wiki/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/wiki/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -2,7 +2,7 @@ <% if @editable %> <% if @content.current_version? %> <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> - <%= watcher_tag(@page, User.current) %> + <%= watcher_link(@page, User.current) %> <%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %> <%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %> <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') %> @@ -17,6 +17,10 @@ <%= wiki_page_breadcrumb(@page) %> <% unless @content.current_version? %> + <%= title [@page.pretty_title, project_wiki_page_path(@page.project, @page.title, :version => nil)], + [l(:label_history), history_project_wiki_page_path(@page.project, @page.title)], + "#{l(:label_version)} #{@content.version}" %> +

    <%= link_to(("\xc2\xab " + l(:label_previous)), :action => 'show', :id => @page.title, :project_id => @page.project, diff -r d98d22a98252 -r afce8026aaeb app/views/workflows/_action_menu.html.erb --- a/app/views/workflows/_action_menu.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/workflows/_action_menu.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,4 @@

    -<%= link_to l(:button_edit), {:action => 'edit'}, :class => 'icon icon-edit' %> <%= link_to l(:button_copy), {:action => 'copy'}, :class => 'icon icon-copy' %> <%= link_to l(:field_summary), {:action => 'index'}, :class => 'icon icon-summary' %>
    diff -r d98d22a98252 -r afce8026aaeb app/views/workflows/_form.html.erb --- a/app/views/workflows/_form.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/workflows/_form.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,17 +1,17 @@ - - + <% for new_status in @statuses %> - <% for old_status in @statuses %> "> - <% for new_status in @statuses -%> <% checked = workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id} %> - diff -r d98d22a98252 -r afce8026aaeb app/views/workflows/copy.html.erb --- a/app/views/workflows/copy.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/workflows/copy.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,4 @@ -<%= render :partial => 'action_menu' %> - -

    <%=l(:label_workflow)%>

    +<%= title [l(:label_workflow), workflows_edit_path], l(:button_copy) %> <%= form_tag({}, :id => 'workflow_copy_form') do %>
    diff -r d98d22a98252 -r afce8026aaeb app/views/workflows/edit.html.erb --- a/app/views/workflows/edit.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/workflows/edit.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@ <%= render :partial => 'action_menu' %> -

    <%=l(:label_workflow)%>

    +<%= title l(:label_workflow) %>
      @@ -54,5 +54,3 @@ <%= submit_tag l(:button_save) %> <% end %> <% end %> - -<% html_title(l(:label_workflow)) -%> diff -r d98d22a98252 -r afce8026aaeb app/views/workflows/index.html.erb --- a/app/views/workflows/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/workflows/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,4 @@ -<%= render :partial => 'action_menu' %> - -

      <%=l(:label_workflow)%>

      +<%= title [l(:label_workflow), workflows_edit_path], l(:field_summary) %> <% if @workflow_counts.empty? %>

      <%= l(:label_no_data) %>

      @@ -21,9 +19,9 @@
    <% @workflow_counts.each do |tracker, roles| -%> - + <% roles.each do |role, count| -%> - <% end -%> diff -r d98d22a98252 -r afce8026aaeb app/views/workflows/permissions.html.erb --- a/app/views/workflows/permissions.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/app/views/workflows/permissions.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@ <%= render :partial => 'action_menu' %> -

    <%=l(:label_workflow)%>

    +<%= title l(:label_workflow) %>
      @@ -35,14 +35,14 @@
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input')", :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> <%=l(:label_current_status)%> <%=l(:label_new_statuses_allowed)%><%=l(:label_new_statuses_allowed)%>
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.new-status-#{new_status.id}')", :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> <%=h new_status.name %> @@ -22,7 +22,7 @@
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.old-status-#{old_status.id}')", :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> @@ -30,7 +30,7 @@ + <%= check_box_tag "issue_status[#{ old_status.id }][#{new_status.id}][]", name, checked, :class => "old-status-#{old_status.id} new-status-#{new_status.id}" %>
    <%= h tracker %><%= h tracker %> + <%= link_to((count > 0 ? count : image_tag('false.png')), {:action => 'edit', :role_id => role, :tracker_id => tracker}, :title => l(:button_edit)) %>
    - - + <% for status in @statuses %> - <% end %> @@ -57,12 +57,13 @@ <% @fields.each do |field, name| %> "> - <% for status in @statuses -%> - <% end -%> @@ -76,12 +77,13 @@ <% @custom_fields.each do |field| %> "> - <% for status in @statuses -%> - <% end -%> @@ -93,3 +95,12 @@ <%= submit_tag l(:button_save) %> <% end %> <% end %> + +<%= javascript_tag do %> +$("a.repeat-value").click(function(e){ + e.preventDefault(); + var td = $(this).closest('td'); + var selected = td.find("select").find(":selected").val(); + td.nextAll('td').find("select").val(selected); +}); +<% end %> diff -r d98d22a98252 -r afce8026aaeb config/application.rb --- a/config/application.rb Wed May 07 14:15:02 2014 +0100 +++ b/config/application.rb Tue Sep 09 09:34:53 2014 +0100 @@ -22,9 +22,6 @@ # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - # Activate observers that should always be running. - config.active_record.observers = :message_observer, :issue_observer, :journal_observer, :news_observer, :document_observer, :wiki_content_observer, :comment_observer - config.active_record.store_full_sti_class = true config.active_record.default_timezone = :local @@ -40,6 +37,8 @@ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de + I18n.enforce_available_locales = false + # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" diff -r d98d22a98252 -r afce8026aaeb config/configuration.yml.example --- a/config/configuration.yml.example Wed May 07 14:15:02 2014 +0100 +++ b/config/configuration.yml.example Tue Sep 09 09:34:53 2014 +0100 @@ -72,10 +72,9 @@ # # === More configuration options # -# See the "Configuration options" at the following website for a list of the -# full options allowed: +# See following page: # -# http://wiki.rubyonrails.org/rails/pages/HowToSendEmailsWithActionMailer +# http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration # default configuration options for all environments @@ -133,6 +132,12 @@ scm_bazaar_command: scm_darcs_command: + # Absolute path to the SCM commands errors (stderr) log file. + # The default is to log in the 'log' directory of your Redmine instance. + # Example: + # scm_stderr_log_file: /var/log/redmine_scm_stderr.log + scm_stderr_log_file: + # Key used to encrypt sensitive data in the database (SCM and LDAP passwords). # If you don't want to enable data encryption, just leave it blank. # WARNING: losing/changing this key will make encrypted data unreadable. @@ -188,6 +193,14 @@ # rmagick_font_path: + # Maximum number of simultaneous AJAX uploads + #max_concurrent_ajax_uploads: 2 + + # Configure OpenIdAuthentication.store + # + # allowed values: :memory, :file, :memcache + #openid_authentication_store: :memory + # specific configuration options for production environment # that overrides the default ones production: diff -r d98d22a98252 -r afce8026aaeb config/database.yml.example --- a/config/database.yml.example Wed May 07 14:15:02 2014 +0100 +++ b/config/database.yml.example Tue Sep 09 09:34:53 2014 +0100 @@ -1,9 +1,10 @@ -# Default setup is given for MySQL with ruby1.8. If you're running Redmine -# with MySQL and ruby1.9, replace the adapter name with `mysql2`. -# Examples for PostgreSQL and SQLite3 can be found at the end. +# Default setup is given for MySQL with ruby1.9. If you're running Redmine +# with MySQL and ruby1.8, replace the adapter name with `mysql`. +# Examples for PostgreSQL, SQLite3 and SQL Server can be found at the end. +# Line indentation must be 2 spaces (no tabs). production: - adapter: mysql + adapter: mysql2 database: redmine host: localhost username: root @@ -11,7 +12,7 @@ encoding: utf8 development: - adapter: mysql + adapter: mysql2 database: redmine_development host: localhost username: root @@ -22,20 +23,30 @@ # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: mysql + adapter: mysql2 database: redmine_test host: localhost username: root password: "" encoding: utf8 -test_pgsql: - adapter: postgresql - database: redmine_test - host: localhost - username: postgres - password: "postgres" +# PostgreSQL configuration example +#production: +# adapter: postgresql +# database: redmine +# host: localhost +# username: postgres +# password: "postgres" -test_sqlite3: - adapter: sqlite3 - database: db/test.sqlite3 +# SQLite3 configuration example +#production: +# adapter: sqlite3 +# database: db/redmine.sqlite3 + +# SQL Server configuration example +#production: +# adapter: sqlserver +# database: redmine +# host: localhost +# username: jenkins +# password: jenkins diff -r d98d22a98252 -r afce8026aaeb config/environments/production.rb --- a/config/environments/production.rb Wed May 07 14:15:02 2014 +0100 +++ b/config/environments/production.rb Tue Sep 09 09:34:53 2014 +0100 @@ -5,7 +5,8 @@ config.cache_classes = true ##### - # Customize the default logger (http://ruby-doc.org/core/classes/Logger.html) + # Customize the default logger + # http://www.ruby-doc.org/stdlib-1.8.7/libdoc/logger/rdoc/Logger.html # # Use a different logger for distributed setups # config.logger = SyslogLogger.new diff -r d98d22a98252 -r afce8026aaeb config/initializers/00-core_plugins.rb --- a/config/initializers/00-core_plugins.rb Wed May 07 14:15:02 2014 +0100 +++ b/config/initializers/00-core_plugins.rb Tue Sep 09 09:34:53 2014 +0100 @@ -8,7 +8,7 @@ end initializer = File.join(directory, "init.rb") if File.file?(initializer) - config = config = RedmineApp::Application.config + config = RedmineApp::Application.config eval(File.read(initializer), binding, initializer) end end diff -r d98d22a98252 -r afce8026aaeb config/initializers/10-patches.rb --- a/config/initializers/10-patches.rb Wed May 07 14:15:02 2014 +0100 +++ b/config/initializers/10-patches.rb Tue Sep 09 09:34:53 2014 +0100 @@ -91,6 +91,43 @@ ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| html_tag || ''.html_safe } +# HTML5: is invalid, use instead +module ActionView + module Helpers + class InstanceTag + private + def add_options_with_non_empty_blank_option(option_tags, options, value = nil) + if options[:include_blank] == true + options = options.dup + options[:include_blank] = ' '.html_safe + end + add_options_without_non_empty_blank_option(option_tags, options, value) + end + alias_method_chain :add_options, :non_empty_blank_option + end + + module FormTagHelper + def select_tag_with_non_empty_blank_option(name, option_tags = nil, options = {}) + if options.delete(:include_blank) + options[:prompt] = ' '.html_safe + end + select_tag_without_non_empty_blank_option(name, option_tags, options) + end + alias_method_chain :select_tag, :non_empty_blank_option + end + + module FormOptionsHelper + def options_for_select_with_non_empty_blank_option(container, selected = nil) + if container.is_a?(Array) + container = container.map {|element| element.blank? ? [" ".html_safe, ""] : element} + end + options_for_select_without_non_empty_blank_option(container, selected) + end + alias_method_chain :options_for_select, :non_empty_blank_option + end + end +end + require 'mail' module DeliveryMethods diff -r d98d22a98252 -r afce8026aaeb config/initializers/30-redmine.rb --- a/config/initializers/30-redmine.rb Wed May 07 14:15:02 2014 +0100 +++ b/config/initializers/30-redmine.rb Tue Sep 09 09:34:53 2014 +0100 @@ -9,6 +9,13 @@ RedmineApp::Application.config.secret_token = secret end +if Object.const_defined?(:OpenIdAuthentication) + openid_authentication_store = Redmine::Configuration['openid_authentication_store'] + OpenIdAuthentication.store = + openid_authentication_store.present? ? + openid_authentication_store : :memory +end + Redmine::Plugin.load unless Redmine::Configuration['mirror_plugins_assets_on_startup'] == false Redmine::Plugin.mirror_assets diff -r d98d22a98252 -r afce8026aaeb config/locales/ar.yml --- a/config/locales/ar.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/ar.yml Tue Sep 09 09:34:53 2014 +0100 @@ -29,7 +29,7 @@ short: "%d %b %H:%M" long: "%B %d, %Y %H:%M" am: "صباحا" - pm: "مساءا" + pm: "مساءاً" datetime: distance_in_words: @@ -50,8 +50,8 @@ one: "حوالي ساعة" other: "ساعات %{count}حوالي " x_hours: - one: "1 hour" - other: "%{count} hours" + one: "%{count} ساعة" + other: "%{count} ساعات" x_days: one: "يوم" other: "%{count} أيام" @@ -111,23 +111,24 @@ accepted: "مقبولة" empty: "لا يمكن ان تكون ÙØ§Ø±ØºØ©" blank: "لا يمكن ان تكون ÙØ§Ø±ØºØ©" - too_long: " %{count}طويلة جدا، الحد الاقصى هو )" - too_short: " %{count}قصيرة جدا، الحد الادنى هو)" - wrong_length: " %{count}خطأ ÙÙŠ الطول، يجب ان يكون )" + too_long: " %{count} طويلة جدا، الحد الاقصى هو " + too_short: " %{count} قصيرة جدا، الحد الادنى هو " + wrong_length: " %{count} خطأ ÙÙŠ الطول، يجب ان يكون " taken: "لقد اتخذت سابقا" not_a_number: "ليس رقما" not_a_date: "ليس تاريخا صالحا" greater_than: "%{count}يجب ان تكون اكثر من " - greater_than_or_equal_to: "%{count}يجب ان تكون اكثر من او تساوي" + greater_than_or_equal_to: "%{count} يجب ان تكون اكثر من او تساوي" equal_to: "%{count}يجب ان تساوي" less_than: " %{count}يجب ان تكون اقل من" - less_than_or_equal_to: " %{count}يجب ان تكون اقل من او تساوي" + less_than_or_equal_to: " %{count} يجب ان تكون اقل من او تساوي" odd: "must be odd" even: "must be even" greater_than_start_date: "يجب ان تكون اكثر من تاريخ البداية" not_same_project: "لا ينتمي الى Ù†ÙØ³ المشروع" circular_dependency: "هذه العلاقة سو٠تخلق علاقة تبعية دائرية" cant_link_an_issue_with_a_descendant: "لا يمكن ان تكون المشكلة مرتبطة بواحدة من المهام Ø§Ù„ÙØ±Ø¹ÙŠØ©" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: الرجاء التحديد @@ -161,7 +162,7 @@ notice_not_authorized_archived_project: المشروع الذي تحاول الدخول اليه تم Ø§Ø±Ø´ÙØªÙ‡ notice_email_sent: "%{value}تم ارسال رسالة الى " notice_email_error: " (%{value})لقد حدث خطأ ما اثناء ارسال الرسالة الى " - notice_feeds_access_key_reseted: كلمة الدخول RSSلقد تم تعديل . + notice_feeds_access_key_reseted: كلمة الدخول Atomلقد تم تعديل . notice_api_access_key_reseted: كلمة الدخولAPIلقد تم تعديل . notice_failed_to_save_issues: "ÙØ´Ù„ ÙÙŠ Ø­ÙØ¸ الملÙ" notice_failed_to_save_members: "ÙØ´Ù„ ÙÙŠ Ø­ÙØ¸ الاعضاء: %{errors}." @@ -181,17 +182,17 @@ error_scm_annotate: "الادخال غير موجود." error_scm_annotate_big_text_file: "لا يمكن Ø­ÙØ¸ الادخال لانه تجاوز الحد الاقصى لحجم الملÙ." error_issue_not_found_in_project: 'لم يتم العثور على المخرج او انه ينتمي الى مشروع اخر' - error_no_tracker_in_project: 'لا يوجد متتبع لهذا المشروع، الرجاء التحقق من إعدادات المشروع. ' + error_no_tracker_in_project: 'لا يوجد انواع بنود عمل لهذا المشروع، الرجاء التحقق من إعدادات المشروع. ' error_no_default_issue_status: 'لم يتم التعر٠على اي وضع Ø§ÙØªØ±Ø§Ø¶ÙŠØŒ الرجاء التحقق من التكوين الخاص بك (اذهب الى إدارة-إصدار الحالات)' error_can_not_delete_custom_field: غير قادر على حذ٠الحقل المظلل - error_can_not_delete_tracker: "هذا المتتبع يحتوي على مسائل نشطة ولا يمكن حذÙÙ‡" + error_can_not_delete_tracker: "هذا النوع من بنود العمل يحتوي على بنود نشطة ولا يمكن حذÙÙ‡" error_can_not_remove_role: "هذا الدور قيد الاستخدام، لا يمكن حذÙÙ‡" - error_can_not_reopen_issue_on_closed_version: 'لا يمكن إعادة ÙØªØ­ قضية معينه لاصدار مقÙÙ„' + error_can_not_reopen_issue_on_closed_version: 'لا يمكن إعادة ÙØªØ­ بند عمل معين لاصدار مقÙÙ„' error_can_not_archive_project: لا يمكن Ø§Ø±Ø´ÙØ© هذا المشروع error_issue_done_ratios_not_updated: "لم يتم تحديث النسب" - error_workflow_copy_source: 'الرجاء اختيار المتتبع او الادوار' - error_workflow_copy_target: 'الرجاء اختيار هد٠المتتبع او هد٠الادوار' - error_unable_delete_issue_status: 'غير قادر على حذ٠حالة القضية' + error_workflow_copy_source: 'الرجاء اختيار نوع بند العمل او الادوار' + error_workflow_copy_target: 'الرجاء اختيار نوع بند العمل المستهد٠او الادوار Ø§Ù„Ù…Ø³ØªÙ‡Ø¯ÙØ©' + error_unable_delete_issue_status: 'غير قادر على حذ٠حالة بند العمل' error_unable_to_connect: "تعذر الاتصال(%{value})" error_attachment_too_big: " (%{max_size})لا يمكن تحميل هذا Ø§Ù„Ù…Ù„ÙØŒ لقد تجاوز الحد الاقصى المسموح به " warning_attachments_not_saved: "%{count}تعذر Ø­ÙØ¸ الملÙ" @@ -205,14 +206,12 @@ mail_subject_account_activation_request: "%{value}طلب ØªÙØ¹ÙŠÙ„ الحساب " mail_body_account_activation_request: " (%{value})تم تسجيل حساب جديد، بانتظار المواÙقة:" mail_subject_reminder: "%{count}تم تأجيل المهام التالية " - mail_body_reminder: "%{count}يجب ان تقوم بتسليم المهام التالية :" + mail_body_reminder: "%{count}يجب ان تقوم بتسليم المهام التالية:" mail_subject_wiki_content_added: "'%{id}' تم Ø§Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي" mail_body_wiki_content_added: "The '%{id}' تم Ø§Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي من قبل %{author}." mail_subject_wiki_content_updated: "'%{id}' تم تحديث ØµÙØ­Ø© ويكي" mail_body_wiki_content_updated: "The '%{id}'تم تحديث ØµÙØ­Ø© ويكي من قبل %{author}." - gui_validation_error: خطأ - gui_validation_error_plural: "%{count}أخطاء" field_name: الاسم field_description: الوص٠@@ -237,14 +236,14 @@ field_category: Ø§Ù„ÙØ¦Ø© field_title: العنوان field_project: المشروع - field_issue: القضية + field_issue: بند العمل field_status: الحالة field_notes: ملاحظات - field_is_closed: القضية مغلقة + field_is_closed: بند العمل مغلق field_is_default: القيمة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© - field_tracker: المتتبع + field_tracker: نوع بند العمل field_subject: الموضوع - field_due_date: تاريخ الاستحقاق + field_due_date: تاريخ النهاية field_assigned_to: المحال اليه field_priority: الأولوية field_fixed_version: الاصدار المستهد٠@@ -254,7 +253,7 @@ field_homepage: Ø§Ù„ØµÙØ­Ø© الرئيسية field_is_public: عام field_parent: مشروع ÙØ±Ø¹ÙŠ Ù…Ù† - field_is_in_roadmap: القضايا المعروضة ÙÙŠ خارطة الطريق + field_is_in_roadmap: معروض ÙÙŠ خارطة الطريق field_login: تسجيل الدخول field_mail_notification: ملاحظات على البريد الالكتروني field_admin: المدير @@ -275,8 +274,8 @@ field_attr_lastname: سمة الاسم الاخير field_attr_mail: سمة البريد الالكتروني field_onthefly: إنشاء حساب مستخدم على تحرك - field_start_date: تاريخ البدية - field_done_ratio: "% تم" + field_start_date: تاريخ البداية + field_done_ratio: "نسبة الانجاز" field_auth_source: وضع المصادقة field_hide_mail: Ø¥Ø®ÙØ§Ø¡ بريدي الإلكتروني field_comments: تعليق @@ -288,9 +287,9 @@ field_spent_on: تاريخ field_identifier: المعر٠field_is_filter: استخدم كتصÙية - field_issue_to: القضايا المتصلة + field_issue_to: بنود العمل المتصلة field_delay: تأخير - field_assignable: يمكن ان تستند القضايا الى هذا الدور + field_assignable: يمكن اسناد بنود العمل الى هذا الدور field_redirect_existing_links: إعادة توجيه الروابط الموجودة field_estimated_hours: الوقت المتوقع field_column_names: أعمدة @@ -304,15 +303,15 @@ field_watcher: مراقب field_identity_url: Ø§ÙØªØ­ الرابط الخاص بالهوية الشخصية field_content: المحتويات - field_group_by: مجموعة النتائج عن طريق + field_group_by: تصني٠النتائج بواسطة field_sharing: مشاركة - field_parent_issue: مهمة الوالدين + field_parent_issue: بند العمل الأصلي field_member_of_group: "مجموعة المحال" field_assigned_to_role: "دور المحال" field_text: حقل نصي field_visible: غير مرئي field_warn_on_leaving_unsaved: "الرجاء التحذير عند مغادرة ØµÙØ­Ø© والنص غير محÙوظ" - field_issues_visibility: القضايا المرئية + field_issues_visibility: بنود العمل المرئية field_is_private: خاص field_commit_logs_encoding: رسائل الترميز field_scm_path_encoding: ترميز المسار @@ -328,7 +327,7 @@ setting_login_required: مطلوب المصادقة setting_self_registration: التسجيل الذاتي setting_attachment_max_size: الحد الاقصى Ù„Ù„Ù…Ù„ÙØ§Øª المرÙقة - setting_issues_export_limit: الحد الاقصى لقضايا التصدير + setting_issues_export_limit: الحد الاقصى لتصدير بنود العمل Ù„Ù…Ù„ÙØ§Øª خارجية setting_mail_from: انبعاثات عنوان بريدك setting_bcc_recipients: مستلمين النسخ المخÙية (bcc) setting_plain_text_mail: نص عادي (no HTML) @@ -344,8 +343,8 @@ setting_autologin: الدخول التلقائي setting_date_format: تنسيق التاريخ setting_time_format: تنسيق الوقت - setting_cross_project_issue_relations: السماح بادارج القضايا ÙÙŠ هذا المشروع - setting_issue_list_default_columns: الاعمدة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© المعروضة ÙÙŠ قائمة القضية + setting_cross_project_issue_relations: السماح بإدراج بنود العمل ÙÙŠ هذا المشروع + setting_issue_list_default_columns: الاعمدة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© المعروضة ÙÙŠ قائمة بند العمل setting_repositories_encodings: ترميز المرÙقات والمستودعات setting_emails_header: رأس رسائل البريد الإلكتروني setting_emails_footer: ذيل رسائل البريد الإلكتروني @@ -353,7 +352,7 @@ setting_per_page_options: الكائنات لكل خيارات Ø§Ù„ØµÙØ­Ø© setting_user_format: تنسيق عرض المستخدم setting_activity_days_default: الايام المعروضة على نشاط المشروع - setting_display_subprojects_issues: عرض القضايا Ø§Ù„ÙØ±Ø¹ÙŠØ© للمشارع الرئيسية بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ + setting_display_subprojects_issues: عرض بنود العمل للمشارع الرئيسية بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ setting_enabled_scm: SCM تمكين setting_mail_handler_body_delimiters: "اقتطاع رسائل البريد الإلكتروني بعد هذه الخطوط" setting_mail_handler_api_enabled: للرسائل الواردةWS تمكين @@ -368,18 +367,18 @@ setting_password_min_length: الحد الادني لطول كلمة المرور setting_new_project_user_role_id: الدور المسند الى المستخدم غير المسؤول الذي يقوم بإنشاء المشروع setting_default_projects_modules: تمكين الوحدات النمطية للمشاريع الجديدة بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ - setting_issue_done_ratio: حساب نسبة القضية المنتهية - setting_issue_done_ratio_issue_field: استخدم حقل القضية - setting_issue_done_ratio_issue_status: استخدم وضع القضية + setting_issue_done_ratio: حساب نسبة بند العمل المنتهية + setting_issue_done_ratio_issue_field: استخدم حقل بند العمل + setting_issue_done_ratio_issue_status: استخدم وضع بند العمل setting_start_of_week: بدأ التقويم setting_rest_api_enabled: تمكين باقي خدمات الويب setting_cache_formatted_text: النص المسبق تنسيقه ÙÙŠ ذاكرة التخزين المؤقت setting_default_notification_option: خيار الاعلام Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ setting_commit_logtime_enabled: تميكن وقت الدخول setting_commit_logtime_activity_id: النشاط ÙÙŠ وقت الدخول - setting_gantt_items_limit: الحد الاقصى لعدد العناصر المعروضة على المخطط + setting_gantt_items_limit: الحد الاقصى لعدد العناصر المعروضة على مخطط جانت setting_issue_group_assignment: السماح للإحالة الى المجموعات - setting_default_issue_start_date_to_creation_date: استخدام التاريخ الحالي كتاريخ بدأ للقضايا الجديدة + setting_default_issue_start_date_to_creation_date: استخدام التاريخ الحالي كتاريخ بدأ لبنود العمل الجديدة permission_add_project: إنشاء مشروع permission_add_subprojects: إنشاء مشاريع ÙØ±Ø¹ÙŠØ© @@ -388,18 +387,18 @@ permission_manage_members: إدارة الاعضاء permission_manage_project_activities: ادارة اصدارات المشروع permission_manage_versions: ادارة الاصدارات - permission_manage_categories: ادارة انواع القضايا - permission_view_issues: عرض القضايا - permission_add_issues: Ø§Ø¶Ø§ÙØ© القضايا - permission_edit_issues: تعديل القضايا - permission_manage_issue_relations: ادارة علاقات القضايا - permission_set_issues_private: تعين قضايا عامة او خاصة - permission_set_own_issues_private: تعين القضايا الخاصة بك كقضايا عامة او خاصة + permission_manage_categories: ادارة انواع بنود العمل + permission_view_issues: عرض بنود العمل + permission_add_issues: Ø§Ø¶Ø§ÙØ© بنود العمل + permission_edit_issues: تعديل بنود العمل + permission_manage_issue_relations: ادارة علاقات بنود العمل + permission_set_issues_private: تعين بنود العمل عامة او خاصة + permission_set_own_issues_private: تعين بنود العمل الخاصة بك كبنود عمل عامة او خاصة permission_add_issue_notes: Ø§Ø¶Ø§ÙØ© ملاحظات permission_edit_issue_notes: تعديل ملاحظات permission_edit_own_issue_notes: تعديل ملاحظاتك - permission_move_issues: تحريك القضايا - permission_delete_issues: حذ٠القضايا + permission_move_issues: تحريك بنود العمل + permission_delete_issues: حذ٠بنود العمل permission_manage_public_queries: ادارة الاستعلامات العامة permission_save_queries: Ø­ÙØ¸ الاستعلامات permission_view_gantt: عرض طريقة"جانت" @@ -413,7 +412,6 @@ permission_edit_own_time_entries: تعديل الدخولات الشخصية permission_manage_news: ادارة الاخبار permission_comment_news: اخبار التعليقات - permission_manage_documents: ادارة المستندات permission_view_documents: عرض المستندات permission_manage_files: ادارة Ø§Ù„Ù…Ù„ÙØ§Øª permission_view_files: عرض Ø§Ù„Ù…Ù„ÙØ§Øª @@ -439,7 +437,7 @@ permission_export_wiki_pages: تصدير ØµÙØ­Ø§Øª ويكي permission_manage_subtasks: ادارة المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© - project_module_issue_tracking: تعقب القضايا + project_module_issue_tracking: تعقب بنود العمل project_module_time_tracking: التعقب الزمني project_module_news: الاخبار project_module_documents: المستندات @@ -463,13 +461,13 @@ other: "%{count} مشاريع" label_project_all: كل المشاريع label_project_latest: احدث المشاريع - label_issue: قضية - label_issue_new: قضية جديدة - label_issue_plural: قضايا - label_issue_view_all: عرض كل القضايا - label_issues_by: " %{value}القضية لصحابها" - label_issue_added: تم Ø§Ø¶Ø§ÙØ© القضية - label_issue_updated: تم تحديث القضية + label_issue: بند عمل + label_issue_new: بند عمل جديد + label_issue_plural: بنود عمل + label_issue_view_all: عرض كل بنود العمل + label_issues_by: " %{value}بند العمل لصحابها" + label_issue_added: تم Ø§Ø¶Ø§ÙØ© بند العمل + label_issue_updated: تم تحديث بند العمل label_issue_note_added: تم Ø§Ø¶Ø§ÙØ© الملاحظة label_issue_status_updated: تم تحديث الحالة label_issue_priority_updated: تم تحديث الاولويات @@ -480,22 +478,22 @@ label_role: دور label_role_plural: ادوار label_role_new: دور جديد - label_role_and_permissions: الادوار والاذن + label_role_and_permissions: الأدوار والصلاحيات label_role_anonymous: مجهول الهوية label_role_non_member: ليس عضو label_member: عضو label_member_new: عضو جديد label_member_plural: اعضاء - label_tracker: المتتبع - label_tracker_plural: المتتبعين - label_tracker_new: متتبع جديد + label_tracker: نوع بند عمل + label_tracker_plural: أنواع بنود العمل + label_tracker_new: نوع بند عمل جديد label_workflow: سير العمل - label_issue_status: وضع القضية - label_issue_status_plural: اوضاع القضية - label_issue_status_new: وضع جديد - label_issue_category: نوع القضية - label_issue_category_plural: انواع القضايا - label_issue_category_new: نوع جديد + label_issue_status: حالة بند العمل + label_issue_status_plural: حالات بند العمل + label_issue_status_new: حالة جديدة + label_issue_category: ÙØ¦Ø© بند العمل + label_issue_category_plural: ÙØ¦Ø§Øª بنود العمل + label_issue_category_new: ÙØ¦Ø© جديدة label_custom_field: تخصيص حقل label_custom_field_plural: تخصيص حقول label_custom_field_new: حقل مخصص جديد @@ -507,17 +505,17 @@ label_register: تسجيل label_login_with_open_id_option: او الدخول بهوية Ù…ÙØªÙˆØ­Ø© label_password_lost: Ùقدت كلمة السر - label_home: Ø§Ù„ØµÙØ­Ø© الرئيسية + label_home: "Ø§Ù„ØµÙØ­Ø© الرئيسية" label_my_page: Ø§Ù„ØµÙØ­Ø© الخاصة بي label_my_account: حسابي label_my_projects: مشاريعي الخاصة label_my_page_block: حجب ØµÙØ­ØªÙŠ Ø§Ù„Ø®Ø§ØµØ© - label_administration: الإدارة + label_administration: إدارة النظام label_login: تسجيل الدخول label_logout: تسجيل الخروج label_help: مساعدة - label_reported_issues: أبلغ القضايا - label_assigned_to_me_issues: المسائل المعنية إلى + label_reported_issues: بنود العمل التي أدخلتها + label_assigned_to_me_issues: بنود العمل المسندة إلي label_last_login: آخر اتصال label_registered_on: مسجل على label_activity: النشاط @@ -532,21 +530,19 @@ label_auth_source_plural: أوضاع المصادقة label_subproject_plural: مشاريع ÙØ±Ø¹ÙŠØ© label_subproject_new: مشروع ÙØ±Ø¹ÙŠ Ø¬Ø¯ÙŠØ¯ - label_and_its_subprojects: "قيمةالمشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ© الخاصة بك" + label_and_its_subprojects: "قيمة المشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ© الخاصة بك" label_min_max_length: الحد الاقصى والادنى للطول label_list: قائمة label_date: تاريخ label_integer: عدد صحيح - label_float: تعويم - label_boolean: منطقية - label_string: النص + label_float: عدد كسري + label_boolean: "نعم/لا" + label_string: نص label_text: نص طويل label_attribute: سمة label_attribute_plural: السمات - label_download: "تحميل" - label_download_plural: "تحميل" label_no_data: لا توجد بيانات للعرض - label_change_status: تغيير الوضع + label_change_status: تغيير الحالة label_history: التاريخ label_attachment: المل٠label_attachment_new: مل٠جديد @@ -572,10 +568,10 @@ label_export_to: 'Ù…ØªÙˆÙØ±Ø© أيضا ÙÙŠ:' label_read: القراءة... label_public_projects: المشاريع العامة - label_open_issues: ÙØªØ­ قضية - label_open_issues_plural: ÙØªØ­ قضايا - label_closed_issues: قضية مغلقة - label_closed_issues_plural: قضايا مغلقة + label_open_issues: Ù…ÙØªÙˆØ­ + label_open_issues_plural: بنود عمل Ù…ÙØªÙˆØ­Ø© + label_closed_issues: مغلق + label_closed_issues_plural: بنود عمل مغلقة label_x_open_issues_abbr_on_total: zero: 0 Ù…ÙØªÙˆØ­ / %{total} one: 1 Ù…ÙØªÙˆØ­ / %{total} @@ -589,8 +585,8 @@ one: 1 مغلق other: "%{count} مغلق" label_total: الإجمالي - label_permissions: أذونات - label_current_status: الوضع الحالي + label_permissions: صلاحيات + label_current_status: الحالة الحالية label_new_statuses_allowed: يسمح بادراج حالات جديدة label_all: جميع label_none: لا شيء @@ -625,8 +621,8 @@ label_filter_plural: عوامل التصÙية label_equals: يساوي label_not_equals: لا يساوي - label_in_less_than: ÙÙŠ أقل من - label_in_more_than: ÙÙŠ أكثر من + label_in_less_than: أقل من + label_in_more_than: أكثر من label_greater_or_equal: '>=' label_less_or_equal: '< =' label_between: بين @@ -636,13 +632,13 @@ label_yesterday: بالأمس label_this_week: هذا الأسبوع label_last_week: الأسبوع الماضي - label_last_n_days: "ايام %{count} اخر" + label_last_n_days: "آخر %{count} أيام" label_this_month: هذا الشهر label_last_month: الشهر الماضي label_this_year: هذا العام label_date_range: نطاق التاريخ - label_less_than_ago: أقل من قبل أيام - label_more_than_ago: أكثر من قبل أيام + label_less_than_ago: أقل من عدد أيام + label_more_than_ago: أكثر من عدد أيام label_ago: منذ أيام label_contains: يحتوي على label_not_contains: لا يحتوي على @@ -650,32 +646,30 @@ label_repository: المستودع label_repository_plural: المستودعات label_browse: ØªØµÙØ­ - label_modification: "%{count} تغير" - label_modification_plural: "%{count}تغيرات " label_branch: ÙØ±Ø¹ label_tag: ربط label_revision: مراجعة label_revision_plural: تنقيحات label_revision_id: " %{value}مراجعة" label_associated_revisions: التنقيحات المرتبطة - label_added: Ø¥Ø¶Ø§ÙØ© - label_modified: تعديل - label_copied: نسخ + label_added: مضا٠+ label_modified: معدل + label_copied: منسوخ label_renamed: إعادة تسمية - label_deleted: حذ٠+ label_deleted: محذو٠label_latest_revision: آخر تنقيح label_latest_revision_plural: أحدث المراجعات label_view_revisions: عرض التنقيحات label_view_all_revisions: عرض ÙƒØ§ÙØ© المراجعات label_max_size: الحد الأقصى للحجم - label_sort_highest: التحرك إلى أعلى + label_sort_highest: التحرك لأعلى مرتبة label_sort_higher: تحريك لأعلى label_sort_lower: تحريك لأسÙÙ„ - label_sort_lowest: الانتقال إلى أسÙÙ„ + label_sort_lowest: التحرك لأسÙÙ„ مرتبة label_roadmap: خارطة الطريق label_roadmap_due_in: " %{value}تستحق ÙÙŠ " label_roadmap_overdue: "%{value}تأخير" - label_roadmap_no_issues: لا يوجد قضايا لهذا الإصدار + label_roadmap_no_issues: لا يوجد بنود عمل لهذا الإصدار label_search: البحث label_result_plural: النتائج label_all_words: كل الكلمات @@ -690,8 +684,8 @@ label_preview: معاينة label_feed_plural: موجز ويب label_changes_details: ØªÙØ§ØµÙŠÙ„ جميع التغييرات - label_issue_tracking: تعقب القضايا - label_spent_time: أمضى بعض الوقت + label_issue_tracking: تعقب بنود العمل + label_spent_time: ما تم Ø¥Ù†ÙØ§Ù‚Ù‡ من الوقت label_overall_spent_time: الوقت الذي تم Ø§Ù†ÙØ§Ù‚Ù‡ كاملا label_f_hour: "%{value} ساعة" label_f_hour_plural: "%{value} ساعات" @@ -706,27 +700,27 @@ label_diff_side_by_side: جنبا إلى جنب label_options: خيارات label_copy_workflow_from: نسخ سير العمل من - label_permissions_report: تقرير أذونات - label_watched_issues: شاهد القضايا - label_related_issues: القضايا ذات الصلة - label_applied_status: تطبيق مركز + label_permissions_report: تقرير الصلاحيات + label_watched_issues: بنود العمل المتابعة بريدياً + label_related_issues: بنود العمل ذات الصلة + label_applied_status: الحالة المطبقة label_loading: تحميل... label_relation_new: علاقة جديدة label_relation_delete: حذ٠العلاقة - label_relates_to: ذات الصلة إلى - label_duplicates: التكرارات - label_duplicated_by: ازدواج - label_blocks: حظر - label_blocked_by: حظر بواسطة + label_relates_to: ذات علاقة بـ + label_duplicates: مكرر من + label_duplicated_by: مكرر بواسطة + label_blocks: يجب تنÙيذه قبل + label_blocked_by: لا يمكن تنÙيذه إلا بعد label_precedes: يسبق label_follows: يتبع label_end_to_start: نهاية لبدء - label_end_to_end: نهاية إلى نهاية - label_start_to_start: بدء إلى بدء + label_end_to_end: نهاية لنهاية + label_start_to_start: بدء لبدء label_start_to_end: بداية لنهاية label_stay_logged_in: تسجيل الدخول ÙÙŠ label_disabled: تعطيل - label_show_completed_versions: أكملت إظهار إصدارات + label_show_completed_versions: إظهار الإصدارات الكاملة label_me: لي label_board: المنتدى label_board_new: منتدى جديد @@ -748,20 +742,20 @@ label_language_based: استناداً إلى لغة المستخدم label_sort_by: " %{value}الترتيب حسب " label_send_test_email: ارسل رسالة الكترونية كاختبار - label_feeds_access_key: RSS Ù…ÙØªØ§Ø­ دخول - label_missing_feeds_access_key: Ù…ÙقودRSS Ù…ÙØªØ§Ø­ دخول - label_feeds_access_key_created_on: "RSS تم انشاء Ù…ÙØªØ§Ø­ %{value} منذ" + label_feeds_access_key: Atom Ù…ÙØªØ§Ø­ دخول + label_missing_feeds_access_key: Ù…ÙقودAtom Ù…ÙØªØ§Ø­ دخول + label_feeds_access_key_created_on: "Atom تم انشاء Ù…ÙØªØ§Ø­ %{value} منذ" label_module_plural: الوحدات النمطية - label_added_time_by: " تم Ø§Ø¶Ø§ÙØªÙ‡ من قبل%{author} %{age} منذ" - label_updated_time_by: " تم تحديثه من قبل%{author} %{age} منذ" - label_updated_time: "تم التحديث %{value} منذ" + label_added_time_by: " تم Ø§Ø¶Ø§ÙØªÙ‡ من قبل %{author} منذ %{age}" + label_updated_time_by: " تم تحديثه من قبل %{author} منذ %{age}" + label_updated_time: "تم التحديث منذ %{value}" label_jump_to_a_project: الانتقال إلى مشروع... label_file_plural: Ø§Ù„Ù…Ù„ÙØ§Øª label_changeset_plural: اعدادات التغير label_default_columns: الاعمدة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© - label_no_change_option: (أي تغيير) - label_bulk_edit_selected_issues: تحرير القضايا المظللة - label_bulk_edit_selected_time_entries: تعديل كل الإدخالات ÙÙŠ كل الاوقات + label_no_change_option: (لا تغيير) + label_bulk_edit_selected_issues: تحرير بنود العمل المظللة + label_bulk_edit_selected_time_entries: تعديل بنود الأوقات المظللة label_theme: الموضوع label_default: Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ label_search_titles_only: البحث ÙÙŠ العناوين Ùقط @@ -771,7 +765,7 @@ label_user_mail_option_only_my_events: "السماح لي Ùقط بمشاهدة الاحداث الخاصة" label_user_mail_option_only_assigned: "Ùقط الخيارات التي تم تعيينها" label_user_mail_option_only_owner: "Ùقط للخيارات التي املكها" - label_user_mail_no_self_notified: "لا تريد اعلامك بالتغيرات التي تجريها Ø¨Ù†ÙØ³Ùƒ" + label_user_mail_no_self_notified: "لا تريد إعلامك بالتغيرات التي تجريها Ø¨Ù†ÙØ³Ùƒ" label_registration_activation_by_email: حساب التنشيط عبر البريد الإلكتروني label_registration_manual_activation: تنشيط الحساب اليدوي label_registration_automatic_activation: تنشيط الحساب التلقائي @@ -783,7 +777,7 @@ label_scm: scm label_plugins: Ø§Ù„Ø¥Ø¶Ø§ÙØ§Øª label_ldap_authentication: مصادقة LDAP - label_downloads_abbr: D/L + label_downloads_abbr: تنزيل label_optional_description: وص٠اختياري label_add_another_file: Ø¥Ø¶Ø§ÙØ© مل٠آخر label_preferences: ØªÙØ¶ÙŠÙ„ات @@ -804,17 +798,17 @@ label_group: مجموعة label_group_plural: المجموعات label_group_new: مجموعة جديدة - label_time_entry_plural: أمضى بعض الوقت - label_version_sharing_none: لم يشارك - label_version_sharing_descendants: يشارك - label_version_sharing_hierarchy: مع التسلسل الهرمي للمشروع + label_time_entry_plural: الأوقات المنÙقة + label_version_sharing_none: غير متاح + label_version_sharing_descendants: متاح للمشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ© + label_version_sharing_hierarchy: متاح للتسلسل الهرمي للمشروع label_version_sharing_tree: مع شجرة المشروع label_version_sharing_system: مع جميع المشاريع - label_update_issue_done_ratios: تحديث قضيةالنسب - label_copy_source: مصدر + label_update_issue_done_ratios: تحديث نسبة الأداء لبند العمل + label_copy_source: المصدر label_copy_target: الهد٠- label_copy_same_as_target: Ù†ÙØ³ الهد٠- label_display_used_statuses_only: عرض الحالات المستخدمة من قبل هذا "تعقب" Ùقط + label_copy_same_as_target: مطابق للهد٠+ label_display_used_statuses_only: عرض الحالات المستخدمة من قبل هذا النوع من بنود العمل Ùقط label_api_access_key: Ù…ÙØªØ§Ø­ الوصول إلى API label_missing_api_access_key: API لم يتم الحصول على Ù…ÙØªØ§Ø­ الوصول label_api_access_key_created_on: " API إنشاء Ù…ÙØªØ§Ø­ الوصول إلى" @@ -825,9 +819,9 @@ label_user_search: "البحث عن المستخدم:" label_additional_workflow_transitions_for_author: الانتقالات الإضاÙية المسموح بها عند المستخدم صاحب البلاغ label_additional_workflow_transitions_for_assignee: الانتقالات الإضاÙية المسموح بها عند المستخدم المحال إليه - label_issues_visibility_all: جميع القضايا - label_issues_visibility_public: جميع القضايا الخاصة - label_issues_visibility_own: القضايا التي أنشأها المستخدم + label_issues_visibility_all: جميع بنود العمل + label_issues_visibility_public: جميع بنود العمل الخاصة + label_issues_visibility_own: بنود العمل التي أنشأها المستخدم label_git_report_last_commit: اعتماد التقرير الأخير Ù„Ù„Ù…Ù„ÙØ§Øª والدلائل label_parent_revision: الوالدين label_child_revision: الطÙÙ„ @@ -836,20 +830,20 @@ button_login: دخول button_submit: تثبيت button_save: Ø­ÙØ¸ - button_check_all: نحديد الكل + button_check_all: تحديد الكل button_uncheck_all: عدم تحديد الكل - button_collapse_all: تقليص الكل + button_collapse_all: تقليص الكل button_expand_all: عرض الكل button_delete: حذ٠- button_create: انشاء - button_create_and_continue: انشاء واستمرار + button_create: إنشاء + button_create_and_continue: إنشاء واستمرار button_test: اختبار button_edit: تعديل button_edit_associated_wikipage: "تغير ØµÙØ­Ø© ويكي: %{page_title}" - button_add: Ø§Ø¶Ø§ÙØ© - button_change: تغير + button_add: Ø¥Ø¶Ø§ÙØ© + button_change: تغيير button_apply: تطبيق - button_clear: واضح + button_clear: إخلاء الحقول button_lock: Ù‚ÙÙ„ button_unlock: الغاء القÙÙ„ button_download: تنزيل @@ -863,11 +857,11 @@ button_sort: ترتيب button_log_time: وقت الدخول button_rollback: الرجوع الى هذا الاصدار - button_watch: يشاهد - button_unwatch: إلغاء المشاهدة + button_watch: تابع عبر البريد + button_unwatch: إلغاء المتابعة عبر البريد button_reply: رد - button_archive: الارشي٠- button_unarchive: إلغاء Ø§Ù„Ø§Ø±Ø´ÙØ© + button_archive: Ø£Ø±Ø´ÙØ© + button_unarchive: إلغاء Ø§Ù„Ø£Ø±Ø´ÙØ© button_reset: إعادة button_rename: إعادة التسمية button_change_password: تغير كلمة المرور @@ -876,11 +870,11 @@ button_annotate: تعليق button_update: تحديث button_configure: تكوين - button_quote: يقتبس - button_duplicate: يضاع٠- button_show: يظهر - button_edit_section: يعدل هذا الجزء - button_export: يستورد + button_quote: اقتباس + button_duplicate: عمل نسخة + button_show: إظهار + button_edit_section: تعديل هذا الجزء + button_export: تصدير لمل٠status_active: نشيط status_registered: مسجل @@ -896,34 +890,34 @@ text_regexp_info: مثال. ^[A-Z0-9]+$ text_min_max_length_info: الحد الاقصى والادني لطول المعلومات text_project_destroy_confirmation: هل أنت متأكد من أنك تريد حذ٠هذا المشروع والبيانات ذات الصلة؟ - text_subprojects_destroy_warning: "subproject(s): سيتم حذ٠أيضا." - text_workflow_edit: حدد دوراً وتعقب لتحرير سير العمل + text_subprojects_destroy_warning: "المشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ©: %{value} سيتم حذÙها أيضاً." + text_workflow_edit: حدد دوراً Ùˆ نوع بند عمل لتحرير سير العمل text_are_you_sure: هل أنت متأكد؟ text_journal_changed: "%{label} تغير %{old} الى %{new}" text_journal_changed_no_detail: "%{label} تم التحديث" text_journal_set_to: "%{label} تغير الى %{value}" text_journal_deleted: "%{label} تم الحذ٠(%{old})" text_journal_added: "%{label} %{value} تم Ø§Ù„Ø§Ø¶Ø§ÙØ©" - text_tip_issue_begin_day: قضية بدأت اليوم - text_tip_issue_end_day: قضية انتهت اليوم - text_tip_issue_begin_end_day: قضية بدأت وانتهت اليوم + text_tip_issue_begin_day: بند عمل بدأ اليوم + text_tip_issue_end_day: بند عمل انتهى اليوم + text_tip_issue_begin_end_day: بند عمل بدأ وانتهى اليوم text_caracters_maximum: "%{count} الحد الاقصى." text_caracters_minimum: "الحد الادنى %{count}" text_length_between: "الطول %{min} بين %{max} رمز" - text_tracker_no_workflow: لم يتم تحديد سير العمل لهذا المتتبع + text_tracker_no_workflow: لم يتم تحديد سير العمل لهذا النوع من بنود العمل text_unallowed_characters: رموز غير مسموحة text_comma_separated: مسموح رموز متنوعة ÙŠÙØµÙ„ها ÙØ§ØµÙ„Ø© . text_line_separated: مسموح رموز متنوعة ÙŠÙØµÙ„ها سطور - text_issues_ref_in_commit_messages: الرجوع واصلاح القضايا ÙÙŠ رسائل المشتكين - text_issue_added: "القضية %{id} تم ابلاغها عن طريق %{author}." - text_issue_updated: "القضية %{id} تم تحديثها عن طريق %{author}." + text_issues_ref_in_commit_messages: الارتباط وتغيير حالة بنود العمل ÙÙŠ رسائل تحرير Ø§Ù„Ù…Ù„ÙØ§Øª + text_issue_added: "بند العمل %{id} تم ابلاغها عن طريق %{author}." + text_issue_updated: "بند العمل %{id} تم تحديثها عن طريق %{author}." text_wiki_destroy_confirmation: هل انت متأكد من رغبتك ÙÙŠ حذ٠هذا الويكي ومحتوياته؟ - text_issue_category_destroy_question: "بعض القضايا (%{count}) مرتبطة بهذه Ø§Ù„ÙØ¦Ø©ØŒ ماذا تريد ان ØªÙØ¹Ù„ بها؟" + text_issue_category_destroy_question: "بعض بنود العمل (%{count}) مرتبطة بهذه Ø§Ù„ÙØ¦Ø©ØŒ ماذا تريد ان ØªÙØ¹Ù„ بها؟" text_issue_category_destroy_assignments: Ø­Ø°Ù Ø§Ù„ÙØ¦Ø© text_issue_category_reassign_to: اعادة تثبيت البنود ÙÙŠ Ø§Ù„ÙØ¦Ø© text_user_mail_option: "بالنسبة للمشاريع غير المحددة، سو٠يتم ابلاغك عن المشاريع التي تشاهدها او تشارك بها Ùقط!" - text_no_configuration_data: "الادوار والمتتبع وحالات القضية ومخطط سير العمل لم يتم تحديد وضعها Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ Ø¨Ø¹Ø¯. " - text_load_default_configuration: احمل الاعدادات Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + text_no_configuration_data: "الادوار والمتتبع وحالات بند العمل ومخطط سير العمل لم يتم تحديد وضعها Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ Ø¨Ø¹Ø¯. " + text_load_default_configuration: تحميل الاعدادات Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© text_status_changed_by_changeset: " طبق التغيرات المعينة على %{value}." text_time_logged_by_changeset: "تم تطبيق التغيرات المعينة على %{value}." text_issues_destroy_confirmation: هل انت متأكد من حذ٠البنود المظللة؟' @@ -933,20 +927,20 @@ text_default_administrator_account_changed: تم تعديل الاعدادات Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© لحساب المدير text_file_repository_writable: المرÙقات قابلة للكتابة text_plugin_assets_writable: الدليل المساعد قابل للكتابة - text_destroy_time_entries_question: " ساعة على القضية التي تود حذÙها، ماذا تريد ان ØªÙØ¹Ù„ØŸ %{hours} تم تثبيت" + text_destroy_time_entries_question: " ساعة على بند العمل التي تود حذÙها، ماذا تريد ان ØªÙØ¹Ù„ØŸ %{hours} تم تثبيت" text_destroy_time_entries: قم بحذ٠الساعات المسجلة text_assign_time_entries_to_project: ثبت الساعات المسجلة على التقرير - text_reassign_time_entries: 'اعادة تثبيت الساعات المسجلة لهذه القضية:' + text_reassign_time_entries: 'اعادة تثبيت الساعات المسجلة لبند العمل هذا:' text_user_wrote: "%{value} كتب:" text_enumeration_destroy_question: "%{count} الكائنات المعنية لهذه القيمة" text_enumeration_category_reassign_to: اعادة تثبيت الكائنات التالية لهذه القيمة:' text_email_delivery_not_configured: "لم يتم تسليم البريد الالكتروني" - text_diff_truncated: '... لقد تم اقتطلع هذا الجزء لانه تجاوز الحد الاقصى المسموح بعرضه' + text_diff_truncated: '... لقد تم اقتطاع هذا الجزء لانه تجاوز الحد الاقصى المسموح بعرضه' text_custom_field_possible_values_info: 'سطر لكل قيمة' - text_wiki_page_nullify_children: "Ø§Ù„Ø§Ø­ØªÙØ§Ø¸ Ø¨ØµÙØ­Ø§Øª الطÙÙ„ ÙƒØµÙØ­Ø§Øª جذر" - text_wiki_page_destroy_children: "Ø­Ø°Ù ØµÙØ­Ø§Øª الطÙÙ„ وجميع أولادهم" + text_wiki_page_nullify_children: "Ø§Ù„Ø§Ø­ØªÙØ§Ø¸ Ø¨ØµÙØ­Ø§Øª الابن ÙƒØµÙØ­Ø§Øª جذر" + text_wiki_page_destroy_children: "Ø­Ø°Ù ØµÙØ­Ø§Øª الابن وجميع أولادهم" text_wiki_page_reassign_children: "إعادة تعيين ØµÙØ­Ø§Øª تابعة لهذه Ø§Ù„ØµÙØ­Ø© الأصلية" - text_own_membership_delete_confirmation: "انت على وشك إزالة بعض أو ÙƒØ§ÙØ© الأذونات الخاصة بك، لن تكون قادراً على تحرير هذا المشروع بعد ذلك. هل أنت متأكد من أنك تريد المتابعة؟" + text_own_membership_delete_confirmation: "انت على وشك إزالة بعض أو ÙƒØ§ÙØ© الصلاحيات الخاصة بك، لن تكون قادراً على تحرير هذا المشروع بعد ذلك. هل أنت متأكد من أنك تريد المتابعة؟" text_zoom_in: تصغير text_zoom_out: تكبير text_warn_on_leaving_unsaved: "Ø§Ù„ØµÙØ­Ø© تحتوي على نص غير مخزن، سو٠يÙقد النص اذا تم الخروج من Ø§Ù„ØµÙØ­Ø©." @@ -976,7 +970,7 @@ default_priority_normal: عادي default_priority_high: عالي default_priority_urgent: طارئ - default_priority_immediate: مباشرة + default_priority_immediate: طارئ الآن default_activity_design: تصميم default_activity_development: تطوير @@ -986,7 +980,7 @@ enumeration_system_activity: نشاط النظام description_filter: Ùلترة description_search: حقل البحث - description_choose_project: مشاريع + description_choose_project: المشاريع description_project_scope: مجال البحث description_notes: ملاحظات description_message_content: محتويات الرسالة @@ -1009,9 +1003,9 @@ Users with the same Redmine and repository username or email are automatically mapped. notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." label_x_issues: - zero: 0 قضية - one: 1 قضية - other: "%{count} قضايا" + zero: لا يوجد بنود عمل + one: بند عمل واحد + other: "%{count} بنود عمل" label_repository_new: New repository field_repository_is_default: Main repository label_copy_attachments: Copy attachments @@ -1062,8 +1056,8 @@ label_attribute_of_assigned_to: Assignee's %{name} label_attribute_of_fixed_version: Target version's %{name} label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from + label_copied_to: منسوخ لـ + label_copied_from: منسوخ من label_any_issues_in_project: any issues in project label_any_issues_not_in_project: any issues not in project field_private_notes: Private notes @@ -1071,13 +1065,39 @@ permission_set_notes_private: Set notes as private label_no_issues_in_project: no issues in project label_any: جميع - label_last_n_weeks: last %{count} weeks + label_last_n_weeks: آخر %{count} أسبوع/أسابيع setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: يشارك - label_cross_project_tree: مع شجرة المشروع - label_cross_project_hierarchy: مع التسلسل الهرمي للمشروع - label_cross_project_system: مع جميع المشاريع - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past + label_cross_project_descendants: متاح للمشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ© + label_cross_project_tree: متاح مع شجرة المشروع + label_cross_project_hierarchy: متاح مع التسلسل الهرمي للمشروع + label_cross_project_system: متاح مع جميع المشاريع + button_hide: Ø¥Ø®ÙØ§Ø¡ + setting_non_working_week_days: "أيام أجازة/راحة أسبوعية" + label_in_the_next_days: ÙÙŠ الأيام المقبلة + label_in_the_past_days: ÙÙŠ الأيام الماضية + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: الإجمالي + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/az.yml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/config/locales/az.yml Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,1200 @@ +# +# Translated by Saadat Mutallimova +# Data Processing Center of the Ministry of Communication and Information Technologies +# +az: + direction: ltr + date: + formats: + default: "%d.%m.%Y" + short: "%d %b" + long: "%d %B %Y" + + day_names: [bazar, bazar ertÉ™si, çərÅŸÉ™nbÉ™ axÅŸamı, çərÅŸÉ™nbÉ™, cümÉ™ axÅŸamı, cümÉ™, ÅŸÉ™nbÉ™] + standalone_day_names: [Bazar, Bazar ertÉ™si, ÇərÅŸÉ™nbÉ™ axÅŸamı, ÇərÅŸÉ™nbÉ™, CümÉ™ axÅŸamı, CümÉ™, ŞənbÉ™] + abbr_day_names: [B, Be, Ça, Ç, Ca, C, Åž] + + month_names: [~, yanvar, fevral, mart, aprel, may, iyun, iyul, avqust, sentyabr, oktyabr, noyabr, dekabr] + # see russian gem for info on "standalone" day names + standalone_month_names: [~, Yanvar, Fevral, Mart, Aprel, May, İyun, İyul, Avqust, Sentyabr, Oktyabr, Noyabr, Dekabr] + abbr_month_names: [~, yan., fev., mart, apr., may, iyun, iyul, avq., sent., okt., noy., dek.] + standalone_abbr_month_names: [~, yan., fev., mart, apr., may, iyun, iyul, avq., sent., okt., noy., dek.] + + order: + - :day + - :month + - :year + + time: + formats: + default: "%a, %d %b %Y, %H:%M:%S %z" + time: "%H:%M" + short: "%d %b, %H:%M" + long: "%d %B %Y, %H:%M" + + am: "sÉ™hÉ™r" + pm: "axÅŸam" + + number: + format: + separator: "," + delimiter: " " + precision: 3 + + currency: + format: + format: "%n %u" + unit: "man." + separator: "." + delimiter: " " + precision: 2 + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + # Rails 2.2 + # storage_units: [байт, КБ, МБ, ГБ, ТБ] + + # Rails 2.3 + storage_units: + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u" + units: + byte: + one: "bayt" + few: "bayt" + many: "bayt" + other: "bayt" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + datetime: + distance_in_words: + half_a_minute: "bir dÉ™qiqÉ™dÉ™n az" + less_than_x_seconds: + one: "%{count} saniyÉ™dÉ™n az" + few: "%{count} saniyÉ™dÉ™n az" + many: "%{count} saniyÉ™dÉ™n az" + other: "%{count} saniyÉ™dÉ™n az" + x_seconds: + one: "%{count} saniyÉ™" + few: "%{count} saniyÉ™" + many: "%{count} saniyÉ™" + other: "%{count} saniyÉ™" + less_than_x_minutes: + one: "%{count} dÉ™qiqÉ™dÉ™n az" + few: "%{count} dÉ™qiqÉ™dÉ™n az" + many: "%{count} dÉ™qiqÉ™dÉ™n az" + other: "%{count} dÉ™qiqÉ™dÉ™n az" + x_minutes: + one: "%{count} dÉ™qiqÉ™" + few: "%{count} dÉ™qiqÉ™" + many: "%{count} dÉ™qiqÉ™" + other: "%{count} dÉ™qiqÉ™" + about_x_hours: + one: "tÉ™xminÉ™n %{count} saat" + few: "tÉ™xminÉ™n %{count} saat" + many: "tÉ™xminÉ™n %{count} saat" + other: "tÉ™xminÉ™n %{count} saat" + x_hours: + one: "1 saat" + other: "%{count} saat" + x_days: + one: "%{count} gün" + few: "%{count} gün" + many: "%{count} gün" + other: "%{count} gün" + about_x_months: + one: "tÉ™xminÉ™n %{count} ay" + few: "tÉ™xminÉ™n %{count} ay" + many: "tÉ™xminÉ™n %{count} ay" + other: "tÉ™xminÉ™n %{count} ay" + x_months: + one: "%{count} ay" + few: "%{count} ay" + many: "%{count} ay" + other: "%{count} ay" + about_x_years: + one: "tÉ™xminÉ™n %{count} il" + few: "tÉ™xminÉ™n %{count} il" + many: "tÉ™xminÉ™n %{count} il" + other: "tÉ™xminÉ™n %{count} il" + over_x_years: + one: "%{count} ildÉ™n çox" + few: "%{count} ildÉ™n çox" + many: "%{count} ildÉ™n çox" + other: "%{count} ildÉ™n çox" + almost_x_years: + one: "tÉ™xminÉ™n 1 il" + few: "tÉ™xminÉ™n %{count} il" + many: "tÉ™xminÉ™n %{count} il" + other: "tÉ™xminÉ™n %{count} il" + prompts: + year: "İl" + month: "Ay" + day: "Gün" + hour: "Saat" + minute: "DÉ™qiqÉ™" + second: "SaniyÉ™" + + activerecord: + errors: + template: + header: + one: "%{model}: %{count} sÉ™hvÉ™ görÉ™ yadda saxlamaq mümkün olmadı" + few: "%{model}: %{count} sÉ™hvlÉ™rÉ™ görÉ™ yadda saxlamaq mümkün olmadı" + many: "%{model}: %{count} sÉ™hvlÉ™rÉ™ görÉ™ yadda saxlamaq mümkün olmadı" + other: "%{model}: %{count} sÉ™hvÉ™ görÉ™ yadda saxlamaq mümkün olmadı" + + body: "ProblemlÉ™r aÅŸağıdakı sahÉ™lÉ™rdÉ™ yarandı:" + + messages: + inclusion: "nÉ™zÉ™rdÉ™ tutulmamış tÉ™yinata malikdir" + exclusion: "ehtiyata götürülmÉ™miÅŸ tÉ™yinata malikdir" + invalid: "düzgün tÉ™yinat deyildir" + confirmation: "tÉ™sdiq ilÉ™ üst-üstÉ™ düşmür" + accepted: "tÉ™sdiq etmÉ™k lazımdır" + empty: "boÅŸ saxlanıla bilmÉ™z" + blank: "boÅŸ saxlanıla bilmÉ™z" + too_long: + one: "çox böyük uzunluq (%{count} simvoldan çox ola bilmÉ™z)" + few: "çox böyük uzunluq (%{count} simvoldan çox ola bilmÉ™z)" + many: "çox böyük uzunluq (%{count} simvoldan çox ola bilmÉ™z)" + other: "çox böyük uzunluq (%{count} simvoldan çox ola bilmÉ™z)" + too_short: + one: "uzunluq kifayÉ™t qÉ™dÉ™r deyildir (%{count} simvoldan az ola bilmÉ™z)" + few: "uzunluq kifayÉ™t qÉ™dÉ™r deyildir (%{count} simvoldan az ola bilmÉ™z)" + many: "uzunluq kifayÉ™t qÉ™dÉ™r deyildir (%{count} simvoldan az ola bilmÉ™z)" + other: "uzunluq kifayÉ™t qÉ™dÉ™r deyildir (%{count} simvoldan az ola bilmÉ™z)" + wrong_length: + one: "düzgün olmayan uzunluq (tam %{count} simvol ola bilÉ™r)" + few: "düzgün olmayan uzunluq (tam %{count} simvol ola bilÉ™r)" + many: "düzgün olmayan uzunluq (tam %{count} simvol ola bilÉ™r)" + other: "düzgün olmayan uzunluq (tam %{count} simvol ola bilÉ™r)" + taken: "artıq mövcuddur" + not_a_number: "say kimi hesab edilmir" + greater_than: "%{count} çox tÉ™yinata malik ola bilÉ™r" + greater_than_or_equal_to: "%{count} çox vÉ™ ya ona bÉ™rabÉ™r tÉ™yinata malik ola bilÉ™r" + equal_to: "yalnız %{count} bÉ™rabÉ™r tÉ™yinata malik ola bilÉ™r" + less_than: "%{count} az tÉ™yinata malik ola bilÉ™r" + less_than_or_equal_to: "%{count} az vÉ™ ya ona bÉ™rabÉ™r tÉ™yinata malik ola bilÉ™r" + odd: "yalnız tÉ™k tÉ™yinata malik ola bilÉ™r" + even: "yalnız cüt tÉ™yinata malik ola bilÉ™r" + greater_than_start_date: "baÅŸlanğıc tarixindÉ™n sonra olmalıdır" + not_same_project: "tÉ™kcÉ™ bir layihÉ™yÉ™ aid deyildir" + circular_dependency: "BelÉ™ É™laqÉ™ dövri asılılığa gÉ™tirib çıxaracaq" + cant_link_an_issue_with_a_descendant: "Tapşırıq özünün alt tapşırığı ilÉ™ É™laqÉ™li ola bilmÉ™z" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + support: + array: + # Rails 2.2 + sentence_connector: "vÉ™" + skip_last_comma: true + + # Rails 2.3 + words_connector: ", " + two_words_connector: " vÉ™" + last_word_connector: " vÉ™ " + + actionview_instancetag_blank_option: Seçim edin + + button_activate: AktivləşdirmÉ™k + button_add: ÆlavÉ™ etmÉ™k + button_annotate: Müəlliflik + button_apply: TÉ™tbiq etmÉ™k + button_archive: ArxivləşdirmÉ™k + button_back: GeriyÉ™ + button_cancel: İmtina + button_change_password: Parolu dÉ™yiÅŸmÉ™k + button_change: DÉ™yiÅŸmÉ™k + button_check_all: Hamını qeyd etmÉ™k + button_clear: TÉ™mizlÉ™mÉ™k + button_configure: ParametlÉ™r + button_copy: SürÉ™tini çıxarmaq + button_create: Yaratmaq + button_create_and_continue: Yaratmaq vÉ™ davam etmÉ™k + button_delete: SilmÉ™k + button_download: YüklÉ™mÉ™k + button_edit: RedaktÉ™ etmÉ™k + button_edit_associated_wikipage: "ÆlaqÉ™li wiki-sÉ™hifÉ™ni redaktÉ™ etmÉ™k: %{page_title}" + button_list: Siyahı + button_lock: Bloka salmaq + button_login: GiriÅŸ + button_log_time: SÉ™rf olunan vaxt + button_move: Yerini dÉ™yiÅŸmÉ™k + button_quote: Sitat gÉ™tirmÉ™k + button_rename: Adını dÉ™yiÅŸmÉ™k + button_reply: Cavablamaq + button_reset: Sıfırlamaq + button_rollback: Bu versiyaya qayıtmaq + button_save: Yadda saxlamaq + button_sort: ÇeÅŸidlÉ™mÉ™k + button_submit: QÉ™bul etmÉ™k + button_test: Yoxlamaq + button_unarchive: ArxivdÉ™n çıxarmaq + button_uncheck_all: TÉ™mizlÉ™mÉ™k + button_unlock: Blokdan çıxarmaq + button_unwatch: İzlÉ™mÉ™mÉ™k + button_update: YenilÉ™mÉ™k + button_view: Baxmaq + button_watch: İzlÉ™mÉ™k + + default_activity_design: LayihÉ™nin hazırlanması + default_activity_development: Hazırlanma prosesi + default_doc_category_tech: Texniki sÉ™nÉ™dləşmÉ™ + default_doc_category_user: İstifadəçi sÉ™nÉ™di + default_issue_status_in_progress: İşlÉ™nmÉ™kdÉ™dir + default_issue_status_closed: BaÄŸlanıb + default_issue_status_feedback: Æks É™laqÉ™ + default_issue_status_new: Yeni + default_issue_status_rejected: RÉ™dd etmÉ™ + default_issue_status_resolved: HÉ™ll edilib + default_priority_high: YüksÉ™k + default_priority_immediate: TÉ™xirsiz + default_priority_low: AÅŸağı + default_priority_normal: Normal + default_priority_urgent: TÉ™cili + default_role_developer: Hazırlayan + default_role_manager: Menecer + default_role_reporter: Reportyor + default_tracker_bug: SÉ™hv + default_tracker_feature: TÉ™kmilləşmÉ™ + default_tracker_support: DÉ™stÉ™k + + enumeration_activities: HÉ™rÉ™kÉ™tlÉ™r (vaxtın uçotu) + enumeration_doc_categories: SÉ™nÉ™dlÉ™rin kateqoriyası + enumeration_issue_priorities: Tapşırıqların prioriteti + + error_can_not_remove_role: Bu rol istifadÉ™ edilir vÉ™ silinÉ™ bilmÉ™z. + error_can_not_delete_custom_field: Sazlanmış sahÉ™ni silmÉ™k mümkün deyildir + error_can_not_delete_tracker: Bu treker tapşırıqlardan ibarÉ™t olduÄŸu üçün silinÉ™ bilmÉ™z. + error_can_t_load_default_data: "Susmaya görÉ™ konfiqurasiya yüklÉ™nmÉ™miÅŸdir: %{value}" + error_issue_not_found_in_project: Tapşırıq tapılmamışdır vÉ™ ya bu layihÉ™yÉ™ bÉ™rkidilmÉ™miÅŸdir + error_scm_annotate: "VerilÉ™nlÉ™r mövcud deyildir vÉ™ ya imzalana bilmÉ™z." + error_scm_command_failed: "Saxlayıcıya giriÅŸ imkanı sÉ™hvi: %{value}" + error_scm_not_found: Saxlayıcıda yazı vÉ™/ vÉ™ ya düzÉ™liÅŸ yoxdur. + error_unable_to_connect: QoÅŸulmaq mümkün deyildir (%{value}) + error_unable_delete_issue_status: Tapşırığın statusunu silmÉ™k mümkün deyildir + + field_account: İstifadəçi hesabı + field_activity: FÉ™aliyyÉ™t + field_admin: İnzibatçı + field_assignable: Tapşırıq bu rola tÉ™yin edilÉ™ bilÉ™r + field_assigned_to: TÉ™yin edilib + field_attr_firstname: Ad + field_attr_lastname: Soyad + field_attr_login: Atribut Login + field_attr_mail: e-poçt + field_author: Müəllif + field_auth_source: Autentifikasiya rejimi + field_base_dn: BaseDN + field_category: Kateqoriya + field_column_names: Sütunlar + field_comments: ŞərhlÉ™r + field_comments_sorting: ŞərhlÉ™rin tÉ™sviri + field_content: Content + field_created_on: Yaradılıb + field_default_value: Susmaya görÉ™ tÉ™yinat + field_delay: TÉ™xirÉ™ salmaq + field_description: TÉ™svir + field_done_ratio: Hazırlıq + field_downloads: YüklÉ™mÉ™lÉ™r + field_due_date: YerinÉ™ yetirilmÉ™ tarixi + field_editable: RedaktÉ™ edilÉ™n + field_effective_date: Tarix + field_estimated_hours: Vaxtın dÉ™yÉ™rlÉ™ndirilmÉ™si + field_field_format: Format + field_filename: Fayl + field_filesize: Ölçü + field_firstname: Ad + field_fixed_version: Variant + field_hide_mail: E-poçtumu gizlÉ™t + field_homepage: BaÅŸlanğıc sÉ™hifÉ™ + field_host: Kompyuter + field_hours: saat + field_identifier: Unikal identifikator + field_identity_url: OpenID URL + field_is_closed: Tapşırıq baÄŸlanıb + field_is_default: Susmaya görÉ™ tapşırıq + field_is_filter: Filtr kimi istifadÉ™ edilir + field_is_for_all: Bütün layihÉ™lÉ™r üçün + field_is_in_roadmap: Operativ planda É™ks olunan tapşırıqlar + field_is_public: Ümümaçıq + field_is_required: MütlÉ™q + field_issue_to: ÆlaqÉ™li tapşırıqlar + field_issue: Tapşırıq + field_language: Dil + field_last_login_on: Son qoÅŸulma + field_lastname: Soyad + field_login: İstifadəçi + field_mail: e-poçt + field_mail_notification: e-poçt ilÉ™ bildiriÅŸ + field_max_length: maksimal uzunluq + field_min_length: minimal uzunluq + field_name: Ad + field_new_password: Yeni parol + field_notes: Qeyd + field_onthefly: Tez bir zamanda istifadəçinin yaradılması + field_parent_title: Valideyn sÉ™hifÉ™ + field_parent: Valideyn layihÉ™ + field_parent_issue: Valideyn tapşırıq + field_password_confirmation: TÉ™sdiq + field_password: Parol + field_port: Port + field_possible_values: Mümkün olan tÉ™yinatlar + field_priority: Prioritet + field_project: LayihÉ™ + field_redirect_existing_links: Mövcud olan istinadları istiqamÉ™tlÉ™ndirmÉ™k + field_regexp: MüntÉ™zÉ™m ifadÉ™ + field_role: Rol + field_searchable: Axtarış üçün açıqdır + field_spent_on: Tarix + field_start_date: BaÅŸlanıb + field_start_page: BaÅŸlanğıc sÉ™hifÉ™ + field_status: Status + field_subject: Mövzu + field_subproject: AltlayihÉ™ + field_summary: Qısa tÉ™svir + field_text: MÉ™tn sahÉ™si + field_time_entries: SÉ™rf olunan zaman + field_time_zone: Saat qurÅŸağı + field_title: BaÅŸlıq + field_tracker: Treker + field_type: Tip + field_updated_on: YenilÉ™nib + field_url: URL + field_user: İstifadəçi + field_value: TÉ™yinat + field_version: Variant + field_watcher: NÉ™zarÉ™tçi + + general_csv_decimal_separator: ',' + general_csv_encoding: UTF-8 + general_csv_separator: ';' + general_first_day_of_week: '1' + general_lang_name: 'Azerbaijanian (Azeri)' + general_pdf_encoding: UTF-8 + general_text_no: 'xeyr' + general_text_No: 'Xeyr' + general_text_yes: 'bÉ™li' + general_text_Yes: 'BÉ™li' + + label_activity: GörülÉ™n iÅŸlÉ™r + label_add_another_file: Bir fayl daha É™lavÉ™ etmÉ™k + label_added_time_by: "ÆlavÉ™ etdi %{author} %{age} É™vvÉ™l" + label_added: É™lavÉ™ edilib + label_add_note: Qeydi É™lavÉ™ etmÉ™k + label_administration: İnzibatçılıq + label_age: YaÅŸ + label_ago: gün É™vvÉ™l + label_all_time: hÉ™r zaman + label_all_words: Bütün sözlÉ™r + label_all: hamı + label_and_its_subprojects: "%{value} vÉ™ bütün altlayihÉ™lÉ™r" + label_applied_status: TÉ™tbiq olunan status + label_ascending: Artmaya görÉ™ + label_assigned_to_me_issues: MÉ™nim tapşırıqlarım + label_associated_revisions: ÆlaqÉ™li redaksiyalar + label_attachment: Fayl + label_attachment_delete: Faylı silmÉ™k + label_attachment_new: Yeni fayl + label_attachment_plural: Fayllar + label_attribute: Atribut + label_attribute_plural: Atributlar + label_authentication: Autentifikasiya + label_auth_source: Autentifikasiyanın rejimi + label_auth_source_new: Autentifikasiyanın yeni rejimi + label_auth_source_plural: Autentifikasiyanın rejimlÉ™ri + label_blocked_by: bloklanır + label_blocks: bloklayır + label_board: Forum + label_board_new: Yeni forum + label_board_plural: Forumlar + label_boolean: MÉ™ntiqi + label_browse: Baxış + label_bulk_edit_selected_issues: SeçilÉ™n bütün tapşırıqları redaktÉ™ etmÉ™k + label_calendar: TÉ™qvim + label_calendar_filter: O cümlÉ™dÉ™n + label_calendar_no_assigned: MÉ™nim deyil + label_change_plural: DÉ™yiÅŸikliklÉ™r + label_change_properties: XassÉ™lÉ™ri dÉ™yiÅŸmÉ™k + label_change_status: Statusu dÉ™yiÅŸmÉ™k + label_change_view_all: Bütün dÉ™yiÅŸikliklÉ™rÉ™ baxmaq + label_changes_details: Bütün dÉ™yiÅŸikliklÉ™rÉ™ görÉ™ tÉ™fsilatlar + label_changeset_plural: DÉ™yiÅŸikliklÉ™r + label_chronological_order: Xronoloji ardıcıllıq ilÉ™ + label_closed_issues: BaÄŸlıdır + label_closed_issues_plural: baÄŸlıdır + label_closed_issues_plural2: baÄŸlıdır + label_closed_issues_plural5: baÄŸlıdır + label_comment: ÅŸÉ™rhlÉ™r + label_comment_add: ŞərhlÉ™ri qeyd etmÉ™k + label_comment_added: ÆlavÉ™ olunmuÅŸ ÅŸÉ™rhlÉ™r + label_comment_delete: Şərhi silmÉ™k + label_comment_plural: ŞərhlÉ™r + label_comment_plural2: ŞərhlÉ™r + label_comment_plural5: ÅŸÉ™rhlÉ™rin + label_commits_per_author: İstifadəçi üzÉ™rindÉ™ dÉ™yiÅŸikliklÉ™r + label_commits_per_month: Ay üzÉ™rindÉ™ dÉ™yiÅŸikliklÉ™r + label_confirmation: TÉ™sdiq + label_contains: tÉ™rkibi + label_copied: surÉ™ti köçürülüb + label_copy_workflow_from: görülÉ™n iÅŸlÉ™rin ardıcıllığının surÉ™tini köçürmÉ™k + label_current_status: Cari status + label_current_version: Cari variant + label_custom_field: Sazlanan sahÉ™ + label_custom_field_new: Yeni sazlanan sahÉ™ + label_custom_field_plural: Sazlanan sahÉ™lÉ™r + label_date_from: С + label_date_from_to: С %{start} по %{end} + label_date_range: vaxt intervalı + label_date_to: üzrÉ™ + label_date: Tarix + label_day_plural: gün + label_default: Susmaya görÉ™ + label_default_columns: Susmaya görÉ™ sütunlar + label_deleted: silinib + label_descending: Azalmaya görÉ™ + label_details: TÉ™fsilatlar + label_diff_inline: mÉ™tndÉ™ + label_diff_side_by_side: Yanaşı + label_disabled: söndürülüb + label_display: TÉ™svir + label_display_per_page: "SÉ™hifÉ™yÉ™: %{value}" + label_document: SÉ™nÉ™d + label_document_added: SÉ™nÉ™d É™lavÉ™ edilib + label_document_new: Yeni sÉ™nÉ™d + label_document_plural: SÉ™nÉ™dlÉ™r + label_downloads_abbr: YüklÉ™mÉ™lÉ™r + label_duplicated_by: çoxaldılır + label_duplicates: çoxaldır + label_end_to_end: sondan sona doÄŸru + label_end_to_start: sondan É™vvÉ™lÉ™ doÄŸru + label_enumeration_new: Yeni qiymÉ™t + label_enumerations: QiymÉ™tlÉ™rin siyahısı + label_environment: Mühit + label_equals: sayılır + label_example: NümunÉ™ + label_export_to: ixrac etmÉ™k + label_feed_plural: Atom + label_feeds_access_key_created_on: "Atom-É™ giriÅŸ açarı %{value} É™vvÉ™l yaradılıb" + label_f_hour: "%{value} saat" + label_f_hour_plural: "%{value} saat" + label_file_added: Fayl É™lavÉ™ edilib + label_file_plural: Fayllar + label_filter_add: Filtr É™lavÉ™ etmÉ™k + label_filter_plural: FiltrlÉ™r + label_float: HÉ™qiqi É™dÉ™d + label_follows: ÆvvÉ™lki + label_gantt: Qant diaqramması + label_general: Ümumi + label_generate_key: Açarı generasiya etmÉ™k + label_greater_or_equal: ">=" + label_help: KömÉ™k + label_history: Tarixçə + label_home: Ana sÉ™hifÉ™ + label_incoming_emails: MÉ™lumatların qÉ™bulu + label_index_by_date: SÉ™hifÉ™lÉ™rin tarixçəsi + label_index_by_title: BaÅŸlıq + label_information_plural: İnformasiya + label_information: İnformasiya + label_in_less_than: az + label_in_more_than: çox + label_integer: Tam + label_internal: Daxili + label_in: da (dÉ™) + label_issue: Tapşırıq + label_issue_added: Tapşırıq É™lavÉ™ edilib + label_issue_category_new: Yeni kateqoriya + label_issue_category_plural: Tapşırığın kateqoriyası + label_issue_category: Tapşırığın kateqoriyası + label_issue_new: Yeni tapşırıq + label_issue_plural: Tapşırıqlar + label_issues_by: "%{value} üzrÉ™ çeÅŸidlÉ™mÉ™k" + label_issue_status_new: Yeni status + label_issue_status_plural: Tapşırıqların statusu + label_issue_status: Tapşırığın statusu + label_issue_tracking: Tapşırıqlar + label_issue_updated: Tapşırıq yenilÉ™nib + label_issue_view_all: Bütün tapşırıqlara baxmaq + label_issue_watchers: NÉ™zarÉ™tçilÉ™r + label_jump_to_a_project: ... layihÉ™yÉ™ keçid + label_language_based: Dilin É™sasında + label_last_changes: "%{count} az dÉ™yiÅŸiklik" + label_last_login: Sonuncu qoÅŸulma + label_last_month: sonuncu ay + label_last_n_days: "son %{count} gün" + label_last_week: sonuncu hÉ™ftÉ™ + label_latest_revision: Sonuncu redaksiya + label_latest_revision_plural: Sonuncu redaksiyalar + label_ldap_authentication: LDAP vasitÉ™silÉ™ avtorizasiya + label_less_or_equal: <= + label_less_than_ago: gündÉ™n az + label_list: Siyahı + label_loading: YüklÉ™mÉ™... + label_logged_as: Daxil olmusunuz + label_login: Daxil olmaq + label_login_with_open_id_option: vÉ™ ya OpenID vasitÉ™silÉ™ daxil olmaq + label_logout: Çıxış + label_max_size: Maksimal ölçü + label_member_new: Yeni iÅŸtirakçı + label_member: İştirakçı + label_member_plural: İştirakçılar + label_message_last: Sonuncu mÉ™lumat + label_message_new: Yeni mÉ™lumat + label_message_plural: MÉ™lumatlar + label_message_posted: MÉ™lumat É™lavÉ™ olunub + label_me: mÉ™nÉ™ + label_min_max_length: Minimal - maksimal uzunluq + label_modified: dÉ™yiÅŸilib + label_module_plural: Modullar + label_months_from: ay + label_month: Ay + label_more_than_ago: gündÉ™n É™vvÉ™l + label_more: Çox + label_my_account: MÉ™nim hesabım + label_my_page: MÉ™nim sÉ™hifÉ™m + label_my_page_block: MÉ™nim sÉ™hifÉ™min bloku + label_my_projects: MÉ™nim layihÉ™lÉ™rim + label_new: Yeni + label_new_statuses_allowed: İcazÉ™ verilÉ™n yeni statuslar + label_news_added: XÉ™bÉ™r É™lavÉ™ edilib + label_news_latest: Son xÉ™bÉ™rlÉ™r + label_news_new: XÉ™bÉ™r É™lavÉ™ etmÉ™k + label_news_plural: XÉ™bÉ™rlÉ™r + label_news_view_all: Bütün xÉ™bÉ™rlÉ™rÉ™ baxmaq + label_news: XÉ™bÉ™rlÉ™r + label_next: NövbÉ™ti + label_nobody: heç kim + label_no_change_option: (DÉ™yiÅŸiklik yoxdur) + label_no_data: TÉ™svir üçün verilÉ™nlÉ™r yoxdur + label_none: yoxdur + label_not_contains: mövcud deyil + label_not_equals: sayılmır + label_open_issues: açıqdır + label_open_issues_plural: açıqdır + label_open_issues_plural2: açıqdır + label_open_issues_plural5: açıqdır + label_optional_description: TÉ™svir (vacib deyil) + label_options: Opsiyalar + label_overall_activity: GörülÉ™n iÅŸlÉ™rin toplu hesabatı + label_overview: Baxış + label_password_lost: Parolun bÉ™rpası + label_permissions_report: GiriÅŸ hüquqları üzrÉ™ hesabat + label_permissions: GiriÅŸ hüquqları + label_per_page: SÉ™hifÉ™yÉ™ + label_personalize_page: bu sÉ™hifÉ™ni fÉ™rdiləşdirmÉ™k + label_planning: PlanlaÅŸdırma + label_please_login: XahiÅŸ edirik, daxil olun. + label_plugins: Modullar + label_precedes: növbÉ™ti + label_preferences: Üstünlük + label_preview: İlkin baxış + label_previous: ÆvvÉ™lki + label_profile: Profil + label_project: LayihÉ™ + label_project_all: Bütün layihÉ™lÉ™r + label_project_copy_notifications: LayihÉ™nin surÉ™tinin çıxarılması zamanı elektron poçt ilÉ™ bildiriÅŸ göndÉ™rmÉ™k + label_project_latest: Son layihÉ™lÉ™r + label_project_new: Yeni layihÉ™ + label_project_plural: LayihÉ™lÉ™r + label_project_plural2: layihÉ™ni + label_project_plural5: layihÉ™lÉ™ri + label_public_projects: Ümumi layihÉ™lÉ™r + label_query: Yadda saxlanılmış sorÄŸu + label_query_new: Yeni sorÄŸu + label_query_plural: Yadda saxlanılmış sorÄŸular + label_read: Oxu... + label_register: Qeydiyyat + label_registered_on: Qeydiyyatdan keçib + label_registration_activation_by_email: e-poçt üzrÉ™ hesabımın aktivləşdirilmÉ™si + label_registration_automatic_activation: uçot qeydlÉ™rinin avtomatik aktivləşdirilmÉ™si + label_registration_manual_activation: uçot qeydlÉ™rini É™l ilÉ™ aktivləşdirmÉ™k + label_related_issues: ÆlaqÉ™li tapşırıqlar + label_relates_to: É™laqÉ™lidir + label_relation_delete: ÆlaqÉ™ni silmÉ™k + label_relation_new: Yeni münasibÉ™t + label_renamed: adını dÉ™yiÅŸmÉ™k + label_reply_plural: Cavablar + label_report: Hesabat + label_report_plural: Hesabatlar + label_reported_issues: Yaradılan tapşırıqlar + label_repository: Saxlayıcı + label_repository_plural: Saxlayıcı + label_result_plural: NÉ™ticÉ™lÉ™r + label_reverse_chronological_order: Æks ardıcıllıqda + label_revision: Redaksiya + label_revision_plural: Redaksiyalar + label_roadmap: Operativ plan + label_roadmap_due_in: "%{value} müddÉ™tindÉ™" + label_roadmap_no_issues: bu versiya üçün tapşırıq yoxdur + label_roadmap_overdue: "gecikmÉ™ %{value}" + label_role: Rol + label_role_and_permissions: Rollar vÉ™ giriÅŸ hüquqları + label_role_new: Yeni rol + label_role_plural: Rollar + label_scm: Saxlayıcının tipi + label_search: Axtarış + label_search_titles_only: Ancaq adlarda axtarmaq + label_send_information: İstifadəçiyÉ™ uçot qeydlÉ™ri üzrÉ™ informasiyanı göndÉ™rmÉ™k + label_send_test_email: Yoxlama üçün email göndÉ™rmÉ™k + label_settings: Sazlamalar + label_show_completed_versions: BitmiÅŸ variantları göstÉ™rmÉ™k + label_sort: ÇeÅŸidlÉ™mÉ™k + label_sort_by: "%{value} üzrÉ™ çeÅŸidlÉ™mÉ™k" + label_sort_higher: Yuxarı + label_sort_highest: ÆvvÉ™lÉ™ qayıt + label_sort_lower: AÅŸağı + label_sort_lowest: Sona qayıt + label_spent_time: SÉ™rf olunan vaxt + label_start_to_end: É™vvÉ™ldÉ™n axıra doÄŸru + label_start_to_start: É™vvÉ™ldÉ™n É™vvÉ™lÉ™ doÄŸru + label_statistics: Statistika + label_stay_logged_in: SistemdÉ™ qalmaq + label_string: MÉ™tn + label_subproject_plural: AltlayihÉ™lÉ™r + label_subtask_plural: Alt tapşırıqlar + label_text: Uzun mÉ™tn + label_theme: Mövzu + label_this_month: bu ay + label_this_week: bu hÉ™ftÉ™ + label_this_year: bu il + label_time_tracking: Vaxtın uçotu + label_timelog_today: Bu günÉ™ sÉ™rf olunan vaxt + label_today: bu gün + label_topic_plural: Mövzular + label_total: CÉ™mi + label_tracker: Treker + label_tracker_new: Yeni treker + label_tracker_plural: TrekerlÉ™r + label_updated_time: "%{value} É™vvÉ™l yenilÉ™nib" + label_updated_time_by: "%{author} %{age} É™vvÉ™l yenilÉ™nib" + label_used_by: İstifadÉ™ olunur + label_user: İstifasdəçi + label_user_activity: "İstifadəçinin gördüyü iÅŸlÉ™r %{value}" + label_user_mail_no_self_notified: "TÉ™rÉ™fimdÉ™n edilÉ™n dÉ™yiÅŸikliklÉ™r haqqında mÉ™ni xÉ™bÉ™rdar etmÉ™mÉ™k" + label_user_mail_option_all: "MÉ™nim layihÉ™lÉ™rimdÉ™ki bütün hadisÉ™lÉ™r haqqında" + label_user_mail_option_selected: "Yalnız seçilÉ™n layihÉ™dÉ™ki bütün hadisÉ™lÉ™r haqqında..." + label_user_mail_option_only_owner: Yalnız sahibi olduÄŸum obyektlÉ™r üçün + label_user_mail_option_only_my_events: Yalnız izlÉ™diyim vÉ™ ya iÅŸtirak etdiyim obyektlÉ™r üçün + label_user_mail_option_only_assigned: Yalnız mÉ™nÉ™ tÉ™yin edilÉ™n obyektlÉ™r üçün + label_user_new: Yeni istifadəçi + label_user_plural: İstifadəçilÉ™r + label_version: Variant + label_version_new: Yeni variant + label_version_plural: Variantlar + label_view_diff: FÉ™rqlÉ™rÉ™ baxmaq + label_view_revisions: Redaksiyalara baxmaq + label_watched_issues: Tapşırığın izlÉ™nilmÉ™si + label_week: HÉ™ftÉ™ + label_wiki: Wiki + label_wiki_edit: Wiki-nin redaktÉ™si + label_wiki_edit_plural: Wiki + label_wiki_page: Wiki sÉ™hifÉ™si + label_wiki_page_plural: Wiki sÉ™hifÉ™lÉ™ri + label_workflow: GörülÉ™n iÅŸlÉ™rin ardıcıllığı + label_x_closed_issues_abbr: + zero: "0 baÄŸlıdır" + one: "1 baÄŸlanıb" + few: "%{count} baÄŸlıdır" + many: "%{count} baÄŸlıdır" + other: "%{count} baÄŸlıdır" + label_x_comments: + zero: "ÅŸÉ™rh yoxdur" + one: "1 ÅŸÉ™rh" + few: "%{count} ÅŸÉ™rhlÉ™r" + many: "%{count} ÅŸÉ™rh" + other: "%{count} ÅŸÉ™rh" + label_x_open_issues_abbr: + zero: "0 açıqdır" + one: "1 açıq" + few: "%{count} açıqdır" + many: "%{count} açıqdır" + other: "%{count} açıqdır" + label_x_open_issues_abbr_on_total: + zero: "0 açıqdır / %{total}" + one: "1 açıqdır / %{total}" + few: "%{count} açıqdır / %{total}" + many: "%{count} açıqdır / %{total}" + other: "%{count} açıqdır / %{total}" + label_x_projects: + zero: "layihÉ™lÉ™r yoxdur" + one: "1 layihÉ™" + few: "%{count} layihÉ™" + many: "%{count} layihÉ™" + other: "%{count} layihÉ™" + label_year: İl + label_yesterday: dünÉ™n + + mail_body_account_activation_request: "Yeni istifadəçi qeydiyyatdan keçib (%{value}). Uçot qeydi Sizin tÉ™sdiqinizi gözlÉ™yir:" + mail_body_account_information: Sizin uçot qeydiniz haqqında informasiya + mail_body_account_information_external: "Siz özünüzün %{value} uçot qeydinizi giriÅŸ üçün istifadÉ™ edÉ™ bilÉ™rsiniz." + mail_body_lost_password: 'Parolun dÉ™yiÅŸdirilmÉ™si üçün aÅŸağıdakı linkÉ™ keçin:' + mail_body_register: 'Uçot qeydinin aktivləşdirilmÉ™si üçün aÅŸağıdakı linkÉ™ keçin:' + mail_body_reminder: "növbÉ™ti %{days} gün üçün SizÉ™ tÉ™yin olunan %{count}:" + mail_subject_account_activation_request: "SistemdÉ™ istifadəçinin aktivləşdirilmÉ™si üçün sorÄŸu %{value}" + mail_subject_lost_password: "Sizin %{value} parolunuz" + mail_subject_register: "Uçot qeydinin aktivləşdirilmÉ™si %{value}" + mail_subject_reminder: "yaxın %{days} gün üçün SizÉ™ tÉ™yin olunan %{count}" + + notice_account_activated: Sizin uçot qeydiniz aktivləşdirilib. SistemÉ™ daxil ola bilÉ™rsiniz. + notice_account_invalid_creditentials: İstifadəçi adı vÉ™ ya parolu düzgün deyildir + notice_account_lost_email_sent: SizÉ™ yeni parolun seçimi ilÉ™ baÄŸlı tÉ™limatı É™ks etdirÉ™n mÉ™ktub göndÉ™rilmiÅŸdir. + notice_account_password_updated: Parol müvÉ™ffÉ™qiyyÉ™tlÉ™ yenilÉ™ndi. + notice_account_pending: "Sizin uçot qeydiniz yaradıldı vÉ™ inzibatçının tÉ™sdiqini gözlÉ™yir." + notice_account_register_done: Uçot qeydi müvÉ™ffÉ™qiyyÉ™tlÉ™ yaradıldı. Sizin uçot qeydinizin aktivləşdirilmÉ™si üçün elektron poçtunuza göndÉ™rilÉ™n linkÉ™ keçin. + notice_account_unknown_email: NamÉ™lum istifadəçi. + notice_account_updated: Uçot qeydi müvÉ™ffÉ™qiyyÉ™tlÉ™ yenilÉ™ndi. + notice_account_wrong_password: Parol düzgün deyildir + notice_can_t_change_password: Bu uçot qeydi üçün xarici autentifikasiya mÉ™nbÉ™yi istifadÉ™ olunur. Parolu dÉ™yiÅŸmÉ™k mümkün deyildir. + notice_default_data_loaded: Susmaya görÉ™ konfiqurasiya yüklÉ™nilmiÅŸdir. + notice_email_error: "MÉ™ktubun göndÉ™rilmÉ™si zamanı sÉ™hv baÅŸ vermiÅŸdi (%{value})" + notice_email_sent: "MÉ™ktub göndÉ™rilib %{value}" + notice_failed_to_save_issues: "SeçilÉ™n %{total} içərisindÉ™n %{count} bÉ™ndlÉ™ri saxlamaq mümkün olmadı: %{ids}." + notice_failed_to_save_members: "İştirakçını (ları) yadda saxlamaq mümkün olmadı: %{errors}." + notice_feeds_access_key_reseted: Sizin Atom giriÅŸ açarınız sıfırlanmışdır. + notice_file_not_found: Daxil olmaÄŸa çalışdığınız sÉ™hifÉ™ mövcud deyildir vÉ™ ya silinib. + notice_locking_conflict: İnformasiya digÉ™r istifadəçi tÉ™rÉ™findÉ™n yenilÉ™nib. + notice_no_issue_selected: "Heç bir tapşırıq seçilmÉ™yib! XahiÅŸ edirik, redaktÉ™ etmÉ™k istÉ™diyiniz tapşırığı qeyd edin." + notice_not_authorized: Sizin bu sÉ™hifÉ™yÉ™ daxil olmaq hüququnuz yoxdur. + notice_successful_connection: QoÅŸulma müvÉ™ffÉ™qiyyÉ™tlÉ™ yerinÉ™ yetirilib. + notice_successful_create: Yaratma müvÉ™ffÉ™qiyyÉ™tlÉ™ yerinÉ™ yetirildi. + notice_successful_delete: SilinmÉ™ müvÉ™ffÉ™qiyyÉ™tlÉ™ yerinÉ™ yetirildi. + notice_successful_update: YenilÉ™mÉ™ müvÉ™ffÉ™qiyyÉ™tlÉ™ yerinÉ™ yetirildi. + notice_unable_delete_version: Variantı silmÉ™k mümkün olmadı. + + permission_add_issues: Tapşırıqların É™lavÉ™ edilmÉ™si + permission_add_issue_notes: QeydlÉ™rin É™lavÉ™ edilmÉ™si + permission_add_issue_watchers: NÉ™zarÉ™tçilÉ™rin É™lavÉ™ edilmÉ™si + permission_add_messages: MÉ™lumatların göndÉ™rilmÉ™si + permission_browse_repository: Saxlayıcıya baxış + permission_comment_news: XÉ™bÉ™rlÉ™rÉ™ ÅŸÉ™rh + permission_commit_access: Saxlayıcıda faylların dÉ™yiÅŸdirilmÉ™si + permission_delete_issues: Tapşırıqların silinmÉ™si + permission_delete_messages: MÉ™lumatların silinmÉ™si + permission_delete_own_messages: Şəxsi mÉ™lumatların silinmÉ™si + permission_delete_wiki_pages: Wiki-sÉ™hifÉ™lÉ™rin silinmÉ™si + permission_delete_wiki_pages_attachments: BÉ™rkidilÉ™n faylların silinmÉ™si + permission_edit_issue_notes: QeydlÉ™rin redaktÉ™ edilmÉ™si + permission_edit_issues: Tapşırıqların redaktÉ™ edilmÉ™si + permission_edit_messages: MÉ™lumatların redaktÉ™ edilmÉ™si + permission_edit_own_issue_notes: Şəxsi qeydlÉ™rin redaktÉ™ edilmÉ™si + permission_edit_own_messages: Şəxsi mÉ™lumatların redaktÉ™ edilmÉ™si + permission_edit_own_time_entries: Şəxsi vaxt uçotunun redaktÉ™ edilmÉ™si + permission_edit_project: LayihÉ™lÉ™rin redaktÉ™ edilmÉ™si + permission_edit_time_entries: Vaxt uçotunun redaktÉ™ edilmÉ™si + permission_edit_wiki_pages: Wiki-sÉ™hifÉ™nin redaktÉ™ edilmÉ™si + permission_export_wiki_pages: Wiki-sÉ™hifÉ™nin ixracı + permission_log_time: SÉ™rf olunan vaxtın uçotu + permission_view_changesets: Saxlayıcı dÉ™yiÅŸikliklÉ™rinÉ™ baxış + permission_view_time_entries: SÉ™rf olunan vaxta baxış + permission_manage_project_activities: LayihÉ™ üçün hÉ™rÉ™kÉ™t tiplÉ™rinin idarÉ™ edilmÉ™si + permission_manage_boards: Forumların idarÉ™ edilmÉ™si + permission_manage_categories: Tapşırıq kateqoriyalarının idarÉ™ edilmÉ™si + permission_manage_files: Faylların idarÉ™ edilmÉ™si + permission_manage_issue_relations: Tapşırıq baÄŸlantılarının idarÉ™ edilmÉ™si + permission_manage_members: İştirakçıların idarÉ™ edilmÉ™si + permission_manage_news: XÉ™bÉ™rlÉ™rin idarÉ™ edilmÉ™si + permission_manage_public_queries: Ümumi sorÄŸuların idarÉ™ edilmÉ™si + permission_manage_repository: Saxlayıcının idarÉ™ edilmÉ™si + permission_manage_subtasks: Alt tapşırıqların idarÉ™ edilmÉ™si + permission_manage_versions: Variantların idarÉ™ edilmÉ™si + permission_manage_wiki: Wiki-nin idarÉ™ edilmÉ™si + permission_move_issues: Tapşırıqların köçürülmÉ™si + permission_protect_wiki_pages: Wiki-sÉ™hifÉ™lÉ™rin bloklanması + permission_rename_wiki_pages: Wiki-sÉ™hifÉ™lÉ™rin adının dÉ™yiÅŸdirilmÉ™si + permission_save_queries: SorÄŸuların yadda saxlanılması + permission_select_project_modules: LayihÉ™ modulunun seçimi + permission_view_calendar: TÉ™qvimÉ™ baxış + permission_view_documents: SÉ™nÉ™dlÉ™rÉ™ baxış + permission_view_files: Fayllara baxış + permission_view_gantt: Qant diaqramına baxış + permission_view_issue_watchers: NÉ™zarÉ™tçilÉ™rin siyahılarına baxış + permission_view_messages: MÉ™lumatlara baxış + permission_view_wiki_edits: Wiki tarixçəsinÉ™ baxış + permission_view_wiki_pages: Wiki-yÉ™ baxış + + project_module_boards: Forumlar + project_module_documents: SÉ™nÉ™dlÉ™r + project_module_files: Fayllar + project_module_issue_tracking: Tapşırıqlar + project_module_news: XÉ™bÉ™rlÉ™r + project_module_repository: Saxlayıcı + project_module_time_tracking: Vaxtın uçotu + project_module_wiki: Wiki + project_module_gantt: Qant diaqramı + project_module_calendar: TÉ™qvim + + setting_activity_days_default: GörülÉ™n iÅŸlÉ™rdÉ™ É™ks olunan günlÉ™rin sayı + setting_app_subtitle: ÆlavÉ™nin sÉ™rlövhÉ™si + setting_app_title: ÆlavÉ™nin adı + setting_attachment_max_size: YerləşdirmÉ™nin maksimal ölçüsü + setting_autofetch_changesets: Saxlayıcının dÉ™yiÅŸikliklÉ™rini avtomatik izlÉ™mÉ™k + setting_autologin: Avtomatik giriÅŸ + setting_bcc_recipients: Gizli surÉ™tlÉ™ri istifadÉ™ etmÉ™k (BCC) + setting_cache_formatted_text: FormatlaÅŸdırılmış mÉ™tnin heÅŸlÉ™nmÉ™si + setting_commit_fix_keywords: Açar sözlÉ™rin tÉ™yini + setting_commit_ref_keywords: Axtarış üçün açar sözlÉ™r + setting_cross_project_issue_relations: LayihÉ™lÉ™r üzrÉ™ tapşırıqların kÉ™siÅŸmÉ™sinÉ™ icazÉ™ vermÉ™k + setting_date_format: Tarixin formatı + setting_default_language: Susmaya görÉ™ dil + setting_default_notification_option: Susmaya görÉ™ xÉ™bÉ™rdarlıq üsulu + setting_default_projects_public: Yeni layihÉ™lÉ™r ümumaçıq hesab edilir + setting_diff_max_lines_displayed: diff üçün sÉ™tirlÉ™rin maksimal sayı + setting_display_subprojects_issues: Susmaya görÉ™ altlayihÉ™lÉ™rin É™ks olunması + setting_emails_footer: MÉ™ktubun sÉ™tiraltı qeydlÉ™ri + setting_enabled_scm: Daxil edilÉ™n SCM + setting_feeds_limit: Atom axını üçün baÅŸlıqların sayının mÉ™hdudlaÅŸdırılması + setting_file_max_size_displayed: Æks olunma üçün mÉ™tn faylının maksimal ölçüsü + setting_gravatar_enabled: İstifadəçi avatarını Gravatar-dan istifadÉ™ etmÉ™k + setting_host_name: Kompyuterin adı + setting_issue_list_default_columns: Susmaya görÉ™ tapşırıqların siyahısında É™ks oluna sütunlar + setting_issues_export_limit: İxrac olunan tapşırıqlar üzrÉ™ mÉ™hdudiyyÉ™tlÉ™r + setting_login_required: Autentifikasiya vacibdir + setting_mail_from: Çıxan e-poçt ünvanı + setting_mail_handler_api_enabled: Daxil olan mÉ™lumatlar üçün veb-servisi qoÅŸmaq + setting_mail_handler_api_key: API açar + setting_openid: GiriÅŸ vÉ™ qeydiyyat üçün OpenID izacÉ™ vermÉ™k + setting_per_page_options: SÉ™hifÉ™ üçün qeydlÉ™rin sayı + setting_plain_text_mail: Yalnız sadÉ™ mÉ™tn (HTML olmadan) + setting_protocol: Protokol + setting_repository_log_display_limit: DÉ™yiÅŸikliklÉ™r jurnalında É™ks olunan redaksiyaların maksimal sayı + setting_self_registration: Özünüqeydiyyat + setting_sequential_project_identifiers: LayihÉ™lÉ™rin ardıcıl identifikatorlarını generasiya etmÉ™k + setting_sys_api_enabled: Saxlayıcının idarÉ™ edilmÉ™si üçün veb-servisi qoÅŸmaq + setting_text_formatting: MÉ™tnin formatlaÅŸdırılması + setting_time_format: Vaxtın formatı + setting_user_format: Adın É™ks olunma formatı + setting_welcome_text: Salamlama mÉ™tni + setting_wiki_compression: Wiki tarixçəsinin sıxlaÅŸdırılması + + status_active: aktivdir + status_locked: bloklanıb + status_registered: qeydiyyatdan keçib + + text_are_you_sure: Siz É™minsinizmi? + text_assign_time_entries_to_project: Qeydiyyata alınmış vaxtı layihÉ™yÉ™ bÉ™rkitmÉ™k + text_caracters_maximum: "Maksimum %{count} simvol." + text_caracters_minimum: "%{count} simvoldan az olmamalıdır." + text_comma_separated: Bir neçə qiymÉ™t mümkündür (vergül vasitÉ™silÉ™). + text_custom_field_possible_values_info: 'HÉ™r sÉ™tirÉ™ bir qiymÉ™t' + text_default_administrator_account_changed: İnzibatçının uçot qeydi susmaya görÉ™ dÉ™yiÅŸmiÅŸdir + text_destroy_time_entries_question: "Bu tapşırıq üçün sÉ™rf olunan vaxta görÉ™ %{hours} saat qeydiyyata alınıb. Siz nÉ™ etmÉ™k istÉ™yirsiniz?" + text_destroy_time_entries: Qeydiyyata alınmış vaxtı silmÉ™k + text_diff_truncated: '... Bu diff mÉ™hduddur, çünki É™ks olunan maksimal ölçünü keçir.' + text_email_delivery_not_configured: "Poçt serveri ilÉ™ iÅŸin parametrlÉ™ri sazlanmayıb vÉ™ e-poçt ilÉ™ bildiriÅŸ funksiyası aktiv deyildir.\nSizin SMTP-server üçün parametrlÉ™ri config/configuration.yml faylından sazlaya bilÉ™rsiniz. DÉ™yiÅŸikliklÉ™rin tÉ™tbiq edilmÉ™si üçün É™lavÉ™ni yenidÉ™n baÅŸladın." + text_enumeration_category_reassign_to: 'Onlara aÅŸağıdakı qiymÉ™tlÉ™ri tÉ™yin etmÉ™k:' + text_enumeration_destroy_question: "%{count} obyekt bu qiymÉ™tlÉ™ baÄŸlıdır." + text_file_repository_writable: QeydÉ™ giriÅŸ imkanı olan saxlayıcı + text_issue_added: "Yeni tapşırıq yaradılıb %{id} (%{author})." + text_issue_category_destroy_assignments: Kateqoriyanın tÉ™yinatını silmÉ™k + text_issue_category_destroy_question: "Bir neçə tapşırıq (%{count}) bu kateqoriya üçün tÉ™yin edilib. Siz nÉ™ etmÉ™k istÉ™yirsiniz?" + text_issue_category_reassign_to: Bu kateqoriya üçün tapşırığı yenidÉ™n tÉ™yin etmÉ™k + text_issues_destroy_confirmation: 'SeçilÉ™n tapşırıqları silmÉ™k istÉ™diyinizÉ™ É™minsinizmi?' + text_issues_ref_in_commit_messages: MÉ™lumatın mÉ™tnindÉ™n çıxış edÉ™rÉ™k tapşırıqların statuslarının tutuÅŸdurulması vÉ™ dÉ™yiÅŸdirilmÉ™si + text_issue_updated: "Tapşırıq %{id} yenilÉ™nib (%{author})." + text_journal_changed: "Parametr %{label} %{old} - %{new} dÉ™yiÅŸib" + text_journal_deleted: "Parametrin %{old} qiymÉ™ti %{label} silinib" + text_journal_set_to: "%{label} parametri %{value} dÉ™yiÅŸib" + text_length_between: "%{min} vÉ™ %{max} simvollar arasındakı uzunluq." + text_load_default_configuration: Susmaya görÉ™ konfiqurasiyanı yüklÉ™mÉ™k + text_min_max_length_info: 0 mÉ™hdudiyyÉ™tlÉ™rin olmadığını bildirir + text_no_configuration_data: "Rollar, trekerlÉ™r, tapşırıqların statusları vÉ™ operativ plan konfiqurasiya olunmayıblar.\nSusmaya görÉ™ konfiqurasiyanın yüklÉ™nmÉ™si tÉ™kidlÉ™ xahiÅŸ olunur. Siz onu sonradan dÉ™yiÅŸÉ™ bilÉ™rsiniz." + text_plugin_assets_writable: Modullar kataloqu qeyd üçün açıqdır + text_project_destroy_confirmation: Siz bu layihÉ™ vÉ™ ona aid olan bütün informasiyanı silmÉ™k istÉ™diyinizÉ™ É™minsinizmi? + text_reassign_time_entries: 'Qeydiyyata alınmış vaxtı aÅŸağıdakı tapşırığa keçir:' + text_regexp_info: "mÉ™sÉ™lÉ™n: ^[A-Z0-9]+$" + text_repository_usernames_mapping: "Saxlayıcının jurnalında tapılan adlarla baÄŸlı olan Redmine istifadəçisini seçin vÉ™ ya yenilÉ™yin.\nEyni ad vÉ™ e-poçta sahib olan istifadəçilÉ™r Redmine vÉ™ saxlayıcıda avtomatik É™laqÉ™lÉ™ndirilir." + text_rmagick_available: RMagick istifadÉ™si mümkündür (opsional olaraq) + text_select_mail_notifications: Elektron poçta bildiriÅŸlÉ™rin göndÉ™rilmÉ™si seçim edÉ™cÉ™yiniz hÉ™rÉ™kÉ™tlÉ™rdÉ™n asılıdır. + text_select_project_modules: 'LayihÉ™dÉ™ istifadÉ™ olunacaq modulları seçin:' + text_status_changed_by_changeset: "%{value} redaksiyada reallaÅŸdırılıb." + text_subprojects_destroy_warning: "AltlayihÉ™lÉ™r: %{value} hÉ™mçinin silinÉ™cÉ™k." + text_tip_issue_begin_day: tapşırığın baÅŸlanğıc tarixi + text_tip_issue_begin_end_day: elÉ™ hÉ™min gün tapşırığın baÅŸlanğıc vÉ™ bitmÉ™ tarixi + text_tip_issue_end_day: tapşırığın baÅŸa çatma tarixi + text_tracker_no_workflow: Bu treker üçün hÉ™rÉ™kÉ™tlÉ™rin ardıcıllığı müəyyÉ™n edimÉ™yib + text_unallowed_characters: QadaÄŸan edilmiÅŸ simvollar + text_user_mail_option: "SeçilmÉ™yÉ™n layihÉ™lÉ™r üçün Siz yalnız baxdığınız vÉ™ ya iÅŸtirak etdiyiniz layihÉ™lÉ™r barÉ™dÉ™ bildiriÅŸ alacaqsınız mÉ™sÉ™lÉ™n, müəllifi olduÄŸunuz layihÉ™lÉ™r vÉ™ ya o layihÉ™lÉ™r ki, SizÉ™ tÉ™yin edilib)." + text_user_wrote: "%{value} yazıb:" + text_wiki_destroy_confirmation: Siz bu Wiki vÉ™ onun tÉ™rkibindÉ™kilÉ™ri silmÉ™k istÉ™diyinizÉ™ É™minsinizmi? + text_workflow_edit: VÉ™ziyyÉ™tlÉ™rin ardıcıllığını redaktÉ™ etmÉ™k üçün rol vÉ™ trekeri seçin + + warning_attachments_not_saved: "faylın (ların) %{count} yadda saxlamaq mümkün deyildir." + text_wiki_page_destroy_question: Bu sÉ™hifÉ™ %{descendants} yaxın vÉ™ çox yaxın sÉ™hifÉ™lÉ™rÉ™ malikdir. Siz nÉ™ etmÉ™k istÉ™yirsiniz? + text_wiki_page_reassign_children: Cari sÉ™hifÉ™ üçün yaxın sÉ™hifÉ™lÉ™ri yenidÉ™n tÉ™yin etmÉ™k + text_wiki_page_nullify_children: Yaxın sÉ™hifÉ™lÉ™ri baÅŸ sÉ™hifÉ™lÉ™r etmÉ™k + text_wiki_page_destroy_children: Yaxın vÉ™ çox yaxın sÉ™hifÉ™lÉ™ri silmÉ™k + setting_password_min_length: Parolun minimal uzunluÄŸu + field_group_by: NÉ™ticÉ™lÉ™ri qruplaÅŸdırmaq + mail_subject_wiki_content_updated: "Wiki-sÉ™hifÉ™ '%{id}' yenilÉ™nmiÅŸdir" + label_wiki_content_added: Wiki-sÉ™hifÉ™ É™lavÉ™ olunub + mail_subject_wiki_content_added: "Wiki-sÉ™hifÉ™ '%{id}' É™lavÉ™ edilib" + mail_body_wiki_content_added: "%{author} Wiki-sÉ™hifÉ™ni '%{id}' É™lavÉ™ edib." + label_wiki_content_updated: Wiki-sÉ™hifÉ™ yenilÉ™nib + mail_body_wiki_content_updated: "%{author} Wiki-sÉ™hifÉ™ni '%{id}' yenilÉ™yib." + permission_add_project: LayihÉ™nin yaradılması + setting_new_project_user_role_id: LayihÉ™ni yaradan istifadəçiyÉ™ tÉ™yin olunan rol + label_view_all_revisions: Bütün yoxlamaları göstÉ™rmÉ™k + label_tag: NiÅŸan + label_branch: ŞöbÉ™ + error_no_tracker_in_project: Bu layihÉ™ ilÉ™ heç bir treker assosiasiya olunmayıb. LayihÉ™nin sazlamalarını yoxlayın. + error_no_default_issue_status: Susmaya görÉ™ tapşırıqların statusu müəyyÉ™n edilmÉ™yib. Sazlamaları yoxlayın (bax. "İnzibatçılıq -> Tapşırıqların statusu"). + label_group_plural: Qruplar + label_group: Qrup + label_group_new: Yeni qrup + label_time_entry_plural: SÉ™rf olunan vaxt + text_journal_added: "%{label} %{value} É™lavÉ™ edilib" + field_active: Aktiv + enumeration_system_activity: Sistemli + permission_delete_issue_watchers: NÉ™zarÉ™tçilÉ™rin silinmÉ™si + version_status_closed: BaÄŸlanıb + version_status_locked: bloklanıb + version_status_open: açıqdır + error_can_not_reopen_issue_on_closed_version: BaÄŸlı varianta tÉ™yin edilÉ™n tapşırıq yenidÉ™n açıq ola bilmÉ™z + label_user_anonymous: Anonim + button_move_and_follow: YerləşdirmÉ™k vÉ™ keçid + setting_default_projects_modules: Yeni layihÉ™lÉ™r üçün susmaya görÉ™ daxil edilÉ™n modullar + setting_gravatar_default: Susmaya görÉ™ Gravatar tÉ™sviri + field_sharing: BirgÉ™ istifadÉ™ + label_version_sharing_hierarchy: LayihÉ™lÉ™rin iyerarxiyasına görÉ™ + label_version_sharing_system: bütün layihÉ™lÉ™r ilÉ™ + label_version_sharing_descendants: Alt layihÉ™lÉ™r ilÉ™ + label_version_sharing_tree: LayihÉ™lÉ™rin iyerarxiyası ilÉ™ + label_version_sharing_none: BirgÉ™ istifadÉ™ olmadan + error_can_not_archive_project: Bu layihÉ™ arxivləşdirilÉ™ bilmÉ™z + button_duplicate: TÉ™krarlamaq + button_copy_and_follow: SurÉ™tini çıxarmaq vÉ™ davam etmÉ™k + label_copy_source: MÉ™nbÉ™ + setting_issue_done_ratio: SahÉ™nin kömÉ™yi ilÉ™ tapşırığın hazırlığını nÉ™zÉ™rÉ™ almaq + setting_issue_done_ratio_issue_status: Tapşırığın statusu + error_issue_done_ratios_not_updated: Tapşırıqların hazırlıq parametri yenilÉ™nmÉ™yib + error_workflow_copy_target: MÉ™qsÉ™dÉ™ uyÄŸun trekerlÉ™ri vÉ™ rolları seçin + setting_issue_done_ratio_issue_field: Tapşırığın hazırlıq sÉ™viyyÉ™si + label_copy_same_as_target: MÉ™qsÉ™ddÉ™ olduÄŸu kimi + label_copy_target: MÉ™qsÉ™d + notice_issue_done_ratios_updated: Parametr «hazırlıq» yenilÉ™nib. + error_workflow_copy_source: Cari trekeri vÉ™ ya rolu seçin + label_update_issue_done_ratios: Tapşırığın hazırlıq sÉ™viyyÉ™sini yenilÉ™mÉ™k + setting_start_of_week: HÉ™ftÉ™nin birinci günü + label_api_access_key: API-yÉ™ giriÅŸ açarı + text_line_separated: Bİr neçə qiymÉ™t icazÉ™ verilib (hÉ™r sÉ™tirÉ™ bir qiymÉ™t). + label_revision_id: Yoxlama %{value} + permission_view_issues: Tapşırıqlara baxış + label_display_used_statuses_only: Yalnız bu trekerdÉ™ istifadÉ™ olunan statusları É™ks etdirmÉ™k + label_api_access_key_created_on: API-yÉ™ giriÅŸ açarı %{value} É™vvÉ™l aradılıb + label_feeds_access_key: Atom giriÅŸ açarı + notice_api_access_key_reseted: Sizin API giriÅŸ açarınız sıfırlanıb. + setting_rest_api_enabled: REST veb-servisini qoÅŸmaq + button_show: GöstÉ™rmÉ™k + label_missing_api_access_key: API-yÉ™ giriÅŸ açarı mövcud deyildir + label_missing_feeds_access_key: Atom-É™ giriÅŸ açarı mövcud deyildir + setting_mail_handler_body_delimiters: Bu sÉ™tirlÉ™rin birindÉ™n sonra mÉ™ktubu qısaltmaq + permission_add_subprojects: Alt layihÉ™lÉ™rin yaradılması + label_subproject_new: Yeni alt layihÉ™ + text_own_membership_delete_confirmation: |- + Siz bÉ™zi vÉ™ ya bütün hüquqları silmÉ™yÉ™ çalışırsınız, nÉ™ticÉ™dÉ™ bu layihÉ™ni redaktÉ™ etmÉ™k hüququnu da itirÉ™ bilÉ™rsiniz. Davam etmÉ™k istÉ™diyinizÉ™ É™minsinizmi? + + label_close_versions: BaÅŸa çatmış variantları baÄŸlamaq + label_board_sticky: BÉ™rkidilib + label_board_locked: Bloklanıb + field_principal: Ad + text_zoom_out: UzaqlaÅŸdırmaq + text_zoom_in: YaxınlaÅŸdırmaq + notice_unable_delete_time_entry: Jurnalın qeydini silmÉ™k mümkün deyildir. + label_overall_spent_time: CÉ™mi sÉ™rf olunan vaxt + label_user_mail_option_none: HadisÉ™ yoxdur + field_member_of_group: TÉ™yin olunmuÅŸ qrup + field_assigned_to_role: TÉ™yin olunmuÅŸ rol + notice_not_authorized_archived_project: SorÄŸulanan layihÉ™ arxivləşdirilib. + label_principal_search: "İstifadəçini vÉ™ ya qrupu tapmaq:" + label_user_search: "İstifadəçini tapmaq:" + field_visible: GörünmÉ™ dÉ™rÉ™cÉ™si + setting_emails_header: MÉ™ktubun baÅŸlığı + + setting_commit_logtime_activity_id: Vaxtın uçotu üçün görülÉ™n hÉ™rÉ™kÉ™tlÉ™r + text_time_logged_by_changeset: "%{value} redaksiyada nÉ™zÉ™rÉ™ alınıb." + setting_commit_logtime_enabled: Vaxt uçotunu qoÅŸmaq + notice_gantt_chart_truncated: Æks oluna bilÉ™cÉ™k elementlÉ™rin maksimal sayı artdığına görÉ™ diaqram kÉ™silÉ™cÉ™k (%{max}) + setting_gantt_items_limit: Qant diaqramında É™ks olunan elementlÉ™rin maksimal sayı + field_warn_on_leaving_unsaved: Yadda saxlanılmayan mÉ™tnin sÉ™hifÉ™si baÄŸlanan zaman xÉ™bÉ™rdarlıq etmÉ™k + text_warn_on_leaving_unsaved: TÉ™rk etmÉ™k istÉ™diyiniz cari sÉ™hifÉ™dÉ™ yadda saxlanılmayan vÉ™ itÉ™ bilÉ™cÉ™k mÉ™tn vardır. + label_my_queries: MÉ™nim yadda saxlanılan sorÄŸularım + text_journal_changed_no_detail: "%{label} yenilÉ™nib" + label_news_comment_added: XÉ™bÉ™rÉ™ ÅŸÉ™rh É™lavÉ™ olunub + button_expand_all: Hamısını aç + button_collapse_all: Hamısını çevir + label_additional_workflow_transitions_for_assignee: İstifadəçi icraçı olduÄŸu zaman É™lavÉ™ keçidlÉ™r + label_additional_workflow_transitions_for_author: İstifadəçi müəllif olduÄŸu zaman É™lavÉ™ keçidlÉ™r + label_bulk_edit_selected_time_entries: SÉ™rf olunan vaxtın seçilÉ™n qeydlÉ™rinin kütlÉ™vi ÅŸÉ™kildÉ™ dÉ™yiÅŸdirilmÉ™si + text_time_entries_destroy_confirmation: Siz sÉ™rf olunan vaxtın seçilÉ™n qeydlÉ™rini silmÉ™k istÉ™diyinizÉ™ É™minsinizmi? + label_role_anonymous: Anonim + label_role_non_member: İştirakçı deyil + label_issue_note_added: Qeyd É™lavÉ™ olunub + label_issue_status_updated: Status yenilÉ™nib + label_issue_priority_updated: Prioritet yenilÉ™nib + label_issues_visibility_own: İstifadəçi üçün yaradılan vÉ™ ya ona tÉ™yin olunan tapşırıqlar + field_issues_visibility: Tapşırıqların görünmÉ™ dÉ™rÉ™cÉ™si + label_issues_visibility_all: Bütün tapşırıqlar + permission_set_own_issues_private: Şəxsi tapşırıqlar üçün görünmÉ™ dÉ™rÉ™cÉ™sinin (ümumi/ÅŸÉ™xsi) qurulması + field_is_private: Şəxsi + permission_set_issues_private: Tapşırıqlar üçün görünmÉ™ dÉ™rÉ™cÉ™sinin (ümumi/ÅŸÉ™xsi) qurulması + label_issues_visibility_public: Yalnız ümumi tapşırıqlar + text_issues_destroy_descendants_confirmation: HÉ™mçinin %{count} tapşırıq (lar) silinÉ™cÉ™k. + field_commit_logs_encoding: Saxlayıcıda ÅŸÉ™rhlÉ™rin kodlaÅŸdırılması + field_scm_path_encoding: Yolun kodlaÅŸdırılması + text_scm_path_encoding_note: "Susmaya görÉ™: UTF-8" + field_path_to_repository: Saxlayıcıya yol + field_root_directory: Kök direktoriya + field_cvs_module: Modul + field_cvsroot: CVSROOT + text_mercurial_repository_note: Lokal saxlayıcı (mÉ™sÉ™lÉ™n, /hgrepo, c:\hgrepo) + text_scm_command: Komanda + text_scm_command_version: Variant + label_git_report_last_commit: Fayllar vÉ™ direktoriyalar üçün son dÉ™yiÅŸikliklÉ™ri göstÉ™rmÉ™k + text_scm_config: Siz config/configuration.yml faylında SCM komandasını sazlaya bilÉ™rsiniz. XahiÅŸ olunur, bu faylın redaktÉ™sindÉ™n sonra É™lavÉ™ni iÅŸÉ™ salın. + text_scm_command_not_available: Variantların nÉ™zarÉ™t sisteminin komandasına giriÅŸ mümkün deyildir. XahiÅŸ olunur, inzibatçı panelindÉ™ki sazlamaları yoxlayın. + notice_issue_successful_create: Tapşırıq %{id} yaradılıb. + label_between: arasında + setting_issue_group_assignment: İstifadəçi qruplarına tÉ™yinata icazÉ™ vermÉ™k + label_diff: FÉ™rq(diff) + text_git_repository_note: "Saxlama yerini göstÉ™rin (mÉ™s: /gitrepo, c:\\gitrepo)" + description_query_sort_criteria_direction: ÇeÅŸidlÉ™mÉ™ qaydası + description_project_scope: LayihÉ™nin hÉ™cmi + description_filter: Filtr + description_user_mail_notification: E-poçt Mail xÉ™bÉ™rdarlıqlarının sazlaması + description_date_from: BaÅŸlama tarixini daxil edin + description_message_content: Mesajın kontenti + description_available_columns: Mövcud sütunlar + description_date_range_interval: TarixlÉ™r diapazonunu seçin + description_issue_category_reassign: MÉ™sÉ™lÉ™nin kateqoriyasını seçin + description_search: Axtarış sahÉ™si + description_notes: Qeyd + description_date_range_list: Siyahıdan diapazonu seçin + description_choose_project: LayihÉ™lÉ™r + description_date_to: YerinÉ™ yetirilmÉ™ tarixini daxil edin + description_query_sort_criteria_attribute: ÇeÅŸidlÉ™mÉ™ meyarları + description_wiki_subpages_reassign: Yeni valideyn sÉ™hifÉ™sini seçmÉ™k + description_selected_columns: SeçilmiÅŸ sütunlar + label_parent_revision: Valideyn + label_child_revision: Æsas + error_scm_annotate_big_text_file: MÉ™tn faylının maksimal ölçüsü artdığına görÉ™ ÅŸÉ™rh mümkün deyildir. + setting_default_issue_start_date_to_creation_date: Yeni tapşırıqlar üçün cari tarixi baÅŸlanğıc tarixi kimi istifadÉ™ etmÉ™k + button_edit_section: Bu bölmÉ™ni redaktÉ™ etmÉ™k + setting_repositories_encodings: ÆlavÉ™lÉ™rin vÉ™ saxlayıcıların kodlaÅŸdırılması + description_all_columns: Bütün sütunlar + button_export: İxrac + label_export_options: "%{export_format} ixracın parametrlÉ™ri" + error_attachment_too_big: Faylın maksimal ölçüsü artdığına görÉ™ bu faylı yüklÉ™mÉ™k mümkün deyildir (%{max_size}) + notice_failed_to_save_time_entries: "SÉ™hv N %{ids}. %{total} giriÅŸdÉ™n %{count} yaddaÅŸa saxlanıla bilmÉ™di." + label_x_issues: + zero: 0 Tapşırıq + one: 1 Tapşırıq + few: "%{count} Tapşırıq" + many: "%{count} Tapşırıq" + other: "%{count} Tapşırıq" + label_repository_new: Yeni saxlayıcı + field_repository_is_default: Susmaya görÉ™ saxlayıcı + label_copy_attachments: ÆlavÉ™nin surÉ™tini çıxarmaq + label_item_position: "%{position}/%{count}" + label_completed_versions: BaÅŸa çatdırılmış variantlar + text_project_identifier_info: Yalnız kiçik latın hÉ™rflÉ™rinÉ™ (a-z), rÉ™qÉ™mlÉ™rÉ™, tire vÉ™ çicgilÉ™rÉ™ icazÉ™ verilir.
    Yadda saxladıqdan sonra identifikatoru dÉ™yiÅŸmÉ™k olmaz. + field_multiple: Çoxsaylı qiymÉ™tlÉ™r + setting_commit_cross_project_ref: DigÉ™r bütün layihÉ™lÉ™rdÉ™ tapşırıqları düzÉ™ltmÉ™k vÉ™ istinad etmÉ™k + text_issue_conflict_resolution_add_notes: QeydlÉ™rimi É™lavÉ™ etmÉ™k vÉ™ mÉ™nim dÉ™yiÅŸikliklÉ™rimdÉ™n imtina etmÉ™k + text_issue_conflict_resolution_overwrite: DÉ™yiÅŸikliklÉ™rimi tÉ™tbiq etmÉ™k (É™vvÉ™lki bütün qeydlÉ™r yadda saxlanacaq, lakin bÉ™zi qeydlÉ™r yenidÉ™n yazıla bilÉ™r) + notice_issue_update_conflict: Tapşırığı redaktÉ™ etdiyiniz zaman kimsÉ™ onu artıq dÉ™yiÅŸib. + text_issue_conflict_resolution_cancel: MÉ™nim dÉ™yiÅŸikliklÉ™rimi ləğv etmÉ™k vÉ™ tapşırığı yenidÉ™n göstÉ™rmÉ™k %{link} + permission_manage_related_issues: ÆlaqÉ™li tapşırıqların idarÉ™ edilmÉ™si + field_auth_source_ldap_filter: LDAP filtri + label_search_for_watchers: NÉ™zarÉ™tçilÉ™ri axtarmaq + notice_account_deleted: "Sizin uçot qeydiniz tam olaraq silinib" + setting_unsubscribe: "İstifadəçilÉ™rÉ™ ÅŸÉ™xsi uçot qeydlÉ™rini silmÉ™yÉ™ icazÉ™ vermÉ™k" + button_delete_my_account: "MÉ™nim uçot qeydlÉ™rimi silmÉ™k" + text_account_destroy_confirmation: "Sizin uçot qeydiniz bir daha bÉ™rpa edilmÉ™dÉ™n tam olaraq silinÉ™cÉ™k.\nDavam etmÉ™k istÉ™diyinizÉ™ É™minsinizmi?" + error_session_expired: Sizin sessiya bitmiÅŸdir. XahiÅŸ edirik yenidÉ™n daxil olun. + text_session_expiration_settings: "DiqqÉ™t: bu sazlamaların dÉ™yiÅŸmÉ™yi cari sessiyanın baÄŸlanmasına çıxara bilÉ™r." + setting_session_lifetime: Sessiyanın maksimal Session maximum hÉ™yat müddÉ™ti + setting_session_timeout: Sessiyanın qeyri aktivlik müddÉ™ti + label_session_expiration: Sessiyanın bitmÉ™si + permission_close_project: LayihÉ™ni baÄŸla / yenidÉ™n aç + label_show_closed_projects: BaÄŸlı layihÉ™lÉ™rÉ™ baxmaq + button_close: BaÄŸla + button_reopen: YenidÉ™n aç + project_status_active: aktiv + project_status_closed: baÄŸlı + project_status_archived: arxiv + text_project_closed: Bu layihÉ™ baÄŸlıdı vÉ™ yalnız oxuma olar. + notice_user_successful_create: İstifadəçi %{id} yaradıldı. + field_core_fields: Standart sahÉ™lÉ™r + field_timeout: Zaman aşımı (saniyÉ™ ilÉ™) + setting_thumbnails_enabled: ÆlavÉ™lÉ™rin kiçik ÅŸÉ™klini göstÉ™r + setting_thumbnails_size: Kiçik ÅŸÉ™killÉ™rin ölçüsü (piksel ilÉ™) + label_status_transitions: Status keçidlÉ™ri + label_fields_permissions: SahÉ™lÉ™rin icazÉ™lÉ™ri + label_readonly: Ancaq oxumaq üçün + label_required: TÉ™lÉ™b olunur + text_repository_identifier_info: Yalnız kiçik latın hÉ™rflÉ™rinÉ™ (a-z), rÉ™qÉ™mlÉ™rÉ™, tire vÉ™ çicgilÉ™rÉ™ icazÉ™ verilir.
    Yadda saxladıqdan sonra identifikatoru dÉ™yiÅŸmÉ™k olmaz. + field_board_parent: Ana forum + label_attribute_of_project: LayihÉ™ %{name} + label_attribute_of_author: Müəllif %{name} + label_attribute_of_assigned_to: TÉ™yin edilib %{name} + label_attribute_of_fixed_version: Æsas versiya %{name} + label_copy_subtasks: Alt tapşırığın surÉ™tini çıxarmaq + label_cross_project_hierarchy: With project hierarchy + permission_edit_documents: Edit documents + button_hide: Hide + text_turning_multiple_off: If you disable multiple values, multiple values will be removed in order to preserve only one value per item. + label_any: any + label_cross_project_system: With all projects + label_last_n_weeks: last %{count} weeks + label_in_the_past_days: in the past + label_copied_to: Copied to + permission_set_notes_private: Set notes as private + label_in_the_next_days: in the next + label_attribute_of_issue: Issue's %{name} + label_any_issues_in_project: any issues in project + label_cross_project_descendants: With subprojects + field_private_notes: Private notes + setting_jsonp_enabled: Enable JSONP support + label_gantt_progress_line: Progress line + permission_add_documents: Add documents + permission_view_private_notes: View private notes + label_attribute_of_user: User's %{name} + permission_delete_documents: Delete documents + field_inherit_members: Inherit members + setting_cross_project_subtasks: Allow cross-project subtasks + label_no_issues_in_project: no issues in project + label_copied_from: Copied from + setting_non_working_week_days: Non-working days + label_any_issues_not_in_project: any issues not in project + label_cross_project_tree: With project tree + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: CÉ™mi + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/bg.yml --- a/config/locales/bg.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/bg.yml Tue Sep 09 09:34:53 2014 +0100 @@ -51,8 +51,8 @@ one: "около 1 чаÑ" other: "около %{count} чаÑа" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 чаÑ" + other: "%{count} чаÑа" x_days: one: "1 ден" other: "%{count} дена" @@ -130,6 +130,7 @@ not_same_project: "не е от ÑÑŠÑ‰Ð¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚" circular_dependency: "Тази Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ Ñ‰Ðµ доведе до безкрайна завиÑимоÑÑ‚" cant_link_an_issue_with_a_descendant: "Една задача не може да бъде Ñвързвана към ÑÐ²Ð¾Ñ Ð¿Ð¾Ð´Ð·Ð°Ð´Ð°Ñ‡Ð°" + earlier_than_minimum_start_date: "не може да бъде по-рано от %{date} поради предхождащи задачи" actionview_instancetag_blank_option: Изберете @@ -148,8 +149,12 @@ notice_account_invalid_creditentials: Ðевалиден потребител или парола. notice_account_password_updated: Паролата е уÑпешно променена. notice_account_wrong_password: Грешна парола - notice_account_register_done: Профилът е Ñъздаден уÑпешно. + notice_account_register_done: Профилът е Ñъздаден уÑпешно. E-mail, Ñъдържащ инÑтрукции за активиране на профила + е изпратен на %{email}. notice_account_unknown_email: Ðепознат e-mail. + notice_account_not_activated_yet: Вие не Ñте активирали Ð²Ð°ÑˆÐ¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð» вÑе още. Ðко иÑкате да + получите нов e-mail за активиране, Ð¼Ð¾Ð»Ñ Ð½Ð°Ñ‚Ð¸Ñнете тази връзка. + notice_account_locked: ВашиÑÑ‚ профил е блокиран. notice_can_t_change_password: Този профил е Ñ Ð²ÑŠÐ½ÑˆÐµÐ½ метод за оторизациÑ. Ðевъзможна ÑмÑна на паролата. notice_account_lost_email_sent: Изпратен ви е e-mail Ñ Ð¸Ð½Ñтрукции за избор на нова парола. notice_account_activated: Профилът ви е активиран. Вече може да влезете в ÑиÑтемата. @@ -163,7 +168,7 @@ notice_not_authorized_archived_project: Проектът, който Ñе опитвате да видите е архивиран. Ðко ÑмÑтате, че това не е правилно, обърнете Ñе към админиÑтратора за разархивиране. notice_email_sent: "Изпратен e-mail на %{value}" notice_email_error: "Грешка при изпращане на e-mail (%{value})" - notice_feeds_access_key_reseted: Ð’Ð°ÑˆÐ¸Ñ ÐºÐ»ÑŽÑ‡ за RSS доÑтъп беше променен. + notice_feeds_access_key_reseted: Ð’Ð°ÑˆÐ¸Ñ ÐºÐ»ÑŽÑ‡ за Atom доÑтъп беше променен. notice_api_access_key_reseted: ВашиÑÑ‚ API ключ за доÑтъп беше изчиÑтен. notice_failed_to_save_issues: "ÐеуÑпешен Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° %{count} задачи от %{total} избрани: %{ids}." notice_failed_to_save_time_entries: "ÐевъзможноÑÑ‚ за Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° %{count} запиÑа за използвано време от %{total} избрани: %{ids}." @@ -179,8 +184,9 @@ notice_issue_update_conflict: Задачата е била променена от друг потребител, докато вие Ñте Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð°Ð»Ð¸. notice_account_deleted: ВашиÑÑ‚ профил беше премахнат без възможноÑÑ‚ за възÑтановÑване. notice_user_successful_create: Потребител %{id} е Ñъздаден. + notice_new_password_must_be_different: Ðовата парола трÑбва да бъде различна от Ñегашната парола - error_can_t_load_default_data: "Грешка при зареждане на примерната информациÑ: %{value}" + error_can_t_load_default_data: "Грешка при зареждане на началната информациÑ: %{value}" error_scm_not_found: ÐеÑъщеÑтвуващ обект в хранилището. error_scm_command_failed: "Грешка при опит за ÐºÐ¾Ð¼ÑƒÐ½Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ðµ: %{value}" error_scm_annotate: "Обектът не ÑъщеÑтвува или не може да бъде анотиран." @@ -217,9 +223,6 @@ mail_subject_wiki_content_updated: "Wiki Ñтраницата '%{id}' беше обновена" mail_body_wiki_content_updated: Wiki Ñтраницата '%{id}' беше обновена от %{author}. - gui_validation_error: 1 грешка - gui_validation_error_plural: "%{count} грешки" - field_name: Име field_description: ОпиÑание field_summary: ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ @@ -233,6 +236,7 @@ field_author: Ðвтор field_created_on: От дата field_updated_on: Обновена + field_closed_on: Затворена field_field_format: Тип field_is_for_all: За вÑички проекти field_possible_values: Възможни ÑтойноÑти @@ -249,7 +253,7 @@ field_is_closed: Затворена задача field_is_default: СъÑтоÑние по подразбиране field_tracker: Тракер - field_subject: ОтноÑно + field_subject: Заглавие field_due_date: Крайна дата field_assigned_to: Възложена на field_priority: Приоритет @@ -333,6 +337,9 @@ field_timeout: Таймаут (в Ñекунди) field_board_parent: РодителÑки форум field_private_notes: Лични бележки + field_inherit_members: ÐаÑледÑване на членовете на родителÑÐºÐ¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚ + field_generate_password: Генериране на парола + field_must_change_passwd: Паролата трÑбва да бъде Ñменена при Ñледващото влизане в Redmine setting_app_title: Заглавие setting_app_subtitle: ОпиÑание @@ -361,7 +368,7 @@ setting_cross_project_subtasks: Подзадачи от други проекти setting_issue_list_default_columns: Показвани колони по подразбиране setting_repositories_encodings: Кодова таблица на прикачените файлове и хранилищата - setting_emails_header: Emails header + setting_emails_header: Email header setting_emails_footer: ПодтекÑÑ‚ за e-mail setting_protocol: Протокол setting_per_page_options: Опции за Ñтраниране @@ -401,6 +408,9 @@ setting_thumbnails_enabled: Показване на миниатюри на прикачените Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ setting_thumbnails_size: Размер на миниатюрите (в пикÑели) setting_non_working_week_days: Ðе работни дни + setting_jsonp_enabled: Разрешаване на поддръжка на JSONP + setting_default_projects_tracker_ids: Тракери по подразбиране за нови проекти + setting_mail_handler_excluded_filenames: Имена на прикачени файлове, които да Ñе пропуÑкат при приемане на e-mail-и (например *.vcf, companylogo.gif). permission_add_project: Създаване на проект permission_add_subprojects: Създаване на подпроекти @@ -437,8 +447,10 @@ permission_edit_own_time_entries: Редактиране на ÑобÑтвените time logs permission_manage_news: Управление на новини permission_comment_news: Коментиране на новини - permission_manage_documents: Управление на документи permission_view_documents: Разглеждане на документи + permission_add_documents: ДобавÑне на документи + permission_edit_documents: Редактиране на документи + permission_delete_documents: Изтриване на документи permission_manage_files: Управление на файлове permission_view_files: Разглеждане на файлове permission_manage_wiki: Управление на wiki @@ -569,8 +581,6 @@ label_text: Дълъг текÑÑ‚ label_attribute: Ðтрибут label_attribute_plural: Ðтрибути - label_download: "%{count} изтеглÑне" - label_download_plural: "%{count} изтеглÑниÑ" label_no_data: ÐÑма изходни данни label_change_status: ПромÑна на ÑÑŠÑтоÑнието label_history: ИÑÑ‚Ð¾Ñ€Ð¸Ñ @@ -619,11 +629,12 @@ one: 1 задача other: "%{count} задачи" label_total: Общо + label_total_time: Общо label_permissions: Права label_current_status: Текущо ÑÑŠÑтоÑние label_new_statuses_allowed: Позволени ÑÑŠÑтоÑÐ½Ð¸Ñ label_all: вÑички - label_any: коÑто и да е + label_any: без значение label_none: никакви label_nobody: никой label_next: Следващ @@ -688,8 +699,6 @@ label_repository_new: Ðово хранилище label_repository_plural: Хранилища label_browse: Разглеждане - label_modification: "%{count} промÑна" - label_modification_plural: "%{count} промени" label_branch: работен вариант label_tag: ВерÑÐ¸Ñ label_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ @@ -735,7 +744,7 @@ label_f_hour_plural: "%{value} чаÑа" label_time_tracking: ОтделÑне на време label_change_plural: Промени - label_statistics: СтатиÑтики + label_statistics: СтатиÑтика label_commits_per_month: Ревизии по меÑеци label_commits_per_author: Ревизии по автор label_diff: diff @@ -788,9 +797,9 @@ label_language_based: Ð’ завиÑимоÑÑ‚ от езика label_sort_by: "Сортиране по %{value}" label_send_test_email: Изпращане на теÑтов e-mail - label_feeds_access_key: RSS access ключ - label_missing_feeds_access_key: ЛипÑващ RSS ключ за доÑтъп - label_feeds_access_key_created_on: "%{value} от Ñъздаването на RSS ключа" + label_feeds_access_key: Atom access ключ + label_missing_feeds_access_key: ЛипÑващ Atom ключ за доÑтъп + label_feeds_access_key_created_on: "%{value} от Ñъздаването на Atom ключа" label_module_plural: Модули label_added_time_by: "Публикувана от %{author} преди %{age}" label_updated_time_by: "Обновена от %{author} преди %{age}" @@ -883,14 +892,21 @@ label_fields_permissions: ВидимоÑÑ‚ на полетата label_readonly: Само за четене label_required: Задължително + label_hidden: Скрит label_attribute_of_project: Project's %{name} + label_attribute_of_issue: Issue's %{name} label_attribute_of_author: Author's %{name} label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_user: User's %{name} label_attribute_of_fixed_version: Target version's %{name} label_cross_project_descendants: С подпроекти label_cross_project_tree: С дърво на проектите label_cross_project_hierarchy: С проектна Ð¹ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ label_cross_project_system: С вÑички проекти + label_gantt_progress_line: Ð›Ð¸Ð½Ð¸Ñ Ð½Ð° изпълнението + label_visibility_private: лични (Ñамо за мен) + label_visibility_roles: Ñамо за тези роли + label_visibility_public: за вÑички потребители button_login: Вход button_submit: Изпращане @@ -1002,6 +1018,7 @@ text_file_repository_writable: ВъзможноÑÑ‚ за пиÑане в хранилището Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ðµ text_plugin_assets_writable: Папката на приÑтавките е разрешена за Ð·Ð°Ð¿Ð¸Ñ text_rmagick_available: Ðаличен RMagick (по избор) + text_convert_available: Ðаличен ImageMagick convert (по избор) text_destroy_time_entries_question: "%{hours} чаÑа Ñа отделени на задачите, които иÑкате да изтриете. Какво избирате?" text_destroy_time_entries: Изтриване на отделеното време text_assign_time_entries_to_project: ПрехвърлÑне на отделеното време към проект @@ -1034,6 +1051,8 @@ text_account_destroy_confirmation: "Сигурен/на ли Ñте, че желаете да продължите?\nВашиÑÑ‚ профил ще бъде премахнат без възможноÑÑ‚ за възÑтановÑване." text_session_expiration_settings: "Внимание: промÑната на тези уÑтановÑÐ²Ð°Ð½Ð¾Ñ Ð¼Ð¾Ð¶Ðµ да прекрати вÑички активни ÑеÑии, включително и вашата." text_project_closed: Този проект е затворен и е Ñамо за четене. + text_turning_multiple_off: Ðко забраните възможноÑтта за повече от една ÑтойноÑÑ‚, повечето ÑтойноÑти ще бъдат + премахнати Ñ Ñ†ÐµÐ» да оÑтане Ñамо по една ÑтойноÑÑ‚ за поле. default_role_manager: Мениджър default_role_developer: Разработчик diff -r d98d22a98252 -r afce8026aaeb config/locales/bs.yml --- a/config/locales/bs.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/bs.yml Tue Sep 09 09:34:53 2014 +0100 @@ -49,8 +49,8 @@ one: "oko 1 sahat" other: "oko %{count} sahata" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 sahat" + other: "%{count} sahata" x_days: one: "1 dan" other: "%{count} dana" @@ -140,6 +140,7 @@ not_same_project: "ne pripada istom projektu" circular_dependency: "Ova relacija stvar cirkularnu zavisnost" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Molimo odaberite @@ -168,7 +169,7 @@ notice_email_error: DoÅ¡lo je do greÅ¡ke pri slanju emaila (%{value}) notice_email_sent: "Email je poslan %{value}" notice_failed_to_save_issues: "NeuspjeÅ¡no snimanje %{count} aktivnosti na %{total} izabrano: %{ids}." - notice_feeds_access_key_reseted: VaÅ¡ RSS pristup je resetovan. + notice_feeds_access_key_reseted: VaÅ¡ Atom pristup je resetovan. notice_file_not_found: Stranica kojoj pokuÅ¡avate da pristupite ne postoji ili je uklonjena. notice_locking_conflict: "Konflikt: podaci su izmjenjeni od strane drugog korisnika." notice_no_issue_selected: "Nijedna aktivnost nije izabrana! Molim, izaberite aktivnosti koje želite za ispravljate." @@ -198,8 +199,6 @@ mail_subject_reminder: "%{count} aktivnost(i) u kaÅ¡njenju u narednim %{days} danima" mail_body_reminder: "%{count} aktivnost(i) koje su dodjeljenje vama u narednim %{days} danima:" - gui_validation_error: 1 greÅ¡ka - gui_validation_error_plural: "%{count} greÅ¡aka" field_name: Ime field_description: Opis @@ -305,7 +304,7 @@ setting_text_formatting: Formatiranje teksta setting_wiki_compression: Kompresija Wiki istorije - setting_feeds_limit: 'Limit za "RSS" feed-ove' + setting_feeds_limit: 'Limit za "Atom" feed-ove' setting_default_projects_public: Podrazumjeva se da je novi projekat javni setting_autofetch_changesets: 'Automatski kupi "commit"-e' setting_sys_api_enabled: 'Omogući "WS" za upravljanje repozitorijom' @@ -357,7 +356,6 @@ permission_edit_own_time_entries: Ispravka svog utroÅ¡ka vremena permission_manage_news: Upravljaj novostima permission_comment_news: Komentiraj novosti - permission_manage_documents: Upravljaj dokumentima permission_view_documents: Pregled dokumenata permission_manage_files: Upravljaj fajlovima permission_view_files: Pregled fajlova @@ -477,8 +475,6 @@ label_text: Dugi tekst label_attribute: Atribut label_attribute_plural: Atributi - label_download: "%{count} download" - label_download_plural: "%{count} download-i" label_no_data: Nema podataka za prikaz label_change_status: Promjeni status label_history: Istorija @@ -578,8 +574,6 @@ label_repository: Repozitorij label_repository_plural: Repozitoriji label_browse: Listaj - label_modification: "%{count} promjena" - label_modification_plural: "%{count} promjene" label_revision: Revizija label_revision_plural: Revizije label_associated_revisions: Doddjeljene revizije @@ -668,7 +662,7 @@ label_language_based: Bazirano na korisnikovom jeziku label_sort_by: "Sortiraj po %{value}" label_send_test_email: PoÅ¡alji testni email - label_feeds_access_key_created_on: "RSS pristupni kljuÄ kreiran prije %{value} dana" + label_feeds_access_key_created_on: "Atom pristupni kljuÄ kreiran prije %{value} dana" label_module_plural: Moduli label_added_time_by: "Dodano od %{author} prije %{age}" label_updated_time_by: "Izmjenjeno od %{author} prije %{age}" @@ -894,11 +888,11 @@ label_revision_id: Revision %{value} label_api_access_key: API access key label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key + label_feeds_access_key: Atom access key notice_api_access_key_reseted: Your API access key was reset. setting_rest_api_enabled: Enable REST web service label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key + label_missing_feeds_access_key: Missing a Atom access key button_show: Show text_line_separated: Multiple values allowed (one line for each value). setting_mail_handler_body_delimiters: Truncate emails after one of these lines @@ -946,7 +940,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -987,8 +980,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1095,3 +1086,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/ca.yml --- a/config/locales/ca.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/ca.yml Tue Sep 09 09:34:53 2014 +0100 @@ -53,8 +53,8 @@ one: "aproximadament 1 hora" other: "aproximadament %{count} hores" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hora" + other: "%{count} hores" x_days: one: "1 dia" other: "%{count} dies" @@ -132,6 +132,7 @@ not_same_project: "no pertany al mateix projecte" circular_dependency: "Aquesta relació crearia una dependència circular" cant_link_an_issue_with_a_descendant: "Un assumpte no es pot enllaçar a una de les seves subtasques" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Seleccioneu @@ -164,7 +165,7 @@ notice_not_authorized: No teniu permís per a accedir a aquesta pàgina. notice_email_sent: "S'ha enviat un correu electrònic a %{value}" notice_email_error: "S'ha produït un error en enviar el correu (%{value})" - notice_feeds_access_key_reseted: "S'ha reiniciat la clau d'accés del RSS." + notice_feeds_access_key_reseted: "S'ha reiniciat la clau d'accés del Atom." notice_api_access_key_reseted: "S'ha reiniciat la clau d'accés a l'API." notice_failed_to_save_issues: "No s'han pogut desar %{count} assumptes de %{total} seleccionats: %{ids}." notice_failed_to_save_members: "No s'han pogut desar els membres: %{errors}." @@ -209,8 +210,6 @@ mail_subject_wiki_content_updated: "S'ha actualitzat la pàgina wiki «%{id}»" mail_body_wiki_content_updated: "En %{author} ha actualitzat la pàgina wiki «%{id}»." - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errors" field_name: Nom field_description: Descripció @@ -388,7 +387,6 @@ permission_edit_own_time_entries: Edita els registres de temps propis permission_manage_news: Gestiona les noticies permission_comment_news: Comenta les noticies - permission_manage_documents: Gestiona els documents permission_view_documents: Visualitza els documents permission_manage_files: Gestiona els fitxers permission_view_files: Visualitza els fitxers @@ -514,8 +512,6 @@ label_text: Text llarg label_attribute: Atribut label_attribute_plural: Atributs - label_download: "%{count} baixada" - label_download_plural: "%{count} baixades" label_no_data: Sense dades a mostrar label_change_status: "Canvia l'estat" label_history: Historial @@ -618,8 +614,6 @@ label_repository: Dipòsit label_repository_plural: Dipòsits label_browse: Navega - label_modification: "%{count} canvi" - label_modification_plural: "%{count} canvis" label_branch: Branca label_tag: Etiqueta label_revision: Revisió @@ -715,9 +709,9 @@ label_language_based: "Basat en l'idioma de l'usuari" label_sort_by: "Ordena per %{value}" label_send_test_email: Envia un correu electrònic de prova - label_feeds_access_key: "Clau d'accés del RSS" - label_missing_feeds_access_key: "Falta una clau d'accés del RSS" - label_feeds_access_key_created_on: "Clau d'accés del RSS creada fa %{value}" + label_feeds_access_key: "Clau d'accés del Atom" + label_missing_feeds_access_key: "Falta una clau d'accés del Atom" + label_feeds_access_key_created_on: "Clau d'accés del Atom creada fa %{value}" label_module_plural: Mòduls label_added_time_by: "Afegit per %{author} fa %{age}" label_updated_time_by: "Actualitzat per %{author} fa %{age}" @@ -935,7 +929,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -976,8 +969,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1084,3 +1075,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/cs.yml --- a/config/locales/cs.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/cs.yml Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -# Update to 2.2 by Karel Picman +# Update to 2.2, 2.4 by Karel Picman # Update to 1.1 by Michal Gebauer # Updated by Josef LiÅ¡ka # CZ translation by Maxim KruÅ¡ina | Massimo Filippi, s.r.o. | maxim@mxm.cz @@ -55,8 +55,8 @@ one: "asi 1 hodina" other: "asi %{count} hodin" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hodina" + other: "%{count} hodin" x_days: one: "1 den" other: "%{count} dnů" @@ -134,6 +134,7 @@ not_same_project: "nepatří stejnému projektu" circular_dependency: "Tento vztah by vytvoÅ™il cyklickou závislost" cant_link_an_issue_with_a_descendant: "Úkol nemůže být spojen s jedním z jeho dílÄích úkolů" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Prosím vyberte @@ -161,13 +162,13 @@ notice_successful_update: ÚspěšnÄ› aktualizováno. notice_successful_delete: ÚspěšnÄ› odstranÄ›no. notice_successful_connection: Úspěšné pÅ™ipojení. - notice_file_not_found: Stránka na kterou se snažíte zobrazit neexistuje nebo byla smazána. + notice_file_not_found: Stránka, kterou se snažíte zobrazit, neexistuje nebo byla smazána. notice_locking_conflict: Údaje byly zmÄ›nÄ›ny jiným uživatelem. notice_not_authorized: Nemáte dostateÄná práva pro zobrazení této stránky. - notice_not_authorized_archived_project: Projekt ke kterému se snažíte pÅ™istupovat byl archivován. + notice_not_authorized_archived_project: Projekt, ke kterému se snažíte pÅ™istupovat, byl archivován. notice_email_sent: "Na adresu %{value} byl odeslán email" notice_email_error: "PÅ™i odesílání emailu nastala chyba (%{value})" - notice_feeds_access_key_reseted: Váš klÃ­Ä pro přístup k RSS byl resetován. + notice_feeds_access_key_reseted: Váš klÃ­Ä pro přístup k Atom byl resetován. notice_api_access_key_reseted: Váš API přístupový klÃ­Ä byl resetován. notice_failed_to_save_issues: "Chyba pÅ™i uložení %{count} úkolu(ů) z %{total} vybraných: %{ids}." notice_failed_to_save_members: "NepodaÅ™ilo se uložit Älena(y): %{errors}." @@ -175,7 +176,7 @@ notice_account_pending: "Váš úÄet byl vytvoÅ™en, nyní Äeká na schválení administrátorem." notice_default_data_loaded: Výchozí konfigurace úspěšnÄ› nahrána. notice_unable_delete_version: Nemohu odstanit verzi - notice_unable_delete_time_entry: Nelze smazat Äas ze záznamu. + notice_unable_delete_time_entry: Nelze smazat záznam Äasu. notice_issue_done_ratios_updated: Koeficienty dokonÄení úkolu byly aktualizovány. notice_gantt_chart_truncated: Graf byl oříznut, poÄet položek pÅ™esáhl limit pro zobrazení (%{max}) @@ -185,15 +186,15 @@ error_scm_annotate: "Položka neexistuje nebo nemůže být komentována." error_issue_not_found_in_project: 'Úkol nebyl nalezen nebo nepatří k tomuto projektu' error_no_tracker_in_project: Žádná fronta nebyla pÅ™iÅ™azena tomuto projektu. Prosím zkontroluje nastavení projektu. - error_no_default_issue_status: Není nastaven výchozí stav úkolu. Prosím zkontrolujte nastavení ("Administrace -> Stavy úkolů"). + error_no_default_issue_status: Není nastaven výchozí stav úkolů. Prosím zkontrolujte nastavení ("Administrace -> Stavy úkolů"). error_can_not_delete_custom_field: Nelze smazat volitelné pole - error_can_not_delete_tracker: Tato fronta obsahuje úkoly a nemůže být smazán. + error_can_not_delete_tracker: Tato fronta obsahuje úkoly a nemůže být smazána. error_can_not_remove_role: Tato role je právÄ› používaná a nelze ji smazat. error_can_not_reopen_issue_on_closed_version: Úkol pÅ™iÅ™azený k uzavÅ™ené verzi nemůže být znovu otevÅ™en error_can_not_archive_project: Tento projekt nemůže být archivován error_issue_done_ratios_not_updated: Koeficient dokonÄení úkolu nebyl aktualizován. - error_workflow_copy_source: Prosím vyberte zdrojovou frontu nebo roly - error_workflow_copy_target: Prosím vyberte cílovou frontu(y) a roly(e) + error_workflow_copy_source: Prosím vyberte zdrojovou frontu nebo roli + error_workflow_copy_target: Prosím vyberte cílovou frontu(y) a roli(e) error_unable_delete_issue_status: Nelze smazat stavy úkolů error_unable_to_connect: Nelze se pÅ™ipojit (%{value}) warning_attachments_not_saved: "%{count} soubor(ů) nebylo možné uložit." @@ -207,14 +208,12 @@ mail_subject_account_activation_request: "Aktivace %{value} úÄtu" mail_body_account_activation_request: "Byl zaregistrován nový uživatel %{value}. Aktivace jeho úÄtu závisí na vaÅ¡em potvrzení." mail_subject_reminder: "%{count} úkol(ů) má termín bÄ›hem nÄ›kolik dní (%{days})" - mail_body_reminder: "%{count} úkol(ů), které máte pÅ™iÅ™azeny má termín bÄ›hem nÄ›kolik dní (%{days}):" + mail_body_reminder: "%{count} úkol(ů), které máte pÅ™iÅ™azeny má termín bÄ›hem nÄ›kolika dní (%{days}):" mail_subject_wiki_content_added: "'%{id}' Wiki stránka byla pÅ™idána" mail_body_wiki_content_added: "'%{id}' Wiki stránka byla pÅ™idána od %{author}." mail_subject_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována" mail_body_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována od %{author}." - gui_validation_error: 1 chyba - gui_validation_error_plural: "%{count} chyb(y)" field_name: Název field_description: Popis @@ -293,7 +292,7 @@ field_issue_to: Související úkol field_delay: ZpoždÄ›ní field_assignable: Úkoly mohou být pÅ™iÅ™azeny této roli - field_redirect_existing_links: PÅ™esmÄ›rovat stvávající odkazy + field_redirect_existing_links: PÅ™esmÄ›rovat stávající odkazy field_estimated_hours: Odhadovaná doba field_column_names: Sloupce field_time_entries: Zaznamenaný Äas @@ -323,7 +322,7 @@ setting_attachment_max_size: Maximální velikost přílohy setting_issues_export_limit: Limit pro export úkolů setting_mail_from: Odesílat emaily z adresy - setting_bcc_recipients: Příjemci skryté kopie (bcc) + setting_bcc_recipients: Příjemci jako skrytá kopie (bcc) setting_plain_text_mail: pouze prostý text (ne HTML) setting_host_name: Jméno serveru setting_text_formatting: Formátování textu @@ -339,8 +338,8 @@ setting_time_format: Formát Äasu setting_cross_project_issue_relations: Povolit vazby úkolů napÅ™Ã­Ä projekty setting_issue_list_default_columns: Výchozí sloupce zobrazené v seznamu úkolů - setting_emails_header: HlaviÄka emailů - setting_emails_footer: PatiÄka emailů + setting_emails_header: Záhlaví emailů + setting_emails_footer: Zápatí emailů setting_protocol: Protokol setting_per_page_options: Povolené poÄty řádků na stránce setting_user_format: Formát zobrazení uživatele @@ -353,7 +352,7 @@ setting_sequential_project_identifiers: Generovat sekvenÄní identifikátory projektů setting_gravatar_enabled: Použít uživatelské ikony Gravatar setting_gravatar_default: Výchozí Gravatar - setting_diff_max_lines_displayed: Maximální poÄet zobrazených řádků rozdílů + setting_diff_max_lines_displayed: Maximální poÄet zobrazených řádků rozdílu setting_file_max_size_displayed: Maximální velikost textových souborů zobrazených přímo na stránce setting_repository_log_display_limit: Maximální poÄet revizí zobrazených v logu souboru setting_openid: Umožnit pÅ™ihlaÅ¡ování a registrace s OpenID @@ -369,7 +368,7 @@ setting_default_notification_option: Výchozí nastavení oznámení setting_commit_logtime_enabled: Povolit zapisování Äasu setting_commit_logtime_activity_id: Aktivita pro zapsaný Äas - setting_gantt_items_limit: Maximální poÄet položek zobrazený na ganttovÄ› grafu + setting_gantt_items_limit: Maximální poÄet položek zobrazený na ganttovÄ› diagramu permission_add_project: VytvoÅ™it projekt permission_add_subprojects: VytvoÅ™it podprojekty @@ -390,18 +389,17 @@ permission_delete_issues: Mazání úkolů permission_manage_public_queries: Správa veÅ™ejných dotazů permission_save_queries: Ukládání dotazů - permission_view_gantt: Zobrazené Ganttova diagramu + permission_view_gantt: Zobrazení ganttova diagramu permission_view_calendar: Prohlížení kalendáře - permission_view_issue_watchers: Zobrazení seznamu sledujícíh uživatelů + permission_view_issue_watchers: Zobrazení seznamu sledujících uživatelů permission_add_issue_watchers: PÅ™idání sledujících uživatelů - permission_delete_issue_watchers: Smazat pÅ™ihlížející + permission_delete_issue_watchers: Smazat sledující uživatele permission_log_time: Zaznamenávání stráveného Äasu permission_view_time_entries: Zobrazení stráveného Äasu permission_edit_time_entries: Upravování záznamů o stráveném Äasu permission_edit_own_time_entries: Upravování vlastních zázamů o stráveném Äase permission_manage_news: Spravování novinek permission_comment_news: Komentování novinek - permission_manage_documents: Správa dokumentů permission_view_documents: Prohlížení dokumentů permission_manage_files: Spravování souborů permission_view_files: Prohlížení souborů @@ -425,7 +423,7 @@ permission_delete_messages: Mazání zpráv permission_delete_own_messages: Smazat vlastní zprávy permission_export_wiki_pages: Exportovat Wiki stránky - permission_manage_subtasks: Spravovat podúkoly + permission_manage_subtasks: Spravovat dílÄí úkoly project_module_issue_tracking: Sledování úkolů project_module_time_tracking: Sledování Äasu @@ -486,7 +484,7 @@ label_enumeration_new: Nová hodnota label_information: Informace label_information_plural: Informace - label_please_login: Prosím pÅ™ihlaÅ¡te se + label_please_login: PÅ™ihlaÅ¡te se, prosím label_register: Registrovat label_login_with_open_id_option: nebo se pÅ™ihlaÅ¡te s OpenID label_password_lost: Zapomenuté heslo @@ -527,8 +525,6 @@ label_text: Dlouhý text label_attribute: Atribut label_attribute_plural: Atributy - label_download: "%{count} stažení" - label_download_plural: "%{count} stažení" label_no_data: Žádné položky label_change_status: ZmÄ›nit stav label_history: Historie @@ -586,7 +582,7 @@ label_per_page: Na stránku label_calendar: Kalendář label_months_from: mÄ›síců od - label_gantt: Ganttův graf + label_gantt: Ganttův diagram label_internal: Interní label_last_changes: "posledních %{count} zmÄ›n" label_change_view_all: Zobrazit vÅ¡echny zmÄ›ny @@ -631,8 +627,6 @@ label_repository: Repozitář label_repository_plural: Repozitáře label_browse: Procházet - label_modification: "%{count} zmÄ›na" - label_modification_plural: "%{count} zmÄ›n" label_branch: VÄ›tev label_tag: Tag label_revision: Revize @@ -695,9 +689,9 @@ label_relation_delete: Odstranit souvislost label_relates_to: související s label_duplicates: duplikuje - label_duplicated_by: zduplikován + label_duplicated_by: duplikován label_blocks: blokuje - label_blocked_by: zablokován + label_blocked_by: blokován label_precedes: pÅ™edchází label_follows: následuje label_end_to_start: od konce do zaÄátku @@ -706,12 +700,12 @@ label_start_to_end: od zaÄátku do konce label_stay_logged_in: Zůstat pÅ™ihlášený label_disabled: zakázán - label_show_completed_versions: Ukázat dokonÄené verze + label_show_completed_versions: Zobrazit dokonÄené verze label_me: já label_board: Fórum label_board_new: Nové fórum label_board_plural: Fóra - label_board_locked: UzamÄeno + label_board_locked: ZamÄeno label_board_sticky: Nálepka label_topic_plural: Témata label_message_plural: Zprávy @@ -725,19 +719,19 @@ label_week: Týden label_date_from: Od label_date_to: Do - label_language_based: Podle výchozího jazyku + label_language_based: Podle výchozího jazyka label_sort_by: "SeÅ™adit podle %{value}" label_send_test_email: Poslat testovací email - label_feeds_access_key: Přístupový klÃ­Ä pro RSS - label_missing_feeds_access_key: Postrádá přístupový klÃ­Ä pro RSS - label_feeds_access_key_created_on: "Přístupový klÃ­Ä pro RSS byl vytvoÅ™en pÅ™ed %{value}" + label_feeds_access_key: Přístupový klÃ­Ä pro Atom + label_missing_feeds_access_key: Postrádá přístupový klÃ­Ä pro Atom + label_feeds_access_key_created_on: "Přístupový klÃ­Ä pro Atom byl vytvoÅ™en pÅ™ed %{value}" label_module_plural: Moduly label_added_time_by: "PÅ™idáno uživatelem %{author} pÅ™ed %{age}" label_updated_time_by: "Aktualizováno uživatelem %{author} pÅ™ed %{age}" label_updated_time: "Aktualizováno pÅ™ed %{value}" label_jump_to_a_project: Vyberte projekt... label_file_plural: Soubory - label_changeset_plural: Changesety + label_changeset_plural: Sady zmÄ›n label_default_columns: Výchozí sloupce label_no_change_option: (beze zmÄ›ny) label_bulk_edit_selected_issues: Hromadná úprava vybraných úkolů @@ -747,9 +741,9 @@ label_user_mail_option_all: "Pro vÅ¡echny události vÅ¡ech mých projektů" label_user_mail_option_selected: "Pro vÅ¡echny události vybraných projektů..." label_user_mail_option_none: "Žádné události" - label_user_mail_option_only_my_events: "Jen pro vÄ›ci co sleduji nebo jsem v nich zapojen" - label_user_mail_option_only_assigned: "Jen pro vÅ¡eci kterým sem pÅ™iÅ™azen" - label_user_mail_option_only_owner: "Jen pro vÄ›ci které vlastním" + label_user_mail_option_only_my_events: "Jen pro vÄ›ci, co sleduji nebo jsem v nich zapojen" + label_user_mail_option_only_assigned: "Jen pro vÄ›ci, ke kterým sem pÅ™iÅ™azen" + label_user_mail_option_only_owner: "Jen pro vÄ›ci, které vlastním" label_user_mail_no_self_notified: "Nezasílat informace o mnou vytvoÅ™ených zmÄ›nách" label_registration_activation_by_email: aktivace úÄtu emailem label_registration_manual_activation: manuální aktivace úÄtu @@ -798,7 +792,7 @@ label_missing_api_access_key: ChybÄ›jící přístupový klÃ­Ä API label_api_access_key_created_on: API přístupový klÃ­Ä vytvoÅ™en %{value} label_profile: Profil - label_subtask_plural: Podúkol + label_subtask_plural: DílÄí úkoly label_project_copy_notifications: Odeslat email oznámení v průbÄ›hu kopie projektu label_principal_search: "Hledat uživatele nebo skupinu:" label_user_search: "Hledat uživatele:" @@ -835,7 +829,7 @@ button_unwatch: Nesledovat button_reply: OdpovÄ›dÄ›t button_archive: Archivovat - button_unarchive: Odarchivovat + button_unarchive: Dearchivovat button_reset: Resetovat button_rename: PÅ™ejmenovat button_change_password: ZmÄ›nit heslo @@ -850,18 +844,18 @@ status_active: aktivní status_registered: registrovaný - status_locked: uzamÄený + status_locked: zamÄený version_status_open: otevÅ™ený - version_status_locked: uzamÄený + version_status_locked: zamÄený version_status_closed: zavÅ™ený field_active: Aktivní - text_select_mail_notifications: Vyberte akci pÅ™i které bude zasláno upozornÄ›ní emailem. + text_select_mail_notifications: Vyberte akci, pÅ™i které bude zasláno upozornÄ›ní emailem. text_regexp_info: napÅ™. ^[A-Z0-9]+$ text_min_max_length_info: 0 znamená bez limitu - text_project_destroy_confirmation: Jste si jisti, že chcete odstranit tento projekt a vÅ¡echna související data ? + text_project_destroy_confirmation: Jste si jisti, že chcete odstranit tento projekt a vÅ¡echna související data? text_subprojects_destroy_warning: "Jeho podprojek(y): %{value} budou také smazány." text_workflow_edit: Vyberte roli a frontu k editaci průbÄ›hu práce text_are_you_sure: Jste si jisti? @@ -879,7 +873,7 @@ text_unallowed_characters: Nepovolené znaky text_comma_separated: Povoleno více hodnot (oddÄ›lÄ›né Äárkou). text_line_separated: Více hodnot povoleno (jeden řádek pro každou hodnotu). - text_issues_ref_in_commit_messages: Odkazování a opravování úkolů ve zprávách commitů + text_issues_ref_in_commit_messages: Odkazování a opravování úkolů v poznámkách commitů text_issue_added: "Úkol %{id} byl vytvoÅ™en uživatelem %{author}." text_issue_updated: "Úkol %{id} byl aktualizován uživatelem %{author}." text_wiki_destroy_confirmation: Opravdu si pÅ™ejete odstranit tuto Wiki a celý její obsah? @@ -889,30 +883,30 @@ text_user_mail_option: "U projektů, které nebyly vybrány, budete dostávat oznámení pouze o vaÅ¡ich Äi o sledovaných položkách (napÅ™. o položkách jejichž jste autor nebo ke kterým jste pÅ™iÅ™azen(a))." text_no_configuration_data: "Role, fronty, stavy úkolů ani průbÄ›h práce nebyly zatím nakonfigurovány.\nVelice doporuÄujeme nahrát výchozí konfiguraci. Po té si můžete vÅ¡e upravit" text_load_default_configuration: Nahrát výchozí konfiguraci - text_status_changed_by_changeset: "Použito v changesetu %{value}." - text_time_logged_by_changeset: Aplikováno v changesetu %{value}. + text_status_changed_by_changeset: "Použito v sadÄ› zmÄ›n %{value}." + text_time_logged_by_changeset: Aplikováno v sadÄ› zmÄ›n %{value}. text_issues_destroy_confirmation: 'Opravdu si pÅ™ejete odstranit vÅ¡echny zvolené úkoly?' text_select_project_modules: 'Aktivní moduly v tomto projektu:' text_default_administrator_account_changed: Výchozí nastavení administrátorského úÄtu zmÄ›nÄ›no text_file_repository_writable: Povolen zápis do adresáře ukládání souborů text_plugin_assets_writable: Možnost zápisu do adresáře plugin assets text_rmagick_available: RMagick k dispozici (volitelné) - text_destroy_time_entries_question: "U úkolů, které chcete odstranit je evidováno %{hours} práce. Co chete udÄ›lat?" - text_destroy_time_entries: Odstranit evidované hodiny. - text_assign_time_entries_to_project: PÅ™iÅ™adit evidované hodiny projektu - text_reassign_time_entries: 'PÅ™eÅ™adit evidované hodiny k tomuto úkolu:' + text_destroy_time_entries_question: "U úkolů, které chcete odstranit, je evidováno %{hours} práce. Co chete udÄ›lat?" + text_destroy_time_entries: Odstranit zaznamenané hodiny. + text_assign_time_entries_to_project: PÅ™iÅ™adit zaznamenané hodiny projektu + text_reassign_time_entries: 'PÅ™eÅ™adit zaznamenané hodiny k tomuto úkolu:' text_user_wrote: "%{value} napsal:" text_enumeration_destroy_question: "NÄ›kolik (%{count}) objektů je pÅ™iÅ™azeno k této hodnotÄ›." text_enumeration_category_reassign_to: 'PÅ™eÅ™adit je do této:' text_email_delivery_not_configured: "DoruÄování e-mailů není nastaveno a odesílání notifikací je zakázáno.\nNastavte Váš SMTP server v souboru config/configuration.yml a restartujte aplikaci." - text_repository_usernames_mapping: "Vybrat nebo upravit mapování mezi Redmine uživateli a uživatelskými jmény nalezenými v logu repozitáře.\nUživatelé se shodným Redmine uživatelským jménem a uživatelským jménem v repozitáři jsou mapovaní automaticky." + text_repository_usernames_mapping: "Vybrat nebo upravit mapování mezi Redmine uživateli a uživatelskými jmény nalezenými v logu repozitáře.\nUživatelé se shodným Redmine uživatelským jménem a uživatelským jménem v repozitáři jsou mapováni automaticky." text_diff_truncated: '... Rozdílový soubor je zkrácen, protože jeho délka pÅ™esahuje max. limit.' text_custom_field_possible_values_info: 'Každá hodnota na novém řádku' text_wiki_page_destroy_question: Tato stránka má %{descendants} podstránek a potomků. Co chcete udÄ›lat? text_wiki_page_nullify_children: Ponechat podstránky jako koÅ™enové stránky text_wiki_page_destroy_children: Smazat podstránky a vÅ¡echny jejich potomky text_wiki_page_reassign_children: PÅ™iÅ™adit podstránky k tomuto rodiÄi - text_own_membership_delete_confirmation: "Chystáte se odebrat si nÄ›která nebo vÅ¡echny svá oprávnÄ›ní a potom již nemusíte být schopni upravit tento projekt.\nOpravdu chcete pokraÄovat?" + text_own_membership_delete_confirmation: "Chystáte se odebrat si nÄ›která nebo vÅ¡echna svá oprávnÄ›ní, potom již nemusíte být schopni upravit tento projekt.\nOpravdu chcete pokraÄovat?" text_zoom_in: PÅ™iblížit text_zoom_out: Oddálit @@ -966,7 +960,7 @@ field_is_private: Soukromý permission_set_issues_private: Nastavit úkoly jako veÅ™ejné nebo soukromé label_issues_visibility_public: VÅ¡echny úkoly, které nejsou soukromé - text_issues_destroy_descendants_confirmation: "%{count} podúkol(ů) bude rovněž smazán(o)." + text_issues_destroy_descendants_confirmation: "%{count} dílÄí(ch) úkol(ů) bude rovněž smazán(o)." field_commit_logs_encoding: Kódování zpráv pÅ™i commitu field_scm_path_encoding: Kódování cesty SCM text_scm_path_encoding_note: "Výchozí: UTF-8" @@ -1004,14 +998,14 @@ description_selected_columns: Vybraný sloupec label_parent_revision: RodiÄ label_child_revision: Potomek - error_scm_annotate_big_text_file: Vstup nemůže být anotován, protože pÅ™ekraÄuje povolenou velikost textového souboru + error_scm_annotate_big_text_file: Vstup nemůže být komentován, protože pÅ™ekraÄuje povolenou velikost textového souboru setting_default_issue_start_date_to_creation_date: Použij aktuální datum jako poÄáteÄní datum pro nové úkoly - button_edit_section: Uprav tuto sekci + button_edit_section: Uprav tuto Äást setting_repositories_encodings: Kódování příloh a repositářů description_all_columns: VÅ¡echny sloupce button_export: Export label_export_options: "nastavení exportu %{export_format}" - error_attachment_too_big: Soubor nemůže být nahrán, protože jeho velikost je vÄ›tší než maximum (%{max_size}) + error_attachment_too_big: Soubor nemůže být nahrán, protože jeho velikost je vÄ›tší než maximální (%{max_size}) notice_failed_to_save_time_entries: "Chyba pÅ™i ukládání %{count} Äasov(ých/ého) záznam(ů) z %{total} vybraného: %{ids}." label_x_issues: zero: 0 Úkol @@ -1024,7 +1018,7 @@ label_completed_versions: DokonÄené verze text_project_identifier_info: Jsou povolena pouze malá písmena (a-z), Äíslice, pomlÄky a podtržítka.
    Po uložení již nelze identifikátor mÄ›nit. field_multiple: Více hodnot - setting_commit_cross_project_ref: Povolit reference a opravy úklů ze vÅ¡ech ostatních projektů + setting_commit_cross_project_ref: Povolit reference a opravy úkolů ze vÅ¡ech ostatních projektů text_issue_conflict_resolution_add_notes: PÅ™idat moje poznámky a zahodit ostatní zmÄ›ny text_issue_conflict_resolution_overwrite: PÅ™esto pÅ™ijmout moje úpravy (pÅ™edchozí poznámky budou zachovány, ale nÄ›které zmÄ›ny mohou být pÅ™epsány) notice_issue_update_conflict: BÄ›hem vaÅ¡ich úprav byl úkol aktualizován jiným uživatelem. @@ -1066,18 +1060,18 @@ label_attribute_of_author: Autorovo %{name} label_attribute_of_assigned_to: "%{name} pÅ™iÅ™azené(ho)" label_attribute_of_fixed_version: Cílová verze %{name} - label_copy_subtasks: Kopírovat podúkoly + label_copy_subtasks: Kopírovat dílÄí úkoly label_copied_to: zkopírováno do label_copied_from: zkopírováno z label_any_issues_in_project: jakékoli úkoly v projektu - label_any_issues_not_in_project: jakékoli úkoly mimo projektu + label_any_issues_not_in_project: jakékoli úkoly mimo projekt field_private_notes: Soukromé poznámky permission_view_private_notes: Zobrazit soukromé poznámky permission_set_notes_private: Nastavit poznámky jako soukromé label_no_issues_in_project: žádné úkoly v projektu label_any: vÅ¡e label_last_n_weeks: poslední %{count} týdny - setting_cross_project_subtasks: Povolit podúkoly napÅ™Ã­Ä projekty + setting_cross_project_subtasks: Povolit dílÄí úkoly napÅ™Ã­Ä projekty label_cross_project_descendants: S podprojekty label_cross_project_tree: Se stromem projektu label_cross_project_hierarchy: S hierarchií projektu @@ -1086,3 +1080,28 @@ setting_non_working_week_days: Dny pracovního volna/klidu label_in_the_next_days: v přístích label_in_the_past_days: v minulých + label_attribute_of_user: "%{name} uživatel(e/ky)" + text_turning_multiple_off: Jestliže zakážete více hodnot, + hodnoty budou smazány za úÄelem rezervace pouze jediné hodnoty na položku. + label_attribute_of_issue: "%{name} úkolu" + permission_add_documents: PÅ™idat dokument + permission_edit_documents: Upravit dokumenty + permission_delete_documents: Smazet dokumenty + label_gantt_progress_line: Vývojová Äára + setting_jsonp_enabled: Povolit podporu JSONP + field_inherit_members: ZdÄ›dit Äleny + field_closed_on: UzavÅ™eno + field_generate_password: Generovat heslo + setting_default_projects_tracker_ids: Výchozí fronta pro nové projekty + label_total_time: Celkem + notice_account_not_activated_yet: Neaktivovali jste si dosud Váš úÄet. + Pro opÄ›tovné zaslání aktivaÄního emailu kliknÄ›te na tento odkaz, prosím. + notice_account_locked: Váš úÄet je uzamÄen. + label_hidden: Skrytý + label_visibility_private: pouze pro mÄ› + label_visibility_roles: pouze pro tyto role + label_visibility_public: pro vÅ¡echny uživatele + field_must_change_passwd: Musí zmÄ›nit heslo pÅ™i příštím pÅ™ihlášení + notice_new_password_must_be_different: Nové heslo se musí liÅ¡it od stávajícího + setting_mail_handler_excluded_filenames: VyÅ™adit přílohy podle jména + text_convert_available: ImageMagick convert k dispozici (volitelné) diff -r d98d22a98252 -r afce8026aaeb config/locales/da.yml --- a/config/locales/da.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/da.yml Tue Sep 09 09:34:53 2014 +0100 @@ -52,8 +52,8 @@ one: "cirka en time" other: "cirka %{count} timer" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 time" + other: "%{count} timer" x_days: one: "en dag" other: "%{count} dage" @@ -141,6 +141,7 @@ not_same_project: "hører ikke til samme projekt" circular_dependency: "Denne relation vil skabe et afhængighedsforhold" cant_link_an_issue_with_a_descendant: "En sag kan ikke relateres til en af dens underopgaver" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" template: header: @@ -178,7 +179,7 @@ notice_not_authorized: Du har ikke adgang til denne side. notice_email_sent: "En email er sendt til %{value}" notice_email_error: "En fejl opstod under afsendelse af email (%{value})" - notice_feeds_access_key_reseted: Din adgangsnøgle til RSS er nulstillet. + notice_feeds_access_key_reseted: Din adgangsnøgle til Atom er nulstillet. notice_failed_to_save_issues: "Det mislykkedes at gemme %{count} sage(r) pÃ¥ %{total} valgt: %{ids}." notice_no_issue_selected: "Ingen sag er valgt! Vælg venligst hvilke emner du vil rette." notice_account_pending: "Din konto er oprettet, og afventer administrators godkendelse." @@ -197,8 +198,6 @@ mail_subject_account_activation_request: "%{value} kontoaktivering" mail_body_account_activation_request: "En ny bruger (%{value}) er registreret. Godkend venligst kontoen:" - gui_validation_error: 1 fejl - gui_validation_error_plural: "%{count} fejl" field_name: Navn field_description: Beskrivelse @@ -401,8 +400,6 @@ label_text: Lang tekst label_attribute: Attribut label_attribute_plural: Attributter - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: Ingen data at vise label_change_status: Ændringsstatus label_history: Historik @@ -502,8 +499,6 @@ label_repository: Repository label_repository_plural: Repositories label_browse: Gennemse - label_modification: "%{count} ændring" - label_modification_plural: "%{count} ændringer" label_revision: Revision label_revision_plural: Revisioner label_associated_revisions: Tilknyttede revisioner @@ -589,7 +584,7 @@ label_language_based: Baseret pÃ¥ brugerens sprog label_sort_by: "Sortér efter %{value}" label_send_test_email: Send en test email - label_feeds_access_key_created_on: "RSS adgangsnøgle dannet for %{value} siden" + label_feeds_access_key_created_on: "Atom adgangsnøgle dannet for %{value} siden" label_module_plural: Moduler label_added_time_by: "Tilføjet af %{author} for %{age} siden" label_updated_time: "Opdateret for %{value} siden" @@ -760,7 +755,6 @@ field_parent_title: Siden over text_email_delivery_not_configured: "Email-afsendelse er ikke indstillet og notifikationer er defor slÃ¥et fra.\nKonfigurér din SMTP server i config/configuration.yml og genstart applikationen for at aktivere email-afsendelse." permission_protect_wiki_pages: Beskyt wiki sider - permission_manage_documents: Administrér dokumenter permission_add_issue_watchers: Tilføj overvÃ¥gere warning_attachments_not_saved: "der var %{count} fil(er), som ikke kunne gemmes." permission_comment_news: Kommentér nyheder @@ -897,11 +891,11 @@ label_revision_id: Revision %{value} label_api_access_key: API nøgle label_api_access_key_created_on: API nøgle genereret %{value} siden - label_feeds_access_key: RSS nøgle + label_feeds_access_key: Atom nøgle notice_api_access_key_reseted: Din API nøgle er nulstillet. setting_rest_api_enabled: Aktiver REST web service label_missing_api_access_key: Mangler en API nøgle - label_missing_feeds_access_key: Mangler en RSS nøgle + label_missing_feeds_access_key: Mangler en Atom nøgle button_show: Vis text_line_separated: Flere væredier tilladt (en linje for hver værdi). setting_mail_handler_body_delimiters: Trunkér emails efter en af disse linjer @@ -949,7 +943,6 @@ label_principal_search: "Søg efter bruger eller gruppe:" label_user_search: "Søg efter bruger:" field_visible: Synlig - setting_emails_header: Emails header setting_commit_logtime_activity_id: Aktivitet for registreret tid text_time_logged_by_changeset: Anvendt i changeset %{value}. setting_commit_logtime_enabled: Aktiver tidsregistrering @@ -991,8 +984,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1099,3 +1090,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/de.yml --- a/config/locales/de.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/de.yml Tue Sep 09 09:34:53 2014 +0100 @@ -53,8 +53,8 @@ one: 'etwa 1 Stunde' other: 'etwa %{count} Stunden' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 Stunde" + other: "%{count} Stunden" x_days: one: '1 Tag' other: '%{count} Tagen' @@ -133,7 +133,7 @@ wrong_length: "hat die falsche Länge (muss genau %{count} Zeichen haben)" taken: "ist bereits vergeben" not_a_number: "ist keine Zahl" - not_a_date: "is kein gültiges Datum" + not_a_date: "ist kein gültiges Datum" greater_than: "muss größer als %{count} sein" greater_than_or_equal_to: "muss größer oder gleich %{count} sein" equal_to: "muss genau %{count} sein" @@ -144,311 +144,680 @@ greater_than_start_date: "muss größer als Anfangsdatum sein" not_same_project: "gehört nicht zum selben Projekt" circular_dependency: "Diese Beziehung würde eine zyklische Abhängigkeit erzeugen" - cant_link_an_issue_with_a_descendant: "Ein Ticket kann nicht mit einer ihrer Unteraufgaben verlinkt werden" + cant_link_an_issue_with_a_descendant: "Ein Ticket kann nicht mit einer Ihrer Unteraufgaben verlinkt werden" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Bitte auswählen + button_activate: Aktivieren + button_add: Hinzufügen + button_annotate: Annotieren + button_apply: Anwenden + button_archive: Archivieren + button_back: Zurück + button_cancel: Abbrechen + button_change: Wechseln + button_change_password: Kennwort ändern + button_check_all: Alles auswählen + button_clear: Zurücksetzen + button_close: Schließen + button_collapse_all: Alle einklappen + button_configure: Konfigurieren + button_copy: Kopieren + button_copy_and_follow: Kopieren und Ticket anzeigen + button_create: Anlegen + button_create_and_continue: Anlegen und weiter + button_delete: Löschen + button_delete_my_account: Mein Benutzerkonto löschen + button_download: Download + button_duplicate: Duplizieren + button_edit: Bearbeiten + button_edit_associated_wikipage: "Zugehörige Wikiseite bearbeiten: %{page_title}" + button_edit_section: Diesen Bereich bearbeiten + button_expand_all: Alle ausklappen + button_export: Exportieren + button_hide: Verstecken + button_list: Liste + button_lock: Sperren + button_log_time: Aufwand buchen + button_login: Anmelden + button_move: Verschieben + button_move_and_follow: Verschieben und Ticket anzeigen + button_quote: Zitieren + button_rename: Umbenennen + button_reopen: Öffnen + button_reply: Antworten + button_reset: Zurücksetzen + button_rollback: Auf diese Version zurücksetzen + button_save: Speichern + button_show: Anzeigen + button_sort: Sortieren + button_submit: OK + button_test: Testen + button_unarchive: Entarchivieren + button_uncheck_all: Alles abwählen + button_unlock: Entsperren + button_unwatch: Nicht beobachten + button_update: Bearbeiten + button_view: Anzeigen + button_watch: Beobachten + + default_activity_design: Design + default_activity_development: Entwicklung + default_doc_category_tech: Technische Dokumentation + default_doc_category_user: Benutzerdokumentation + default_issue_status_closed: Erledigt + default_issue_status_feedback: Feedback + default_issue_status_in_progress: In Bearbeitung + default_issue_status_new: Neu + default_issue_status_rejected: Abgewiesen + default_issue_status_resolved: Gelöst + default_priority_high: Hoch + default_priority_immediate: Sofort + default_priority_low: Niedrig + default_priority_normal: Normal + default_priority_urgent: Dringend + default_role_developer: Entwickler + default_role_manager: Manager + default_role_reporter: Reporter + default_tracker_bug: Fehler + default_tracker_feature: Feature + default_tracker_support: Unterstützung + + description_all_columns: Alle Spalten + description_available_columns: Verfügbare Spalten + description_choose_project: Projekte + description_date_from: Startdatum eintragen + description_date_range_interval: Zeitraum durch Start- und Enddatum festlegen + description_date_range_list: Zeitraum aus einer Liste wählen + description_date_to: Enddatum eintragen + description_filter: Filter + description_issue_category_reassign: Neue Kategorie wählen + description_message_content: Nachrichteninhalt + description_notes: Kommentare + description_project_scope: Suchbereich + description_query_sort_criteria_attribute: Sortierattribut + description_query_sort_criteria_direction: Sortierrichtung + description_search: Suchfeld + description_selected_columns: Ausgewählte Spalten + + description_user_mail_notification: Mailbenachrichtigungseinstellung + description_wiki_subpages_reassign: Neue Elternseite wählen + + enumeration_activities: Aktivitäten (Zeiterfassung) + enumeration_doc_categories: Dokumentenkategorien + enumeration_issue_priorities: Ticket-Prioritäten + enumeration_system_activity: System-Aktivität + + error_attachment_too_big: Diese Datei kann nicht hochgeladen werden, da sie die maximale Dateigröße von (%{max_size}) überschreitet. + error_can_not_archive_project: Dieses Projekt kann nicht archiviert werden. + error_can_not_delete_custom_field: Kann das benutzerdefinierte Feld nicht löschen. + error_can_not_delete_tracker: Dieser Tracker enthält Tickets und kann nicht gelöscht werden. + error_can_not_remove_role: Diese Rolle wird verwendet und kann nicht gelöscht werden. + error_can_not_reopen_issue_on_closed_version: Das Ticket ist einer abgeschlossenen Version zugeordnet und kann daher nicht wieder geöffnet werden. + error_can_t_load_default_data: "Die Standard-Konfiguration konnte nicht geladen werden: %{value}" + error_issue_done_ratios_not_updated: Der Ticket-Fortschritt wurde nicht aktualisiert. + error_issue_not_found_in_project: 'Das Ticket wurde nicht gefunden oder gehört nicht zu diesem Projekt.' + error_no_default_issue_status: Es ist kein Status als Standard definiert. Bitte überprüfen Sie Ihre Konfiguration (unter "Administration -> Ticket-Status"). + error_no_tracker_in_project: Diesem Projekt ist kein Tracker zugeordnet. Bitte überprüfen Sie die Projekteinstellungen. + error_scm_annotate: "Der Eintrag existiert nicht oder kann nicht annotiert werden." + error_scm_annotate_big_text_file: Der Eintrag kann nicht umgesetzt werden, da er die maximale Textlänge überschreitet. + error_scm_command_failed: "Beim Zugriff auf das Projektarchiv ist ein Fehler aufgetreten: %{value}" + error_scm_not_found: Eintrag und/oder Revision existiert nicht im Projektarchiv. + error_session_expired: Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an. + error_unable_delete_issue_status: "Der Ticket-Status konnte nicht gelöscht werden." + error_unable_to_connect: Fehler beim Verbinden (%{value}) + error_workflow_copy_source: Bitte wählen Sie einen Quell-Tracker und eine Quell-Rolle. + error_workflow_copy_target: Bitte wählen Sie die Ziel-Tracker und -Rollen. + + field_account: Konto + field_active: Aktiv + field_activity: Aktivität + field_admin: Administrator + field_assignable: Tickets können dieser Rolle zugewiesen werden + field_assigned_to: Zugewiesen an + field_assigned_to_role: Zuständigkeitsrolle + field_attr_firstname: Vorname-Attribut + field_attr_lastname: Name-Attribut + field_attr_login: Mitgliedsname-Attribut + field_attr_mail: E-Mail-Attribut + field_auth_source: Authentifizierungs-Modus + field_auth_source_ldap_filter: LDAP-Filter + field_author: Autor + field_base_dn: Base DN + field_board_parent: Übergeordnetes Forum + field_category: Kategorie + field_column_names: Spalten + field_closed_on: Geschlossen am + field_comments: Kommentar + field_comments_sorting: Kommentare anzeigen + field_commit_logs_encoding: Kodierung der Commit-Log-Meldungen + field_content: Inhalt + field_core_fields: Standardwerte + field_created_on: Angelegt + field_cvs_module: Modul + field_cvsroot: CVSROOT + field_default_value: Standardwert + field_delay: Pufferzeit + field_description: Beschreibung + field_done_ratio: "% erledigt" + field_downloads: Downloads + field_due_date: Abgabedatum + field_editable: Bearbeitbar + field_effective_date: Datum + field_estimated_hours: Geschätzter Aufwand + field_field_format: Format + field_filename: Datei + field_filesize: Größe + field_firstname: Vorname + field_fixed_version: Zielversion + field_generate_password: Passwort generieren + field_group_by: Gruppiere Ergebnisse nach + field_hide_mail: E-Mail-Adresse nicht anzeigen + field_homepage: Projekt-Homepage + field_host: Host + field_hours: Stunden + field_identifier: Kennung + field_identity_url: OpenID-URL + field_inherit_members: Benutzer erben + field_is_closed: Ticket geschlossen + field_is_default: Standardeinstellung + field_is_filter: Als Filter benutzen + field_is_for_all: Für alle Projekte + field_is_in_roadmap: In der Roadmap anzeigen + field_is_private: Privat + field_is_public: Öffentlich + field_is_required: Erforderlich + field_issue: Ticket + field_issue_to: Zugehöriges Ticket + field_issues_visibility: Ticket Sichtbarkeit + field_language: Sprache + field_last_login_on: Letzte Anmeldung + field_lastname: Nachname + field_login: Mitgliedsname + field_mail: E-Mail + field_mail_notification: Mailbenachrichtigung + field_max_length: Maximale Länge + field_member_of_group: Zuständigkeitsgruppe + field_min_length: Minimale Länge + field_multiple: Mehrere Werte + field_must_change_passwd: Passwort beim nächsten Login ändern + field_name: Name + field_new_password: Neues Kennwort + field_notes: Kommentare + field_onthefly: On-the-fly-Benutzererstellung + field_parent: Unterprojekt von + field_parent_issue: Übergeordnete Aufgabe + field_parent_title: Übergeordnete Seite + field_password: Kennwort + field_password_confirmation: Bestätigung + field_path_to_repository: Pfad zum Repository + field_port: Port + field_possible_values: Mögliche Werte + field_principal: Auftraggeber + field_priority: Priorität + field_private_notes: Privater Kommentar + field_project: Projekt + field_redirect_existing_links: Existierende Links umleiten + field_regexp: Regulärer Ausdruck + field_repository_is_default: Haupt-Repository + field_role: Rolle + field_root_directory: Wurzelverzeichnis + field_scm_path_encoding: Pfad-Kodierung + field_searchable: Durchsuchbar + field_sharing: Gemeinsame Verwendung + field_spent_on: Datum + field_start_date: Beginn + field_start_page: Hauptseite + field_status: Status + field_subject: Thema + field_subproject: Unterprojekt von + field_summary: Zusammenfassung + field_text: Textfeld + field_time_entries: Logzeit + field_time_zone: Zeitzone + field_timeout: Auszeit (in Sekunden) + field_title: Titel + field_tracker: Tracker + field_type: Typ + field_updated_on: Aktualisiert + field_url: URL + field_user: Benutzer + field_value: Wert + field_version: Version + field_visible: Sichtbar + field_warn_on_leaving_unsaved: Vor dem Verlassen einer Seite mit ungesichertem Text im Editor warnen + field_watcher: Beobachter + + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-1 + general_csv_separator: ';' + general_first_day_of_week: '1' + general_lang_name: 'Deutsch' + general_pdf_encoding: UTF-8 general_text_No: 'Nein' general_text_Yes: 'Ja' general_text_no: 'nein' general_text_yes: 'ja' - general_lang_name: 'Deutsch' - general_csv_separator: ';' - general_csv_decimal_separator: ',' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - notice_account_updated: Konto wurde erfolgreich aktualisiert. - notice_account_invalid_creditentials: Benutzer oder Kennwort ist ungültig. - notice_account_password_updated: Kennwort wurde erfolgreich aktualisiert. - notice_account_wrong_password: Falsches Kennwort. - notice_account_register_done: Konto wurde erfolgreich angelegt. - notice_account_unknown_email: Unbekannter Benutzer. - notice_can_t_change_password: Dieses Konto verwendet eine externe Authentifizierungs-Quelle. Unmöglich, das Kennwort zu ändern. - notice_account_lost_email_sent: Eine E-Mail mit Anweisungen, ein neues Kennwort zu wählen, wurde Ihnen geschickt. - notice_account_activated: Ihr Konto ist aktiviert. Sie können sich jetzt anmelden. - notice_successful_create: Erfolgreich angelegt - notice_successful_update: Erfolgreich aktualisiert. - notice_successful_delete: Erfolgreich gelöscht. - notice_successful_connection: Verbindung erfolgreich. - notice_file_not_found: Anhang existiert nicht oder ist gelöscht worden. - notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert. - notice_not_authorized: Sie sind nicht berechtigt, auf diese Seite zuzugreifen. - notice_email_sent: "Eine E-Mail wurde an %{value} gesendet." - notice_email_error: "Beim Senden einer E-Mail ist ein Fehler aufgetreten (%{value})." - notice_feeds_access_key_reseted: Ihr Atom-Zugriffsschlüssel wurde zurückgesetzt. - notice_api_access_key_reseted: Ihr API-Zugriffsschlüssel wurde zurückgesetzt. - notice_failed_to_save_issues: "%{count} von %{total} ausgewählten Tickets konnte(n) nicht gespeichert werden: %{ids}." - notice_failed_to_save_members: "Benutzer konnte nicht gespeichert werden: %{errors}." - notice_no_issue_selected: "Kein Ticket ausgewählt! Bitte wählen Sie die Tickets, die Sie bearbeiten möchten." - notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Genehmigung des Administrators." - notice_default_data_loaded: Die Standard-Konfiguration wurde erfolgreich geladen. - notice_unable_delete_version: Die Version konnte nicht gelöscht werden. - notice_unable_delete_time_entry: Der Zeiterfassungseintrag konnte nicht gelöscht werden. - notice_issue_done_ratios_updated: Der Ticket-Fortschritt wurde aktualisiert. - - error_can_t_load_default_data: "Die Standard-Konfiguration konnte nicht geladen werden: %{value}" - error_scm_not_found: Eintrag und/oder Revision existiert nicht im Projektarchiv. - error_scm_command_failed: "Beim Zugriff auf das Projektarchiv ist ein Fehler aufgetreten: %{value}" - error_scm_annotate: "Der Eintrag existiert nicht oder kann nicht annotiert werden." - error_issue_not_found_in_project: 'Das Ticket wurde nicht gefunden oder gehört nicht zu diesem Projekt.' - error_no_tracker_in_project: Diesem Projekt ist kein Tracker zugeordnet. Bitte überprüfen Sie die Projekteinstellungen. - error_no_default_issue_status: Es ist kein Status als Standard definiert. Bitte überprüfen Sie Ihre Konfiguration (unter "Administration -> Ticket-Status"). - error_can_not_delete_custom_field: Kann das benutzerdefinierte Feld nicht löschen. - error_can_not_delete_tracker: Dieser Tracker enthält Tickets und kann nicht gelöscht werden. - error_can_not_remove_role: Diese Rolle wird verwendet und kann nicht gelöscht werden. - error_can_not_reopen_issue_on_closed_version: Das Ticket ist einer abgeschlossenen Version zugeordnet und kann daher nicht wieder geöffnet werden. - error_can_not_archive_project: Dieses Projekt kann nicht archiviert werden. - error_issue_done_ratios_not_updated: Der Ticket-Fortschritt wurde nicht aktualisiert. - error_workflow_copy_source: Bitte wählen Sie einen Quell-Tracker und eine Quell-Rolle. - error_workflow_copy_target: Bitte wählen Sie die Ziel-Tracker und -Rollen. - error_unable_delete_issue_status: "Der Ticket-Status konnte nicht gelöscht werden." - error_unable_to_connect: Fehler beim Verbinden (%{value}) - warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden." - - mail_subject_lost_password: "Ihr %{value} Kennwort" - mail_body_lost_password: 'Benutzen Sie den folgenden Link, um Ihr Kennwort zu ändern:' - mail_subject_register: "%{value} Kontoaktivierung" - mail_body_register: 'Um Ihr Konto zu aktivieren, benutzen Sie folgenden Link:' - mail_body_account_information_external: "Sie können sich mit Ihrem Konto %{value} anmelden." - mail_body_account_information: Ihre Konto-Informationen - mail_subject_account_activation_request: "Antrag auf %{value} Kontoaktivierung" - mail_body_account_activation_request: "Ein neuer Benutzer (%{value}) hat sich registriert. Sein Konto wartet auf Ihre Genehmigung:" - mail_subject_reminder: "%{count} Tickets müssen in den nächsten %{days} Tagen abgegeben werden" - mail_body_reminder: "%{count} Tickets, die Ihnen zugewiesen sind, müssen in den nächsten %{days} Tagen abgegeben werden:" - mail_subject_wiki_content_added: "Wiki-Seite '%{id}' hinzugefügt" - mail_body_wiki_content_added: "Die Wiki-Seite '%{id}' wurde von %{author} hinzugefügt." - mail_subject_wiki_content_updated: "Wiki-Seite '%{id}' erfolgreich aktualisiert" - mail_body_wiki_content_updated: "Die Wiki-Seite '%{id}' wurde von %{author} aktualisiert." - - gui_validation_error: 1 Fehler - gui_validation_error_plural: "%{count} Fehler" - - field_name: Name - field_description: Beschreibung - field_summary: Zusammenfassung - field_is_required: Erforderlich - field_firstname: Vorname - field_lastname: Nachname - field_mail: E-Mail - field_filename: Datei - field_filesize: Größe - field_downloads: Downloads - field_author: Autor - field_created_on: Angelegt - field_updated_on: Aktualisiert - field_field_format: Format - field_is_for_all: Für alle Projekte - field_possible_values: Mögliche Werte - field_regexp: Regulärer Ausdruck - field_min_length: Minimale Länge - field_max_length: Maximale Länge - field_value: Wert - field_category: Kategorie - field_title: Titel - field_project: Projekt - field_issue: Ticket - field_status: Status - field_notes: Kommentare - field_is_closed: Ticket geschlossen - field_is_default: Standardeinstellung - field_tracker: Tracker - field_subject: Thema - field_due_date: Abgabedatum - field_assigned_to: Zugewiesen an - field_priority: Priorität - field_fixed_version: Zielversion - field_user: Benutzer - field_principal: Auftraggeber - field_role: Rolle - field_homepage: Projekt-Homepage - field_is_public: Öffentlich - field_parent: Unterprojekt von - field_is_in_roadmap: In der Roadmap anzeigen - field_login: Mitgliedsname - field_mail_notification: Mailbenachrichtigung - field_admin: Administrator - field_last_login_on: Letzte Anmeldung - field_language: Sprache - field_effective_date: Datum - field_password: Kennwort - field_new_password: Neues Kennwort - field_password_confirmation: Bestätigung - field_version: Version - field_type: Typ - field_host: Host - field_port: Port - field_account: Konto - field_base_dn: Base DN - field_attr_login: Mitgliedsname-Attribut - field_attr_firstname: Vorname-Attribut - field_attr_lastname: Name-Attribut - field_attr_mail: E-Mail-Attribut - field_onthefly: On-the-fly-Benutzererstellung - field_start_date: Beginn - field_done_ratio: "% erledigt" - field_auth_source: Authentifizierungs-Modus - field_hide_mail: E-Mail-Adresse nicht anzeigen - field_comments: Kommentar - field_url: URL - field_start_page: Hauptseite - field_subproject: Unterprojekt von - field_hours: Stunden - field_activity: Aktivität - field_spent_on: Datum - field_identifier: Kennung - field_is_filter: Als Filter benutzen - field_issue_to: Zugehöriges Ticket - field_delay: Pufferzeit - field_assignable: Tickets können dieser Rolle zugewiesen werden - field_redirect_existing_links: Existierende Links umleiten - field_estimated_hours: Geschätzter Aufwand - field_column_names: Spalten - field_time_entries: Logzeit - field_time_zone: Zeitzone - field_searchable: Durchsuchbar - field_default_value: Standardwert - field_comments_sorting: Kommentare anzeigen - field_parent_title: Übergeordnete Seite - field_editable: Bearbeitbar - field_watcher: Beobachter - field_identity_url: OpenID-URL - field_content: Inhalt - field_group_by: Gruppiere Ergebnisse nach - field_sharing: Gemeinsame Verwendung - field_parent_issue: Übergeordnete Aufgabe - - setting_app_title: Applikations-Titel - setting_app_subtitle: Applikations-Untertitel - setting_welcome_text: Willkommenstext - setting_default_language: Standardsprache - setting_login_required: Authentifizierung erforderlich - setting_self_registration: Anmeldung ermöglicht - setting_attachment_max_size: Max. Dateigröße - setting_issues_export_limit: Max. Anzahl Tickets bei CSV/PDF-Export - setting_mail_from: E-Mail-Absender - setting_bcc_recipients: E-Mails als Blindkopie (BCC) senden - setting_plain_text_mail: Nur reinen Text (kein HTML) senden - setting_host_name: Hostname - setting_text_formatting: Textformatierung - setting_wiki_compression: Wiki-Historie komprimieren - setting_feeds_limit: Max. Anzahl Einträge pro Atom-Feed - setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich - setting_autofetch_changesets: Changesets automatisch abrufen - setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen - setting_commit_ref_keywords: Schlüsselwörter (Beziehungen) - setting_commit_fix_keywords: Schlüsselwörter (Status) - setting_autologin: Automatische Anmeldung - setting_date_format: Datumsformat - setting_time_format: Zeitformat - setting_cross_project_issue_relations: Ticket-Beziehungen zwischen Projekten erlauben - setting_issue_list_default_columns: Standard-Spalten in der Ticket-Auflistung - setting_emails_footer: E-Mail-Fußzeile - setting_protocol: Protokoll - setting_per_page_options: Objekte pro Seite - setting_user_format: Benutzer-Anzeigeformat - setting_activity_days_default: Anzahl Tage pro Seite der Projekt-Aktivität - setting_display_subprojects_issues: Tickets von Unterprojekten im Hauptprojekt anzeigen - setting_enabled_scm: Aktivierte Versionskontrollsysteme - setting_mail_handler_body_delimiters: "Schneide E-Mails nach einer dieser Zeilen ab" - setting_mail_handler_api_enabled: Abruf eingehender E-Mails aktivieren - setting_mail_handler_api_key: API-Schlüssel - setting_sequential_project_identifiers: Fortlaufende Projektkennungen generieren - setting_gravatar_enabled: Gravatar-Benutzerbilder benutzen - setting_gravatar_default: Standard-Gravatar-Bild - setting_diff_max_lines_displayed: Maximale Anzahl anzuzeigender Diff-Zeilen - setting_file_max_size_displayed: Maximale Größe inline angezeigter Textdateien - setting_repository_log_display_limit: Maximale Anzahl anzuzeigender Revisionen in der Historie einer Datei - setting_openid: Erlaube OpenID-Anmeldung und -Registrierung - setting_password_min_length: Mindestlänge des Kennworts - setting_new_project_user_role_id: Rolle, die einem Nicht-Administrator zugeordnet wird, der ein Projekt erstellt - setting_default_projects_modules: Standardmäßig aktivierte Module für neue Projekte - setting_issue_done_ratio: Berechne den Ticket-Fortschritt mittels - setting_issue_done_ratio_issue_field: Ticket-Feld %-erledigt - setting_issue_done_ratio_issue_status: Ticket-Status - setting_start_of_week: Wochenanfang - setting_rest_api_enabled: REST-Schnittstelle aktivieren - setting_cache_formatted_text: Formatierten Text im Cache speichern - - permission_add_project: Projekt erstellen - permission_add_subprojects: Unterprojekte erstellen - permission_edit_project: Projekt bearbeiten - permission_select_project_modules: Projektmodule auswählen - permission_manage_members: Mitglieder verwalten - permission_manage_project_activities: Aktivitäten (Zeiterfassung) verwalten - permission_manage_versions: Versionen verwalten - permission_manage_categories: Ticket-Kategorien verwalten - permission_view_issues: Tickets anzeigen - permission_add_issues: Tickets hinzufügen - permission_edit_issues: Tickets bearbeiten - permission_manage_issue_relations: Ticket-Beziehungen verwalten - permission_add_issue_notes: Kommentare hinzufügen - permission_edit_issue_notes: Kommentare bearbeiten - permission_edit_own_issue_notes: Eigene Kommentare bearbeiten - permission_move_issues: Tickets verschieben - permission_delete_issues: Tickets löschen - permission_manage_public_queries: Öffentliche Filter verwalten - permission_save_queries: Filter speichern - permission_view_gantt: Gantt-Diagramm ansehen - permission_view_calendar: Kalender ansehen - permission_view_issue_watchers: Liste der Beobachter ansehen - permission_add_issue_watchers: Beobachter hinzufügen - permission_delete_issue_watchers: Beobachter löschen - permission_log_time: Aufwände buchen - permission_view_time_entries: Gebuchte Aufwände ansehen - permission_edit_time_entries: Gebuchte Aufwände bearbeiten - permission_edit_own_time_entries: Selbst gebuchte Aufwände bearbeiten - permission_manage_news: News verwalten - permission_comment_news: News kommentieren - permission_manage_documents: Dokumente verwalten - permission_view_documents: Dokumente ansehen - permission_manage_files: Dateien verwalten - permission_view_files: Dateien ansehen - permission_manage_wiki: Wiki verwalten - permission_rename_wiki_pages: Wiki-Seiten umbenennen - permission_delete_wiki_pages: Wiki-Seiten löschen - permission_view_wiki_pages: Wiki ansehen - permission_view_wiki_edits: Wiki-Versionsgeschichte ansehen - permission_edit_wiki_pages: Wiki-Seiten bearbeiten - permission_delete_wiki_pages_attachments: Anhänge löschen - permission_protect_wiki_pages: Wiki-Seiten schützen - permission_manage_repository: Projektarchiv verwalten - permission_browse_repository: Projektarchiv ansehen - permission_view_changesets: Changesets ansehen - permission_commit_access: Commit-Zugriff (über WebDAV) - permission_manage_boards: Foren verwalten - permission_view_messages: Forenbeiträge ansehen - permission_add_messages: Forenbeiträge hinzufügen - permission_edit_messages: Forenbeiträge bearbeiten - permission_edit_own_messages: Eigene Forenbeiträge bearbeiten - permission_delete_messages: Forenbeiträge löschen - permission_delete_own_messages: Eigene Forenbeiträge löschen - permission_export_wiki_pages: Wiki-Seiten exportieren - permission_manage_subtasks: Unteraufgaben verwalten - - project_module_issue_tracking: Ticket-Verfolgung - project_module_time_tracking: Zeiterfassung - project_module_news: News - project_module_documents: Dokumente - project_module_files: Dateien - project_module_wiki: Wiki - project_module_repository: Projektarchiv - project_module_boards: Foren - project_module_calendar: Kalender - project_module_gantt: Gantt - - label_user: Benutzer - label_user_plural: Benutzer - label_user_new: Neuer Benutzer - label_user_anonymous: Anonym + label_activity: Aktivität + label_add_another_file: Eine weitere Datei hinzufügen + label_add_note: Kommentar hinzufügen + label_added: hinzugefügt + label_added_time_by: "Von %{author} vor %{age} hinzugefügt" + label_additional_workflow_transitions_for_assignee: Zusätzliche Berechtigungen wenn der Benutzer der Zugewiesene ist + label_additional_workflow_transitions_for_author: Zusätzliche Berechtigungen wenn der Benutzer der Autor ist + label_administration: Administration + label_age: Geändert vor + label_ago: vor + label_all: alle + label_all_time: gesamter Zeitraum + label_all_words: Alle Wörter + label_and_its_subprojects: "%{value} und dessen Unterprojekte" + label_any: alle + label_any_issues_in_project: irgendein Ticket im Projekt + label_any_issues_not_in_project: irgendein Ticket nicht im Projekt + label_api_access_key: API-Zugriffsschlüssel + label_api_access_key_created_on: Der API-Zugriffsschlüssel wurde vor %{value} erstellt + label_applied_status: Zugewiesener Status + label_ascending: Aufsteigend + label_assigned_to_me_issues: Mir zugewiesene Tickets + label_associated_revisions: Zugehörige Revisionen + label_attachment: Datei + label_attachment_delete: Anhang löschen + label_attachment_new: Neue Datei + label_attachment_plural: Dateien + label_attribute: Attribut + label_attribute_of_assigned_to: "%{name} des Bearbeiters" + label_attribute_of_author: "%{name} des Autors" + label_attribute_of_fixed_version: "%{name} der Zielversion" + label_attribute_of_issue: "%{name} des Tickets" + label_attribute_of_project: "%{name} des Projekts" + label_attribute_of_user: "%{name} des Benutzers" + label_attribute_plural: Attribute + label_auth_source: Authentifizierungs-Modus + label_auth_source_new: Neuer Authentifizierungs-Modus + label_auth_source_plural: Authentifizierungs-Arten + label_authentication: Authentifizierung + label_between: zwischen + label_blocked_by: Blockiert durch + label_blocks: Blockiert + label_board: Forum + label_board_locked: Gesperrt + label_board_new: Neues Forum + label_board_plural: Foren + label_board_sticky: Wichtig (immer oben) + label_boolean: Boolean + label_branch: Zweig + label_browse: Codebrowser + label_bulk_edit_selected_issues: Alle ausgewählten Tickets bearbeiten + label_bulk_edit_selected_time_entries: Ausgewählte Zeitaufwände bearbeiten + label_calendar: Kalender + label_change_plural: Änderungen + label_change_properties: Eigenschaften ändern + label_change_status: Statuswechsel + label_change_view_all: Alle Änderungen anzeigen + label_changes_details: Details aller Änderungen + label_changeset_plural: Changesets + label_child_revision: Nachfolger + label_chronological_order: in zeitlicher Reihenfolge + label_close_versions: Vollständige Versionen schließen + label_closed_issues: geschlossen + label_closed_issues_plural: geschlossen + label_comment: Kommentar + label_comment_add: Kommentar hinzufügen + label_comment_added: Kommentar hinzugefügt + label_comment_delete: Kommentar löschen + label_comment_plural: Kommentare + label_commits_per_author: Übertragungen pro Autor + label_commits_per_month: Übertragungen pro Monat + label_completed_versions: Abgeschlossene Versionen + label_confirmation: Bestätigung + label_contains: enthält + label_copied: kopiert + label_copied_from: Kopiert von + label_copied_to: Kopiert nach + label_copy_attachments: Anhänge kopieren + label_copy_same_as_target: So wie das Ziel + label_copy_source: Quelle + label_copy_subtasks: Unteraufgaben kopieren + label_copy_target: Ziel + label_copy_workflow_from: Workflow kopieren von + label_cross_project_descendants: Mit Unterprojekten + label_cross_project_hierarchy: Mit Projekthierarchie + label_cross_project_system: Mit allen Projekten + label_cross_project_tree: Mit Projektbaum + label_current_status: Gegenwärtiger Status + label_current_version: Gegenwärtige Version + label_custom_field: Benutzerdefiniertes Feld + label_custom_field_new: Neues Feld + label_custom_field_plural: Benutzerdefinierte Felder + label_date: Datum + label_date_from: Von + label_date_from_to: von %{start} bis %{end} + label_date_range: Zeitraum + label_date_to: Bis + label_day_plural: Tage + label_default: Standard + label_default_columns: Standard-Spalten + label_deleted: gelöscht + label_descending: Absteigend + label_details: Details + label_diff: diff + label_diff_inline: einspaltig + label_diff_side_by_side: nebeneinander + label_disabled: gesperrt + label_display: Anzeige + label_display_per_page: "Pro Seite: %{value}" + label_display_used_statuses_only: Zeige nur Status an, die von diesem Tracker verwendet werden + label_document: Dokument + label_document_added: Dokument hinzugefügt + label_document_new: Neues Dokument + label_document_plural: Dokumente + label_downloads_abbr: D/L + label_duplicated_by: Dupliziert durch + label_duplicates: Duplikat von + label_end_to_end: Ende - Ende + label_end_to_start: Ende - Anfang + label_enumeration_new: Neuer Wert + label_enumerations: Aufzählungen + label_environment: Umgebung + label_equals: ist + label_example: Beispiel + label_export_options: "%{export_format} Export-Eigenschaften" + label_export_to: "Auch abrufbar als:" + label_f_hour: "%{value} Stunde" + label_f_hour_plural: "%{value} Stunden" + label_feed_plural: Feeds + label_feeds_access_key: Atom-Zugriffsschlüssel + label_feeds_access_key_created_on: "Atom-Zugriffsschlüssel vor %{value} erstellt" + label_fields_permissions: Feldberechtigungen + label_file_added: Datei hinzugefügt + label_file_plural: Dateien + label_filter_add: Filter hinzufügen + label_filter_plural: Filter + label_float: Fließkommazahl + label_follows: Nachfolger von + label_gantt: Gantt-Diagramm + label_gantt_progress_line: Fortschrittslinie + label_general: Allgemein + label_generate_key: Generieren + label_git_report_last_commit: Bericht des letzten Commits für Dateien und Verzeichnisse + label_greater_or_equal: ">=" + label_group: Gruppe + label_group_new: Neue Gruppe + label_group_plural: Gruppen + label_help: Hilfe + label_hidden: Versteckt + label_history: Historie + label_home: Hauptseite + label_in: in + label_in_less_than: in weniger als + label_in_more_than: in mehr als + label_in_the_next_days: in den nächsten + label_in_the_past_days: in den letzten + label_incoming_emails: Eingehende E-Mails + label_index_by_date: Seiten nach Datum sortiert + label_index_by_title: Seiten nach Titel sortiert + label_information: Information + label_information_plural: Informationen + label_integer: Zahl + label_internal: Intern + label_issue: Ticket + label_issue_added: Ticket hinzugefügt + label_issue_category: Ticket-Kategorie + label_issue_category_new: Neue Kategorie + label_issue_category_plural: Ticket-Kategorien + label_issue_new: Neues Ticket + label_issue_note_added: Notiz hinzugefügt + label_issue_plural: Tickets + label_issue_priority_updated: Priorität aktualisiert + label_issue_status: Ticket-Status + label_issue_status_new: Neuer Status + label_issue_status_plural: Ticket-Status + label_issue_status_updated: Status aktualisiert + label_issue_tracking: Tickets + label_issue_updated: Ticket aktualisiert + label_issue_view_all: Alle Tickets anzeigen + label_issue_watchers: Beobachter + label_issues_by: "Tickets pro %{value}" + label_issues_visibility_all: Alle Tickets + label_issues_visibility_own: Tickets die folgender Benutzer erstellt hat oder die ihm zugewiesen sind + label_issues_visibility_public: Alle öffentlichen Tickets + label_item_position: "%{position}/%{count}" + label_jump_to_a_project: Zu einem Projekt springen... + label_language_based: Sprachabhängig + label_last_changes: "%{count} letzte Änderungen" + label_last_login: Letzte Anmeldung + label_last_month: voriger Monat + label_last_n_days: "die letzten %{count} Tage" + label_last_n_weeks: letzte %{count} Wochen + label_last_week: vorige Woche + label_latest_revision: Aktuellste Revision + label_latest_revision_plural: Aktuellste Revisionen + label_ldap_authentication: LDAP-Authentifizierung + label_less_or_equal: "<=" + label_less_than_ago: vor weniger als + label_list: Liste + label_loading: Lade... + label_logged_as: Angemeldet als + label_login: Anmelden + label_login_with_open_id_option: oder mit OpenID anmelden + label_logout: Abmelden + label_max_size: Maximale Größe + label_me: ich + label_member: Mitglied + label_member_new: Neues Mitglied + label_member_plural: Mitglieder + label_message_last: Letzter Forenbeitrag + label_message_new: Neues Thema + label_message_plural: Forenbeiträge + label_message_posted: Forenbeitrag hinzugefügt + label_min_max_length: Länge (Min. - Max.) + label_missing_api_access_key: Der API-Zugriffsschlüssel fehlt. + label_missing_feeds_access_key: Der Atom-Zugriffsschlüssel fehlt. + label_modified: geändert + label_module_plural: Module + label_month: Monat + label_months_from: Monate ab + label_more: Mehr + label_more_than_ago: vor mehr als + label_my_account: Mein Konto + label_my_page: Meine Seite + label_my_page_block: Verfügbare Widgets + label_my_projects: Meine Projekte + label_my_queries: Meine eigenen Abfragen + label_new: Neu + label_new_statuses_allowed: Neue Berechtigungen + label_news: News + label_news_added: News hinzugefügt + label_news_comment_added: Kommentar zu einer News hinzugefügt + label_news_latest: Letzte News + label_news_new: News hinzufügen + label_news_plural: News + label_news_view_all: Alle News anzeigen + label_next: Weiter + label_no_change_option: (Keine Änderung) + label_no_data: Nichts anzuzeigen + label_no_issues_in_project: keine Tickets im Projekt + label_nobody: Niemand + label_none: kein + label_not_contains: enthält nicht + label_not_equals: ist nicht + label_open_issues: offen + label_open_issues_plural: offen + label_optional_description: Beschreibung (optional) + label_options: Optionen + label_overall_activity: Aktivitäten aller Projekte anzeigen + label_overall_spent_time: Aufgewendete Zeit aller Projekte anzeigen + label_overview: Übersicht + label_parent_revision: Vorgänger + label_password_lost: Kennwort vergessen + label_per_page: Pro Seite + label_permissions: Berechtigungen + label_permissions_report: Berechtigungsübersicht + label_personalize_page: Diese Seite anpassen + label_planning: Terminplanung + label_please_login: Anmelden + label_plugins: Plugins + label_precedes: Vorgänger von + label_preferences: Präferenzen + label_preview: Vorschau + label_previous: Zurück + label_principal_search: "Nach Benutzer oder Gruppe suchen:" + label_profile: Profil label_project: Projekt + label_project_all: Alle Projekte + label_project_copy_notifications: Sende Mailbenachrichtigungen beim Kopieren des Projekts. + label_project_latest: Neueste Projekte label_project_new: Neues Projekt label_project_plural: Projekte + label_public_projects: Öffentliche Projekte + label_query: Benutzerdefinierte Abfrage + label_query_new: Neue Abfrage + label_query_plural: Benutzerdefinierte Abfragen + label_read: Lesen... + label_readonly: Nur-Lese-Zugriff + label_register: Registrieren + label_registered_on: Angemeldet am + label_registration_activation_by_email: Kontoaktivierung durch E-Mail + label_registration_automatic_activation: Automatische Kontoaktivierung + label_registration_manual_activation: Manuelle Kontoaktivierung + label_related_issues: Zugehörige Tickets + label_relates_to: Beziehung mit + label_relation_delete: Beziehung löschen + label_relation_new: Neue Beziehung + label_renamed: umbenannt + label_reply_plural: Antworten + label_report: Bericht + label_report_plural: Berichte + label_reported_issues: Erstellte Tickets + label_repository: Projektarchiv + label_repository_new: Neues Repository + label_repository_plural: Projektarchive + label_required: Erforderlich + label_result_plural: Resultate + label_reverse_chronological_order: in umgekehrter zeitlicher Reihenfolge + label_revision: Revision + label_revision_id: Revision %{value} + label_revision_plural: Revisionen + label_roadmap: Roadmap + label_roadmap_due_in: "Fällig in %{value}" + label_roadmap_no_issues: Keine Tickets für diese Version + label_roadmap_overdue: "%{value} verspätet" + label_role: Rolle + label_role_and_permissions: Rollen und Rechte + label_role_anonymous: Anonymous + label_role_new: Neue Rolle + label_role_non_member: Nichtmitglied + label_role_plural: Rollen + label_scm: Versionskontrollsystem + label_search: Suche + label_search_for_watchers: Nach hinzufügbaren Beobachtern suchen + label_search_titles_only: Nur Titel durchsuchen + label_send_information: Sende Kontoinformationen an Benutzer + label_send_test_email: Test-E-Mail senden + label_session_expiration: Ende einer Sitzung + label_settings: Konfiguration + label_show_closed_projects: Geschlossene Projekte anzeigen + label_show_completed_versions: Abgeschlossene Versionen anzeigen + label_sort: Sortierung + label_sort_by: "Sortiert nach %{value}" + label_sort_higher: Eins höher + label_sort_highest: An den Anfang + label_sort_lower: Eins tiefer + label_sort_lowest: Ans Ende + label_spent_time: Aufgewendete Zeit + label_start_to_end: Anfang - Ende + label_start_to_start: Anfang - Anfang + label_statistics: Statistiken + label_status_transitions: Statusänderungen + label_stay_logged_in: Angemeldet bleiben + label_string: Text + label_subproject_new: Neues Unterprojekt + label_subproject_plural: Unterprojekte + label_subtask_plural: Unteraufgaben + label_tag: Markierung + label_text: Langer Text + label_theme: Stil + label_this_month: aktueller Monat + label_this_week: aktuelle Woche + label_this_year: aktuelles Jahr + label_time_entry_plural: Benötigte Zeit + label_time_tracking: Zeiterfassung + label_today: heute + label_topic_plural: Themen + label_total: Gesamtzahl + label_total_time: Gesamtzeit + label_tracker: Tracker + label_tracker_new: Neuer Tracker + label_tracker_plural: Tracker + label_update_issue_done_ratios: Ticket-Fortschritt aktualisieren + label_updated_time: "Vor %{value} aktualisiert" + label_updated_time_by: "Von %{author} vor %{age} aktualisiert" + label_used_by: Benutzt von + label_user: Benutzer + label_user_activity: "Aktivität von %{value}" + label_user_anonymous: Anonym + label_user_mail_no_self_notified: "Ich möchte nicht über Änderungen benachrichtigt werden, die ich selbst durchführe." + label_user_mail_option_all: "Für alle Ereignisse in all meinen Projekten" + label_user_mail_option_none: Keine Ereignisse + label_user_mail_option_only_assigned: Nur für Aufgaben für die ich zuständig bin + label_user_mail_option_only_my_events: Nur für Aufgaben die ich beobachte oder an welchen ich mitarbeite + label_user_mail_option_only_owner: Nur für Aufgaben die ich angelegt habe + label_user_mail_option_selected: "Für alle Ereignisse in den ausgewählten Projekten" + label_user_new: Neuer Benutzer + label_user_plural: Benutzer + label_user_search: "Nach Benutzer suchen:" + label_version: Version + label_version_new: Neue Version + label_version_plural: Versionen + label_version_sharing_descendants: Mit Unterprojekten + label_version_sharing_hierarchy: Mit Projekthierarchie + label_version_sharing_none: Nicht gemeinsam verwenden + label_version_sharing_system: Mit allen Projekten + label_version_sharing_tree: Mit Projektbaum + label_view_all_revisions: Alle Revisionen anzeigen + label_view_diff: Unterschiede anzeigen + label_view_revisions: Revisionen anzeigen + label_visibility_private: nur für mich + label_visibility_public: für jeden Benutzer + label_visibility_roles: nur für diese Rollen + label_watched_issues: Beobachtete Tickets + label_week: Woche + label_wiki: Wiki + label_wiki_content_added: Wiki-Seite hinzugefügt + label_wiki_content_updated: Wiki-Seite aktualisiert + label_wiki_edit: Wiki-Bearbeitung + label_wiki_edit_plural: Wiki-Bearbeitungen + label_wiki_page: Wiki-Seite + label_wiki_page_plural: Wiki-Seiten + label_workflow: Workflow + label_x_closed_issues_abbr: + zero: 0 geschlossen + one: 1 geschlossen + other: "%{count} geschlossen" + label_x_comments: + zero: keine Kommentare + one: 1 Kommentar + other: "%{count} Kommentare" + label_x_issues: + zero: 0 Tickets + one: 1 Ticket + other: "%{count} Tickets" + label_x_open_issues_abbr: + zero: 0 offen + one: 1 offen + other: "%{count} offen" + label_x_open_issues_abbr_on_total: + zero: 0 offen / %{total} + one: 1 offen / %{total} + other: "%{count} offen / %{total}" label_x_projects: zero: keine Projekte one: 1 Projekt other: "%{count} Projekte" + label_year: Jahr label_project_all: Alle Projekte label_project_latest: Neueste Projekte label_issue: Ticket @@ -614,487 +983,299 @@ label_in: an label_today: heute label_all_time: gesamter Zeitraum +>>>>>>> other label_yesterday: gestern - label_this_week: aktuelle Woche - label_last_week: vorige Woche - label_last_n_days: "die letzten %{count} Tage" - label_this_month: aktueller Monat - label_last_month: voriger Monat - label_this_year: aktuelles Jahr - label_date_range: Zeitraum - label_less_than_ago: vor weniger als - label_more_than_ago: vor mehr als - label_ago: vor - label_contains: enthält - label_not_contains: enthält nicht - label_day_plural: Tage - label_repository: Projektarchiv - label_repository_plural: Projektarchive - label_browse: Codebrowser - label_modification: "%{count} Änderung" - label_modification_plural: "%{count} Änderungen" - label_branch: Zweig - label_tag: Markierung - label_revision: Revision - label_revision_plural: Revisionen - label_revision_id: Revision %{value} - label_associated_revisions: Zugehörige Revisionen - label_added: hinzugefügt - label_modified: geändert - label_copied: kopiert - label_renamed: umbenannt - label_deleted: gelöscht - label_latest_revision: Aktuellste Revision - label_latest_revision_plural: Aktuellste Revisionen - label_view_revisions: Revisionen anzeigen - label_view_all_revisions: Alle Revisionen anzeigen - label_max_size: Maximale Größe - label_sort_highest: An den Anfang - label_sort_higher: Eins höher - label_sort_lower: Eins tiefer - label_sort_lowest: Ans Ende - label_roadmap: Roadmap - label_roadmap_due_in: "Fällig in %{value}" - label_roadmap_overdue: "%{value} verspätet" - label_roadmap_no_issues: Keine Tickets für diese Version - label_search: Suche - label_result_plural: Resultate - label_all_words: Alle Wörter - label_wiki: Wiki - label_wiki_edit: Wiki-Bearbeitung - label_wiki_edit_plural: Wiki-Bearbeitungen - label_wiki_page: Wiki-Seite - label_wiki_page_plural: Wiki-Seiten - label_index_by_title: Seiten nach Titel sortiert - label_index_by_date: Seiten nach Datum sortiert - label_current_version: Gegenwärtige Version - label_preview: Vorschau - label_feed_plural: Feeds - label_changes_details: Details aller Änderungen - label_issue_tracking: Tickets - label_spent_time: Aufgewendete Zeit - label_overall_spent_time: Aufgewendete Zeit aller Projekte anzeigen - label_f_hour: "%{value} Stunde" - label_f_hour_plural: "%{value} Stunden" - label_time_tracking: Zeiterfassung - label_change_plural: Änderungen - label_statistics: Statistiken - label_commits_per_month: Übertragungen pro Monat - label_commits_per_author: Übertragungen pro Autor - label_view_diff: Unterschiede anzeigen - label_diff_inline: einspaltig - label_diff_side_by_side: nebeneinander - label_options: Optionen - label_copy_workflow_from: Workflow kopieren von - label_permissions_report: Berechtigungsübersicht - label_watched_issues: Beobachtete Tickets - label_related_issues: Zugehörige Tickets - label_applied_status: Zugewiesener Status - label_loading: Lade... - label_relation_new: Neue Beziehung - label_relation_delete: Beziehung löschen - label_relates_to: Beziehung mit - label_duplicates: Duplikat von - label_duplicated_by: Dupliziert durch - label_blocks: Blockiert - label_blocked_by: Blockiert durch - label_precedes: Vorgänger von - label_follows: Folgt - label_end_to_start: Ende - Anfang - label_end_to_end: Ende - Ende - label_start_to_start: Anfang - Anfang - label_start_to_end: Anfang - Ende - label_stay_logged_in: Angemeldet bleiben - label_disabled: gesperrt - label_show_completed_versions: Abgeschlossene Versionen anzeigen - label_me: ich - label_board: Forum - label_board_new: Neues Forum - label_board_plural: Foren - label_board_locked: Gesperrt - label_board_sticky: Wichtig (immer oben) - label_topic_plural: Themen - label_message_plural: Forenbeiträge - label_message_last: Letzter Forenbeitrag - label_message_new: Neues Thema - label_message_posted: Forenbeitrag hinzugefügt - label_reply_plural: Antworten - label_send_information: Sende Kontoinformationen an Benutzer - label_year: Jahr - label_month: Monat - label_week: Woche - label_date_from: Von - label_date_to: Bis - label_language_based: Sprachabhängig - label_sort_by: "Sortiert nach %{value}" - label_send_test_email: Test-E-Mail senden - label_feeds_access_key: RSS-Zugriffsschlüssel - label_missing_feeds_access_key: Der RSS-Zugriffsschlüssel fehlt. - label_feeds_access_key_created_on: "Atom-Zugriffsschlüssel vor %{value} erstellt" - label_module_plural: Module - label_added_time_by: "Von %{author} vor %{age} hinzugefügt" - label_updated_time_by: "Von %{author} vor %{age} aktualisiert" - label_updated_time: "Vor %{value} aktualisiert" - label_jump_to_a_project: Zu einem Projekt springen... - label_file_plural: Dateien - label_changeset_plural: Changesets - label_default_columns: Standard-Spalten - label_no_change_option: (Keine Änderung) - label_bulk_edit_selected_issues: Alle ausgewählten Tickets bearbeiten - label_theme: Stil - label_default: Standard - label_search_titles_only: Nur Titel durchsuchen - label_user_mail_option_all: "Für alle Ereignisse in all meinen Projekten" - label_user_mail_option_selected: "Für alle Ereignisse in den ausgewählten Projekten..." - label_user_mail_no_self_notified: "Ich möchte nicht über Änderungen benachrichtigt werden, die ich selbst durchführe." - label_registration_activation_by_email: Kontoaktivierung durch E-Mail - label_registration_manual_activation: Manuelle Kontoaktivierung - label_registration_automatic_activation: Automatische Kontoaktivierung - label_display_per_page: "Pro Seite: %{value}" - label_age: Geändert vor - label_change_properties: Eigenschaften ändern - label_general: Allgemein - label_more: Mehr - label_scm: Versionskontrollsystem - label_plugins: Plugins - label_ldap_authentication: LDAP-Authentifizierung - label_downloads_abbr: D/L - label_optional_description: Beschreibung (optional) - label_add_another_file: Eine weitere Datei hinzufügen - label_preferences: Präferenzen - label_chronological_order: in zeitlicher Reihenfolge - label_reverse_chronological_order: in umgekehrter zeitlicher Reihenfolge - label_planning: Terminplanung - label_incoming_emails: Eingehende E-Mails - label_generate_key: Generieren - label_issue_watchers: Beobachter - label_example: Beispiel - label_display: Anzeige - label_sort: Sortierung - label_ascending: Aufsteigend - label_descending: Absteigend - label_date_from_to: von %{start} bis %{end} - label_wiki_content_added: Die Wiki-Seite wurde erfolgreich hinzugefügt. - label_wiki_content_updated: Die Wiki-Seite wurde erfolgreich aktualisiert. - label_group: Gruppe - label_group_plural: Gruppen - label_group_new: Neue Gruppe - label_time_entry_plural: Benötigte Zeit - label_version_sharing_none: Nicht gemeinsam verwenden - label_version_sharing_descendants: Mit Unterprojekten - label_version_sharing_hierarchy: Mit Projekthierarchie - label_version_sharing_tree: Mit Projektbaum - label_version_sharing_system: Mit allen Projekten - label_update_issue_done_ratios: Ticket-Fortschritt aktualisieren - label_copy_source: Quelle - label_copy_target: Ziel - label_copy_same_as_target: So wie das Ziel - label_display_used_statuses_only: Zeige nur Status an, die von diesem Tracker verwendet werden - label_api_access_key: API-Zugriffsschlüssel - label_missing_api_access_key: Der API-Zugriffsschlüssel fehlt. - label_api_access_key_created_on: Der API-Zugriffsschlüssel wurde vor %{value} erstellt - label_profile: Profil - label_subtask_plural: Unteraufgaben - label_project_copy_notifications: Sende Mailbenachrichtigungen beim Kopieren des Projekts. - label_principal_search: "Nach Benutzer oder Gruppe suchen:" - label_user_search: "Nach Benutzer suchen:" - button_login: Anmelden - button_submit: OK - button_save: Speichern - button_check_all: Alles auswählen - button_uncheck_all: Alles abwählen - button_delete: Löschen - button_create: Anlegen - button_create_and_continue: Anlegen und weiter - button_test: Testen - button_edit: Bearbeiten - button_edit_associated_wikipage: "Zugehörige Wikiseite bearbeiten: %{page_title}" - button_add: Hinzufügen - button_change: Wechseln - button_apply: Anwenden - button_clear: Zurücksetzen - button_lock: Sperren - button_unlock: Entsperren - button_download: Download - button_list: Liste - button_view: Anzeigen - button_move: Verschieben - button_move_and_follow: Verschieben und Ticket anzeigen - button_back: Zurück - button_cancel: Abbrechen - button_activate: Aktivieren - button_sort: Sortieren - button_log_time: Aufwand buchen - button_rollback: Auf diese Version zurücksetzen - button_watch: Beobachten - button_unwatch: Nicht beobachten - button_reply: Antworten - button_archive: Archivieren - button_unarchive: Entarchivieren - button_reset: Zurücksetzen - button_rename: Umbenennen - button_change_password: Kennwort ändern - button_copy: Kopieren - button_copy_and_follow: Kopieren und Ticket anzeigen - button_annotate: Annotieren - button_update: Bearbeiten - button_configure: Konfigurieren - button_quote: Zitieren - button_duplicate: Duplizieren - button_show: Anzeigen + mail_body_account_activation_request: "Ein neuer Benutzer (%{value}) hat sich registriert. Sein Konto wartet auf Ihre Genehmigung:" + mail_body_account_information: Ihre Konto-Informationen + mail_body_account_information_external: "Sie können sich mit Ihrem Konto %{value} anmelden." + mail_body_lost_password: 'Benutzen Sie den folgenden Link, um Ihr Kennwort zu ändern:' + mail_body_register: 'Um Ihr Konto zu aktivieren, benutzen Sie folgenden Link:' + mail_body_reminder: "%{count} Tickets, die Ihnen zugewiesen sind, müssen in den nächsten %{days} Tagen abgegeben werden:" + mail_body_wiki_content_added: "Die Wiki-Seite '%{id}' wurde von %{author} hinzugefügt." + mail_body_wiki_content_updated: "Die Wiki-Seite '%{id}' wurde von %{author} aktualisiert." + mail_subject_account_activation_request: "Antrag auf %{value} Kontoaktivierung" + mail_subject_lost_password: "Ihr %{value} Kennwort" + mail_subject_register: "%{value} Kontoaktivierung" + mail_subject_reminder: "%{count} Tickets müssen in den nächsten %{days} Tagen abgegeben werden" + mail_subject_wiki_content_added: "Wiki-Seite '%{id}' hinzugefügt" + mail_subject_wiki_content_updated: "Wiki-Seite '%{id}' erfolgreich aktualisiert" + + notice_account_activated: Ihr Konto ist aktiviert. Sie können sich jetzt anmelden. + notice_account_deleted: Ihr Benutzerkonto wurde unwiderruflich gelöscht. + notice_account_invalid_creditentials: Benutzer oder Kennwort ist ungültig. + notice_account_lost_email_sent: Eine E-Mail mit Anweisungen, ein neues Kennwort zu wählen, wurde Ihnen geschickt. + notice_account_locked: Ihr Konto ist gesperrt. + notice_account_not_activated_yet: Sie haben Ihr Konto noch nicht aktiviert. Wenn Sie die Aktivierungsmail erneut erhalten wollen, klicken Sie bitte hier. + notice_account_password_updated: Kennwort wurde erfolgreich aktualisiert. + notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Genehmigung des Administrators." + notice_account_register_done: Konto wurde erfolgreich angelegt. Eine E-Mail mit weiteren Instruktionen zur Kontoaktivierung wurde an %{email} gesendet. + notice_account_unknown_email: Unbekannter Benutzer. + notice_account_updated: Konto wurde erfolgreich aktualisiert. + notice_account_wrong_password: Falsches Kennwort. + notice_api_access_key_reseted: Ihr API-Zugriffsschlüssel wurde zurückgesetzt. + notice_can_t_change_password: Dieses Konto verwendet eine externe Authentifizierungs-Quelle. Unmöglich, das Kennwort zu ändern. + notice_default_data_loaded: Die Standard-Konfiguration wurde erfolgreich geladen. + notice_email_error: "Beim Senden einer E-Mail ist ein Fehler aufgetreten (%{value})." + notice_email_sent: "Eine E-Mail wurde an %{value} gesendet." + notice_failed_to_save_issues: "%{count} von %{total} ausgewählten Tickets konnte(n) nicht gespeichert werden: %{ids}." + notice_failed_to_save_members: "Benutzer konnte nicht gespeichert werden: %{errors}." + notice_failed_to_save_time_entries: "Gescheitert %{count} Zeiteinträge für %{total} von ausgewählten: %{ids} zu speichern." + notice_feeds_access_key_reseted: Ihr Atom-Zugriffsschlüssel wurde zurückgesetzt. + notice_file_not_found: Anhang existiert nicht oder ist gelöscht worden. + notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max}) + notice_issue_done_ratios_updated: Der Ticket-Fortschritt wurde aktualisiert. + notice_issue_successful_create: Ticket %{id} erstellt. + notice_issue_update_conflict: Das Ticket wurde während Ihrer Bearbeitung von einem anderen Nutzer überarbeitet. + notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert. + notice_new_password_must_be_different: Das neue Passwort muss sich vom dem aktuellen + unterscheiden + notice_no_issue_selected: "Kein Ticket ausgewählt! Bitte wählen Sie die Tickets, die Sie bearbeiten möchten." + notice_not_authorized: Sie sind nicht berechtigt, auf diese Seite zuzugreifen. + notice_not_authorized_archived_project: Das Projekt wurde archiviert und ist daher nicht nicht verfügbar. + notice_successful_connection: Verbindung erfolgreich. + notice_successful_create: Erfolgreich angelegt + notice_successful_delete: Erfolgreich gelöscht. + notice_successful_update: Erfolgreich aktualisiert. + notice_unable_delete_time_entry: Der Zeiterfassungseintrag konnte nicht gelöscht werden. + notice_unable_delete_version: Die Version konnte nicht gelöscht werden. + notice_user_successful_create: Benutzer %{id} angelegt. + + permission_add_issue_notes: Kommentare hinzufügen + permission_add_issue_watchers: Beobachter hinzufügen + permission_add_issues: Tickets hinzufügen + permission_add_messages: Forenbeiträge hinzufügen + permission_add_project: Projekt erstellen + permission_add_subprojects: Unterprojekte erstellen + permission_add_documents: Dokumente hinzufügen + permission_browse_repository: Projektarchiv ansehen + permission_close_project: Schließen / erneutes Öffnen eines Projekts + permission_comment_news: News kommentieren + permission_commit_access: Commit-Zugriff + permission_delete_issue_watchers: Beobachter löschen + permission_delete_issues: Tickets löschen + permission_delete_messages: Forenbeiträge löschen + permission_delete_own_messages: Eigene Forenbeiträge löschen + permission_delete_wiki_pages: Wiki-Seiten löschen + permission_delete_wiki_pages_attachments: Anhänge löschen + permission_delete_documents: Dokumente löschen + permission_edit_issue_notes: Kommentare bearbeiten + permission_edit_issues: Tickets bearbeiten + permission_edit_messages: Forenbeiträge bearbeiten + permission_edit_own_issue_notes: Eigene Kommentare bearbeiten + permission_edit_own_messages: Eigene Forenbeiträge bearbeiten + permission_edit_own_time_entries: Selbst gebuchte Aufwände bearbeiten + permission_edit_project: Projekt bearbeiten + permission_edit_time_entries: Gebuchte Aufwände bearbeiten + permission_edit_wiki_pages: Wiki-Seiten bearbeiten + permission_edit_documents: Dokumente bearbeiten + permission_export_wiki_pages: Wiki-Seiten exportieren + permission_log_time: Aufwände buchen + permission_manage_boards: Foren verwalten + permission_manage_categories: Ticket-Kategorien verwalten + permission_manage_files: Dateien verwalten + permission_manage_issue_relations: Ticket-Beziehungen verwalten + permission_manage_members: Mitglieder verwalten + permission_manage_news: News verwalten + permission_manage_project_activities: Aktivitäten (Zeiterfassung) verwalten + permission_manage_public_queries: Öffentliche Filter verwalten + permission_manage_related_issues: Zugehörige Tickets verwalten + permission_manage_repository: Projektarchiv verwalten + permission_manage_subtasks: Unteraufgaben verwalten + permission_manage_versions: Versionen verwalten + permission_manage_wiki: Wiki verwalten + permission_move_issues: Tickets verschieben + permission_protect_wiki_pages: Wiki-Seiten schützen + permission_rename_wiki_pages: Wiki-Seiten umbenennen + permission_save_queries: Filter speichern + permission_select_project_modules: Projektmodule auswählen + permission_set_issues_private: Tickets privat oder öffentlich markieren + permission_set_notes_private: Kommentar als privat markieren + permission_set_own_issues_private: Eigene Tickets privat oder öffentlich markieren + permission_view_calendar: Kalender ansehen + permission_view_changesets: Changesets ansehen + permission_view_documents: Dokumente ansehen + permission_view_files: Dateien ansehen + permission_view_gantt: Gantt-Diagramm ansehen + permission_view_issue_watchers: Liste der Beobachter ansehen + permission_view_issues: Tickets anzeigen + permission_view_messages: Forenbeiträge ansehen + permission_view_private_notes: Private Kommentare sehen + permission_view_time_entries: Gebuchte Aufwände ansehen + permission_view_wiki_edits: Wiki-Versionsgeschichte ansehen + permission_view_wiki_pages: Wiki ansehen + + project_module_boards: Foren + project_module_calendar: Kalender + project_module_documents: Dokumente + project_module_files: Dateien + project_module_gantt: Gantt + project_module_issue_tracking: Ticket-Verfolgung + project_module_news: News + project_module_repository: Projektarchiv + project_module_time_tracking: Zeiterfassung + project_module_wiki: Wiki + project_status_active: aktiv + project_status_archived: archiviert + project_status_closed: geschlossen + + setting_activity_days_default: Anzahl Tage pro Seite der Projekt-Aktivität + setting_app_subtitle: Applikations-Untertitel + setting_app_title: Applikations-Titel + setting_attachment_max_size: Max. Dateigröße + setting_autofetch_changesets: Changesets automatisch abrufen + setting_autologin: Automatische Anmeldung + setting_bcc_recipients: E-Mails als Blindkopie (BCC) senden + setting_cache_formatted_text: Formatierten Text im Cache speichern + setting_commit_cross_project_ref: Erlauben auf Tickets aller anderen Projekte zu referenzieren + setting_commit_fix_keywords: Schlüsselwörter (Status) + setting_commit_logtime_activity_id: Aktivität für die Zeiterfassung + setting_commit_logtime_enabled: Aktiviere Zeitlogging + setting_commit_ref_keywords: Schlüsselwörter (Beziehungen) + setting_cross_project_issue_relations: Ticket-Beziehungen zwischen Projekten erlauben + setting_cross_project_subtasks: Projektübergreifende Unteraufgaben erlauben + setting_date_format: Datumsformat + setting_default_issue_start_date_to_creation_date: Aktuelles Datum als Beginn für neue Tickets verwenden + setting_default_language: Standardsprache + setting_default_notification_option: Standard Benachrichtigungsoptionen + setting_default_projects_modules: Standardmäßig aktivierte Module für neue Projekte + setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich + setting_diff_max_lines_displayed: Maximale Anzahl anzuzeigender Diff-Zeilen + setting_display_subprojects_issues: Tickets von Unterprojekten im Hauptprojekt anzeigen + setting_emails_footer: E-Mail-Fußzeile + setting_emails_header: E-Mail-Kopfzeile + setting_enabled_scm: Aktivierte Versionskontrollsysteme + setting_feeds_limit: Max. Anzahl Einträge pro Atom-Feed + setting_file_max_size_displayed: Maximale Größe inline angezeigter Textdateien + setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden + setting_gravatar_default: Standard-Gravatar-Bild + setting_gravatar_enabled: Gravatar-Benutzerbilder benutzen + setting_host_name: Hostname + setting_issue_done_ratio: Berechne den Ticket-Fortschritt mittels + setting_issue_done_ratio_issue_field: Ticket-Feld %-erledigt + setting_issue_done_ratio_issue_status: Ticket-Status + setting_issue_group_assignment: Ticketzuweisung an Gruppen erlauben + setting_issue_list_default_columns: Standard-Spalten in der Ticket-Auflistung + setting_issues_export_limit: Max. Anzahl Tickets bei CSV/PDF-Export + setting_jsonp_enabled: JSONP Unterstützung aktivieren + setting_login_required: Authentifizierung erforderlich + setting_mail_from: E-Mail-Absender + setting_mail_handler_api_enabled: Abruf eingehender E-Mails aktivieren + setting_mail_handler_api_key: API-Schlüssel + setting_mail_handler_body_delimiters: "Schneide E-Mails nach einer dieser Zeilen ab" + setting_mail_handler_excluded_filenames: Anhänge nach Namen ausschließen + setting_new_project_user_role_id: Rolle, die einem Nicht-Administrator zugeordnet wird, der ein Projekt erstellt + setting_non_working_week_days: Arbeitsfreie Tage + setting_openid: Erlaube OpenID-Anmeldung und -Registrierung + setting_password_min_length: Mindestlänge des Kennworts + setting_per_page_options: Objekte pro Seite + setting_plain_text_mail: Nur reinen Text (kein HTML) senden + setting_protocol: Protokoll + setting_repositories_encodings: Enkodierung von Anhängen und Repositories + setting_repository_log_display_limit: Maximale Anzahl anzuzeigender Revisionen in der Historie einer Datei + setting_rest_api_enabled: REST-Schnittstelle aktivieren + setting_self_registration: Anmeldung ermöglicht + setting_sequential_project_identifiers: Fortlaufende Projektkennungen generieren + setting_session_lifetime: Längste Dauer einer Sitzung + setting_session_timeout: Zeitüberschreitung bei Inaktivität + setting_start_of_week: Wochenanfang + setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen + setting_text_formatting: Textformatierung + setting_thumbnails_enabled: Vorschaubilder von Dateianhängen anzeigen + setting_thumbnails_size: Größe der Vorschaubilder (in Pixel) + setting_time_format: Zeitformat + setting_unsubscribe: Erlaubt Benutzern das eigene Benutzerkonto zu löschen + setting_user_format: Benutzer-Anzeigeformat + setting_welcome_text: Willkommenstext + setting_wiki_compression: Wiki-Historie komprimieren + setting_default_projects_tracker_ids: Standardmäßig aktivierte Tracker für neue Projekte status_active: aktiv + status_locked: gesperrt status_registered: nicht aktivierte - status_locked: gesperrt - version_status_open: offen - version_status_locked: gesperrt - version_status_closed: abgeschlossen - - field_active: Aktiv - - text_select_mail_notifications: Bitte wählen Sie die Aktionen aus, für die eine Mailbenachrichtigung gesendet werden soll. - text_regexp_info: z. B. ^[A-Z0-9]+$ - text_min_max_length_info: 0 heißt keine Beschränkung - text_project_destroy_confirmation: Sind Sie sicher, dass sie das Projekt löschen wollen? - text_subprojects_destroy_warning: "Dessen Unterprojekte (%{value}) werden ebenfalls gelöscht." - text_workflow_edit: Workflow zum Bearbeiten auswählen + text_account_destroy_confirmation: Möchten Sie wirklich fortfahren?\nIhr Benutzerkonto wird für immer gelöscht und kann nicht wiederhergestellt werden. text_are_you_sure: Sind Sie sicher? - text_journal_changed: "%{label} wurde von %{old} zu %{new} geändert" - text_journal_set_to: "%{label} wurde auf %{value} gesetzt" - text_journal_deleted: "%{label} %{old} wurde gelöscht" - text_journal_added: "%{label} %{value} wurde hinzugefügt" - text_tip_issue_begin_day: Aufgabe, die an diesem Tag beginnt - text_tip_issue_end_day: Aufgabe, die an diesem Tag endet - text_tip_issue_begin_end_day: Aufgabe, die an diesem Tag beginnt und endet - text_project_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
    Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + text_assign_time_entries_to_project: Gebuchte Aufwände dem Projekt zuweisen text_caracters_maximum: "Max. %{count} Zeichen." text_caracters_minimum: "Muss mindestens %{count} Zeichen lang sein." + text_comma_separated: Mehrere Werte erlaubt (durch Komma getrennt). + text_convert_available: ImageMagick Konvertierung verfügbar (optional) + text_custom_field_possible_values_info: 'Eine Zeile pro Wert' + text_default_administrator_account_changed: Administrator-Kennwort geändert + text_destroy_time_entries: Gebuchte Aufwände löschen + text_destroy_time_entries_question: Es wurden bereits %{hours} Stunden auf dieses Ticket gebucht. Was soll mit den Aufwänden geschehen? + text_diff_truncated: '... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.' + text_email_delivery_not_configured: "Der SMTP-Server ist nicht konfiguriert und Mailbenachrichtigungen sind ausgeschaltet.\nNehmen Sie die Einstellungen für Ihren SMTP-Server in config/configuration.yml vor und starten Sie die Applikation neu." + text_enumeration_category_reassign_to: 'Die Objekte stattdessen diesem Wert zuordnen:' + text_enumeration_destroy_question: "%{count} Objekt(e) sind diesem Wert zugeordnet." + text_file_repository_writable: Verzeichnis für Dateien beschreibbar + text_git_repository_note: Repository steht für sich alleine (bare) und liegt lokal (z.B. /gitrepo, c:\gitrepo) + text_issue_added: "Ticket %{id} wurde erstellt von %{author}." + text_issue_category_destroy_assignments: Kategorie-Zuordnung entfernen + text_issue_category_destroy_question: "Einige Tickets (%{count}) sind dieser Kategorie zugeodnet. Was möchten Sie tun?" + text_issue_category_reassign_to: Tickets dieser Kategorie zuordnen + text_issue_conflict_resolution_add_notes: Meine Änderungen übernehmen und alle anderen Änderungen verwerfen + text_issue_conflict_resolution_cancel: Meine Änderungen verwerfen und %{link} neu anzeigen + text_issue_conflict_resolution_overwrite: Meine Änderungen trotzdem übernehmen (vorherige Notizen bleiben erhalten aber manche können überschrieben werden) + text_issue_updated: "Ticket %{id} wurde aktualisiert von %{author}." + text_issues_destroy_confirmation: 'Sind Sie sicher, dass Sie die ausgewählten Tickets löschen möchten?' + text_issues_destroy_descendants_confirmation: Dies wird auch %{count} Unteraufgabe/n löschen. + text_issues_ref_in_commit_messages: Ticket-Beziehungen und -Status in Commit-Log-Meldungen + text_journal_added: "%{label} %{value} wurde hinzugefügt" + text_journal_changed: "%{label} wurde von %{old} zu %{new} geändert" + text_journal_changed_no_detail: "%{label} aktualisiert" + text_journal_deleted: "%{label} %{old} wurde gelöscht" + text_journal_set_to: "%{label} wurde auf %{value} gesetzt" text_length_between: "Länge zwischen %{min} und %{max} Zeichen." + text_line_separated: Mehrere Werte sind erlaubt (eine Zeile pro Wert). + text_load_default_configuration: Standard-Konfiguration laden + text_mercurial_repository_note: Lokales repository (e.g. /hgrepo, c:\hgrepo) + text_min_max_length_info: 0 heißt keine Beschränkung + text_no_configuration_data: "Rollen, Tracker, Ticket-Status und Workflows wurden noch nicht konfiguriert.\nEs ist sehr zu empfehlen, die Standard-Konfiguration zu laden. Sobald sie geladen ist, können Sie diese abändern." + text_own_membership_delete_confirmation: "Sie sind dabei, einige oder alle Ihre Berechtigungen zu entfernen. Es ist möglich, dass Sie danach das Projekt nicht mehr ansehen oder bearbeiten dürfen.\nSind Sie sicher, dass Sie dies tun möchten?" + text_plugin_assets_writable: Verzeichnis für Plugin-Assets beschreibbar + text_project_closed: Dieses Projekt ist geschlossen und kann nicht bearbeitet werden. + text_project_destroy_confirmation: Sind Sie sicher, dass Sie das Projekt löschen wollen? + text_project_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt, muss mit einem Kleinbuchstaben beginnen.
    Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + text_reassign_time_entries: 'Gebuchte Aufwände diesem Ticket zuweisen:' + text_regexp_info: z. B. ^[A-Z0-9]+$ + text_repository_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
    Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + text_repository_usernames_mapping: "Bitte legen Sie die Zuordnung der Redmine-Benutzer zu den Benutzernamen der Commit-Log-Meldungen des Projektarchivs fest.\nBenutzer mit identischen Redmine- und Projektarchiv-Benutzernamen oder -E-Mail-Adressen werden automatisch zugeordnet." + text_rmagick_available: RMagick verfügbar (optional) + text_scm_command: Kommando + text_scm_command_not_available: SCM-Kommando ist nicht verfügbar. Bitte prüfen Sie die Einstellungen im Administrationspanel. + text_scm_command_version: Version + text_scm_config: Die SCM-Kommandos können in der in config/configuration.yml konfiguriert werden. Redmine muss anschließend neu gestartet werden. + text_scm_path_encoding_note: "Standard: UTF-8" + text_select_mail_notifications: Bitte wählen Sie die Aktionen aus, für die eine Mailbenachrichtigung gesendet werden soll. + text_select_project_modules: 'Bitte wählen Sie die Module aus, die in diesem Projekt aktiviert sein sollen:' + text_session_expiration_settings: "Achtung: Änderungen können aktuelle Sitzungen beenden, Ihre eingeschlossen!" + text_status_changed_by_changeset: "Status geändert durch Changeset %{value}." + text_subprojects_destroy_warning: "Dessen Unterprojekte (%{value}) werden ebenfalls gelöscht." + text_time_entries_destroy_confirmation: Sind Sie sicher, dass Sie die ausgewählten Zeitaufwände löschen möchten? + text_time_logged_by_changeset: Angewendet in Changeset %{value}. + text_tip_issue_begin_day: Aufgabe, die an diesem Tag beginnt + text_tip_issue_begin_end_day: Aufgabe, die an diesem Tag beginnt und endet + text_tip_issue_end_day: Aufgabe, die an diesem Tag endet text_tracker_no_workflow: Kein Workflow für diesen Tracker definiert. + text_turning_multiple_off: Wenn Sie die Mehrfachauswahl deaktivieren, werden Felder mit Mehrfachauswahl bereinigt. + Dadurch wird sichergestellt, dass lediglich ein Wert pro Feld ausgewählt ist. text_unallowed_characters: Nicht erlaubte Zeichen - text_comma_separated: Mehrere Werte erlaubt (durch Komma getrennt). - text_line_separated: Mehrere Werte sind erlaubt (eine Zeile pro Wert). - text_issues_ref_in_commit_messages: Ticket-Beziehungen und -Status in Commit-Log-Meldungen - text_issue_added: "Ticket %{id} wurde erstellt von %{author}." - text_issue_updated: "Ticket %{id} wurde aktualisiert von %{author}." + text_user_mail_option: "Für nicht ausgewählte Projekte werden Sie nur Benachrichtigungen für Dinge erhalten, die Sie beobachten oder an denen Sie beteiligt sind (z. B. Tickets, deren Autor Sie sind oder die Ihnen zugewiesen sind)." + text_user_wrote: "%{value} schrieb:" + text_warn_on_leaving_unsaved: Die aktuellen Änderungen gehen verloren, wenn Sie diese Seite verlassen. text_wiki_destroy_confirmation: Sind Sie sicher, dass Sie dieses Wiki mit sämtlichem Inhalt löschen möchten? - text_issue_category_destroy_question: "Einige Tickets (%{count}) sind dieser Kategorie zugeodnet. Was möchten Sie tun?" - text_issue_category_destroy_assignments: Kategorie-Zuordnung entfernen - text_issue_category_reassign_to: Tickets dieser Kategorie zuordnen - text_user_mail_option: "Für nicht ausgewählte Projekte werden Sie nur Benachrichtigungen für Dinge erhalten, die Sie beobachten oder an denen Sie beteiligt sind (z. B. Tickets, deren Autor Sie sind oder die Ihnen zugewiesen sind)." - text_no_configuration_data: "Rollen, Tracker, Ticket-Status und Workflows wurden noch nicht konfiguriert.\nEs ist sehr zu empfehlen, die Standard-Konfiguration zu laden. Sobald sie geladen ist, können Sie sie abändern." - text_load_default_configuration: Standard-Konfiguration laden - text_status_changed_by_changeset: "Status geändert durch Changeset %{value}." - text_issues_destroy_confirmation: 'Sind Sie sicher, dass Sie die ausgewählten Tickets löschen möchten?' - text_select_project_modules: 'Bitte wählen Sie die Module aus, die in diesem Projekt aktiviert sein sollen:' - text_default_administrator_account_changed: Administrator-Kennwort geändert - text_file_repository_writable: Verzeichnis für Dateien beschreibbar - text_plugin_assets_writable: Verzeichnis für Plugin-Assets beschreibbar - text_rmagick_available: RMagick verfügbar (optional) - text_destroy_time_entries_question: Es wurden bereits %{hours} Stunden auf dieses Ticket gebucht. Was soll mit den Aufwänden geschehen? - text_destroy_time_entries: Gebuchte Aufwände löschen - text_assign_time_entries_to_project: Gebuchte Aufwände dem Projekt zuweisen - text_reassign_time_entries: 'Gebuchte Aufwände diesem Ticket zuweisen:' - text_user_wrote: "%{value} schrieb:" - text_enumeration_destroy_question: "%{count} Objekt(e) sind diesem Wert zugeordnet." - text_enumeration_category_reassign_to: 'Die Objekte stattdessen diesem Wert zuordnen:' - text_email_delivery_not_configured: "Der SMTP-Server ist nicht konfiguriert und Mailbenachrichtigungen sind ausgeschaltet.\nNehmen Sie die Einstellungen für Ihren SMTP-Server in config/configuration.yml vor und starten Sie die Applikation neu." - text_repository_usernames_mapping: "Bitte legen Sie die Zuordnung der Redmine-Benutzer zu den Benutzernamen der Commit-Log-Meldungen des Projektarchivs fest.\nBenutzer mit identischen Redmine- und Projektarchiv-Benutzernamen oder -E-Mail-Adressen werden automatisch zugeordnet." - text_diff_truncated: '... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.' - text_custom_field_possible_values_info: 'Eine Zeile pro Wert' + text_wiki_page_destroy_children: Lösche alle Unterseiten text_wiki_page_destroy_question: "Diese Seite hat %{descendants} Unterseite(n). Was möchten Sie tun?" text_wiki_page_nullify_children: Verschiebe die Unterseiten auf die oberste Ebene - text_wiki_page_destroy_children: Lösche alle Unterseiten text_wiki_page_reassign_children: Ordne die Unterseiten dieser Seite zu - text_own_membership_delete_confirmation: "Sie sind dabei, einige oder alle Ihre Berechtigungen zu entfernen. Es ist möglich, dass Sie danach das Projekt nicht mehr ansehen oder bearbeiten dürfen.\nSind Sie sicher, dass Sie dies tun möchten?" - text_zoom_in: Zoom in - text_zoom_out: Zoom out + text_workflow_edit: Workflow zum Bearbeiten auswählen + text_zoom_in: Ansicht vergrößern + text_zoom_out: Ansicht verkleinern - default_role_manager: Manager - default_role_developer: Entwickler - default_role_reporter: Reporter - default_tracker_bug: Fehler - default_tracker_feature: Feature - default_tracker_support: Unterstützung - default_issue_status_new: Neu - default_issue_status_in_progress: In Bearbeitung - default_issue_status_resolved: Gelöst - default_issue_status_feedback: Feedback - default_issue_status_closed: Erledigt - default_issue_status_rejected: Abgewiesen - default_doc_category_user: Benutzerdokumentation - default_doc_category_tech: Technische Dokumentation - default_priority_low: Niedrig - default_priority_normal: Normal - default_priority_high: Hoch - default_priority_urgent: Dringend - default_priority_immediate: Sofort - default_activity_design: Design - default_activity_development: Entwicklung + version_status_closed: abgeschlossen + version_status_locked: gesperrt + version_status_open: offen - enumeration_issue_priorities: Ticket-Prioritäten - enumeration_doc_categories: Dokumentenkategorien - enumeration_activities: Aktivitäten (Zeiterfassung) - enumeration_system_activity: System-Aktivität - - field_text: Textfeld - label_user_mail_option_only_owner: Nur für Aufgaben die ich angelegt habe - setting_default_notification_option: Standard Benachrichtigungsoptionen - label_user_mail_option_only_my_events: Nur für Aufgaben die ich beobachte oder an welchen ich mitarbeite - label_user_mail_option_only_assigned: Nur für Aufgaben für die ich zuständig bin. - notice_not_authorized_archived_project: Das Projekt wurde archiviert und ist daher nicht nicht verfügbar. - label_user_mail_option_none: keine Ereignisse - field_member_of_group: Zuständigkeitsgruppe - field_assigned_to_role: Zuständigkeitsrolle - field_visible: Sichtbar - setting_emails_header: E-Mail Betreffzeile - setting_commit_logtime_activity_id: Aktivität für die Zeiterfassung - text_time_logged_by_changeset: Angewendet in Changeset %{value}. - setting_commit_logtime_enabled: Aktiviere Zeitlogging - notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max}) - setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden. - field_warn_on_leaving_unsaved: vor dem Verlassen einer Seite mit ungesichertem Text im Editor warnen - text_warn_on_leaving_unsaved: Die aktuellen Änderungen gehen verloren, wenn Sie diese Seite verlassen. - label_my_queries: Meine eigenen Abfragen - text_journal_changed_no_detail: "%{label} aktualisiert" - label_news_comment_added: Kommentar zu einer News hinzugefügt - button_expand_all: Alle ausklappen - button_collapse_all: Alle einklappen - label_additional_workflow_transitions_for_assignee: Zusätzliche Berechtigungen wenn der Benutzer der Zugewiesene ist - label_additional_workflow_transitions_for_author: Zusätzliche Berechtigungen wenn der Benutzer der Autor ist - label_bulk_edit_selected_time_entries: Ausgewählte Zeitaufwände bearbeiten - text_time_entries_destroy_confirmation: Sind Sie sicher, dass Sie die ausgewählten Zeitaufwände löschen möchten? - label_role_anonymous: Anonymous - label_role_non_member: Nichtmitglied - label_issue_note_added: Notiz hinzugefügt - label_issue_status_updated: Status aktualisiert - label_issue_priority_updated: Priorität aktualisiert - label_issues_visibility_own: Tickets die folgender User erstellt hat oder die ihm zugewiesen sind - field_issues_visibility: Ticket Sichtbarkeit - label_issues_visibility_all: Alle Tickets - permission_set_own_issues_private: Eigene Tickets privat oder öffentlich markieren - field_is_private: Privat - permission_set_issues_private: Tickets privat oder öffentlich markieren - label_issues_visibility_public: Alle öffentlichen Tickets - text_issues_destroy_descendants_confirmation: Dies wird auch %{count} Unteraufgabe/n löschen. - field_commit_logs_encoding: Kodierung der Commit-Log-Meldungen - field_scm_path_encoding: Pfad Kodierung - text_scm_path_encoding_note: "Standard: UTF-8" - field_path_to_repository: Pfad zum repository - field_root_directory: Wurzelverzeichnis - field_cvs_module: Modul - field_cvsroot: CVSROOT - text_mercurial_repository_note: Lokales repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Kommando - text_scm_command_version: Version - label_git_report_last_commit: Bericht des letzten Commits für Dateien und Verzeichnisse - text_scm_config: Die SCM-Kommandos können in der in config/configuration.yml konfiguriert werden. Redmine muss anschließend neu gestartet werden. - text_scm_command_not_available: Scm Kommando ist nicht verfügbar. Bitte prüfen Sie die Einstellungen im Administrationspanel. - - notice_issue_successful_create: Ticket %{id} erstellt. - label_between: zwischen - setting_issue_group_assignment: Ticketzuweisung an Gruppen erlauben - label_diff: diff - text_git_repository_note: Repository steht für sich alleine (bare) und liegt lokal (z.B. /gitrepo, c:\gitrepo) - - description_filter: Filter - description_search: Suchfeld - description_choose_project: Projekte - description_project_scope: Suchbereich - description_notes: Kommentare - description_message_content: Nachrichteninhalt - description_query_sort_criteria_attribute: Sortierattribut - description_query_sort_criteria_direction: Sortierrichtung - description_user_mail_notification: Mailbenachrichtigungseinstellung - description_available_columns: Verfügbare Spalten - description_selected_columns: Ausgewählte Spalten - description_issue_category_reassign: Neue Kategorie wählen - description_wiki_subpages_reassign: Neue Elternseite wählen - description_date_range_list: Zeitraum aus einer Liste wählen - description_date_range_interval: Zeitraum durch Start- und Enddatum festlegen - description_date_from: Startdatum eintragen - description_date_to: Enddatum eintragen - - label_parent_revision: Vorgänger - label_child_revision: Nachfolger - error_scm_annotate_big_text_file: Der Eintrag kann nicht umgesetzt werden, da er die maximale Textlänge überschreitet. - setting_default_issue_start_date_to_creation_date: Aktuelles Datum als Beginn für neue Tickets verwenden - button_edit_section: Diesen Bereich bearbeiten - setting_repositories_encodings: Encoding von Anhängen und Repositories - description_all_columns: Alle Spalten - button_export: Exportieren - label_export_options: "%{export_format} Export-Eigenschaften" - error_attachment_too_big: Diese Datei kann nicht hochgeladen werden, da sie die maximale Dateigröße von (%{max_size}) überschreitet. - notice_failed_to_save_time_entries: "Gescheitert %{count} Zeiteinträge für %{total} von ausgewählten: %{ids} zu speichern." - label_x_issues: - zero: 0 Tickets - one: 1 Ticket - other: "%{count} Tickets" - label_repository_new: Neues Repository - field_repository_is_default: Haupt-Repository - label_copy_attachments: Anhänge Kopieren - label_item_position: "%{position}/%{count}" - label_completed_versions: Abgeschlossene Versionen - field_multiple: Mehrere Werte - setting_commit_cross_project_ref: Erlauben auf Tickets aller anderen Projekte zu referenzieren - text_issue_conflict_resolution_add_notes: Meine Änderungen übernehmen und alle anderen Änderungen verwerfen - text_issue_conflict_resolution_overwrite: Meine Änderungen trotzdem übernehmen (vorherige Notizen bleiben erhalten aber manche können überschrieben werden) - notice_issue_update_conflict: Das Ticket wurde von einem anderen Nutzer überarbeitet während Ihrer Bearbeitung. - text_issue_conflict_resolution_cancel: Meine Änderungen verwerfen und %{link} neu anzeigen - permission_manage_related_issues: Zugehörige Tickets verwalten - field_auth_source_ldap_filter: LDAP Filter - label_search_for_watchers: Nach hinzufügbaren Beobachtern suchen - notice_account_deleted: Ihr Benutzerkonto wurde unwiderruflich gelöscht. - setting_unsubscribe: Erlaubt Benutzern das eigene Benutzerkonto zu löschen - button_delete_my_account: Mein Benutzerkonto löschen - text_account_destroy_confirmation: Möchten Sie wirklich fortfahren?\nIhr Benutzerkonto wird für immer gelöscht und kann nicht wiederhergestellt werden. - error_session_expired: Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an. - text_session_expiration_settings: "Achtung: Änderungen können aktuelle Sitzungen beenden, Ihre eingeschlossen!" - setting_session_lifetime: Längste Dauer einer Sitzung - setting_session_timeout: Zeitüberschreitung bei Inaktivität - label_session_expiration: Ende einer Sitzung - permission_close_project: Schließen / erneutes Öffnen eines Projekts - label_show_closed_projects: Geschlossene Projekte anzeigen - button_close: Schließen - button_reopen: Öffnen - project_status_active: aktiv - project_status_closed: geschlossen - project_status_archived: archiviert - text_project_closed: Dieses Projekt ist geschlossen und kann nicht bearbeitet werden. - notice_user_successful_create: Benutzer %{id} angelegt. - field_core_fields: Standardwerte - field_timeout: Auszeit (in Sekunden) - setting_thumbnails_enabled: Vorschaubilder von Dateianhängen anzeigen - setting_thumbnails_size: Größe der Vorschaubilder (in Pixel) - label_status_transitions: Statusänderungen - label_fields_permissions: Feldberechtigungen - label_readonly: Nur-Lese-Zugriff - label_required: Erforderlich - text_repository_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
    Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' - field_board_parent: Übergeordnetes Forum - label_attribute_of_project: "%{name} des Projekts" - label_attribute_of_author: "%{name} des Autors" - label_attribute_of_assigned_to: "%{name} des Bearbeiters" - label_attribute_of_fixed_version: "%{name} der Zielversion" - label_copy_subtasks: Unteraufgaben kopieren - label_copied_to: Kopiert nach - label_copied_from: Kopiert von - label_any_issues_in_project: irgendein Ticket im Projekt - label_any_issues_not_in_project: irgendein Ticket nicht im Projekt - field_private_notes: Privater Kommentar - permission_view_private_notes: Private Kommentare sehen - permission_set_notes_private: Kommentar als privat markieren - label_no_issues_in_project: keine Tickets im Projekt - label_any: alle - label_last_n_weeks: letzte %{count} Wochen - setting_cross_project_subtasks: Projektübergreifende Unteraufgaben erlauben - label_cross_project_descendants: Mit Unterprojekten - label_cross_project_tree: Mit Projektbaum - label_cross_project_hierarchy: Mit Projekthierarchie - label_cross_project_system: Mit allen Projekten - button_hide: Verstecken - setting_non_working_week_days: Arbeitsfreie Tage - label_in_the_next_days: in den nächsten - label_in_the_past_days: in den letzten + warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden." diff -r d98d22a98252 -r afce8026aaeb config/locales/el.yml --- a/config/locales/el.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/el.yml Tue Sep 09 09:34:53 2014 +0100 @@ -52,8 +52,8 @@ one: "πεÏίπου 1 ÏŽÏα" other: "πεÏίπου %{count} ÏŽÏες" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ÏŽÏα" + other: "%{count} ÏŽÏες" x_days: one: "1 ημέÏα" other: "%{count} ημέÏες" @@ -130,6 +130,7 @@ not_same_project: "δεν ανήκει στο ίδιο έÏγο" circular_dependency: "Αυτή η σχέση θα δημιουÏγήσει κυκλικές εξαÏτήσεις" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: ΠαÏακαλώ επιλέξτε @@ -162,7 +163,7 @@ notice_not_authorized: Δεν έχετε δικαίωμα Ï€Ïόσβασης σε αυτή τη σελίδα. notice_email_sent: "Ένα μήνυμα ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου εστάλη στο %{value}" notice_email_error: "Σφάλμα κατά την αποστολή του μηνÏματος στο (%{value})" - notice_feeds_access_key_reseted: Έγινε επαναφοÏά στο κλειδί Ï€Ïόσβασης RSS. + notice_feeds_access_key_reseted: Έγινε επαναφοÏά στο κλειδί Ï€Ïόσβασης Atom. notice_failed_to_save_issues: "Αποτυχία αποθήκευσης %{count} θεμα(των) από τα %{total} επιλεγμένα: %{ids}." notice_no_issue_selected: "Κανένα θέμα δεν είναι επιλεγμένο! ΠαÏακαλοÏμε, ελέγξτε τα θέματα που θέλετε να επεξεÏγαστείτε." notice_account_pending: "Ο λογαÏιασμός σας έχει δημιουÏγηθεί και είναι σε στάδιο έγκÏισης από τον διαχειÏιστή." @@ -194,8 +195,6 @@ mail_subject_wiki_content_updated: "'ενημεÏώθηκε η σελίδα wiki %{id}' " mail_body_wiki_content_updated: "Η σελίδα wiki '%{id}' ενημεÏώθηκε από τον %{author}." - gui_validation_error: 1 σφάλμα - gui_validation_error_plural: "%{count} σφάλματα" field_name: Όνομα field_description: ΠεÏιγÏαφή @@ -356,7 +355,6 @@ permission_edit_own_time_entries: ΕπεξεÏγασία Î´Î¹ÎºÎ¿Ï Î¼Î¿Ï… ιστοÏÎ¹ÎºÎ¿Ï Ï‡Ïόνου permission_manage_news: ΔιαχείÏιση νέων permission_comment_news: Σχολιασμός νέων - permission_manage_documents: ΔιαχείÏιση εγγÏάφων permission_view_documents: ΠÏοβολή εγγÏάφων permission_manage_files: ΔιαχείÏιση αÏχείων permission_view_files: ΠÏοβολή αÏχείων @@ -475,8 +473,6 @@ label_text: ΜακÏοσκελές κείμενο label_attribute: Ιδιότητα label_attribute_plural: Ιδιότητες - label_download: "%{count} ΜεταφόÏτωση" - label_download_plural: "%{count} ΜεταφοÏτώσεις" label_no_data: Δεν υπάÏχουν δεδομένα label_change_status: Αλλαγή κατάστασης label_history: ΙστοÏικό @@ -578,8 +574,6 @@ label_repository: ΑποθετήÏιο label_repository_plural: ΑποθετήÏια label_browse: Πλοήγηση - label_modification: "%{count} Ï„Ïοποποίηση" - label_modification_plural: "%{count} Ï„Ïοποποιήσεις" label_branch: Branch label_tag: Tag label_revision: ΑναθεώÏηση @@ -671,7 +665,7 @@ label_language_based: Με βάση τη γλώσσα του χÏήστη label_sort_by: "Ταξινόμηση ανά %{value}" label_send_test_email: Αποστολή Î´Î¿ÎºÎ¹Î¼Î±ÏƒÏ„Î¹ÎºÎ¿Ï email - label_feeds_access_key_created_on: "το κλειδί Ï€Ïόσβασης RSS δημιουÏγήθηκε Ï€Ïιν από %{value}" + label_feeds_access_key_created_on: "το κλειδί Ï€Ïόσβασης Atom δημιουÏγήθηκε Ï€Ïιν από %{value}" label_module_plural: Μονάδες label_added_time_by: "ΠÏοστέθηκε από τον %{author} Ï€Ïιν από %{age}" label_updated_time_by: "ΕνημεÏώθηκε από τον %{author} Ï€Ïιν από %{age}" @@ -881,11 +875,11 @@ label_revision_id: Revision %{value} label_api_access_key: API access key label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key + label_feeds_access_key: Atom access key notice_api_access_key_reseted: Your API access key was reset. setting_rest_api_enabled: Enable REST web service label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key + label_missing_feeds_access_key: Missing a Atom access key button_show: Show text_line_separated: Multiple values allowed (one line for each value). setting_mail_handler_body_delimiters: Truncate emails after one of these lines @@ -933,7 +927,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -974,8 +967,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1082,3 +1073,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: ΣÏνολο + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/en.yml --- a/config/locales/en.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/en.yml Tue Sep 09 09:34:53 2014 +0100 @@ -132,6 +132,7 @@ not_same_project: "doesn't belong to the same project" circular_dependency: "This relation would create a circular dependency" cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" must_accept_terms_and_conditions: "You must accept the Terms and Conditions" public_or_private: "You must select either public or private" @@ -152,8 +153,10 @@ notice_account_invalid_creditentials: Invalid user or password notice_account_password_updated: Password was successfully updated. notice_account_wrong_password: Wrong password - notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you. + notice_account_register_done: Account was successfully created. An email containing the instructions to activate your account was sent to %{email}. notice_account_unknown_email: Unknown user. + notice_account_not_activated_yet: You haven't activated your account yet. If you want to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. notice_account_activated: Your account has been activated. You can now log in. @@ -167,7 +170,7 @@ notice_not_authorized_archived_project: The project you're trying to access has been archived. notice_email_sent: "An email was sent to %{value}" notice_email_error: "An error occurred while sending mail (%{value})" - notice_feeds_access_key_reseted: Your RSS access key was reset. + notice_feeds_access_key_reseted: Your Atom access key was reset. notice_api_access_key_reseted: Your API access key was reset. notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." @@ -183,6 +186,7 @@ notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it." notice_account_deleted: "Your account has been permanently deleted." notice_user_successful_create: "User %{id} created." + notice_new_password_must_be_different: The new password must be different from the current password error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" error_scm_not_found: "The entry or revision was not found in the repository." @@ -243,6 +247,7 @@ field_author: Author field_created_on: Created field_updated_on: Updated + field_closed_on: Closed field_field_format: Format field_is_for_all: For all projects field_possible_values: Possible values @@ -344,6 +349,9 @@ field_timeout: "Timeout (in seconds)" field_board_parent: Parent forum field_private_notes: Private notes + field_inherit_members: Inherit members + field_generate_password: Generate password + field_must_change_passwd: Must change password at next logon field_public_or_private: "Public or Private?" setting_external_repository: "Select this if the project's main repository is hosted somewhere else" @@ -378,8 +386,8 @@ setting_cross_project_subtasks: Allow cross-project subtasks setting_issue_list_default_columns: Default columns displayed on the issue list setting_repositories_encodings: Attachments and repositories encodings - setting_emails_header: Emails header - setting_emails_footer: Emails footer + setting_emails_header: Email header + setting_emails_footer: Email footer setting_protocol: Protocol setting_per_page_options: Objects per page options setting_user_format: Users display format @@ -421,6 +429,9 @@ setting_thumbnails_enabled: Display attachment thumbnails setting_thumbnails_size: Thumbnails size (in pixels) setting_non_working_week_days: Non-working days + setting_jsonp_enabled: Enable JSONP support + setting_default_projects_tracker_ids: Default trackers for new projects + setting_mail_handler_excluded_filenames: Exclude attachments by name permission_add_project: Create project permission_add_subprojects: Create subprojects @@ -457,8 +468,10 @@ permission_edit_own_time_entries: Edit own time logs permission_manage_news: Manage news permission_comment_news: Comment news - permission_manage_documents: Manage documents permission_view_documents: View documents + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents permission_manage_files: Manage downloads permission_view_files: View downloads permission_manage_wiki: Manage wiki @@ -614,8 +627,6 @@ label_text: Long text label_attribute: Attribute label_attribute_plural: Attributes - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: No data to display label_change_status: Change status label_history: History @@ -666,6 +677,7 @@ one: 1 issue other: "%{count} issues" label_total: Total + label_total_time: Total time label_permissions: Permissions label_current_status: Current status label_new_statuses_allowed: New statuses allowed @@ -735,6 +747,7 @@ label_repository_new: New repository label_is_external_repository: Track an external repository label_repository_plural: Repositories + label_browse: Browse label_explore_projects: Explore projects label_modification: "%{count} change" label_modification_plural: "%{count} changes" @@ -838,9 +851,9 @@ label_language_based: Based on user's language label_sort_by: "Sort by %{value}" label_send_test_email: Send a test email - label_feeds_access_key: RSS access key - label_missing_feeds_access_key: Missing a RSS access key - label_feeds_access_key_created_on: "RSS access key created %{value} ago" + label_feeds_access_key: Atom access key + label_missing_feeds_access_key: Missing a Atom access key + label_feeds_access_key_created_on: "Atom access key created %{value} ago" label_module_plural: Modules label_added_time_by: "Added by %{author} %{age} ago" label_updated_time_by: "Updated by %{author} %{age} ago" @@ -939,14 +952,21 @@ label_fields_permissions: Fields permissions label_readonly: Read-only label_required: Required + label_hidden: Hidden label_attribute_of_project: "Project's %{name}" + label_attribute_of_issue: "Issue's %{name}" label_attribute_of_author: "Author's %{name}" label_attribute_of_assigned_to: "Assignee's %{name}" + label_attribute_of_user: "User's %{name}" label_attribute_of_fixed_version: "Target version's %{name}" label_cross_project_descendants: With subprojects label_cross_project_tree: With project tree label_cross_project_hierarchy: With project hierarchy label_cross_project_system: With all projects + label_gantt_progress_line: Progress line + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users button_login: Login button_submit: Submit @@ -1033,7 +1053,7 @@ text_tip_issue_begin_day: issue beginning this day text_tip_issue_end_day: issue ending this day text_tip_issue_begin_end_day: issue beginning and ending this day - text_project_identifier_info: 'The system identifier that will form the unique part of the URL for your project.
    Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed.' + text_project_identifier_info: 'The system identifier that will form the unique part of the URL for your project.
    Only lower case letters (a-z), numbers, dashes and underscores are allowed. Must start with a letter.
    Once saved, the identifier cannot be changed.' text_project_homepage_info: 'Optional link to an external project page.' text_caracters_maximum: "%{count} characters maximum." text_caracters_minimum: "Must be at least %{count} characters long." @@ -1069,6 +1089,7 @@ text_file_repository_writable: Attachments directory writable text_plugin_assets_writable: Plugin assets directory writable text_rmagick_available: RMagick available (optional) + text_convert_available: ImageMagick convert available (optional) text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" text_destroy_time_entries: Delete reported hours text_assign_time_entries_to_project: Assign reported hours to the project @@ -1095,14 +1116,15 @@ text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) text_scm_command: Command text_scm_command_version: Version - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it." text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." text_project_closed: This project is closed and read-only. + text_turning_multiple_off: "If you disable multiple values, multiple values will be removed in order to preserve only one value per item." text_settings_repo_explanation: External repositories

    Normally your project's primary repository will be the Mercurial repository hosted at this site.

    However, if you already have your project hosted somewhere else, you can specify your existing external repository's URL here – then this site will track that repository in a read-only “mirror” copy. External Mercurial, git and Subversion repositories can be tracked. Note that you cannot switch to an external repository if you have already made any commits to the repository hosted here. text_settings_repo_is_internal: Currently the repository hosted at this site is the primary repository for this project. text_settings_repo_is_external: Currently the repository hosted at this site is a read-only copy of an external repository. diff -r d98d22a98252 -r afce8026aaeb config/locales/es.yml --- a/config/locales/es.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/es.yml Tue Sep 09 09:34:53 2014 +0100 @@ -80,8 +80,8 @@ one: "alrededor de 1 hora" other: "alrededor de %{count} horas" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hora" + other: "%{count} horas" x_days: one: "1 día" other: "%{count} días" @@ -136,6 +136,7 @@ not_same_project: "no pertenece al mismo proyecto" circular_dependency: "Esta relación podría crear una dependencia circular" cant_link_an_issue_with_a_descendant: "Esta petición no puede ser ligada a una de estas tareas" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" # Append your own errors here or at the model/attributes scope. @@ -203,7 +204,7 @@ button_list: Listar button_lock: Bloquear button_log_time: Tiempo dedicado - button_login: Conexión + button_login: Acceder button_move: Mover button_quote: Citar button_rename: Renombrar @@ -345,8 +346,6 @@ general_text_Yes: 'Sí' general_text_no: 'no' general_text_yes: 'sí' - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errores" label_activity: Actividad label_add_another_file: Añadir otro fichero label_add_note: Añadir una nota @@ -439,8 +438,6 @@ label_document_added: Documento añadido label_document_new: Nuevo documento label_document_plural: Documentos - label_download: "%{count} Descarga" - label_download_plural: "%{count} Descargas" label_downloads_abbr: D/L label_duplicated_by: duplicada por label_duplicates: duplicada de @@ -455,7 +452,7 @@ label_f_hour: "%{value} hora" label_f_hour_plural: "%{value} horas" label_feed_plural: Feeds - label_feeds_access_key_created_on: "Clave de acceso por RSS creada hace %{value}" + label_feeds_access_key_created_on: "Clave de acceso por Atom creada hace %{value}" label_file_added: Fichero añadido label_file_plural: Archivos label_filter_add: Añadir el filtro @@ -508,8 +505,8 @@ label_list: Lista label_loading: Cargando... label_logged_as: Conectado como - label_login: Conexión - label_logout: Desconexión + label_login: Iniciar sesión + label_logout: Terminar sesión label_max_size: Tamaño máximo label_me: yo mismo label_member: Miembro @@ -520,8 +517,6 @@ label_message_plural: Mensajes label_message_posted: Mensaje añadido label_min_max_length: Longitud mín - máx - label_modification: "%{count} modificación" - label_modification_plural: "%{count} modificaciones" label_modified: modificado label_module_plural: Módulos label_month: Mes @@ -558,7 +553,7 @@ label_permissions_report: Informe de permisos label_personalize_page: Personalizar esta página label_planning: Planificación - label_please_login: Conexión + label_please_login: Por favor, inicie sesión label_plugins: Extensiones label_precedes: anterior a label_preferences: Preferencias @@ -622,7 +617,7 @@ label_start_to_end: principio a fin label_start_to_start: principio a principio label_statistics: Estadísticas - label_stay_logged_in: Recordar conexión + label_stay_logged_in: Mantener la sesión abierta label_string: Texto label_subproject_plural: Proyectos secundarios label_text: Texto largo @@ -686,7 +681,7 @@ notice_email_error: "Ha ocurrido un error mientras enviando el correo (%{value})" notice_email_sent: "Se ha enviado un correo a %{value}" notice_failed_to_save_issues: "Imposible grabar %{count} peticion(es) de %{total} seleccionada(s): %{ids}." - notice_feeds_access_key_reseted: Su clave de acceso para RSS ha sido reiniciada. + notice_feeds_access_key_reseted: Su clave de acceso para Atom ha sido reiniciada. notice_file_not_found: La página a la que intenta acceder no existe. notice_locking_conflict: Los datos han sido modificados por otro usuario. notice_no_issue_selected: "Ninguna petición seleccionada. Por favor, compruebe la petición que quiere modificar" @@ -720,7 +715,6 @@ permission_log_time: Anotar tiempo dedicado permission_manage_boards: Administrar foros permission_manage_categories: Administrar categorías de peticiones - permission_manage_documents: Administrar documentos permission_manage_files: Administrar ficheros permission_manage_issue_relations: Administrar relación con otras peticiones permission_manage_members: Administrar miembros @@ -757,7 +751,7 @@ setting_app_title: Título de la aplicación setting_attachment_max_size: Tamaño máximo del fichero setting_autofetch_changesets: Autorellenar los commits del repositorio - setting_autologin: Conexión automática + setting_autologin: Inicio de sesión automático setting_bcc_recipients: Ocultar las copias de carbón (bcc) setting_commit_fix_keywords: Palabras clave para la corrección setting_commit_ref_keywords: Palabras clave para la referencia @@ -918,11 +912,11 @@ label_revision_id: Revisión %{value} label_api_access_key: Clave de acceso de la API label_api_access_key_created_on: Clave de acceso de la API creada hace %{value} - label_feeds_access_key: Clave de acceso RSS + label_feeds_access_key: Clave de acceso Atom notice_api_access_key_reseted: Clave de acceso a la API regenerada. setting_rest_api_enabled: Activar servicio web REST label_missing_api_access_key: Clave de acceso a la API ausente - label_missing_feeds_access_key: Clave de accesso RSS ausente + label_missing_feeds_access_key: Clave de accesso Atom ausente button_show: Mostrar text_line_separated: Múltiples valores permitidos (un valor en cada línea). setting_mail_handler_body_delimiters: Truncar correos tras una de estas líneas @@ -1119,3 +1113,27 @@ setting_non_working_week_days: Días no laborables label_in_the_next_days: en los próximos label_in_the_past_days: en los anteriores + label_attribute_of_user: "%{name} del usuario" + text_turning_multiple_off: Si desactiva los valores múltiples, éstos serán eliminados para dejar un único valor por elemento. + label_attribute_of_issue: "%{name} de la petición" + permission_add_documents: Añadir documentos + permission_edit_documents: Editar documentos + permission_delete_documents: Borrar documentos + label_gantt_progress_line: Línea de progreso + setting_jsonp_enabled: Habilitar soporte de JSONP + field_inherit_members: Heredar miembros + field_closed_on: Cerrada + field_generate_password: Generar contraseña + setting_default_projects_tracker_ids: Tipos de petición habilitados por defecto + label_total_time: Total + notice_account_not_activated_yet: No ha activado su cuenta aún. Si quiere + recibir un nuevo correo de activación, por favor haga clic en este enlace. + notice_account_locked: Su cuenta está bloqueada. + label_hidden: Oculto + label_visibility_private: solamente para mí + label_visibility_roles: solamente para estos roles + label_visibility_public: para cualquier usuario + field_must_change_passwd: Cambiar contraseña en el próximo inicio de sesión + notice_new_password_must_be_different: La nueva contraseña debe ser diferente de la actual + setting_mail_handler_excluded_filenames: Excluir adjuntos por nombre + text_convert_available: Conversión ImageMagick disponible (opcional) diff -r d98d22a98252 -r afce8026aaeb config/locales/et.yml --- a/config/locales/et.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/et.yml Tue Sep 09 09:34:53 2014 +0100 @@ -67,8 +67,8 @@ one: "umbes tund" other: "umbes %{count} tundi" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 tund" + other: "%{count} tundi" x_days: one: "1 päev" other: "%{count} päeva" @@ -146,6 +146,7 @@ not_same_project: "ei kuulu sama projekti juurde" circular_dependency: "See suhe looks vastastikuse sõltuvuse" cant_link_an_issue_with_a_descendant: "Teemat ei saa sidustada tema enda alamteemaga" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: "Palun vali" @@ -179,7 +180,7 @@ notice_not_authorized_archived_project: "See projekt on arhiveeritud." notice_email_sent: "%{value}-le saadeti kiri" notice_email_error: "Kirja saatmisel tekkis viga (%{value})" - notice_feeds_access_key_reseted: "Sinu RSS juurdepääsuvõti nulliti." + notice_feeds_access_key_reseted: "Sinu Atom juurdepääsuvõti nulliti." notice_api_access_key_reseted: "Sinu API juurdepääsuvõti nulliti." notice_failed_to_save_issues: "%{count} teemat %{total}-st ei õnnestunud salvestada: %{ids}." notice_failed_to_save_time_entries: "%{count} ajakulu kannet %{total}-st ei õnnestunud salvestada: %{ids}." @@ -231,8 +232,6 @@ mail_subject_wiki_content_updated: "Uuendati '%{id}' vikilehte" mail_body_wiki_content_updated: "'%{id}' vikilehte uuendati %{author} poolt." - gui_validation_error: "1 viga" - gui_validation_error_plural: "%{count} viga" field_name: "Nimi" field_description: "Kirjeldus" @@ -438,7 +437,6 @@ permission_edit_own_time_entries: "Omi ajakulu kandeid muuta" permission_manage_news: "Uudiseid hallata" permission_comment_news: "Uudiseid kommenteerida" - permission_manage_documents: "Dokumente hallata" permission_view_documents: "Dokumente näha" permission_manage_files: "Faile hallata" permission_view_files: "Faile näha" @@ -569,8 +567,6 @@ label_text: "Pikk tekst" label_attribute: "Atribuut" label_attribute_plural: "Atribuudid" - label_download: "%{count} allalaadimine" - label_download_plural: "%{count} allalaadimist" label_no_data: "Pole" label_change_status: "Muuda olekut" label_history: "Ajalugu" @@ -681,8 +677,6 @@ label_repository_new: "Uus hoidla" label_repository_plural: "Hoidlad" label_browse: "Sirvi" - label_modification: "%{count} muudatus" - label_modification_plural: "%{count} muudatust" label_branch: "Haru" label_tag: "Sildiga" label_revision: "Sissekanne" @@ -779,9 +773,9 @@ label_language_based: "Kasutaja keele põhjal" label_sort_by: "Sorteeri %{value} järgi" label_send_test_email: "Saada kontrollkiri" - label_feeds_access_key: "RSS juurdepääsuvõti" - label_missing_feeds_access_key: "RSS juurdepääsuvõti on puudu" - label_feeds_access_key_created_on: "RSS juurdepääsuvõti loodi %{value} tagasi" + label_feeds_access_key: "Atom juurdepääsuvõti" + label_missing_feeds_access_key: "Atom juurdepääsuvõti on puudu" + label_feeds_access_key_created_on: "Atom juurdepääsuvõti loodi %{value} tagasi" label_module_plural: "Moodulid" label_added_time_by: "Lisatud %{author} poolt %{age} tagasi" label_updated_time_by: "Uuendatud %{author} poolt %{age} tagasi" @@ -1094,3 +1088,29 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: "Kokku" + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/eu.yml --- a/config/locales/eu.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/eu.yml Tue Sep 09 09:34:53 2014 +0100 @@ -53,8 +53,8 @@ one: "ordu 1 inguru" other: "%{count} ordu inguru" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "ordu 1" + other: "%{count} ordu" x_days: one: "egun 1" other: "%{count} egun" @@ -131,6 +131,7 @@ not_same_project: "ez dago proiektu berdinean" circular_dependency: "Erlazio honek mendekotasun zirkular bat sortuko luke" cant_link_an_issue_with_a_descendant: "Zeregin bat ezin da bere azpiataza batekin estekatu." + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Hautatu mesedez @@ -163,7 +164,7 @@ notice_not_authorized: Ez duzu orri hau atzitzeko baimenik. notice_email_sent: "%{value} helbidera eposta bat bidali da" notice_email_error: "Errorea eposta bidaltzean (%{value})" - notice_feeds_access_key_reseted: Zure RSS atzipen giltza berrezarri da. + notice_feeds_access_key_reseted: Zure Atom atzipen giltza berrezarri da. notice_api_access_key_reseted: Zure API atzipen giltza berrezarri da. notice_failed_to_save_issues: "Hautatutako %{total} zereginetatik %{count} ezin izan dira konpondu: %{ids}." notice_no_issue_selected: "Ez da zereginik hautatu! Mesedez, editatu nahi dituzun arazoak markatu." @@ -202,8 +203,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki orria eguneratu da" mail_body_wiki_content_updated: "%{author}-(e)k '%{id}' wiki orria eguneratu du." - gui_validation_error: akats 1 - gui_validation_error_plural: "%{count} akats" field_name: Izena field_description: Deskribapena @@ -376,7 +375,6 @@ permission_edit_own_time_entries: Nork bere denbora egunkariak editatu permission_manage_news: Berriak kudeatu permission_comment_news: Berrien iruzkinak egin - permission_manage_documents: Dokumentuak kudeatu permission_view_documents: Dokumentuak ikusi permission_manage_files: Fitxategiak kudeatu permission_view_files: Fitxategiak ikusi @@ -497,8 +495,6 @@ label_text: Testu luzea label_attribute: Atributua label_attribute_plural: Atributuak - label_download: "Deskarga %{count}" - label_download_plural: "%{count} Deskarga" label_no_data: Ez dago erakusteko daturik label_change_status: Egoera aldatu label_history: Historikoa @@ -601,8 +597,6 @@ label_repository: Biltegia label_repository_plural: Biltegiak label_browse: Arakatu - label_modification: "aldaketa %{count}" - label_modification_plural: "%{count} aldaketa" label_branch: Adarra label_tag: Etiketa label_revision: Berrikuspena @@ -695,9 +689,9 @@ label_language_based: Erabiltzailearen hizkuntzaren arabera label_sort_by: "Ordenazioa: %{value}" label_send_test_email: Frogako mezua bidali - label_feeds_access_key: RSS atzipen giltza - label_missing_feeds_access_key: RSS atzipen giltza falta da - label_feeds_access_key_created_on: "RSS atzipen giltza orain dela %{value} sortuta" + label_feeds_access_key: Atom atzipen giltza + label_missing_feeds_access_key: Atom atzipen giltza falta da + label_feeds_access_key_created_on: "Atom atzipen giltza orain dela %{value} sortuta" label_module_plural: Moduluak label_added_time_by: "%{author}, orain dela %{age} gehituta" label_updated_time_by: "%{author}, orain dela %{age} eguneratuta" @@ -975,8 +969,6 @@ text_scm_command: Komandoa text_scm_command_version: Bertsioa label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1075,31 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Guztira + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/fa.yml --- a/config/locales/fa.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/fa.yml Tue Sep 09 09:34:53 2014 +0100 @@ -50,8 +50,8 @@ one: "نزدیک 1 ساعت" other: "نزدیک %{count} ساعت" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ساعت" + other: "%{count} ساعت" x_days: one: "1 روز" other: "%{count} روز" @@ -129,6 +129,7 @@ not_same_project: "به همان پروژه وابسته نیست" circular_dependency: "این وابستگی یک وابستگی دایره وار خواهد ساخت" cant_link_an_issue_with_a_descendant: "یک پیامد نمی‌تواند به یکی از زیر کارهایش پیوند بخورد" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: گزینش کنید @@ -162,7 +163,7 @@ notice_not_authorized_archived_project: پروژه درخواستی شما بایگانی شده است. notice_email_sent: "یک ایمیل به %{value} ÙØ±Ø³ØªØ§Ø¯Ù‡ شد." notice_email_error: "یک ایراد در ÙØ±Ø³ØªØ§Ø¯Ù† ایمیل پیش آمد (%{value})." - notice_feeds_access_key_reseted: کلید دسترسی RSS شما بازنشانی شد. + notice_feeds_access_key_reseted: کلید دسترسی Atom شما بازنشانی شد. notice_api_access_key_reseted: کلید دسترسی API شما بازنشانی شد. notice_failed_to_save_issues: "ذخیره سازی %{count} پیامد از %{total} پیامد گزینش شده شکست خورد: %{ids}." notice_failed_to_save_members: "ذخیره سازی اعضا شکست خورد: %{errors}." @@ -208,8 +209,6 @@ mail_subject_wiki_content_updated: "برگه ویکی «%{id}» بروز شد" mail_body_wiki_content_updated: "برگه ویکی «%{id}» به دست %{author} بروز شد." - gui_validation_error: 1 ایراد - gui_validation_error_plural: "%{count} ایراد" field_name: نام field_description: یادداشت @@ -396,7 +395,6 @@ permission_edit_own_time_entries: ویرایش زمان گذاشته شده خود permission_manage_news: سرپرستی رویدادها permission_comment_news: گذاشتن دیدگاه روی رویدادها - permission_manage_documents: سرپرستی نوشتارها permission_view_documents: دیدن نوشتارها permission_manage_files: سرپرستی پرونده‌ها permission_view_files: دیدن پرونده‌ها @@ -521,8 +519,6 @@ label_text: نوشته بلند label_attribute: نشانه label_attribute_plural: نشانه - label_download: "%{count} بار Ø¯Ø±ÛŒØ§ÙØª شده" - label_download_plural: "%{count} بار Ø¯Ø±ÛŒØ§ÙØª شده" label_no_data: هیچ داده‌ای برای نمایش نیست label_change_status: جایگزینی وضعیت label_history: پیشینه @@ -625,8 +621,6 @@ label_repository: انباره label_repository_plural: انباره label_browse: چریدن - label_modification: "%{count} جایگذاری" - label_modification_plural: "%{count} جایگذاری" label_branch: شاخه label_tag: برچسب label_revision: بازبینی @@ -722,9 +716,9 @@ label_language_based: بر اساس زبان کاربر label_sort_by: "جور کرد با %{value}" label_send_test_email: ÙØ±Ø³ØªØ§Ø¯Ù† ایمیل آزمایشی - label_feeds_access_key: کلید دسترسی RSS - label_missing_feeds_access_key: کلید دسترسی RSS در دسترس نیست - label_feeds_access_key_created_on: "کلید دسترسی RSS %{value} پیش ساخته شده است" + label_feeds_access_key: کلید دسترسی Atom + label_missing_feeds_access_key: کلید دسترسی Atom در دسترس نیست + label_feeds_access_key_created_on: "کلید دسترسی Atom %{value} پیش ساخته شده است" label_module_plural: پیمانه label_added_time_by: "Ø§ÙØ²ÙˆØ¯Ù‡ شده به دست %{author} در %{age} پیش" label_updated_time_by: "بروز شده به دست %{author} در %{age} پیش" @@ -975,8 +969,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1075,31 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: جمله + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/fi.yml --- a/config/locales/fi.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/fi.yml Tue Sep 09 09:34:53 2014 +0100 @@ -95,8 +95,8 @@ one: "noin tunti" other: "noin %{count} tuntia" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 tunti" + other: "%{count} tuntia" x_days: one: "päivä" other: "%{count} päivää" @@ -154,6 +154,7 @@ not_same_project: "ei kuulu samaan projektiin" circular_dependency: "Tämä suhde loisi kehän." cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Valitse, ole hyvä @@ -186,7 +187,7 @@ notice_not_authorized: Sinulla ei ole oikeutta näyttää tätä sivua. notice_email_sent: "Sähköposti on lähetty osoitteeseen %{value}" notice_email_error: "Sähköpostilähetyksessä tapahtui virhe (%{value})" - notice_feeds_access_key_reseted: RSS salasana on nollaantunut. + notice_feeds_access_key_reseted: Atom salasana on nollaantunut. notice_failed_to_save_issues: "%{count} Tapahtum(an/ien) tallennus epäonnistui %{total} valitut: %{ids}." notice_no_issue_selected: "Tapahtumia ei ole valittu! Valitse tapahtumat joita haluat muokata." notice_account_pending: "Tilisi on luotu ja odottaa ylläpitäjän hyväksyntää." @@ -205,8 +206,6 @@ mail_subject_account_activation_request: "%{value} tilin aktivointi pyyntö" mail_body_account_activation_request: "Uusi käyttäjä (%{value}) on rekisteröitynyt. Hänen tili odottaa hyväksyntääsi:" - gui_validation_error: 1 virhe - gui_validation_error_plural: "%{count} virhettä" field_name: Nimi field_description: Kuvaus @@ -397,8 +396,6 @@ label_text: Pitkä merkkijono label_attribute: Määre label_attribute_plural: Määreet - label_download: "%{count} Lataus" - label_download_plural: "%{count} Lataukset" label_no_data: Ei tietoa näytettäväksi label_change_status: Muutos tila label_history: Historia @@ -487,8 +484,6 @@ label_repository: Tietovarasto label_repository_plural: Tietovarastot label_browse: Selaus - label_modification: "%{count} muutos" - label_modification_plural: "%{count} muutettu" label_revision: Versio label_revision_plural: Versiot label_added: lisätty @@ -570,7 +565,7 @@ label_language_based: Pohjautuen käyttäjän kieleen label_sort_by: "Lajittele %{value}" label_send_test_email: Lähetä testi sähköposti - label_feeds_access_key_created_on: "RSS salasana luotiin %{value} sitten" + label_feeds_access_key_created_on: "Atom salasana luotiin %{value} sitten" label_module_plural: Moduulit label_added_time_by: "Lisännyt %{author} %{age} sitten" label_updated_time: "Päivitetty %{value} sitten" @@ -779,7 +774,6 @@ permission_comment_news: Kommentoi uutisia permission_delete_messages: Poista viestit permission_select_project_modules: Valitse projektin modulit - permission_manage_documents: Hallinnoi dokumentteja permission_edit_wiki_pages: Muokkaa wiki sivuja permission_add_issue_watchers: Lisää seuraajia permission_view_gantt: Näytä gantt kaavio @@ -902,11 +896,11 @@ label_revision_id: Revision %{value} label_api_access_key: API access key label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key + label_feeds_access_key: Atom access key notice_api_access_key_reseted: Your API access key was reset. setting_rest_api_enabled: Enable REST web service label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key + label_missing_feeds_access_key: Missing a Atom access key button_show: Show text_line_separated: Multiple values allowed (one line for each value). setting_mail_handler_body_delimiters: Truncate emails after one of these lines @@ -954,7 +948,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -995,8 +988,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1103,3 +1094,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Yhteensä + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/fr.yml --- a/config/locales/fr.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/fr.yml Tue Sep 09 09:34:53 2014 +0100 @@ -146,6 +146,7 @@ not_same_project: "n'appartient pas au même projet" circular_dependency: "Cette relation créerait une dépendance circulaire" cant_link_an_issue_with_a_descendant: "Une demande ne peut pas être liée à l'une de ses sous-tâches" + earlier_than_minimum_start_date: "ne peut pas être antérieure au %{date} à cause des demandes qui précédent" actionview_instancetag_blank_option: Choisir @@ -164,8 +165,10 @@ notice_account_invalid_creditentials: Identifiant ou mot de passe invalide. notice_account_password_updated: Mot de passe mis à jour avec succès. notice_account_wrong_password: Mot de passe incorrect - notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a été envoyé. + notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a été envoyé à l'adresse %{email}. notice_account_unknown_email: Aucun compte ne correspond à cette adresse. + notice_account_not_activated_yet: Vous n'avez pas encore activé votre compte. Si vous voulez recevoir un nouveau message d'activation, veuillez cliquer sur ce lien. + notice_account_locked: Votre compte est verrouillé. notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe. notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a été envoyé. notice_account_activated: Votre compte a été activé. Vous pouvez à présent vous connecter. @@ -179,7 +182,7 @@ notice_not_authorized_archived_project: Le projet auquel vous tentez d'accéder a été archivé. notice_email_sent: "Un email a été envoyé à %{value}" notice_email_error: "Erreur lors de l'envoi de l'email (%{value})" - notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée." + notice_feeds_access_key_reseted: "Votre clé d'accès aux flux Atom a été réinitialisée." notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sélectionnées n'ont pas pu être mise(s) à jour : %{ids}." notice_failed_to_save_time_entries: "%{count} temps passé(s) sur les %{total} sélectionnés n'ont pas pu être mis à jour: %{ids}." notice_no_issue_selected: "Aucune demande sélectionnée ! Cochez les demandes que vous voulez mettre à jour." @@ -193,6 +196,7 @@ notice_issue_update_conflict: "La demande a été mise à jour par un autre utilisateur pendant que vous la modifiez." notice_account_deleted: "Votre compte a été définitivement supprimé." notice_user_successful_create: "Utilisateur %{id} créé." + notice_new_password_must_be_different: Votre nouveau mot de passe doit être différent de votre mot de passe actuel error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramétrage : %{value}" error_scm_not_found: "L'entrée et/ou la révision demandée n'existe pas dans le dépôt." @@ -224,8 +228,6 @@ mail_subject_wiki_content_updated: "Page wiki '%{id}' mise à jour" mail_body_wiki_content_updated: "La page wiki '%{id}' a été mise à jour par %{author}." - gui_validation_error: 1 erreur - gui_validation_error_plural: "%{count} erreurs" field_name: Nom field_description: Description @@ -240,6 +242,7 @@ field_author: Auteur field_created_on: "Créé " field_updated_on: "Mis-à-jour " + field_closed_on: Fermé field_field_format: Format field_is_for_all: Pour tous les projets field_possible_values: Valeurs possibles @@ -331,6 +334,9 @@ field_timeout: "Timeout (en secondes)" field_board_parent: Forum parent field_private_notes: Notes privées + field_inherit_members: Hériter les membres + field_generate_password: Générer un mot de passe + field_must_change_passwd: Doit changer de mot de passe à la prochaine connexion setting_app_title: Titre de l'application setting_app_subtitle: Sous-titre de l'application @@ -396,6 +402,9 @@ setting_thumbnails_enabled: Afficher les vignettes des images setting_thumbnails_size: Taille des vignettes (en pixels) setting_non_working_week_days: Jours non travaillés + setting_jsonp_enabled: Activer le support JSONP + setting_default_projects_tracker_ids: Trackers par défaut pour les nouveaux projets + setting_mail_handler_excluded_filenames: Exclure les fichiers attachés par leur nom permission_add_project: Créer un projet permission_add_subprojects: Créer des sous-projets @@ -431,8 +440,10 @@ permission_edit_own_time_entries: Modifier son propre temps passé permission_manage_news: Gérer les annonces permission_comment_news: Commenter les annonces - permission_manage_documents: Gérer les documents permission_view_documents: Voir les documents + permission_add_documents: Ajouter des documents + permission_edit_documents: Modifier les documents + permission_delete_documents: Supprimer les documents permission_manage_files: Gérer les fichiers permission_view_files: Voir les fichiers permission_manage_wiki: Gérer le wiki @@ -562,8 +573,6 @@ label_text: Texte long label_attribute: Attribut label_attribute_plural: Attributs - label_download: "%{count} téléchargement" - label_download_plural: "%{count} téléchargements" label_no_data: Aucune donnée à afficher label_change_status: Changer le statut label_history: Historique @@ -611,6 +620,7 @@ one: 1 demande other: "%{count} demandes" label_total: Total + label_total_time: Temps total label_permissions: Permissions label_current_status: Statut actuel label_new_statuses_allowed: Nouveaux statuts autorisés @@ -677,8 +687,6 @@ label_repository_new: Nouveau dépôt label_repository_plural: Dépôts label_browse: Parcourir - label_modification: "%{count} modification" - label_modification_plural: "%{count} modifications" label_revision: "Révision " label_revision_plural: Révisions label_associated_revisions: Révisions associées @@ -711,7 +719,7 @@ label_index_by_date: Index par date label_current_version: Version actuelle label_preview: Prévisualisation - label_feed_plural: Flux RSS + label_feed_plural: Flux Atom label_changes_details: Détails de tous les changements label_issue_tracking: Suivi des demandes label_spent_time: Temps passé @@ -769,7 +777,7 @@ label_language_based: Basé sur la langue de l'utilisateur label_sort_by: "Trier par %{value}" label_send_test_email: Envoyer un email de test - label_feeds_access_key_created_on: "Clé d'accès RSS créée il y a %{value}" + label_feeds_access_key_created_on: "Clé d'accès Atom créée il y a %{value}" label_module_plural: Modules label_added_time_by: "Ajouté par %{author} il y a %{age}" label_updated_time_by: "Mis à jour par %{author} il y a %{age}" @@ -831,9 +839,9 @@ label_display_used_statuses_only: N'afficher que les statuts utilisés dans ce tracker label_api_access_key: Clé d'accès API label_api_access_key_created_on: Clé d'accès API créée il y a %{value} - label_feeds_access_key: Clé d'accès RSS + label_feeds_access_key: Clé d'accès Atom label_missing_api_access_key: Clé d'accès API manquante - label_missing_feeds_access_key: Clé d'accès RSS manquante + label_missing_feeds_access_key: Clé d'accès Atom manquante label_close_versions: Fermer les versions terminées label_revision_id: Révision %{value} label_profile: Profil @@ -857,14 +865,21 @@ label_fields_permissions: Permissions sur les champs label_readonly: Lecture label_required: Obligatoire + label_hidden: Caché label_attribute_of_project: "%{name} du projet" + label_attribute_of_issue: "%{name} de la demande" label_attribute_of_author: "%{name} de l'auteur" label_attribute_of_assigned_to: "%{name} de l'assigné" + label_attribute_of_user: "%{name} de l'utilisateur" label_attribute_of_fixed_version: "%{name} de la version cible" label_cross_project_descendants: Avec les sous-projets label_cross_project_tree: Avec tout l'arbre label_cross_project_hierarchy: Avec toute la hiérarchie label_cross_project_system: Avec tous les projets + label_gantt_progress_line: Ligne de progression + label_visibility_private: par moi uniquement + label_visibility_roles: par ces roles uniquement + label_visibility_public: par tout le monde button_login: Connexion button_submit: Soumettre @@ -940,7 +955,7 @@ text_tip_issue_begin_day: tâche commençant ce jour text_tip_issue_end_day: tâche finissant ce jour text_tip_issue_begin_end_day: tâche commençant et finissant ce jour - text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisés.
    Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' + text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et tirets bas sont autorisés, doit commencer par une minuscule.
    Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' text_caracters_maximum: "%{count} caractères maximum." text_caracters_minimum: "%{count} caractères minimum." text_length_between: "Longueur comprise entre %{min} et %{max} caractères." @@ -990,6 +1005,7 @@ text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver." text_session_expiration_settings: "Attention : le changement de ces paramètres peut entrainer l'expiration des sessions utilisateurs en cours, y compris la vôtre." text_project_closed: Ce projet est fermé et accessible en lecture seule. + text_turning_multiple_off: "Si vous désactivez les valeurs multiples, les valeurs multiples seront supprimées pour n'en conserver qu'une par objet." default_role_manager: "Manager " default_role_developer: "Développeur " @@ -1095,4 +1111,5 @@ error_scm_annotate_big_text_file: Cette entrée ne peut pas être annotée car elle excède la taille maximale. setting_repositories_encodings: Encodages des fichiers et des dépôts label_search_for_watchers: Rechercher des observateurs - text_repository_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisés.
    Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' + text_repository_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et tirets bas sont autorisés.
    Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' + text_convert_available: Binaire convert de ImageMagick présent (optionel) diff -r d98d22a98252 -r afce8026aaeb config/locales/gl.yml --- a/config/locales/gl.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/gl.yml Tue Sep 09 09:34:53 2014 +0100 @@ -91,8 +91,8 @@ one: 'aproximadamente unha hora' other: '%{count} horas' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hora" + other: "%{count} horas" x_days: one: '1 día' other: '%{count} días' @@ -156,6 +156,7 @@ not_same_project: "non pertence ao mesmo proxecto" circular_dependency: "Esta relación podería crear unha dependencia circular" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Por favor seleccione @@ -320,8 +321,6 @@ general_text_Yes: 'Si' general_text_no: 'non' general_text_yes: 'si' - gui_validation_error: 1 erro - gui_validation_error_plural: "%{count} erros" label_activity: Actividade label_add_another_file: Engadir outro arquivo label_add_note: Engadir unha nota @@ -414,8 +413,6 @@ label_document_added: Documento engadido label_document_new: Novo documento label_document_plural: Documentos - label_download: "%{count} Descarga" - label_download_plural: "%{count} Descargas" label_downloads_abbr: D/L label_duplicated_by: duplicada por label_duplicates: duplicada de @@ -430,7 +427,7 @@ label_f_hour: "%{value} hora" label_f_hour_plural: "%{value} horas" label_feed_plural: Feeds - label_feeds_access_key_created_on: "Clave de acceso por RSS creada fai %{value}" + label_feeds_access_key_created_on: "Clave de acceso por Atom creada fai %{value}" label_file_added: Arquivo engadido label_file_plural: Arquivos label_filter_add: Engadir o filtro @@ -495,8 +492,6 @@ label_message_plural: Mensaxes label_message_posted: Mensaxe engadida label_min_max_length: Lonxitude mín - máx - label_modification: "%{count} modificación" - label_modification_plural: "%{count} modificacións" label_modified: modificado label_module_plural: Módulos label_month: Mes @@ -661,7 +656,7 @@ notice_email_error: "Ocorreu un error enviando o correo (%{value})" notice_email_sent: "Enviouse un correo a %{value}" notice_failed_to_save_issues: "Imposible gravar %{count} petición(s) de %{total} seleccionada(s): %{ids}." - notice_feeds_access_key_reseted: A súa clave de acceso para RSS reiniciouse. + notice_feeds_access_key_reseted: A súa clave de acceso para Atom reiniciouse. notice_file_not_found: A páxina á que tenta acceder non existe. notice_locking_conflict: Os datos modificáronse por outro usuario. notice_no_issue_selected: "Ningunha petición seleccionada. Por favor, comprobe a petición que quere modificar" @@ -695,7 +690,6 @@ permission_log_time: Anotar tempo dedicado permission_manage_boards: Administrar foros permission_manage_categories: Administrar categorías de peticións - permission_manage_documents: Administrar documentos permission_manage_files: Administrar arquivos permission_manage_issue_relations: Administrar relación con outras peticións permission_manage_members: Administrar membros @@ -892,11 +886,11 @@ label_revision_id: Revision %{value} label_api_access_key: API access key label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key + label_feeds_access_key: Atom access key notice_api_access_key_reseted: Your API access key was reset. setting_rest_api_enabled: Enable REST web service label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key + label_missing_feeds_access_key: Missing a Atom access key button_show: Show text_line_separated: Multiple values allowed (one line for each value). setting_mail_handler_body_delimiters: Truncate emails after one of these lines @@ -944,7 +938,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -985,8 +978,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1093,3 +1084,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/he.yml --- a/config/locales/he.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/he.yml Tue Sep 09 09:34:53 2014 +0100 @@ -56,8 +56,8 @@ one: 'בערך שעה ×חת' other: 'בערך %{count} שעות' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 שעה" + other: "%{count} שעות" x_days: one: '×™×•× ×חד' other: '%{count} ימי×' @@ -134,6 +134,7 @@ not_same_project: "×œ× ×©×™×™×š ל×ותו הפרויקט" circular_dependency: "קשר ×–×” יצור תלות מעגלית" cant_link_an_issue_with_a_descendant: "×œ× × ×™×ª×Ÿ לקשר × ×•×©× ×œ×ª×ªÖ¾×ž×©×™×ž×” שלו" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: בחר בבקשה @@ -167,7 +168,7 @@ notice_not_authorized_archived_project: הפרויקט ש×תה מנסה לגשת ×ליו × ×ž×¦× ×‘×רכיון. notice_email_sent: "דו×ל נשלח לכתובת %{value}" notice_email_error: "×רעה שגי××” בעת שליחת הדו×ל (%{value})" - notice_feeds_access_key_reseted: מפתח ×”Ö¾RSS שלך ×ופס. + notice_feeds_access_key_reseted: מפתח ×”Ö¾Atom שלך ×ופס. notice_api_access_key_reseted: מפתח הגישה שלך ל־API ×ופס. notice_failed_to_save_issues: "נכשרת בשמירת %{count} נוש××™× ×‘ %{total} נבחרו: %{ids}." notice_failed_to_save_members: "כשלון בשמירת חבר(×™×): %{errors}." @@ -212,8 +213,6 @@ mail_subject_wiki_content_updated: "דף ×”Ö¾wiki â€'%{id}' עודכן" mail_body_wiki_content_updated: דף ×”Ö¾wiki â€'%{id}' עודכן ×¢"×™ %{author}. - gui_validation_error: שגי××” 1 - gui_validation_error_plural: "%{count} שגי×ות" field_name: ×©× field_description: תי×ור @@ -393,7 +392,6 @@ permission_edit_own_time_entries: עריכת ×¨×™×©×•× ×”×–×ž× ×™× ×©×œ עצמו permission_manage_news: ניהול חדשות permission_comment_news: תגובה לחדשות - permission_manage_documents: ניהול ×ž×¡×ž×›×™× permission_view_documents: צפיה ×‘×ž×¡×ž×›×™× permission_manage_files: ניהול ×§×‘×¦×™× permission_view_files: צפיה ×‘×§×‘×¦×™× @@ -519,8 +517,6 @@ label_text: טקסט ×רוך label_attribute: תכונה label_attribute_plural: תכונות - label_download: "הורדה %{count}" - label_download_plural: "%{count} הורדות" label_no_data: ×ין מידע להציג label_change_status: שנה מצב label_history: היסטוריה @@ -623,8 +619,6 @@ label_repository: מ×גר label_repository_plural: מ××’×¨×™× label_browse: סייר - label_modification: "שינוי %{count}" - label_modification_plural: "%{count} שינויי×" label_branch: ×¢× ×£ label_tag: סימון label_revision: מהדורה @@ -720,9 +714,9 @@ label_language_based: מבוסס שפה label_sort_by: "מיין לפי %{value}" label_send_test_email: שלח דו×"ל בדיקה - label_feeds_access_key: מפתח גישה ל־RSS - label_missing_feeds_access_key: חסר מפתח גישה ל־RSS - label_feeds_access_key_created_on: "מפתח הזנת RSS נוצר לפני%{value}" + label_feeds_access_key: מפתח גישה ל־Atom + label_missing_feeds_access_key: חסר מפתח גישה ל־Atom + label_feeds_access_key_created_on: "מפתח הזנת Atom נוצר לפני%{value}" label_module_plural: ×ž×•×“×•×œ×™× label_added_time_by: 'נוסף ×¢"×™ %{author} לפני %{age}' label_updated_time_by: 'עודכן ×¢"×™ %{author} לפני %{age}' @@ -938,7 +932,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -979,8 +972,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1087,3 +1078,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: סה"×› + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/hr.yml --- a/config/locales/hr.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/hr.yml Tue Sep 09 09:34:53 2014 +0100 @@ -9,12 +9,12 @@ short: "%b %d" long: "%B %d, %Y" - day_names: [Ponedjeljak, Utorak, Srijeda, ÄŒetvrtak, Petak, Subota, Nedjelja] + day_names: [Nedjelja, Ponedjeljak, Utorak, Srijeda, ÄŒetvrtak, Petak, Subota] abbr_day_names: [Ned, Pon, Uto, Sri, ÄŒet, Pet, Sub] # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Sijecanj, Veljaca, Ožujak, Travanj, Svibanj, Lipanj, Srpanj, Kolovoz, Rujan, Listopad, Studeni, Prosinac] - abbr_month_names: [~, Sij, Velj, Ožu, Tra, Svi, Lip, Srp, Kol, Ruj, List, Stu, Pro] + month_names: [~, SijeÄanj, VeljaÄa, Ožujak, Travanj, Svibanj, Lipanj, Srpanj, Kolovoz, Rujan, Listopad, Studeni, Prosinac] + abbr_month_names: [~, Sij, Velj, Ožu, Tra, Svi, Lip, Srp, Kol, Ruj, Lis, Stu, Pro] # Used in date_select and datime_select. order: - :year @@ -49,8 +49,8 @@ one: "oko sat vremena" other: "oko %{count} sati" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 sata" + other: "%{count} sati" x_days: one: "1 dan" other: "%{count} dana" @@ -124,6 +124,7 @@ not_same_project: "ne pripada istom projektu" circular_dependency: "Ovaj relacija stvara kružnu ovisnost" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Molimo odaberite @@ -156,7 +157,7 @@ notice_not_authorized: Niste ovlaÅ¡teni za pristup ovoj stranici. notice_email_sent: E-mail je poslan %{value}" notice_email_error: Dogodila se pogreÅ¡ka tijekom slanja E-maila (%{value})" - notice_feeds_access_key_reseted: VaÅ¡ RSS pristup je resetovan. + notice_feeds_access_key_reseted: VaÅ¡ Atom pristup je resetovan. notice_api_access_key_reseted: VaÅ¡ API pristup je resetovan. notice_failed_to_save_issues: "Neuspjelo spremanje %{count} predmeta na %{total} odabrane: %{ids}." notice_no_issue_selected: "Niti jedan predmet nije odabran! Molim, odaberite predmete koje želite urediti." @@ -195,8 +196,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - gui_validation_error: 1 pogreÅ¡ka - gui_validation_error_plural: "%{count} pogreÅ¡aka" field_name: Ime field_description: Opis @@ -368,7 +367,6 @@ permission_edit_own_time_entries: Edit own time logs permission_manage_news: Upravljaj novostima permission_comment_news: Komentiraj novosti - permission_manage_documents: Upravljaj dokumentima permission_view_documents: Pregledaj dokumente permission_manage_files: Upravljaj datotekama permission_view_files: Pregledaj datoteke @@ -489,8 +487,6 @@ label_text: Long text label_attribute: Atribut label_attribute_plural: Atributi - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: Nema podataka za prikaz label_change_status: Promjena statusa label_history: Povijest @@ -592,8 +588,6 @@ label_repository: SkladiÅ¡te label_repository_plural: SkladiÅ¡ta label_browse: Pregled - label_modification: "%{count} promjena" - label_modification_plural: "%{count} promjena" label_branch: Branch label_tag: Tag label_revision: Revizija @@ -686,9 +680,9 @@ label_language_based: Zasnovano na jeziku label_sort_by: "Uredi po %{value}" label_send_test_email: PoÅ¡alji testno E-pismo - label_feeds_access_key: RSS access key - label_missing_feeds_access_key: Missing a RSS access key - label_feeds_access_key_created_on: "RSS kljuc za pristup je napravljen prije %{value}" + label_feeds_access_key: Atom access key + label_missing_feeds_access_key: Missing a Atom access key + label_feeds_access_key_created_on: "Atom kljuc za pristup je napravljen prije %{value}" label_module_plural: Moduli label_added_time_by: "Promijenio %{author} prije %{age}" label_updated_time_by: "Dodao/la %{author} prije %{age}" @@ -934,7 +928,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -975,8 +968,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1074,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/hu.yml --- a/config/locales/hu.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/hu.yml Tue Sep 09 09:34:53 2014 +0100 @@ -51,8 +51,8 @@ one: 'csaknem 1 órája' other: 'csaknem %{count} órája' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 óra" + other: "%{count} óra" x_days: one: '1 napja' other: '%{count} napja' @@ -150,6 +150,7 @@ not_same_project: "nem azonos projekthez tartozik" circular_dependency: "Ez a kapcsolat egy körkörös függÅ‘séget eredményez" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Kérem válasszon @@ -182,7 +183,7 @@ notice_not_authorized: Nincs hozzáférési engedélye ehhez az oldalhoz. notice_email_sent: "Egy e-mail üzenetet küldtünk a következÅ‘ címre %{value}" notice_email_error: "Hiba történt a levél küldése közben (%{value})" - notice_feeds_access_key_reseted: Az RSS hozzáférési kulcsát újra generáltuk. + notice_feeds_access_key_reseted: Az Atom hozzáférési kulcsát újra generáltuk. notice_failed_to_save_issues: "Nem sikerült a %{count} feladat(ok) mentése a %{total} -ban kiválasztva: %{ids}." notice_no_issue_selected: "Nincs feladat kiválasztva! Kérem jelölje meg melyik feladatot szeretné szerkeszteni!" notice_account_pending: "A fiókja létrejött, és adminisztrátori jóváhagyásra vár." @@ -203,8 +204,6 @@ mail_subject_account_activation_request: Redmine azonosító aktiválási kérelem mail_body_account_activation_request: "Egy új felhasználó (%{value}) regisztrált, azonosítója jóváhasgyásra várakozik:" - gui_validation_error: 1 hiba - gui_validation_error_plural: "%{count} hiba" field_name: Név field_description: Leírás @@ -303,7 +302,7 @@ setting_host_name: Kiszolgáló neve setting_text_formatting: Szöveg formázás setting_wiki_compression: Wiki történet tömörítés - setting_feeds_limit: RSS tartalom korlát + setting_feeds_limit: Atom tartalom korlát setting_default_projects_public: Az új projektek alapértelmezés szerint nyilvánosak setting_autofetch_changesets: Commitok automatikus lehúzása setting_sys_api_enabled: WS engedélyezése a tárolók kezeléséhez @@ -415,8 +414,6 @@ label_text: Hosszú szöveg label_attribute: Tulajdonság label_attribute_plural: Tulajdonságok - label_download: "%{count} Letöltés" - label_download_plural: "%{count} Letöltés" label_no_data: Nincs megjeleníthetÅ‘ adat label_change_status: Státusz módosítása label_history: Történet @@ -516,8 +513,6 @@ label_repository: Forráskód label_repository_plural: Forráskódok label_browse: Tallóz - label_modification: "%{count} változás" - label_modification_plural: "%{count} változás" label_revision: Revízió label_revision_plural: Revíziók label_associated_revisions: Kapcsolt revíziók @@ -603,7 +598,7 @@ label_language_based: A felhasználó nyelve alapján label_sort_by: "%{value} szerint rendezve" label_send_test_email: Teszt e-mail küldése - label_feeds_access_key_created_on: "RSS hozzáférési kulcs létrehozva %{value}" + label_feeds_access_key_created_on: "Atom hozzáférési kulcs létrehozva %{value}" label_module_plural: Modulok label_added_time_by: "%{author} adta hozzá %{age}" label_updated_time: "Utolsó módosítás %{value}" @@ -777,7 +772,6 @@ permission_comment_news: Hírek kommentelése permission_delete_messages: Üzenetek törlése permission_select_project_modules: Projekt modulok kezelése - permission_manage_documents: Dokumentumok kezelése permission_edit_wiki_pages: Wiki oldalak szerkesztése permission_add_issue_watchers: MegfigyelÅ‘k felvétele permission_view_gantt: Gannt diagramm megtekintése @@ -900,11 +894,11 @@ label_revision_id: Revízió %{value} label_api_access_key: API hozzáférési kulcs label_api_access_key_created_on: API hozzáférési kulcs létrehozva %{value} ezelÅ‘tt - label_feeds_access_key: RSS hozzáférési kulcs + label_feeds_access_key: Atom hozzáférési kulcs notice_api_access_key_reseted: Az API hozzáférési kulcsa újragenerálva. setting_rest_api_enabled: REST web service engedélyezése label_missing_api_access_key: Egy API hozzáférési kulcs hiányzik - label_missing_feeds_access_key: RSS hozzáférési kulcs hiányzik + label_missing_feeds_access_key: Atom hozzáférési kulcs hiányzik button_show: Megmutat text_line_separated: Több érték megadása lehetséges (soronként 1 érték). setting_mail_handler_body_delimiters: E-mailek levágása a következÅ‘ sorok valamelyike esetén @@ -993,8 +987,6 @@ text_scm_command: Parancs text_scm_command_version: Verzió label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1101,3 +1093,31 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Összesen + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/id.yml --- a/config/locales/id.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/id.yml Tue Sep 09 09:34:53 2014 +0100 @@ -47,8 +47,8 @@ one: "sekitar sejam" other: "sekitar %{count} jam" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 jam" + other: "%{count} jam" x_days: one: "sehari" other: "%{count} hari" @@ -129,6 +129,7 @@ not_same_project: "tidak tergabung dalam proyek yang sama" circular_dependency: "kaitan ini akan menghasilkan circular dependency" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Silakan pilih @@ -161,7 +162,7 @@ notice_not_authorized: Anda tidak memiliki akses ke halaman ini. notice_email_sent: "Email sudah dikirim ke %{value}" notice_email_error: "Terjadi kesalahan pada saat pengiriman email (%{value})" - notice_feeds_access_key_reseted: RSS access key anda sudah direset. + notice_feeds_access_key_reseted: Atom access key anda sudah direset. notice_failed_to_save_issues: "Gagal menyimpan %{count} masalah dari %{total} yang dipilih: %{ids}." notice_no_issue_selected: "Tidak ada masalah yang dipilih! Silakan pilih masalah yang akan anda sunting." notice_account_pending: "Akun anda sudah dibuat dan sekarang sedang menunggu persetujuan administrator." @@ -195,8 +196,6 @@ mail_subject_wiki_content_updated: "'%{id}' halaman wiki sudah diperbarui" mail_body_wiki_content_updated: "The '%{id}' halaman wiki sudah diperbarui oleh %{author}." - gui_validation_error: 1 kesalahan - gui_validation_error_plural: "%{count} kesalahan" field_name: Nama field_description: Deskripsi @@ -361,7 +360,6 @@ permission_edit_own_time_entries: Sunting catatan waktu saya permission_manage_news: Atur berita permission_comment_news: Komentari berita - permission_manage_documents: Atur dokumen permission_view_documents: Tampilkan dokumen permission_manage_files: Atur berkas permission_view_files: Tampilkan berkas @@ -481,8 +479,6 @@ label_text: Long text label_attribute: Atribut label_attribute_plural: Atribut - label_download: "%{count} Unduhan" - label_download_plural: "%{count} Unduhan" label_no_data: Tidak ada data untuk ditampilkan label_change_status: Status perubahan label_history: Riwayat @@ -584,8 +580,6 @@ label_repository: Repositori label_repository_plural: Repositori label_browse: Jelajah - label_modification: "%{count} perubahan" - label_modification_plural: "%{count} perubahan" label_branch: Cabang label_tag: Tag label_revision: Revisi @@ -677,7 +671,7 @@ label_language_based: Berdasarkan bahasa pengguna label_sort_by: "Urut berdasarkan %{value}" label_send_test_email: Kirim email percobaan - label_feeds_access_key_created_on: "RSS access key dibuat %{value} yang lalu" + label_feeds_access_key_created_on: "Atom access key dibuat %{value} yang lalu" label_module_plural: Modul label_added_time_by: "Ditambahkan oleh %{author} %{age} yang lalu" label_updated_time_by: "Diperbarui oleh %{author} %{age} yang lalu" @@ -880,7 +874,7 @@ label_display_used_statuses_only: Only display statuses that are used by this tracker error_workflow_copy_target: Please select target tracker(s) and role(s) label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key + label_feeds_access_key: Atom access key notice_api_access_key_reseted: Your API access key was reset. setting_rest_api_enabled: Enable REST web service label_copy_same_as_target: Same as target @@ -888,7 +882,7 @@ setting_issue_done_ratio_issue_field: Use the issue field label_missing_api_access_key: Missing an API access key label_copy_target: Target - label_missing_feeds_access_key: Missing a RSS access key + label_missing_feeds_access_key: Missing a Atom access key notice_issue_done_ratios_updated: Issue done ratios updated. error_workflow_copy_source: Please select a source tracker or role setting_start_of_week: Start calendars on @@ -937,7 +931,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -978,8 +971,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1086,3 +1077,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/it.yml --- a/config/locales/it.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/it.yml Tue Sep 09 09:34:53 2014 +0100 @@ -55,8 +55,8 @@ one: "circa un'ora" other: "circa %{count} ore" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ora" + other: "%{count} ore" x_days: one: "1 giorno" other: "%{count} giorni" @@ -134,6 +134,7 @@ not_same_project: "non appartiene allo stesso progetto" circular_dependency: "Questa relazione creerebbe una dipendenza circolare" cant_link_an_issue_with_a_descendant: "Una segnalazione non può essere collegata a una delle sue discendenti" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Scegli @@ -152,7 +153,6 @@ notice_account_invalid_creditentials: Nome utente o password non validi. notice_account_password_updated: La password è stata aggiornata. notice_account_wrong_password: Password errata - notice_account_register_done: L'utente è stato creato. notice_account_unknown_email: Utente sconosciuto. notice_can_t_change_password: Questo utente utilizza un metodo di autenticazione esterno. Impossibile cambiare la password. notice_account_lost_email_sent: Ti è stata spedita una email con le istruzioni per cambiare la password. @@ -166,7 +166,7 @@ notice_not_authorized: Non sei autorizzato ad accedere a questa pagina. notice_email_sent: "Una email è stata spedita a %{value}" notice_email_error: "Si è verificato un errore durante l'invio di una email (%{value})" - notice_feeds_access_key_reseted: La tua chiave di accesso RSS è stata reimpostata. + notice_feeds_access_key_reseted: La tua chiave di accesso Atom è stata reimpostata. error_scm_not_found: "La risorsa e/o la versione non esistono nel repository." error_scm_command_failed: "Si è verificato un errore durante l'accesso al repository: %{value}" @@ -176,8 +176,6 @@ mail_subject_register: "Attivazione utente %{value}" mail_body_register: "Per attivare l'utente, usa il seguente collegamento:" - gui_validation_error: 1 errore - gui_validation_error_plural: "%{count} errori" field_name: Nome field_description: Descrizione @@ -357,8 +355,6 @@ label_text: Testo esteso label_attribute: Attributo label_attribute_plural: Attributi - label_download: "%{count} Download" - label_download_plural: "%{count} Download" label_no_data: Nessun dato disponibile label_change_status: Cambia stato label_history: Cronologia @@ -446,8 +442,6 @@ label_day_plural: giorni label_repository: Repository label_browse: Sfoglia - label_modification: "%{count} modifica" - label_modification_plural: "%{count} modifiche" label_revision: Versione label_revision_plural: Versioni label_added: aggiunto @@ -500,10 +494,10 @@ label_loading: Caricamento... label_relation_new: Nuova relazione label_relation_delete: Elimina relazione - label_relates_to: correlato a - label_duplicates: duplicati - label_blocks: blocchi - label_blocked_by: bloccato da + label_relates_to: correlata a + label_duplicates: duplica + label_blocks: blocca + label_blocked_by: bloccata da label_precedes: precede label_follows: segue label_end_to_start: fine a inizio @@ -531,7 +525,7 @@ label_language_based: Basato sul linguaggio label_sort_by: "Ordina per %{value}" label_send_test_email: Invia una email di prova - label_feeds_access_key_created_on: "chiave di accesso RSS creata %{value} fa" + label_feeds_access_key_created_on: "chiave di accesso Atom creata %{value} fa" label_module_plural: Moduli label_added_time_by: "Aggiunto da %{author} %{age} fa" label_updated_time: "Aggiornato %{value} fa" @@ -589,8 +583,8 @@ text_unallowed_characters: Caratteri non permessi text_comma_separated: Valori multipli permessi (separati da virgole). text_issues_ref_in_commit_messages: Segnalazioni di riferimento e chiusura nei messaggi di commit - text_issue_added: "E' stata segnalata l'anomalia %{id} da %{author}." - text_issue_updated: "L'anomalia %{id} è stata aggiornata da %{author}." + text_issue_added: "%{author} ha aggiunto la segnalazione %{id}." + text_issue_updated: "La segnalazione %{id} è stata aggiornata da %{author}." text_wiki_destroy_confirmation: Sicuro di voler eliminare questo wiki e tutti i suoi contenuti? text_issue_category_destroy_question: "Alcune segnalazioni (%{count}) risultano assegnate a questa categoria. Cosa vuoi fare ?" text_issue_category_destroy_assignments: Rimuovi le assegnazioni a questa categoria @@ -727,7 +721,7 @@ mail_body_reminder: "%{count} segnalazioni che ti sono state assegnate scadranno nei prossimi %{days} giorni:" mail_subject_reminder: "%{count} segnalazioni in scadenza nei prossimi %{days} giorni" text_user_wrote: "%{value} ha scritto:" - label_duplicated_by: duplicato da + label_duplicated_by: duplicata da setting_enabled_scm: SCM abilitato text_enumeration_category_reassign_to: 'Riassegnale a questo valore:' text_enumeration_destroy_question: "%{count} oggetti hanno un assegnamento su questo valore." @@ -759,7 +753,6 @@ permission_comment_news: Commenta notizie permission_delete_messages: Elimina messaggi permission_select_project_modules: Seleziona moduli progetto - permission_manage_documents: Gestisci documenti permission_edit_wiki_pages: Modifica pagine wiki permission_add_issue_watchers: Aggiungi osservatori permission_view_gantt: Vedi diagrammi gantt @@ -882,11 +875,11 @@ label_revision_id: Revisione %{value} label_api_access_key: Chiave di accesso API label_api_access_key_created_on: Chiave di accesso API creata %{value} fa - label_feeds_access_key: Chiave di accesso RSS + label_feeds_access_key: Chiave di accesso Atom notice_api_access_key_reseted: La chiave di accesso API è stata reimpostata. setting_rest_api_enabled: Abilita il servizio web REST label_missing_api_access_key: Chiave di accesso API mancante - label_missing_feeds_access_key: Chiave di accesso RSS mancante + label_missing_feeds_access_key: Chiave di accesso Atom mancante button_show: Mostra text_line_separated: Valori multipli permessi (un valore per ogni riga). setting_mail_handler_body_delimiters: Tronca email dopo una di queste righe @@ -1009,77 +1002,102 @@ button_export: Esporta label_export_options: "%{export_format} opzioni per l'export" error_attachment_too_big: Questo file non può essere caricato in quanto la sua dimensione supera la massima consentita (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + notice_failed_to_save_time_entries: "Non ho potuto salvare %{count} registrazioni di tempo impiegato su %{total} selezionate: %{ids}." label_x_issues: zero: 0 segnalazione one: 1 segnalazione other: "%{count} segnalazioni" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments + label_repository_new: Nuovo repository + field_repository_is_default: Repository principale + label_copy_attachments: Copia allegati label_item_position: "%{position}/%{count}" label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values + text_project_identifier_info: Consentiti solo lettere minuscole (a-z), numeri, trattini e trattini bassi.
    Una volta salvato, l'identificatore non può essere modificato. + field_multiple: Valori multipli setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + text_issue_conflict_resolution_add_notes: Aggiunge le mie note e non salvare le mie ulteriori modifiche + text_issue_conflict_resolution_overwrite: Applica comunque le mie modifiche (le note precedenti verranno mantenute ma alcuni cambiamenti potrebbero essere sovrascritti) + notice_issue_update_conflict: La segnalazione è stata aggiornata da un altro utente mentre la stavi editando. + text_issue_conflict_resolution_cancel: Cancella ogni modifica e rivisualizza %{link} + permission_manage_related_issues: Gestisci relative segnalazioni + field_auth_source_ldap_filter: Filtro LDAP + label_search_for_watchers: Cerca osservatori da aggiungere + notice_account_deleted: Il tuo account sarà definitivamente rimosso. + setting_unsubscribe: Consentire agli utenti di cancellare il proprio account + button_delete_my_account: Cancella il mio account + text_account_destroy_confirmation: "Sei sicuro di voler procedere?\nIl tuo account sarà definitivamente cancellato, senza alcuna possibilità di ripristino." + error_session_expired: "La tua sessione è scaduta. Effettua nuovamente il login." + text_session_expiration_settings: "Attenzione: la modifica di queste impostazioni può far scadere le sessioni correnti, compresa la tua." + setting_session_lifetime: Massima durata di una sessione + setting_session_timeout: Timeout di inattività di una sessione + label_session_expiration: Scadenza sessione + permission_close_project: Chiusura / riapertura progetto + label_show_closed_projects: Vedi progetti chiusi + button_close: Chiudi + button_reopen: Riapri + project_status_active: attivo + project_status_closed: chiuso + project_status_archived: archiviato + text_project_closed: Questo progetto è chiuso e in sola lettura. + notice_user_successful_create: Creato utente %{id}. + field_core_fields: Campi standard + field_timeout: Timeout (in secondi) + setting_thumbnails_enabled: Mostra miniature degli allegati + setting_thumbnails_size: Dimensioni delle miniature (in pixels) + label_status_transitions: Transizioni di stato + label_fields_permissions: Permessi sui campi + label_readonly: Sola lettura + label_required: Richiesto + text_repository_identifier_info: Consentiti solo lettere minuscole (a-z), numeri, trattini e trattini bassi.
    Una volta salvato, ll'identificatore non può essere modificato. field_board_parent: Parent forum label_attribute_of_project: Project's %{name} label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_assigned_to: Assegnatari %{name} label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project + label_copy_subtasks: Copia sottoattività + label_copied_to: copia + label_copied_from: copiata da + label_any_issues_in_project: ogni segnalazione del progetto + label_any_issues_not_in_project: ogni segnalazione non nel progetto + field_private_notes: Note private + permission_view_private_notes: Visualizza note private + permission_set_notes_private: Imposta note come private + label_no_issues_in_project: progetto privo di segnalazioni label_any: tutti - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks + label_last_n_weeks: ultime %{count} settimane + setting_cross_project_subtasks: Consenti sottoattività cross-project label_cross_project_descendants: Con sottoprogetti label_cross_project_tree: Con progetto padre label_cross_project_hierarchy: Con gerarchia progetto label_cross_project_system: Con tutti i progetti - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past + button_hide: Nascondi + setting_non_working_week_days: Giorni non lavorativi + label_in_the_next_days: nei prossimi + label_in_the_past_days: nei passati + label_attribute_of_user: Utente %{name} + text_turning_multiple_off: Disabilitando valori multipli, i valori multipli verranno rimossi, in modo da mantenere un solo valore per item. + label_attribute_of_issue: Segnalazione %{name} + permission_add_documents: Aggiungi documenti + permission_edit_documents: Edita documenti + permission_delete_documents: Cancella documenti + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Abilita supporto a JSONP + field_inherit_members: Eredita membri + field_closed_on: Chiuso + field_generate_password: Generate password + setting_default_projects_tracker_ids: Trackers di default per nuovi progetti + label_total_time: Totale + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + notice_account_register_done: Account was successfully created. An email containing + the instructions to activate your account was sent to %{email}. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/ja.yml --- a/config/locales/ja.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/ja.yml Tue Sep 09 09:34:53 2014 +0100 @@ -146,10 +146,11 @@ less_than_or_equal_to: "ã¯%{count}以下ã®å€¤ã«ã—ã¦ãã ã•ã„。" odd: "ã¯å¥‡æ•°ã«ã—ã¦ãã ã•ã„。" even: "ã¯å¶æ•°ã«ã—ã¦ãã ã•ã„。" - greater_than_start_date: "を開始日より後ã«ã—ã¦ãã ã•ã„" - not_same_project: "åŒã˜ãƒ—ロジェクトã«å±žã—ã¦ã„ã¾ã›ã‚“" - circular_dependency: "ã“ã®é–¢ä¿‚ã§ã¯ã€å¾ªç’°ä¾å­˜ã«ãªã‚Šã¾ã™" - cant_link_an_issue_with_a_descendant: "指定ã—ãŸãƒã‚±ãƒƒãƒˆã¨ã¯è¦ªå­é–¢ä¿‚ã«ãªã£ã¦ã„ã‚‹ãŸã‚関連ã¥ã‘られã¾ã›ã‚“" + greater_than_start_date: "を開始日より後ã«ã—ã¦ãã ã•ã„。" + not_same_project: "åŒã˜ãƒ—ロジェクトã«å±žã—ã¦ã„ã¾ã›ã‚“。" + circular_dependency: "ã“ã®é–¢ä¿‚ã§ã¯ã€å¾ªç’°ä¾å­˜ã«ãªã‚Šã¾ã™ã€‚" + cant_link_an_issue_with_a_descendant: "親å­é–¢ä¿‚ã«ã‚ã‚‹ãƒã‚±ãƒƒãƒˆé–“ã§ã®é–¢é€£ã®è¨­å®šã¯ã§ãã¾ã›ã‚“。" + earlier_than_minimum_start_date: "ã‚’%{date}よりå‰ã«ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。先行ã™ã‚‹ãƒã‚±ãƒƒãƒˆãŒã‚りã¾ã™ã€‚" actionview_instancetag_blank_option: é¸ã‚“ã§ãã ã•ã„ @@ -176,7 +177,7 @@ notice_account_invalid_creditentials: ユーザーåã‚‚ã—ãã¯ãƒ‘スワードãŒç„¡åйã§ã™ notice_account_password_updated: ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ãŒæ›´æ–°ã•れã¾ã—ãŸã€‚ notice_account_wrong_password: パスワードãŒé•ã„ã¾ã™ - notice_account_register_done: アカウントãŒä½œæˆã•れã¾ã—ãŸã€‚ + notice_account_register_done: アカウントを作æˆã—ã¾ã—ãŸã€‚アカウントを有効ã«ã™ã‚‹ãŸã‚ã®æ‰‹é †ã‚’記載ã—ãŸãƒ¡ãƒ¼ãƒ«ã‚’ %{email} å®›ã«é€ä¿¡ã—ã¾ã—ãŸã€‚ notice_account_unknown_email: ユーザーãŒå­˜åœ¨ã—ã¾ã›ã‚“。 notice_can_t_change_password: ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã¯å¤–部èªè¨¼ã‚’使ã£ã¦ã„ã¾ã™ã€‚パスワードã¯å¤‰æ›´ã§ãã¾ã›ã‚“。 notice_account_lost_email_sent: æ–°ã—ã„パスワードã®ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚ @@ -187,20 +188,20 @@ notice_successful_connection: 接続ã—ã¾ã—ãŸã€‚ notice_file_not_found: アクセスã—よã†ã¨ã—ãŸãƒšãƒ¼ã‚¸ã¯å­˜åœ¨ã—ãªã„ã‹å‰Šé™¤ã•れã¦ã„ã¾ã™ã€‚ notice_locking_conflict: 別ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒãƒ‡ãƒ¼ã‚¿ã‚’æ›´æ–°ã—ã¦ã„ã¾ã™ã€‚ - notice_not_authorized: ã“ã®ãƒšãƒ¼ã‚¸ã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã«ã¯èªè¨¼ãŒå¿…è¦ã§ã™ã€‚ + notice_not_authorized: ã“ã®ãƒšãƒ¼ã‚¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“。 notice_not_authorized_archived_project: プロジェクトã¯ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ã•れã¦ã„ã¾ã™ã€‚ notice_email_sent: "%{value} å®›ã«ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚" notice_email_error: "メールé€ä¿¡ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—㟠(%{value})" - notice_feeds_access_key_reseted: RSSã‚¢ã‚¯ã‚»ã‚¹ã‚­ãƒ¼ã‚’åˆæœŸåŒ–ã—ã¾ã—ãŸã€‚ + notice_feeds_access_key_reseted: Atomã‚¢ã‚¯ã‚»ã‚¹ã‚­ãƒ¼ã‚’åˆæœŸåŒ–ã—ã¾ã—ãŸã€‚ notice_api_access_key_reseted: APIã‚¢ã‚¯ã‚»ã‚¹ã‚­ãƒ¼ã‚’åˆæœŸåŒ–ã—ã¾ã—ãŸã€‚ notice_failed_to_save_issues: "å…¨%{total}件中%{count}ä»¶ã®ãƒã‚±ãƒƒãƒˆãŒä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸ: %{ids}." notice_failed_to_save_members: "メンãƒãƒ¼ã®ä¿å­˜ã«å¤±æ•—ã—ã¾ã—ãŸ: %{errors}." notice_no_issue_selected: "ãƒã‚±ãƒƒãƒˆãŒé¸æŠžã•れã¦ã„ã¾ã›ã‚“! 更新対象ã®ãƒã‚±ãƒƒãƒˆã‚’é¸æŠžã—ã¦ãã ã•ã„。" - notice_account_pending: アカウントã¯ä½œæˆæ¸ˆã¿ã§ã€ã‚·ã‚¹ãƒ†ãƒ ç®¡ç†è€…ã®æ‰¿èªå¾…ã¡ã§ã™ã€‚ + notice_account_pending: アカウントを作æˆã—ã¾ã—ãŸã€‚システム管ç†è€…ã®æ‰¿èªå¾…ã¡ã§ã™ã€‚ notice_default_data_loaded: デフォルト設定をロードã—ã¾ã—ãŸã€‚ notice_unable_delete_version: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’削除ã§ãã¾ã›ã‚“ notice_unable_delete_time_entry: 作業時間を削除ã§ãã¾ã›ã‚“ - notice_issue_done_ratios_updated: ãƒã‚±ãƒƒãƒˆã®é€²æ—ãŒæ›´æ–°ã•れã¾ã—ãŸã€‚ + notice_issue_done_ratios_updated: ãƒã‚±ãƒƒãƒˆã®é€²æ—率を更新ã—ã¾ã—ãŸã€‚ notice_gantt_chart_truncated: ガントãƒãƒ£ãƒ¼ãƒˆã¯ã€æœ€å¤§è¡¨ç¤ºé …目数(%{max})ã‚’è¶…ãˆãŸãŸãŸã‚切りæ¨ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚ error_can_t_load_default_data: "デフォルト設定ãŒãƒ­ãƒ¼ãƒ‰ã§ãã¾ã›ã‚“ã§ã—ãŸ: %{value}" @@ -216,7 +217,7 @@ error_can_not_remove_role: 'ã“ã®ãƒ­ãƒ¼ãƒ«ã¯ä½¿ç”¨ã•れã¦ã„ã¾ã™ã€‚削除ã§ãã¾ã›ã‚“。' error_can_not_reopen_issue_on_closed_version: '終了ã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ã²ã‚‚付ã‘ã•れãŸãƒã‚±ãƒƒãƒˆã®å†ã‚ªãƒ¼ãƒ—ンã¯ã§ãã¾ã›ã‚“。' error_can_not_archive_project: ã“ã®ãƒ—ロジェクトã¯ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ã§ãã¾ã›ã‚“ - error_issue_done_ratios_not_updated: "ãƒã‚±ãƒƒãƒˆã®é€²æ—ãŒæ›´æ–°ã§ãã¾ã›ã‚“。" + error_issue_done_ratios_not_updated: "ãƒã‚±ãƒƒãƒˆã®é€²æ—çŽ‡ãŒæ›´æ–°ã§ãã¾ã›ã‚“。" error_workflow_copy_source: 'コピー元ã¨ãªã‚‹ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã¾ãŸã¯ãƒ­ãƒ¼ãƒ«ã‚’é¸æŠžã—ã¦ãã ã•ã„' error_workflow_copy_target: 'コピー先ã¨ãªã‚‹ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã¨ãƒ­ãƒ¼ãƒ«ã‚’é¸æŠžã—ã¦ãã ã•ã„' error_can_not_delete_tracker: 'ã“ã®ãƒˆãƒ©ãƒƒã‚«ãƒ¼ã¯ä½¿ç”¨ã•れã¦ã„ã¾ã™ã€‚削除ã§ãã¾ã›ã‚“。' @@ -238,8 +239,6 @@ mail_subject_wiki_content_updated: "Wikiページ %{id} ãŒæ›´æ–°ã•れã¾ã—ãŸ" mail_body_wiki_content_updated: "%{author} ã«ã‚ˆã£ã¦Wikiページ %{id} ãŒæ›´æ–°ã•れã¾ã—ãŸã€‚" - gui_validation_error: 1ä»¶ã®ã‚¨ãƒ©ãƒ¼ - gui_validation_error_plural: "%{count}ä»¶ã®ã‚¨ãƒ©ãƒ¼" field_name: åç§° field_description: 説明 @@ -303,7 +302,7 @@ field_attr_mail: メール属性 field_onthefly: ã‚ã‚ã›ã¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’ä½œæˆ field_start_date: é–‹å§‹æ—¥ - field_done_ratio: é€²æ— % + field_done_ratio: 進æ—率 field_auth_source: èªè¨¼æ–¹å¼ field_hide_mail: メールアドレスを隠㙠field_comments: コメント @@ -381,7 +380,7 @@ setting_activity_days_default: ãƒ—ãƒ­ã‚¸ã‚§ã‚¯ãƒˆã®æ´»å‹•ページã«è¡¨ç¤ºã•れる日数 setting_display_subprojects_issues: サブプロジェクトã®ãƒã‚±ãƒƒãƒˆã‚’メインプロジェクトã«è¡¨ç¤ºã™ã‚‹ setting_enabled_scm: 使用ã™ã‚‹ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã‚·ã‚¹ãƒ†ãƒ  - setting_mail_handler_body_delimiters: "メール本文ã‹ã‚‰ä¸€è‡´ã™ã‚‹è¡Œä»¥é™ã‚’切りå–ã‚‹" + setting_mail_handler_body_delimiters: "メール本文ã‹ã‚‰ä¸€è‡´ã™ã‚‹è¡Œä»¥é™ã‚’切りæ¨ã¦ã‚‹" setting_mail_handler_api_enabled: å—信メール用ã®Webサービスを有効ã«ã™ã‚‹ setting_mail_handler_api_key: APIキー setting_sequential_project_identifiers: プロジェクト識別å­ã‚’連番ã§ç”Ÿæˆã™ã‚‹ @@ -394,8 +393,8 @@ setting_password_min_length: ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ã®æœ€ä½Žå¿…è¦æ–‡å­—æ•° setting_new_project_user_role_id: システム管ç†è€…以外ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒä½œæˆã—ãŸãƒ—ロジェクトã«è¨­å®šã™ã‚‹ãƒ­ãƒ¼ãƒ« setting_default_projects_modules: æ–°è¦ãƒ—ロジェクトã«ãŠã„ã¦ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§æœ‰åйã«ãªã‚‹ãƒ¢ã‚¸ãƒ¥ãƒ¼ãƒ« - setting_issue_done_ratio: 進æ—ã®ç®—出方法 - setting_issue_done_ratio_issue_field: å„ãƒã‚±ãƒƒãƒˆã«ãƒ•ィールドを用æ„ã™ã‚‹ + setting_issue_done_ratio: 進æ—率ã®ç®—出方法 + setting_issue_done_ratio_issue_field: ãƒã‚±ãƒƒãƒˆã®ãƒ•ィールドを使用ã™ã‚‹ setting_issue_done_ratio_issue_status: ãƒã‚±ãƒƒãƒˆã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’使用ã™ã‚‹ setting_start_of_week: 週ã®é–‹å§‹æ›œæ—¥ setting_rest_api_enabled: RESTã«ã‚ˆã‚‹Webサービスを有効ã«ã™ã‚‹ @@ -403,6 +402,7 @@ setting_commit_logtime_enabled: コミット時ã«ä½œæ¥­æ™‚間を記録ã™ã‚‹ setting_commit_logtime_activity_id: 作業時間ã®ä½œæ¥­åˆ†é¡ž setting_gantt_items_limit: ガントãƒãƒ£ãƒ¼ãƒˆæœ€å¤§è¡¨ç¤ºé …目数 + setting_default_projects_tracker_ids: æ–°è¦ãƒ—ロジェクトã«ãŠã„ã¦ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§æœ‰åйã«ãªã‚‹ãƒˆãƒ©ãƒƒã‚«ãƒ¼ permission_add_project: プロジェクトã®è¿½åŠ  permission_add_subprojects: サブプロジェクトã®è¿½åŠ  @@ -434,7 +434,6 @@ permission_manage_project_activities: 作業分類 (時間トラッキング) ã®ç®¡ç† permission_manage_news: ニュースã®ç®¡ç† permission_comment_news: ニュースã¸ã®ã‚³ãƒ¡ãƒ³ãƒˆ - permission_manage_documents: 文書ã®ç®¡ç† permission_view_documents: 文書ã®é–²è¦§ permission_manage_files: ファイルã®ç®¡ç† permission_view_files: ファイルã®é–²è¦§ @@ -450,7 +449,7 @@ permission_manage_repository: リãƒã‚¸ãƒˆãƒªã®ç®¡ç† permission_browse_repository: リãƒã‚¸ãƒˆãƒªã®é–²è¦§ permission_view_changesets: 更新履歴ã®é–²è¦§ - permission_commit_access: コミットã®é–²è¦§ + permission_commit_access: ã‚³ãƒŸãƒƒãƒˆæ¨©é™ permission_manage_boards: フォーラムã®ç®¡ç† permission_view_messages: メッセージã®é–²è¦§ permission_add_messages: メッセージã®è¿½åŠ  @@ -561,8 +560,6 @@ label_text: é•·ã„テキスト label_attribute: 属性 label_attribute_plural: 属性 - label_download: "%{count}ダウンロード" - label_download_plural: "%{count}ダウンロード" label_no_data: 表示ã™ã‚‹ãƒ‡ãƒ¼ã‚¿ãŒã‚りã¾ã›ã‚“ label_change_status: ステータスã®å¤‰æ›´ label_history: 履歴 @@ -667,8 +664,6 @@ label_repository: リãƒã‚¸ãƒˆãƒª label_repository_plural: リãƒã‚¸ãƒˆãƒª label_browse: ブラウズ - label_modification: "%{count}点ã®å¤‰æ›´" - label_modification_plural: "%{count}点ã®å¤‰æ›´" label_branch: ブランムlabel_tag: ã‚¿ã‚° label_revision: リビジョン @@ -765,9 +760,9 @@ label_language_based: ユーザーã®è¨€èªžã®è¨­å®šã«å¾“ㆠlabel_sort_by: "ä¸¦ã³æ›¿ãˆ %{value}" label_send_test_email: テストメールをé€ä¿¡ - label_feeds_access_key: RSSアクセスキー - label_missing_feeds_access_key: RSSアクセスキーãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ - label_feeds_access_key_created_on: "RSSアクセスキーã¯%{value}å‰ã«ä½œæˆã•れã¾ã—ãŸ" + label_feeds_access_key: Atomアクセスキー + label_missing_feeds_access_key: AtomアクセスキーãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ + label_feeds_access_key_created_on: "Atomアクセスキーã¯%{value}å‰ã«ä½œæˆã•れã¾ã—ãŸ" label_module_plural: モジュール label_added_time_by: "%{author} ãŒ%{age}å‰ã«è¿½åŠ " label_updated_time_by: "%{author} ãŒ%{age}å‰ã«æ›´æ–°" @@ -826,7 +821,7 @@ label_version_sharing_hierarchy: プロジェクト階層å˜ä½ label_version_sharing_tree: プロジェクトツリーå˜ä½ label_version_sharing_system: ã™ã¹ã¦ã®ãƒ—ロジェクト - label_update_issue_done_ratios: 進æ—ã®æ›´æ–° + label_update_issue_done_ratios: 進æ—çŽ‡ã®æ›´æ–° label_copy_source: コピー元 label_copy_target: コピー先 label_copy_same_as_target: åŒã˜ã‚³ãƒ”ー先 @@ -841,6 +836,7 @@ label_git_report_last_commit: ファイルã¨ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®æœ€æ–°ã‚³ãƒŸãƒƒãƒˆã‚’表示ã™ã‚‹ label_parent_revision: 親 label_child_revision: å­ + label_gantt_progress_line: イナズマ線 button_login: ログイン button_submit: é€ä¿¡ @@ -863,7 +859,7 @@ button_unlock: アンロック button_download: ダウンロード button_list: 一覧 - button_view: 見る + button_view: 表示 button_move: 移動 button_move_and_follow: 移動後表示 button_back: 戻る @@ -911,9 +907,9 @@ text_journal_set_to: "%{label} ã‚’ %{value} ã«ã‚»ãƒƒãƒˆ" text_journal_deleted: "%{label} を削除 (%{old})" text_journal_added: "%{label} %{value} を追加" - text_tip_issue_begin_day: ã“ã®æ—¥ã«é–‹å§‹ã™ã‚‹ã‚¿ã‚¹ã‚¯ - text_tip_issue_end_day: ã“ã®æ—¥ã«çµ‚了ã™ã‚‹ã‚¿ã‚¹ã‚¯ - text_tip_issue_begin_end_day: ã“ã®æ—¥ã®ã†ã¡ã«é–‹å§‹ã—ã¦çµ‚了ã™ã‚‹ã‚¿ã‚¹ã‚¯ + text_tip_issue_begin_day: ã“ã®æ—¥ã«é–‹å§‹ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + text_tip_issue_end_day: ã“ã®æ—¥ã«çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + text_tip_issue_begin_end_day: ã“ã®æ—¥ã«é–‹å§‹ãƒ»çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ text_caracters_maximum: "最大%{count}文字ã§ã™ã€‚" text_caracters_minimum: "最低%{count}文字ã®é•·ã•ãŒå¿…è¦ã§ã™" text_length_between: "é•·ã•ã¯%{min}ã‹ã‚‰%{max}文字ã¾ã§ã§ã™ã€‚" @@ -938,7 +934,7 @@ text_default_administrator_account_changed: デフォルト管ç†ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒå¤‰æ›´æ¸ˆ text_file_repository_writable: ファイルリãƒã‚¸ãƒˆãƒªã«æ›¸ãè¾¼ã¿å¯èƒ½ text_plugin_assets_writable: Plugin assetsãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«æ›¸ãè¾¼ã¿å¯èƒ½ - text_rmagick_available: RMagickãŒä½¿ç”¨å¯èƒ½ (オプション) + text_rmagick_available: RMagickãŒåˆ©ç”¨å¯èƒ½ (オプション) text_destroy_time_entries_question: ã“ã®ãƒã‚±ãƒƒãƒˆã®%{hours}時間分ã®ä½œæ¥­è¨˜éŒ²ã®æ‰±ã„ã‚’é¸æŠžã—ã¦ãã ã•ã„。 text_destroy_time_entries: 記録ã•れãŸä½œæ¥­æ™‚é–“ã‚’å«ã‚ã¦å‰Šé™¤ text_assign_time_entries_to_project: 記録ã•れãŸä½œæ¥­æ™‚間をプロジェクト自体ã«å‰²ã‚Šå½“㦠@@ -1048,7 +1044,7 @@ label_copy_attachments: 添付ファイルをコピー label_item_position: "%{position}/%{count}" label_completed_versions: 完了ã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ - text_project_identifier_info: ã‚¢ãƒ«ãƒ•ã‚¡ãƒ™ãƒƒãƒˆå°æ–‡å­—(a-z)・数字・ãƒã‚¤ãƒ•ン・アンダースコアãŒä½¿ãˆã¾ã™ã€‚
    識別å­ã¯å¾Œã§å¤‰æ›´ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。 + text_project_identifier_info: ã‚¢ãƒ«ãƒ•ã‚¡ãƒ™ãƒƒãƒˆå°æ–‡å­—(a-z)・数字・ãƒã‚¤ãƒ•ン・アンダースコアãŒä½¿ãˆã¾ã™ã€‚最åˆã®æ–‡å­—ã¯ã‚¢ãƒ«ãƒ•ァベットã®å°æ–‡å­—ã«ã—ã¦ãã ã•ã„。
    識別å­ã¯å¾Œã§å¤‰æ›´ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。 field_multiple: è¤‡æ•°é¸æŠžå¯ setting_commit_cross_project_ref: ç•°ãªã‚‹ãƒ—ロジェクトã®ãƒã‚±ãƒƒãƒˆã®å‚ç…§/ä¿®æ­£ã‚’è¨±å¯ text_issue_conflict_resolution_add_notes: 自分ã®ç·¨é›†å†…å®¹ã‚’ç ´æ£„ã—æ³¨è¨˜ã®ã¿è¿½åŠ  @@ -1112,3 +1108,24 @@ setting_non_working_week_days: 休業日 label_in_the_next_days: 今後○日 label_in_the_past_days: éŽåŽ»â—‹æ—¥ + label_attribute_of_user: ユーザー㮠%{name} + text_turning_multiple_off: ã“ã®è¨­å®šã‚’無効ã«ã™ã‚‹ã¨ã€è¤‡æ•°é¸æŠžã•れã¦ã„る値ã®ã†ã¡1個ã ã‘ãŒä¿æŒã•れ残りã¯é¸æŠžè§£é™¤ã•れã¾ã™ã€‚ + label_attribute_of_issue: ãƒã‚±ãƒƒãƒˆã® %{name} + permission_add_documents: 文書ã®è¿½åŠ  + permission_edit_documents: 文書ã®ç·¨é›† + permission_delete_documents: 文書ã®å‰Šé™¤ + setting_jsonp_enabled: JSONPを有効ã«ã™ã‚‹ + field_inherit_members: メンãƒãƒ¼ã‚’継承 + field_closed_on: 終了日 + field_generate_password: ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ã‚’è‡ªå‹•ç”Ÿæˆ + label_total_time: åˆè¨ˆ + notice_account_not_activated_yet: ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒæœ‰åŠ¹åŒ–ã•れã¦ã„ã¾ã›ã‚“。アカウントを有効ã«ã™ã‚‹ãŸã‚ã®ãƒ¡ãƒ¼ãƒ«ã‚’ã‚‚ã†ä¸€åº¦å—ä¿¡ã—ãŸã„ã¨ãã¯ã“ã®ãƒªãƒ³ã‚¯ã‚’クリックã—ã¦ãã ã•ã„。 + notice_account_locked: アカウントãŒãƒ­ãƒƒã‚¯ã•れã¦ã„ã¾ã™ + label_hidden: éžè¡¨ç¤º + label_visibility_private: 自分ã®ã¿ + label_visibility_roles: 以下ã®ãƒ­ãƒ¼ãƒ«ã®ã¿ + label_visibility_public: ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ + field_must_change_passwd: 次回ログイン時ã«ãƒ‘スワード変更を強制 + notice_new_password_must_be_different: æ–°ã—ã„パスワードã¯ç¾åœ¨ã®ãƒ‘スワードã¨ç•°ãªã‚‹ã‚‚ã®ã§ãªã‘れã°ãªã‚Šã¾ã›ã‚“ + setting_mail_handler_excluded_filenames: 除外ã™ã‚‹æ·»ä»˜ãƒ•ァイルå + text_convert_available: ImageMagickã®convertコマンドãŒåˆ©ç”¨å¯èƒ½ (オプション) diff -r d98d22a98252 -r afce8026aaeb config/locales/ko.yml --- a/config/locales/ko.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/ko.yml Tue Sep 09 09:34:53 2014 +0100 @@ -50,8 +50,8 @@ one: "약 한시간" other: "약 %{count}시간" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 시간" + other: "%{count} 시간" x_days: one: "하루" other: "%{count}ì¼" @@ -176,6 +176,7 @@ not_same_project: "는 ê°™ì€ í”„ë¡œì íŠ¸ì— ì†í•´ 있지 않습니다" circular_dependency: "ì´ ê´€ê³„ëŠ” 순환 ì˜ì¡´ê´€ê³„를 만들 수 있습니다" cant_link_an_issue_with_a_descendant: "ì¼ê°ì€ 하위 ì¼ê°ê³¼ ì—°ê²°í•  수 없습니다." + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: ì„ íƒí•˜ì„¸ìš” @@ -208,7 +209,7 @@ notice_not_authorized: ì´ íŽ˜ì´ì§€ì— 접근할 ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤. notice_email_sent: "%{value}님ì—게 ë©”ì¼ì´ 발송ë˜ì—ˆìŠµë‹ˆë‹¤." notice_email_error: "ë©”ì¼ì„ 전송하는 ê³¼ì •ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. (%{value})" - notice_feeds_access_key_reseted: RSSì— ì ‘ê·¼ê°€ëŠ¥í•œ 열쇠(key)ê°€ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. + notice_feeds_access_key_reseted: Atomì— ì ‘ê·¼ê°€ëŠ¥í•œ 열쇠(key)ê°€ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. notice_failed_to_save_issues: "ì €ìž¥ì— ì‹¤íŒ¨í•˜ì˜€ìŠµë‹ˆë‹¤: 실패 %{count}(ì„ íƒ %{total}): %{ids}." notice_no_issue_selected: "ì¼ê°ì´ ì„ íƒë˜ì§€ 않았습니다. 수정하기 ì›í•˜ëŠ” ì¼ê°ì„ ì„ íƒí•˜ì„¸ìš”" notice_account_pending: "ê³„ì •ì´ ë§Œë“¤ì–´ì¡Œìœ¼ë©° ê´€ë¦¬ìž ìŠ¹ì¸ ëŒ€ê¸°ì¤‘ìž…ë‹ˆë‹¤." @@ -231,15 +232,13 @@ mail_body_account_information: 계정 ì •ë³´ mail_subject_account_activation_request: "%{value} 계정 활성화 요청" mail_body_account_activation_request: "새 사용ìž(%{value})ê°€ 등ë¡ë˜ì—ˆìŠµë‹ˆë‹¤. 관리ìžë‹˜ì˜ 승ì¸ì„ 기다리고 있습니다.:" - mail_body_reminder: "ë‹¹ì‹ ì´ ë§¡ê³  있는 ì¼ê° %{count}ê°œì˜ ì™„ë£Œ ê¸°í•œì´ %{days}ì¼ í›„ 입니다." + mail_body_reminder: "ë‹¹ì‹ ì´ ë§¡ê³  있는 ì¼ê° %{count}ê°œì˜ ì™„ë£Œê¸°í•œì´ %{days}ì¼ í›„ 입니다." mail_subject_reminder: "ë‚´ì¼ì´ ë§Œê¸°ì¸ ì¼ê° %{count}ê°œ (%{days})" mail_subject_wiki_content_added: "위키페ì´ì§€ '%{id}'ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." mail_subject_wiki_content_updated: "'위키페ì´ì§€ %{id}'ì´(ê°€) 수정ë˜ì—ˆìŠµë‹ˆë‹¤." mail_body_wiki_content_added: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 추가하였습니다." mail_body_wiki_content_updated: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 수정하였습니다." - gui_validation_error: ì—러 - gui_validation_error_plural: "%{count}ê°œ ì—러" field_name: ì´ë¦„ field_description: 설명 @@ -271,7 +270,7 @@ field_is_default: 기본값 field_tracker: 유형 field_subject: 제목 - field_due_date: 완료 기한 + field_due_date: 완료기한 field_assigned_to: ë‹´ë‹¹ìž field_priority: 우선순위 field_fixed_version: 목표버전 @@ -326,7 +325,7 @@ field_comments_sorting: 댓글 ì •ë ¬ field_parent_title: ìƒìœ„ 제목 field_editable: 편집가능 - field_watcher: ì¼ê°ì§€í‚´ì´ + field_watcher: ì¼ê°ê´€ëžŒìž field_identity_url: OpenID URL field_content: ë‚´ìš© field_group_by: 결과를 묶어 보여줄 기준 @@ -392,15 +391,14 @@ permission_save_queries: ê²€ìƒ‰ì–‘ì‹ ì €ìž¥ permission_view_gantt: Gantt차트 보기 permission_view_calendar: 달력 보기 - permission_view_issue_watchers: ì¼ê°ì§€í‚´ì´ 보기 - permission_add_issue_watchers: ì¼ê°ì§€í‚´ì´ 추가 + permission_view_issue_watchers: ì¼ê°ê´€ëžŒìž 보기 + permission_add_issue_watchers: ì¼ê°ê´€ëžŒìž 추가 permission_log_time: 작업시간 ê¸°ë¡ permission_view_time_entries: 시간입력 보기 permission_edit_time_entries: 시간입력 편집 permission_edit_own_time_entries: ë‚´ 시간입력 편집 permission_manage_news: 뉴스 관리 permission_comment_news: ë‰´ìŠ¤ì— ëŒ“ê¸€ë‹¬ê¸° - permission_manage_documents: 문서 관리 permission_view_documents: 문서 보기 permission_manage_files: 파ì¼ê´€ë¦¬ permission_view_files: 파ì¼ë³´ê¸° @@ -460,9 +458,9 @@ label_role_plural: ì—­í•  label_role_new: 새 ì—­í•  label_role_and_permissions: ì—­í•  ë° ê¶Œí•œ - label_member: ë‹´ë‹¹ìž - label_member_new: 새 ë‹´ë‹¹ìž - label_member_plural: ë‹´ë‹¹ìž + label_member: êµ¬ì„±ì› + label_member_new: 새 êµ¬ì„±ì› + label_member_plural: êµ¬ì„±ì› label_tracker: ì¼ê° 유형 label_tracker_plural: ì¼ê° 유형 label_tracker_new: 새 ì¼ê° 유형 @@ -519,8 +517,6 @@ label_text: í…스트 label_attribute: ì†ì„± label_attribute_plural: ì†ì„± - label_download: "%{count}회 다운로드" - label_download_plural: "%{count}회 다운로드" label_no_data: 표시할 ë°ì´í„°ê°€ 없습니다. label_change_status: ìƒíƒœ 변경 label_history: ì´ë ¥ @@ -622,8 +618,6 @@ label_repository: 저장소 label_repository_plural: 저장소 label_browse: 저장소 둘러보기 - label_modification: "%{count} 변경" - label_modification_plural: "%{count} 변경" label_revision: ê°œì •íŒ label_revision_plural: ê°œì •íŒ label_associated_revisions: ê´€ë ¨ëœ ê°œì •íŒë“¤ @@ -668,8 +662,8 @@ label_commits_per_month: 월별 커밋 ë‚´ì—­ label_commits_per_author: ì €ìžë³„ 커밋 ë‚´ì—­ label_view_diff: ì°¨ì´ì  보기 - label_diff_inline: 한줄로 - label_diff_side_by_side: ë‘줄로 + label_diff_inline: ë‘줄로 + label_diff_side_by_side: 한줄로 label_options: 옵션 label_copy_workflow_from: 업무í름 복사하기 label_permissions_report: 권한 보고서 @@ -749,7 +743,7 @@ label_planning: 프로ì íŠ¸ê³„íš label_incoming_emails: 수신 ë©”ì¼ label_generate_key: 키 ìƒì„± - label_issue_watchers: ì¼ê°ì§€í‚´ì´ + label_issue_watchers: ì¼ê°ê´€ëžŒìž label_example: 예 label_display: í‘œì‹œë°©ì‹ label_sort: ì •ë ¬ @@ -795,7 +789,7 @@ button_change_password: 비밀번호 바꾸기 button_copy: 복사 button_annotate: ì´ë ¥í•´ì„¤ - button_update: 수정 + button_update: ì—…ë°ì´íЏ button_configure: 설정 button_quote: 댓글달기 @@ -894,7 +888,7 @@ text_journal_added: "%{label}ì— %{value}ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." field_active: 사용중 enumeration_system_activity: 시스템 작업 - permission_delete_issue_watchers: ì¼ê°ì§€í‚´ì´ 지우기 + permission_delete_issue_watchers: ì¼ê°ê´€ëžŒìž 지우기 version_status_closed: 닫힘 version_status_locked: ìž ê¹€ version_status_open: ì§„í–‰ @@ -929,11 +923,11 @@ label_revision_id: ê°œì •íŒ %{value} label_api_access_key: API 접근키 label_api_access_key_created_on: API 접근키가 %{value} ì „ì— ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. - label_feeds_access_key: RSS 접근키 + label_feeds_access_key: Atom 접근키 notice_api_access_key_reseted: API 접근키가 초기화ë˜ì—ˆìŠµë‹ˆë‹¤. setting_rest_api_enabled: REST 웹서비스 활성화 label_missing_api_access_key: API 접근키가 없습니다. - label_missing_feeds_access_key: RSS 접근키가 없습니다. + label_missing_feeds_access_key: Atom 접근키가 없습니다. button_show: 보기 text_line_separated: 여러 ê°’ì´ í—ˆìš©ë¨(ê°’ 마다 한 줄씩) setting_mail_handler_body_delimiters: ë©”ì¼ ë³¸ë¬¸ êµ¬ë¶„ìž @@ -1010,7 +1004,7 @@ permission_set_own_issues_private: "ìžì‹ ì˜ ì¼ê°ì„ 공개나 비공개로 설정" field_is_private: "비공개" permission_set_issues_private: "ì¼ê°ì„ 공개나 비공개로 설정" - label_issues_visibility_public: "모든 비공개 ì¼ê°" + label_issues_visibility_public: "비공개 ì¼ê° 제외" text_issues_destroy_descendants_confirmation: "%{count} ê°œì˜ í•˜ìœ„ ì¼ê°ì„ 삭제할 것입니다." field_commit_logs_encoding: "커밋(commit) ê¸°ë¡ ì¸ì½”딩" field_scm_path_encoding: "경로 ì¸ì½”딩" @@ -1077,7 +1071,7 @@ text_issue_conflict_resolution_cancel: "ë³€ê²½ë‚´ìš©ì„ ë˜ëŒë¦¬ê³  다시 표시 %{link}" permission_manage_related_issues: ì—°ê²°ëœ ì¼ê° 관리 field_auth_source_ldap_filter: LDAP í•„í„° - label_search_for_watchers: 추가할 ì¼ê°ì§€í‚´ì´ 검색 + label_search_for_watchers: 추가할 ì¼ê°ê´€ëžŒìž 검색 notice_account_deleted: ë‹¹ì‹ ì˜ ê³„ì •ì´ ì™„ì „ížˆ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. setting_unsubscribe: 사용ìžë“¤ì´ ìžì‹ ì˜ ê³„ì •ì„ ì‚­ì œí† ë¡ í—ˆìš© button_delete_my_account: ë‚˜ì˜ ê³„ì • ì‚­ì œ @@ -1120,7 +1114,7 @@ field_private_notes: 비공개 ë§ê¸€ permission_view_private_notes: 비공개 ë§ê¸€ 보기 permission_set_notes_private: ë§ê¸€ì„ 비공개로 설정 - label_no_issues_in_project: ë‹¤ìŒ í”„ë¡œì íЏ ë‚´ì—서 해당 ì¼ê° ì—†ìŒ + label_no_issues_in_project: ë‹¤ìŒ í”„ë¡œì íŠ¸ì˜ ì¼ê° 제외 label_any: ëª¨ë‘ label_last_n_weeks: 최근 %{count} 주 setting_cross_project_subtasks: 다른 프로ì íŠ¸ì˜ ì¼ê°ì„ ìƒìœ„ ì¼ê°ìœ¼ë¡œ 지정하는 ê²ƒì„ í—ˆìš© @@ -1132,3 +1126,28 @@ setting_non_working_week_days: ë¹„ê·¼ë¬´ì¼ (non-working days) label_in_the_next_days: ë‹¤ìŒ label_in_the_past_days: 지난 + label_attribute_of_user: "사용ìžì˜ %{name}" + text_turning_multiple_off: 복수선íƒì„ 비활성화하면, í•˜ë‚˜ì˜ ê°’ì„ ì œì™¸í•œ 나머지 ê°’ë“¤ì´ ì§€ì›Œì§‘ë‹ˆë‹¤. + label_attribute_of_issue: "ì¼ê°ì˜ %{name}" + permission_add_documents: 문서 추가 + permission_edit_documents: 문서 편집 + permission_delete_documents: 문서 ì‚­ì œ + label_gantt_progress_line: Progress line + setting_jsonp_enabled: JSONP 허용 + field_inherit_members: ìƒìœ„ 프로ì íŠ¸ë¡œë¶€í„° 구성ì›ì„ ìƒì† + field_closed_on: ì™„ë£Œì¼ + field_generate_password: Generate password + setting_default_projects_tracker_ids: 새 프로ì íŠ¸ì— ê¸°ë³¸ì ìœ¼ë¡œ 추가할 ì¼ê° 유형 + label_total_time: 합계 + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/lt.yml --- a/config/locales/lt.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/lt.yml Tue Sep 09 09:34:53 2014 +0100 @@ -1,8 +1,10 @@ # Lithuanian translations for Ruby on Rails # by Laurynas Butkus (laurynas.butkus@gmail.com) # Redmine translation by Gediminas Muižis gediminas.muizis@gmail.com -# and Sergej Jegorov sergej.jegorov@gmail.com -# and Gytis Gurklys gytis.gurklys@gmail.com +# and Sergej Jegorov sergej.jegorov@gmail.com +# and Gytis Gurklys gytis.gurklys@gmail.com +# and Andrius KriuÄkovas andrius.kriuckovas@gmail.com + lt: direction: ltr date: @@ -186,6 +188,7 @@ not_same_project: "nepriklauso tam paÄiam projektui" circular_dependency: "Å is ryÅ¡ys sukurtų ciklinÄ™ priklausomybÄ™" cant_link_an_issue_with_a_descendant: "Darbas negali bÅ«ti susietas su viena iÅ¡ savo darbo dalių" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: PraÅ¡om parinkti @@ -219,21 +222,21 @@ notice_not_authorized_archived_project: Projektas, kurį bandote atidaryti, buvo suarchyvuotas. notice_email_sent: "LaiÅ¡kas iÅ¡siųstas %{value}" notice_email_error: "LaiÅ¡ko siuntimo metu įvyko klaida (%{value})" - notice_feeds_access_key_reseted: JÅ«sų RSS raktas buvo atnaujintas. + notice_feeds_access_key_reseted: JÅ«sų Atom raktas buvo atnaujintas. notice_api_access_key_reseted: JÅ«sų API prieigos raktas buvo atnaujintas. notice_failed_to_save_issues: "Nepavyko iÅ¡saugoti %{count} problemos(ų) iÅ¡ %{total} pasirinkto: %{ids}." notice_failed_to_save_members: "Nepavyko iÅ¡saugoti nario(ių): %{errors}." notice_no_issue_selected: "Nepasirinkta nÄ— viena problema! PraÅ¡om pažymÄ—ti problemÄ…, kuriÄ… norite redaguoti." notice_account_pending: "JÅ«sų paskyra buvo sukurta ir dabar laukiama administratoriaus patvirtinimo." - notice_default_data_loaded: Numatytoji konfiguracija sÄ—kmingai užkrauta. + notice_default_data_loaded: Numatytoji konfigÅ«racija sÄ—kmingai įkrauta. notice_unable_delete_version: Neįmanoma panaikinti versijÄ…. notice_unable_delete_time_entry: Neįmano iÅ¡trinti laiko žurnalo įrašą. - notice_issue_done_ratios_updated: Issue done ratios updated. - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + notice_issue_done_ratios_updated: Problemos baigtumo rodikliai atnaujinti. + notice_gantt_chart_truncated: Grafikas buvo sutrumpintas, kadangi jis virÅ¡ija maksimalų (%{max}) leistinų atvaizduoti elementų kiekį notice_issue_successful_create: Darbas %{id} sukurtas. - error_can_t_load_default_data: "Numatytoji konfiguracija negali bÅ«ti užkrauta: %{value}" - error_scm_not_found: "Duomenys ir/ar pakeitimai saugykloje(repozitorojoje) neegzistuoja." + error_can_t_load_default_data: "Numatytoji konfigÅ«racija negali bÅ«ti užkrauta: %{value}" + error_scm_not_found: "Duomenys ir/ar pakeitimai saugykloje (repozitorijoje) neegzistuoja." error_scm_command_failed: "Ä®vyko klaida jungiantis prie saugyklos: %{value}" error_scm_annotate: "Ä®raÅ¡as neegzistuoja arba negalima jo atvaizduoti." error_scm_annotate_big_text_file: "Ä®raÅ¡o negalima atvaizduoti, nes jis virÅ¡ija maksimalų tekstinio failo dydį." @@ -241,17 +244,17 @@ error_no_tracker_in_project: 'Joks pÄ—dsekys nesusietas su Å¡iuo projektu. PraÅ¡om patikrinti Projekto nustatymus.' error_no_default_issue_status: Nenustatyta numatytoji darbų bÅ«sena. PraÅ¡ome patikrinti konfigÅ«ravimÄ… ("Administravimas -> Darbų bÅ«senos"). error_can_not_delete_custom_field: Negalima iÅ¡trinti kliento lauko - error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted." - error_can_not_remove_role: "This role is in use and cannot be deleted." + error_can_not_delete_tracker: "Å is pÄ—dsekys turi įraÅ¡us ir todÄ—l negali bÅ«ti iÅ¡trintas." + error_can_not_remove_role: "Å i rolÄ— yra naudojama ir negali bÅ«ti iÅ¡trinta." error_can_not_reopen_issue_on_closed_version: Uždarytai versijai priskirtas darbas negali bÅ«ti atnaujintas. error_can_not_archive_project: Å io projekto negalima suarchyvuoti - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + error_issue_done_ratios_not_updated: "Ä®raÅ¡o baigtumo rodikliai nebuvo atnaujinti. " + error_workflow_copy_source: 'PraÅ¡ome pasirinkti pirminį Å¡altinio seklį arba rolÄ™' + error_workflow_copy_target: 'PraÅ¡ome pasirinkti galutinį paskirties seklį(-ius) arba rolÄ™(-s)' error_unable_delete_issue_status: 'Negalima iÅ¡trinti darbo statuso' error_unable_to_connect: Negalima prisijungti (%{value}) - error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})" - warning_attachments_not_saved: "%{count} byla(ų) negali bÅ«ti iÅ¡saugota." + error_attachment_too_big: "Å i byla negali bÅ«ti įkelta, nes virÅ¡ija maksimaliÄ… (%{max_size}) leistinÄ… bylos apimtį" + warning_attachments_not_saved: "%{count} byla(-ų) negali bÅ«ti iÅ¡saugota." mail_subject_lost_password: "JÅ«sų %{value} slaptažodis" mail_body_lost_password: 'NorÄ—dami pakeisti slaptažodį, spauskite nuorodÄ…:' @@ -268,8 +271,6 @@ mail_subject_wiki_content_updated: "'%{id}' atnaujintas wiki puslapis" mail_body_wiki_content_updated: "'%{id}' wiki puslapį atnaujino %{author}." - gui_validation_error: 1 klaida - gui_validation_error_plural: "%{count} klaidų(os)" field_name: Pavadinimas field_description: ApraÅ¡as @@ -339,11 +340,11 @@ field_comments: Komentaras field_url: URL field_start_page: Pradžios puslapis - field_subproject: Subprojektas + field_subproject: Sub-projektas field_hours: valandos field_activity: Veikla field_spent_on: Data - field_identifier: Identifikuotojas + field_identifier: Identifikatorius field_is_filter: Panaudotas kaip filtras field_issue_to: SusijÄ™s darbas field_delay: Užlaikymas @@ -371,10 +372,10 @@ field_warn_on_leaving_unsaved: "Ä®spÄ—ti mane, kai paliekamas puslapis su neiÅ¡saugotu tekstu" field_issues_visibility: Darbų matomumas field_is_private: Privatus - field_commit_logs_encoding: Commit praneÅ¡imų koduotÄ— - field_scm_path_encoding: Kelio koduotÄ— + field_commit_logs_encoding: Commit žurnalų koduotÄ— + field_scm_path_encoding: SCM kelio koduotÄ— field_path_to_repository: Saugyklos kelias - field_root_directory: Root directorija + field_root_directory: Å akninis katalogas field_cvsroot: CVSROOT field_cvs_module: Modulis @@ -383,8 +384,8 @@ setting_welcome_text: Pasveikinimas setting_default_language: Numatytoji kalba setting_login_required: Reikalingas autentiÅ¡kumo nustatymas - setting_self_registration: Saviregistracija - setting_attachment_max_size: Priedo maks. dydis + setting_self_registration: Savi-registracija + setting_attachment_max_size: Priedo maksimalus dydis setting_issues_export_limit: Darbų eksportavimo riba setting_mail_from: IÅ¡leidimo elektroninio paÅ¡to adresas setting_bcc_recipients: Akli tikslios kopijos gavÄ—jai (bcc) @@ -401,16 +402,16 @@ setting_autologin: Automatinis prisijungimas setting_date_format: Datos formatas setting_time_format: Laiko formatas - setting_cross_project_issue_relations: Leisti tarprojektinius darbų ryÅ¡ius + setting_cross_project_issue_relations: Leisti tarp-projektinius darbų ryÅ¡ius setting_issue_list_default_columns: Numatytosios skiltys darbų sÄ…raÅ¡e setting_repositories_encodings: PridÄ—tų failų ir saugyklų Å¡ifravimas setting_emails_header: LaiÅ¡ko antraÅ¡tÄ— - setting_emails_footer: LaiÅ¡ko poraÅ¡tÄ— + setting_emails_footer: LaiÅ¡ko paraÅ¡tÄ— setting_protocol: Protokolas - setting_per_page_options: Ä®rašų puslapyje nustatymas + setting_per_page_options: Ä®rašų puslapyje nustatymas setting_user_format: Vartotojo atvaizdavimo formatas setting_activity_days_default: Atvaizduojamos dienos projekto veikloje - setting_display_subprojects_issues: Pagal nutylÄ—jimÄ… rodyti subprojektų darbus pagrindiniame projekte + setting_display_subprojects_issues: Pagal nutylÄ—jimÄ… rodyti sub-projektų darbus pagrindiniame projekte setting_enabled_scm: Ä®galinti SCM setting_mail_handler_body_delimiters: "Trumpinti laiÅ¡kus po vienos iÅ¡ Å¡ių eiluÄių" setting_mail_handler_api_enabled: Ä®galinti WS įeinantiems laiÅ¡kams @@ -420,7 +421,7 @@ setting_gravatar_default: Gravatar paveiksliukas pagal nutylÄ—jimÄ… setting_diff_max_lines_displayed: Maksimalus rodomas pakeitimų eiluÄių skaiÄius setting_file_max_size_displayed: Maksimalus testinių failų dydis rodomas vienoje eilutÄ—je - setting_repository_log_display_limit: Maksimalus revizijų skaiÄius rodomas failo loge + setting_repository_log_display_limit: Maksimalus revizijų skaiÄius rodomas žurnale setting_openid: Leisti OpenID prisijungimÄ… ir registracijÄ… setting_password_min_length: Minimalus slaptažodžio ilgis setting_new_project_user_role_id: Vartotojo vaidmuo, suteikiamas ne administratoriui, kuris sukuria projektÄ… @@ -429,17 +430,17 @@ setting_issue_done_ratio_issue_field: Naudoti darbo laukÄ… setting_issue_done_ratio_issue_status: Naudoti darbo statusÄ… setting_start_of_week: SavaitÄ—s pradžios diena - setting_rest_api_enabled: Ä®jungti REST web service + setting_rest_api_enabled: Ä®jungti REST tinklo servisÄ… setting_cache_formatted_text: PaslÄ—pti formatuotÄ… tekstÄ… setting_default_notification_option: Numatytosios praneÅ¡imų nuostatos setting_commit_logtime_enabled: Ä®jungti laiko registravimÄ… - setting_commit_logtime_activity_id: Activity for logged time + setting_commit_logtime_activity_id: Laiko įrašų veikla setting_gantt_items_limit: Maksimalus rodmenų skaiÄius rodomas Gantt'o grafike setting_issue_group_assignment: Leisti darbo priskirimÄ… grupÄ—ms setting_default_issue_start_date_to_creation_date: Naudoti dabartinÄ™ datÄ… kaip naujų darbų pradžios datÄ… permission_add_project: Sukurti projektÄ… - permission_add_subprojects: Kurti subprojektus + permission_add_subprojects: Kurti sub-projektus permission_edit_project: Taisyti projektÄ… permission_select_project_modules: Parinkti projekto modulius permission_manage_members: Valdyti narius @@ -467,10 +468,9 @@ permission_log_time: Regsitruoti dirbtÄ… laikÄ… permission_view_time_entries: Matyti dirbtÄ… laikÄ… permission_edit_time_entries: Redaguoti laiko įraÅ¡us - permission_edit_own_time_entries: Redguoti savo laiko įraÅ¡us + permission_edit_own_time_entries: Redaguoti savo laiko įraÅ¡us permission_manage_news: Valdyti naujienas permission_comment_news: Komentuoti naujienas - permission_manage_documents: Valdyti dokumentus permission_view_documents: Matyti dokumentus permission_manage_files: Valdyti failus permission_view_files: Matyti failus @@ -527,7 +527,7 @@ label_issues_by: "Darbai pagal %{value}" label_issue_added: Darbas pridÄ—tas label_issue_updated: Darbas atnaujintas - label_issue_note_added: Pastaba pridÄ—ta + label_issue_note_added: Pastaba pridÄ—ta label_issue_status_updated: Statusas atnaujintas label_issue_priority_updated: Prioritetas atnaujintas label_document: Dokumentas @@ -588,9 +588,9 @@ label_auth_source: AutentiÅ¡kumo nustatymo bÅ«das label_auth_source_new: Naujas autentiÅ¡kumo nustatymo bÅ«das label_auth_source_plural: AutentiÅ¡kumo nustatymo bÅ«dai - label_subproject_plural: Subprojektai - label_subproject_new: Naujas subprojektas - label_and_its_subprojects: "%{value} projektas ir jo subprojektai" + label_subproject_plural: Sub-projektai + label_subproject_new: Naujas sub-projektas + label_and_its_subprojects: "%{value} projektas ir jo sub-projektai" label_min_max_length: Min - Maks ilgis label_list: SÄ…raÅ¡as label_date: Data @@ -601,12 +601,10 @@ label_text: Ilgas tekstas label_attribute: Požymis label_attribute_plural: Požymiai - label_download: "%{count} persiuntimas" - label_download_plural: "%{count} persiuntimai" label_no_data: NÄ—ra kÄ… atvaizduoti label_change_status: Pakeitimo bÅ«sena label_history: Istorija - label_attachment: Filas + label_attachment: Failas label_attachment_new: Naujas failas label_attachment_delete: PaÅ¡alinkite failÄ… label_attachment_plural: Failai @@ -663,7 +661,7 @@ label_months_from: mÄ—nesiai nuo label_gantt: Gantt label_internal: Vidinis - label_last_changes: "paskutiniai %{count} pokyÄiai(ių)" + label_last_changes: "paskutiniai %{count} pokyÄiai(-ių)" label_change_view_all: PeržiÅ«rÄ—ti visus pakeitimus label_personalize_page: Suasmeninti šį puslapį label_comment: Komentaras @@ -695,7 +693,7 @@ label_this_week: Å¡iÄ… savaitÄ™ label_last_week: praeita savaitÄ— label_last_n_days: "paskutinių %{count} dienų" - label_this_month: Å¡is menuo + label_this_month: Å¡is mÄ—nuo label_last_month: praeitas mÄ—nuo label_this_year: Å¡iemet label_date_range: Dienų diapazonas @@ -704,12 +702,10 @@ label_ago: prieÅ¡ label_contains: turi label_not_contains: neturi - label_day_plural: dienų(os) + label_day_plural: dienų(-os) label_repository: Saugykla label_repository_plural: Saugyklos label_browse: NarÅ¡yti - label_modification: "%{count} pakeitimas" - label_modification_plural: "%{count} pakeitimai" label_branch: Å aka label_tag: Tag label_revision: Revizija @@ -723,8 +719,8 @@ label_deleted: paÅ¡alintas label_latest_revision: PaskutinÄ— revizija label_latest_revision_plural: PaskutinÄ—s revizijos - label_view_revisions: PežiÅ«rÄ—ti revizijas - label_view_all_revisions: PežiÅ«rÄ—ti visas revizijas + label_view_revisions: PeržiÅ«rÄ—ti revizijas + label_view_all_revisions: PeržiÅ«rÄ—ti visas revizijas label_max_size: Maksimalus dydis label_sort_highest: Perkelti į viršūnÄ™ label_sort_higher: Perkelti į viršų @@ -752,7 +748,7 @@ label_spent_time: Dirbtas laikas label_overall_spent_time: Visas dirbtas laikas label_f_hour: "%{value} valanda" - label_f_hour_plural: "%{value} valandų(os)" + label_f_hour_plural: "%{value} valandų(-os)" label_time_tracking: Laiko sekimas label_change_plural: Pakeitimai label_statistics: Statistika @@ -776,14 +772,14 @@ label_duplicated_by: dubliuojasi su label_blocks: blokuoja label_blocked_by: blokuojamas - label_precedes: ankstesnÄ—(is) + label_precedes: ankstesnÄ—(-is) label_follows: seka label_end_to_start: užbaigti, kad pradÄ—ti label_end_to_end: užbaigti, kad pabaigti label_start_to_start: pradÄ—kite pradÄ—ti label_start_to_end: pradÄ—kite užbaigti label_stay_logged_in: Likti prisijungus - label_disabled: iÅ¡jungta(as) + label_disabled: iÅ¡jungta(-as) label_show_completed_versions: Rodyti užbaigtas versijas label_me: aÅ¡ label_board: Forumas @@ -806,9 +802,9 @@ label_language_based: Pagrįsta vartotojo kalba label_sort_by: "Rūšiuoti pagal %{value}" label_send_test_email: Nusiųsti bandomÄ…jį laiÅ¡kÄ… - label_feeds_access_key: RSS prieigos raktas - label_missing_feeds_access_key: TRÅ«ksta RSS prieigos rakto - label_feeds_access_key_created_on: "RSS prieigos raktas sukurtas prieÅ¡ %{value}" + label_feeds_access_key: Atom prieigos raktas + label_missing_feeds_access_key: TrÅ«ksta Atom prieigos rakto + label_feeds_access_key_created_on: "Atom prieigos raktas sukurtas prieÅ¡ %{value}" label_module_plural: Moduliai label_added_time_by: "PridÄ—jo %{author} prieÅ¡ %{age}" label_updated_time_by: "Atnaujino %{author} prieÅ¡ %{age}" @@ -818,17 +814,17 @@ label_changeset_plural: Pakeitimų rinkiniai label_default_columns: Numatytieji stulpeliai label_no_change_option: (Jokio pakeitimo) - label_bulk_edit_selected_issues: MasiÅ¡kai readguoti pasirinktus darbus + label_bulk_edit_selected_issues: MasiÅ¡kai redaguoti pasirinktus darbus label_bulk_edit_selected_time_entries: MasiÅ¡kai redaguotumÄ—te pasirinktus laiko įraÅ¡us label_theme: Tema - label_default: Numatyta(as) - label_search_titles_only: IeÅ¡koti tiktai pavadinimų + label_default: Numatyta(-as) + label_search_titles_only: IeÅ¡koti tiktai pavadinimų label_user_mail_option_all: "Bet kokiam įvykiui visuose mano projektuose" label_user_mail_option_selected: "Bet kokiam įvykiui tiktai pasirinktuose projektuose ..." label_user_mail_option_none: "NÄ—ra įvykių" - label_user_mail_option_only_my_events: "Tiktai įvikiai, kuriuos stebiu arba esu įtrauktas" + label_user_mail_option_only_my_events: "Tiktai įvykiai, kuriuos stebiu arba esu įtrauktas" label_user_mail_option_only_assigned: "Tiktai įvykiai, kuriems esu priskirtas" - label_user_mail_option_only_owner: "Tiktai įvikiai, kurių Å¡eikininkas esu" + label_user_mail_option_only_owner: "Tiktai įvykiai, kurių Å¡eimininkas esu aÅ¡" label_user_mail_no_self_notified: "Nenoriu bÅ«ti informuotas apie pakeitimus, kuriuos pats atlieku" label_registration_activation_by_email: paskyros aktyvacija per e-paÅ¡tÄ… label_registration_manual_activation: rankinÄ— paskyros aktyvacija @@ -836,7 +832,7 @@ label_display_per_page: "%{value} įrašų puslapyje" label_age: Amžius label_change_properties: Pakeisti nustatymus - label_general: Bendri(as) + label_general: Bendri(-as) label_more: Daugiau label_scm: SCM label_plugins: Ä®skiepiai @@ -864,7 +860,7 @@ label_group_new: Nauja grupÄ— label_time_entry_plural: Sprendimo laikas label_version_sharing_none: Nesidalinama - label_version_sharing_descendants: Su subprojektais + label_version_sharing_descendants: Su sub-projektais label_version_sharing_hierarchy: Su projekto hierarchija label_version_sharing_tree: Su projekto medžiu label_version_sharing_system: Su visais projektais @@ -885,8 +881,8 @@ label_issues_visibility_public: Visi vieÅ¡i darbai label_issues_visibility_own: Darbai, sukurti vartotojo arba jam priskirti label_git_report_last_commit: Nurodyti paskutinį failų ir katalogų pakeitimÄ… - label_parent_revision: Parent - label_child_revision: Child + label_parent_revision: PirminÄ— revizija + label_child_revision: Sekanti revizija label_export_options: "%{export_format} eksportavimo nustatymai" button_login: Registruotis @@ -952,7 +948,7 @@ text_regexp_info: pvz. ^[A-Z0-9]+$ text_min_max_length_info: 0 reiÅ¡kia jokių apribojimų text_project_destroy_confirmation: Ar esate įsitikinÄ™s, kad norite paÅ¡alinti šį projektÄ… ir visus susijusius duomenis? - text_subprojects_destroy_warning: "Å is(ie) subprojektas(ai): %{value} taip pat bus iÅ¡trintas(i)." + text_subprojects_destroy_warning: "Å is(-ie) sub-projektas(-ai): %{value} taip pat bus iÅ¡trintas(-i)." text_workflow_edit: IÅ¡rinkite vaidmenį ir pÄ—dsekį, kad redaguotumÄ—te darbų eigÄ… text_are_you_sure: Ar esate įsitikinÄ™s? text_journal_changed: "%{label} pakeistas(a) iÅ¡ %{old} į %{new}" @@ -979,13 +975,13 @@ text_issue_category_destroy_assignments: PaÅ¡alinti kategorijos užduotis text_issue_category_reassign_to: IÅ¡ naujo priskirti darbus Å¡iai kategorijai text_user_mail_option: "NeiÅ¡rinktiems projektams, jÅ«s gausite tiktai praneÅ¡imus apie tuos įvykius, kuriuos jÅ«s stebite, arba į kuriuos esate įtrauktas (pvz. darbai, kurių autorius jÅ«s esate ar esate priskirtas)." - text_no_configuration_data: "Vaidmenys, pÄ—dsekiai, darbų bÅ«senos ir darbų eiga dar nebuvo konfigÅ«ruoti.\nGriežtai rekomenduojam užkrauti numatytÄ…jÄ… (default) konfiguracijÄ…. Užkrovus, galÄ—site jÄ… modifikuoti." - text_load_default_configuration: Užkrauti numatytÄ…j konfiguracijÄ… + text_no_configuration_data: "Vaidmenys, pÄ—dsekiai, darbų bÅ«senos ir darbų eiga dar nebuvo konfigÅ«ruoti.\nGriežtai rekomenduojam užkrauti numatytÄ…jÄ… (default) konfigÅ«racijÄ…. Užkrovus, galÄ—site jÄ… modifikuoti." + text_load_default_configuration: Užkrauti numatytÄ…jÄ… konfigÅ«racijÄ… text_status_changed_by_changeset: "Pakeista %{value} revizijoje." text_time_logged_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Ar jÅ«s tikrai norite sunaikinti pažymÄ—tÄ…(us) darbÄ…(us)?' - text_issues_destroy_descendants_confirmation: Taip pat bus iÅ¡trinta(os) %{count} darbo dalis(ys). - text_time_entries_destroy_confirmation: 'Ar jÅ«s tikrai norite iÅ¡trinti pasirinktÄ…(us) laiko įrašą(us)?' + text_issues_destroy_confirmation: 'Ar jÅ«s tikrai norite sunaikinti pažymÄ—tÄ…(-us) darbÄ…(-us)?' + text_issues_destroy_descendants_confirmation: Taip pat bus iÅ¡trinta(-os) %{count} darbo dalis(ys). + text_time_entries_destroy_confirmation: 'Ar jÅ«s tikrai norite iÅ¡trinti pasirinktÄ…(-us) laiko įrašą(-us)?' text_select_project_modules: 'Parinkite modulius, kuriuos norite naudoti Å¡iame projekte:' text_default_administrator_account_changed: Administratoriaus numatytoji paskyra pakeista text_file_repository_writable: Ä® failų saugyklÄ… saugoti galima (RW) @@ -999,25 +995,22 @@ text_enumeration_destroy_question: "%{count} objektai(ų) priskirti Å¡iai reikÅ¡mei." text_enumeration_category_reassign_to: 'Priskirti juos Å¡iai reikÅ¡mei:' text_email_delivery_not_configured: "El.paÅ¡to siuntimas nesukonfigÅ«ruotas, ir perspÄ—jimai neaktyvus.\nSukonfigÅ«ruokite savo SMTP serverį byloje config/configuration.yml ir perleiskite programÄ… norÄ—dami pritaikyti pakeitimus." - text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." - text_repository_usernames_mapping: "Parinkite ar atnaujinkite Redmine vartotojÄ…, kuris paminÄ—tas saugyklos log'e.\nVartotojai, turintys tÄ… patį Redmine ir saugyklos vardÄ… ar el.paÅ¡tÄ… yra automatiÅ¡kai suriÅ¡ti." + text_repository_usernames_mapping: "Parinkite ar atnaujinkite Redmine vartotojÄ…, kuris paminÄ—tas saugyklos žurnale.\nVartotojai, turintys tÄ… patį Redmine ir saugyklos vardÄ… ar el. paÅ¡tÄ… yra automatiÅ¡kai suriÅ¡ti." text_diff_truncated: "... Å is diff'as nukarpytas, nes jis virÅ¡ijo maksimalų rodomų eiluÄių skaiÄių." text_custom_field_possible_values_info: 'Po vienÄ… eilutÄ™ kiekvienai reikÅ¡mei' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" - text_wiki_page_nullify_children: Laikyti child puslapius kaip pagrindinius puslapius - text_wiki_page_destroy_children: "PaÅ¡alinti child puslapius ir jų palikuonis" - text_wiki_page_reassign_children: "Priskirkite iÅ¡ naujo 'child' puslapius Å¡iam pagrindiniam puslapiui" - text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" + text_wiki_page_destroy_question: "Å is puslapis turi %{descendants} susijusių arba iÅ¡vestinių puslapių. KÄ… norÄ—tumÄ—te daryti?" + text_wiki_page_nullify_children: Laikyti susijusius puslapius kaip pagrindinius puslapius + text_wiki_page_destroy_children: "PaÅ¡alinti susijusius puslapius ir jų palikuonis" + text_wiki_page_reassign_children: "Priskirkite iÅ¡ naujo 'susijusius' puslapius Å¡iam pagrindiniam puslapiui" + text_own_membership_delete_confirmation: "JÅ«s esate pasiruošęs panaikinti dalį arba visus leidimus ir po Å¡io pakeitimo galite prarasti Å¡io projekto redagavimo galimybÄ™.\nAr jÅ«s esate įsitikinÄ™s ir tÄ™sti?" text_zoom_in: Priartinti text_zoom_out: Nutolinti text_warn_on_leaving_unsaved: "Dabartinis puslapis turi neiÅ¡saugoto teksto, kuris bus prarastas, jeigu paliksite šį puslapį." text_scm_path_encoding_note: "Numatytasis: UTF-8" - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + text_git_repository_note: Saugykla (repository) yra plika ir vietinÄ— (pvz. /gitrepo, c:\gitrepo) text_mercurial_repository_note: VietinÄ— saugykla (e.g. /hgrepo, c:\hgrepo) text_scm_command: Komanda text_scm_command_version: Versija - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. default_role_manager: Vadovas default_role_developer: Projektuotojas @@ -1043,7 +1036,7 @@ enumeration_issue_priorities: Darbo prioritetai enumeration_doc_categories: Dokumento kategorijos - enumeration_activities: Veiklos (laiko sekimas) + enumeration_activities: Veiklos (laiko) sekimas enumeration_system_activity: Sistemos veikla description_filter: Filtras @@ -1079,7 +1072,7 @@ label_completed_versions: Užbaigtos versijos text_project_identifier_info: Leidžiamos tik mažosios raidÄ—s (a-z), skaitmenys, brÅ«kÅ¡neliai ir pabraukimo simboliai.
    KartÄ… iÅ¡saugojus pakeitimai negalimi field_multiple: Keletas reikÅ¡mių - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + setting_commit_cross_project_ref: Leisti visų kitų projektų įraÅ¡us susieti nuorodomis ir sutaisyti text_issue_conflict_resolution_add_notes: IÅ¡saugoti mano žinutÄ™ ir atmesti likusius mano pataisymus text_issue_conflict_resolution_overwrite: IÅ¡saugoti mano pakeitimus (ankstesnių pakeitimų žinutÄ—s bus iÅ¡saugotos, taÄiau kai kurie pakeitimai bus perraÅ¡yti) notice_issue_update_conflict: Darbas buvo pakoreguotas kito vartotojo kol jÅ«s atlikote pakeitimus. @@ -1133,7 +1126,7 @@ label_any: visi label_last_n_weeks: prieÅ¡ %{count} sav. setting_cross_project_subtasks: Leisti susieti skirtingų projektų užduoÄių dalis - label_cross_project_descendants: Su subprojektais + label_cross_project_descendants: Su sub-projektais label_cross_project_tree: Su projekto medžiu label_cross_project_hierarchy: Su projekto hierarchija label_cross_project_system: Su visais projektais @@ -1141,3 +1134,30 @@ setting_non_working_week_days: Nedarbo dienos label_in_the_next_days: per ateinanÄias label_in_the_past_days: per paskutines + label_attribute_of_user: Vartotojo %{name} + text_turning_multiple_off: Jei jÅ«s iÅ¡jungsite kelių reikÅ¡mių pasirinkimÄ…, visos iÅ¡vardintos reikÅ¡mÄ—s bus paÅ¡alintos ir palikta tik viena reikÅ¡mÄ— kiekvienam laukui. + label_attribute_of_issue: Ä®raÅ¡ai %{name} + permission_add_documents: PridÄ—ti dokumentus + permission_edit_documents: Redaguoti dokumentus + permission_delete_documents: Trinti dokumentus + label_gantt_progress_line: Progreso linija + setting_jsonp_enabled: Ä®galinti JSONP palaikymÄ… + field_inherit_members: PaveldÄ—ti narius + field_closed_on: Uždarytas + field_generate_password: Sugeneruoti slaptažodį + setting_default_projects_tracker_ids: Sekliai pagal nutylÄ—jimÄ… naujiems projektams + label_total_time: IÅ¡ viso + text_scm_config: JÅ«s galite pakeisti SCM komandas byloje config/configuration.yml. PraÅ¡ome perkrauti programÄ… po redagavimo, idant įgalinti pakeitimus. + text_scm_command_not_available: SCM komanda nepasiekiama. Patikrinkite administravimo skydelio nustatymus. + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/lv.yml --- a/config/locales/lv.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/lv.yml Tue Sep 09 09:34:53 2014 +0100 @@ -46,8 +46,8 @@ one: "aptuveni 1 stunda" other: "aptuveni %{count} stundas" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 stunda" + other: "%{count} stundas" x_days: one: "1 diena" other: "%{count} dienas" @@ -123,6 +123,7 @@ not_same_project: "nepieder pie tÄ paÅ¡a projekta" circular_dependency: "Å Ä« relÄcija radÄ«tu ciklisku atkarÄ«bu" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: IzvÄ“lieties @@ -155,7 +156,7 @@ notice_not_authorized: Jums nav tiesÄ«bu piekļūt Å¡ai lapai. notice_email_sent: "E-pasts tika nosÅ«tÄ«ts uz %{value}" notice_email_error: "Kļūda sÅ«tot e-pastu (%{value})" - notice_feeds_access_key_reseted: JÅ«su RSS pieejas atslÄ“ga tika iestatÄ«ta sÄkuma stÄvoklÄ«. + notice_feeds_access_key_reseted: JÅ«su Atom pieejas atslÄ“ga tika iestatÄ«ta sÄkuma stÄvoklÄ«. notice_api_access_key_reseted: JÅ«su API pieejas atslÄ“ga tika iestatÄ«ta sÄkuma stÄvoklÄ«. notice_failed_to_save_issues: "NeizdevÄs saglabÄt %{count} uzdevumu(us) no %{total} izvÄ“lÄ“ti: %{ids}." notice_no_issue_selected: "Nav izvÄ“lÄ“ts uzdevums! LÅ«dzu, atzÄ«mÄ“jiet uzdevumus, kurus vÄ“laties rediģēt!" @@ -194,8 +195,6 @@ mail_subject_wiki_content_updated: "'%{id}' Wiki lapa atjaunota" mail_body_wiki_content_updated: "The '%{id}' Wiki lapu atjaunojis %{author}." - gui_validation_error: 1 kļūda - gui_validation_error_plural: "%{count} kļūdas" field_name: Nosaukums field_description: Apraksts @@ -370,7 +369,6 @@ permission_edit_own_time_entries: Rediģēt savus laika reÄ£istrus permission_manage_news: PÄrvaldÄ«t jaunumus permission_comment_news: KomentÄ“t jaunumus - permission_manage_documents: PÄrvaldÄ«t dokumentus permission_view_documents: SkatÄ«t dokumentus permission_manage_files: PÄrvaldÄ«t failus permission_view_files: SkatÄ«t failus @@ -492,8 +490,6 @@ label_text: GarÅ¡ teksts label_attribute: AtribÅ«ts label_attribute_plural: AtribÅ«ti - label_download: "%{count} LejupielÄde" - label_download_plural: "%{count} LejupielÄdes" label_no_data: Nav datu, ko parÄdÄ«t label_change_status: MainÄ«t statusu label_history: VÄ“sture @@ -596,8 +592,6 @@ label_repository: Repozitorijs label_repository_plural: Repozitoriji label_browse: PÄrlÅ«kot - label_modification: "%{count} izmaiņa" - label_modification_plural: "%{count} izmaiņas" label_branch: Zars label_tag: Birka label_revision: RevÄ«zija @@ -692,9 +686,9 @@ label_language_based: Izmantot lietotÄja valodu label_sort_by: "KÄrtot pÄ“c %{value}" label_send_test_email: "SÅ«tÄ«t testa e-pastu" - label_feeds_access_key: RSS piekļuves atslÄ“ga - label_missing_feeds_access_key: TrÅ«kst RSS piekļuves atslÄ“gas - label_feeds_access_key_created_on: "RSS piekļuves atslÄ“ga izveidota pirms %{value}" + label_feeds_access_key: Atom piekļuves atslÄ“ga + label_missing_feeds_access_key: TrÅ«kst Atom piekļuves atslÄ“gas + label_feeds_access_key_created_on: "Atom piekļuves atslÄ“ga izveidota pirms %{value}" label_module_plural: Moduļi label_added_time_by: "Pievienojis %{author} pirms %{age}" label_updated_time_by: "Atjaunojis %{author} pirms %{age}" @@ -783,7 +777,7 @@ button_cancel: Atcelt button_activate: AktivizÄ“t button_sort: KÄrtot - button_log_time: Ilgs laiks + button_log_time: ReÄ£istrÄ“t laiku button_rollback: Atjaunot uz Å¡o versiju button_watch: VÄ“rot button_unwatch: NevÄ“rot @@ -927,7 +921,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -968,8 +961,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1076,3 +1067,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: KopÄ + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/mk.yml --- a/config/locales/mk.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/mk.yml Tue Sep 09 09:34:53 2014 +0100 @@ -50,8 +50,8 @@ one: "околу 1 чаÑ" other: "околу %{count} чаÑа" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 чаÑ" + other: "%{count} чаÑа" x_days: one: "1 ден" other: "%{count} дена" @@ -130,6 +130,7 @@ not_same_project: "не припаѓа на иÑтиот проект" circular_dependency: "Оваа врÑка ќе креира кружна завиÑноÑÑ‚" cant_link_an_issue_with_a_descendant: "Задача неможе да Ñе поврзе Ñо една од нејзините подзадачи" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Изберете @@ -162,7 +163,7 @@ notice_not_authorized: You are not authorized to access this page. notice_email_sent: "Е-порака е пратена на %{value}" notice_email_error: "Се Ñлучи грешка при праќање на е-пораката (%{value})" - notice_feeds_access_key_reseted: Вашиот RSS клуч за приÑтап е reset. + notice_feeds_access_key_reseted: Вашиот Atom клуч за приÑтап е reset. notice_api_access_key_reseted: Вашиот API клуч за приÑтап е reset. notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." notice_failed_to_save_members: "Failed to save member(s): %{errors}." @@ -207,8 +208,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - gui_validation_error: 1 грешка - gui_validation_error_plural: "%{count} грешки" field_name: Име field_description: ÐžÐ¿Ð¸Ñ @@ -329,7 +328,6 @@ setting_time_format: Формат на време setting_cross_project_issue_relations: Дозволи релации на задачи меѓу проекти setting_issue_list_default_columns: Default columns displayed on the issue list - setting_emails_footer: Emails footer setting_protocol: Протокол setting_per_page_options: Objects per page options setting_user_format: Приказ на кориÑниците @@ -386,7 +384,6 @@ permission_edit_own_time_entries: Уредувај ÑопÑтвени белешки за потрошено време permission_manage_news: Manage news permission_comment_news: Коментирај на веÑти - permission_manage_documents: Manage documents permission_view_documents: Прегледувај документи permission_manage_files: Manage files permission_view_files: Прегледувај датотеки @@ -512,8 +509,6 @@ label_text: Долг текÑÑ‚ label_attribute: Ðтрибут label_attribute_plural: Ðтрибути - label_download: "%{count} превземање" - label_download_plural: "%{count} превземања" label_no_data: Ðема податоци за прикажување label_change_status: Промени ÑÑ‚Ð°Ñ‚ÑƒÑ label_history: ИÑторија @@ -616,8 +611,6 @@ label_repository: Складиште label_repository_plural: Складишта label_browse: ПрелиÑтувај - label_modification: "%{count} промени" - label_modification_plural: "%{count} промени" label_branch: Гранка label_tag: Tag label_revision: Ревизија @@ -713,9 +706,9 @@ label_language_based: Според јазикот на кориÑникот label_sort_by: "Подреди Ñпоред %{value}" label_send_test_email: ИÑпрати теÑÑ‚ е-порака - label_feeds_access_key: RSS клуч за приÑтап - label_missing_feeds_access_key: ÐедоÑтика RSS клуч за приÑтап - label_feeds_access_key_created_on: "RSS клучот за приÑтап креиран пред %{value}" + label_feeds_access_key: Atom клуч за приÑтап + label_missing_feeds_access_key: ÐедоÑтика Atom клуч за приÑтап + label_feeds_access_key_created_on: "Atom клучот за приÑтап креиран пред %{value}" label_module_plural: Модули label_added_time_by: "Додадено од %{author} пред %{age}" label_updated_time_by: "Ðжурирано од %{author} пред %{age}" @@ -933,7 +926,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -974,8 +966,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1082,3 +1072,33 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Вкупно + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_footer: Email footer + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/mn.yml --- a/config/locales/mn.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/mn.yml Tue Sep 09 09:34:53 2014 +0100 @@ -51,8 +51,8 @@ one: "1 цаг орчим" other: "ойролцоогоор %{count} цаг" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 цаг" + other: "%{count} цаг" x_days: one: "1 өдөр" other: "%{count} өдөр" @@ -129,6 +129,7 @@ not_same_project: "нÑг ижил төÑөлд хамаарахгүй байна" circular_dependency: "Ð­Ð½Ñ Ñ…Ð°Ñ€ÑŒÑ†Ð°Ð° нь гинжин(рекурÑив) харьцаа Ò¯Ò¯ÑгÑÑ… юм байна" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Сонгоно уу @@ -161,7 +162,7 @@ notice_not_authorized: Танд ÑÐ½Ñ Ñ…ÑƒÑƒÐ´Ñыг үзÑÑ… Ñрх байхгүй байна. notice_email_sent: "%{value} - руу мÑйл илгÑÑлÑÑ" notice_email_error: "МÑйл илгÑÑÑ…Ñд алдаа гарлаа (%{value})" - notice_feeds_access_key_reseted: Таны RSS хандалтын түлхүүрийг дахин ÑхлүүллÑÑ. + notice_feeds_access_key_reseted: Таны Atom хандалтын түлхүүрийг дахин ÑхлүүллÑÑ. notice_api_access_key_reseted: Your API access key was reset. notice_failed_to_save_issues: "%{total} аÑуудал ÑонгогдÑÐ¾Ð½Ð¾Ð¾Ñ %{count} аÑуудлыг нь хадгалахад алдаа гарлаа: %{ids}." notice_no_issue_selected: "Ямар ч аÑуудал Ñонгогдоогүй байна! ЗаÑварлах аÑуудлуудаа Ñонгоно уу." @@ -200,8 +201,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - gui_validation_error: 1 алдаа - gui_validation_error_plural: "%{count} алдаа" field_name: ÐÑÑ€ field_description: Тайлбар @@ -376,7 +375,6 @@ permission_edit_own_time_entries: Өөрийн хугацааны логуудыг заÑварлах permission_manage_news: МÑдÑÑ Ð¼ÑдÑÑллүүд permission_comment_news: МÑдÑÑнд тайлбар үлдÑÑÑ… - permission_manage_documents: Бичиг баримтууд permission_view_documents: Бичиг баримтуудыг харах permission_manage_files: Файлууд permission_view_files: Файлуудыг харах @@ -498,8 +496,6 @@ label_text: Урт текÑÑ‚ label_attribute: Ðттрибут label_attribute_plural: Ðттрибутууд - label_download: "%{count} Татаж авÑан зүйл" - label_download_plural: "%{count} Татаж авÑан зүйлÑ" label_no_data: ҮзүүлÑÑ… өгөгдөл байхгүй байна label_change_status: Төлвийг өөрчлөх label_history: Түүх @@ -602,8 +598,6 @@ label_repository: Репозитори label_repository_plural: Репозиторууд label_browse: ҮзÑÑ… - label_modification: "%{count} өөрчлөлт" - label_modification_plural: "%{count} өөрчлөлтүүд" label_branch: Салбар label_tag: Шошго label_revision: Хувилбар @@ -698,9 +692,9 @@ label_language_based: Ð¥ÑÑ€ÑглÑгчийн Ñ…ÑÐ»Ð½Ð°Ñ ÑˆÐ°Ð»Ñ‚Ð³Ð°Ð°Ð»Ð°Ð½ label_sort_by: "%{value} талбараар нь ÑÑ€ÑмбÑлÑÑ…" label_send_test_email: Турших мÑйл илгÑÑÑ… - label_feeds_access_key: RSS хандах түлхүүр - label_missing_feeds_access_key: RSS хандах түлхүүр алга - label_feeds_access_key_created_on: "RSS хандалтын түлхүүр %{value}-ийн өмнө Ò¯Ò¯ÑÑÑн" + label_feeds_access_key: Atom хандах түлхүүр + label_missing_feeds_access_key: Atom хандах түлхүүр алга + label_feeds_access_key_created_on: "Atom хандалтын түлхүүр %{value}-ийн өмнө Ò¯Ò¯ÑÑÑн" label_module_plural: Модулууд label_added_time_by: "%{author} %{age}-ийн өмнө нÑмÑÑн" label_updated_time_by: "%{author} %{age}-ийн өмнө өөрчилÑөн" @@ -934,7 +928,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -975,8 +968,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1074,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ðийт + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/nl.yml --- a/config/locales/nl.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/nl.yml Tue Sep 09 09:34:53 2014 +0100 @@ -50,7 +50,7 @@ other: "ongeveer %{count} uren" x_hours: one: "1 uur" - other: "%{count} hours" + other: "%{count} uren" x_days: one: "1 dag" other: "%{count} dagen" @@ -110,8 +110,8 @@ accepted: "moet geaccepteerd worden" empty: "mag niet leeg zijn" blank: "mag niet blanco zijn" - too_long: "is te lang" - too_short: "is te kort" + too_long: "is te lang (maximaal %{count} tekens)" + too_short: "is te kort (minimaal %{count} tekens)" wrong_length: "heeft een onjuiste lengte" taken: "is al in gebruik" not_a_number: "is geen getal" @@ -127,6 +127,7 @@ not_same_project: "hoort niet bij hetzelfde project" circular_dependency: "Deze relatie zou een circulaire afhankelijkheid tot gevolg hebben" cant_link_an_issue_with_a_descendant: "Een issue kan niet gelinked worden met een subtask" + earlier_than_minimum_start_date: "kan niet eerder zijn dan %{date} wegens voorafgaande issues" actionview_instancetag_blank_option: Selecteer @@ -291,8 +292,6 @@ general_text_Yes: 'Ja' general_text_no: 'nee' general_text_yes: 'ja' - gui_validation_error: 1 fout - gui_validation_error_plural: "%{count} fouten" label_activity: Activiteit label_add_another_file: Ander bestand toevoegen label_add_note: Voeg een notitie toe @@ -345,9 +344,9 @@ one: 1 open other: "%{count} open" label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" + zero: 0 gesloten + one: 1 gesloten + other: "%{count} gesloten" label_comment: Commentaar label_comment_add: Voeg commentaar toe label_comment_added: Commentaar toegevoegd @@ -385,8 +384,6 @@ label_document_added: Document toegevoegd label_document_new: Nieuw document label_document_plural: Documenten - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_downloads_abbr: D/L label_duplicated_by: gedupliceerd door label_duplicates: dupliceert @@ -401,7 +398,7 @@ label_f_hour: "%{value} uur" label_f_hour_plural: "%{value} uren" label_feed_plural: Feeds - label_feeds_access_key_created_on: "RSS toegangssleutel %{value} geleden gemaakt." + label_feeds_access_key_created_on: "Atom toegangssleutel %{value} geleden gemaakt." label_file_added: Bestand toegevoegd label_file_plural: Bestanden label_filter_add: Voeg filter toe @@ -425,19 +422,19 @@ label_information_plural: Informatie label_integer: Integer label_internal: Intern - label_issue: Incident - label_issue_added: Incident toegevoegd - label_issue_category: Incident categorie + label_issue: Issue + label_issue_added: Issue toegevoegd + label_issue_category: Issue categorie label_issue_category_new: Nieuwe categorie label_issue_category_plural: Issuecategorieën - label_issue_new: Nieuw incident - label_issue_plural: Incidenten - label_issue_status: Incident status + label_issue_new: Nieuw issue + label_issue_plural: Issues + label_issue_status: Issue status label_issue_status_new: Nieuwe status - label_issue_status_plural: Incident statussen - label_issue_tracking: Incident-tracking - label_issue_updated: Incident bijgewerkt - label_issue_view_all: Bekijk alle incidenten + label_issue_status_plural: Issue statussen + label_issue_tracking: Issue-tracking + label_issue_updated: Issue bijgewerkt + label_issue_view_all: Bekijk alle issues label_issue_watchers: Monitoren label_issues_by: "Issues door %{value}" label_jump_to_a_project: Ga naar een project... @@ -466,8 +463,6 @@ label_message_plural: Berichten label_message_posted: Bericht toegevoegd label_min_max_length: Min-max lengte - label_modification: "%{count} wijziging" - label_modification_plural: "%{count} wijzigingen" label_modified: gewijzigd label_module_plural: Modules label_month: Maand @@ -589,7 +584,7 @@ label_user: Gebruiker label_user_activity: "%{value}'s activiteit" label_user_mail_no_self_notified: Ik wil niet op de hoogte gehouden worden van mijn eigen wijzigingen - label_user_mail_option_all: "Bij elk gebeurtenis in al mijn projecten..." + label_user_mail_option_all: "Bij elke gebeurtenis in al mijn projecten..." label_user_mail_option_selected: "Enkel bij elke gebeurtenis op het geselecteerde project..." label_user_new: Nieuwe gebruiker label_user_plural: Gebruikers @@ -623,7 +618,6 @@ notice_account_lost_email_sent: Er is een e-mail naar u verstuurd met instructies over het kiezen van een nieuw wachtwoord. notice_account_password_updated: Wachtwoord is met succes gewijzigd notice_account_pending: "Uw account is aangemaakt, maar wacht nog op goedkeuring van de beheerder." - notice_account_register_done: Account is met succes aangemaakt. notice_account_unknown_email: Onbekende gebruiker. notice_account_updated: Account is met succes gewijzigd notice_account_wrong_password: Incorrect wachtwoord @@ -632,7 +626,7 @@ notice_email_error: "Er is een fout opgetreden tijdens het versturen van (%{value})" notice_email_sent: "Een e-mail werd verstuurd naar %{value}" notice_failed_to_save_issues: "Fout bij bewaren van %{count} issue(s) (%{total} geselecteerd): %{ids}." - notice_feeds_access_key_reseted: Je RSS toegangssleutel werd gereset. + notice_feeds_access_key_reseted: Je Atom toegangssleutel werd gereset. notice_file_not_found: De pagina die u probeerde te benaderen bestaat niet of is verwijderd. notice_locking_conflict: De gegevens zijn gewijzigd door een andere gebruiker. notice_no_issue_selected: "Er is geen issue geselecteerd. Selecteer de issue die u wilt bewerken." @@ -666,7 +660,6 @@ permission_log_time: Gespendeerde tijd loggen permission_manage_boards: Forums beheren permission_manage_categories: Issue-categorieën beheren - permission_manage_documents: Documenten beheren permission_manage_files: Bestanden beheren permission_manage_issue_relations: Issuerelaties beheren permission_manage_members: Leden beheren @@ -864,11 +857,11 @@ label_revision_id: Revisie %{value} label_api_access_key: API access key label_api_access_key_created_on: API access key gemaakt %{value} geleden - label_feeds_access_key: RSS access key + label_feeds_access_key: Atom access key notice_api_access_key_reseted: Uw API access key was gereset. setting_rest_api_enabled: Activeer REST web service label_missing_api_access_key: Geen API access key - label_missing_feeds_access_key: Geen RSS access key + label_missing_feeds_access_key: Geen Atom access key button_show: Laat zien text_line_separated: Meerdere waarden toegestaan (elke regel is een waarde). setting_mail_handler_body_delimiters: Breek email verwerking af na een van deze regels @@ -916,7 +909,6 @@ label_principal_search: "Zoek naar gebruiker of groep:" label_user_search: "Zoek naar gebruiker:" field_visible: Zichtbaar - setting_emails_header: Emails header setting_commit_logtime_activity_id: Standaard activiteit voor tijdregistratie text_time_logged_by_changeset: Toegepast in changeset %{value}. setting_commit_logtime_enabled: Activeer tijdregistratie @@ -931,26 +923,26 @@ button_collapse_all: Klap in label_additional_workflow_transitions_for_assignee: Aanvullende veranderingen toegestaan wanneer de gebruiker de toegewezene is label_additional_workflow_transitions_for_author: Aanvullende veranderingen toegestaan wanneer de gebruiker de auteur is - label_bulk_edit_selected_time_entries: Massa wijziging geselecteerd tijd-registraties? + label_bulk_edit_selected_time_entries: Alle geselecteerde tijdregistraties wijzigen? text_time_entries_destroy_confirmation: Weet u zeker dat u de geselecteerde item(s) wilt verwijderen ? label_role_anonymous: Anoniem label_role_non_member: Geen lid label_issue_note_added: Notitie toegevoegd label_issue_status_updated: Status gewijzigd - label_issue_priority_updated: Prioriteit geupdate - label_issues_visibility_own: Probleem aangemaakt door of toegewezen aan - field_issues_visibility: Incidenten weergave - label_issues_visibility_all: Alle incidenten - permission_set_own_issues_private: Zet eigen incidenten publiekelijk of privé + label_issue_priority_updated: Prioriteit gewijzigd + label_issues_visibility_own: Issue aangemaakt door of toegewezen aan + field_issues_visibility: Issues weergave + label_issues_visibility_all: Alle issues + permission_set_own_issues_private: Zet eigen issues publiekelijk of privé field_is_private: Privé - permission_set_issues_private: Zet incidenten publiekelijk of privé - label_issues_visibility_public: Alle niet privé-incidenten + permission_set_issues_private: Zet issues publiekelijk of privé + label_issues_visibility_public: Alle niet privé-issues text_issues_destroy_descendants_confirmation: Dit zal ook de %{count} subtaken verwijderen. field_commit_logs_encoding: Encodering van commit berichten field_scm_path_encoding: Pad encodering text_scm_path_encoding_note: "Standaard: UTF-8" field_path_to_repository: Pad naar versie overzicht - field_root_directory: Root directorie + field_root_directory: Root directory field_cvs_module: Module field_cvsroot: CVSROOT text_mercurial_repository_note: "Lokale versie overzicht (Voorbeeld: /hgrepo, c:\\hgrepo)" @@ -959,10 +951,10 @@ label_git_report_last_commit: Rapporteer laatste toevoegen voor bestanden en directories text_scm_config: U kan de scm commando's configureren in config/configuration.yml. Herstart de applicatie na het wijzigen ervan. text_scm_command_not_available: Scm commando is niet beschikbaar. Controleer de instellingen in het administratiepaneel. - notice_issue_successful_create: Probleem %{id} aangemaakt. + notice_issue_successful_create: Issue %{id} aangemaakt. label_between: tussen setting_issue_group_assignment: Sta groepstoewijzingen toe - label_diff: Verschil + label_diff: diff text_git_repository_note: "Versie overzicht lokaal is leeg (Voorbeeld: /gitrepo, c:\\gitrepo)" description_query_sort_criteria_direction: Sortering description_project_scope: Zoek bereik @@ -972,7 +964,7 @@ description_message_content: Inhoud bericht description_available_columns: Beschikbare kolommen description_date_range_interval: Kies een bereik bij het selecteren van een start en eind datum - description_issue_category_reassign: Kies probleem categorie + description_issue_category_reassign: Kies issue categorie description_search: Zoekveld description_notes: Notities description_date_range_list: Kies bereik vanuit de lijst @@ -984,7 +976,7 @@ label_parent_revision: Hoofd label_child_revision: Sub error_scm_annotate_big_text_file: De vermelding kan niet worden geannoteerd, omdat het groter is dan de maximale toegewezen grootte. - setting_default_issue_start_date_to_creation_date: Gebruik huidige datum als start datum voor nieuwe incidenten. + setting_default_issue_start_date_to_creation_date: Gebruik huidige datum als start datum voor nieuwe issues. button_edit_section: Wijzig deze sectie setting_repositories_encodings: Bijlage en opgeslagen bestanden coderingen description_all_columns: Alle kolommen @@ -993,23 +985,23 @@ error_attachment_too_big: Dit bestand kan niet worden geupload omdat het de maximaal toegestane grootte overschrijd (%{max_size}) notice_failed_to_save_time_entries: "Opslaan gefaald voor %{count} tijdsnotatie(s) van %{total} geselecteerde: %{ids}." label_x_issues: - zero: 0 incidenten - one: 1 incidenten - other: "%{count} incidenten" + zero: 0 issues + one: 1 issue + other: "%{count} issues" label_repository_new: Nieuw repository field_repository_is_default: Hoofd repository - label_copy_attachments: Copieer bijlage(n) + label_copy_attachments: Kopieer bijlage(n) label_item_position: "%{position}/%{count}" label_completed_versions: Versies compleet field_multiple: Meerdere waardes - setting_commit_cross_project_ref: Sta toe om incidenten van alle projecten te refereren en oplossen + setting_commit_cross_project_ref: Sta toe om issues van alle projecten te refereren en oplossen text_issue_conflict_resolution_add_notes: Voeg mijn notities toe en annuleer andere wijzigingen text_issue_conflict_resolution_overwrite: Voeg mijn wijzigingen alsnog toe (voorgaande notities worden bewaard, maar sommige kunnen overschreden worden) - notice_issue_update_conflict: Dit incident is reeds geupdate door een andere gebruiker terwijl jij bezig was + notice_issue_update_conflict: Dit issue is reeds geupdate door een andere gebruiker terwijl jij bezig was text_issue_conflict_resolution_cancel: Annuleer mijn wijzigingen en geef pagina opnieuw weer %{link} - permission_manage_related_issues: Beheer gerelateerde incidenten + permission_manage_related_issues: Beheer gerelateerde issues field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Zoek om monitoorders toe te voegen + label_search_for_watchers: Zoek om monitors toe te voegen notice_account_deleted: Uw account is permanent verwijderd setting_unsubscribe: Sta gebruikers toe hun eigen account te verwijderen button_delete_my_account: Verwijder mijn account @@ -1031,7 +1023,7 @@ text_project_closed: Dit project is gesloten en op alleen-lezen notice_user_successful_create: Gebruiker %{id} aangemaakt. field_core_fields: Standaard verleden - field_timeout: Timeout (in seconds) + field_timeout: Timeout (in seconden) setting_thumbnails_enabled: Geef bijlage miniaturen weer setting_thumbnails_size: Grootte miniaturen (in pixels) label_status_transitions: Status transitie @@ -1045,22 +1037,47 @@ label_attribute_of_assigned_to: Toegewezen %{name} label_attribute_of_fixed_version: Target versions %{name} label_copy_subtasks: Kopieer subtaken - label_copied_to: copied to - label_copied_from: copied from + label_copied_to: gekopieerd naar + label_copied_from: gekopieerd van label_any_issues_in_project: any issues in project label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project + field_private_notes: Privé notities + permission_view_private_notes: Bekijk privé notities + permission_set_notes_private: Maak notities privé + label_no_issues_in_project: geen issues in project label_any: alle - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks + label_last_n_weeks: afgelopen %{count} weken + setting_cross_project_subtasks: Sta subtaken in andere projecten toe label_cross_project_descendants: Met subprojecten label_cross_project_tree: Met project boom label_cross_project_hierarchy: Met project hiërarchie label_cross_project_system: Met alle projecten - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past + button_hide: Verberg + setting_non_working_week_days: Niet-werkdagen + label_in_the_next_days: in de volgende + label_in_the_past_days: in de afgelopen + label_attribute_of_user: User's %{name} + text_turning_multiple_off: Bij het uitschakelen van meerdere waardes zal er maar een waarde bewaard blijven. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Voeg documenten toe + permission_edit_documents: Bewerk documenten + permission_delete_documents: Verwijder documenten + label_gantt_progress_line: Voortgangslijn + setting_jsonp_enabled: Schakel JSONP support in + field_inherit_members: Neem leden over + field_closed_on: Gesloten + field_generate_password: Genereer wachtwoord + setting_default_projects_tracker_ids: Standaard trackers voor nieuwe projecten + label_total_time: Totaal + setting_emails_header: Email header + notice_account_not_activated_yet: Je hebt je account nog niet geactiveerd. Om een nieuwe activatie email te ontvangen, klik op deze link. + notice_account_locked: Je account is vergrendeld. + notice_account_register_done: "Account aanmaken is gelukt. Een email met instructies om je account te activeren is gestuurd naar: %{email}." + label_hidden: Verborgen + label_visibility_private: voor mij alleen + label_visibility_roles: alleen voor deze rollen + label_visibility_public: voor elke gebruiker + field_must_change_passwd: Moet wachtwoord wijziging bij volgende keer inloggen + notice_new_password_must_be_different: Het nieuwe wachtwoord mag niet hetzelfde zijn als het huidige wachtwoord + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert beschikbaar (optioneel) diff -r d98d22a98252 -r afce8026aaeb config/locales/no.yml --- a/config/locales/no.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/no.yml Tue Sep 09 09:34:53 2014 +0100 @@ -44,8 +44,8 @@ one: "rundt 1 time" other: "rundt %{count} timer" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 time" + other: "%{count} timer" x_days: one: "1 dag" other: "%{count} dager" @@ -118,6 +118,7 @@ not_same_project: "hører ikke til samme prosjekt" circular_dependency: "Denne relasjonen ville lagd en sirkulær avhengighet" cant_link_an_issue_with_a_descendant: "En sak kan ikke kobles mot en av sine undersaker" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Vennligst velg @@ -151,7 +152,7 @@ notice_not_authorized: Du har ikke adgang til denne siden. notice_email_sent: "En e-post er sendt til %{value}" notice_email_error: "En feil oppstod under sending av e-post (%{value})" - notice_feeds_access_key_reseted: Din RSS-tilgangsnøkkel er nullstilt. + notice_feeds_access_key_reseted: Din Atom-tilgangsnøkkel er nullstilt. notice_failed_to_save_issues: "Lykkes ikke Ã¥ lagre %{count} sak(er) pÃ¥ %{total} valgt: %{ids}." notice_no_issue_selected: "Ingen sak valgt! Vennligst merk sakene du vil endre." notice_account_pending: "Din konto ble opprettet og avventer nÃ¥ administrativ godkjenning." @@ -174,8 +175,6 @@ mail_subject_reminder: "%{count} sak(er) har frist de kommende %{days} dagene" mail_body_reminder: "%{count} sak(er) som er tildelt deg har frist de kommende %{days} dager:" - gui_validation_error: 1 feil - gui_validation_error_plural: "%{count} feil" field_name: Navn field_description: Beskrivelse @@ -386,8 +385,6 @@ label_text: Lang tekst label_attribute: Attributt label_attribute_plural: Attributter - label_download: "%{count} Nedlasting" - label_download_plural: "%{count} Nedlastinger" label_no_data: Ingen data Ã¥ vise label_change_status: Endre status label_history: Historikk @@ -487,8 +484,6 @@ label_repository: Depot label_repository_plural: Depoter label_browse: Utforsk - label_modification: "%{count} endring" - label_modification_plural: "%{count} endringer" label_revision: Revisjon label_revision_plural: Revisjoner label_associated_revisions: Assosierte revisjoner @@ -575,7 +570,7 @@ label_language_based: Basert pÃ¥ brukerens sprÃ¥k label_sort_by: "Sorter etter %{value}" label_send_test_email: Send en epost-test - label_feeds_access_key_created_on: "RSS tilgangsnøkkel opprettet for %{value} siden" + label_feeds_access_key_created_on: "Atom tilgangsnøkkel opprettet for %{value} siden" label_module_plural: Moduler label_added_time_by: "Lagt til av %{author} for %{age} siden" label_updated_time: "Oppdatert for %{value} siden" @@ -745,7 +740,6 @@ permission_comment_news: Kommentere nyheter permission_delete_messages: Slette meldinger permission_select_project_modules: Velge prosjektmoduler - permission_manage_documents: Administrere dokumenter permission_edit_wiki_pages: Redigere wiki-sider permission_add_issue_watchers: Legge til overvÃ¥kere permission_view_gantt: Vise gantt-diagram @@ -868,11 +862,11 @@ label_revision_id: Revision %{value} label_api_access_key: API tilgangsnøkkel label_api_access_key_created_on: API tilgangsnøkkel opprettet for %{value} siden - label_feeds_access_key: RSS tilgangsnøkkel + label_feeds_access_key: Atom tilgangsnøkkel notice_api_access_key_reseted: Din API tilgangsnøkkel ble resatt. setting_rest_api_enabled: Aktiver REST webservice label_missing_api_access_key: Mangler en API tilgangsnøkkel - label_missing_feeds_access_key: Mangler en RSS tilgangsnøkkel + label_missing_feeds_access_key: Mangler en Atom tilgangsnøkkel button_show: Vis text_line_separated: Flere verdier er tillatt (en linje per verdi). setting_mail_handler_body_delimiters: Avkort epost etter en av disse linjene @@ -1053,8 +1047,8 @@ label_attribute_of_assigned_to: Assignee's %{name} label_attribute_of_fixed_version: Target version's %{name} label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from + label_copied_to: kopiert til + label_copied_from: kopiert fra label_any_issues_in_project: any issues in project label_any_issues_not_in_project: any issues not in project field_private_notes: Private notes @@ -1072,3 +1066,29 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Totalt + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/pl.yml --- a/config/locales/pl.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/pl.yml Tue Sep 09 09:34:53 2014 +0100 @@ -82,8 +82,8 @@ one: "okoÅ‚o godziny" other: "okoÅ‚o %{count} godzin" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 godzina" + other: "%{count} godzin" x_days: one: "1 dzieÅ„" other: "%{count} dni" @@ -102,8 +102,9 @@ few: "ponad %{count} lata" other: "ponad %{count} lat" almost_x_years: - one: "prawie rok" - other: "prawie %{count} lata" + one: "prawie 1 rok" + few: "prawie %{count} lata" + other: "prawie %{count} lat" activerecord: errors: @@ -134,8 +135,9 @@ even: "musi być parzyste" greater_than_start_date: "musi być wiÄ™ksze niż poczÄ…tkowa data" not_same_project: "nie należy do tego samego projektu" - circular_dependency: "Ta relacja może wytworzyć koÅ‚owÄ… zależność" + circular_dependency: "Ta relacja może wytworzyć zapÄ™tlonÄ… zależność" cant_link_an_issue_with_a_descendant: "Zagadnienie nie może zostać powiÄ…zane z jednym z wÅ‚asnych podzagadnieÅ„" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" support: array: @@ -145,6 +147,9 @@ # Keep this line in order to avoid problems with Windows Notepad UTF-8 EF-BB-BFidea... # Best regards from Lublin@Poland :-) # PL translation by Mariusz@Olejnik.net, + # Wiktor Wandachowicz , 2010 + + actionview_instancetag_blank_option: ProszÄ™ wybrać actionview_instancetag_blank_option: ProszÄ™ wybierz button_activate: Aktywuj @@ -189,7 +194,7 @@ default_activity_development: Rozwój default_doc_category_tech: Dokumentacja techniczna default_doc_category_user: Dokumentacja użytkownika - default_issue_status_in_progress: W Toku + default_issue_status_in_progress: W toku default_issue_status_closed: ZamkniÄ™ty default_issue_status_feedback: Odpowiedź default_issue_status_new: Nowy @@ -202,7 +207,7 @@ default_priority_urgent: Pilny default_role_developer: Programista default_role_manager: Kierownik - default_role_reporter: Wprowadzajacy + default_role_reporter: ZgÅ‚aszajÄ…cy default_tracker_bug: Błąd default_tracker_feature: Zadanie default_tracker_support: Wsparcie @@ -210,7 +215,7 @@ enumeration_doc_categories: Kategorie dokumentów enumeration_issue_priorities: Priorytety zagadnieÅ„ error_can_t_load_default_data: "DomyÅ›lna konfiguracja nie może być zaÅ‚adowana: %{value}" - error_issue_not_found_in_project: 'Zaganienie nie zostaÅ‚o znalezione lub nie należy do tego projektu' + error_issue_not_found_in_project: 'Zagadnienie nie zostaÅ‚o znalezione lub nie należy do tego projektu' error_scm_annotate: "Wpis nie istnieje lub nie można do niego dodawać adnotacji." error_scm_command_failed: "WystÄ…piÅ‚ błąd przy próbie dostÄ™pu do repozytorium: %{value}" error_scm_not_found: "Obiekt lub wersja nie zostaÅ‚y znalezione w repozytorium." @@ -218,11 +223,11 @@ field_activity: Aktywność field_admin: Administrator field_assignable: Zagadnienia mogÄ… być przypisane do tej roli - field_assigned_to: Przydzielony do + field_assigned_to: Przypisany do field_attr_firstname: ImiÄ™ atrybut field_attr_lastname: Nazwisko atrybut field_attr_login: Login atrybut - field_attr_mail: Email atrybut + field_attr_mail: E-mail atrybut field_auth_source: Tryb identyfikacji field_author: Autor field_base_dn: Base DN @@ -244,11 +249,11 @@ field_filesize: Rozmiar field_firstname: ImiÄ™ field_fixed_version: Wersja docelowa - field_hide_mail: Ukryj mój adres email + field_hide_mail: Ukryj mój adres e-mail field_homepage: Strona www field_host: Host field_hours: Godzin - field_identifier: Identifikator + field_identifier: Identyfikator field_is_closed: Zagadnienie zamkniÄ™te field_is_default: DomyÅ›lny status field_is_filter: Atrybut filtrowania @@ -262,15 +267,15 @@ field_last_login_on: Ostatnie połączenie field_lastname: Nazwisko field_login: Login - field_mail: Email - field_mail_notification: Powiadomienia Email + field_mail: E-mail + field_mail_notification: Powiadomienia e-mail field_max_length: Maksymalna dÅ‚ugość field_min_length: Minimalna dÅ‚ugość field_name: Nazwa field_new_password: Nowe hasÅ‚o field_notes: Notatki field_onthefly: Tworzenie użytkownika w locie - field_parent: Nadprojekt + field_parent: Projekt nadrzÄ™dny field_parent_title: Strona rodzica field_password: HasÅ‚o field_password_confirmation: Potwierdzenie @@ -283,7 +288,7 @@ field_role: Rola field_searchable: Przeszukiwalne field_spent_on: Data - field_start_date: Start + field_start_date: Data rozpoczÄ™cia field_start_page: Strona startowa field_status: Status field_subject: Temat @@ -310,10 +315,7 @@ general_text_Yes: 'Tak' general_text_no: 'nie' general_text_yes: 'tak' - gui_validation_error: 1 błąd - gui_validation_error_plural234: "%{count} błędy" - gui_validation_error_plural5: "%{count} błędów" - gui_validation_error_plural: "%{count} błędów" + label_activity: Aktywność label_add_another_file: Dodaj kolejny plik label_add_note: Dodaj notatkÄ™ @@ -339,7 +341,7 @@ label_auth_source_new: Nowy tryb identyfikacji label_auth_source_plural: Tryby identyfikacji label_authentication: Identyfikacja - label_blocked_by: zablokowane przez + label_blocked_by: blokowane przez label_blocks: blokuje label_board: Forum label_board_new: Nowe forum @@ -360,42 +362,46 @@ label_closed_issues_plural5: zamkniÄ™te label_closed_issues_plural: zamkniÄ™te label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" + zero: 0 otwartych / %{total} + one: 1 otwarty / %{total} + few: "%{count} otwarte / %{total}" + other: "%{count} otwartych / %{total}" label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" + zero: 0 otwartych + one: 1 otwarty + few: "%{count} otwarte" + other: "%{count} otwartych" label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" + zero: 0 zamkniÄ™tych + one: 1 zamkniÄ™ty + few: "%{count} zamkniÄ™te" + other: "%{count} zamkniÄ™tych" label_comment: Komentarz label_comment_add: Dodaj komentarz label_comment_added: Komentarz dodany label_comment_delete: UsuÅ„ komentarze label_comment_plural234: Komentarze - label_comment_plural5: Komentarze + label_comment_plural5: Komentarzy label_comment_plural: Komentarze label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" + zero: brak komentarzy + one: 1 komentarz + few: "%{count} komentarze" + other: "%{count} komentarzy" label_commits_per_author: Zatwierdzenia wedÅ‚ug autorów label_commits_per_month: Zatwierdzenia wedÅ‚ug miesiÄ™cy label_confirmation: Potwierdzenie label_contains: zawiera label_copied: skopiowano - label_copy_workflow_from: Kopiuj przepÅ‚yw z + label_copy_workflow_from: Kopiuj przepÅ‚yw pracy z label_current_status: Obecny status label_current_version: Obecna wersja - label_custom_field: Dowolne pole - label_custom_field_new: Nowe dowolne pole - label_custom_field_plural: Dowolne pola + label_custom_field: Pole niestandardowe + label_custom_field_new: Nowe pole niestandardowe + label_custom_field_plural: Pola niestandardowe label_date: Data - label_date_from: Z - label_date_range: Zakres datowy + label_date_from: Od + label_date_range: Zakres dat label_date_to: Do label_day_plural: dni label_default: DomyÅ›lne @@ -410,10 +416,6 @@ label_document_added: Dodano dokument label_document_new: Nowy dokument label_document_plural: Dokumenty - label_download: "%{count} Pobranie" - label_download_plural234: "%{count} Pobrania" - label_download_plural5: "%{count} PobraÅ„" - label_download_plural: "%{count} Pobrania" label_downloads_abbr: Pobieranie label_duplicated_by: zduplikowane przez label_duplicates: duplikuje @@ -427,13 +429,13 @@ label_export_to: Eksportuj do label_f_hour: "%{value} godzina" label_f_hour_plural: "%{value} godzin" - label_feed_plural: Ilość RSS - label_feeds_access_key_created_on: "Klucz dostÄ™pu RSS stworzony %{value} dni temu" + label_feed_plural: Ilość Atom + label_feeds_access_key_created_on: "Klucz dostÄ™pu do kanaÅ‚u Atom stworzony %{value} temu" label_file_added: Dodano plik label_file_plural: Pliki label_filter_add: Dodaj filtr label_filter_plural: Filtry - label_float: Liczba rzeczywista + label_float: Liczba zmiennoprzecinkowa label_follows: nastÄ™puje po label_gantt: Gantt label_general: Ogólne @@ -493,10 +495,6 @@ label_message_plural: WiadomoÅ›ci label_message_posted: Dodano wiadomość label_min_max_length: Min - Maks dÅ‚ugość - label_modification: "%{count} modyfikacja" - label_modification_plural234: "%{count} modyfikacje" - label_modification_plural5: "%{count} modyfikacji" - label_modification_plural: "%{count} modyfikacje" label_modified: zmodyfikowane label_module_plural: ModuÅ‚y label_month: MiesiÄ…c @@ -523,7 +521,7 @@ label_not_equals: różni siÄ™ label_open_issues: otwarte label_open_issues_plural234: otwarte - label_open_issues_plural5: otwarte + label_open_issues_plural5: otwartych label_open_issues_plural: otwarte label_optional_description: Opcjonalny opis label_options: Opcje @@ -533,7 +531,7 @@ label_per_page: Na stronÄ™ label_permissions: Uprawnienia label_permissions_report: Raport uprawnieÅ„ - label_personalize_page: Personalizuj tÄ… stronÄ™ + label_personalize_page: Personalizuj tÄ™ stronÄ™ label_planning: Planowanie label_please_login: Zaloguj siÄ™ label_plugins: Wtyczki @@ -550,7 +548,8 @@ label_project_plural: Projekty label_x_projects: zero: brak projektów - one: jeden projekt + one: 1 projekt + few: "%{count} projekty" other: "%{count} projektów" label_public_projects: Projekty publiczne label_query: Kwerenda @@ -566,7 +565,7 @@ label_relates_to: powiÄ…zane z label_relation_delete: UsuÅ„ powiÄ…zanie label_relation_new: Nowe powiÄ…zanie - label_renamed: przemianowano + label_renamed: zmieniono nazwÄ™ label_reply_plural: Odpowiedzi label_report: Raport label_report_plural: Raporty @@ -582,14 +581,14 @@ label_roadmap_no_issues: Brak zagadnieÅ„ do tej wersji label_roadmap_overdue: "%{value} spóźnienia" label_role: Rola - label_role_and_permissions: Role i Uprawnienia + label_role_and_permissions: Role i uprawnienia label_role_new: Nowa rola label_role_plural: Role label_scm: SCM label_search: Szukaj label_search_titles_only: Przeszukuj tylko tytuÅ‚y label_send_information: WyÅ›lij informacjÄ™ użytkownikowi - label_send_test_email: WyÅ›lij próbny email + label_send_test_email: WyÅ›lij próbny e-mail label_settings: Ustawienia label_show_completed_versions: Pokaż kompletne wersje label_sort_by: "Sortuj po %{value}" @@ -621,7 +620,7 @@ label_user: Użytkownik label_user_mail_no_self_notified: "Nie chcÄ™ powiadomieÅ„ o zmianach, które sam wprowadzam." label_user_mail_option_all: "Dla każdego zdarzenia w każdym moim projekcie" - label_user_mail_option_selected: "Tylko dla każdego zdarzenia w wybranych projektach..." + label_user_mail_option_selected: "Dla każdego zdarzenia w wybranych projektach..." label_user_new: Nowy użytkownik label_user_plural: Użytkownicy label_version: Wersja @@ -636,12 +635,12 @@ label_wiki_edit_plural: Edycje wiki label_wiki_page: Strona wiki label_wiki_page_plural: Strony wiki - label_workflow: PrzepÅ‚yw + label_workflow: PrzepÅ‚yw pracy label_year: Rok label_yesterday: wczoraj mail_body_account_activation_request: "Zarejestrowano nowego użytkownika: (%{value}). Konto oczekuje na twoje zatwierdzenie:" mail_body_account_information: Twoje konto - mail_body_account_information_external: "Możesz użyć twojego %{value} konta do zalogowania." + mail_body_account_information_external: "Możesz użyć Twojego konta %{value} do zalogowania." mail_body_lost_password: 'W celu zmiany swojego hasÅ‚a użyj poniższego odnoÅ›nika:' mail_body_register: 'W celu aktywacji Twojego konta, użyj poniższego odnoÅ›nika:' mail_body_reminder: "Wykaz przypisanych do Ciebie zagadnieÅ„, których termin wypada w ciÄ…gu nastÄ™pnych %{count} dni" @@ -651,23 +650,22 @@ mail_subject_reminder: "Uwaga na terminy, masz zagadnienia do obsÅ‚użenia w ciÄ…gu nastÄ™pnych %{count} dni! (%{days})" notice_account_activated: Twoje konto zostaÅ‚o aktywowane. Możesz siÄ™ zalogować. notice_account_invalid_creditentials: ZÅ‚y użytkownik lub hasÅ‚o - notice_account_lost_email_sent: Email z instrukcjami zmiany hasÅ‚a zostaÅ‚ wysÅ‚any do Ciebie. + notice_account_lost_email_sent: E-mail z instrukcjami zmiany hasÅ‚a zostaÅ‚ wysÅ‚any do Ciebie. notice_account_password_updated: HasÅ‚o prawidÅ‚owo zmienione. notice_account_pending: "Twoje konto zostaÅ‚o utworzone i oczekuje na zatwierdzenie administratora." - notice_account_register_done: Konto prawidÅ‚owo stworzone. notice_account_unknown_email: Nieznany użytkownik. notice_account_updated: Konto prawidÅ‚owo zaktualizowane. notice_account_wrong_password: ZÅ‚e hasÅ‚o notice_can_t_change_password: To konto ma zewnÄ™trzne źródÅ‚o identyfikacji. Nie możesz zmienić hasÅ‚a. notice_default_data_loaded: DomyÅ›lna konfiguracja zostaÅ‚a pomyÅ›lnie zaÅ‚adowana. - notice_email_error: "WystÄ…piÅ‚ błąd w trakcie wysyÅ‚ania maila (%{value})" - notice_email_sent: "Email zostaÅ‚ wysÅ‚any do %{value}" + notice_email_error: "WystÄ…piÅ‚ błąd w trakcie wysyÅ‚ania e-maila (%{value})" + notice_email_sent: "E-mail zostaÅ‚ wysÅ‚any do %{value}" notice_failed_to_save_issues: "Błąd podczas zapisu zagadnieÅ„ %{count} z %{total} zaznaczonych: %{ids}." - notice_feeds_access_key_reseted: Twój klucz dostÄ™pu RSS zostaÅ‚ zrestetowany. + notice_feeds_access_key_reseted: Twój klucz dostÄ™pu do kanaÅ‚u Atom zostaÅ‚ zresetowany. notice_file_not_found: Strona do której próbujesz siÄ™ dostać nie istnieje lub zostaÅ‚a usuniÄ™ta. notice_locking_conflict: Dane poprawione przez innego użytkownika. notice_no_issue_selected: "Nie wybrano zagadnienia! Zaznacz zagadnienie, które chcesz edytować." - notice_not_authorized: Nie jesteÅ› autoryzowany by zobaczyć stronÄ™. + notice_not_authorized: Nie posiadasz autoryzacji do oglÄ…dania tej strony. notice_successful_connection: Udane nawiÄ…zanie połączenia. notice_successful_create: Utworzenie zakoÅ„czone sukcesem. notice_successful_delete: UsuniÄ™cie zakoÅ„czone sukcesem. @@ -696,8 +694,7 @@ permission_edit_wiki_pages: Edycja stron wiki permission_log_time: Zapisywanie przepracowanego czasu permission_manage_boards: ZarzÄ…dzanie forami - permission_manage_categories: ZarzÄ…dzanie kategoriami zaganieÅ„ - permission_manage_documents: ZarzÄ…dzanie dokumentami + permission_manage_categories: ZarzÄ…dzanie kategoriami zagadnieÅ„ permission_manage_files: ZarzÄ…dzanie plikami permission_manage_issue_relations: ZarzÄ…dzanie powiÄ…zaniami zagadnieÅ„ permission_manage_members: ZarzÄ…dzanie uczestnikami @@ -734,7 +731,7 @@ setting_app_title: TytuÅ‚ aplikacji setting_attachment_max_size: Maks. rozm. załącznika setting_autofetch_changesets: Automatyczne pobieranie zmian - setting_autologin: Auto logowanie + setting_autologin: Automatyczne logowanie setting_bcc_recipients: Odbiorcy kopii tajnej (kt/bcc) setting_commit_fix_keywords: SÅ‚owa zmieniajÄ…ce status setting_commit_ref_keywords: SÅ‚owa tworzÄ…ce powiÄ…zania @@ -745,24 +742,24 @@ setting_display_subprojects_issues: DomyÅ›lnie pokazuj zagadnienia podprojektów w głównym projekcie setting_emails_footer: Stopka e-mail setting_enabled_scm: DostÄ™pny SCM - setting_feeds_limit: Limit danych RSS + setting_feeds_limit: Limit danych Atom setting_gravatar_enabled: Używaj ikon użytkowników Gravatar setting_host_name: Nazwa hosta i Å›cieżka setting_issue_list_default_columns: DomyÅ›lne kolumny wyÅ›wietlane na liÅ›cie zagadnieÅ„ setting_issues_export_limit: Limit eksportu zagadnieÅ„ - setting_login_required: Identyfikacja wymagana - setting_mail_from: Adres email wysyÅ‚ki + setting_login_required: Wymagane zalogowanie + setting_mail_from: Adres e-mail wysyÅ‚ki setting_mail_handler_api_enabled: Uaktywnij usÅ‚ugi sieciowe (WebServices) dla poczty przychodzÄ…cej setting_mail_handler_api_key: Klucz API setting_per_page_options: Opcje iloÅ›ci obiektów na stronie setting_plain_text_mail: tylko tekst (bez HTML) - setting_protocol: ProtokoÅ‚ + setting_protocol: Protokół setting_self_registration: Samodzielna rejestracja użytkowników setting_sequential_project_identifiers: Generuj sekwencyjne identyfikatory projektów setting_sys_api_enabled: Włączenie WS do zarzÄ…dzania repozytorium setting_text_formatting: Formatowanie tekstu setting_time_format: Format czasu - setting_user_format: Personalny format wyÅ›wietlania + setting_user_format: WÅ‚asny format wyÅ›wietlania setting_welcome_text: Tekst powitalny setting_wiki_compression: Kompresja historii Wiki status_active: aktywny @@ -772,43 +769,43 @@ text_assign_time_entries_to_project: Przypisz wpisy dziennika do projektu text_caracters_maximum: "%{count} znaków maksymalnie." text_caracters_minimum: "Musi być nie krótsze niż %{count} znaków." - text_comma_separated: Wielokrotne wartoÅ›ci dozwolone (rozdzielone przecinkami). + text_comma_separated: Dozwolone wielokrotne wartoÅ›ci (rozdzielone przecinkami). text_default_administrator_account_changed: Zmieniono domyÅ›lne hasÅ‚o administratora text_destroy_time_entries: UsuÅ„ wpisy dziennika text_destroy_time_entries_question: Przepracowano %{hours} godzin przy zagadnieniu, które chcesz usunąć. Co chcesz zrobić? - text_email_delivery_not_configured: "Dostarczanie poczty elektronicznej nie zostaÅ‚o skonfigurowane, wiÄ™c powiadamianie jest nieaktywne.\nSkonfiguruj serwer SMTP w config/configuration.yml a nastÄ™pnie zrestartuj aplikacjÄ™ i uaktywnij to." + text_email_delivery_not_configured: "Dostarczanie poczty elektronicznej nie zostaÅ‚o skonfigurowane, wiÄ™c powiadamianie jest nieaktywne.\nSkonfiguruj serwer SMTP w config/configuration.yml a nastÄ™pnie zrestartuj aplikacjÄ™ i uaktywnij to." text_enumeration_category_reassign_to: 'ZmieÅ„ przypisanie na tÄ… wartość:' - text_enumeration_destroy_question: "%{count} obiektów jest przypisana do tej wartoÅ›ci." + text_enumeration_destroy_question: "%{count} obiektów jest przypisanych do tej wartoÅ›ci." text_file_repository_writable: Zapisywalne repozytorium plików - text_issue_added: "Zagadnienie %{id} zostaÅ‚o wprowadzone (by %{author})." + text_issue_added: "Zagadnienie %{id} zostaÅ‚o wprowadzone (przez %{author})." text_issue_category_destroy_assignments: UsuÅ„ przydziaÅ‚y kategorii - text_issue_category_destroy_question: "Zagadnienia (%{count}) sÄ… przypisane do tej kategorii. Co chcesz zrobić?" + text_issue_category_destroy_question: "Do tej kategorii sÄ… przypisane zagadnienia (%{count}). Co chcesz zrobić?" text_issue_category_reassign_to: Przydziel zagadnienie do tej kategorii - text_issue_updated: "Zagadnienie %{id} zostaÅ‚o zaktualizowane (by %{author})." - text_issues_destroy_confirmation: 'Czy jestes pewien, że chcesz usunąć wskazane zagadnienia?' + text_issue_updated: "Zagadnienie %{id} zostaÅ‚o zaktualizowane (przez %{author})." + text_issues_destroy_confirmation: 'Czy jesteÅ› pewien, że chcesz usunąć wskazane zagadnienia?' text_issues_ref_in_commit_messages: OdwoÅ‚ania do zagadnieÅ„ w komentarzach zatwierdzeÅ„ text_length_between: "DÅ‚ugość pomiÄ™dzy %{min} i %{max} znaków." text_load_default_configuration: ZaÅ‚aduj domyÅ›lnÄ… konfiguracjÄ™ text_min_max_length_info: 0 oznacza brak restrykcji - text_no_configuration_data: "Role użytkowników, typy zagadnieÅ„, statusy zagadnieÅ„ oraz przepÅ‚yw pracy nie zostaÅ‚y jeszcze skonfigurowane.\nJest wysoce rekomendowane by zaÅ‚adować domyÅ›lnÄ… konfiguracjÄ™. Po zaÅ‚adowaniu bÄ™dzie możliwość edycji tych danych." + text_no_configuration_data: "Role użytkowników, typy zagadnieÅ„, statusy zagadnieÅ„ oraz przepÅ‚yw pracy nie zostaÅ‚y jeszcze skonfigurowane.\nWysoce zalecane jest by zaÅ‚adować domyÅ›lnÄ… konfiguracjÄ™. Po zaÅ‚adowaniu bÄ™dzie możliwość edycji tych danych." text_project_destroy_confirmation: JesteÅ› pewien, że chcesz usunąć ten projekt i wszystkie powiÄ…zane dane? text_reassign_time_entries: 'Przepnij przepracowany czas do tego zagadnienia:' text_regexp_info: np. ^[A-Z0-9]+$ - text_repository_usernames_mapping: "Wybierz lub uaktualnij przyporzÄ…dkowanie użytkowników Redmine do użytkowników repozytorium.\nUżytkownicy z takÄ… samÄ… nazwÄ… lub adresem email sÄ… przyporzÄ…dkowani automatycznie." + text_repository_usernames_mapping: "Wybierz lub uaktualnij przyporzÄ…dkowanie użytkowników Redmine do użytkowników repozytorium.\nUżytkownicy z takÄ… samÄ… nazwÄ… lub adresem e-mail sÄ… przyporzÄ…dkowani automatycznie." text_rmagick_available: RMagick dostÄ™pne (opcjonalnie) - text_select_mail_notifications: Zaznacz czynnoÅ›ci przy których użytkownik powinien być powiadomiony mailem. + text_select_mail_notifications: Zaznacz czynnoÅ›ci przy których użytkownik powinien być powiadomiony e-mailem. text_select_project_modules: 'Wybierz moduÅ‚y do aktywacji w tym projekcie:' text_status_changed_by_changeset: "Zastosowane w zmianach %{value}." text_subprojects_destroy_warning: "Podprojekt(y): %{value} zostanÄ… także usuniÄ™te." text_tip_issue_begin_day: zadanie zaczynajÄ…ce siÄ™ dzisiaj text_tip_issue_begin_end_day: zadanie zaczynajÄ…ce i koÅ„czÄ…ce siÄ™ dzisiaj text_tip_issue_end_day: zadanie koÅ„czÄ…ce siÄ™ dzisiaj - text_tracker_no_workflow: Brak przepÅ‚ywu zdefiniowanego dla tego typu zagadnienia + text_tracker_no_workflow: Brak przepÅ‚ywu pracy zdefiniowanego dla tego typu zagadnienia text_unallowed_characters: Niedozwolone znaki - text_user_mail_option: "W przypadku niezaznaczonych projektów, bÄ™dziesz otrzymywaÅ‚ powiadomienia tylko na temat zagadnieÅ„, które obserwujesz, lub w których bierzesz udziaÅ‚ (np. jesteÅ› autorem lub adresatem)." - text_user_wrote: "%{value} napisaÅ‚:" - text_wiki_destroy_confirmation: JesteÅ› pewien, że chcesz usunąć to wiki i całą jego zawartość ? - text_workflow_edit: Zaznacz rolÄ™ i typ zagadnienia do edycji przepÅ‚ywu + text_user_mail_option: "W przypadku niezaznaczonych projektów, bÄ™dziesz otrzymywaÅ‚ powiadomienia tylko na temat zagadnieÅ„ które obserwujesz, lub w których bierzesz udziaÅ‚ (np. jesteÅ› autorem lub adresatem)." + text_user_wrote: "%{value} napisaÅ‚(a):" + text_wiki_destroy_confirmation: JesteÅ› pewien, że chcesz usunąć to wiki i całą jego zawartość? + text_workflow_edit: Zaznacz rolÄ™ i typ zagadnienia do edycji przepÅ‚ywu pracy label_user_activity: "Aktywność: %{value}" label_updated_time_by: "Uaktualnione przez %{author} %{age} temu" @@ -851,7 +848,7 @@ label_tag: SÅ‚owo kluczowe label_branch: Gałąź error_no_tracker_in_project: Projekt nie posiada powiÄ…zanych typów zagadnieÅ„. Sprawdź ustawienia projektu. - error_no_default_issue_status: Nie zdefiniowano domyÅ›lnego statusu zagadnieÅ„. Sprawdź konfiguracjÄ™ (Przejdź do "Administracja -> Statusy zagadnieÅ„). + error_no_default_issue_status: Nie zdefiniowano domyÅ›lnego statusu zagadnieÅ„. Sprawdź konfiguracjÄ™ (Przejdź do "Administracja -> Statusy zagadnieÅ„"). text_journal_changed: "Zmieniono %{label} z %{old} na %{new}" text_journal_set_to: "Ustawiono %{label} na %{value}" text_journal_deleted: "UsuniÄ™to %{label} (%{old})" @@ -861,7 +858,7 @@ label_time_entry_plural: Przepracowany czas text_journal_added: "Dodano %{label} %{value}" field_active: Aktywne - enumeration_system_activity: Aktywność Systemowa + enumeration_system_activity: Aktywność systemowa button_copy_and_follow: Kopiuj i przejdź do kopii zagadnienia button_duplicate: Duplikuj button_move_and_follow: PrzenieÅ› i przejdź do zagadnienia @@ -879,9 +876,9 @@ label_copy_source: ŹródÅ‚o label_copy_target: Cel label_display_used_statuses_only: WyÅ›wietlaj tylko statusy używane przez ten typ zagadnienia - label_feeds_access_key: Klucz dostÄ™pu do RSS + label_feeds_access_key: Klucz dostÄ™pu do kanaÅ‚u Atom label_missing_api_access_key: Brakuje klucza dostÄ™pu do API - label_missing_feeds_access_key: Brakuje klucza dostÄ™pu do RSS + label_missing_feeds_access_key: Brakuje klucza dostÄ™pu do kanaÅ‚u Atom label_revision_id: Rewizja %{value} label_subproject_new: Nowy podprojekt label_update_issue_done_ratios: Uaktualnij % wykonania @@ -922,7 +919,7 @@ permission_manage_subtasks: ZarzÄ…dzanie podzagadnieniami field_parent_issue: Zagadnienie nadrzÄ™dne label_subtask_plural: Podzagadnienia - label_project_copy_notifications: WyÅ›lij powiadomienia mailowe przy kopiowaniu projektu + label_project_copy_notifications: WyÅ›lij powiadomienia e-mailowe przy kopiowaniu projektu error_can_not_delete_custom_field: Nie można usunąć tego pola error_unable_to_connect: Nie można połączyć (%{value}) error_can_not_remove_role: Ta rola przypisana jest niektórym użytkownikom i nie może zostać usuniÄ™ta. @@ -930,8 +927,8 @@ field_principal: PrzeÅ‚ożony label_my_page_block: Elementy notice_failed_to_save_members: "Nie można zapisać uczestników: %{errors}." - text_zoom_out: Zmniejsz czcionkÄ™ - text_zoom_in: PowiÄ™ksz czcionkÄ™ + text_zoom_out: Zmniejsz + text_zoom_in: PowiÄ™ksz notice_unable_delete_time_entry: Nie można usunąć wpisu z dziennika. label_overall_spent_time: Przepracowany czas field_time_entries: Dziennik @@ -943,14 +940,13 @@ setting_default_notification_option: Default notification option label_user_mail_option_only_my_events: Only for things I watch or I'm involved in label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events + label_user_mail_option_none: "Tylko to, co obserwujÄ™ lub w czym biorÄ™ udziaÅ‚" field_member_of_group: Assignee's group field_assigned_to_role: Assignee's role notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" + label_principal_search: "Szukaj użytkownika lub grupy:" + label_user_search: "Szukaj użytkownika:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -991,8 +987,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1035,7 +1029,7 @@ label_copy_attachments: Copy attachments label_item_position: "%{position}/%{count}" label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + text_project_identifier_info: 'Dozwolone małe litery (a-z), liczby i myślniki.
    Raz zapisany, identyfikator nie może być zmieniony.' field_multiple: Multiple values setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes @@ -1099,3 +1093,34 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ogółem + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + notice_account_register_done: Account was successfully created. An email containing + the instructions to activate your account was sent to %{email}. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/pt-BR.yml --- a/config/locales/pt-BR.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/pt-BR.yml Tue Sep 09 09:34:53 2014 +0100 @@ -122,8 +122,8 @@ errors: template: header: - one: "model não pode ser salvo: 1 erro" - other: "model não pode ser salvo: %{count} erros." + one: "modelo não pode ser salvo: 1 erro" + other: "modelo não pode ser salvo: %{count} erros." body: "Por favor, verifique os seguintes campos:" messages: inclusion: "não está incluso na lista" @@ -134,7 +134,7 @@ empty: "não pode ficar vazio" blank: "não pode ficar vazio" too_long: "é muito longo (máximo: %{count} caracteres)" - too_short: "é muito curto (mínimon: %{count} caracteres)" + too_short: "é muito curto (mínimo: %{count} caracteres)" wrong_length: "deve ter %{count} caracteres" taken: "não está disponível" not_a_number: "não é um número" @@ -149,6 +149,7 @@ not_same_project: "não pertence ao mesmo projeto" circular_dependency: "Esta relação geraria uma dependência circular" cant_link_an_issue_with_a_descendant: "Uma tarefa não pode ser relaciona a uma de suas subtarefas" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Selecione @@ -181,7 +182,7 @@ notice_not_authorized: Você não está autorizado a acessar esta página. notice_email_sent: "Um e-mail foi enviado para %{value}" notice_email_error: "Ocorreu um erro ao enviar o e-mail (%{value})" - notice_feeds_access_key_reseted: Sua chave RSS foi reconfigurada. + notice_feeds_access_key_reseted: Sua chave Atom foi reconfigurada. notice_failed_to_save_issues: "Problema ao salvar %{count} tarefa(s) de %{total} selecionadas: %{ids}." notice_no_issue_selected: "Nenhuma tarefa selecionada! Por favor, marque as tarefas que você deseja editar." notice_account_pending: "Sua conta foi criada e está aguardando aprovação do administrador." @@ -206,8 +207,6 @@ mail_subject_reminder: "%{count} tarefa(s) com data prevista para os próximos %{days} dias" mail_body_reminder: "%{count} tarefa(s) para você com data prevista para os próximos %{days} dias:" - gui_validation_error: 1 erro - gui_validation_error_plural: "%{count} erros" field_name: Nome field_description: Descrição @@ -276,7 +275,7 @@ field_comments: Comentário field_url: URL field_start_page: Página inicial - field_subproject: Sub-projeto + field_subproject: Subprojeto field_hours: Horas field_activity: Atividade field_spent_on: Data @@ -295,7 +294,7 @@ field_parent_title: Página pai setting_app_title: Título da aplicação - setting_app_subtitle: Sub-título da aplicação + setting_app_subtitle: Subtítulo da aplicação setting_welcome_text: Texto de boas-vindas setting_default_language: Idioma padrão setting_login_required: Exigir autenticação @@ -311,8 +310,8 @@ setting_default_projects_public: Novos projetos são públicos por padrão setting_autofetch_changesets: Obter commits automaticamente setting_sys_api_enabled: Ativa WS para gerenciamento do repositório (SVN) - setting_commit_ref_keywords: Palavras de referência - setting_commit_fix_keywords: Palavras de fechamento + setting_commit_ref_keywords: Palavras-chave de referência + setting_commit_fix_keywords: Definição de palavras-chave setting_autologin: Auto-login setting_date_format: Formato da data setting_time_format: Formato de hora @@ -413,8 +412,8 @@ label_auth_source: Modo de autenticação label_auth_source_new: Novo modo de autenticação label_auth_source_plural: Modos de autenticação - label_subproject_plural: Sub-projetos - label_and_its_subprojects: "%{value} e seus sub-projetos" + label_subproject_plural: Subprojetos + label_and_its_subprojects: "%{value} e seus subprojetos" label_min_max_length: Tamanho mín-máx label_list: Lista label_date: Data @@ -425,8 +424,6 @@ label_text: Texto longo label_attribute: Atributo label_attribute_plural: Atributos - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: Nenhuma informação disponível label_change_status: Alterar situação label_history: Histórico @@ -526,8 +523,6 @@ label_repository: Repositório label_repository_plural: Repositórios label_browse: Procurar - label_modification: "%{count} alteração" - label_modification_plural: "%{count} alterações" label_revision: Revisão label_revision_plural: Revisões label_associated_revisions: Revisões associadas @@ -570,7 +565,7 @@ label_commits_per_month: Commits por mês label_commits_per_author: Commits por autor label_view_diff: Ver diferenças - label_diff_inline: inline + label_diff_inline: em linha label_diff_side_by_side: lado a lado label_options: Opções label_copy_workflow_from: Copiar fluxo de trabalho de @@ -614,16 +609,16 @@ label_language_based: Com base no idioma do usuário label_sort_by: "Ordenar por %{value}" label_send_test_email: Enviar um e-mail de teste - label_feeds_access_key_created_on: "chave de acesso RSS criada %{value} atrás" + label_feeds_access_key_created_on: "chave de acesso Atom criada %{value} atrás" label_module_plural: Módulos label_added_time_by: "Adicionado por %{author} %{age} atrás" label_updated_time: "Atualizado %{value} atrás" label_jump_to_a_project: Ir para o projeto... label_file_plural: Arquivos - label_changeset_plural: Changesets + label_changeset_plural: Conjunto de alterações label_default_columns: Colunas padrão label_no_change_option: (Sem alteração) - label_bulk_edit_selected_issues: Edição em massa das tarefas selecionados. + label_bulk_edit_selected_issues: Edição em massa das tarefas selecionadas. label_theme: Tema label_default: Padrão label_search_titles_only: Pesquisar somente títulos @@ -721,7 +716,7 @@ text_user_mail_option: "Para projetos (não selecionados), você somente receberá notificações sobre o que você está observando ou está envolvido (ex. tarefas das quais você é o autor ou que estão atribuídas a você)" text_no_configuration_data: "Os Papéis, tipos de tarefas, situação de tarefas e fluxos de trabalho não foram configurados ainda.\nÉ altamente recomendado carregar as configurações padrão. Você poderá modificar estas configurações assim que carregadas." text_load_default_configuration: Carregar a configuração padrão - text_status_changed_by_changeset: "Aplicado no changeset %{value}." + text_status_changed_by_changeset: "Aplicado no conjunto de alterações %{value}." text_issues_destroy_confirmation: 'Você tem certeza que deseja excluir a(s) tarefa(s) selecionada(s)?' text_select_project_modules: 'Selecione módulos para habilitar para este projeto:' text_default_administrator_account_changed: Conta padrão do administrador alterada @@ -768,10 +763,10 @@ permission_view_files: Ver arquivos permission_edit_issues: Editar tarefas permission_edit_own_time_entries: Editar o próprio tempo de trabalho - permission_manage_public_queries: Gerenciar consultas publicas + permission_manage_public_queries: Gerenciar consultas públicas permission_add_issues: Adicionar tarefas permission_log_time: Adicionar tempo gasto - permission_view_changesets: Ver changesets + permission_view_changesets: Ver conjunto de alterações permission_view_time_entries: Ver tempo gasto permission_manage_versions: Gerenciar versões permission_manage_wiki: Gerenciar wiki @@ -780,7 +775,6 @@ permission_comment_news: Comentar notícias permission_delete_messages: Excluir mensagens permission_select_project_modules: Selecionar módulos de projeto - permission_manage_documents: Gerenciar documentos permission_edit_wiki_pages: Editar páginas wiki permission_add_issue_watchers: Adicionar observadores permission_view_gantt: Ver gráfico gantt @@ -801,7 +795,7 @@ permission_delete_issues: Excluir tarefas permission_view_issue_watchers: Ver lista de observadores permission_manage_repository: Gerenciar repositório - permission_commit_access: Acesso de commit + permission_commit_access: Acesso do commit permission_browse_repository: Pesquisar repositório permission_view_documents: Ver documentos permission_edit_project: Editar projeto @@ -827,7 +821,7 @@ label_display: Exibição field_editable: Editável setting_repository_log_display_limit: Número máximo de revisões exibidas no arquivo de log - setting_file_max_size_displayed: Tamanho máximo dos arquivos textos exibidos inline + setting_file_max_size_displayed: Tamanho máximo dos arquivos textos exibidos em linha field_identity_urler: Observador setting_openid: Permitir Login e Registro via OpenID field_identity_url: OpenID URL @@ -854,8 +848,8 @@ permission_add_project: Criar projeto setting_new_project_user_role_id: Papel atribuído a um usuário não-administrador que cria um projeto label_view_all_revisions: Ver todas as revisões - label_tag: Etiqueta - label_branch: Ramo + label_tag: Tag + label_branch: Branch text_journal_changed: "%{label} alterado de %{old} para %{new}" text_journal_set_to: "%{label} ajustado para %{value}" text_journal_deleted: "%{label} excluído (%{old})" @@ -868,13 +862,13 @@ enumeration_system_activity: Atividade do sistema permission_delete_issue_watchers: Excluir observadores version_status_closed: fechado - version_status_locked: travado + version_status_locked: bloqueado version_status_open: aberto error_can_not_reopen_issue_on_closed_version: Uma tarefa atribuída a uma versão fechada não pode ser reaberta label_user_anonymous: Anônimo button_move_and_follow: Mover e seguir setting_default_projects_modules: Módulos habilitados por padrão para novos projetos - setting_gravatar_default: Imagem-padrão de Gravatar + setting_gravatar_default: Imagem-padrão do Gravatar field_sharing: Compartilhamento label_version_sharing_hierarchy: Com a hierarquia do projeto label_version_sharing_system: Com todos os projetos @@ -887,12 +881,12 @@ label_copy_source: Origem setting_issue_done_ratio: Calcular o percentual de conclusão da tarefa setting_issue_done_ratio_issue_status: Usar a situação da tarefa - error_issue_done_ratios_not_updated: O pecentual de conclusão das tarefas não foi atualizado. + error_issue_done_ratios_not_updated: O percentual de conclusão das tarefas não foi atualizado. error_workflow_copy_target: Por favor, selecione os tipos de tarefa e os papéis alvo setting_issue_done_ratio_issue_field: Use o campo da tarefa label_copy_same_as_target: Mesmo alvo label_copy_target: Alvo - notice_issue_done_ratios_updated: Percentual de conslusão atualizados. + notice_issue_done_ratios_updated: Percentual de conclusão atualizados. error_workflow_copy_source: Por favor, selecione um tipo de tarefa e papel de origem label_update_issue_done_ratios: Atualizar percentual de conclusão das tarefas setting_start_of_week: Início da semana @@ -903,21 +897,21 @@ label_api_access_key: Chave de acesso a API button_show: Exibir label_api_access_key_created_on: Chave de acesso a API criado a %{value} atrás - label_feeds_access_key: Chave de acesso ao RSS + label_feeds_access_key: Chave de acesso ao Atom notice_api_access_key_reseted: Sua chave de acesso a API foi redefinida. - setting_rest_api_enabled: Habilitar REST web service + setting_rest_api_enabled: Habilitar a api REST label_missing_api_access_key: Chave de acesso a API faltando - label_missing_feeds_access_key: Chave de acesso ao RSS faltando + label_missing_feeds_access_key: Chave de acesso ao Atom faltando text_line_separated: Múltiplos valores permitidos (uma linha para cada valor). setting_mail_handler_body_delimiters: Truncar e-mails após uma destas linhas permission_add_subprojects: Criar subprojetos label_subproject_new: Novo subprojeto text_own_membership_delete_confirmation: |- - Você está para excluir algumas de suas próprias permissões e pode não mais estar apto a editar este projeto após esta operação. + Você irá excluir algumas de suas próprias permissões e não estará mais apto a editar este projeto após esta operação. Você tem certeza que deseja continuar? label_close_versions: Fechar versões concluídas label_board_sticky: Marcado - label_board_locked: Travado + label_board_locked: Bloqueado permission_export_wiki_pages: Exportar páginas wiki setting_cache_formatted_text: Realizar cache de texto formatado permission_manage_project_activities: Gerenciar atividades do projeto @@ -933,7 +927,7 @@ error_can_not_delete_tracker: Este tipo de tarefa está atribuído a alguma(s) tarefa(s) e não pode ser excluído. field_principal: Principal label_my_page_block: Meu bloco de página - notice_failed_to_save_members: "Falha ao gravar membro(s): %{errors}." + notice_failed_to_save_members: "Falha ao salvar membro(s): %{errors}." text_zoom_out: Afastar zoom text_zoom_in: Aproximar zoom notice_unable_delete_time_entry: Não foi possível excluir a entrada no registro de horas trabalhadas. @@ -948,7 +942,7 @@ label_user_mail_option_only_my_events: Somente para as coisas que eu esteja observando ou esteja envolvido label_user_mail_option_only_assigned: Somente para as coisas que estejam atribuídas a mim label_user_mail_option_none: Sem eventos - field_member_of_group: Grupo do responsável + field_member_of_group: Responsável pelo grupo field_assigned_to_role: Papel do responsável notice_not_authorized_archived_project: O projeto que você está tentando acessar foi arquivado. label_principal_search: "Pesquisar por usuários ou grupos:" @@ -956,12 +950,12 @@ field_visible: Visível setting_emails_header: Cabeçalho do e-mail setting_commit_logtime_activity_id: Atividade para registrar horas - text_time_logged_by_changeset: Aplicado no changeset %{value}. + text_time_logged_by_changeset: Aplicado no conjunto de alterações %{value}. setting_commit_logtime_enabled: Habilitar registro de horas notice_gantt_chart_truncated: O gráfico foi cortado por exceder o tamanho máximo de linhas que podem ser exibidas (%{max}) - setting_gantt_items_limit: Número máximo de itens exibidos no gráfico gatt + setting_gantt_items_limit: Número máximo de itens exibidos no gráfico gantt field_warn_on_leaving_unsaved: Alertar-me ao sair de uma página sem salvar o texto - text_warn_on_leaving_unsaved: A página atual contem texto que não foi salvo e será perdido se você sair desta página. + text_warn_on_leaving_unsaved: A página atual contém texto que não foi salvo e será perdido se você sair desta página. label_my_queries: Minhas consultas personalizadas text_journal_changed_no_detail: "%{label} atualizado(a)" label_news_comment_added: Notícia recebeu um comentário @@ -1001,30 +995,30 @@ label_diff: diff text_git_repository_note: "Repositório esta vazio e é local (ex: /gitrepo, c:\\gitrepo)" - description_query_sort_criteria_direction: Direção da ordenação + description_query_sort_criteria_direction: Escolher ordenação description_project_scope: Escopo da pesquisa description_filter: Filtro description_user_mail_notification: Configuração de notificações por e-mail - description_date_from: Digita a data inicial + description_date_from: Digite a data inicial description_message_content: Conteúdo da mensagem description_available_columns: Colunas disponíveis description_date_range_interval: Escolha um período selecionando a data de início e fim description_issue_category_reassign: Escolha uma categoria de tarefas - description_search: Searchfield + description_search: Campo de busca description_notes: Notas - description_date_range_list: Escolha um período a partira da lista + description_date_range_list: Escolha um período a partir da lista description_choose_project: Projetos description_date_to: Digite a data final description_query_sort_criteria_attribute: Atributo de ordenação description_wiki_subpages_reassign: Escolha uma nova página pai description_selected_columns: Colunas selecionadas - label_parent_revision: Pais - label_child_revision: Filhos + label_parent_revision: Pai + label_child_revision: Filho error_scm_annotate_big_text_file: A entrada não pode ser anotada, pois excede o tamanho máximo do arquivo de texto. setting_default_issue_start_date_to_creation_date: Usar data corrente como data inicial para novas tarefas button_edit_section: Editar esta seção - setting_repositories_encodings: Encoding dos repositórios e anexos + setting_repositories_encodings: Codificação dos repositórios e anexos description_all_columns: Todas as colunas button_export: Exportar label_export_options: "Opções de exportação %{export_format}" @@ -1038,40 +1032,40 @@ field_repository_is_default: Repositório principal label_copy_attachments: Copiar anexos label_item_position: "%{position}/%{count}" - label_completed_versions: Versões completadas - text_project_identifier_info: Somente letras minúsculas (az), números, traços e sublinhados são permitidos.
    Uma vez salvo, o identificador não pode ser alterado. - field_multiple: Multiplos valores + label_completed_versions: Versões concluídas + text_project_identifier_info: Somente letras minúsculas (a-z), números, traços e sublinhados são permitidos.
    Uma vez salvo, o identificador não pode ser alterado. + field_multiple: Múltiplos valores setting_commit_cross_project_ref: Permitir que tarefas de todos os outros projetos sejam refenciadas e resolvidas - text_issue_conflict_resolution_add_notes: Adicione minhas anotações e descartar minhas outras mudanças - text_issue_conflict_resolution_overwrite: Aplicar as minhas alterações de qualquer maneira (notas anteriores serão mantidos, mas algumas mudanças podem ser substituídos) + text_issue_conflict_resolution_add_notes: Adicionar minhas anotações e descartar minhas outras mudanças + text_issue_conflict_resolution_overwrite: Aplicar as minhas alterações de qualquer maneira (notas anteriores serão mantidas, mas algumas mudanças podem ser substituídas) notice_issue_update_conflict: A tarefa foi atualizada por um outro usuário, enquanto você estava editando. - text_issue_conflict_resolution_cancel: Descartar todas as minhas mudanças e re-exibir %{link} + text_issue_conflict_resolution_cancel: Descartar todas as minhas mudanças e reexibir %{link} permission_manage_related_issues: Gerenciar tarefas relacionadas field_auth_source_ldap_filter: Filtro LDAP label_search_for_watchers: Procurar por outros observadores para adiconar notice_account_deleted: Sua conta foi excluída permanentemente. - setting_unsubscribe: Permitir aos usuários excluir sua conta própria + setting_unsubscribe: Permitir aos usuários excluir sua própria conta button_delete_my_account: Excluir minha conta text_account_destroy_confirmation: |- - Tem certeza de que quer continuar? - Sua conta será excluída permanentemente, sem qualquer forma de reativá-lo. + Tem certeza que quer continuar? + Sua conta será excluída permanentemente, sem qualquer forma de reativá-la. error_session_expired: A sua sessão expirou. Por favor, faça login novamente. text_session_expiration_settings: "Aviso: a alteração dessas configurações pode expirar as sessões atuais, incluindo a sua." setting_session_lifetime: duração máxima da sessão setting_session_timeout: tempo limite de inatividade da sessão label_session_expiration: "Expiração da sessão" permission_close_project: Fechar / reabrir o projeto - label_show_closed_projects: Visualização de projetos fechados + label_show_closed_projects: Visualizar projetos fechados button_close: Fechar button_reopen: Reabrir project_status_active: ativo project_status_closed: fechado project_status_archived: arquivado - text_project_closed: Este projeto é fechado e somente leitura. + text_project_closed: Este projeto está fechado e somente leitura. notice_user_successful_create: Usuário %{id} criado. field_core_fields: campos padrão field_timeout: Tempo de espera (em segundos) - setting_thumbnails_enabled: exibir miniaturas de anexos + setting_thumbnails_enabled: Exibir miniaturas de anexos setting_thumbnails_size: Tamanho das miniaturas (em pixels) label_status_transitions: Estados das transições label_fields_permissions: Permissões de campos @@ -1081,25 +1075,49 @@ field_board_parent: Fórum Pai label_attribute_of_project: "Projeto %{name}" label_attribute_of_author: "autor %{name}" - label_attribute_of_assigned_to: "atribuído %{name}" - label_attribute_of_fixed_version: "versão alvo %{name}" - label_copy_subtasks: Copiar sub-tarefas + label_attribute_of_assigned_to: "atribuído a %{name}" + label_attribute_of_fixed_version: "versão %{name}" + label_copy_subtasks: Copiar subtarefas label_copied_to: copiada label_copied_from: copiado - label_any_issues_in_project: quaisquer problemas em projeto - label_any_issues_not_in_project: todas as questões que não estão em projeto + label_any_issues_in_project: qualquer tarefa do projeto + label_any_issues_not_in_project: qualquer tarefa que não está no projeto field_private_notes: notas privadas permission_view_private_notes: Ver notas privadas - permission_set_notes_private: Defina notas como privada - label_no_issues_in_project: sem problemas em projeto + permission_set_notes_private: Permitir alterar notas para privada + label_no_issues_in_project: sem tarefas no projeto label_any: todos label_last_n_weeks: "últimas %{count} semanas" - setting_cross_project_subtasks: Permitir cruzamento de sub-tarefas entre projetos - label_cross_project_descendants: Com sub-projetos - label_cross_project_tree: Com a árvore do projeto - label_cross_project_hierarchy: Com a hierarquia do projeto - label_cross_project_system: Com todos os projetos - button_hide: Esconder + setting_cross_project_subtasks: Permitir subtarefas entre projetos + label_cross_project_descendants: com subprojetos + label_cross_project_tree: Com a árvore do Projeto + label_cross_project_hierarchy: Com uma hierarquia do Projeto + label_cross_project_system: Com todos os Projetos + button_hide: Omitir setting_non_working_week_days: dias não úteis - label_in_the_next_days: na próxima - label_in_the_past_days: no passado + label_in_the_next_days: nos próximos dias + label_in_the_past_days: nos dias anteriores + label_attribute_of_user: Usuário %{name} + text_turning_multiple_off: Se você desativar vários valores, eles serão removidos, a fim de preservar somente um valor por item. + label_attribute_of_issue: Tarefa %{name} + permission_add_documents: Adicionar documentos + permission_edit_documents: Editar documentos + permission_delete_documents: Excluir documentos + label_gantt_progress_line: Linha de progresso + setting_jsonp_enabled: Ativar suporte JSONP + field_inherit_members: Herdar membros + field_closed_on: Concluído + field_generate_password: Gerar senha + setting_default_projects_tracker_ids: Tipos padrões para novos projeto + label_total_time: Total + notice_account_not_activated_yet: Sua conta ainda não foi ativada. Se você deseja receber + um novo email de ativação, por favor clique aqui. + notice_account_locked: Sua conta está bloqueada. + label_hidden: Visibilidade + label_visibility_private: para mim + label_visibility_roles: para os papéis + label_visibility_public: para qualquer usuário + field_must_change_passwd: É necessário alterar sua senha na próxima vez que tentar acessar sua conta + notice_new_password_must_be_different: A nova senha deve ser diferente da senha atual + setting_mail_handler_excluded_filenames: Exclui anexos por nome + text_convert_available: Conversor ImageMagick disponível (opcional) diff -r d98d22a98252 -r afce8026aaeb config/locales/pt.yml --- a/config/locales/pt.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/pt.yml Tue Sep 09 09:34:53 2014 +0100 @@ -137,6 +137,7 @@ not_same_project: "não pertence ao mesmo projecto" circular_dependency: "Esta relação iria criar uma dependência circular" cant_link_an_issue_with_a_descendant: "Não é possível ligar uma tarefa a uma sub-tarefa que lhe é pertencente" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" ## Translated by: Pedro Araújo actionview_instancetag_blank_option: Seleccione @@ -156,7 +157,6 @@ notice_account_invalid_creditentials: Utilizador ou palavra-chave inválidos. notice_account_password_updated: A palavra-chave foi alterada com sucesso. notice_account_wrong_password: Palavra-chave errada. - notice_account_register_done: A conta foi criada com sucesso. notice_account_unknown_email: Utilizador desconhecido. notice_can_t_change_password: Esta conta utiliza uma fonte de autenticação externa. Não é possível alterar a palavra-chave. notice_account_lost_email_sent: Foi-lhe enviado um e-mail com as instruções para escolher uma nova palavra-chave. @@ -170,7 +170,7 @@ notice_not_authorized: Não está autorizado a visualizar esta página. notice_email_sent: "Foi enviado um e-mail para %{value}" notice_email_error: "Ocorreu um erro ao enviar o e-mail (%{value})" - notice_feeds_access_key_reseted: A sua chave de RSS foi inicializada. + notice_feeds_access_key_reseted: A sua chave de Atom foi inicializada. notice_failed_to_save_issues: "Não foi possível guardar %{count} tarefa(s) das %{total} seleccionadas: %{ids}." notice_no_issue_selected: "Nenhuma tarefa seleccionada! Por favor, seleccione as tarefas que quer editar." notice_account_pending: "A sua conta foi criada e está agora à espera de aprovação do administrador." @@ -194,8 +194,6 @@ mail_subject_reminder: "%{count} tarefa(s) para entregar nos próximos %{days} dias" mail_body_reminder: "%{count} tarefa(s) que estão atribuídas a si estão agendadas para estarem completas nos próximos %{days} dias:" - gui_validation_error: 1 erro - gui_validation_error_plural: "%{count} erros" field_name: Nome field_description: Descrição @@ -410,8 +408,6 @@ label_text: Texto longo label_attribute: Atributo label_attribute_plural: Atributos - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: Sem dados para mostrar label_change_status: Mudar estado label_history: Histórico @@ -511,8 +507,6 @@ label_repository: Repositório label_repository_plural: Repositórios label_browse: Navegar - label_modification: "%{count} alteração" - label_modification_plural: "%{count} alterações" label_revision: Revisão label_revision_plural: Revisões label_associated_revisions: Revisões associadas @@ -601,7 +595,7 @@ label_language_based: Baseado na língua do utilizador label_sort_by: "Ordenar por %{value}" label_send_test_email: enviar um e-mail de teste - label_feeds_access_key_created_on: "Chave RSS criada há %{value} atrás" + label_feeds_access_key_created_on: "Chave Atom criada há %{value} atrás" label_module_plural: Módulos label_added_time_by: "Adicionado por %{author} há %{age} atrás" label_updated_time: "Alterado há %{value} atrás" @@ -764,7 +758,6 @@ permission_comment_news: Comentar notícias permission_delete_messages: Apagar mensagens permission_select_project_modules: Seleccionar módulos do projecto - permission_manage_documents: Gerir documentos permission_edit_wiki_pages: Editar páginas de wiki permission_add_issue_watchers: Adicionar observadores permission_view_gantt: ver diagrama de Gantt @@ -887,11 +880,11 @@ label_revision_id: Revisão %{value} label_api_access_key: Chave de acesso API label_api_access_key_created_on: Chave de acesso API criada há %{value} - label_feeds_access_key: Chave de acesso RSS + label_feeds_access_key: Chave de acesso Atom notice_api_access_key_reseted: A sua chave de acesso API foi reinicializada. setting_rest_api_enabled: Activar serviço Web REST label_missing_api_access_key: Chave de acesso API em falta - label_missing_feeds_access_key: Chave de acesso RSS em falta + label_missing_feeds_access_key: Chave de acesso Atom em falta button_show: Mostrar text_line_separated: Vários valores permitidos (uma linha para cada valor). setting_mail_handler_body_delimiters: Truncar mensagens de correio electrónico após uma destas linhas @@ -943,7 +936,7 @@ setting_commit_logtime_activity_id: Actividade para tempo registado text_time_logged_by_changeset: Aplicado no conjunto de alterações %{value}. setting_commit_logtime_enabled: Activar registo de tempo - notice_gantt_chart_truncated: O gráfico foi truncado porque excede o número máximo de itens visível (%{máx.}) + notice_gantt_chart_truncated: O gráfico foi truncado porque excede o número máximo de itens visíveis (%{max.}) setting_gantt_items_limit: Número máximo de itens exibidos no gráfico Gantt field_warn_on_leaving_unsaved: Avisar-me quando deixar uma página com texto por salvar text_warn_on_leaving_unsaved: A página actual contém texto por salvar que será perdido caso saia desta página. @@ -1016,7 +1009,7 @@ error_attachment_too_big: Este ficheiro não pode ser carregado pois excede o tamanho máximo permitido por ficheiro (%{max_size}) notice_failed_to_save_time_entries: "Falha ao guardar %{count} registo(s) de tempo dos %{total} seleccionados: %{ids}." label_x_issues: - zero: 0 tarefa + zero: 0 tarefas one: 1 tarefa other: "%{count} tarefas" label_repository_new: Novo repositório @@ -1088,3 +1081,30 @@ setting_non_working_week_days: Dias não úteis label_in_the_next_days: no futuro label_in_the_past_days: no passado + label_attribute_of_user: Do utilizador %{name} + text_turning_multiple_off: Se desactivar a escolha múltipla, + a escolha múltipla será apagada de modo a manter apenas um valor por item. + label_attribute_of_issue: Tarefa de %{name} + permission_add_documents: Adicionar documentos + permission_edit_documents: Editar documentos + permission_delete_documents: Apagar documentos + label_gantt_progress_line: Barra de progresso + setting_jsonp_enabled: Activar suporte JSONP + field_inherit_members: Herdar membros + field_closed_on: Fechado + field_generate_password: Gerar palavra-chave + setting_default_projects_tracker_ids: Tipo de tarefa padrão para novos projectos + label_total_time: Total + notice_account_not_activated_yet: Ainda não activou a sua conta. Se quiser + receber um novo email de activação, por favor carregue nesta ligação. + notice_account_locked: A sua conta está bloqueada. + notice_account_register_done: A conta foi criada com sucesso. Um email contendo + as instruções para activar a sua conta foi enviado para %{email}. + label_hidden: Escondido + label_visibility_private: apenas para mim + label_visibility_roles: apenas para estas funções + label_visibility_public: para qualquer utilizador + field_must_change_passwd: Tem que alterar a palavra-passe no próximo início de sessão + notice_new_password_must_be_different: A palavra-passe tem de ser diferente da actual + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/ro.yml --- a/config/locales/ro.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/ro.yml Tue Sep 09 09:34:53 2014 +0100 @@ -10,10 +10,8 @@ day_names: [Duminică, Luni, Marti, Miercuri, Joi, Vineri, Sâmbătă] abbr_day_names: [Dum, Lun, Mar, Mie, Joi, Vin, Sâm] - # Don't forget the nil at the beginning; there's no such thing as a 0th month month_names: [~, Ianuarie, Februarie, Martie, Aprilie, Mai, Iunie, Iulie, August, Septembrie, Octombrie, Noiembrie, Decembrie] abbr_month_names: [~, Ian, Feb, Mar, Apr, Mai, Iun, Iul, Aug, Sep, Oct, Noi, Dec] - # Used in date_select and datime_select. order: - :day - :month @@ -47,8 +45,8 @@ one: "aproximativ o oră" other: "aproximativ %{count} ore" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 oră" + other: "%{count} ore" x_days: one: "o zi" other: "%{count} zile" @@ -126,6 +124,7 @@ not_same_project: "trebuie să aparÈ›ină aceluiaÈ™i proiect" circular_dependency: "Această relaÈ›ie ar crea o dependență circulară" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: SelectaÈ›i @@ -158,7 +157,7 @@ notice_not_authorized: Nu sunteÈ›i autorizat sa accesaÈ›i această pagină. notice_email_sent: "S-a trimis un email către %{value}" notice_email_error: "A intervenit o eroare la trimiterea de email (%{value})" - notice_feeds_access_key_reseted: Cheia de acces RSS a fost resetată. + notice_feeds_access_key_reseted: Cheia de acces Atom a fost resetată. notice_failed_to_save_issues: "Nu s-au putut salva %{count} tichete din cele %{total} selectate: %{ids}." notice_no_issue_selected: "Niciun tichet selectat! Vă rugăm să selectaÈ›i tichetele pe care doriÈ›i să le editaÈ›i." notice_account_pending: "Contul dumneavoastră a fost creat È™i aÈ™teaptă aprobarea administratorului." @@ -184,8 +183,6 @@ mail_subject_reminder: "%{count} tichete trebuie rezolvate în următoarele %{days} zile" mail_body_reminder: "%{count} tichete atribuite dumneavoastră trebuie rezolvate în următoarele %{days} zile:" - gui_validation_error: o eroare - gui_validation_error_plural: "%{count} erori" field_name: Nume field_description: Descriere @@ -342,7 +339,6 @@ permission_edit_own_time_entries: Editează jurnalele proprii cu timpul de lucru permission_manage_news: Editează È™tiri permission_comment_news: Comentează È™tirile - permission_manage_documents: Editează documente permission_view_documents: AfiÈ™ează documente permission_manage_files: Editează fiÈ™iere permission_view_files: AfiÈ™ează fiÈ™iere @@ -461,8 +457,6 @@ label_text: Text lung label_attribute: Atribut label_attribute_plural: Atribute - label_download: "%{count} descărcare" - label_download_plural: "%{count} descărcări" label_no_data: Nu există date de afiÈ™at label_change_status: Schimbă starea label_history: Istoric @@ -562,8 +556,6 @@ label_repository: Depozit label_repository_plural: Depozite label_browse: AfiÈ™ează - label_modification: "%{count} schimbare" - label_modification_plural: "%{count} schimbări" label_revision: Revizie label_revision_plural: Revizii label_associated_revisions: Revizii asociate @@ -878,11 +870,11 @@ label_revision_id: Revision %{value} label_api_access_key: API access key label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key + label_feeds_access_key: Atom access key notice_api_access_key_reseted: Your API access key was reset. setting_rest_api_enabled: Enable REST web service label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key + label_missing_feeds_access_key: Missing a Atom access key button_show: Show text_line_separated: Multiple values allowed (one line for each value). setting_mail_handler_body_delimiters: Truncate emails after one of these lines @@ -930,7 +922,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -971,8 +962,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1079,3 +1068,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/ru.yml --- a/config/locales/ru.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/ru.yml Tue Sep 09 09:34:53 2014 +0100 @@ -117,8 +117,10 @@ many: "около %{count} чаÑов" other: "около %{count} чаÑа" x_hours: - one: "1 чаÑ" - other: "%{count} чаÑов" + one: "%{count} чаÑ" + few: "%{count} чаÑа" + many: "%{count} чаÑов" + other: "%{count} чаÑа" x_days: one: "%{count} день" few: "%{count} днÑ" @@ -145,7 +147,7 @@ many: "больше %{count} лет" other: "больше %{count} лет" almost_x_years: - one: "почти 1 год" + one: "почти %{count} год" few: "почти %{count} года" many: "почти %{count} лет" other: "почти %{count} года" @@ -204,6 +206,7 @@ not_same_project: "не отноÑитÑÑ Ðº одному проекту" circular_dependency: "Ð¢Ð°ÐºÐ°Ñ ÑвÑзь приведет к цикличеÑкой завиÑимоÑти" cant_link_an_issue_with_a_descendant: "Задача не может быть ÑвÑзана Ñо Ñвоей подзадачей" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" support: array: @@ -399,11 +402,6 @@ general_text_yes: 'да' general_text_Yes: 'Да' - gui_validation_error: 1 ошибка - gui_validation_error_plural: "%{count} ошибок" - gui_validation_error_plural2: "%{count} ошибки" - gui_validation_error_plural5: "%{count} ошибок" - label_activity: ДейÑÑ‚Ð²Ð¸Ñ label_add_another_file: Добавить ещё один файл label_added_time_by: "Добавил(а) %{author} %{age} назад" @@ -490,10 +488,6 @@ label_document_added: Добавлен документ label_document_new: Ðовый документ label_document_plural: Документы - label_download: "%{count} загрузка" - label_download_plural: "%{count} Ñкачиваний" - label_download_plural2: "%{count} загрузки" - label_download_plural5: "%{count} загрузок" label_downloads_abbr: Скачиваний label_duplicated_by: дублируетÑÑ label_duplicates: дублирует @@ -505,8 +499,8 @@ label_equals: ÑоответÑтвует label_example: Пример label_export_to: ЭкÑпортировать в - label_feed_plural: RSS - label_feeds_access_key_created_on: "Ключ доÑтупа RSS Ñоздан %{value} назад" + label_feed_plural: Atom + label_feeds_access_key_created_on: "Ключ доÑтупа Atom Ñоздан %{value} назад" label_f_hour: "%{value} чаÑ" label_f_hour_plural: "%{value} чаÑов" label_file_added: Добавлен файл @@ -576,10 +570,6 @@ label_message_posted: Добавлено Ñообщение label_me: мне label_min_max_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ - макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° - label_modification: "%{count} изменение" - label_modification_plural: "%{count} изменений" - label_modification_plural2: "%{count} изменениÑ" - label_modification_plural5: "%{count} изменений" label_modified: изменено label_module_plural: Модули label_months_from: меÑÑцев(ца) Ñ @@ -729,31 +719,31 @@ label_workflow: ПоÑледовательноÑть дейÑтвий label_x_closed_issues_abbr: zero: "0 закрыто" - one: "1 закрыт" + one: "%{count} закрыта" few: "%{count} закрыто" many: "%{count} закрыто" other: "%{count} закрыто" label_x_comments: zero: "нет комментариев" - one: "1 комментарий" + one: "%{count} комментарий" few: "%{count} комментариÑ" many: "%{count} комментариев" other: "%{count} комментариев" label_x_open_issues_abbr: zero: "0 открыто" - one: "1 открыт" + one: "%{count} открыта" few: "%{count} открыто" many: "%{count} открыто" other: "%{count} открыто" label_x_open_issues_abbr_on_total: zero: "0 открыто / %{total}" - one: "1 открыт / %{total}" + one: "%{count} открыта / %{total}" few: "%{count} открыто / %{total}" many: "%{count} открыто / %{total}" other: "%{count} открыто / %{total}" label_x_projects: zero: "нет проектов" - one: "1 проект" + one: "%{count} проект" few: "%{count} проекта" many: "%{count} проектов" other: "%{count} проектов" @@ -786,7 +776,7 @@ notice_email_sent: "Отправлено пиÑьмо %{value}" notice_failed_to_save_issues: "Ðе удалоÑÑŒ Ñохранить %{count} пункт(ов) из %{total} выбранных: %{ids}." notice_failed_to_save_members: "Ðе удалоÑÑŒ Ñохранить учаÑтника(ов): %{errors}." - notice_feeds_access_key_reseted: Ваш ключ доÑтупа RSS был Ñброшен. + notice_feeds_access_key_reseted: Ваш ключ доÑтупа Atom был Ñброшен. notice_file_not_found: Страница, на которую Ð’Ñ‹ пытаетеÑÑŒ зайти, не ÑущеÑтвует или удалена. notice_locking_conflict: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð° другим пользователем. notice_no_issue_selected: "Ðе выбрано ни одной задачи! ПожалуйÑта, отметьте задачи, которые Ð’Ñ‹ хотите отредактировать." @@ -825,7 +815,6 @@ permission_manage_project_activities: Управление типами дейÑтвий Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° permission_manage_boards: Управление форумами permission_manage_categories: Управление категориÑми задач - permission_manage_documents: Управление документами permission_manage_files: Управление файлами permission_manage_issue_relations: Управление ÑвÑзыванием задач permission_manage_members: Управление учаÑтниками @@ -879,7 +868,7 @@ setting_display_subprojects_issues: Отображение подпроектов по умолчанию setting_emails_footer: ПодÑтрочные Ð¿Ñ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð¿Ð¸Ñьма setting_enabled_scm: Включённые SCM - setting_feeds_limit: Ограничение количеÑтва заголовков Ð´Ð»Ñ RSS потока + setting_feeds_limit: Ограничение количеÑтва заголовков Ð´Ð»Ñ Atom потока setting_file_max_size_displayed: МакÑимальный размер текÑтового файла Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ setting_gravatar_enabled: ИÑпользовать аватар Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· Gravatar setting_host_name: Ð˜Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð° @@ -920,7 +909,7 @@ text_email_delivery_not_configured: "Параметры работы Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ñ‹Ð¼ Ñервером не наÑтроены и Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ email не активна.\nÐаÑтроить параметры Ð´Ð»Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ SMTP-Ñервера Ð’Ñ‹ можете в файле config/configuration.yml. Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ перезапуÑтите приложение." text_enumeration_category_reassign_to: 'Ðазначить им Ñледующее значение:' text_enumeration_destroy_question: "%{count} объект(а,ов) ÑвÑзаны Ñ Ñтим значением." - text_file_repository_writable: Хранилище Ñ Ð´Ð¾Ñтупом на запиÑÑŒ + text_file_repository_writable: Хранилище файлов доÑтупно Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи text_issue_added: "Создана Ð½Ð¾Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° %{id} (%{author})." text_issue_category_destroy_assignments: Удалить Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ð¸ text_issue_category_destroy_question: "ÐеÑколько задач (%{count}) назначено в данную категорию. Что Ð’Ñ‹ хотите предпринÑть?" @@ -935,7 +924,7 @@ text_load_default_configuration: Загрузить конфигурацию по умолчанию text_min_max_length_info: 0 означает отÑутÑтвие ограничений text_no_configuration_data: "Роли, трекеры, ÑтатуÑÑ‹ задач и оперативный план не были Ñконфигурированы.\nÐаÑтоÑтельно рекомендуетÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ конфигурацию по-умолчанию. Ð’Ñ‹ Ñможете её изменить потом." - text_plugin_assets_writable: Каталог модулей доÑтупен Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи + text_plugin_assets_writable: Каталог реÑурÑов модулей доÑтупен Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи text_project_destroy_confirmation: Ð’Ñ‹ наÑтаиваете на удалении данного проекта и вÑей отноÑÑщейÑÑ Ðº нему информации? text_reassign_time_entries: 'ПеренеÑти зарегиÑтрированное Ð²Ñ€ÐµÐ¼Ñ Ð½Ð° Ñледующую задачу:' text_regexp_info: "например: ^[A-Z0-9]+$" @@ -1018,12 +1007,12 @@ permission_view_issues: ПроÑмотр задач label_display_used_statuses_only: Отображать только те ÑтатуÑÑ‹, которые иÑпользуютÑÑ Ð² Ñтом трекере label_api_access_key_created_on: Ключ доÑтуп к API был Ñоздан %{value} назад - label_feeds_access_key: Ключ доÑтупа к RSS + label_feeds_access_key: Ключ доÑтупа к Atom notice_api_access_key_reseted: Ваш ключ доÑтупа к API был Ñброшен. setting_rest_api_enabled: Включить веб-ÑÐµÑ€Ð²Ð¸Ñ REST button_show: Показать label_missing_api_access_key: ОтÑутÑтвует ключ доÑтупа к API - label_missing_feeds_access_key: ОтÑутÑтвует ключ доÑтупа к RSS + label_missing_feeds_access_key: ОтÑутÑтвует ключ доÑтупа к Atom setting_mail_handler_body_delimiters: Урезать пиÑьмо поÑле одной из Ñтих Ñтрок permission_add_subprojects: Создание подпроектов label_subproject_new: Ðовый подпроект @@ -1093,17 +1082,17 @@ label_between: между setting_issue_group_assignment: Разрешить назначение задач группам пользователей label_diff: Разница(diff) - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + text_git_repository_note: Хранилище пуÑтое и локальное (Ñ‚.е. /gitrepo, c:\gitrepo) description_query_sort_criteria_direction: ПорÑдок Ñортировки - description_project_scope: Search scope + description_project_scope: ОблаÑть поиÑка description_filter: Фильтр - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield + description_user_mail_notification: ÐаÑтройки почтовых оповещений + description_date_from: Введите дату начала + description_message_content: Содержание ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + description_available_columns: ДоÑтупные Ñтолбцы + description_date_range_interval: Выберите диапазон, уÑтановив дату начала и дату Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ + description_issue_category_reassign: Выберите категорию задачи + description_search: Поле поиÑка description_notes: ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ description_date_range_list: Выберите диапазон из ÑпиÑка description_choose_project: Проекты @@ -1123,11 +1112,10 @@ error_attachment_too_big: Этот файл Ð½ÐµÐ»ÑŒÐ·Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ из-за Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸Ñ Ð¼Ð°ÐºÑимального размера файла (%{max_size}) notice_failed_to_save_time_entries: "Ðевозможно Ñохранить %{count} затраченное Ð²Ñ€ÐµÐ¼Ñ Ð´Ð»Ñ %{total} выбранных: %{ids}." label_x_issues: - zero: 0 Задач - one: 1 Задача - few: "%{count} Задач" - many: "%{count} Задач" - other: "%{count} Задач" + one: "%{count} задача" + few: "%{count} задачи" + many: "%{count} задач" + other: "%{count} Задачи" label_repository_new: Ðовое хранилище field_repository_is_default: Хранилище по умолчанию label_copy_attachments: Копировать Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ @@ -1143,14 +1131,14 @@ permission_manage_related_issues: Управление ÑвÑзанными задачами field_auth_source_ldap_filter: Фильтр LDAP label_search_for_watchers: Ðайти наблюдателей - notice_account_deleted: "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ полноÑтью удалена" - setting_unsubscribe: "Разрешить пользователÑм удалÑть Ñвои учетные запиÑи" - button_delete_my_account: "Удалить мою учетную запиÑÑŒ" - text_account_destroy_confirmation: "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ будет полноÑтью удалена без возможноÑти воÑÑтановлениÑ.\nÐ’Ñ‹ уверены, что хотите продолжить?" + notice_account_deleted: "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ полноÑтью удалена" + setting_unsubscribe: "Разрешить пользователÑм удалÑть Ñвои учетные запиÑи" + button_delete_my_account: "Удалить мою учетную запиÑÑŒ" + text_account_destroy_confirmation: "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ будет полноÑтью удалена без возможноÑти воÑÑтановлениÑ.\nÐ’Ñ‹ уверены, что хотите продолжить?" error_session_expired: Срок вашей ÑеÑÑии иÑтек. ПожалуйÑта войдите еще раз text_session_expiration_settings: "Внимание! Изменение Ñтих наÑтроек может привеÑти к завершению текущих ÑеÑÑий, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð²Ð°ÑˆÑƒ." setting_session_lifetime: МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ñть ÑеÑÑии - setting_session_timeout: Таймут ÑеÑÑии + setting_session_timeout: Таймаут ÑеÑÑии label_session_expiration: Срок иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ ÑеÑÑии permission_close_project: Закрывать / открывать проекты label_show_closed_projects: ПроÑматривать закрытые проекты @@ -1159,7 +1147,7 @@ project_status_active: открытые project_status_closed: закрытые project_status_archived: архивированные - text_project_closed: Проект закрыт и находитьÑÑ Ð² режиме только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ. + text_project_closed: Проект закрыт и находитÑÑ Ð² режиме только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ. notice_user_successful_create: Пользователь %{id} Ñоздан. field_core_fields: Стандартные Ð¿Ð¾Ð»Ñ field_timeout: Таймаут (в Ñекундах) @@ -1185,13 +1173,41 @@ permission_set_notes_private: Размещение приватных комментариев label_no_issues_in_project: нет задач в проекте label_any: вÑе - label_last_n_weeks: поÑледние %{count} недель + label_last_n_weeks: + one: "поÑледнÑÑ %{count} неделÑ" + few: "поÑледние %{count} недели" + many: "поÑледние %{count} недель" + other: "поÑледние %{count} недели" setting_cross_project_subtasks: Разрешить подзадачи в между проектами label_cross_project_descendants: С подпроектами label_cross_project_tree: С деревом проектов label_cross_project_hierarchy: С иерархией проектов label_cross_project_system: Со вÑеми проектами button_hide: Скрыть - setting_non_working_week_days: Ðе рабочие дни + setting_non_working_week_days: Ðерабочие дни label_in_the_next_days: в Ñредующие дни label_in_the_past_days: в прошлые дни + label_attribute_of_user: Пользователь %{name} + text_turning_multiple_off: ЕÑли отключить множеÑтвенные значениÑ, лишние Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¸Ð· ÑпиÑка будут удалены, чтобы оÑталоÑÑŒ только по одному значению. + label_attribute_of_issue: Задача %{name} + permission_add_documents: Добавить документы + permission_edit_documents: Редактировать документы + permission_delete_documents: Удалить документы + label_gantt_progress_line: Ð›Ð¸Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€ÐµÑÑа + setting_jsonp_enabled: Поддержка JSONP + field_inherit_members: ÐаÑледовать учаÑтников + field_closed_on: Закрыта + field_generate_password: Создание Ð¿Ð°Ñ€Ð¾Ð»Ñ + setting_default_projects_tracker_ids: Трекеры по умолчанию Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… проектов + label_total_time: Общее Ð²Ñ€ÐµÐ¼Ñ + notice_account_not_activated_yet: Ð’Ñ‹ пока не имеете активированных учетных запиÑей. + Чтобы получить пиÑьмо Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸ÐµÐ¹, перейдите по ÑÑылке. + notice_account_locked: Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ заблокирована. + label_hidden: Скрытый + label_visibility_private: только мне + label_visibility_roles: только Ñтим ролÑм + label_visibility_public: вÑем пользователÑм + field_must_change_passwd: Изменить пароль при Ñледующем входе + notice_new_password_must_be_different: Ðовый пароль должен отличатьÑÑ Ð¾Ñ‚ текущего + setting_mail_handler_excluded_filenames: ИÑключать Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾ имени + text_convert_available: ДоÑтупно иÑпользование ImageMagick (необÑзательно) diff -r d98d22a98252 -r afce8026aaeb config/locales/sk.yml --- a/config/locales/sk.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/sk.yml Tue Sep 09 09:34:53 2014 +0100 @@ -1,10 +1,10 @@ +# Slovak translation by Stanislav Pach | stano.pach@seznam.cz +# additions for Redmine 2.3.2 and proofreading by Katarína Nosková | noskova.katarina@gmail.com + sk: direction: ltr date: formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! default: "%Y-%m-%d" short: "%b %d" long: "%B %d, %Y" @@ -27,8 +27,8 @@ time: "%H:%M" short: "%d %b %H:%M" long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" + am: "dopoludnia" + pm: "popoludní" datetime: distance_in_words: @@ -43,32 +43,32 @@ one: "menej ako minúta" other: "menej ako %{count} minút" x_minutes: - one: "1 minuta" + one: "1 minúta" other: "%{count} minút" about_x_hours: - one: "okolo 1 hodiny" - other: "okolo %{count} hodín" + one: "približne 1 hodinu" + other: "približne %{count} hodín" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hodina" + other: "%{count} hodín" x_days: one: "1 deň" other: "%{count} dní" about_x_months: - one: "okolo 1 mesiaca" - other: "okolo %{count} mesiace/ov" + one: "približne 1 mesiac" + other: "približne %{count} mesiacov" x_months: one: "1 mesiac" - other: "%{count} mesiace/ov" + other: "%{count} mesiacov" about_x_years: - one: "okolo 1 roka" - other: "okolo %{count} roky/ov" + one: "približne 1 rok" + other: "približne %{count} rokov" over_x_years: - one: "cez 1 rok" - other: "cez %{count} roky/ov" + one: "viac ako 1 rok" + other: "viac ako %{count} rokov" almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" + one: "takmer 1 rok" + other: "takmer %{count} rokov" number: format: @@ -78,18 +78,18 @@ human: format: + delimiter: "" precision: 3 - delimiter: "" storage_units: format: "%n %u" units: - kb: KB - tb: TB - gb: GB byte: - one: Byte - other: Bytes - mb: MB + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" # Used in array.to_sentence. support: @@ -101,37 +101,36 @@ errors: template: header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" + one: "1 chyba bráni uloženiu %{model}" + other: "%{count} chýb bráni uloženiu %{model}" messages: - inclusion: "nieje zahrnuté v zozname" - exclusion: "je rezervované" + inclusion: "nie je zahrnuté v zozname" + exclusion: "nie je k dispozícii" invalid: "je neplatné" confirmation: "sa nezhoduje s potvrdením" accepted: "musí byÅ¥ akceptované" empty: "nemôže byÅ¥ prázdne" blank: "nemôže byÅ¥ prázdne" - too_long: "je príliÅ¡ dlhé" - too_short: "je príliÅ¡ krátke" - wrong_length: "má chybnú dĺžku" + too_long: "je príliÅ¡ dlhé (maximálne %{count} znakov)" + too_short: "je príliÅ¡ krátke (minimálne %{count} znakov)" + wrong_length: "má nesprávnu dĺžku (vyžaduje sa %{count} znakov)" taken: "je už použité" - not_a_number: "nieje Äíslo" - not_a_date: "nieje platný dátum" - greater_than: "musí byÅ¥ väÄšíe ako %{count}" - greater_than_or_equal_to: "musí byÅ¥ väÄÅ¡ie alebo rovné %{count}" + not_a_number: "nie je Äíslo" + not_a_date: "nie je platný dátum" + greater_than: "musí byÅ¥ viac ako %{count}" + greater_than_or_equal_to: "musí byÅ¥ viac alebo rovné %{count}" equal_to: "musí byÅ¥ rovné %{count}" less_than: "musí byÅ¥ menej ako %{count}" less_than_or_equal_to: "musí byÅ¥ menej alebo rovné %{count}" odd: "musí byÅ¥ nepárne" even: "musí byÅ¥ párne" greater_than_start_date: "musí byÅ¥ neskôr ako poÄiatoÄný dátum" - not_same_project: "nepatrí rovnakému projektu" + not_same_project: "nepatrí k rovnakému projektu" circular_dependency: "Tento vzÅ¥ah by vytvoril cyklickú závislosÅ¥" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + cant_link_an_issue_with_a_descendant: "Nemožno prepojiÅ¥ úlohu s niektorou z podúloh" + earlier_than_minimum_start_date: "nemôže byÅ¥ skorší ako %{date} z dôvodu nadväznosti na predchádzajúce úlohy" - # SK translation by Stanislav Pach | stano.pach@seznam.cz - - actionview_instancetag_blank_option: Prosím vyberte + actionview_instancetag_blank_option: Vyberte general_text_No: 'Nie' general_text_Yes: 'Ãno' @@ -145,50 +144,48 @@ general_first_day_of_week: '1' notice_account_updated: ÚÄet bol úspeÅ¡ne zmenený. - notice_account_invalid_creditentials: Chybné meno alebo heslo + notice_account_invalid_creditentials: Nesprávne meno alebo heslo notice_account_password_updated: Heslo bolo úspeÅ¡ne zmenené. - notice_account_wrong_password: Chybné heslo - notice_account_register_done: ÚÄet bol úspeÅ¡ne vytvorený. Pre aktiváciu úÄtu kliknite na odkaz v emailu, ktorý vam bol zaslaný. - notice_account_unknown_email: Neznámy užívateľ. - notice_can_t_change_password: Tento úÄet používa externú autentifikáciu. Tu heslo zmeniÅ¥ nemôžete. - notice_account_lost_email_sent: Bol vám zaslaný email s inÅ¡trukciami ako si nastavite nové heslo. + notice_account_wrong_password: Nesprávne heslo + notice_account_register_done: ÚÄet bol úspeÅ¡ne vytvorený. ÚÄet aktivujete kliknutím na odkaz v emaile, ktorý vám bol zaslaný na %{email}. + notice_account_unknown_email: Neznámy používateľ. + notice_can_t_change_password: Tento úÄet používa externú autentifikáciu. Nemôžete zmeniÅ¥ heslo. + notice_account_lost_email_sent: Bol vám zaslaný email s inÅ¡trukciami, ako si nastaviÅ¥ nové heslo. notice_account_activated: Váš úÄet bol aktivovaný. Teraz se môžete prihlásiÅ¥. notice_successful_create: ÚspeÅ¡ne vytvorené. notice_successful_update: ÚspeÅ¡ne aktualizované. notice_successful_delete: ÚspeÅ¡ne odstránené. notice_successful_connection: ÚspeÅ¡ne pripojené. - notice_file_not_found: Stránka, ktorú se snažíte zobraziÅ¥, neexistuje alebo bola zmazaná. - notice_locking_conflict: Údaje boli zmenené iným užívateľom. - notice_scm_error: Položka a/alebo revízia neexistuje v repozitári. - notice_not_authorized: Nemáte dostatoÄné práva pre zobrazenie tejto stránky. - notice_email_sent: "Na adresu %{value} bol odeslaný email" - notice_email_error: "Pri odosielaní emailu nastala chyba (%{value})" - notice_feeds_access_key_reseted: Váš klÃºÄ pre prístup k Atomu bol resetovaný. - notice_failed_to_save_issues: "Nastala chyba pri ukládaní %{count} úloh na %{total} zvolený: %{ids}." - notice_no_issue_selected: "Nebola zvolená žiadná úloha. Prosím, zvoľte úlohy, ktoré chcete upraviÅ¥" - notice_account_pending: "Váš úÄet bol vytvorený, teraz Äaká na schválenie administrátorom." - notice_default_data_loaded: Výchozia konfigurácia úspeÅ¡ne nahraná. + notice_file_not_found: Stránka, ktorú se pokúšate zobraziÅ¥, neexistuje, alebo bola odstránená. + notice_locking_conflict: Údaje boli aktualizované iným používateľom. + notice_scm_error: Položka a/alebo revízia v repozitári neexistuje. + notice_not_authorized: Nemáte dostatoÄné oprávnenia na zobrazenie tejto stránky. + notice_email_sent: "Na adresu %{value} bol odoslaný email" + notice_email_error: "Pri odosielaní emailu sa vyskytla chyba (%{value})" + notice_feeds_access_key_reseted: Váš prístupový kÄ¾ÃºÄ k Atomu bol resetovaný. + notice_failed_to_save_issues: "Nepodarilo sa uložiÅ¥ %{count} úloh z %{total} vybraných: %{ids}." + notice_no_issue_selected: "Nebola vybraná žiadna úloha. OznaÄte prosím úlohy, ktoré chcete upraviÅ¥." + notice_account_pending: "Váš úÄet bol vytvorený a Äaká na schválenie administrátorom." + notice_default_data_loaded: Predvolená konfigurácia bola úspeÅ¡ne nahraná. - error_can_t_load_default_data: "Výchozia konfigurácia nebola nahraná: %{value}" - error_scm_not_found: "Položka a/alebo revízia neexistuje v repozitári." - error_scm_command_failed: "Pri pokuse o prístup k repozitári doÅ¡lo k chybe: %{value}" - error_issue_not_found_in_project: 'Úloha nebola nájdená alebo nepatrí k tomuto projektu' + error_can_t_load_default_data: "Predvolená konfigurácia nebola nahraná: %{value}" + error_scm_not_found: "Položka alebo revízia nebola v repozitári nájdená." + error_scm_command_failed: "Pri pokuse o prístup k repozitáru sa vyskytla chyba: %{value}" + error_issue_not_found_in_project: 'Úloha nebola nájdená, alebo nepatrí k tomuto projektu' - mail_subject_lost_password: "VaÅ¡e heslo (%{value})" - mail_body_lost_password: 'Pre zmenu vaÅ¡eho hesla kliknite na následujúci odkaz:' - mail_subject_register: "Aktivácia úÄtu (%{value})" - mail_body_register: 'Pre aktiváciu vaÅ¡eho úÄtu kliknite na následujúci odkaz:' - mail_body_account_information_external: "Pomocou vaÅ¡eho úÄtu %{value} se môžete prihlásiÅ¥." + mail_subject_lost_password: "VaÅ¡e heslo %{value}" + mail_body_lost_password: 'Na zmenu hesla kliknite na nasledujúci odkaz:' + mail_subject_register: "Aktivácia vášho úÄtu %{value}" + mail_body_register: 'Ak si želáte aktivovaÅ¥ váš úÄet, kliknite na nasledujúci odkaz:' + mail_body_account_information_external: "Môžete sa prihlásiÅ¥ pomocou vášho úÄtu %{value}." mail_body_account_information: Informácie o vaÅ¡om úÄte - mail_subject_account_activation_request: "Aktivácia %{value} úÄtu" - mail_body_account_activation_request: "Bol zaregistrovaný nový uživateľ %{value}. Aktivácia jeho úÄtu závisí na vaÅ¡om potvrdení." + mail_subject_account_activation_request: "Požiadavka na aktiváciu úÄtu %{value}" + mail_body_account_activation_request: "Bol zaregistrovaný nový používateľ %{value}. ÚÄet Äaká na vaÅ¡e schválenie:" - gui_validation_error: 1 chyba - gui_validation_error_plural: "%{count} chyb(y)" - field_name: Názov + field_name: Meno field_description: Popis - field_summary: Prehľad + field_summary: Zhrnutie field_is_required: Povinné pole field_firstname: Meno field_lastname: Priezvisko @@ -202,7 +199,7 @@ field_field_format: Formát field_is_for_all: Pre vÅ¡etky projekty field_possible_values: Možné hodnoty - field_regexp: Regulérny výraz + field_regexp: Regulárny výraz field_min_length: Minimálna dĺžka field_max_length: Maximálna dĺžka field_value: Hodnota @@ -211,48 +208,48 @@ field_project: Projekt field_issue: Úloha field_status: Stav - field_notes: Poznámka + field_notes: Poznámky field_is_closed: Úloha uzavretá - field_is_default: Východzí stav - field_tracker: Fronta + field_is_default: Predvolený stav + field_tracker: Front field_subject: Predmet - field_due_date: UzavrieÅ¥ do + field_due_date: OdovzdaÅ¥ do field_assigned_to: Priradené field_priority: Priorita - field_fixed_version: Priradené k verzii - field_user: Užívateľ + field_fixed_version: Cieľová verzia + field_user: Používateľ field_role: Rola field_homepage: Domovská stránka - field_is_public: Verejný + field_is_public: Verejné field_parent: Nadradený projekt field_is_in_roadmap: Úlohy zobrazené v pláne - field_login: Login - field_mail_notification: Emailové oznámenie + field_login: Prihlasovacie meno + field_mail_notification: Emailové upozornenie field_admin: Administrátor field_last_login_on: Posledné prihlásenie field_language: Jazyk field_effective_date: Dátum field_password: Heslo field_new_password: Nové heslo - field_password_confirmation: Potvrdenie + field_password_confirmation: Potvrdenie hesla field_version: Verzia field_type: Typ field_host: Host field_port: Port field_account: ÚÄet field_base_dn: Base DN - field_attr_login: Prihlásenie (atribut) - field_attr_firstname: Meno (atribut) - field_attr_lastname: Priezvisko (atribut) - field_attr_mail: Email (atribut) - field_onthefly: Automatické vytváranie užívateľov - field_start_date: ZaÄiatok + field_attr_login: Prihlásenie (atribút) + field_attr_firstname: Meno (atribút) + field_attr_lastname: Priezvisko (atribút) + field_attr_mail: Email (atribút) + field_onthefly: Okamžité vytváranie používateľov + field_start_date: PoÄiatoÄný dátum field_done_ratio: "% hotovo" field_auth_source: AutentifikaÄný mód field_hide_mail: NezobrazovaÅ¥ môj email field_comments: Komentár field_url: URL - field_start_page: Výchozia stránka + field_start_page: Východzia stránka field_subproject: Podprojekt field_hours: Hodiny field_activity: Aktivita @@ -261,46 +258,45 @@ field_is_filter: PoužiÅ¥ ako filter field_issue_to: Súvisiaca úloha field_delay: Oneskorenie - field_assignable: Úlohy môžu byÅ¥ priradené tejto roli + field_assignable: Úlohy môžu byÅ¥ priradené tejto role field_redirect_existing_links: PresmerovaÅ¥ existujúce odkazy - field_estimated_hours: Odhadovaná doba + field_estimated_hours: Odhadovaný Äas field_column_names: Stĺpce field_time_zone: ÄŒasové pásmo - field_searchable: UmožniÅ¥ vyhľadávanie - field_default_value: Východzia hodnota + field_searchable: Možné prehľadávanie + field_default_value: Predvolená hodnota field_comments_sorting: ZobraziÅ¥ komentáre setting_app_title: Názov aplikácie setting_app_subtitle: Podtitulok aplikácie setting_welcome_text: Uvítací text - setting_default_language: Východzí jazyk - setting_login_required: Auten. vyžadovaná - setting_self_registration: Povolenie registrácie + setting_default_language: Predvolený jazyk + setting_login_required: Vyžadovaná autentifikácia + setting_self_registration: Povolená registrácia setting_attachment_max_size: Maximálna veľkosÅ¥ prílohy - setting_issues_export_limit: Limit pre export úloh + setting_issues_export_limit: Maximálny poÄet úloh pri exporte setting_mail_from: OdosielaÅ¥ emaily z adresy - setting_bcc_recipients: Príjemcovia skrytej kópie (bcc) - setting_host_name: Hostname + setting_bcc_recipients: Príjemcov do skrytej kópie (bcc) + setting_host_name: Hostname a cesta setting_text_formatting: Formátovanie textu setting_wiki_compression: Kompresia histórie Wiki - setting_feeds_limit: Limit zobrazených položiek (Atom feed) + setting_feeds_limit: Maximálny poÄet zobrazených položiek v Atom feed setting_default_projects_public: Nové projekty nastavovaÅ¥ ako verejné - setting_autofetch_changesets: Automatický prenos zmien - setting_sys_api_enabled: Povolit Webovú Službu (WS) pre správu repozitára - setting_commit_ref_keywords: KlúÄové slová pre odkazy - setting_commit_fix_keywords: KlúÄové slová pre uzavretie + setting_autofetch_changesets: Automaticky vykonaÅ¥ commity + setting_sys_api_enabled: Povolit Webovú službu (WS) na správu repozitára + setting_commit_ref_keywords: KľúÄové slová pre referencie + setting_commit_fix_keywords: KľúÄové slová pre opravy setting_autologin: Automatické prihlasovanie setting_date_format: Formát dátumu setting_time_format: Formát Äasu - setting_cross_project_issue_relations: PovoliÅ¥ väzby úloh skrz projekty - setting_issue_list_default_columns: Východzie stĺpce zobrazené v zozname úloh - setting_ itories_encodings: Kódovanie - setting_emails_footer: Zapätie emailov + setting_cross_project_issue_relations: PovoliÅ¥ prepojenia úloh naprieÄ projektmi + setting_issue_list_default_columns: Predvolené stĺpce zobrazené v zozname úloh + setting_emails_footer: PätiÄka emailu setting_protocol: Protokol - setting_per_page_options: Povolené množstvo riadkov na stránke - setting_user_format: Formát zobrazenia užívateľa - setting_activity_days_default: "Zobrazené dni aktivity projektu:" - setting_display_subprojects_issues: Prednastavenie zobrazenia úloh podporojektov v hlavnom projekte + setting_per_page_options: Povolené množstvo položiek na stránku + setting_user_format: Formát zobrazenia používateľa + setting_activity_days_default: "PoÄet zobrazených dní na stránku pri aktivitách projektu:" + setting_display_subprojects_issues: Predvolené zobrazovanie úloh podprojektov v hlavnom projekte project_module_issue_tracking: Sledovanie úloh project_module_time_tracking: Sledovanie Äasu @@ -309,82 +305,81 @@ project_module_files: Súbory project_module_wiki: Wiki project_module_repository: Repozitár - project_module_boards: Diskusie + project_module_boards: Diskusné fóra - label_user: Užívateľ - label_user_plural: Užívatelia - label_user_new: Nový užívateľ + label_user: Požívateľ + label_user_plural: Používatelia + label_user_new: Nový používateľ label_project: Projekt label_project_new: Nový projekt label_project_plural: Projekty label_x_projects: zero: žiadne projekty one: 1 projekt - other: "%{count} projekty/ov" + other: "%{count} projektov" label_project_all: VÅ¡etky projekty label_project_latest: Posledné projekty label_issue: Úloha label_issue_new: Nová úloha label_issue_plural: Úlohy - label_issue_view_all: VÅ¡etky úlohy - label_issues_by: "Úlohy od užívateľa %{value}" - label_issue_added: Úloha pridaná - label_issue_updated: Úloha aktualizovaná + label_issue_view_all: ZobraziÅ¥ vÅ¡etky úlohy + label_issues_by: "Úlohy od používateľa %{value}" + label_issue_added: Úloha bola pridaná + label_issue_updated: Úloha bola aktualizovaná label_document: Dokument label_document_new: Nový dokument label_document_plural: Dokumenty - label_document_added: Dokument pridaný + label_document_added: Dokument bol pridaný label_role: Rola - label_role_plural: Role + label_role_plural: Roly label_role_new: Nová rola - label_role_and_permissions: Role a práva + label_role_and_permissions: Roly a oprávnenia label_member: ÄŒlen label_member_new: Nový Älen label_member_plural: ÄŒlenovia - label_tracker: Fronta + label_tracker: Front label_tracker_plural: Fronty - label_tracker_new: Nová fronta - label_workflow: Workflow + label_tracker_new: Nový front + label_workflow: Pracovný postup (workflow) label_issue_status: Stav úloh label_issue_status_plural: Stavy úloh label_issue_status_new: Nový stav label_issue_category: Kategória úloh label_issue_category_plural: Kategórie úloh label_issue_category_new: Nová kategória - label_custom_field: Užívateľské pole - label_custom_field_plural: Užívateľské polia - label_custom_field_new: Nové užívateľské pole + label_custom_field: Vlastné pole + label_custom_field_plural: Vlastné polia + label_custom_field_new: Nové pole label_enumerations: Zoznamy label_enumeration_new: Nová hodnota label_information: Informácia label_information_plural: Informácie - label_please_login: Prosím prihláste sa - label_register: RegistrovaÅ¥ + label_please_login: Prihláste sa prosím + label_register: Registrácia label_password_lost: Zabudnuté heslo - label_home: Domovská stránka - label_home_heading: Domovská stránka + label_home: Domov label_my_page: Moja stránka label_my_account: Môj úÄet label_my_projects: Moje projekty label_administration: Administrácia label_login: Prihlásenie label_logout: Odhlásenie - label_help: Nápoveda + label_help: Pomoc label_reported_issues: Nahlásené úlohy label_assigned_to_me_issues: Moje úlohy label_last_login: Posledné prihlásenie - label_registered_on: Registrovaný + label_registered_on: Dátum registrácie label_activity: Aktivita label_overall_activity: Celková aktivita label_new: Nový label_logged_as: Prihlásený ako label_environment: Prostredie label_authentication: Autentifikácia - label_auth_source: Mód autentifikácie - label_auth_source_new: Nový mód autentifikácie - label_auth_source_plural: Módy autentifikácie + label_auth_source: AutentifikaÄný mód + label_auth_source_new: Nový autentifikaÄný mód + label_auth_source_plural: AutentifikaÄné módy label_subproject_plural: Podprojekty - label_min_max_length: Min - Max dĺžka + label_min_max_length: Min. - max. dĺžka label_list: Zoznam label_date: Dátum label_integer: Celé Äíslo @@ -392,62 +387,60 @@ label_boolean: Ãno/Nie label_string: Text label_text: Dlhý text - label_attribute: Atribut - label_attribute_plural: Atributy - label_download: "%{count} Download" - label_download_plural: "%{count} Downloady" - label_no_data: Žiadné položky + label_attribute: Atribút + label_attribute_plural: Atribúty + label_no_data: Žiadne položky label_change_status: ZmeniÅ¥ stav label_history: História label_attachment: Súbor label_attachment_new: Nový súbor - label_attachment_delete: OdstrániÅ¥ súbor + label_attachment_delete: VymazaÅ¥ súbor label_attachment_plural: Súbory - label_file_added: Súbor pridaný - label_report: Prehľad - label_report_plural: Prehľady + label_file_added: Súbor bol pridaný + label_report: Hlásenie + label_report_plural: Hlásenia label_news: Novinky label_news_new: PridaÅ¥ novinku label_news_plural: Novinky label_news_latest: Posledné novinky - label_news_view_all: Zobrazit vÅ¡etky novinky - label_news_added: Novinka pridaná - label_settings: Nastavenie + label_news_view_all: ZobraziÅ¥ vÅ¡etky novinky + label_news_added: Novinka bola pridaná + label_settings: Nastavenia label_overview: Prehľad label_version: Verzia label_version_new: Nová verzia label_version_plural: Verzie label_confirmation: Potvrdenie - label_export_to: 'Tiež k dispozícií:' - label_read: NaÄíta sa... + label_export_to: 'K dispozícii tiež ako:' + label_read: NaÄítava sa... label_public_projects: Verejné projekty - label_open_issues: Otvorený - label_open_issues_plural: Otvorené - label_closed_issues: Uzavrený - label_closed_issues_plural: Uzavrené + label_open_issues: otvorená + label_open_issues_plural: otvorené + label_closed_issues: uzavretá + label_closed_issues_plural: uzavreté label_x_open_issues_abbr_on_total: - zero: 0 otvorených z celkovo %{total} - one: 1 otvorený z celkovo %{total} - other: "%{count} otvorené/ých z celkovo %{total}" + zero: 0 otvorených z celkom %{total} + one: 1 otvorená z celkom %{total} + other: "%{count} otvorených z celkom %{total}" label_x_open_issues_abbr: zero: 0 otvorených - one: 1 otvorený - other: "%{count} otvorené/ých" + one: 1 otvorená + other: "%{count} otvorených" label_x_closed_issues_abbr: - zero: 0 zavretých - one: 1 zavretý - other: "%{count} zavreté/ých" - label_total: Celkovo - label_permissions: Práva + zero: 0 uzavretých + one: 1 uzavretá + other: "%{count} uzavretých" + label_total: Celkom + label_permissions: Oprávnenia label_current_status: Aktuálny stav - label_new_statuses_allowed: Nové povolené stavy + label_new_statuses_allowed: Povolené nové stavy label_all: vÅ¡etko label_none: niÄ label_nobody: nikto - label_next: ÄŽalší - label_previous: Predchádzajúci + label_next: ÄŽalÅ¡ie + label_previous: Predchádzajúce label_used_by: Použité - label_details: Detaily + label_details: Podrobnosti label_add_note: PridaÅ¥ poznámku label_per_page: Na stránku label_calendar: Kalendár @@ -460,19 +453,19 @@ label_comment: Komentár label_comment_plural: Komentáre label_x_comments: - zero: žiaden komentár + zero: žiadne komentáre one: 1 komentár - other: "%{count} komentáre/ov" + other: "%{count} komentárov" label_comment_add: PridaÅ¥ komentár - label_comment_added: Komentár pridaný - label_comment_delete: OdstrániÅ¥ komentár - label_query: Užívateľský dotaz - label_query_plural: Užívateľské dotazy - label_query_new: Nový dotaz + label_comment_added: Komentár bol pridaný + label_comment_delete: VymazaÅ¥ komentár + label_query: Vlastný filter + label_query_plural: Vlastné filtre + label_query_new: Nový filter vyhľadávania label_filter_add: PridaÅ¥ filter label_filter_plural: Filtre label_equals: je - label_not_equals: nieje + label_not_equals: nie je label_in_less_than: je menší ako label_in_more_than: je väÄší ako label_in: v @@ -481,7 +474,7 @@ label_yesterday: vÄera label_this_week: tento týždeň label_last_week: minulý týždeň - label_last_n_days: "posledných %{count} dní" + label_last_n_days: "v posledných %{count} dňoch" label_this_month: tento mesiac label_last_month: minulý mesiac label_this_year: tento rok @@ -495,34 +488,32 @@ label_repository: Repozitár label_repository_plural: Repozitáre label_browse: PrechádzaÅ¥ - label_modification: "%{count} zmena" - label_modification_plural: "%{count} zmien" label_revision: Revízia - label_revision_plural: Revízií - label_associated_revisions: Súvisiace verzie + label_revision_plural: Revízie + label_associated_revisions: Súvisiace revízie label_added: pridané label_modified: zmenené - label_deleted: odstránené + label_deleted: vymazané label_latest_revision: Posledná revízia label_latest_revision_plural: Posledné revízie label_view_revisions: ZobraziÅ¥ revízie label_max_size: Maximálna veľkosÅ¥ label_sort_highest: Presunúť na zaÄiatok - label_sort_higher: Presunúť navrch - label_sort_lower: Presunúť dole + label_sort_higher: Presunúť vyššie + label_sort_lower: Presunúť nižšie label_sort_lowest: Presunúť na koniec label_roadmap: Plán label_roadmap_due_in: "Zostáva %{value}" - label_roadmap_overdue: "%{value} neskoro" - label_roadmap_no_issues: Pre túto verziu niesú žiadne úlohy + label_roadmap_overdue: "%{value} meÅ¡kanie" + label_roadmap_no_issues: Pre túto verziu neexistujú žiadne úlohy label_search: HľadaÅ¥ label_result_plural: Výsledky - label_all_words: VÅ¡etky slova + label_all_words: VÅ¡etky slová label_wiki: Wiki label_wiki_edit: Wiki úprava label_wiki_edit_plural: Wiki úpravy label_wiki_page: Wiki stránka - label_wiki_page_plural: Wiki stránky + label_wiki_page_plural: Wikistránky label_index_by_title: Index podľa názvu label_index_by_date: Index podľa dátumu label_current_version: Aktuálna verzia @@ -533,35 +524,35 @@ label_spent_time: Strávený Äas label_f_hour: "%{value} hodina" label_f_hour_plural: "%{value} hodín" - label_time_tracking: Sledovánie Äasu + label_time_tracking: Sledovanie Äasu label_change_plural: Zmeny label_statistics: Å tatistiky - label_commits_per_month: Úkony za mesiac - label_commits_per_author: Úkony podľa autora - label_view_diff: Zobrazit rozdiely + label_commits_per_month: Commity za mesiac + label_commits_per_author: Commity podľa autora + label_view_diff: ZobraziÅ¥ rozdiely label_diff_inline: vo vnútri label_diff_side_by_side: vedľa seba - label_options: Nastavenie - label_copy_workflow_from: KopírovaÅ¥ workflow z - label_permissions_report: Prehľad práv + label_options: Nastavenia + label_copy_workflow_from: KopírovaÅ¥ pracovný postup (workflow) z + label_permissions_report: Prehľad oprávnení label_watched_issues: Sledované úlohy label_related_issues: Súvisiace úlohy label_applied_status: Použitý stav - label_loading: Nahrávam ... - label_relation_new: Nová súvislosÅ¥ - label_relation_delete: OdstrániÅ¥ súvislosÅ¥ - label_relates_to: súvisiací s - label_duplicates: duplicity - label_blocks: blokovaný - label_blocked_by: zablokovaný - label_precedes: predcháza - label_follows: následuje - label_end_to_start: od konca na zaÄiatok + label_loading: Nahráva sa... + label_relation_new: Nové prepojenie + label_relation_delete: OdstrániÅ¥ prepojenie + label_relates_to: Súvisiace s + label_duplicates: Duplikáty + label_blocks: Blokované + label_blocked_by: Zablokované používateľom + label_precedes: Predchádza + label_follows: Nasleduje po + label_end_to_start: od konca po zaÄiatok label_end_to_end: od konca do konca label_start_to_start: od zaÄiatku do zaÄiatku label_start_to_end: od zaÄiatku do konca label_stay_logged_in: ZostaÅ¥ prihlásený - label_disabled: zakazané + label_disabled: zakázané label_show_completed_versions: UkázaÅ¥ dokonÄené verzie label_me: ja label_board: Fórum @@ -571,32 +562,32 @@ label_message_plural: Správy label_message_last: Posledná správa label_message_new: Nová správa - label_message_posted: Správa pridaná + label_message_posted: Správa bola pridaná label_reply_plural: Odpovede - label_send_information: ZaslaÅ¥ informácie o úÄte užívateľa + label_send_information: ZaslaÅ¥ informácie o úÄte používateľa label_year: Rok label_month: Mesiac - label_week: Týžden + label_week: Týždeň label_date_from: Od label_date_to: Do - label_language_based: Podľa výchozieho jazyka + label_language_based: Podľa jazyka používateľa label_sort_by: "Zoradenie podľa %{value}" label_send_test_email: PoslaÅ¥ testovací email - label_feeds_access_key_created_on: "Prístupový klÃºÄ pre RSS bol vytvorený pred %{value}" + label_feeds_access_key_created_on: "Prístupový kÄ¾ÃºÄ pre Atom bol vytvorený pred %{value}" label_module_plural: Moduly - label_added_time_by: "Pridané užívateľom %{author} pred %{age}" + label_added_time_by: "Pridané používateľom %{author} pred %{age}" label_updated_time: "Aktualizované pred %{value}" - label_jump_to_a_project: ZvoliÅ¥ projekt... + label_jump_to_a_project: PrejsÅ¥ na projekt... label_file_plural: Súbory - label_changeset_plural: Sady zmien - label_default_columns: Východzie stĺpce + label_changeset_plural: Súbory zmien + label_default_columns: Predvolené stĺpce label_no_change_option: (bez zmeny) - label_bulk_edit_selected_issues: Skupinová úprava vybraných úloh + label_bulk_edit_selected_issues: Hromadná úprava vybraných úloh label_theme: Téma - label_default: Východzí + label_default: Predvolené label_search_titles_only: VyhľadávaÅ¥ iba v názvoch - label_user_mail_option_all: "Pre vÅ¡etky události vÅ¡etkých mojích projektov" - label_user_mail_option_selected: "Pre vÅ¡etky události vybraných projektov" + label_user_mail_option_all: "Pre vÅ¡etky udalosti vÅ¡etkých mojich projektov" + label_user_mail_option_selected: "Pre vÅ¡etky udalosti vybraných projektov..." label_user_mail_no_self_notified: "NezasielaÅ¥ informácie o mnou vytvorených zmenách" label_registration_activation_by_email: aktivácia úÄtu emailom label_registration_manual_activation: manuálna aktivácia úÄtu @@ -611,7 +602,7 @@ label_ldap_authentication: Autentifikácia LDAP label_downloads_abbr: D/L label_optional_description: Voliteľný popis - label_add_another_file: PridaÅ¥ Äaľší súbor + label_add_another_file: PridaÅ¥ Äalší súbor label_preferences: Nastavenia label_chronological_order: V chronologickom poradí label_reverse_chronological_order: V obrátenom chronologickom poradí @@ -620,7 +611,7 @@ button_submit: PotvrdiÅ¥ button_save: UložiÅ¥ button_check_all: OznaÄiÅ¥ vÅ¡etko - button_uncheck_all: OdznaÄiÅ¥ vÅ¡etko + button_uncheck_all: ZruÅ¡iÅ¥ výber button_delete: OdstrániÅ¥ button_create: VytvoriÅ¥ button_test: Test @@ -628,25 +619,25 @@ button_add: PridaÅ¥ button_change: ZmeniÅ¥ button_apply: PoužiÅ¥ - button_clear: ZmazaÅ¥ - button_lock: Zamknúť + button_clear: Späť na pôvodné + button_lock: Uzamknúť button_unlock: Odomknúť - button_download: Stiahnúť + button_download: StiahnuÅ¥ button_list: VypísaÅ¥ button_view: ZobraziÅ¥ button_move: Presunúť - button_back: Naspäť - button_cancel: Storno + button_back: Späť + button_cancel: ZruÅ¡iÅ¥ button_activate: AktivovaÅ¥ - button_sort: Zoradenie - button_log_time: PridaÅ¥ Äas - button_rollback: Naspäť k tejto verzii + button_sort: ZoradiÅ¥ + button_log_time: PridaÅ¥ Äasový záznam + button_rollback: Naspäť na túto verziu button_watch: SledovaÅ¥ button_unwatch: NesledovaÅ¥ button_reply: OdpovedaÅ¥ button_archive: ArchivovaÅ¥ - button_unarchive: OdarchivovaÅ¥ - button_reset: Reset + button_unarchive: zruÅ¡iÅ¥ archiváciu + button_reset: ResetovaÅ¥ button_rename: PremenovaÅ¥ button_change_password: ZmeniÅ¥ heslo button_copy: KopírovaÅ¥ @@ -655,120 +646,119 @@ button_configure: KonfigurovaÅ¥ status_active: aktívny - status_registered: registrovaný + status_registered: zaregistrovaný status_locked: uzamknutý - text_select_mail_notifications: Vyberte akciu, pri ktorej bude zaslané upozornenie emailom + text_select_mail_notifications: Vyberte akciu, pri ktorej bude zaslané upozornenie emailom. text_regexp_info: napr. ^[A-Z0-9]+$ text_min_max_length_info: 0 znamená bez limitu - text_project_destroy_confirmation: Ste si istý, že chcete odstránit tento projekt a vÅ¡etky súvisiace dáta ? - text_workflow_edit: Vyberte rolu a frontu k úprave workflow - text_are_you_sure: Ste si istý? + text_project_destroy_confirmation: Naozaj si želáte odstrániÅ¥ tento projekt a vÅ¡etky súvisiace údaje? + text_workflow_edit: Vyberte rolu a front na úpravu pracovného postupu (workflow) + text_are_you_sure: Naozaj si želáte vykonaÅ¥ túto akciu? text_tip_issue_begin_day: úloha zaÄína v tento deň text_tip_issue_end_day: úloha konÄí v tento deň text_tip_issue_begin_end_day: úloha zaÄína a konÄí v tento deň - text_caracters_maximum: "%{count} znakov maximálne." - text_caracters_minimum: "Musí byÅ¥ aspoň %{count} znaky/ov dlhé." + text_caracters_maximum: "Maximálne %{count} znakov." + text_caracters_minimum: "Dĺžka musí byÅ¥ minimálne %{count} znakov." text_length_between: "Dĺžka medzi %{min} až %{max} znakmi." - text_tracker_no_workflow: Pre tuto frontu nieje definovaný žiadný workflow + text_tracker_no_workflow: Pre tento front nie je definovaný žiadny pracovný postup (workflow) text_unallowed_characters: Nepovolené znaky - text_comma_separated: Je povolené viacero hodnôt (oddelené navzájom Äiarkou). - text_issues_ref_in_commit_messages: OdkazovaÅ¥ a upravovaÅ¥ úlohy v správach s následovnym obsahom - text_issue_added: "úloha %{id} bola vytvorená užívateľom %{author}." - text_issue_updated: "Úloha %{id} byla aktualizovaná užívateľom %{author}." - text_wiki_destroy_confirmation: Naozaj si prajete odstrániÅ¥ túto Wiki a celý jej obsah? - text_issue_category_destroy_question: "Niektoré úlohy (%{count}) sú priradené k tejto kategórii. ÄŒo chtete s nimi spraviÅ¥?" - text_issue_category_destroy_assignments: ZruÅ¡iÅ¥ priradenie ku kategórii - text_issue_category_reassign_to: PriradiÅ¥ úlohy do tejto kategórie - text_user_mail_option: "U projektov, které neboli vybrané, budete dostávaÅ¥ oznamenie iba o vaÅ¡ich Äi o sledovaných položkách (napr. o položkách, ktorých ste autor, alebo ku ktorým ste priradený/á)." - text_no_configuration_data: "Role, fronty, stavy úloh ani workflow neboli zatiaľ nakonfigurované.\nVelmi doporuÄujeme nahraÅ¥ východziu konfiguráciu. Potom si môžete vÅ¡etko upraviÅ¥" - text_load_default_configuration: NahraÅ¥ východziu konfiguráciu - text_status_changed_by_changeset: "Aktualizované v sade zmien %{value}." - text_issues_destroy_confirmation: 'Naozaj si prajete odstrániÅ¥ vÅ¡etky zvolené úlohy?' - text_select_project_modules: 'Aktivne moduly v tomto projekte:' - text_default_administrator_account_changed: Zmenené výchozie nastavenie administrátorského úÄtu - text_file_repository_writable: Povolený zápis do repozitára - text_rmagick_available: RMagick k dispozícií (voliteľné) - text_destroy_time_entries_question: U úloh, které chcete odstraniÅ¥, je evidované %.02f práce. ÄŒo chcete vykonaÅ¥? - text_destroy_time_entries: OdstrániÅ¥ evidované hodiny. - text_assign_time_entries_to_project: PriradiÅ¥ evidované hodiny projektu - text_reassign_time_entries: 'PreradiÅ¥ evidované hodiny k tejto úlohe:' + text_comma_separated: Je povolených viacero hodnôt (navzájom oddelené Äiarkou). + text_issues_ref_in_commit_messages: Referencie a opravy úloh v commitoch + text_issue_added: "úloha %{id} bola vytvorená používateľom %{author}." + text_issue_updated: "Úloha %{id} bola aktualizovaná používateľom %{author}." + text_wiki_destroy_confirmation: Naozaj si želáte vymazaÅ¥ túto Wiki a celý jej obsah? + text_issue_category_destroy_question: "Do tejto kategórie je zaradených niekoľko úloh (%{count}). ÄŒo si želáte s nimi urobiÅ¥?" + text_issue_category_destroy_assignments: ZruÅ¡iÅ¥ zaradenie do kategórie + text_issue_category_reassign_to: PreradiÅ¥ úlohy do tejto kategórie + text_user_mail_option: "Pri projektoch, ktoré neboli vybrané, budete dostávaÅ¥ upozornenia týkajúce sa iba vaÅ¡ich alebo vami sledovaných vecí (napr. vecí, ktorých ste autor, alebo ku ktorým ste priradení)." + text_no_configuration_data: "Roly, fronty, stavy úloh ani pracovné postupy (workflow) neboli zatiaľ nakonfigurované.\nOdporúÄame vám nahraÅ¥ predvolenú konfiguráciu. Následne môžete vÅ¡etky nastavenia upraviÅ¥." + text_load_default_configuration: NahraÅ¥ predvolenú konfiguráciu + text_status_changed_by_changeset: "Aplikované v súbore zmien %{value}." + text_issues_destroy_confirmation: 'Naozaj si želáte odstrániÅ¥ vÅ¡etky vybrané úlohy?' + text_select_project_modules: 'VybraÅ¥ moduly povolené v tomto projekte:' + text_default_administrator_account_changed: Predvolené nastavenie administrátorského úÄtu bolo zmenené + text_file_repository_writable: Povolený zápis do prieÄinka s prílohami + text_rmagick_available: RMagick k dispozícii (voliteľné) + text_destroy_time_entries_question: Pri úlohách, ktoré chcete odstrániÅ¥, je zaznamenaných %{hours} hodín práce. ÄŒo si želáte urobiÅ¥? + text_destroy_time_entries: OdstrániÅ¥ zaznamenané hodiny + text_assign_time_entries_to_project: PriradiÅ¥ zaznamenané hodiny k projektu + text_reassign_time_entries: 'PreradiÅ¥ zaznamenané hodiny k tejto úlohe:' default_role_manager: Manažér default_role_developer: Vývojár default_role_reporter: Reportér default_tracker_bug: Chyba - default_tracker_feature: Rozšírenie + default_tracker_feature: Funkcionalita default_tracker_support: Podpora - default_issue_status_new: Nový - default_issue_status_in_progress: In Progress - default_issue_status_resolved: VyrieÅ¡ený - default_issue_status_feedback: ÄŒaká sa - default_issue_status_closed: Uzavrený - default_issue_status_rejected: Odmietnutý - default_doc_category_user: Užívateľská dokumentácia + default_issue_status_new: Nová + default_issue_status_in_progress: Rozpracovaná + default_issue_status_resolved: VyrieÅ¡ená + default_issue_status_feedback: ÄŒaká na odsúhlasenie + default_issue_status_closed: Uzavretá + default_issue_status_rejected: Odmietnutá + default_doc_category_user: Používateľská dokumentácia default_doc_category_tech: Technická dokumentácia default_priority_low: Nízká default_priority_normal: Normálna default_priority_high: Vysoká default_priority_urgent: Urgentná default_priority_immediate: Okamžitá - default_activity_design: Design + default_activity_design: Dizajn default_activity_development: Vývoj enumeration_issue_priorities: Priority úloh - enumeration_doc_categories: Kategorie dokumentov + enumeration_doc_categories: Kategórie dokumentov enumeration_activities: Aktivity (sledovanie Äasu) - error_scm_annotate: "Položka neexistuje alebo nemôže byÅ¥ komentovaná." + error_scm_annotate: "Položka neexistuje, alebo nemôže byÅ¥ komentovaná." label_planning: Plánovanie - text_subprojects_destroy_warning: "Jeho podprojekt(y): %{value} budú takisto vymazané." + text_subprojects_destroy_warning: "Jeho podprojekty: %{value} budú tiež vymazané." label_and_its_subprojects: "%{value} a jeho podprojekty" - mail_body_reminder: "%{count} úloha(y), ktorá(é) je(sú) vám priradený(é), ma(jú) byÅ¥ hotova(é) za %{days} dní:" - mail_subject_reminder: "%{count} úloha(y) ma(jú) byÅ¥ hotova(é) za pár %{days} dní" - text_user_wrote: "%{value} napísal:" - label_duplicated_by: duplikovaný - setting_enabled_scm: Zapnúť SCM + mail_body_reminder: "Nasledovné úlohy (%{count}), ktoré sú vám priradené, majú byÅ¥ hotové za %{days} dní:" + mail_subject_reminder: "%{count} úlohy majú byÅ¥ hotové za %{days} dní" + text_user_wrote: "Používateľ %{value} napísal:" + label_duplicated_by: Duplikoval + setting_enabled_scm: Povolené SCM text_enumeration_category_reassign_to: 'PrenastaviÅ¥ na túto hodnotu:' - text_enumeration_destroy_question: "%{count} objekty sú nastavené na túto hodnotu." - label_incoming_emails: Príchádzajúce emaily + text_enumeration_destroy_question: "%{count} objektov je nastavených na túto hodnotu." + label_incoming_emails: Prichádzajúce emaily label_generate_key: VygenerovaÅ¥ kÄ¾ÃºÄ - setting_mail_handler_api_enabled: Zapnúť Webovú Službu (WS) pre príchodzie emaily + setting_mail_handler_api_enabled: Zapnúť Webovú službu (WS) pre prichádzajúce emaily setting_mail_handler_api_key: API kÄ¾ÃºÄ - text_email_delivery_not_configured: "DoruÄenie emailov nieje nastavené, notifikácie sú vypnuté.\nNastavte váš SMTP server v config/configuration.yml a reÅ¡tartnite aplikáciu pre aktiváciu funkcie." + text_email_delivery_not_configured: "DoruÄovanie emailov nie je nastavené, notifikácie sú vypnuté.\nNastavte váš SMTP server v config/configuration.yml a reÅ¡tartujte aplikáciu, Äím funkciu aktivujete." field_parent_title: Nadradená stránka label_issue_watchers: Pozorovatelia button_quote: Citácia setting_sequential_project_identifiers: GenerovaÅ¥ sekvenÄné identifikátory projektov - notice_unable_delete_version: Verzia nemôže byÅ¥ zmazaná + notice_unable_delete_version: Verzia nemôže byÅ¥ zmazaná. label_renamed: premenované label_copied: kopírované setting_plain_text_mail: Len jednoduchý text (bez HTML) permission_view_files: Zobrazenie súborov permission_edit_issues: Úprava úloh - permission_edit_own_time_entries: Úprava vlastných zaznamov o strávenom Äase - permission_manage_public_queries: Správa verejných otáziek - permission_add_issues: Pridanie úlohy + permission_edit_own_time_entries: Úprava vlastných záznamov o strávenom Äase + permission_manage_public_queries: Správa verejných filtrov vyhľadávania + permission_add_issues: Pridávanie úloh permission_log_time: Zaznamenávanie stráveného Äasu - permission_view_changesets: Zobrazenie sád zmien + permission_view_changesets: Zobrazenie súborov zmien permission_view_time_entries: Zobrazenie stráveného Äasu permission_manage_versions: Správa verzií permission_manage_wiki: Správa Wiki permission_manage_categories: Správa kategórií úloh - permission_protect_wiki_pages: Ochrana Wiki strániek + permission_protect_wiki_pages: Ochrana wikistránok permission_comment_news: Komentovanie noviniek permission_delete_messages: Mazanie správ permission_select_project_modules: Voľba projektových modulov - permission_manage_documents: Správa dokumentov - permission_edit_wiki_pages: Úprava Wiki strániek - permission_add_issue_watchers: Pridanie pozorovateľov - permission_view_gantt: Zobrazenie Ganttovho diagramu + permission_edit_wiki_pages: Úprava wikistránok + permission_add_issue_watchers: Pridávanie pozorovateľov + permission_view_gantt: Zobrazenie Ganttovho grafu permission_move_issues: Presun úloh - permission_manage_issue_relations: Správa vzÅ¥ahov medzi úlohami - permission_delete_wiki_pages: Mazanie Wiki strániek + permission_manage_issue_relations: Správa prepojení medzi úlohami + permission_delete_wiki_pages: Mazanie wikistránok permission_manage_boards: Správa diskusií - permission_delete_wiki_pages_attachments: Mazanie Wiki príloh - permission_view_wiki_edits: Zobrazenie Wiki úprav - permission_add_messages: Pridanie správ + permission_delete_wiki_pages_attachments: Mazanie wikipríloh + permission_view_wiki_edits: Zobrazenie wikiúprav + permission_add_messages: Pridávanie správ permission_view_messages: Zobrazenie správ permission_manage_files: Správa súborov permission_edit_issue_notes: Úprava poznámok úlohy @@ -779,306 +769,325 @@ permission_delete_issues: Mazanie správ permission_view_issue_watchers: Zobrazenie zoznamu pozorovateľov permission_manage_repository: Správa repozitára - permission_commit_access: PovoliÅ¥ prístup + permission_commit_access: Prístup ku commitom permission_browse_repository: Prechádzanie repozitára permission_view_documents: Zobrazenie dokumentov permission_edit_project: Úprava projektu - permission_add_issue_notes: Pridanie poznámky úlohy - permission_save_queries: Uloženie otáziek - permission_view_wiki_pages: Zobrazenie Wiki strániek - permission_rename_wiki_pages: Premenovanie Wiki strániek + permission_add_issue_notes: Pridanie poznámky k úlohe + permission_save_queries: Ukladanie filtrov vyhľadávania + permission_view_wiki_pages: Zobrazenie wikistránok + permission_rename_wiki_pages: Premenovanie wikistránok permission_edit_time_entries: Úprava záznamov o strávenom Äase - permission_edit_own_issue_notes: Úprava vlastných poznámok úlohy - setting_gravatar_enabled: Použitie užívateľských Gravatar ikon + permission_edit_own_issue_notes: Úprava vlastných poznámok k úlohe + setting_gravatar_enabled: PoužívaÅ¥ používateľské Gravatar ikonky permission_edit_own_messages: Úprava vlastných správ permission_delete_own_messages: Mazanie vlastných správ - text_repository_usernames_mapping: "Vyberte alebo upravte mapovanie medzi užívateľmi systému Redmine a užívateľskými menami nájdenými v logu repozitára.\nUžívatelia s rovnakým prihlasovacím menom alebo emailom v systéme Redmine a repozitára sú mapovaní automaticky." + text_repository_usernames_mapping: "Vyberte alebo aktualizujte priradenie používateľov systému Redmine k menám používateľov nájdených v logu repozitára.\nPoužívatelia s rovnakým prihlasovacím menom alebo emailom v systéme Redmine a repozitári sú priradení automaticky." label_example: Príklad - label_user_activity: "Aktivita užívateľa %{value}" - label_updated_time_by: "Aktualizované užívateľom %{author} pred %{age}" - text_diff_truncated: '... Tento rozdielový výpis bol skratený, pretože prekraÄuje maximálnu veľkosÅ¥, ktorá môže byÅ¥ zobrazená.' - setting_diff_max_lines_displayed: Maximálne množstvo zobrazených riadkov rozdielového výpisu - text_plugin_assets_writable: Adresár pre pluginy s možnosÅ¥ou zápisu - warning_attachments_not_saved: "%{count} súbor(y) nemohol(li) byÅ¥ uložené." + label_user_activity: "Aktivita používateľa %{value}" + label_updated_time_by: "Aktualizované používateľom %{author} pred %{age}" + text_diff_truncated: '...Tento výpis rozdielov bol skrátený, pretože prekraÄuje maximálny poÄet riadkov, ktorý môže byÅ¥ zobrazený.' + setting_diff_max_lines_displayed: Maximálny poÄet zobrazených riadkov výpisu rozdielov + text_plugin_assets_writable: Povolený zápis do prieÄinka pre pluginy + warning_attachments_not_saved: "%{count} súborov nemohlo byÅ¥ uložených." field_editable: Editovateľné label_display: Zobrazenie button_create_and_continue: VytvoriÅ¥ a pokraÄovaÅ¥ - text_custom_field_possible_values_info: 'Jeden riadok pre každú hodnotu' - setting_repository_log_display_limit: Maximálne množstvo revizií zobrazené v logu + text_custom_field_possible_values_info: 'Každá hodnota na samostatný riadok' + setting_repository_log_display_limit: Maximálny poÄet revízií zobrazených v log súbore setting_file_max_size_displayed: Maximálna veľkosÅ¥ textových súborov zobrazených priamo na stránke - field_watcher: Pozorovateľ - setting_openid: PovoliÅ¥ OpenID prihlasovanie a registráciu + field_watcher: Pozorovatelia + setting_openid: PovoliÅ¥ prihlasovanie a registráciu pomocou OpenID field_identity_url: OpenID URL label_login_with_open_id_option: alebo sa prihlásiÅ¥ pomocou OpenID field_content: Obsah - label_descending: Zostupné + label_descending: Zostupne label_sort: Zoradenie - label_ascending: Rastúce + label_ascending: Vzostupne label_date_from_to: Od %{start} do %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Táto stránka má %{descendants} podstránku/y a potomka/ov. ÄŒo chcete vykonaÅ¥? - text_wiki_page_reassign_children: PreradiÅ¥ podstránky k tejto hlavnej stránke + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + text_wiki_page_destroy_question: Táto stránka má %{descendants} podstránok a potomkov. ÄŒo si želáte urobiÅ¥? + text_wiki_page_reassign_children: PreradiÅ¥ podstránky k tejto nadradenej stránke text_wiki_page_nullify_children: ZachovaÅ¥ podstránky ako hlavné stránky text_wiki_page_destroy_children: VymazaÅ¥ podstránky a vÅ¡etkých ich potomkov setting_password_min_length: Minimálna dĺžka hesla - field_group_by: Skupinové výsledky podľa - mail_subject_wiki_content_updated: "'%{id}' Wiki stránka bola aktualizovaná" - label_wiki_content_added: Wiki stránka pridaná - mail_subject_wiki_content_added: "'%{id}' Wiki stránka bola pridaná" - mail_body_wiki_content_added: The '%{id}' Wiki stránka bola pridaná užívateľom %{author}. + field_group_by: ZoskupiÅ¥ výsledky podľa + mail_subject_wiki_content_updated: "Wikistránka '%{id}' bola aktualizovaná" + label_wiki_content_added: Wikistránka bola pridaná + mail_subject_wiki_content_added: "Bola pridaná wikistránka '%{id}'" + mail_body_wiki_content_added: Používateľ %{author} pridal wikistránku '%{id}'. permission_add_project: Vytvorenie projektu - label_wiki_content_updated: Wiki stránka aktualizovaná - mail_body_wiki_content_updated: Wiki stránka '%{id}' bola aktualizovaná užívateľom %{author}. - setting_new_project_user_role_id: Rola dána non-admin užívateľovi, ktorý vytvorí projekt - label_view_all_revisions: ZobraziÅ¥ vÅ¡etkz revízie - label_tag: Tag + label_wiki_content_updated: Wikistránka bola aktualizovaná + mail_body_wiki_content_updated: Používateľ %{author} aktualizoval wikistránku '%{id}'. + setting_new_project_user_role_id: Rola non-admin používateľa, ktorý vytvorí projekt + label_view_all_revisions: ZobraziÅ¥ vÅ¡etky revízie + label_tag: Å títok label_branch: Vetva - error_no_tracker_in_project: K tomuto projektu nieje priradená žiadna fronta. Prosím skontrolujte nastavenie projektu. - error_no_default_issue_status: Nieje definovaný východzí stav úlohy. Prosím skontrolujte vase nastavenie (ChoÄte na "Administrácia -> Stavz úloh"). - text_journal_changed: "%{label} zmenené z %{old} na %{new}" - text_journal_set_to: "%{label} nastavené na %{value}" - text_journal_deleted: "%{label} zmazané (%{old})" + error_no_tracker_in_project: K tomuto projektu nie je priradený žiadny front. Skontrolujte prosím nastavenia projektu. + error_no_default_issue_status: Nie je definovaný predvolený stav úlohy. Skontrolujte prosím vaÅ¡u konfiguráciu (choÄte na "Administrácia -> Stavy úloh"). + text_journal_changed: "%{label} bolo zmenené z %{old} na %{new}" + text_journal_set_to: "%{label} bolo nastavené na %{value}" + text_journal_deleted: "%{label} bolo vymazané (%{old})" label_group_plural: Skupiny label_group: Skupina label_group_new: Nová skupina label_time_entry_plural: Strávený Äas - text_journal_added: "%{label} %{value} pridané" + text_journal_added: "%{label} %{value} bolo pridané" field_active: Aktívne enumeration_system_activity: Aktivita systému - permission_delete_issue_watchers: OdstrániÅ¥ pozorovateľov - version_status_closed: zavreté - version_status_locked: uzavreté + permission_delete_issue_watchers: Odstránenie pozorovateľov + version_status_closed: uzavreté + version_status_locked: uzamknuté version_status_open: otvorené - error_can_not_reopen_issue_on_closed_version: Úloha priradená uzavretej verzií nemôže byÅ¥ znovu-otvorená + error_can_not_reopen_issue_on_closed_version: Nemožno opäť otvoriÅ¥ úlohu, ktorá bola priradená uzavretej verzii label_user_anonymous: Anonym - button_move_and_follow: Presunúť a následovaÅ¥ - setting_default_projects_modules: Prednastavené aktívne moduly pre nové projekty - setting_gravatar_default: Východzí Gravatar obrázok + button_move_and_follow: Presunúť a sledovaÅ¥ + setting_default_projects_modules: Predvolené povolené moduly pre nové projekty + setting_gravatar_default: Predvolený Gravatar obrázok field_sharing: Zdieľanie label_version_sharing_hierarchy: S hierarchiou projektu - label_version_sharing_system: So vÅ¡etkými projektami - label_version_sharing_descendants: S podprojektami + label_version_sharing_system: So vÅ¡etkými projektmi + label_version_sharing_descendants: S podprojektmi label_version_sharing_tree: S projektovým stromom - label_version_sharing_none: Nezdielané + label_version_sharing_none: Nezdieľané error_can_not_archive_project: Tento projekt nemôže byÅ¥ archivovaný button_duplicate: DuplikovaÅ¥ - button_copy_and_follow: KopírovaÅ¥ a následovaÅ¥ + button_copy_and_follow: KopírovaÅ¥ a sledovaÅ¥ label_copy_source: Zdroj - setting_issue_done_ratio: VyrátaÅ¥ pomer vypracovania úlohy s + setting_issue_done_ratio: VyrátaÅ¥ mieru vypracovania úlohy pomocou setting_issue_done_ratio_issue_status: PoužiÅ¥ stav úlohy - error_issue_done_ratios_not_updated: Stav vypracovania úlohy neaktualizovaný. - error_workflow_copy_target: Prosím zvoľte cieľovú frontu(y) a rolu(e) + error_issue_done_ratios_not_updated: Stav vypracovania úlohy nebol aktualizovaný. + error_workflow_copy_target: Vyberte prosím jednu alebo viaceré cieľové fronty a roly setting_issue_done_ratio_issue_field: PoužiÅ¥ pole úlohy label_copy_same_as_target: Rovnaké ako cieľ label_copy_target: Cieľ - notice_issue_done_ratios_updated: Stav vypracovania úlohy aktualizovaný. - error_workflow_copy_source: Prosím zvoľte zdrojovú frontu alebo rolu - label_update_issue_done_ratios: Aktualizácia stavu úloh - setting_start_of_week: Å tart pracovného týždňa v - permission_view_issues: ZobraziÅ¥ úlohy - label_display_used_statuses_only: ZobraziÅ¥ len stavy, ktoré sú priradené k tejto fronte - label_revision_id: Revízia %{value} - label_api_access_key: API prístupový kÄ¾ÃºÄ - label_api_access_key_created_on: API prístupový kÄ¾ÃºÄ vytvorený pred %{value} - label_feeds_access_key: RSS prístupový kÄ¾ÃºÄ - notice_api_access_key_reseted: Váš API prístupový kÄ¾ÃºÄ bol resetovaný. - setting_rest_api_enabled: Zapnúť REST web službu - label_missing_api_access_key: API prístupový kľuÄ nenájdený - label_missing_feeds_access_key: RSS prístupový kÄ¾ÃºÄ nenájdený + notice_issue_done_ratios_updated: Stav vypracovania úlohy bol aktualizovaný. + error_workflow_copy_source: Vyberte prosím zdrojový front alebo rolu + label_update_issue_done_ratios: AktualizovaÅ¥ stav vypracovania úlohy + setting_start_of_week: Pracovný týždeň zaÄína v + permission_view_issues: Zobrazenie úloh + label_display_used_statuses_only: ZobraziÅ¥ len stavy, ktoré sú priradené k tomuto frontu + label_revision_id: "Revízia %{value}" + label_api_access_key: Prístupový kÄ¾ÃºÄ API + label_api_access_key_created_on: "Prístupový kÄ¾ÃºÄ API bol vytvorený pred %{value}" + label_feeds_access_key: Prístupový kÄ¾ÃºÄ Atom + notice_api_access_key_reseted: Váš prístupový kÄ¾ÃºÄ API bol resetovaný. + setting_rest_api_enabled: Zapnúť webovú službu REST + label_missing_api_access_key: Prístupový kľuÄ API nenájdený + label_missing_feeds_access_key: Prístupový kÄ¾ÃºÄ Atom nenájdený button_show: ZobraziÅ¥ - text_line_separated: MožnosÅ¥ viacerých hodnôt (jeden riadok pre každú hodnotu). - setting_mail_handler_body_delimiters: OrezaÅ¥ emaily po následujúcich riadkoch + text_line_separated: Je povolené zadaÅ¥ viaceré hodnoty (každú hodnotu na samostatný riadok). + setting_mail_handler_body_delimiters: "OrezaÅ¥ emaily po jednom z nasledovných riadkov" permission_add_subprojects: Vytváranie podprojektov label_subproject_new: Nový podprojekt - text_own_membership_delete_confirmation: |- - Práve sa pokúšate o odstránenie niektorých alebo vÅ¡etkých prístupových práv a možno nebudete maÅ¥ možnost naÄalej upravovaÅ¥ tento projekt. - Ste si istý(á), že chcete pokraÄovat? - label_close_versions: UzavrieÅ¥ ukonÄené verzie - label_board_sticky: Sticky + text_own_membership_delete_confirmation: "Pokúšate sa odstrániÅ¥ niektoré alebo vÅ¡etky svoje prístupové práva a je možné, že už nebudete maÅ¥ možnosÅ¥ naÄalej upravovaÅ¥ tento projekt.\nNaozaj si želáte pokraÄovaÅ¥?" + label_close_versions: UzavrieÅ¥ dokonÄené verzie + label_board_sticky: Dôležité label_board_locked: Uzamknuté - permission_export_wiki_pages: ExportovaÅ¥ WiKi stránky - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: NastavovaÅ¥ aktivity projektu - error_unable_delete_issue_status: Nieje možné zmeniÅ¥ stav úlohy + permission_export_wiki_pages: Export wikistránok + setting_cache_formatted_text: UložiÅ¥ formátovaný text do vyrovnávacej pamäte + permission_manage_project_activities: Správa aktivít projektu + error_unable_delete_issue_status: 'Nie je možné vymazaÅ¥ stav úlohy' label_profile: Profil - permission_manage_subtasks: NastavovaÅ¥ podúlohy + permission_manage_subtasks: Správa podúloh field_parent_issue: Nadradená úloha label_subtask_plural: Podúlohy - label_project_copy_notifications: ZaslaÅ¥ emailové upozornenie behom kopírovania projektu - error_can_not_delete_custom_field: Nieje možné vymazaÅ¥ užívateľské pole - error_unable_to_connect: Nieje možné vymazaÅ¥ (%{value}) - error_can_not_remove_role: Táto roľa sa používa a nemôže byÅ¥ vymazaná. - error_can_not_delete_tracker: Táto fronta obsahuje úlohy a nemôže byÅ¥ vymazaná. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Kódovanie prenášaných správ - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module + label_project_copy_notifications: ZaslaÅ¥ emailové upozornenie poÄas kopírovania projektu + error_can_not_delete_custom_field: Nie je možné vymazaÅ¥ vlastné pole + error_unable_to_connect: Nie je možné sa pripojiÅ¥ (%{value}) + error_can_not_remove_role: "Táto rola sa používa a nemôže byÅ¥ vymazaná." + error_can_not_delete_tracker: "Tento front obsahuje úlohy a nemôže byÅ¥ vymazaný." + field_principal: Objednávateľ + label_my_page_block: Blok obsahu + notice_failed_to_save_members: "Nepodarilo sa uložiÅ¥ Älenov: %{errors}." + text_zoom_out: OddialiÅ¥ + text_zoom_in: PriblížiÅ¥ + notice_unable_delete_time_entry: Nie je možné vymazaÅ¥ Äasový záznam. + label_overall_spent_time: Celkový strávený Äas + field_time_entries: Zaznamenaný Äas + project_module_gantt: Ganttov graf + project_module_calendar: Kalendár + button_edit_associated_wikipage: "UpraviÅ¥ súvisiacu wikistránku: %{page_title}" + field_text: Textové pole + label_user_mail_option_only_owner: "Len pre veci, ktorých som vlastníkom" + setting_default_notification_option: Predvolené nastavenie upozornení + label_user_mail_option_only_my_events: Len pre veci, ktoré sledujem, alebo v ktorých som zapojený + label_user_mail_option_only_assigned: "Len pre veci, ktoré mi boli priradené" + label_user_mail_option_none: "Žiadne udalosti" + field_member_of_group: "Skupina priradeného používateľa" + field_assigned_to_role: "Rola priradeného používateľa" + notice_not_authorized_archived_project: Projekt, ku ktorému sa snažíte pristupovaÅ¥, bol archivovaný. + label_principal_search: "HľadaÅ¥ používateľa alebo skupinu:" + label_user_search: "HľadaÅ¥ používateľa:" + field_visible: Viditeľné + setting_commit_logtime_activity_id: Aktivita pre zaznamenaný Äas + text_time_logged_by_changeset: "Aplikované v súbore zmien %{value}." + setting_commit_logtime_enabled: PovoliÅ¥ zaznamenávanie Äasu + notice_gantt_chart_truncated: "Graf bol skrátený, pretože bol prekroÄený maximálny povolený poÄet zobrazených položiek (%{max})" + setting_gantt_items_limit: Maximálny poÄet položiek zobrazených na Ganttovom grafe + field_warn_on_leaving_unsaved: "VarovaÅ¥ ma, keÄ opúšťam stránku s neuloženým textom" + text_warn_on_leaving_unsaved: "Stránka, ktorú sa chystáte opustiÅ¥, obsahuje neuložený obsah. Ak stránku opustíte, neuložený obsah bude stratený." + label_my_queries: Moje filtre vyhľadávania + text_journal_changed_no_detail: "%{label} bolo aktualizované" + label_news_comment_added: Komentár k novinke bol pridaný + button_expand_all: RozbaliÅ¥ vÅ¡etko + button_collapse_all: ZbaliÅ¥ vÅ¡etko + label_additional_workflow_transitions_for_assignee: ÄŽalÅ¡ie zmeny stavu sú povolené, len ak je používateľ priradený + label_additional_workflow_transitions_for_author: ÄŽalÅ¡ie zmeny stavu sú povolené, len ak je používateľ autorom + label_bulk_edit_selected_time_entries: Hromadne upraviÅ¥ vybrané Äasové záznamy + text_time_entries_destroy_confirmation: 'Naozaj si želáte vymazaÅ¥ vybrané Äasové záznamy?' + label_role_anonymous: Anonymný + label_role_non_member: Nie je Älenom + label_issue_note_added: Poznámka bola pridaná + label_issue_status_updated: Stav bol aktualizovaný + label_issue_priority_updated: Priorita bola aktualizovaná + label_issues_visibility_own: Úlohy od používateľa alebo priradené používateľovi + field_issues_visibility: ViditeľnosÅ¥ úloh + label_issues_visibility_all: VÅ¡etky úlohy + permission_set_own_issues_private: Nastavenie vlastných úloh ako verejné alebo súkromné + field_is_private: Súkromné + permission_set_issues_private: Nastavenie úloh ako verejné alebo súkromné + label_issues_visibility_public: VÅ¡etky úlohy, ktoré nie sú súkromné + text_issues_destroy_descendants_confirmation: "Týmto krokom vymažate aj %{count} podúloh." + field_commit_logs_encoding: Kódovanie komentárov ku kommitom + field_scm_path_encoding: Kódovanie cesty SCM + text_scm_path_encoding_note: "Predvolené: UTF-8" + field_path_to_repository: Cesta k repozitáru + field_root_directory: Koreňový adresár + field_cvs_module: Modul field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope + text_mercurial_repository_note: Lokálny repozitár (napr. /hgrepo, c:\hgrepo) + text_scm_command: Príkaz + text_scm_command_version: Verzia + label_git_report_last_commit: HlásiÅ¥ posledný commit pre súbory a prieÄinky + notice_issue_successful_create: "Úloha %{id} bola vytvorená." + label_between: medzi + setting_issue_group_assignment: PovoliÅ¥ priradenie úlohy skupine + label_diff: rozdiely + text_git_repository_note: Repozitár je iba jeden a je lokálny (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Smer triedenia + description_project_scope: Rozsah vyhľadávania description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns + description_user_mail_notification: Nastavenia upozornení emailom + description_date_from: Zadajte poÄiatoÄný dátum + description_message_content: Obsah správy + description_available_columns: Dostupné stĺpce + description_date_range_interval: Zvoľte obdobie zadaním poÄiatoÄného a koncového dátumu + description_issue_category_reassign: VybraÅ¥ kategóriu úlohy + description_search: Vyhľadávanie + description_notes: Poznámky + description_date_range_list: Vyberte zo zoznamu obdobie + description_choose_project: Projekty + description_date_to: Zadajte koncový dátum + description_query_sort_criteria_attribute: Atribút triedenia + description_wiki_subpages_reassign: VybraÅ¥ novú nadradenú stránku + description_selected_columns: Vybrané stĺpce + label_parent_revision: RodiÄ + label_child_revision: Potomok + error_scm_annotate_big_text_file: "Komentár nemohol byÅ¥ pridaný, pretože bola prekroÄená maximálna povolená veľkosÅ¥ textového súboru." + setting_default_issue_start_date_to_creation_date: PoužiÅ¥ aktuálny dátum ako poÄiatoÄný dátum pri nových úlohách + button_edit_section: UpraviÅ¥ túto sekciu + setting_repositories_encodings: Kódovania pre prílohy a repozitáre + description_all_columns: VÅ¡etky stĺpce button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_export_options: "Nastavenia exportu do %{export_format}" + error_attachment_too_big: "Súbor nemožno nahraÅ¥, pretože prekraÄuje maximálnu povolenú veľkosÅ¥ súboru (%{max_size})" + notice_failed_to_save_time_entries: "%{count} Äasových záznamov z %{total} vybraných nebolo uložených: %{ids}." label_x_issues: - zero: 0 Úloha - one: 1 Úloha - other: "%{count} Úlohy" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project + zero: 0 úloh + one: 1 úloha + other: "%{count} úloh" + label_repository_new: Nový repozitár + field_repository_is_default: Hlavný repozitár + label_copy_attachments: KopírovaÅ¥ prílohy + label_item_position: "%{position} z %{count}" + label_completed_versions: DokonÄené verzie + text_project_identifier_info: 'Sú povolené iba malé písmená (a-z), Äísla, pomlÄky a podÄiarkovníky. Identifikátor musí zaÄínaÅ¥ malým písmenom.
    Po uložení už nie je možné identifikátor zmeniÅ¥.' + field_multiple: Viacero hodnôt + setting_commit_cross_project_ref: PovoliÅ¥ referencie a opravy úloh vÅ¡etkých ostatných projektov + text_issue_conflict_resolution_add_notes: PridaÅ¥ moje poznámky a zahodiÅ¥ ostatné moje zmeny + text_issue_conflict_resolution_overwrite: "Napriek tomu aplikovaÅ¥ moje zmeny (predchádzajúce poznámky budú zachované, ale niektoré zmeny môžu byÅ¥ prepísané)" + notice_issue_update_conflict: "PoÄas vaÅ¡ich úprav bola úloha aktualizovaná iným používateľom." + text_issue_conflict_resolution_cancel: "ZahodiÅ¥ vÅ¡etky moje zmeny a znovu zobraziÅ¥ %{link}" + permission_manage_related_issues: Správa súvisiacich úloh + field_auth_source_ldap_filter: Filter LDAP + label_search_for_watchers: HľadaÅ¥ pozorovateľov, ktorých chcete pridaÅ¥ + notice_account_deleted: "Váš úÄet bol natrvalo vymazaný." + setting_unsubscribe: PovoliÅ¥ používateľom vymazaÅ¥ svoj vlastný úÄet + button_delete_my_account: VymazaÅ¥ môj úÄet + text_account_destroy_confirmation: "Naozaj si želáte pokraÄovaÅ¥?\nVáš úÄet bude natrvalo vymazaný, bez možnosti obnovenia." + error_session_expired: "VaÅ¡a relácia vyprÅ¡ala. Prihláste sa prosím znovu." + text_session_expiration_settings: "Varovanie: zmenou týchto nastavení môžu vyprÅ¡aÅ¥ aktuálne relácie vrátane vaÅ¡ej." + setting_session_lifetime: Maximálny Äas relácie + setting_session_timeout: VyprÅ¡anie relácie pri neaktivite + label_session_expiration: VyprÅ¡anie relácie + permission_close_project: Uzavretie / opätovné otvorenie projektu + label_show_closed_projects: ZobraziÅ¥ uzavreté projekty + button_close: UzavrieÅ¥ + button_reopen: Opäť otvoriÅ¥ + project_status_active: aktívny + project_status_closed: uzavretý + project_status_archived: archivovaný + text_project_closed: Tento projekt je uzavretý a prístupný iba na Äítanie. + notice_user_successful_create: "Používateľ %{id} bol vytvorený." + field_core_fields: Å tandardné polia + field_timeout: "VyprÅ¡anie (v sekundách)" + setting_thumbnails_enabled: ZobraziÅ¥ miniatúry príloh + setting_thumbnails_size: VeľkosÅ¥ miniatúry (v pixeloch) + label_status_transitions: Zmeny stavu + label_fields_permissions: Oprávnenia k poliam + label_readonly: Iba na Äítanie + label_required: Povinné + text_repository_identifier_info: 'Sú povolené iba malé písmená (a-z), Äísla, pomlÄky a podÄiarkovníky.
    Po uložení už nie je možné identifikátor zmeniÅ¥.' + field_board_parent: Nadradené fórum + label_attribute_of_project: "%{name} projektu" + label_attribute_of_author: "%{name} autora" + label_attribute_of_assigned_to: "%{name} priradeného používateľa" + label_attribute_of_fixed_version: "%{name} cieľovej verzie" + label_copy_subtasks: KopírovaÅ¥ podúlohy + label_copied_to: Skopírované do + label_copied_from: Skopírované z + label_any_issues_in_project: vÅ¡etky úlohy projektu + label_any_issues_not_in_project: vÅ¡etky úlohy nepatriace do projektu + field_private_notes: Súkromné poznámky + permission_view_private_notes: Zobrazenie súkromných poznámok + permission_set_notes_private: Nastavenie poznámok ako súkromné + label_no_issues_in_project: žiadne úlohy projektu label_any: vÅ¡etko - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: S podprojektami + label_last_n_weeks: "posledných %{count} týždňov" + setting_cross_project_subtasks: PovoliÅ¥ podúlohy naprieÄ projektmi + label_cross_project_descendants: S podprojektmi label_cross_project_tree: S projektovým stromom label_cross_project_hierarchy: S hierarchiou projektu - label_cross_project_system: So vÅ¡etkými projektami - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past + label_cross_project_system: So vÅ¡etkými projektmi + button_hide: SkryÅ¥ + setting_non_working_week_days: Dni voľna + label_in_the_next_days: poÄas nasledujúcich + label_in_the_past_days: poÄas minulých + label_attribute_of_user: "%{name} používateľa" + text_turning_multiple_off: "Ak zakážete výber viacerých hodnôt, polia s výberom viacerých hodnôt budú vyÄistené. Tým sa zaistí, aby bola pre každé pole vybraná vždy len jedna hodnota." + label_attribute_of_issue: "%{name} úlohy" + permission_add_documents: Pridávanie dokumentov + permission_edit_documents: Úprava dokumentov + permission_delete_documents: Mazanie dokumentov + label_gantt_progress_line: Indikátor napredovania + setting_jsonp_enabled: PovoliÅ¥ podporu JSONP + field_inherit_members: ZdediÅ¥ Älenov + field_closed_on: Uzavreté + field_generate_password: VygenerovaÅ¥ heslo + setting_default_projects_tracker_ids: Predvolené fronty pri nových projektoch + label_total_time: Celkový Äas + text_scm_config: Svoje príkazy SCM môžete konfigurovaÅ¥ v súbore config/configuration.yml. Po jeho úprave prosím reÅ¡tartujte aplikáciu. + text_scm_command_not_available: Príkaz SCM nie je k dispozícii. Skontrolujte prosím nastavenia na administraÄnom paneli. + setting_emails_header: HlaviÄka emailu + notice_account_not_activated_yet: Váš úÄet eÅ¡te nebol aktivovaný. Ak potrebujete zaslaÅ¥ nový aktivaÄný email, kliknite prosím na tento odkaz. + notice_account_locked: Váš úÄet je uzamknutý. + label_hidden: Skryté + label_visibility_private: iba pre mňa + label_visibility_roles: iba pre tieto roly + label_visibility_public: pre vÅ¡etkých používateľov + field_must_change_passwd: Pri najbližšom prihlásení je potrebné zmeniÅ¥ heslo + notice_new_password_must_be_different: Nové heslo nesmie byÅ¥ rovnaké ako súÄasné heslo + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/sl.yml --- a/config/locales/sl.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/sl.yml Tue Sep 09 09:34:53 2014 +0100 @@ -6,9 +6,9 @@ # Use the strftime parameters for formats. # When no format has been given, it uses default. # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" + default: "%d.%m.%Y" + short: "%d. %b" + long: "%d. %B, %Y" day_names: [Nedelja, Ponedeljek, Torek, Sreda, ÄŒetrtek, Petek, Sobota] abbr_day_names: [Ned, Pon, To, Sr, ÄŒet, Pet, Sob] @@ -18,16 +18,16 @@ abbr_month_names: [~, Jan, Feb, Mar, Apr, Maj, Jun, Jul, Aug, Sep, Okt, Nov, Dec] # Used in date_select and datime_select. order: + - :day + - :month - :year - - :month - - :day time: formats: - default: "%a, %d %b %Y %H:%M:%S %z" + default: "%a, %d. %b, %Y %H:%M:%S %z" time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" + short: "%d. %b %H:%M" + long: "%d. %B, %Y %H:%M" am: "am" pm: "pm" @@ -50,8 +50,8 @@ one: "okrog 1. ure" other: "okrog %{count} ur" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ura" + other: "%{count} ur" x_days: one: "1 dan" other: "%{count} dni" @@ -128,6 +128,7 @@ not_same_project: "ne pripada istemu projektu" circular_dependency: "Ta odnos bi povzroÄil krožno odvisnost" cant_link_an_issue_with_a_descendant: "Zahtevek ne more biti povezan s svojo podnalogo" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Prosimo izberite @@ -160,7 +161,7 @@ notice_not_authorized: Nimate privilegijev za dostop do te strani. notice_email_sent: "E-poÅ¡tno sporoÄilo je bilo poslano %{value}" notice_email_error: "Ob poÅ¡iljanju e-sporoÄila je priÅ¡lo do napake (%{value})" - notice_feeds_access_key_reseted: VaÅ¡ RSS dostopni kljuÄ je bil ponastavljen. + notice_feeds_access_key_reseted: VaÅ¡ Atom dostopni kljuÄ je bil ponastavljen. notice_failed_to_save_issues: "Neuspelo shranjevanje %{count} zahtevka na %{total} izbranem: %{ids}." notice_no_issue_selected: "Izbran ni noben zahtevek! Prosimo preverite zahtevke, ki jih želite urediti." notice_account_pending: "VaÅ¡ raÄun je bil ustvarjen in Äaka na potrditev s strani administratorja." @@ -178,14 +179,12 @@ mail_subject_register: "Aktivacija %{value} vaÅ¡ega raÄuna" mail_body_register: 'Za aktivacijo vaÅ¡ega raÄuna kliknite na naslednjo povezavo:' mail_body_account_information_external: "Za prijavo lahko uporabite vaÅ¡ %{value} raÄun." - mail_body_account_information: Informacije o vaÅ¡em raÄunu + mail_body_account_information: "Informacije o vaÅ¡em raÄunu. Za spremembo gesla sledite linku 'Spremeni geslo' na naslovu za prijavo, spodaj." mail_subject_account_activation_request: "%{value} zahtevek za aktivacijo raÄuna" mail_body_account_activation_request: "Registriral se je nov uporabnik (%{value}). RaÄun Äaka na vaÅ¡o odobritev:" mail_subject_reminder: "%{count} zahtevek(zahtevki) zapadejo v naslednjih %{days} dneh" mail_body_reminder: "%{count} zahtevek(zahtevki), ki so vam dodeljeni bodo zapadli v naslednjih %{days} dneh:" - gui_validation_error: 1 napaka - gui_validation_error_plural: "%{count} napak" field_name: Ime field_description: Opis @@ -286,7 +285,7 @@ setting_host_name: Ime gostitelja in pot setting_text_formatting: Oblikovanje besedila setting_wiki_compression: Stiskanje Wiki zgodovine - setting_feeds_limit: Meja obsega RSS virov + setting_feeds_limit: Meja obsega Atom virov setting_default_projects_public: Novi projekti so privzeto javni setting_autofetch_changesets: Samodejni izvleÄek zapisa sprememb setting_sys_api_enabled: OmogoÄi WS za upravljanje shrambe @@ -335,7 +334,6 @@ permission_edit_own_time_entries: Uredi beležko lastnega Äasa permission_manage_news: Uredi novice permission_comment_news: Komentiraj novice - permission_manage_documents: Uredi dokumente permission_view_documents: Poglej dokumente permission_manage_files: Uredi datoteke permission_view_files: Poglej datoteke @@ -417,21 +415,21 @@ label_information_plural: Informacije label_please_login: Prosimo prijavite se label_register: Registracija - label_password_lost: Izgubljeno geslo + label_password_lost: Spremeni geslo label_home: Domov label_home_heading: Domov label_my_page: Moja stran label_my_account: Moj raÄun label_my_projects: Moji projekti label_administration: Upravljanje - label_login: Prijavi se - label_logout: Odjavi se + label_login: Prijava + label_logout: Odjava label_help: PomoÄ label_reported_issues: Prijavljeni zahtevki label_assigned_to_me_issues: Zahtevki dodeljeni meni label_last_login: Zadnja povezava label_registered_on: Registriran - label_activity: Aktivnost + label_activity: Aktivnosti label_overall_activity: Celotna aktivnost label_user_activity: "Aktivnost %{value}" label_new: Nov @@ -453,8 +451,6 @@ label_text: Dolgo besedilo label_attribute: Lastnost label_attribute_plural: Lastnosti - label_download: "%{count} Prenos" - label_download_plural: "%{count} Prenosi" label_no_data: Ni podatkov za prikaz label_change_status: Spremeni stanje label_history: Zgodovina @@ -472,7 +468,7 @@ label_news_view_all: Poglej vse novice label_news_added: Dodane novice label_settings: Nastavitve - label_overview: Pregled + label_overview: Povzetek label_version: Verzija label_version_new: Nova verzija label_version_plural: Verzije @@ -554,8 +550,6 @@ label_repository: Shramba label_repository_plural: Shrambe label_browse: Prebrskaj - label_modification: "%{count} sprememba" - label_modification_plural: "%{count} spremembe" label_revision: Revizija label_revision_plural: Revizije label_associated_revisions: Povezane revizije @@ -579,16 +573,16 @@ label_search: IÅ¡Äi label_result_plural: Rezultati label_all_words: Vse besede - label_wiki: Wiki - label_wiki_edit: Wiki urejanje - label_wiki_edit_plural: Wiki urejanja - label_wiki_page: Wiki stran - label_wiki_page_plural: Wiki strani + label_wiki: Predstavitev + label_wiki_edit: Uredi stran + label_wiki_edit_plural: Uredi strani + label_wiki_page: Predstavitvena stran + label_wiki_page_plural: Predstavitvene strani label_index_by_title: Razvrsti po naslovu label_index_by_date: Razvrsti po datumu label_current_version: Trenutna verzija label_preview: Predogled - label_feed_plural: RSS viri + label_feed_plural: Atom viri label_changes_details: Podrobnosti o vseh spremembah label_issue_tracking: Sledenje zahtevkom label_spent_time: Porabljen Äas @@ -644,11 +638,11 @@ label_language_based: Glede na uporabnikov jezik label_sort_by: "Razporedi po %{value}" label_send_test_email: PoÅ¡lji testno e-pismo - label_feeds_access_key_created_on: "RSS dostopni kljuÄ narejen %{value} nazaj" + label_feeds_access_key_created_on: "Atom dostopni kljuÄ narejen %{value} nazaj" label_module_plural: Moduli - label_added_time_by: "Dodan %{author} %{age} nazaj" - label_updated_time_by: "Posodobljen od %{author} %{age} nazaj" - label_updated_time: "Posodobljen %{value} nazaj" + label_added_time_by: "Dodal %{author} %{age} nazaj" + label_updated_time_by: "Posodobil %{author} %{age} nazaj" + label_updated_time: "Posodobljeno %{value} nazaj" label_jump_to_a_project: SkoÄi na projekt... label_file_plural: Datoteke label_changeset_plural: Zapisi sprememb @@ -746,7 +740,7 @@ text_issues_ref_in_commit_messages: Zahtevki sklicev in popravkov v sporoÄilu predaje text_issue_added: "Zahtevek %{id} je sporoÄil(a) %{author}." text_issue_updated: "Zahtevek %{id} je posodobil(a) %{author}." - text_wiki_destroy_confirmation: Ali ste prepriÄani da želite izbrisati ta wiki in vso njegovo vsebino? + text_wiki_destroy_confirmation: Ali ste prepriÄani da želite izbrisati to wiki stran in vso njeno vsebino? text_issue_category_destroy_question: "Nekateri zahtevki (%{count}) so dodeljeni tej kategoriji. Kaj želite storiti?" text_issue_category_destroy_assignments: Odstrani naloge v kategoriji text_issue_category_reassign_to: Ponovno dodeli zahtevke tej kategoriji @@ -878,11 +872,11 @@ label_revision_id: Revizija %{value} label_api_access_key: API dostopni kljuÄ label_api_access_key_created_on: API dostopni kljuÄ ustvarjen pred %{value} - label_feeds_access_key: RSS dostopni kljuÄ + label_feeds_access_key: Atom dostopni kljuÄ notice_api_access_key_reseted: VaÅ¡ API dostopni kljuÄ je bil ponastavljen. setting_rest_api_enabled: OmogoÄi REST spletni servis label_missing_api_access_key: ManjkajoÄ API dostopni kljuÄ - label_missing_feeds_access_key: ManjkajoÄ RSS dostopni kljuÄ + label_missing_feeds_access_key: ManjkajoÄ Atom dostopni kljuÄ button_show: Prikaži text_line_separated: Dovoljenih veÄ vrednosti (ena vrstica za vsako vrednost). setting_mail_handler_body_delimiters: Odreži e-poÅ¡to po eni od teh vrstic @@ -1082,3 +1076,29 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Skupaj + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/sq.yml --- a/config/locales/sq.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/sq.yml Tue Sep 09 09:34:53 2014 +0100 @@ -50,8 +50,8 @@ one: "about 1 hour" other: "about %{count} hours" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ore" + other: "%{count} ore" x_days: one: "1 day" other: "%{count} days" @@ -129,6 +129,7 @@ not_same_project: "nuk i perket te njejtit projekt" circular_dependency: "Ky relacion do te krijoje nje varesi ciklike (circular dependency)" cant_link_an_issue_with_a_descendant: "Nje ceshtje nuk mund te lidhet me nenceshtje" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Zgjidhni @@ -162,7 +163,7 @@ notice_not_authorized_archived_project: Projekti, qe po tentoni te te aksesoni eshte arkivuar. notice_email_sent: "Nje email eshte derguar ne %{value}" notice_email_error: "Pati nje gabim gjate dergimit te email-it (%{value})" - notice_feeds_access_key_reseted: Your RSS access key was reset. + notice_feeds_access_key_reseted: Your Atom access key was reset. notice_api_access_key_reseted: Your API access key was reset. notice_failed_to_save_issues: "Deshtoi ne ruajtjen e %{count} ceshtje(ve) ne %{total} te zgjedhura: %{ids}." notice_failed_to_save_time_entries: "Deshtoi ne ruajtjen e %{count} time entrie(s) ne %{total} te zgjedhura: %{ids}." @@ -214,8 +215,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page eshte modifikuar" mail_body_wiki_content_updated: "The '%{id}' wiki page eshte modifikuar nga %{author}." - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} gabime" field_name: Emri field_description: Pershkrimi @@ -353,8 +352,6 @@ setting_cross_project_issue_relations: Allow cross-project issue relations setting_issue_list_default_columns: Default columns displayed on the issue list setting_repositories_encodings: Attachments and repositories encodings - setting_emails_header: Emails header - setting_emails_footer: Emails footer setting_protocol: Protocol setting_per_page_options: Objects per page options setting_user_format: Users display format @@ -421,7 +418,6 @@ permission_edit_own_time_entries: Edit own time logs permission_manage_news: Manage news permission_comment_news: Comment news - permission_manage_documents: Manage documents permission_view_documents: View documents permission_manage_files: Manage files permission_view_files: View files @@ -552,8 +548,6 @@ label_text: Long text label_attribute: Attribute label_attribute_plural: Attributes - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: No data to display label_change_status: Change status label_history: Histori @@ -664,8 +658,6 @@ label_repository_new: New repository label_repository_plural: Repositories label_browse: Browse - label_modification: "%{count} ndryshim" - label_modification_plural: "%{count} ndryshime" label_branch: Dege label_tag: Tag label_revision: Revizion @@ -762,9 +754,9 @@ label_language_based: Based on user's language label_sort_by: "Sort by %{value}" label_send_test_email: Send a test email - label_feeds_access_key: RSS access key - label_missing_feeds_access_key: Missing a RSS access key - label_feeds_access_key_created_on: "RSS access key created %{value} ago" + label_feeds_access_key: Atom access key + label_missing_feeds_access_key: Missing a Atom access key + label_feeds_access_key_created_on: "Atom access key created %{value} ago" label_module_plural: Modules label_added_time_by: "Added by %{author} %{age} ago" label_updated_time_by: "Updated by %{author} %{age} ago" @@ -978,8 +970,6 @@ text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) text_scm_command: Command text_scm_command_version: Version - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" @@ -1077,3 +1067,33 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_footer: Email footer + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/sr-YU.yml --- a/config/locales/sr-YU.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/sr-YU.yml Tue Sep 09 09:34:53 2014 +0100 @@ -53,8 +53,8 @@ one: "približno jedan sat" other: "približno %{count} sati" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 sat" + other: "%{count} sati" x_days: one: "jedan dan" other: "%{count} dana" @@ -132,6 +132,7 @@ not_same_project: "ne pripada istom projektu" circular_dependency: "Ova veza će stvoriti kružnu referencu" cant_link_an_issue_with_a_descendant: "Problem ne može biti povezan sa jednim od svojih podzadataka" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Molim odaberite @@ -164,7 +165,7 @@ notice_not_authorized: Niste ovlašćeni za pristup ovoj strani. notice_email_sent: "E-poruka je poslata na %{value}" notice_email_error: "Dogodila se greÅ¡ka prilikom slanja e-poruke (%{value})" - notice_feeds_access_key_reseted: VaÅ¡ RSS pristupni kljuÄ je poniÅ¡ten. + notice_feeds_access_key_reseted: VaÅ¡ Atom pristupni kljuÄ je poniÅ¡ten. notice_api_access_key_reseted: VaÅ¡ API pristupni kljuÄ je poniÅ¡ten. notice_failed_to_save_issues: "NeuspeÅ¡no snimanje %{count} problema od %{total} odabranih: %{ids}." notice_failed_to_save_members: "NeuspeÅ¡no snimanje Älana(ova): %{errors}." @@ -209,8 +210,6 @@ mail_subject_wiki_content_updated: "Wiki stranica '%{id}' je ažurirana" mail_body_wiki_content_updated: "%{author} je ažurirao wiki stranicu '%{id}'." - gui_validation_error: jedna greÅ¡ka - gui_validation_error_plural: "%{count} greÅ¡aka" field_name: Naziv field_description: Opis @@ -290,7 +289,7 @@ field_delay: KaÅ¡njenje field_assignable: Problem može biti dodeljen ovoj ulozi field_redirect_existing_links: Preusmeri postojeće veze - field_estimated_hours: Proteklo vreme + field_estimated_hours: Procenjeno vreme field_column_names: Kolone field_time_zone: Vremenska zona field_searchable: Može da se pretražuje @@ -387,7 +386,6 @@ permission_edit_own_time_entries: Izmena sopstvenog utroÅ¡enog vremena permission_manage_news: Upravljanje vestima permission_comment_news: Komentarisanje vesti - permission_manage_documents: Upravljanje dokumentima permission_view_documents: Pregledanje dokumenata permission_manage_files: Upravljanje datotekama permission_view_files: Pregledanje datoteka @@ -511,8 +509,6 @@ label_text: Dugi tekst label_attribute: Osobina label_attribute_plural: Osobine - label_download: "%{count} preuzimanje" - label_download_plural: "%{count} preuzimanja" label_no_data: Nema podataka za prikazivanje label_change_status: Promena statusa label_history: Istorija @@ -615,8 +611,6 @@ label_repository: SpremiÅ¡te label_repository_plural: SpremiÅ¡ta label_browse: Pregledanje - label_modification: "%{count} promena" - label_modification_plural: "%{count} promena" label_branch: Grana label_tag: Oznaka label_revision: Revizija @@ -712,9 +706,9 @@ label_language_based: Bazirano na jeziku korisnika label_sort_by: "Sortirano po %{value}" label_send_test_email: Slanje probne e-poruke - label_feeds_access_key: RSS pristupni kljuÄ - label_missing_feeds_access_key: RSS pristupni kljuÄ nedostaje - label_feeds_access_key_created_on: "RSS pristupni kljuÄ je napravljen pre %{value}" + label_feeds_access_key: Atom pristupni kljuÄ + label_missing_feeds_access_key: Atom pristupni kljuÄ nedostaje + label_feeds_access_key_created_on: "Atom pristupni kljuÄ je napravljen pre %{value}" label_module_plural: Moduli label_added_time_by: "Dodao %{author} pre %{age}" label_updated_time_by: "Ažurirao %{author} pre %{age}" @@ -976,8 +970,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1084,3 +1076,31 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/sr.yml --- a/config/locales/sr.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/sr.yml Tue Sep 09 09:34:53 2014 +0100 @@ -51,8 +51,8 @@ one: "приближно један Ñат" other: "приближно %{count} Ñати" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 Ñат" + other: "%{count} Ñати" x_days: one: "један дан" other: "%{count} дана" @@ -130,6 +130,7 @@ not_same_project: "не припада иÑтом пројекту" circular_dependency: "Ова веза ће Ñтворити кружну референцу" cant_link_an_issue_with_a_descendant: "Проблем не може бити повезан Ñа једним од Ñвојих подзадатака" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Молим одаберите @@ -162,7 +163,7 @@ notice_not_authorized: ÐиÑте овлашћени за приÑтуп овој Ñтрани. notice_email_sent: "E-порука је поÑлата на %{value}" notice_email_error: "Догодила Ñе грешка приликом Ñлања е-поруке (%{value})" - notice_feeds_access_key_reseted: Ваш RSS приÑтупни кључ је поништен. + notice_feeds_access_key_reseted: Ваш Atom приÑтупни кључ је поништен. notice_api_access_key_reseted: Ваш API приÑтупни кључ је поништен. notice_failed_to_save_issues: "ÐеуÑпешно Ñнимање %{count} проблема од %{total} одабраних: %{ids}." notice_failed_to_save_members: "ÐеуÑпешно Ñнимање члана(ова): %{errors}." @@ -207,8 +208,6 @@ mail_subject_wiki_content_updated: "Wiki Ñтраница '%{id}' је ажурирана" mail_body_wiki_content_updated: "%{author} је ажурирао wiki Ñтраницу '%{id}'." - gui_validation_error: једна грешка - gui_validation_error_plural: "%{count} грешака" field_name: Ðазив field_description: ÐžÐ¿Ð¸Ñ @@ -385,7 +384,6 @@ permission_edit_own_time_entries: Измена ÑопÑтвеног утрошеног времена permission_manage_news: Управљање веÑтима permission_comment_news: КоментариÑање веÑти - permission_manage_documents: Управљање документима permission_view_documents: Прегледање докумената permission_manage_files: Управљање датотекама permission_view_files: Прегледање датотека @@ -509,8 +507,6 @@ label_text: Дуги текÑÑ‚ label_attribute: ОÑобина label_attribute_plural: ОÑобине - label_download: "%{count} преузимање" - label_download_plural: "%{count} преузимања" label_no_data: Ðема података за приказивање label_change_status: Промена ÑтатуÑа label_history: ИÑторија @@ -613,8 +609,6 @@ label_repository: Спремиште label_repository_plural: Спремишта label_browse: Прегледање - label_modification: "%{count} промена" - label_modification_plural: "%{count} промена" label_branch: Грана label_tag: Ознака label_revision: Ревизија @@ -710,9 +704,9 @@ label_language_based: Базирано на језику кориÑника label_sort_by: "Сортирано по %{value}" label_send_test_email: Слање пробне е-поруке - label_feeds_access_key: RSS приÑтупни кључ - label_missing_feeds_access_key: RSS приÑтупни кључ недоÑтаје - label_feeds_access_key_created_on: "RSS приÑтупни кључ је направљен пре %{value}" + label_feeds_access_key: Atom приÑтупни кључ + label_missing_feeds_access_key: Atom приÑтупни кључ недоÑтаје + label_feeds_access_key_created_on: "Atom приÑтупни кључ је направљен пре %{value}" label_module_plural: Модули label_added_time_by: "Додао %{author} пре %{age}" label_updated_time_by: "Ðжурирао %{author} пре %{age}" @@ -934,7 +928,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -975,8 +968,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1074,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Укупно + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/sv.yml --- a/config/locales/sv.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/sv.yml Tue Sep 09 09:34:53 2014 +0100 @@ -134,6 +134,7 @@ not_same_project: "tillhör inte samma projekt" circular_dependency: "Denna relation skulle skapa ett cirkulärt beroende" cant_link_an_issue_with_a_descendant: "Ett ärende kan inte länkas till ett av dess underärenden" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" direction: ltr date: @@ -204,7 +205,7 @@ notice_not_authorized_archived_project: Projektet du försöker komma Ã¥t har arkiverats. notice_email_sent: "Ett mail skickades till %{value}" notice_email_error: "Ett fel inträffade när mail skickades (%{value})" - notice_feeds_access_key_reseted: Din RSS-nyckel Ã¥terställdes. + notice_feeds_access_key_reseted: Din Atom-nyckel Ã¥terställdes. notice_api_access_key_reseted: Din API-nyckel Ã¥terställdes. notice_failed_to_save_issues: "Misslyckades med att spara %{count} ärende(n) pÃ¥ %{total} valda: %{ids}." notice_failed_to_save_time_entries: "Misslyckades med att spara %{count} tidloggning(ar) pÃ¥ %{total} valda: %{ids}." @@ -215,10 +216,10 @@ notice_unable_delete_version: Denna version var inte möjlig att ta bort. notice_unable_delete_time_entry: Tidloggning kunde inte tas bort. notice_issue_done_ratios_updated: "% klart uppdaterade." - notice_gantt_chart_truncated: "Schemat förminskades eftersom det överskrider det maximala antalet aktiviteter som fÃ¥r visas (%{max})" - notice_issue_successful_create: Ärende %{id} skapades. - notice_issue_update_conflict: Detta ärende har uppdaterats av en annan användare samtidigt som du redigerade det. - notice_account_deleted: Ditt konto har avslutats permanent. + notice_gantt_chart_truncated: "Schemat förminskades eftersom det överskrider det maximala antalet aktiviteter som kan visas (%{max})" + notice_issue_successful_create: "Ärende %{id} skapades." + notice_issue_update_conflict: "Detta ärende har uppdaterats av en annan användare samtidigt som du redigerade det." + notice_account_deleted: "Ditt konto har avslutats permanent." notice_user_successful_create: "Användare %{id} skapad." error_can_t_load_default_data: "Standardkonfiguration gick inte att läsa in: %{value}" @@ -239,7 +240,7 @@ error_workflow_copy_target: 'Vänligen välj ärendetyp(er) och roll(er) för mÃ¥l' error_unable_delete_issue_status: 'Ärendestatus kunde inte tas bort' error_unable_to_connect: "Kan inte ansluta (%{value})" - error_attachment_too_big: Denna fil kan inte laddas upp eftersom den överstiger maximalt tillÃ¥ten filstorlek (%{max_size}) + error_attachment_too_big: "Denna fil kan inte laddas upp eftersom den överstiger maximalt tillÃ¥ten filstorlek (%{max_size})" error_session_expired: "Din session har gÃ¥tt ut. Vänligen logga in pÃ¥ nytt." warning_attachments_not_saved: "%{count} fil(er) kunde inte sparas." @@ -258,8 +259,6 @@ mail_subject_wiki_content_updated: "'%{id}' wikisida har uppdaterats" mail_body_wiki_content_updated: "The '%{id}' wikisida har uppdaterats av %{author}." - gui_validation_error: 1 fel - gui_validation_error_plural: "%{count} fel" field_name: Namn field_description: Beskrivning @@ -274,6 +273,7 @@ field_author: Författare field_created_on: Skapad field_updated_on: Uppdaterad + field_closed_on: Stängd field_field_format: Format field_is_for_all: För alla projekt field_possible_values: Möjliga värden @@ -374,6 +374,8 @@ field_timeout: "Timeout (i sekunder)" field_board_parent: Förälderforum field_private_notes: Privata anteckningar + field_inherit_members: Ärv medlemmar + field_generate_password: Generera lösenord setting_app_title: Applikationsrubrik setting_app_subtitle: Applikationsunderrubrik @@ -442,6 +444,8 @@ setting_thumbnails_enabled: Visa miniatyrbilder av bilagor setting_thumbnails_size: Storlek pÃ¥ miniatyrbilder (i pixlar) setting_non_working_week_days: Lediga dagar + setting_jsonp_enabled: Aktivera JSONP-stöd + setting_default_projects_tracker_ids: Standardärendetyper för nya projekt permission_add_project: Skapa projekt permission_add_subprojects: Skapa underprojekt @@ -458,9 +462,9 @@ permission_manage_issue_relations: Hantera ärenderelationer permission_set_issues_private: Sätta ärenden publika eller privata permission_set_own_issues_private: Sätta egna ärenden publika eller privata - permission_add_issue_notes: Lägga till ärendenotering - permission_edit_issue_notes: Ändra ärendenoteringar - permission_edit_own_issue_notes: Ändra egna ärendenoteringar + permission_add_issue_notes: Lägga till ärendeanteckning + permission_edit_issue_notes: Ändra ärendeanteckningar + permission_edit_own_issue_notes: Ändra egna ärendeanteckningar permission_view_private_notes: Visa privata anteckningar permission_set_notes_private: Ställa in anteckningar som privata permission_move_issues: Flytta ärenden @@ -478,8 +482,10 @@ permission_edit_own_time_entries: Ändra egna tidloggningar permission_manage_news: Hantera nyheter permission_comment_news: Kommentera nyheter - permission_manage_documents: Hantera dokument permission_view_documents: Visa dokument + permission_add_documents: Lägga till dokument + permission_edit_documents: Ändra dokument + permission_delete_documents: Ta bort dokument permission_manage_files: Hantera filer permission_view_files: Visa filer permission_manage_wiki: Hantera wiki @@ -610,8 +616,6 @@ label_text: LÃ¥ng text label_attribute: Attribut label_attribute_plural: Attribut - label_download: "%{count} Nerladdning" - label_download_plural: "%{count} Nerladdningar" label_no_data: Ingen data att visa label_change_status: Ändra status label_history: Historia @@ -660,11 +664,13 @@ one: 1 ärende other: "%{count} ärenden" label_total: Total + label_total_time: Total tid label_permissions: Behörigheter label_current_status: Nuvarande status label_new_statuses_allowed: Nya tillÃ¥tna statusvärden label_all: alla - label_none: ingen + label_any: vad/vem som helst + label_none: inget/ingen label_nobody: ingen label_next: Nästa label_previous: FöregÃ¥ende @@ -728,8 +734,6 @@ label_repository_new: Nytt versionsarkiv label_repository_plural: Versionsarkiv label_browse: Bläddra - label_modification: "%{count} ändring" - label_modification_plural: "%{count} ändringar" label_branch: Branch label_tag: Tag label_revision: Revision @@ -791,13 +795,13 @@ label_loading: Laddar... label_relation_new: Ny relation label_relation_delete: Ta bort relation - label_relates_to: relaterar till - label_duplicates: kopierar - label_duplicated_by: kopierad av - label_blocks: blockerar - label_blocked_by: blockerad av - label_precedes: kommer före - label_follows: följer + label_relates_to: Relaterar till + label_duplicates: Kopierar + label_duplicated_by: Kopierad av + label_blocks: Blockerar + label_blocked_by: Blockerad av + label_precedes: Kommer före + label_follows: Följer label_copied_to: Kopierad till label_copied_from: Kopierad frÃ¥n label_end_to_start: slut till start @@ -828,9 +832,9 @@ label_language_based: SprÃ¥kbaserad label_sort_by: "Sortera pÃ¥ %{value}" label_send_test_email: Skicka testmail - label_feeds_access_key: RSS-nyckel - label_missing_feeds_access_key: Saknar en RSS-nyckel - label_feeds_access_key_created_on: "RSS-nyckel skapad för %{value} sedan" + label_feeds_access_key: Atom-nyckel + label_missing_feeds_access_key: Saknar en Atom-nyckel + label_feeds_access_key_created_on: "Atom-nyckel skapad för %{value} sedan" label_module_plural: Moduler label_added_time_by: "Tillagd av %{author} för %{age} sedan" label_updated_time_by: "Uppdaterad av %{author} för %{age} sedan" @@ -924,9 +928,16 @@ label_readonly: Skrivskyddad label_required: Nödvändig label_attribute_of_project: Projektets %{name} + label_attribute_of_issue: Ärendets %{name} label_attribute_of_author: Författarens %{name} - label_attribute_of_assigned_to: Tilldelads %{name} + label_attribute_of_assigned_to: Tilldelad användares %{name} + label_attribute_of_user: Användarens %{name} label_attribute_of_fixed_version: MÃ¥lversionens %{name} + label_cross_project_descendants: Med underprojekt + label_cross_project_tree: Med projektträd + label_cross_project_hierarchy: Med projekthierarki + label_cross_project_system: Med alla projekt + label_gantt_progress_line: Framstegslinje button_login: Logga in button_submit: Skicka @@ -1010,7 +1021,7 @@ text_tip_issue_begin_day: ärende som börjar denna dag text_tip_issue_end_day: ärende som slutar denna dag text_tip_issue_begin_end_day: ärende som börjar och slutar denna dag - text_project_identifier_info: Ändast gemener (a-z), siffror, streck och understreck är tillÃ¥tna.
    När identifieraren sparats kan den inte ändras. + text_project_identifier_info: 'Endast gemener (a-z), siffror, streck och understreck är tillåtna, måste börja med en bokstav.
    När identifieraren sparats kan den inte ändras.' text_caracters_maximum: "max %{count} tecken." text_caracters_minimum: "Måste vara minst %{count} tecken lång." text_length_between: "Längd mellan %{min} och %{max} tecken." @@ -1056,7 +1067,7 @@ text_own_membership_delete_confirmation: "Några av, eller alla, dina behörigheter kommer att tas bort och du kanske inte längre kommer kunna göra ändringar i det här projektet.\nVill du verkligen fortsätta?" text_zoom_out: Zooma ut text_zoom_in: Zooma in - text_warn_on_leaving_unsaved: Nuvarande sida innehåller osparad text som kommer försvinna om du lämnar sidan. + text_warn_on_leaving_unsaved: "Nuvarande sida innehåller osparad text som kommer försvinna om du lämnar sidan." text_scm_path_encoding_note: "Standard: UTF-8" text_git_repository_note: Versionsarkiv är tomt och lokalt (t.ex. /gitrepo, c:\gitrepo) text_mercurial_repository_note: Lokalt versionsarkiv (t.ex. /hgrepo, c:\hgrepo) @@ -1064,12 +1075,13 @@ text_scm_command_version: Version text_scm_config: Du kan konfigurera dina scm-kommando i config/configuration.yml. Vänligen starta om applikationen när ändringar gjorts. text_scm_command_not_available: Scm-kommando är inte tillgängligt. Vänligen kontrollera inställningarna i administratörspanelen. - text_issue_conflict_resolution_overwrite: Använd mina ändringar i alla fall (tidigare anteckningar kommer behållas men några ändringar kan bli överskrivna) - text_issue_conflict_resolution_add_notes: Lägg till mina anteckningar och kasta mina andra ändringar - text_issue_conflict_resolution_cancel: Kasta alla mina ändringar och visa igen %{link} + text_issue_conflict_resolution_overwrite: "Använd mina ändringar i alla fall (tidigare anteckningar kommer behållas men några ändringar kan bli överskrivna)" + text_issue_conflict_resolution_add_notes: "Lägg till mina anteckningar och kasta mina andra ändringar" + text_issue_conflict_resolution_cancel: "Kasta alla mina ändringar och visa igen %{link}" text_account_destroy_confirmation: "Är du säker på att du vill fortsätta?\nDitt konto kommer tas bort permanent, utan möjlighet att återaktivera det." text_session_expiration_settings: "Varning: ändring av dessa inställningar kan få alla nuvarande sessioner, inklusive din egen, att gå ut." text_project_closed: Detta projekt är stängt och skrivskyddat. + text_turning_multiple_off: "Om du inaktiverar möjligheten till flera värden kommer endast ett värde per objekt behållas." default_role_manager: Projektledare default_role_developer: Utvecklare @@ -1115,9 +1127,16 @@ description_date_range_interval: Ange intervall genom att välja start- och slutdatum description_date_from: Ange startdatum description_date_to: Ange slutdatum - text_repository_identifier_info: Ändast gemener (a-z), siffror, streck och understreck är tillåtna.
    När identifieraren sparats kan den inte ändras. - label_any: alla - label_cross_project_descendants: Med underprojekt - label_cross_project_tree: Med projektträd - label_cross_project_hierarchy: Med projekthierarki - label_cross_project_system: Med alla projekt + text_repository_identifier_info: 'Endast gemener (a-z), siffror, streck och understreck är tillåtna.
    När identifieraren sparats kan den inte ändras.' + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/th.yml --- a/config/locales/th.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/th.yml Tue Sep 09 09:34:53 2014 +0100 @@ -127,6 +127,7 @@ not_same_project: "ไม่ได้อยู่ในโครงà¸à¸²à¸£à¹€à¸”ียวà¸à¸±à¸™" circular_dependency: "ความสัมพันธ์อ้างอิงเป็นวงà¸à¸¥à¸¡" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: à¸à¸£à¸¸à¸“าเลือภ@@ -159,7 +160,7 @@ notice_not_authorized: คุณไม่มีสิทธิเข้าถึงหน้านี้. notice_email_sent: "อีเมล์ได้ถูà¸à¸ªà¹ˆà¸‡à¸–ึง %{value}" notice_email_error: "เà¸à¸´à¸”ความผิดพลาดขณะà¸à¸³à¸ªà¹ˆà¸‡à¸­à¸µà¹€à¸¡à¸¥à¹Œ (%{value})" - notice_feeds_access_key_reseted: RSS access key ของคุณถูภreset à¹à¸¥à¹‰à¸§. + notice_feeds_access_key_reseted: Atom access key ของคุณถูภreset à¹à¸¥à¹‰à¸§. notice_failed_to_save_issues: "%{count} ปัà¸à¸«à¸²à¸ˆà¸²à¸ %{total} ปัà¸à¸«à¸²à¸—ี่ถูà¸à¹€à¸¥à¸·à¸­à¸à¹„ม่สามารถจัดเà¸à¹‡à¸š: %{ids}." notice_no_issue_selected: "ไม่มีปัà¸à¸«à¸²à¸—ี่ถูà¸à¹€à¸¥à¸·à¸­à¸! à¸à¸£à¸¸à¸“าเลือà¸à¸›à¸±à¸à¸«à¸²à¸—ี่คุณต้องà¸à¸²à¸£à¹à¸à¹‰à¹„ข." notice_account_pending: "บัà¸à¸Šà¸µà¸‚องคุณสร้างเสร็จà¹à¸¥à¹‰à¸§ ขณะนี้รอà¸à¸²à¸£à¸­à¸™à¸¸à¸¡à¸±à¸•ิจาà¸à¸œà¸¹à¹‰à¸šà¸£à¸´à¸«à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£." @@ -180,8 +181,6 @@ mail_subject_account_activation_request: "à¸à¸£à¸¸à¸“าเปิดบัà¸à¸Šà¸µ %{value}" mail_body_account_activation_request: "ผู้ใช้ใหม่ (%{value}) ได้ลงทะเบียน. บัà¸à¸Šà¸µà¸‚องเขาà¸à¸³à¸¥à¸±à¸‡à¸£à¸­à¸­à¸™à¸¸à¸¡à¸±à¸•ิ:" - gui_validation_error: 1 ข้อผิดพลาด - gui_validation_error_plural: "%{count} ข้อผิดพลาด" field_name: ชื่อ field_description: รายละเอียด @@ -392,8 +391,6 @@ label_text: ข้อความขนาดยาว label_attribute: คุณลัà¸à¸©à¸“ะ label_attribute_plural: คุณลัà¸à¸©à¸“ะ - label_download: "%{count} ดาวน์โหลด" - label_download_plural: "%{count} ดาวน์โหลด" label_no_data: จำนวนข้อมูลที่à¹à¸ªà¸”ง label_change_status: เปลี่ยนสถานะ label_history: ประวัติ @@ -493,8 +490,6 @@ label_repository: ที่เà¸à¹‡à¸šà¸•้นฉบับ label_repository_plural: ที่เà¸à¹‡à¸šà¸•้นฉบับ label_browse: เปิดหา - label_modification: "%{count} เปลี่ยนà¹à¸›à¸¥à¸‡" - label_modification_plural: "%{count} เปลี่ยนà¹à¸›à¸¥à¸‡" label_revision: à¸à¸²à¸£à¹à¸à¹‰à¹„ข label_revision_plural: à¸à¸²à¸£à¹à¸à¹‰à¹„ข label_associated_revisions: à¸à¸²à¸£à¹à¸à¹‰à¹„ขที่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง @@ -580,7 +575,7 @@ label_language_based: ขึ้นอยู่à¸à¸±à¸šà¸ à¸²à¸©à¸²à¸‚องผู้ใช้ label_sort_by: "เรียงโดย %{value}" label_send_test_email: ส่งจดหมายทดสอบ - label_feeds_access_key_created_on: "RSS access key สร้างเมื่อ %{value} ที่ผ่านมา" + label_feeds_access_key_created_on: "Atom access key สร้างเมื่อ %{value} ที่ผ่านมา" label_module_plural: ส่วนประà¸à¸­à¸š label_added_time_by: "เพิ่มโดย %{author} %{age} ที่ผ่านมา" label_updated_time: "ปรับปรุง %{value} ที่ผ่านมา" @@ -755,7 +750,6 @@ permission_comment_news: Comment news permission_delete_messages: Delete messages permission_select_project_modules: Select project modules - permission_manage_documents: Manage documents permission_edit_wiki_pages: Edit wiki pages permission_add_issue_watchers: Add watchers permission_view_gantt: View gantt chart @@ -878,11 +872,11 @@ label_revision_id: Revision %{value} label_api_access_key: API access key label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key + label_feeds_access_key: Atom access key notice_api_access_key_reseted: Your API access key was reset. setting_rest_api_enabled: Enable REST web service label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key + label_missing_feeds_access_key: Missing a Atom access key button_show: Show text_line_separated: Multiple values allowed (one line for each value). setting_mail_handler_body_delimiters: Truncate emails after one of these lines @@ -930,7 +924,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -971,8 +964,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1079,3 +1070,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: จำนวนรวม + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/tr.yml --- a/config/locales/tr.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/tr.yml Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,7 @@ # Turkish translations for Ruby on Rails # by Ozgun Ataman (ozataman@gmail.com) # by Burak Yigit Kaya (ben@byk.im) +# by Mert Salih Kaplan (mail@mertskaplan.com) tr: locale: @@ -56,8 +57,8 @@ one: 'yaklaşık 1 saat' other: 'yaklaşık %{count} saat' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 saat" + other: "%{count} saat" x_days: one: '1 gün' other: '%{count} gün' @@ -151,6 +152,7 @@ not_same_project: "aynı projeye ait deÄŸil" circular_dependency: "Bu iliÅŸki döngüsel bağımlılık meydana getirecektir" cant_link_an_issue_with_a_descendant: "Bir iÅŸ, alt iÅŸlerinden birine baÄŸlanamaz" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" models: actionview_instancetag_blank_option: Lütfen Seçin @@ -183,7 +185,7 @@ notice_not_authorized: Bu sayfaya eriÅŸme yetkiniz yok. notice_email_sent: "E-posta gönderildi %{value}" notice_email_error: "E-posta gönderilirken bir hata oluÅŸtu (%{value})" - notice_feeds_access_key_reseted: RSS eriÅŸim anahtarınız sıfırlandı. + notice_feeds_access_key_reseted: Atom eriÅŸim anahtarınız sıfırlandı. notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." notice_no_issue_selected: "Seçili iÅŸ yok! Lütfen, düzenlemek istediÄŸiniz iÅŸleri iÅŸaretleyin." notice_account_pending: "Hesabınız oluÅŸturuldu ve yönetici onayı bekliyor." @@ -204,8 +206,6 @@ mail_subject_account_activation_request: "%{value} hesabı etkinleÅŸtirme isteÄŸi" mail_body_account_activation_request: "Yeni bir kullanıcı (%{value}) kaydedildi. Hesap onaylanmayı bekliyor:" - gui_validation_error: 1 hata - gui_validation_error_plural: "%{count} hata" field_name: İsim field_description: Yorum @@ -255,7 +255,7 @@ field_effective_date: Tarih field_password: Parola field_new_password: Yeni Parola - field_password_confirmation: Onay + field_password_confirmation: Parola DoÄŸrulama field_version: Sürüm field_type: Tip field_host: Host @@ -279,7 +279,7 @@ field_activity: Etkinlik field_spent_on: Tarih field_identifier: Tanımlayıcı - field_is_filter: filtre olarak kullanılmış + field_is_filter: süzgeç olarak kullanılmış field_issue_to: İliÅŸkili iÅŸ field_delay: Gecikme field_assignable: Bu role atanabilecek iÅŸler @@ -414,8 +414,6 @@ label_text: Uzun Metin label_attribute: Nitelik label_attribute_plural: Nitelikler - label_download: "%{count} indirme" - label_download_plural: "%{count} indirme" label_no_data: Gösterilecek veri yok label_change_status: DeÄŸiÅŸim Durumu label_history: GeçmiÅŸ @@ -475,7 +473,7 @@ label_gantt: İş-Zaman Çizelgesi label_internal: Dahili label_last_changes: "Son %{count} deÄŸiÅŸiklik" - label_change_view_all: Tüm DeÄŸiÅŸiklikleri gör + label_change_view_all: Tüm DeÄŸiÅŸiklikleri göster label_personalize_page: Bu sayfayı kiÅŸiselleÅŸtir label_comment: Yorum label_comment_plural: Yorumlar @@ -489,8 +487,8 @@ label_query: Özel Sorgu label_query_plural: Özel Sorgular label_query_new: Yeni Sorgu - label_filter_add: Filtre ekle - label_filter_plural: Filtreler + label_filter_add: Süzgeç ekle + label_filter_plural: Süzgeçler label_equals: EÅŸit label_not_equals: EÅŸit deÄŸil label_in_less_than: küçüktür @@ -515,8 +513,6 @@ label_repository: Depo label_repository_plural: Depolar label_browse: Gözat - label_modification: "%{count} deÄŸiÅŸim" - label_modification_plural: "%{count} deÄŸiÅŸim" label_revision: DeÄŸiÅŸiklik label_revision_plural: DeÄŸiÅŸiklikler label_associated_revisions: BirleÅŸtirilmiÅŸ deÄŸiÅŸiklikler @@ -602,7 +598,7 @@ label_language_based: Kullanıcı dili bazlı label_sort_by: "%{value} göre sırala" label_send_test_email: Test e-postası gönder - label_feeds_access_key_created_on: "RSS eriÅŸim anahtarı %{value} önce oluÅŸturuldu" + label_feeds_access_key_created_on: "Atom eriÅŸim anahtarı %{value} önce oluÅŸturuldu" label_module_plural: Modüller label_added_time_by: "%{author} tarafından %{age} önce eklendi" label_updated_time: "%{value} önce güncellendi" @@ -747,7 +743,7 @@ label_generate_key: "Anahtar oluÅŸtur" setting_sequential_project_identifiers: "Sıralı proje tanımlayıcıları oluÅŸtur" field_parent_title: Üst sayfa - text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." + text_email_delivery_not_configured: "E-posta gönderme yapılandırılmadı ve bildirimler devre dışı.\nconfig/configuration.yml içinden SMTP sunucusunu yapılandırın ve uygulamayı yeniden baÅŸlatın." text_enumeration_category_reassign_to: 'Hepsini ÅŸuna çevir:' label_issue_watchers: Takipçiler mail_body_reminder: "Size atanmış olan %{count} iÅŸ %{days} gün içerisinde bitirilmeli:" @@ -764,14 +760,14 @@ label_renamed: yeniden adlandırılmış label_copied: kopyalanmış setting_plain_text_mail: sadece düz metin (HTML yok) - permission_view_files: Dosyaları görme + permission_view_files: Dosyaları gösterme permission_edit_issues: İşleri düzenleme permission_edit_own_time_entries: Kendi zaman giriÅŸlerini düzenleme permission_manage_public_queries: Herkese açık sorguları yönetme permission_add_issues: İş ekleme permission_log_time: Harcanan zamanı kaydetme - permission_view_changesets: DeÄŸiÅŸimleri görme(SVN, vs.) - permission_view_time_entries: Harcanan zamanı görme + permission_view_changesets: DeÄŸiÅŸimleri gösterme(SVN, vs.) + permission_view_time_entries: Harcanan zamanı gösterme permission_manage_versions: Sürümleri yönetme permission_manage_wiki: Wiki'yi yönetme permission_manage_categories: İş kategorilerini yönetme @@ -779,40 +775,39 @@ permission_comment_news: Haberlere yorum yapma permission_delete_messages: Mesaj silme permission_select_project_modules: Proje modüllerini seçme - permission_manage_documents: Belgeleri yönetme permission_edit_wiki_pages: Wiki sayfalarını düzenleme permission_add_issue_watchers: Takipçi ekleme - permission_view_gantt: İş-Zaman çizelgesi görme + permission_view_gantt: İş-Zaman çizelgesi gösterme permission_move_issues: İşlerin yerini deÄŸiÅŸtirme permission_manage_issue_relations: İşlerin biribiriyle baÄŸlantılarını yönetme permission_delete_wiki_pages: Wiki sayfalarını silme permission_manage_boards: Panoları yönetme permission_delete_wiki_pages_attachments: Ekleri silme - permission_view_wiki_edits: Wiki geçmiÅŸini görme + permission_view_wiki_edits: Wiki geçmiÅŸini gösterme permission_add_messages: Mesaj gönderme - permission_view_messages: Mesajları görme + permission_view_messages: Mesajları gösterme permission_manage_files: Dosyaları yönetme permission_edit_issue_notes: Notları düzenleme permission_manage_news: Haberleri yönetme - permission_view_calendar: Takvimleri görme + permission_view_calendar: Takvimleri gösterme permission_manage_members: Üyeleri yönetme permission_edit_messages: Mesajları düzenleme permission_delete_issues: İşleri silme - permission_view_issue_watchers: Takipçi listesini görme + permission_view_issue_watchers: Takipçi listesini gösterme permission_manage_repository: Depo yönetimi permission_commit_access: Gönderme eriÅŸimi permission_browse_repository: Depoya gözatma - permission_view_documents: Belgeleri görme + permission_view_documents: Belgeleri gösterme permission_edit_project: Projeyi düzenleme permission_add_issue_notes: Not ekleme permission_save_queries: Sorgu kaydetme - permission_view_wiki_pages: Wiki görme + permission_view_wiki_pages: Wiki gösterme permission_rename_wiki_pages: Wiki sayfasının adını deÄŸiÅŸtirme permission_edit_time_entries: Zaman kayıtlarını düzenleme permission_edit_own_issue_notes: Kendi notlarını düzenleme setting_gravatar_enabled: Kullanıcı resimleri için Gravatar kullan label_example: Örnek - text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." + text_repository_usernames_mapping: "Redmine kullanıcı adlarını depo deÄŸiÅŸiklik kayıtlarındaki kullanıcı adlarıyla eÅŸleÅŸtirin veya eÅŸleÅŸtirmeleri güncelleyin.\nRedmine kullanıcı adları ile depo kullanıcı adları aynı olan kullanıcılar otomatik olarak eÅŸlendirilecektir." permission_edit_own_messages: Kendi mesajlarını düzenleme permission_delete_own_messages: Kendi mesajlarını silme label_user_activity: "%{value} kullanıcısının etkinlikleri" @@ -852,7 +847,7 @@ mail_body_wiki_content_updated: "'%{id}' wiki sayfası, %{author} tarafından güncellendi." permission_add_project: Proje oluÅŸtur setting_new_project_user_role_id: Yönetici olmayan ancak proje yaratabilen kullanıcıya verilen rol - label_view_all_revisions: Tüm deÄŸiÅŸiklikleri gör + label_view_all_revisions: Tüm deÄŸiÅŸiklikleri göster label_tag: Etiket label_branch: Kol error_no_tracker_in_project: Bu projeye baÄŸlanmış bir iÅŸ tipi yok. Lütfen proje ayarlarını kontrol edin. @@ -897,16 +892,16 @@ error_workflow_copy_source: Lütfen kaynak iÅŸ tipi ve rolleri seçin label_update_issue_done_ratios: İş tamamlanma oranlarını güncelle setting_start_of_week: Takvimleri ÅŸundan baÅŸlat - permission_view_issues: İşleri Gör + permission_view_issues: İşleri Göster label_display_used_statuses_only: Sadece bu iÅŸ tipi tarafından kullanılan durumları göster label_revision_id: DeÄŸiÅŸiklik %{value} label_api_access_key: API eriÅŸim anahtarı label_api_access_key_created_on: API eriÅŸim anahtarı %{value} önce oluÅŸturuldu - label_feeds_access_key: RSS eriÅŸim anahtarı + label_feeds_access_key: Atom eriÅŸim anahtarı notice_api_access_key_reseted: API eriÅŸim anahtarınız sıfırlandı. setting_rest_api_enabled: REST web servisini etkinleÅŸtir label_missing_api_access_key: Bir API eriÅŸim anahtarı eksik - label_missing_feeds_access_key: Bir RSS eriÅŸim anahtarı eksik + label_missing_feeds_access_key: Bir Atom eriÅŸim anahtarı eksik button_show: Göster text_line_separated: Çoklu deÄŸer girilebilir (her satıra bir deÄŸer). setting_mail_handler_body_delimiters: Åžu satırların birinden sonra e-postayı sonlandır @@ -939,7 +934,7 @@ field_time_entries: Zaman Kayıtları project_module_gantt: İş-Zaman Çizelgesi project_module_calendar: Takvim - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + button_edit_associated_wikipage: "İliÅŸkilendirilmiÅŸ Wiki sayfasını düzenle: %{page_title}" field_text: Metin alanı label_user_mail_option_only_owner: Sadece sahibi olduÄŸum ÅŸeyler için setting_default_notification_option: Varsayılan bildirim seçeneÄŸi @@ -954,9 +949,9 @@ field_visible: Görünür setting_emails_header: "E-Posta baÅŸlığı" setting_commit_logtime_activity_id: Kaydedilen zaman için etkinlik - text_time_logged_by_changeset: Applied in changeset %{value}. + text_time_logged_by_changeset: DeÄŸiÅŸiklik uygulandı %{value}. setting_commit_logtime_enabled: Zaman kaydını etkinleÅŸtir - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + notice_gantt_chart_truncated: Görüntülenebilir öğelerin sayısını aÅŸtığı için tablo kısaltıldı (%{max}) setting_gantt_items_limit: İş-Zaman çizelgesinde gösterilecek en fazla öğe sayısı field_warn_on_leaving_unsaved: KaydedilmemiÅŸ metin bulunan bir sayfadan çıkarken beni uyar text_warn_on_leaving_unsaved: Bu sayfada terkettiÄŸiniz takdirde kaybolacak kaydedilmemiÅŸ metinler var. @@ -965,8 +960,8 @@ label_news_comment_added: Bir habere yorum eklendi button_expand_all: Tümünü geniÅŸlet button_collapse_all: Tümünü daralt - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_additional_workflow_transitions_for_assignee: Kullanıcı atanan olduÄŸu zaman tanınacak ek yetkiler + label_additional_workflow_transitions_for_author: Kullanıcı yazar olduÄŸu zaman tanınacak ek yetkiler label_bulk_edit_selected_time_entries: Seçilen zaman kayıtlarını toplu olarak düzenle text_time_entries_destroy_confirmation: Seçilen zaman kaydını/kayıtlarını silmek istediÄŸinize emin misiniz? label_role_anonymous: Anonim @@ -974,130 +969,154 @@ label_issue_note_added: Not eklendi label_issue_status_updated: Durum güncellendi label_issue_priority_updated: Öncelik güncellendi - label_issues_visibility_own: Issues created by or assigned to the user + label_issues_visibility_own: Kullanıcı tarafından oluÅŸturulmuÅŸ ya da kullanıcıya atanmış sorunlar field_issues_visibility: İşlerin görünürlüğü label_issues_visibility_all: Tüm iÅŸler - permission_set_own_issues_private: Set own issues public or private + permission_set_own_issues_private: Kendi iÅŸlerini özel ya da genel olarak iÅŸaretle field_is_private: Özel permission_set_issues_private: İşleri özel ya da genel olarak iÅŸaretleme label_issues_visibility_public: Özel olmayan tüm iÅŸler - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Commit messages encoding + text_issues_destroy_descendants_confirmation: "%{count} alt görev de silinecek." + field_commit_logs_encoding: DeÄŸiÅŸiklik mesajı kodlaması(encoding) field_scm_path_encoding: Yol kodlaması(encoding) text_scm_path_encoding_note: "Varsayılan: UTF-8" - field_path_to_repository: Path to repository + field_path_to_repository: Depo yolu field_root_directory: Ana dizin field_cvs_module: Modül field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_mercurial_repository_note: Yerel depo (ör. /hgrepo, c:\hgrepo) text_scm_command: Komut text_scm_command_version: Sürüm - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_git_report_last_commit: Son gönderilen dosya ve dizinleri raporla + notice_issue_successful_create: İş %{id} oluÅŸturuldu. + label_between: arasında + setting_issue_group_assignment: Gruplara iÅŸ atanmasına izin ver + label_diff: farklar + text_git_repository_note: Depo yalın halde (bare) ve yerel sistemde bulunuyor. (örn. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sıralama yönü + description_project_scope: Arama kapsamı + description_filter: Süzgeç + description_user_mail_notification: E-posta bildirim ayarları + description_date_from: BaÅŸlangıç tarihini gir + description_message_content: Mesaj içeriÄŸi + description_available_columns: Kullanılabilir Sütunlar + description_date_range_interval: BaÅŸlangıç ve bitiÅŸ tarihini seçerek tarih aralığını belirleyin + description_issue_category_reassign: İş kategorisini seçin + description_search: Arama alanı + description_notes: Notlar + description_date_range_list: Listeden tarih aralığını seçin + description_choose_project: Projeler + description_date_to: BitiÅŸ tarihini gir + description_query_sort_criteria_attribute: Sıralama ölçütü + description_wiki_subpages_reassign: Yeni üst sayfa seç + description_selected_columns: SeçilmiÅŸ Sütunlar + label_parent_revision: Üst + label_child_revision: Alt + error_scm_annotate_big_text_file: Girdi maksimum metin dosyası boyutundan büyük olduÄŸu için ek açıklama girilemiyor. + setting_default_issue_start_date_to_creation_date: Geçerli tarihi yeni iÅŸler için baÅŸlangıç tarihi olarak kullan + button_edit_section: Bölümü düzenle + setting_repositories_encodings: Eklerin ve depoların kodlamaları + description_all_columns: Tüm sütunlar + button_export: Dışarı aktar + label_export_options: "%{export_format} dışa aktarım seçenekleri" + error_attachment_too_big: İzin verilen maksimum dosya boyutunu (%{max_size}) aÅŸtığı için dosya yüklenemedi. + notice_failed_to_save_time_entries: "Seçilen %{total} adet zaman girdisinden %{count} tanesi kaydedilemedi: %{ids}." label_x_issues: zero: 0 İş one: 1 İş other: "%{count} İşler" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments + label_repository_new: Yeni depo + field_repository_is_default: Ana depo + label_copy_attachments: Ekleri kopyala label_item_position: "%{position}/%{count}" - label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account + label_completed_versions: Tamamlanmış sürümler + text_project_identifier_info: Yalnızca küçük harfler (a-z), sayılar, tire ve alt tire kullanılabilir.
    Kaydedilen tanımlayıcı daha sonra değiştirilemez. + field_multiple: Çoklu değer + setting_commit_cross_project_ref: Diğer bütün projelerdeki iş kayıtlarının kaynak gösterilmesine ve kayıtların kapatılabilmesine izin ver + text_issue_conflict_resolution_add_notes: Notlarımı ekle ve diğer değişikliklerimi iptal et + text_issue_conflict_resolution_overwrite: Değişikliklerimi yine de uygula (önceki notlar saklanacak ancak bazı değişikliklerin üzerine yazılabilir) + notice_issue_update_conflict: Düzenleme yaparken başka bir kullanıcı tarafından sorun güncellendi. + text_issue_conflict_resolution_cancel: Tüm değişiklikleri iptal et ve yeniden görüntüle %{link} + permission_manage_related_issues: Benzer sorunları yönet + field_auth_source_ldap_filter: LDAP süzgeçi + label_search_for_watchers: Takipçi eklemek için ara + notice_account_deleted: Hesabınız kalıcı olarak silinmiştir. + setting_unsubscribe: Kullanıcıların kendi hesaplarını silebilmesine izin ver + button_delete_my_account: Hesabımı sil text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project + Devam etmek istediğinize emin misiniz? + Hesabınız tekrar açılmamak üzere kalıcı olarak silinecektir. + error_session_expired: Oturum zaman aşımına uğradı. Lütfen tekrar giriş yapın. + text_session_expiration_settings: "Uyarı: Bu ayarları değiştirmek (sizinki de dahil) tüm oturumları sonlandırabilir." + setting_session_lifetime: Maksimum oturum süresi + setting_session_timeout: Maksimum hareketsizlik zaman aşımı + label_session_expiration: Oturum süre sonu + permission_close_project: Projeyi kapat/yeniden aç + label_show_closed_projects: Kapatılmış projeleri göster + button_close: Kapat + button_reopen: Yeniden aç + project_status_active: etkin + project_status_closed: kapalı + project_status_archived: arşivlenmiş + text_project_closed: Proje kapatıldı ve artık değiştirilemez. + notice_user_successful_create: Kullanıcı %{id} yaratıldı. + field_core_fields: Standart alanlar + field_timeout: Zaman aşımı (saniye olarak) + setting_thumbnails_enabled: Küçük resmi görüntüle + setting_thumbnails_size: Küçük resim boyutu (pixel olarak) + label_status_transitions: Durum değiştirme + label_fields_permissions: Alan izinleri + label_readonly: Salt okunur + label_required: Zorunlu + text_repository_identifier_info: Yalnızca küçük harfler (a-z), sayılar, tire ve alt tire kullanılabilir.
    Kaydedilen tanımlayıcı daha sonra deÄŸiÅŸtirilemez. + field_board_parent: Üst forum + label_attribute_of_project: Proje %{name} + label_attribute_of_author: Yazar %{name} + label_attribute_of_assigned_to: Atanan %{name} + label_attribute_of_fixed_version: Hedef sürüm %{name} + label_copy_subtasks: Alt görevi kopyala + label_copied_to: Kopyalama hedefi + label_copied_from: Kopyalanacak kaynak + label_any_issues_in_project: projedeki herhangi bir sorun + label_any_issues_not_in_project: projede olmayan herhangi bir sorun + field_private_notes: Özel notlar + permission_view_private_notes: Özel notları görüntüle + permission_set_notes_private: Notları özel olarak iÅŸaretle + label_no_issues_in_project: projede hiçbir sorun yok label_any: Hepsi - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks + label_last_n_weeks: son %{count} hafta + setting_cross_project_subtasks: Projeler arası alt iÅŸlere izin ver label_cross_project_descendants: Alt projeler ile label_cross_project_tree: Proje aÄŸacı ile label_cross_project_hierarchy: Proje hiyerarÅŸisi ile label_cross_project_system: Tüm projeler ile - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past + button_hide: Gizle + setting_non_working_week_days: Tatil günleri + label_in_the_next_days: gelecekte + label_in_the_past_days: geçmiÅŸte + label_attribute_of_user: Kullanıcı %{name} + text_turning_multiple_off: Çoklu deÄŸer seçimini devre dışı bırakırsanız, çoklu deÄŸer içeren alanlar, her alan için yalnızca tek deÄŸer girilecek ÅŸekilde düzenlenecektir. + label_attribute_of_issue: Sorun %{name} + permission_add_documents: Belgeleri ekle + permission_edit_documents: Belgeleri düzenle + permission_delete_documents: Belgeleri sil + label_gantt_progress_line: İlerleme çizgisi + setting_jsonp_enabled: JSONP desteÄŸini etkinleÅŸtir + field_inherit_members: Devralan kullanıcılar + field_closed_on: Kapanış tarihi + field_generate_password: Parola oluÅŸtur + setting_default_projects_tracker_ids: Yeni projeler için varsayılan iÅŸ tipi + label_total_time: Toplam + text_scm_config: config/configuration.yml içinden SCM komutlarını yapılandırabilirsiniz. Lütfen yapılandırmadan sonra uygulamayı tekrar baÅŸlatın. + text_scm_command_not_available: SCM komutu kullanılamıyor. Lütfen yönetim panelinden ayarları kontrol edin. + notice_account_not_activated_yet: Hesabınız henüz etkinleÅŸtirilmedi. Yeni bir hesap etkinleÅŸtirme e-postası istiyorsanız, lütfen buraya tıklayınız.. + notice_account_locked: Hesabınız kilitlendi. + label_hidden: Gizle + label_visibility_private: yalnız benim için + label_visibility_roles: yalnız bu roller için + label_visibility_public: herhangi bir kullanıcı için + field_must_change_passwd: Bir sonraki giriÅŸinizde ÅŸifrenizi deÄŸiÅŸtirmeniz gerekir. + notice_new_password_must_be_different: Yeni parola geçerli paroladan + farklı olmalı + setting_mail_handler_excluded_filenames: Dosya adı belirtilen ekleri hariç tut + text_convert_available: ImageMagick dönüştürmesi kullanılabilir (isteÄŸe baÄŸlı) diff -r d98d22a98252 -r afce8026aaeb config/locales/uk.yml --- a/config/locales/uk.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/uk.yml Tue Sep 09 09:34:53 2014 +0100 @@ -127,6 +127,7 @@ not_same_project: "не відноÑÑтьÑÑ Ð´Ð¾ одного проекту" circular_dependency: "Такий зв'Ñзок приведе до циклічної залежноÑті" cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: Оберіть @@ -160,7 +161,7 @@ notice_not_authorized: У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” прав Ð´Ð»Ñ Ð²Ñ–Ð´Ð²Ñ–Ð´Ð¸Ð½Ð¸ даної Ñторінки. notice_email_sent: "Відправлено лиÑта %{value}" notice_email_error: "Під Ñ‡Ð°Ñ Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²ÐºÐ¸ лиÑта відбулаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° (%{value})" - notice_feeds_access_key_reseted: Ваш ключ доÑтупу RSS було Ñкинуто. + notice_feeds_access_key_reseted: Ваш ключ доÑтупу Atom було Ñкинуто. notice_failed_to_save_issues: "Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ %{count} пункт(ів) з %{total} вибраних: %{ids}." notice_no_issue_selected: "Ðе вибрано жодної задачі! Будь лаÑка, відзначте задачу, Ñку ви хочете відредагувати." notice_account_pending: "Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ñтворено Ñ– він чекає на Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратором." @@ -174,8 +175,6 @@ mail_subject_account_activation_request: "Запит на активацію облікового запиÑу %{value}" mail_body_account_activation_request: "Ðовий кориÑтувач (%{value}) зареєÑтрувавÑÑ. Його обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ‡ÐµÐºÐ°Ñ” на ваше підтвердженнÑ:" - gui_validation_error: 1 помилка - gui_validation_error_plural: "%{count} помилки(ок)" field_name: Ім'Ñ field_description: ÐžÐ¿Ð¸Ñ @@ -364,8 +363,6 @@ label_text: Довгий текÑÑ‚ label_attribute: Ðтрибут label_attribute_plural: атрибути - label_download: "%{count} Завантажено" - label_download_plural: "%{count} Завантажень" label_no_data: Ðемає даних Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ label_change_status: Змінити ÑÑ‚Ð°Ñ‚ÑƒÑ label_history: ІÑÑ‚Ð¾Ñ€Ñ–Ñ @@ -454,8 +451,6 @@ label_day_plural: днів(Ñ) label_repository: Репозиторій label_browse: ПроглÑнути - label_modification: "%{count} зміна" - label_modification_plural: "%{count} змін" label_revision: ВерÑÑ–Ñ label_revision_plural: ВерÑій label_added: додано @@ -539,7 +534,7 @@ label_language_based: Ðа оÑнові мови кориÑтувача label_sort_by: "Сортувати за %{value}" label_send_test_email: ПоÑлати email Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ - label_feeds_access_key_created_on: "Ключ доÑтупу RSS Ñтворений %{value} назад " + label_feeds_access_key_created_on: "Ключ доÑтупу Atom Ñтворений %{value} назад " label_module_plural: Модулі label_added_time_by: "Доданий %{author} %{age} назад" label_updated_time: "Оновлений %{value} назад" @@ -755,7 +750,6 @@ permission_comment_news: Comment news permission_delete_messages: Delete messages permission_select_project_modules: Select project modules - permission_manage_documents: Manage documents permission_edit_wiki_pages: Edit wiki pages permission_add_issue_watchers: Add watchers permission_view_gantt: View gantt chart @@ -878,11 +872,11 @@ label_revision_id: Revision %{value} label_api_access_key: API access key label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key + label_feeds_access_key: Atom access key notice_api_access_key_reseted: Your API access key was reset. setting_rest_api_enabled: Enable REST web service label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key + label_missing_feeds_access_key: Missing a Atom access key button_show: Show text_line_separated: Multiple values allowed (one line for each value). setting_mail_handler_body_delimiters: Truncate emails after one of these lines @@ -930,7 +924,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -971,8 +964,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1077,3 +1068,32 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ð’Ñього + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/vi.yml --- a/config/locales/vi.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/vi.yml Tue Sep 09 09:34:53 2014 +0100 @@ -144,6 +144,7 @@ not_same_project: "không thuá»™c cùng dá»± án" circular_dependency: "quan hệ có thể gây ra lặp vô tận" cant_link_an_issue_with_a_descendant: "Má»™t vấn đỠkhông thể liên kết tá»›i má»™t trong số những tác vụ con cá»§a nó" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" direction: ltr date: @@ -214,7 +215,7 @@ notice_not_authorized: Bạn không có quyá»n xem trang này. notice_email_sent: "Email đã được gá»­i tá»›i %{value}" notice_email_error: "Lá»—i xảy ra khi gá»­i email (%{value})" - notice_feeds_access_key_reseted: Mã số chứng thá»±c RSS đã được tạo lại. + notice_feeds_access_key_reseted: Mã số chứng thá»±c Atom đã được tạo lại. notice_failed_to_save_issues: "Thất bại khi lưu %{count} vấn đỠtrong %{total} lá»±a chá»n: %{ids}." notice_no_issue_selected: "Không có vấn đỠđược chá»n! Vui lòng kiểm tra các vấn đỠbạn cần chỉnh sá»­a." notice_account_pending: "Thông tin tài khoản đã được tạo ra và Ä‘ang chá» chứng thá»±c từ ban quản trị." @@ -236,16 +237,13 @@ mail_subject_account_activation_request: "%{value}: Yêu cầu chứng thá»±c tài khoản" mail_body_account_activation_request: "Ngưá»i dùng (%{value}) má»›i đăng ký và cần bạn xác nhận:" mail_subject_reminder: "%{count} vấn đỠhết hạn trong các %{days} ngày tá»›i" - mail_body_reminder: "%{count} vấn đỠgán cho bạn sẽ hết hạn trong %{days} ngày tá»›i:" + mail_body_reminder: "%{count} công việc bạn được phân công sẽ hết hạn trong %{days} ngày tá»›i:" - gui_validation_error: 1 lá»—i - gui_validation_error_plural: "%{count} lá»—i" - - field_name: Tên + field_name: Tên dá»± án field_description: Mô tả field_summary: Tóm tắt field_is_required: Bắt buá»™c - field_firstname: Tên lót + Tên + field_firstname: Tên đệm và Tên field_lastname: Há» field_mail: Email field_filename: Tập tin @@ -269,11 +267,11 @@ field_notes: Ghi chú field_is_closed: Vấn đỠđóng field_is_default: Giá trị mặc định - field_tracker: Dòng vấn đỠ+ field_tracker: Kiểu vấn đỠfield_subject: Chá»§ đỠfield_due_date: Hết hạn - field_assigned_to: Gán cho - field_priority: Ưu tiên + field_assigned_to: Phân công cho + field_priority: Mức ưu tiên field_fixed_version: Phiên bản field_user: Ngưá»i dùng field_role: Quyá»n @@ -287,9 +285,9 @@ field_last_login_on: Kết nối cuối field_language: Ngôn ngữ field_effective_date: Ngày - field_password: Mật mã - field_new_password: Mật mã má»›i - field_password_confirmation: Khẳng định lại + field_password: Mật khẩu + field_new_password: Mật khẩu má»›i + field_password_confirmation: Nhập lại mật khẩu field_version: Phiên bản field_type: Kiểu field_host: Host @@ -304,7 +302,7 @@ field_start_date: Bắt đầu field_done_ratio: Tiến độ field_auth_source: Chế độ xác thá»±c - field_hide_mail: Không làm lá»™ email cá»§a bạn + field_hide_mail: Không hiện email cá»§a tôi field_comments: Bình luận field_url: URL field_start_page: Trang bắt đầu @@ -313,12 +311,12 @@ field_activity: Hoạt động field_spent_on: Ngày field_identifier: Mã nhận dạng - field_is_filter: Dùng như má»™t lá»c - field_issue_to: Vấn Ä‘á»n liên quan + field_is_filter: Dùng như bá»™ lá»c + field_issue_to: Vấn đỠliên quan field_delay: Äá»™ trá»… field_assignable: Vấn đỠcó thể gán cho vai trò này field_redirect_existing_links: Chuyển hướng trang đã có - field_estimated_hours: Thá»i gian ước Ä‘oán + field_estimated_hours: Thá»i gian ước lượng field_column_names: Cá»™t field_time_zone: Múi giá» field_searchable: Tìm kiếm được @@ -340,7 +338,7 @@ setting_text_formatting: Äịnh dạng bài viết setting_wiki_compression: Nén lịch sá»­ Wiki setting_feeds_limit: Giá»›i hạn ná»™i dung cá»§a feed - setting_default_projects_public: Dá»± án mặc định là công cá»™ng + setting_default_projects_public: Dá»± án mặc định là public setting_autofetch_changesets: Tá»± động tìm nạp commits setting_sys_api_enabled: Cho phép WS quản lý kho chứa setting_commit_ref_keywords: Từ khóa tham khảo @@ -400,9 +398,9 @@ label_member: Thành viên label_member_new: Thành viên má»›i label_member_plural: Thành viên - label_tracker: Dòng vấn đỠ- label_tracker_plural: Dòng vấn đỠ- label_tracker_new: Tạo dòng vấn đỠmá»›i + label_tracker: Kiểu vấn đỠ+ label_tracker_plural: Kiểu vấn đỠ+ label_tracker_new: Tạo kiểu vấn đỠmá»›i label_workflow: Quy trình làm việc label_issue_status: Trạng thái vấn đỠlabel_issue_status_plural: Trạng thái vấn đỠ@@ -429,8 +427,8 @@ label_login: Äăng nhập label_logout: Thoát label_help: Giúp đỡ - label_reported_issues: Vấn đỠđã báo cáo - label_assigned_to_me_issues: Vấn đỠgán cho bạn + label_reported_issues: Công việc bạn phân công + label_assigned_to_me_issues: Công việc được phân công label_last_login: Kết nối cuối label_registered_on: Ngày tham gia label_activity: Hoạt động @@ -454,8 +452,6 @@ label_text: Văn bản dài label_attribute: Thuá»™c tính label_attribute_plural: Các thuá»™c tính - label_download: "%{count} lần tải" - label_download_plural: "%{count} lần tải" label_no_data: Chưa có thông tin gì label_change_status: Äổi trạng thái label_history: Lược sá»­ @@ -501,7 +497,7 @@ label_permissions: Quyá»n label_current_status: Trạng thái hiện tại label_new_statuses_allowed: Trạng thái má»›i được phép - label_all: tất cả + label_all: Tất cả label_none: không label_nobody: Chẳng ai label_next: Sau @@ -555,8 +551,6 @@ label_repository: Kho lưu trữ label_repository_plural: Kho lưu trữ label_browse: Duyệt - label_modification: "%{count} thay đổi" - label_modification_plural: "%{count} thay đổi" label_revision: Bản Ä‘iá»u chỉnh label_revision_plural: Bản Ä‘iá»u chỉnh label_associated_revisions: Các bản Ä‘iá»u chỉnh được ghép @@ -624,8 +618,8 @@ label_start_to_start: đầu tá»› đầu label_start_to_end: đầu tá»›i cuối label_stay_logged_in: Lưu thông tin đăng nhập - label_disabled: bị vô hiệu - label_show_completed_versions: Xem phiên bản đã xong + label_disabled: Bị vô hiệu + label_show_completed_versions: Xem phiên bản đã hoàn thành label_me: tôi label_board: Diá»…n đàn label_board_new: Tạo diá»…n đàn má»›i @@ -645,9 +639,9 @@ label_language_based: Theo ngôn ngữ ngưá»i dùng label_sort_by: "Sắp xếp theo %{value}" label_send_test_email: Gá»­i má»™t email kiểm tra - label_feeds_access_key_created_on: "Mã chứng thá»±c RSS được tạo ra cách đây %{value}" - label_module_plural: Mô-Ä‘un - label_added_time_by: "thêm bởi %{author} cách đây %{age}" + label_feeds_access_key_created_on: "Mã chứng thá»±c Atom được tạo ra cách đây %{value}" + label_module_plural: Module + label_added_time_by: "Thêm bởi %{author} cách đây %{age}" label_updated_time: "Cập nhật cách đây %{value}" label_jump_to_a_project: Nhảy đến dá»± án... label_file_plural: Tập tin @@ -658,9 +652,9 @@ label_theme: Giao diện label_default: Mặc định label_search_titles_only: Chỉ tìm trong tá»±a đỠ- label_user_mail_option_all: "Má»i sá»± kiện trên má»i dá»± án cá»§a bạn" + label_user_mail_option_all: "Má»i sá»± kiện trên má»i dá»± án cá»§a tôi" label_user_mail_option_selected: "Má»i sá»± kiện trên các dá»± án được chá»n..." - label_user_mail_no_self_notified: "Äừng gá»­i email vá» các thay đổi do chính bạn thá»±c hiện" + label_user_mail_no_self_notified: "Äừng gá»­i email vá» các thay đổi do chính tôi thá»±c hiện" label_registration_activation_by_email: kích hoạt tài khoản qua email label_registration_manual_activation: kích hoạt tài khoản thá»§ công label_registration_automatic_activation: kích hoạt tài khoản tá»± động @@ -670,9 +664,9 @@ label_general: Tổng quan label_more: Chi tiết label_scm: SCM - label_plugins: Mô-Ä‘un + label_plugins: Module label_ldap_authentication: Chứng thá»±c LDAP - label_downloads_abbr: Tải vá» + label_downloads_abbr: Số lượng Download label_optional_description: Mô tả bổ sung label_add_another_file: Thêm tập tin khác label_preferences: Cấu hình @@ -716,17 +710,17 @@ button_reset: Tạo lại button_rename: Äổi tên button_change_password: Äổi mật mã - button_copy: Chép + button_copy: Sao chép button_annotate: Chú giải button_update: Cập nhật button_configure: Cấu hình button_quote: Trích dẫn - status_active: hoạt động - status_registered: đăng ký - status_locked: khóa + status_active: Äang hoạt động + status_registered: Má»›i đăng ký + status_locked: Äã khóa - text_select_mail_notifications: Chá»n hành động đối vá»›i má»—i email thông báo sẽ gá»­i. + text_select_mail_notifications: Chá»n hành động đối vá»›i má»—i email sẽ gá»­i. text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 để chỉ không hạn chế text_project_destroy_confirmation: Bạn có chắc chắn muốn xóa dá»± án này và các dữ liệu liên quan ? @@ -754,7 +748,7 @@ text_load_default_configuration: Nạp lại cấu hình mặc định text_status_changed_by_changeset: "Ãp dụng trong changeset : %{value}." text_issues_destroy_confirmation: 'Bạn có chắc chắn muốn xóa các vấn đỠđã chá»n ?' - text_select_project_modules: 'Chá»n các mô-Ä‘un cho dá»± án:' + text_select_project_modules: 'Chá»n các module cho dá»± án:' text_default_administrator_account_changed: Thay đổi tài khoản quản trị mặc định text_file_repository_writable: Cho phép ghi thư mục đính kèm text_rmagick_available: Trạng thái RMagick @@ -767,17 +761,17 @@ text_enumeration_category_reassign_to: 'Gán lại giá trị này:' text_email_delivery_not_configured: "Cấu hình gá»­i Email chưa được đặt, và chức năng thông báo bị loại bá».\nCấu hình máy chá»§ SMTP cá»§a bạn ở file config/configuration.yml và khởi động lại để kích hoạt chúng." - default_role_manager: Äiá»u hành - default_role_developer: Phát triển + default_role_manager: 'Äiá»u hành ' + default_role_developer: 'Phát triển ' default_role_reporter: Báo cáo default_tracker_bug: Lá»—i default_tracker_feature: Tính năng default_tracker_support: Há»— trợ default_issue_status_new: Má»›i default_issue_status_in_progress: Äang tiến hành - default_issue_status_resolved: Quyết tâm + default_issue_status_resolved: Äã được giải quyết default_issue_status_feedback: Phản hồi - default_issue_status_closed: Äóng + default_issue_status_closed: Äã đóng default_issue_status_rejected: Từ chối default_doc_category_user: Tài liệu ngưá»i dùng default_doc_category_tech: Tài liệu kỹ thuật @@ -790,13 +784,13 @@ default_activity_development: Phát triển enumeration_issue_priorities: Mức độ ưu tiên vấn đỠ- enumeration_doc_categories: Chá»§ đỠtài liệu - enumeration_activities: Hoạt động (theo dõi thá»i gian) + enumeration_doc_categories: Danh mục tài liệu + enumeration_activities: Hoạt động - setting_plain_text_mail: mail dạng text đơn giản (không dùng HTML) + setting_plain_text_mail: Mail dạng text đơn giản (không dùng HTML) setting_gravatar_enabled: Dùng biểu tượng Gravatar permission_edit_project: Chỉnh dá»± án - permission_select_project_modules: Chá»n mô-Ä‘un + permission_select_project_modules: Chá»n Module permission_manage_members: Quản lý thành viên permission_manage_versions: Quản lý phiên bản permission_manage_categories: Quản lý chá»§ đỠ@@ -808,19 +802,18 @@ permission_edit_own_issue_notes: Sá»­a chú thích cá nhân permission_move_issues: Chuyển vấn đỠpermission_delete_issues: Xóa vấn đỠ- permission_manage_public_queries: Quản lý truy cấn công cá»™ng + permission_manage_public_queries: Quản lý truy vấn công cá»™ng permission_save_queries: Lưu truy vấn permission_view_gantt: Xem biểu đồ sá»± kiện permission_view_calendar: Xem lịch - permission_view_issue_watchers: Xem các ngưá»i theo dõi + permission_view_issue_watchers: Xem những ngưá»i theo dõi permission_add_issue_watchers: Thêm ngưá»i theo dõi - permission_log_time: Lưu thá»i gian đã tốn - permission_view_time_entries: Xem thá»i gian đã tốn + permission_log_time: Lưu thá»i gian đã qua + permission_view_time_entries: Xem thá»i gian đã qua permission_edit_time_entries: Xem nhật ký thá»i gian permission_edit_own_time_entries: Sá»­a thá»i gian đã lưu permission_manage_news: Quản lý tin má»›i permission_comment_news: Chú thích vào tin má»›i - permission_manage_documents: Quản lý tài liệu permission_view_documents: Xem tài liệu permission_manage_files: Quản lý tập tin permission_view_files: Xem tập tin @@ -844,7 +837,7 @@ permission_delete_messages: Xóa bài viết permission_delete_own_messages: Xóa bài viết cá nhân label_example: Ví dụ - text_repository_usernames_mapping: "Chá»n hoặc cập nhật ánh xạ ngưá»i dùng hệ thống vá»›i ngưá»i dùng trong kho lưu trữ.\nNhững trưá»ng hợp trùng hợp vá» tên và email sẽ được tá»± động ánh xạ." + text_repository_usernames_mapping: "Lá»±a chá»n hoặc cập nhật ánh xạ ngưá»i dùng hệ thống vá»›i ngưá»i dùng trong kho lưu trữ.\nKhi ngưá»i dùng trùng hợp vá» tên và email sẽ được tá»± động ánh xạ." permission_delete_own_messages: Xóa thông Ä‘iệp label_user_activity: "%{value} hoạt động" label_updated_time_by: "Cập nhật bởi %{author} cách đây %{age}" @@ -933,11 +926,11 @@ label_revision_id: "Bản Ä‘iá»u chỉnh %{value}" label_api_access_key: Khoá truy cập API label_api_access_key_created_on: "Khoá truy cập API đựơc tạo cách đây %{value}. Khóa này được dùng cho eDesignLab Client." - label_feeds_access_key: Khoá truy cập RSS + label_feeds_access_key: Khoá truy cập Atom notice_api_access_key_reseted: Khoá truy cập API cá»§a bạn đã được đặt lại. setting_rest_api_enabled: Cho phép dịch vụ web REST label_missing_api_access_key: Mất Khoá truy cập API - label_missing_feeds_access_key: Mất Khoá truy cập RSS + label_missing_feeds_access_key: Mất Khoá truy cập Atom button_show: Hiện text_line_separated: Nhiá»u giá trị được phép(má»—i dòng má»™t giá trị). setting_mail_handler_body_delimiters: "Cắt bá»›t email sau những dòng :" @@ -1137,3 +1130,28 @@ setting_non_working_week_days: Các ngày không làm việc label_in_the_next_days: Trong tương lai label_in_the_past_days: Trong quá khứ + label_attribute_of_user: "Cá»§a ngưá»i dùng %{name}" + text_turning_multiple_off: Nếu bạn vô hiệu hóa nhiá»u giá trị, chúng sẽ bị loại bỠđể duy trì chỉ có má»™t giá trị cho má»—i mục. + label_attribute_of_issue: "Vấn đỠcá»§a %{name}" + permission_add_documents: Thêm tài liệu + permission_edit_documents: Soạn thảo tài liệu + permission_delete_documents: Xóa tài liệu + label_gantt_progress_line: Tiến độ + setting_jsonp_enabled: Cho phép trợ giúp JSONP + field_inherit_members: Các thành viên kế thừa + field_closed_on: Äã đóng + field_generate_password: Generate password + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Tổng cá»™ng + notice_account_not_activated_yet: You haven't activated your account yet. If you want + to receive a new activation email, please click this link. + notice_account_locked: Your account is locked. + label_hidden: Hidden + label_visibility_private: to me only + label_visibility_roles: to these roles only + label_visibility_public: to any users + field_must_change_passwd: Must change password at next logon + notice_new_password_must_be_different: The new password must be different from the + current password + setting_mail_handler_excluded_filenames: Exclude attachments by name + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/locales/zh-TW.yml --- a/config/locales/zh-TW.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/zh-TW.yml Tue Sep 09 09:34:53 2014 +0100 @@ -4,6 +4,8 @@ "zh-TW": direction: ltr + jquery: + locale: "zh-TW" date: formats: # Use the strftime parameters for formats. @@ -121,8 +123,8 @@ one: "ç´„ 1 å°æ™‚" other: "ç´„ %{count} å°æ™‚" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 å°æ™‚" + other: "%{count} å°æ™‚" x_days: one: "1 天" other: "%{count} 天" @@ -184,6 +186,7 @@ not_same_project: "ä¸å±¬æ–¼åŒä¸€å€‹å°ˆæ¡ˆ" circular_dependency: "é€™å€‹é—œè¯æœƒå°Žè‡´ç’°ç‹€ç›¸ä¾" cant_link_an_issue_with_a_descendant: "å•題無法被連çµè‡³è‡ªå·±çš„å­ä»»å‹™" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" # You can define own errors for models or model attributes. # The values :model, :attribute and :value are always available for interpolation. @@ -231,6 +234,8 @@ notice_account_wrong_password: å¯†ç¢¼ä¸æ­£ç¢º notice_account_register_done: 帳號已建立æˆåŠŸã€‚æ¬²å•Ÿç”¨æ‚¨çš„å¸³è™Ÿï¼Œè«‹é»žæ“Šç³»çµ±ç¢ºèªä¿¡å‡½ä¸­çš„啟用連çµã€‚ notice_account_unknown_email: 未知的使用者 + notice_account_not_activated_yet: 您尚未完æˆå•Ÿç”¨æ‚¨çš„帳號。若您è¦ç´¢å–新的帳號啟用 Email ,請 é»žæ“Šæ­¤é€£çµ ã€‚ + notice_account_locked: 您的帳號已被鎖定。 notice_can_t_change_password: 這個帳號使用外部èªè­‰æ–¹å¼ï¼Œç„¡æ³•變更其密碼。 notice_account_lost_email_sent: 包å«é¸æ“‡æ–°å¯†ç¢¼æŒ‡ç¤ºçš„é›»å­éƒµä»¶ï¼Œå·²ç¶“寄出給您。 notice_account_activated: 您的帳號已經啟用,å¯ç”¨å®ƒç™»å…¥ç³»çµ±ã€‚ @@ -244,7 +249,7 @@ notice_not_authorized_archived_project: 您欲存å–的專案已經被å°å­˜ã€‚ notice_email_sent: "郵件已經æˆåŠŸå¯„é€è‡³ä»¥ä¸‹æ”¶ä»¶è€…: %{value}" notice_email_error: "寄é€éƒµä»¶çš„éŽç¨‹ä¸­ç™¼ç”ŸéŒ¯èª¤ (%{value})" - notice_feeds_access_key_reseted: 您的 RSS å­˜å–é‡‘é‘°å·²è¢«é‡æ–°è¨­å®šã€‚ + notice_feeds_access_key_reseted: 您的 Atom å­˜å–é‡‘é‘°å·²è¢«é‡æ–°è¨­å®šã€‚ notice_api_access_key_reseted: 您的 API å­˜å–é‡‘é‘°å·²è¢«é‡æ–°è¨­å®šã€‚ notice_failed_to_save_issues: "無法儲存 %{count} å•題到下列所é¸å–çš„ %{total} 個項目中: %{ids}。" notice_failed_to_save_time_entries: "無法儲存 %{count} 個工時到下列所é¸å–çš„ %{total} 個項目中: %{ids}。" @@ -260,6 +265,7 @@ notice_issue_update_conflict: "當您正在編輯這個å•題的時候,它已經被其他人æ¶å…ˆä¸€æ­¥æ›´æ–°éŽã€‚" notice_account_deleted: "您的帳戶已被永久刪除。" notice_user_successful_create: "已建立用戶 %{id}。" + notice_new_password_must_be_different: 新舊密碼必須相異 error_can_t_load_default_data: "無法載入é è¨­çµ„態: %{value}" error_scm_not_found: "在儲存機制中找ä¸åˆ°é€™å€‹é …目或修訂版。" @@ -298,8 +304,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki é é¢å·²è¢«æ›´æ–°" mail_body_wiki_content_updated: "æ­¤ '%{id}' wiki é é¢å·²è¢« %{author} 更新。" - gui_validation_error: 1 個錯誤 - gui_validation_error_plural: "%{count} 個錯誤" field_name: å稱 field_description: 概述 @@ -313,7 +317,8 @@ field_downloads: 下載次數 field_author: 作者 field_created_on: 建立日期 - field_updated_on: æ›´æ–° + field_updated_on: 更新日期 + field_closed_on: çµæŸæ—¥æœŸ field_field_format: æ ¼å¼ field_is_for_all: 給全部的專案 field_possible_values: å¯èƒ½å€¼ @@ -414,6 +419,9 @@ field_timeout: "逾時 (å–®ä½: ç§’)" field_board_parent: 父論壇 field_private_notes: ç§äººç­†è¨˜ + field_inherit_members: 繼承父專案æˆå“¡ + field_generate_password: 產生密碼 + field_must_change_passwd: 必須在下次登入時變更密碼 setting_app_title: 標題 setting_app_subtitle: 副標題 @@ -429,7 +437,7 @@ setting_host_name: 主機å稱 setting_text_formatting: æ–‡å­—æ ¼å¼ setting_wiki_compression: 壓縮 Wiki æ­·å²æ–‡ç«  - setting_feeds_limit: RSS æ–°èžé™åˆ¶ + setting_feeds_limit: Atom æ–°èžé™åˆ¶ setting_autofetch_changesets: 自動擷å–èªå¯ setting_default_projects_public: 新建立之專案é è¨­ç‚ºã€Œå…¬é–‹ã€ setting_sys_api_enabled: 啟用管ç†å„²å­˜æ©Ÿåˆ¶çš„ç¶²é æœå‹™ (Web Service) @@ -482,6 +490,9 @@ setting_thumbnails_enabled: 顯示附加檔案的縮圖 setting_thumbnails_size: "ç¸®åœ–å¤§å° (å–®ä½: åƒç´  pixels)" setting_non_working_week_days: éžå·¥ä½œæ—¥ + setting_jsonp_enabled: 啟用 JSONP æ”¯æ´ + setting_default_projects_tracker_ids: 新專案é è¨­ä½¿ç”¨çš„追蹤標籤 + setting_mail_handler_excluded_filenames: 移除符åˆä¸‹åˆ—å稱的附件 permission_add_project: 建立專案 permission_add_subprojects: 建立å­å°ˆæ¡ˆ @@ -518,8 +529,10 @@ permission_edit_own_time_entries: 編輯自己的工時記錄 permission_manage_news: ç®¡ç†æ–°èž permission_comment_news: å›žæ‡‰æ–°èž - permission_manage_documents: ç®¡ç†æ–‡ä»¶ permission_view_documents: 檢視文件 + permission_add_documents: 新增文件 + permission_edit_documents: 編輯文件 + permission_delete_documents: 刪除文件 permission_manage_files: ç®¡ç†æª”案 permission_view_files: 檢視檔案 permission_manage_wiki: ç®¡ç† wiki @@ -650,8 +663,6 @@ label_text: 長文字 label_attribute: 屬性 label_attribute_plural: 屬性 - label_download: "%{count} 個下載" - label_download_plural: "%{count} 個下載" label_no_data: 沒有任何資料å¯ä¾›é¡¯ç¤º label_change_status: 變更狀態 label_history: æ­·å² @@ -700,6 +711,7 @@ one: 1 個å•題 other: "%{count} 個å•題" label_total: 總計 + label_total_time: 工時總計 label_permissions: æ¬Šé™ label_current_status: ç›®å‰ç‹€æ…‹ label_new_statuses_allowed: å¯è®Šæ›´è‡³ä»¥ä¸‹ç‹€æ…‹ @@ -709,7 +721,7 @@ label_nobody: ç„¡å label_next: ä¸‹ä¸€é  label_previous: ä¸Šä¸€é  - label_used_by: Used by + label_used_by: 已使用專案 label_details: 明細 label_add_note: 加入一個新筆記 label_per_page: æ¯é  @@ -769,8 +781,6 @@ label_repository_new: 建立新儲存機制 label_repository_plural: 儲存機制清單 label_browse: ç€è¦½ - label_modification: "%{count} 變更" - label_modification_plural: "%{count} 變更" label_branch: 分支 label_tag: 標籤 label_revision: 修訂版 @@ -869,9 +879,9 @@ label_language_based: ä¾ç”¨æˆ¶ä¹‹èªžç³»æ±ºå®š label_sort_by: "按 %{value} 排åº" label_send_test_email: 坄逿¸¬è©¦éƒµä»¶ - label_feeds_access_key: RSS å­˜å–金鑰 - label_missing_feeds_access_key: 找ä¸åˆ° RSS å­˜å–金鑰 - label_feeds_access_key_created_on: "RSS å­˜å–éµå»ºç«‹æ–¼ %{value} 之å‰" + label_feeds_access_key: Atom å­˜å–金鑰 + label_missing_feeds_access_key: 找ä¸åˆ° Atom å­˜å–金鑰 + label_feeds_access_key_created_on: "Atom å­˜å–éµå»ºç«‹æ–¼ %{value} 之å‰" label_module_plural: 模組 label_added_time_by: "是由 %{author} æ–¼ %{age} å‰åŠ å…¥" label_updated_time_by: "是由 %{author} æ–¼ %{age} 剿›´æ–°" @@ -964,14 +974,21 @@ label_fields_permissions: æ¬„ä½æ¬Šé™ label_readonly: 唯讀 label_required: å¿…å¡« + label_hidden: éš±è— label_attribute_of_project: "專案是 %{name}" + label_attribute_of_issue: "å•題是 %{name}" label_attribute_of_author: "作者是 %{name}" label_attribute_of_assigned_to: "被指派者是 %{name}" + label_attribute_of_user: "用戶是 %{name}" label_attribute_of_fixed_version: "版本是 %{name}" label_cross_project_descendants: 與å­å°ˆæ¡ˆå…±ç”¨ label_cross_project_tree: 與專案樹共用 label_cross_project_hierarchy: 與專案階層架構共用 label_cross_project_system: 與全部的專案共用 + label_gantt_progress_line: 進度線 + label_visibility_private: 僅我自己å¯è¦‹ + label_visibility_roles: 僅é¸å–之角色å¯è¦‹ + label_visibility_public: 任何用戶å‡å¯è¦‹ button_login: 登入 button_submit: é€å‡º @@ -1083,6 +1100,7 @@ text_file_repository_writable: å¯å¯«å…¥é™„加檔案目錄 text_plugin_assets_writable: å¯å¯«å…¥é™„加元件目錄 text_rmagick_available: å¯ä½¿ç”¨ RMagick (é¸é…) + text_convert_available: å¯ä½¿ç”¨ ImageMagick 轉æ›åœ–ç‰‡æ ¼å¼ (é¸é…) text_destroy_time_entries_question: 您å³å°‡åˆªé™¤çš„å•題已報工 %{hours} å°æ™‚. æ‚¨çš„é¸æ“‡æ˜¯ï¼Ÿ text_destroy_time_entries: 刪除已報工的時數 text_assign_time_entries_to_project: 指定已報工的時數至專案中 @@ -1117,6 +1135,7 @@ æ‚¨çš„å¸³æˆ¶å°‡æœƒè¢«æ°¸ä¹…åˆªé™¤ï¼Œä¸”ç„¡æ³•è¢«é‡æ–°å•Ÿç”¨ã€‚ text_session_expiration_settings: "è­¦å‘Šï¼šè®Šæ›´é€™äº›è¨­å®šå°‡æœƒå°Žè‡´åŒ…å«æ‚¨åœ¨å…§çš„æ‰€æœ‰å·¥ä½œéšŽæ®µéŽæœŸã€‚" text_project_closed: 此專案已被關閉,僅供唯讀使用。 + text_turning_multiple_off: "若您åœç”¨å¤šé‡å€¼è¨­å®šï¼Œé‡è¤‡çš„值將會被移除,以使æ¯å€‹é …目僅ä¿ç•™ä¸€å€‹å€¼ã€‚" default_role_manager: 管ç†äººå“¡ default_role_developer: 開發人員 diff -r d98d22a98252 -r afce8026aaeb config/locales/zh.yml --- a/config/locales/zh.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/locales/zh.yml Tue Sep 09 09:34:53 2014 +0100 @@ -133,6 +133,7 @@ not_same_project: "ä¸å±žäºŽåŒä¸€ä¸ªé¡¹ç›®" circular_dependency: "此关è”将导致循环ä¾èµ–" cant_link_an_issue_with_a_descendant: "问题ä¸èƒ½å…³è”到它的å­ä»»åŠ¡" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" actionview_instancetag_blank_option: 请选择 @@ -166,7 +167,7 @@ notice_not_authorized_archived_project: è¦è®¿é—®çš„项目已ç»å½’档。 notice_email_sent: "邮件已å‘é€è‡³ %{value}" notice_email_error: "å‘é€é‚®ä»¶æ—¶å‘生错误 (%{value})" - notice_feeds_access_key_reseted: 您的RSSå­˜å–键已被é‡ç½®ã€‚ + notice_feeds_access_key_reseted: 您的Atomå­˜å–键已被é‡ç½®ã€‚ notice_api_access_key_reseted: 您的API访问键已被é‡ç½®ã€‚ notice_failed_to_save_issues: "%{count} 个问题ä¿å­˜å¤±è´¥ï¼ˆå…±é€‰æ‹© %{total} 个问题):%{ids}." notice_failed_to_save_members: "æˆå‘˜ä¿å­˜å¤±è´¥: %{errors}." @@ -212,8 +213,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki页é¢å·²æ›´æ–°ã€‚" mail_body_wiki_content_updated: "'%{id}' wiki页é¢å·²ç”± %{author} 更新。" - gui_validation_error: 1 个错误 - gui_validation_error_plural: "%{count} 个错误" field_name: åç§° field_description: æè¿° @@ -327,7 +326,7 @@ setting_host_name: 主机åç§° setting_text_formatting: æ–‡æœ¬æ ¼å¼ setting_wiki_compression: 压缩WikiåŽ†å²æ–‡æ¡£ - setting_feeds_limit: RSS Feedå†…å®¹æ¡æ•°é™åˆ¶ + setting_feeds_limit: Atom Feedå†…å®¹æ¡æ•°é™åˆ¶ setting_default_projects_public: 新建项目默认为公开项目 setting_autofetch_changesets: 自动获å–程åºå˜æ›´ setting_sys_api_enabled: å¯ç”¨ç”¨äºŽç‰ˆæœ¬åº“管ç†çš„Web Service @@ -400,7 +399,6 @@ permission_edit_own_time_entries: 编辑自己的耗时 permission_manage_news: ç®¡ç†æ–°é—» permission_comment_news: 为新闻添加评论 - permission_manage_documents: ç®¡ç†æ–‡æ¡£ permission_view_documents: 查看文档 permission_manage_files: ç®¡ç†æ–‡ä»¶ permission_view_files: 查看文件 @@ -526,8 +524,6 @@ label_text: 文本 label_attribute: 属性 label_attribute_plural: 属性 - label_download: "%{count} 次下载" - label_download_plural: "%{count} 次下载" label_no_data: 没有任何数æ®å¯ä¾›æ˜¾ç¤º label_change_status: å˜æ›´çŠ¶æ€ label_history: 历å²è®°å½• @@ -630,8 +626,6 @@ label_repository: 版本库 label_repository_plural: 版本库 label_browse: æµè§ˆ - label_modification: "%{count} 个更新" - label_modification_plural: "%{count} 个更新" label_branch: 分支 label_tag: 标签 label_revision: 修订 @@ -727,9 +721,9 @@ label_language_based: æ ¹æ®ç”¨æˆ·çš„语言 label_sort_by: "æ ¹æ® %{value} 排åº" label_send_test_email: å‘逿µ‹è¯•邮件 - label_feeds_access_key: RSSå­˜å–é”® - label_missing_feeds_access_key: 缺少RSSå­˜å–é”® - label_feeds_access_key_created_on: "RSSå­˜å–键是在 %{value} 之å‰å»ºç«‹çš„" + label_feeds_access_key: Atomå­˜å–é”® + label_missing_feeds_access_key: 缺少Atomå­˜å–é”® + label_feeds_access_key_created_on: "Atomå­˜å–键是在 %{value} 之å‰å»ºç«‹çš„" label_module_plural: æ¨¡å— label_added_time_by: "ç”± %{author} 在 %{age} 之剿·»åŠ " label_updated_time: " 更新于 %{value} 之å‰" @@ -1083,6 +1077,29 @@ label_cross_project_hierarchy: 与项目继承层次共享 label_cross_project_system: 与所有项目共享 button_hide: éšè— - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past + setting_non_working_week_days: éžå·¥ä½œæ—¥ + label_in_the_next_days: 在未æ¥å‡ å¤©ä¹‹å†… + label_in_the_past_days: 在过去几天之内 + label_attribute_of_user: 用户是 %{name} + text_turning_multiple_off: 如果您åœç”¨å¤šé‡å€¼è®¾å®šï¼Œé‡å¤çš„值将被移除,以使æ¯ä¸ªé¡¹ç›®ä»…ä¿ç•™ä¸€ä¸ªå€¼ + label_attribute_of_issue: 问题是 %{name} + permission_add_documents: 添加文档 + permission_edit_documents: 编辑文档 + permission_delete_documents: 删除文档 + label_gantt_progress_line: 进度线 + setting_jsonp_enabled: å¯ç”¨JSONPæ”¯æŒ + field_inherit_members: 继承父项目æˆå‘˜ + field_closed_on: ç»“æŸæ—¥æœŸ + field_generate_password: 生æˆå¯†ç  + setting_default_projects_tracker_ids: 新建项目默认跟踪标签 + label_total_time: åˆè®¡ + notice_account_not_activated_yet: 您的账å·å°šæœªæ¿€æ´». 若您è¦é‡æ–°æ”¶å–激活邮件, 请å•击此链接. + notice_account_locked: 您的å¸å·å·²è¢«é”定 + label_hidden: éšè— + label_visibility_private: 仅对我å¯è§ + label_visibility_roles: 仅对选å–角色å¯è§ + label_visibility_public: 对任何人å¯è§ + field_must_change_passwd: ä¸‹æ¬¡ç™»å½•æ—¶å¿…é¡»ä¿®æ”¹å¯†ç  + notice_new_password_must_be_different: 新密ç å¿…须和旧密ç ä¸åŒ + setting_mail_handler_excluded_filenames: 移除符åˆä¸‹åˆ—å称的附件 + text_convert_available: ImageMagick convert available (optional) diff -r d98d22a98252 -r afce8026aaeb config/routes.rb --- a/config/routes.rb Wed May 07 14:15:02 2014 +0100 +++ b/config/routes.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,47 +18,48 @@ RedmineApp::Application.routes.draw do root :to => 'welcome#index', :as => 'home' - match 'login', :to => 'account#login', :as => 'signin' - match 'logout', :to => 'account#logout', :as => 'signout' + match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post] + match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post] match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register' match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password' match 'account/activate', :to => 'account#activate', :via => :get + get 'account/activation_email', :to => 'account#activation_email', :as => 'activation_email' - match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news' - match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue' - match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue' - match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue' + match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put] + match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put] + match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put] + match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put] match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post] - match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post] + match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message' get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message' match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post] get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' - post 'boards/:board_id/topics/preview', :to => 'messages#preview' + post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message' post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply' post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' # Misc issue routes. TODO: move into resources match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' - match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu' - match '/issues/changes', :to => 'journals#index', :as => 'issue_changes' + match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post] + match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue' match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post] - match '/projects/:project_id/issues/gantt', :to => 'gantts#show' - match '/issues/gantt', :to => 'gantts#show' + get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt' + get '/issues/gantt', :to => 'gantts#show' - match '/projects/:project_id/issues/calendar', :to => 'calendars#show' - match '/issues/calendar', :to => 'calendars#show' + get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar' + get '/issues/calendar', :to => 'calendars#show' - match 'projects/:id/issues/report', :to => 'reports#issue_report', :via => :get - match 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :via => :get + get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report' + get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details' match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post] match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post] @@ -77,20 +78,23 @@ match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships' - match 'watchers/new', :controller=> 'watchers', :action => 'new', :via => :get - match 'watchers', :controller=> 'watchers', :action => 'create', :via => :post - match 'watchers/append', :controller=> 'watchers', :action => 'append', :via => :post - match 'watchers/destroy', :controller=> 'watchers', :action => 'destroy', :via => :post - match 'watchers/watch', :controller=> 'watchers', :action => 'watch', :via => :post - match 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch', :via => :post - match 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :via => :get + post 'watchers/watch', :to => 'watchers#watch', :as => 'watch' + delete 'watchers/watch', :to => 'watchers#unwatch' + get 'watchers/new', :to => 'watchers#new' + post 'watchers', :to => 'watchers#create' + post 'watchers/append', :to => 'watchers#append' + delete 'watchers', :to => 'watchers#destroy' + get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user' + # Specific routes for issue watchers API + post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue' + delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue' match 'projects/:id/settings/:tab', :to => "projects#settings" match 'projects/:id/overview', :to => "projects#overview" resources :projects do member do - get 'settings' + get 'settings(/:tab)', :action => 'settings', :as => 'settings' post 'modules' post 'archive' post 'unarchive' @@ -99,20 +103,25 @@ match 'copy', :via => [:get, :post] end - resources :members, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do - collection do - get 'autocomplete' + shallow do + resources :memberships, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do + collection do + get 'autocomplete' + end end end - resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do - collection do - get 'autocomplete' + + shallow do + resources :members, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do + collection do + get 'autocomplete' + end end end resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy] - match 'issues/:copy_from/copy', :to => 'issues#new' + get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue' resources :issues, :only => [:index, :new, :create] do resources :time_entries, :controller => 'timelog' do collection do @@ -121,7 +130,7 @@ end end # issue form update - match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form' + match 'issues/update_form', :controller => 'issues', :action => 'update_form', :via => [:put, :post], :as => 'issue_form' resources :files, :only => [:index, :new, :create] @@ -130,26 +139,30 @@ put 'close_completed' end end - match 'versions.:format', :to => 'versions#index' - match 'roadmap', :to => 'versions#index', :format => false - match 'versions', :to => 'versions#index' + get 'versions.:format', :to => 'versions#index' + get 'roadmap', :to => 'versions#index', :format => false + get 'versions', :to => 'versions#index' resources :news, :except => [:show, :edit, :update, :destroy] resources :time_entries, :controller => 'timelog' do get 'report', :on => :collection end resources :queries, :only => [:new, :create] - resources :issue_categories, :shallow => true + shallow do + resources :issue_categories + end resources :documents, :except => [:show, :edit, :update, :destroy] resources :boards - resources :repositories, :shallow => true, :except => [:index, :show] do - member do - match 'committers', :via => [:get, :post] + shallow do + resources :repositories, :except => [:index, :show] do + member do + match 'committers', :via => [:get, :post] + end end end - + match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get - resources :wiki, :except => [:index, :new, :create] do + resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do member do get 'rename' post 'rename' @@ -165,7 +178,7 @@ end end match 'wiki', :controller => 'wiki', :action => 'show', :via => :get - get 'wiki/:id/:version', :to => 'wiki#show' + get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/} delete 'wiki/:id/:version', :to => 'wiki#destroy_version' get 'wiki/:id/:version/annotate', :to => 'wiki#annotate' get 'wiki/:id/:version/diff', :to => 'wiki#diff' @@ -181,7 +194,9 @@ get 'report' end end - resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy] + shallow do + resources :relations, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy] + end end match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete @@ -202,7 +217,7 @@ post 'add_attachment', :on => :member end - match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu + match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post] resources :time_entries, :controller => 'timelog', :except => :destroy do collection do @@ -215,9 +230,6 @@ # TODO: delete /time_entries for bulk deletion match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete - # TODO: port to be part of the resources route(s) - match 'projects/:id/settings/:tab', :to => 'projects#settings', :via => :get - get 'projects/:id/activity', :to => 'activities#index' get 'projects/:id/activity.:format', :to => 'activities#index' get 'activity', :to => 'activities#index' @@ -271,11 +283,11 @@ get 'projects/:id/repository', :to => 'repositories#show', :path => nil # additional routes for having the file name at the end of url - match 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/, :via => :get - match 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/, :via => :get - match 'attachments/download/:id', :controller => 'attachments', :action => 'download', :id => /\d+/, :via => :get - match 'attachments/toggle_active/:id', :controller => 'attachments', :action => 'toggle_active', :id => /\d+/, :via => :get - match 'attachments/thumbnail/:id(/:size)', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get, :size => /\d+/ + get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment' + get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment' + get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/ + get 'attachments/toggle_active/:id', :to => 'attachments#toggle_active', :id => /\d+/ + get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail' resources :attachments, :only => [:show, :destroy] resources :groups do @@ -322,7 +334,10 @@ resources :auth_sources do member do - get 'test_connection' + get 'test_connection', :as => 'try_connection' + end + collection do + get 'autocomplete_for_new_user' end end @@ -332,7 +347,7 @@ match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post] match 'settings', :controller => 'settings', :action => 'index', :via => :get match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post] - match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post] + match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings' match 'sys/projects', :to => 'sys#projects', :via => :get match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post diff -r d98d22a98252 -r afce8026aaeb config/settings.yml --- a/config/settings.yml Wed May 07 14:15:02 2014 +0100 +++ b/config/settings.yml Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -110,13 +110,9 @@ default: 0 commit_ref_keywords: default: 'refs,references,IssueID' -commit_fix_keywords: - default: 'fixes,closes' -commit_fix_status_id: - format: int - default: 0 -commit_fix_done_ratio: - default: 100 +commit_update_keywords: + serialized: true + default: [] commit_logtime_enabled: default: 0 commit_logtime_activity_id: @@ -151,6 +147,8 @@ - issue_updated mail_handler_body_delimiters: default: '' +mail_handler_excluded_filenames: + default: '' mail_handler_api_enabled: default: 0 mail_handler_api_key: @@ -183,6 +181,9 @@ - boards - calendar - gantt +default_projects_tracker_ids: + serialized: true + default: # Role given to a non-admin user who creates a project new_project_user_role_id: format: int @@ -215,6 +216,8 @@ default: '' rest_api_enabled: default: 0 +jsonp_enabled: + default: 0 default_notification_option: default: 'only_my_events' emails_header: diff -r d98d22a98252 -r afce8026aaeb db/migrate/001_setup.rb --- a/db/migrate/001_setup.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/001_setup.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -# redMine - project management software +# Redmine - project management software # Copyright (C) 2006 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or @@ -292,7 +292,6 @@ :lastname => "Admin", :mail => "admin@example.net", :mail_notification => true, - :language => "en", :status => 1 end diff -r d98d22a98252 -r afce8026aaeb db/migrate/002_issue_move.rb --- a/db/migrate/002_issue_move.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/002_issue_move.rb Tue Sep 09 09:34:53 2014 +0100 @@ -7,6 +7,6 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'move_issues']).destroy + Permission.where("controller=? and action=?", 'projects', 'move_issues').first.destroy end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/003_issue_add_note.rb --- a/db/migrate/003_issue_add_note.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/003_issue_add_note.rb Tue Sep 09 09:34:53 2014 +0100 @@ -7,6 +7,6 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'add_note']).destroy + Permission.where("controller=? and action=?", 'issues', 'add_note').first.destroy end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/004_export_pdf.rb --- a/db/migrate/004_export_pdf.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/004_export_pdf.rb Tue Sep 09 09:34:53 2014 +0100 @@ -8,7 +8,7 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'export_issues_pdf']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'export_pdf']).destroy + Permission.where("controller=? and action=?", 'projects', 'export_issues_pdf').first.destroy + Permission.where("controller=? and action=?", 'issues', 'export_pdf').first.destroy end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/006_calendar_and_activity.rb --- a/db/migrate/006_calendar_and_activity.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/006_calendar_and_activity.rb Tue Sep 09 09:34:53 2014 +0100 @@ -9,8 +9,8 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'activity']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'calendar']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'gantt']).destroy + Permission.where("controller=? and action=?", 'projects', 'activity').first.destroy + Permission.where("controller=? and action=?", 'projects', 'calendar').first.destroy + Permission.where("controller=? and action=?", 'projects', 'gantt').first.destroy end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/007_create_journals.rb --- a/db/migrate/007_create_journals.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/007_create_journals.rb Tue Sep 09 09:34:53 2014 +0100 @@ -28,7 +28,7 @@ Permission.create :controller => "issues", :action => "history", :description => "label_history", :sort => 1006, :is_public => true, :mail_option => 0, :mail_enabled => 0 # data migration - IssueHistory.find(:all, :include => :issue).each {|h| + IssueHistory.all.each {|h| j = Journal.new(:journalized => h.issue, :user_id => h.author_id, :notes => h.notes, :created_on => h.created_on) j.details << JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :value => h.status_id) j.save @@ -51,6 +51,6 @@ add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id" - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'history']).destroy + Permission.where("controller=? and action=?", 'issues', 'history').first.destroy end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/012_add_comments_permissions.rb --- a/db/migrate/012_add_comments_permissions.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/012_add_comments_permissions.rb Tue Sep 09 09:34:53 2014 +0100 @@ -8,7 +8,7 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'news', 'add_comment']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'news', 'destroy_comment']).destroy + Permission.where("controller=? and action=?", 'news', 'add_comment').first.destroy + Permission.where("controller=? and action=?", 'news', 'destroy_comment').first.destroy end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/014_add_queries_permissions.rb --- a/db/migrate/014_add_queries_permissions.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/014_add_queries_permissions.rb Tue Sep 09 09:34:53 2014 +0100 @@ -7,6 +7,6 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'add_query']).destroy + Permission.where("controller=? and action=?", 'projects', 'add_query').first.destroy end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/016_add_repositories_permissions.rb --- a/db/migrate/016_add_repositories_permissions.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/016_add_repositories_permissions.rb Tue Sep 09 09:34:53 2014 +0100 @@ -12,11 +12,11 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'show']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'browse']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'entry']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revisions']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revision']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'diff']).destroy + Permission.where("controller=? and action=?", 'repositories', 'show').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'browse').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'entry').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'revisions').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'revision').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'diff').first.destroy end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/019_add_issue_status_position.rb --- a/db/migrate/019_add_issue_status_position.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/019_add_issue_status_position.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ class AddIssueStatusPosition < ActiveRecord::Migration def self.up add_column :issue_statuses, :position, :integer, :default => 1 - IssueStatus.find(:all).each_with_index {|status, i| status.update_attribute(:position, i+1)} + IssueStatus.all.each_with_index {|status, i| status.update_attribute(:position, i+1)} end def self.down diff -r d98d22a98252 -r afce8026aaeb db/migrate/021_add_tracker_position.rb --- a/db/migrate/021_add_tracker_position.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/021_add_tracker_position.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ class AddTrackerPosition < ActiveRecord::Migration def self.up add_column :trackers, :position, :integer, :default => 1 - Tracker.find(:all).each_with_index {|tracker, i| tracker.update_attribute(:position, i+1)} + Tracker.all.each_with_index {|tracker, i| tracker.update_attribute(:position, i+1)} end def self.down diff -r d98d22a98252 -r afce8026aaeb db/migrate/022_serialize_possibles_values.rb --- a/db/migrate/022_serialize_possibles_values.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/022_serialize_possibles_values.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@ class SerializePossiblesValues < ActiveRecord::Migration def self.up - CustomField.find(:all).each do |field| + CustomField.all.each do |field| if field.possible_values and field.possible_values.is_a? String field.possible_values = field.possible_values.split('|') field.save diff -r d98d22a98252 -r afce8026aaeb db/migrate/024_add_roadmap_permission.rb --- a/db/migrate/024_add_roadmap_permission.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/024_add_roadmap_permission.rb Tue Sep 09 09:34:53 2014 +0100 @@ -7,6 +7,6 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'roadmap']).destroy + Permission.where("controller=? and action=?", 'projects', 'roadmap').first.destroy end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/065_add_settings_updated_on.rb --- a/db/migrate/065_add_settings_updated_on.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/065_add_settings_updated_on.rb Tue Sep 09 09:34:53 2014 +0100 @@ -2,7 +2,7 @@ def self.up add_column :settings, :updated_on, :timestamp # set updated_on - Setting.find(:all).each(&:save) + Setting.all.each(&:save) end def self.down diff -r d98d22a98252 -r afce8026aaeb db/migrate/068_create_enabled_modules.rb --- a/db/migrate/068_create_enabled_modules.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/068_create_enabled_modules.rb Tue Sep 09 09:34:53 2014 +0100 @@ -7,7 +7,7 @@ add_index :enabled_modules, [:project_id], :name => :enabled_modules_project_id # Enable all modules for existing projects - Project.find(:all).each do |project| + Project.all.each do |project| project.enabled_module_names = Redmine::AccessControl.available_project_modules end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/072_add_enumerations_position.rb --- a/db/migrate/072_add_enumerations_position.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/072_add_enumerations_position.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ class AddEnumerationsPosition < ActiveRecord::Migration def self.up add_column(:enumerations, :position, :integer, :default => 1) unless Enumeration.column_names.include?('position') - Enumeration.find(:all).group_by(&:opt).each do |opt, enums| + Enumeration.all.group_by(&:opt).each do |opt, enums| enums.each_with_index do |enum, i| # do not call model callbacks Enumeration.update_all "position = #{i+1}", {:id => enum.id} diff -r d98d22a98252 -r afce8026aaeb db/migrate/078_add_custom_fields_position.rb --- a/db/migrate/078_add_custom_fields_position.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/078_add_custom_fields_position.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ class AddCustomFieldsPosition < ActiveRecord::Migration def self.up add_column(:custom_fields, :position, :integer, :default => 1) - CustomField.find(:all).group_by(&:type).each do |t, fields| + CustomField.all.group_by(&:type).each do |t, fields| fields.each_with_index do |field, i| # do not call model callbacks CustomField.update_all "position = #{i+1}", {:id => field.id} diff -r d98d22a98252 -r afce8026aaeb db/migrate/081_create_projects_trackers.rb --- a/db/migrate/081_create_projects_trackers.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/081_create_projects_trackers.rb Tue Sep 09 09:34:53 2014 +0100 @@ -7,8 +7,8 @@ add_index :projects_trackers, :project_id, :name => :projects_trackers_project_id # Associates all trackers to all projects (as it was before) - tracker_ids = Tracker.find(:all).collect(&:id) - Project.find(:all).each do |project| + tracker_ids = Tracker.all.collect(&:id) + Project.all.each do |project| project.tracker_ids = tracker_ids end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/091_change_changesets_revision_to_string.rb --- a/db/migrate/091_change_changesets_revision_to_string.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/091_change_changesets_revision_to_string.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,9 +1,32 @@ class ChangeChangesetsRevisionToString < ActiveRecord::Migration def self.up + # Some backends (eg. SQLServer 2012) do not support changing the type + # of an indexed column so the index needs to be dropped first + # BUT this index is renamed with some backends (at least SQLite3) for + # some (unknown) reasons, thus we check for the other name as well + # so we don't end up with 2 identical indexes + if index_exists? :changesets, [:repository_id, :revision], :name => :changesets_repos_rev + remove_index :changesets, :name => :changesets_repos_rev + end + if index_exists? :changesets, [:repository_id, :revision], :name => :altered_changesets_repos_rev + remove_index :changesets, :name => :altered_changesets_repos_rev + end + change_column :changesets, :revision, :string, :null => false + + add_index :changesets, [:repository_id, :revision], :unique => true, :name => :changesets_repos_rev end def self.down + if index_exists? :changesets, :changesets_repos_rev + remove_index :changesets, :name => :changesets_repos_rev + end + if index_exists? :changesets, [:repository_id, :revision], :name => :altered_changesets_repos_rev + remove_index :changesets, :name => :altered_changesets_repos_rev + end + change_column :changesets, :revision, :integer, :null => false + + add_index :changesets, [:repository_id, :revision], :unique => true, :name => :changesets_repos_rev end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/096_add_commit_access_permission.rb --- a/db/migrate/096_add_commit_access_permission.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/096_add_commit_access_permission.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,12 +1,12 @@ class AddCommitAccessPermission < ActiveRecord::Migration def self.up - Role.find(:all).select { |r| not r.builtin? }.each do |r| + Role.all.select { |r| not r.builtin? }.each do |r| r.add_permission!(:commit_access) end end def self.down - Role.find(:all).select { |r| not r.builtin? }.each do |r| + Role.all.select { |r| not r.builtin? }.each do |r| r.remove_permission!(:commit_access) end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/097_add_view_wiki_edits_permission.rb --- a/db/migrate/097_add_view_wiki_edits_permission.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/097_add_view_wiki_edits_permission.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,12 +1,12 @@ class AddViewWikiEditsPermission < ActiveRecord::Migration def self.up - Role.find(:all).each do |r| + Role.all.each do |r| r.add_permission!(:view_wiki_edits) if r.has_permission?(:view_wiki_pages) end end def self.down - Role.find(:all).each do |r| + Role.all.each do |r| r.remove_permission!(:view_wiki_edits) end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/099_add_delete_wiki_pages_attachments_permission.rb --- a/db/migrate/099_add_delete_wiki_pages_attachments_permission.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/099_add_delete_wiki_pages_attachments_permission.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,12 +1,12 @@ class AddDeleteWikiPagesAttachmentsPermission < ActiveRecord::Migration def self.up - Role.find(:all).each do |r| + Role.all.each do |r| r.add_permission!(:delete_wiki_pages_attachments) if r.has_permission?(:edit_wiki_pages) end end def self.down - Role.find(:all).each do |r| + Role.all.each do |r| r.remove_permission!(:delete_wiki_pages_attachments) end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20090312194159_add_projects_trackers_unique_index.rb --- a/db/migrate/20090312194159_add_projects_trackers_unique_index.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/20090312194159_add_projects_trackers_unique_index.rb Tue Sep 09 09:34:53 2014 +0100 @@ -10,7 +10,7 @@ # Removes duplicates in projects_trackers table def self.remove_duplicates - Project.find(:all).each do |project| + Project.all.each do |project| ids = project.trackers.collect(&:id) unless ids == ids.uniq project.trackers.clear diff -r d98d22a98252 -r afce8026aaeb db/migrate/20090503121505_populate_member_roles.rb --- a/db/migrate/20090503121505_populate_member_roles.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/20090503121505_populate_member_roles.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ class PopulateMemberRoles < ActiveRecord::Migration def self.up MemberRole.delete_all - Member.find(:all).each do |member| + Member.all.each do |member| MemberRole.create!(:member_id => member.id, :role_id => member.role_id) end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20091114105931_add_view_issues_permission.rb --- a/db/migrate/20091114105931_add_view_issues_permission.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/20091114105931_add_view_issues_permission.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,12 +1,12 @@ class AddViewIssuesPermission < ActiveRecord::Migration def self.up - Role.find(:all).each do |r| + Role.all.each do |r| r.add_permission!(:view_issues) end end def self.down - Role.find(:all).each do |r| + Role.all.each do |r| r.remove_permission!(:view_issues) end end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20100819172912_enable_calendar_and_gantt_modules_where_appropriate.rb --- a/db/migrate/20100819172912_enable_calendar_and_gantt_modules_where_appropriate.rb Wed May 07 14:15:02 2014 +0100 +++ b/db/migrate/20100819172912_enable_calendar_and_gantt_modules_where_appropriate.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@ class EnableCalendarAndGanttModulesWhereAppropriate < ActiveRecord::Migration def self.up - EnabledModule.find(:all, :conditions => ["name = ?", 'issue_tracking']).each do |e| + EnabledModule.where(:name => 'issue_tracking').all.each do |e| EnabledModule.create(:name => 'calendar', :project_id => e.project_id) EnabledModule.create(:name => 'gantt', :project_id => e.project_id) end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20121209123234_add_queries_type.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20121209123234_add_queries_type.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddQueriesType < ActiveRecord::Migration + def up + add_column :queries, :type, :string + end + + def down + remove_column :queries, :type + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20121209123358_update_queries_to_sti.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20121209123358_update_queries_to_sti.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class UpdateQueriesToSti < ActiveRecord::Migration + def up + ::Query.update_all :type => 'IssueQuery' + end + + def down + ::Query.update_all :type => nil + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20121213084931_add_attachments_disk_directory.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20121213084931_add_attachments_disk_directory.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddAttachmentsDiskDirectory < ActiveRecord::Migration + def up + add_column :attachments, :disk_directory, :string + end + + def down + remove_column :attachments, :disk_directory + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130110122628_split_documents_permissions.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130110122628_split_documents_permissions.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,23 @@ +class SplitDocumentsPermissions < ActiveRecord::Migration + def up + # :manage_documents permission split into 3 permissions: + # :add_documents, :edit_documents and :delete_documents + Role.all.each do |role| + if role.has_permission?(:manage_documents) + role.add_permission! :add_documents, :edit_documents, :delete_documents + role.remove_permission! :manage_documents + end + end + end + + def down + Role.all.each do |role| + if role.has_permission?(:add_documents) || + role.has_permission?(:edit_documents) || + role.has_permission?(:delete_documents) + role.remove_permission! :add_documents, :edit_documents, :delete_documents + role.add_permission! :manage_documents + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130201184705_add_unique_index_on_tokens_value.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130201184705_add_unique_index_on_tokens_value.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,15 @@ +class AddUniqueIndexOnTokensValue < ActiveRecord::Migration + def up + say_with_time "Adding unique index on tokens, this may take some time..." do + # Just in case + duplicates = Token.connection.select_values("SELECT value FROM #{Token.table_name} GROUP BY value HAVING COUNT(id) > 1") + Token.where(:value => duplicates).delete_all + + add_index :tokens, :value, :unique => true, :name => 'tokens_value' + end + end + + def down + remove_index :tokens, :name => 'tokens_value' + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130202090625_add_projects_inherit_members.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130202090625_add_projects_inherit_members.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddProjectsInheritMembers < ActiveRecord::Migration + def up + add_column :projects, :inherit_members, :boolean, :default => false, :null => false + end + + def down + remove_column :projects, :inherit_members + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130207175206_add_unique_index_on_custom_fields_trackers.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130207175206_add_unique_index_on_custom_fields_trackers.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,24 @@ +class AddUniqueIndexOnCustomFieldsTrackers < ActiveRecord::Migration + def up + table_name = "#{CustomField.table_name_prefix}custom_fields_trackers#{CustomField.table_name_suffix}" + duplicates = CustomField.connection.select_rows("SELECT custom_field_id, tracker_id FROM #{table_name} GROUP BY custom_field_id, tracker_id HAVING COUNT(*) > 1") + duplicates.each do |custom_field_id, tracker_id| + # Removes duplicate rows + CustomField.connection.execute("DELETE FROM #{table_name} WHERE custom_field_id=#{custom_field_id} AND tracker_id=#{tracker_id}") + # And insert one + CustomField.connection.execute("INSERT INTO #{table_name} (custom_field_id, tracker_id) VALUES (#{custom_field_id}, #{tracker_id})") + end + + if index_exists? :custom_fields_trackers, [:custom_field_id, :tracker_id] + remove_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end + add_index :custom_fields_trackers, [:custom_field_id, :tracker_id], :unique => true + end + + def down + if index_exists? :custom_fields_trackers, [:custom_field_id, :tracker_id] + remove_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end + add_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130207181455_add_unique_index_on_custom_fields_projects.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130207181455_add_unique_index_on_custom_fields_projects.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,24 @@ +class AddUniqueIndexOnCustomFieldsProjects < ActiveRecord::Migration + def up + table_name = "#{CustomField.table_name_prefix}custom_fields_projects#{CustomField.table_name_suffix}" + duplicates = CustomField.connection.select_rows("SELECT custom_field_id, project_id FROM #{table_name} GROUP BY custom_field_id, project_id HAVING COUNT(*) > 1") + duplicates.each do |custom_field_id, project_id| + # Removes duplicate rows + CustomField.connection.execute("DELETE FROM #{table_name} WHERE custom_field_id=#{custom_field_id} AND project_id=#{project_id}") + # And insert one + CustomField.connection.execute("INSERT INTO #{table_name} (custom_field_id, project_id) VALUES (#{custom_field_id}, #{project_id})") + end + + if index_exists? :custom_fields_projects, [:custom_field_id, :project_id] + remove_index :custom_fields_projects, [:custom_field_id, :project_id] + end + add_index :custom_fields_projects, [:custom_field_id, :project_id], :unique => true + end + + def down + if index_exists? :custom_fields_projects, [:custom_field_id, :project_id] + remove_index :custom_fields_projects, [:custom_field_id, :project_id] + end + add_index :custom_fields_projects, [:custom_field_id, :project_id] + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130215073721_change_users_lastname_length_to_255.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130215073721_change_users_lastname_length_to_255.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class ChangeUsersLastnameLengthTo255 < ActiveRecord::Migration + def self.up + change_column :users, :lastname, :string, :limit => 255, :default => '', :null => false + end + + def self.down + change_column :users, :lastname, :string, :limit => 30, :default => '', :null => false + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130215111127_add_issues_closed_on.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130215111127_add_issues_closed_on.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddIssuesClosedOn < ActiveRecord::Migration + def up + add_column :issues, :closed_on, :datetime, :default => nil + end + + def down + remove_column :issues, :closed_on + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130215111141_populate_issues_closed_on.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130215111141_populate_issues_closed_on.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,25 @@ +class PopulateIssuesClosedOn < ActiveRecord::Migration + def up + closed_status_ids = IssueStatus.where(:is_closed => true).pluck(:id) + if closed_status_ids.any? + # First set closed_on for issues that have been closed once + closed_status_values = closed_status_ids.map {|status_id| "'#{status_id}'"}.join(',') + subselect = "SELECT MAX(#{Journal.table_name}.created_on)" + + " FROM #{Journal.table_name}, #{JournalDetail.table_name}" + + " WHERE #{Journal.table_name}.id = #{JournalDetail.table_name}.journal_id" + + " AND #{Journal.table_name}.journalized_type = 'Issue' AND #{Journal.table_name}.journalized_id = #{Issue.table_name}.id" + + " AND #{JournalDetail.table_name}.property = 'attr' AND #{JournalDetail.table_name}.prop_key = 'status_id'" + + " AND #{JournalDetail.table_name}.old_value NOT IN (#{closed_status_values})" + + " AND #{JournalDetail.table_name}.value IN (#{closed_status_values})" + Issue.update_all "closed_on = (#{subselect})" + + # Then set closed_on for closed issues that weren't up updated by the above UPDATE + # No journal was found so we assume that they were closed on creation + Issue.update_all "closed_on = created_on", {:status_id => closed_status_ids, :closed_on => nil} + end + end + + def down + Issue.update_all :closed_on => nil + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130217094251_remove_issues_default_fk_values.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130217094251_remove_issues_default_fk_values.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,19 @@ +class RemoveIssuesDefaultFkValues < ActiveRecord::Migration + def up + change_column_default :issues, :tracker_id, nil + change_column_default :issues, :project_id, nil + change_column_default :issues, :status_id, nil + change_column_default :issues, :assigned_to_id, nil + change_column_default :issues, :priority_id, nil + change_column_default :issues, :author_id, nil + end + + def down + change_column_default :issues, :tracker_id, 0 + change_column_default :issues, :project_id, 0 + change_column_default :issues, :status_id, 0 + change_column_default :issues, :assigned_to_id, 0 + change_column_default :issues, :priority_id, 0 + change_column_default :issues, :author_id, 0 + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130602092539_create_queries_roles.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130602092539_create_queries_roles.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,13 @@ +class CreateQueriesRoles < ActiveRecord::Migration + def self.up + create_table :queries_roles, :id => false do |t| + t.column :query_id, :integer, :null => false + t.column :role_id, :integer, :null => false + end + add_index :queries_roles, [:query_id, :role_id], :unique => true, :name => :queries_roles_ids + end + + def self.down + drop_table :queries_roles + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130710182539_add_queries_visibility.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130710182539_add_queries_visibility.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,13 @@ +class AddQueriesVisibility < ActiveRecord::Migration + def up + add_column :queries, :visibility, :integer, :default => 0 + Query.where(:is_public => true).update_all(:visibility => 2) + remove_column :queries, :is_public + end + + def down + add_column :queries, :is_public, :boolean, :default => true, :null => false + Query.where('visibility <> ?', 2).update_all(:is_public => false) + remove_column :queries, :visibility + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130713104233_create_custom_fields_roles.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130713104233_create_custom_fields_roles.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,14 @@ +class CreateCustomFieldsRoles < ActiveRecord::Migration + def self.up + create_table :custom_fields_roles, :id => false do |t| + t.column :custom_field_id, :integer, :null => false + t.column :role_id, :integer, :null => false + end + add_index :custom_fields_roles, [:custom_field_id, :role_id], :unique => true, :name => :custom_fields_roles_ids + CustomField.update_all({:visible => true}, {:type => 'IssueCustomField'}) + end + + def self.down + drop_table :custom_fields_roles + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130713111657_add_queries_options.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130713111657_add_queries_options.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddQueriesOptions < ActiveRecord::Migration + def up + add_column :queries, :options, :text + end + + def down + remove_column :queries, :options + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130729070143_add_users_must_change_passwd.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130729070143_add_users_must_change_passwd.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddUsersMustChangePasswd < ActiveRecord::Migration + def up + add_column :users, :must_change_passwd, :boolean, :default => false, :null => false + end + + def down + remove_column :users, :must_change_passwd + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20130911193200_remove_eols_from_attachments_filename.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130911193200_remove_eols_from_attachments_filename.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,12 @@ +class RemoveEolsFromAttachmentsFilename < ActiveRecord::Migration + def up + Attachment.where("filename like ? or filename like ?", "%\r%", "%\n%").each do |attachment| + filename = attachment.filename.to_s.tr("\r\n", "_") + Attachment.where(:id => attachment.id).update_all(:filename => filename) + end + end + + def down + # nop + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20131004113137_support_for_multiple_commit_keywords.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20131004113137_support_for_multiple_commit_keywords.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,17 @@ +class SupportForMultipleCommitKeywords < ActiveRecord::Migration + def up + # Replaces commit_fix_keywords, commit_fix_status_id, commit_fix_done_ratio settings + # with commit_update_keywords setting + keywords = Setting.where(:name => 'commit_fix_keywords').limit(1).pluck(:value).first + status_id = Setting.where(:name => 'commit_fix_status_id').limit(1).pluck(:value).first + done_ratio = Setting.where(:name => 'commit_fix_done_ratio').limit(1).pluck(:value).first + if keywords.present? + Setting.commit_update_keywords = [{'keywords' => keywords, 'status_id' => status_id, 'done_ratio' => done_ratio}] + end + Setting.where(:name => %w(commit_fix_keywords commit_fix_status_id commit_fix_done_ratio)).delete_all + end + + def down + Setting.where(:name => 'commit_update_keywords').delete_all + end +end diff -r d98d22a98252 -r afce8026aaeb db/migrate/20131005100610_add_repositories_created_on.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20131005100610_add_repositories_created_on.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,9 @@ +class AddRepositoriesCreatedOn < ActiveRecord::Migration + def up + add_column :repositories, :created_on, :timestamp + end + + def down + remove_column :repositories, :created_on + end +end diff -r d98d22a98252 -r afce8026aaeb doc/CHANGELOG --- a/doc/CHANGELOG Wed May 07 14:15:02 2014 +0100 +++ b/doc/CHANGELOG Tue Sep 09 09:34:53 2014 +0100 @@ -1,9 +1,374 @@ == Redmine changelog Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang +Copyright (C) 2006-2014 Jean-Philippe Lang http://www.redmine.org/ +== 2014-07-06 v2.4.6 + +* Defect #13932: File upload does not work with Safari +* Defect #16467: back_url redirection does not work for '/' +* Defect #16511: Potentiel data leak in "Invalid form authenticity token" error screen +* Defect #16530: back_url redirection work for non sub uri +* Defect #16711: Non-ascii attachment file name get corrupted in IE11 +* Defect #17151: File upload broken on Chrome 36 +* Defect #17235: use test_email report can't modify frozen String +* Defect #17391: Upgrade to Rails 3.2.19 +* Patch #17206: Fix Invalid CSS on Error-Pages + +== 2014-03-29 v2.4.5 + +* Defect #16466: Fixed back url verification + +== 2014-03-02 v2.4.4 + +* Defect #16081: Export CSV - Custom field true/false not using translation +* Defect #16161: Parent task search and datepicker not available after changing status +* Defect #16169: Wrong validation when updating integer custom field with spaces +* Defect #16177: Mercurial 2.9 compatibility + +== 2014-02-08 v2.4.3 + +* Defect #13544: Commit reference: autogenerated issue note has wrong commit link syntax in multi-repo or cross-project context +* Defect #15664: Unable to upload attachments without add_issues, edit_issues or add_issue_notes permission +* Defect #15756: 500 on admin info/settings page on development environment +* Defect #15781: Customfields have a noticable impact on search performance due to slow database COUNT +* Defect #15849: Redmine:Fetch_Changesets Single-inheritance issue in subclass "Repository:Git" +* Defect #15870: Parent task completion is 104% after update of subtasks +* Defect #16032: Repository.fetch_changesets > app/models/repository/git.rb:137:in `[]=': string not matched (IndexError) +* Defect #16038: Issue#css_classes corrupts user.groups association cache +* Patch #15960: pt-BR translation for 2.4-stable + +Additional note: + +#15781 was forgotten to merge to v2.4.3. +It is in v2.5.0. + +== 2013-12-23 v2.4.2 + +* Defect #15398: HTML 5 invalid

    tag +* Defect #15523: CSS class for done ratio is not properly generated +* Defect #15623: Timelog filtering by activity field does not handle project activity overrides +* Defect #15677: Links for relations in notifications do not include hostname +* Defect #15684: MailHandler : text/plain attachments are added to description +* Defect #15714: Notification on loosing assignment does not work +* Defect #15735: OpenID login fails due to CSRF verification +* Defect #15741: Multiple scrollbars in project selection tree +* Patch #9442: Russian wiki syntax help translations +* Patch #15524: Japanese translation update (r12278) +* Patch #15601: Turkish translation update +* Patch #15688: Spanish translation updated +* Patch #15696: Russian translation update + +== 2013-11-23 v2.4.1 + +* Defect #15401: Wiki syntax "bold italic" is incorrect +* Defect #15414: Empty sidebar should not be displayed in project overview +* Defect #15427: REST API POST and PUT broken +* Patch #15376: Traditional Chinese translation (to r12295) +* Patch #15395: German "ImageMagick convert available" translation +* Patch #15400: Czech Wiki syntax traslation +* Patch #15402: Czech translation for 2.4-stable + +== 2013-11-17 v2.4.0 + +* Defect #1983: statistics get rather cramped with more than 15 or so contributers +* Defect #7335: Sorting issues in gantt by date, not by id +* Defect #12681: Treat group assignments as assigned to me +* Defect #12824: Useless "edit" link in workflow menu +* Defect #13260: JQuery Datepicker popup is missing multiple month/year modifiers +* Defect #13537: Filters will show issues with unused custom fields. +* Defect #13829: Favicon bug in IE8 +* Defect #13949: Handling of attachment uploads when 'Maximum attachment size' is set to 0 +* Defect #13989: Trac and Mantis importers reset global notification settings +* Defect #13990: Trac importer breaks on exotic filenames and ruby 1.9+ +* Defect #14028: Plugins Gemfiles loading breaks __FILE__ +* Defect #14086: Better handling of issue start date validation +* Defect #14206: Synchronize the lang attribute of the HTML with the display language +* Defect #14403: No error message if notification mail could not delivered +* Defect #14516: Missing Sort Column Label and Center Align on Admin-Enumerations +* Defect #14517: Missing Html Tile on Admin (Groups, LDAP and Plugins) +* Defect #14598: Wrong test with logger.info in model mail_handler +* Defect #14615: Warn me when leaving a page with unsaved text doesn't work when editing an update note +* Defect #14621: AJAX call on the issue form resets data entered during the request +* Defect #14657: Wrong German translation for member inheritance +* Defect #14773: ActiveRecord::Acts::Versioned::ActMethods#next_version Generates ArgumentError +* Defect #14819: Newlines in attachment filename causes crash +* Defect #14986: 500 error when viewing a wiki page without WikiContent +* Defect #14995: Japanese "notice_not_authorized" translation is incorrect +* Defect #15044: Patch for giving controller_issues_edit_after_save api hook the correct context +* Defect #15050: redmine:migrate_from_mantis fails to migrate projects with all upper case name +* Defect #15058: Project authorization EnabledModule N+1 queries +* Defect #15113: The mail method should return a Mail::Message +* Defect #15135: Issue#update_nested_set_attributes comparing nil with empty string +* Defect #15191: HTML 5 validation failures +* Defect #15227: Custom fields in issue form - splitting is incorrect +* Defect #15307: HTML 5 deprecates width and align attributes +* Feature #1005: Add the addition/removal/change of related issues to the history +* Feature #1019: Role based custom queries +* Feature #1391: Ability to force user to change password +* Feature #2199: Ability to clear dates and text fields when bulk editing issues +* Feature #2427: Document horizontal rule syntax +* Feature #2795: Add a "Cancel" button to the "Delete" project page when deleting a project. +* Feature #2865: One click filter in search view +* Feature #3413: Exclude attachments from incoming emails based on file name +* Feature #3872: New user password - better functionality +* Feature #4911: Multiple issue update rules with different keywords in commit messages +* Feature #5037: Role-based issue custom field visibility +* Feature #7590: Different commit Keywords for each tracker +* Feature #7836: Ability to save Gantt query filters +* Feature #8253: Update CodeRay to 1.1 final +* Feature #11159: REST API for getting CustomField definitions +* Feature #12293: Add links to attachments in new issue email notification +* Feature #12912: Issue-notes Redmine links: append actual note reference to rendered links +* Feature #13157: Link on "My Page" to view all my spent time +* Feature #13746: Highlighting of source link target line +* Feature #13943: Better handling of validation errors when bulk editing issues +* Feature #13945: Disable autofetching of repository changesets if projects are closed +* Feature #14024: Default of issue start and due date +* Feature #14060: Enable configuration of OpenIdAuthentication.store +* Feature #14228: Registered users should have a way to get a new action email +* Feature #14614: View hooks for user preferences +* Feature #14630: wiki_syntax.html per language (wiki help localization mechanism) +* Feature #15136: Activate Custom Fields on a selection of projects directly from Custom fields page +* Feature #15182: Return to section anchor after wiki section edit +* Feature #15218: Update Rails 3.2.15 +* Feature #15311: Add an indication to admin/info whether or not ImageMagick convert is available +* Patch #6689: Document project-links in parse_redmine_links +* Patch #13460: All translations: RSS -> Atom +* Patch #13482: Do not add empty header/footer to notification emails +* Patch #13528: Traditional Chinese "label_total_time" translation +* Patch #13551: update Dutch translations - March 2013 +* Patch #13577: Japanese translation improvement ("done ratio") +* Patch #13646: Fix handling multiple text parts in email +* Patch #13674: Lithuanian translation +* Patch #13687: Favicon bug in opera browser +* Patch #13697: Back-button on diff page is not working when I'm directed from email +* Patch #13745: Correct translation for member save button +* Patch #13808: Changed Bulgarian "label_statistics" translation +* Patch #13825: German translation: jquery.ui.datepicker-de.js +* Patch #13900: Update URL when changing tab +* Patch #13931: Error and inconsistencies in Croatian translation +* Patch #13948: REST API should return user.status +* Patch #13988: Enhanced Arabic translation +* Patch #14138: Output changeset comment in html title +* Patch #14180: Improve pt-BR translation +* Patch #14222: German translation: grammar + spelling +* Patch #14223: Fix icon transparency issues +* Patch #14360: Slovene language translation +* Patch #14767: More CSS classes on various fields +* Patch #14901: Slovak translation +* Patch #14920: Russian numeric translation +* Patch #14981: Italian translation +* Patch #15072: Optimization of issues journal custom fields display +* Patch #15073: list custom fields : multiple select filter wider +* Patch #15075: Fix typo in the Dutch "label_user_mail_option_all" translation +* Patch #15277: Accept custom field format added at runtime +* Patch #15295: Log error messages when moving attachements in sub-directories +* Patch #15369: Bulgarian translation (r12278) + +== 2013-11-17 v2.3.4 + +* Defect #13348: Repository tree can't handle two loading at once +* Defect #13632: Empty page attached when exporting PDF +* Defect #14590: migrate_from_trac.rake does not import Trac users, uses too short password +* Defect #14656: JRuby: Encoding error when creating issues +* Defect #14883: Update activerecord-jdbc-adapter +* Defect #14902: Potential invalid SQL error with invalid group_ids +* Defect #14931: SCM annotate with non ASCII author +* Defect #14960: migrate_from_mantis.rake does not import Mantis users, uses too short password +* Defect #14977: Internal Server Error while uploading file +* Defect #15190: JS-error while using a global custom query w/ project filter in a project context +* Defect #15235: Wiki Pages REST API with version returns wrong comments +* Defect #15344: Default status always inserted to allowed statuses when changing status +* Feature #14919: Update ruby-openid version above 2.3.0 +* Patch #14592: migrate_from_trac.rake does not properly parse First Name and Last Name +* Patch #14886: Norweigan - label_copied_to and label_copied_from translated +* Patch #15185: Simplified Chinese translation for 2.3-stable + +== 2013-09-14 v2.3.3 + +* Defect #13008: Usage of attribute_present? in UserPreference +* Defect #14340: Autocomplete fields rendering issue with alternate theme +* Defect #14366: Spent Time report sorting on custom fields causes error +* Defect #14369: Open/closed issue counts on issues summary are not displayed with SQLServer +* Defect #14401: Filtering issues on "related to" may ignore other filters +* Defect #14415: Spent time details and report should ignore 'Setting.display_subprojects_issues?' when 'Subproject' filter is enabled. +* Defect #14422: CVS root_url not recognized when connection string does not include port +* Defect #14447: Additional status transitions for assignees do not work if assigned to a group +* Defect #14511: warning: class variable access from toplevel on Ruby 2.0 +* Defect #14562: diff of CJK (Chinese/Japanese/Korean) is broken on Ruby 1.8 +* Defect #14584: Standard fields disabled for certain trackers still appear in email notifications +* Defect #14607: rake redmine:load_default_data Error +* Defect #14697: Wrong Russian translation in close project message +* Defect #14798: Wrong done_ratio calculation for parent with subtask having estimated_hours=0 +* Patch #14485: Traditional Chinese translation for 2.3-stable +* Patch #14502: Russian translation for 2.3-stable +* Patch #14531: Spanish translations for 2.3.x +* Patch #14686: Portuguese translation for 2.3-stable + +== 2013-07-14 v2.3.2 + +* Defect #9996: configuration.yml in documentation , but redmine ask me to create email.yml +* Defect #13692: warning: already initialized constant on Ruby 1.8.7 +* Defect #13783: Internal error on time tracking activity enumeration deletion +* Defect #13821: "obj" parameter is not defined for macros used in description of documents +* Defect #13850: Unable to set custom fields for versions using the REST API +* Defect #13910: Values of custom fields are not kept in issues when copying a project +* Defect #13950: Duplicate Lithuanian "error_attachment_too_big" translation keys +* Defect #14015: Ruby hangs when adding a subtask +* Defect #14020: Locking and unlocking a user resets the email notification checkbox +* Defect #14023: Can't delete relation when Redmine runs in a subpath +* Defect #14051: Filtering issues with custom field in date format with NULL(empty) value +* Defect #14178: PDF API broken in version 2.3.1 +* Defect #14186: Project name is not properly escaped in issue filters JSON +* Defect #14242: Project auto generation fails when projects created in the same time +* Defect #14245: Gem::InstallError: nokogiri requires Ruby version >= 1.9.2. +* Defect #14346: Latvian translation for "Log time" +* Feature #12888: Adding markings to emails generated by Private comments +* Feature #14419: Include RUBY_PATCHLEVEL and RUBY_RELEASE_DATE in info.rb +* Patch #14005: Swedish Translation for 2.3-stable +* Patch #14101: Receive IMAP by uid's +* Patch #14103: Disconnect and logout from IMAP after mail receive +* Patch #14145: German translation of x_hours +* Patch #14182: pt-BR translation for 2.3-stable +* Patch #14196: Italian translation for 2.3-stable +* Patch #14221: Translation of x_hours for many languages + +== 2013-05-01 v2.3.1 + +* Defect #12650: Lost text after selection in issue list with IE +* Defect #12684: Hotkey for Issue-Edit doesn't work as expected +* Defect #13405: Commit link title is escaped twice when using "commit:" prefix +* Defect #13541: Can't access SCM when log/production.scm.stderr.log is not writable +* Defect #13579: Datepicker uses Simplified Chinese in Traditional Chinese locale +* Defect #13584: Missing Portuguese jQuery UI date picker +* Defect #13586: Circular loop testing prevents precedes/follows relation between subtasks +* Defect #13618: CSV export of spent time ignores filters and columns selection +* Defect #13630: PDF export generates the issue id twice +* Defect #13644: Diff - Internal Error +* Defect #13712: Fix email rake tasks to also support no_account_notice and default_group options +* Defect #13811: Broken javascript in IE7 ; recurrence of #12195 +* Defect #13823: Trailing comma in javascript files +* Patch #13531: Traditional Chinese translation for 2.3-stable +* Patch #13552: Dutch translations for 2.3-stable +* Patch #13678: Lithuanian translation for 2.3-stable + +== 2013-03-19 v2.3.0 + +* Defect #3107: Issue with two digit year on Logtime +* Defect #3371: Autologin does not work when using openid +* Defect #3676: www. generates broken link in formatted text +* Defect #4700: Adding news does not send notification to all project members +* Defect #5329: Time entries report broken on first week of year +* Defect #8794: Circular loop when using relations and subtasks +* Defect #9475: German Translation "My custom queries" and "Custom queries" +* Defect #9549: Only 100 users are displayed when adding new project members +* Defect #10277: Redmine wikitext URL-into-link creation with hyphen is wrong +* Defect #10364: Custom field float separator in CSV export +* Defect #10930: rake redmine:load_default_data error in 2.0 with SQLServer +* Defect #10977: Redmine shouldn't require all database gems +* Defect #12528: Handle temporary failures gracefully in the external mail handler script +* Defect #12629: Wrong German "label_issues_by" translation +* Defect #12641: Diff outputs become ??? in some non ASCII words. +* Defect #12707: Typo in app/models/tracker.rb +* Defect #12716: Attachment description lost when issue validation fails +* Defect #12735: Negative duration allowed +* Defect #12736: Negative start/due dates allowed +* Defect #12968: Subtasks don't resepect following/precedes +* Defect #13006: Filter "Assignee's group" doesn't work with group assignments +* Defect #13022: Image pointing towards /logout signs out user +* Defect #13059: Custom fields are listed two times in workflow/Fields permission +* Defect #13076: Project overview page shows trackers from subprojects with disabled issue module +* Defect #13119: custom_field_values are not reloaded on #reload +* Defect #13154: After upgrade to 2.2.2 ticket list on some projects fails +* Defect #13188: Forms are not updated after changing the status field without "Add issues" permission +* Defect #13251: Adding a "follows" relation may not refresh relations list +* Defect #13272: translation missing: setting_default_projects_tracker_ids +* Defect #13328: Copying an issue as a child of itself creates an extra issue +* Defect #13335: Autologin does not work with custom autologin cookie name +* Defect #13350: Japanese mistranslation fix +* Feature #824: Add "closed_on" issue field (storing time of last closing) & add it as a column and filter on the issue list. +* Feature #1766: Custom fields should become addable to Spent Time list/report +* Feature #3436: Show relations in Gantt diagram +* Feature #3957: Ajax file upload with progress bar +* Feature #5298: Store attachments in sub directories +* Feature #5605: Subprojects should (optionally) inherit Members from their parent +* Feature #6727: Add/remove issue watchers via REST API +* Feature #7159: Bulk watch/unwatch issues from the context menu +* Feature #8529: Get the API key of the user through REST API +* Feature #8579: Multiple file upload with HTML5 / Drag-and-Drop +* Feature #10191: Add Filters For Spent time's Details and Report +* Feature #10286: Auto-populate fields while creating a new user with LDAP +* Feature #10352: Preview should already display the freshly attached images +* Feature #11498: Add --no-account-notice option for the mail handler script +* Feature #12122: Gantt progress lines (html only) +* Feature #12228: JRuby 1.7.2 support +* Feature #12251: Custom fields: 'Multiple values' should be able to be checked and then unchecked +* Feature #12401: Split "Manage documents" permission into create, edit and delete permissions +* Feature #12542: Group events in the activity view +* Feature #12665: Link to a file in a repository branch +* Feature #12713: Microsoft SQLServer support +* Feature #12787: Remove "Warning - iconv will be deprecated in the future, use String#encode instead." +* Feature #12843: Add links to projects in Group projects list +* Feature #12898: Handle GET /issues/context_menu parameters nicely to prevent returning error 500 to crawlers +* Feature #12992: Make JSONP support optional and disabled by default +* Feature #13174: Raise group name maximum length to 255 characters +* Feature #13175: Possibility to define the default enable trackers when creating a project +* Feature #13329: Ruby 2.0 support +* Feature #13337: Split translation "label_total" +* Feature #13340: Mail handler: option to add created user to default group +* Feature #13341: Mail handler: --no-notification option to disable notifications to the created user +* Patch #7202: Polish translation for v1.0.4 +* Patch #7851: Italian translation for 'issue' +* Patch #9225: Generate project identifier automatically with JavaScript +* Patch #10916: Optimisation in issues relations display +* Patch #12485: Don't force english language for default admin account +* Patch #12499: Use lambda in model scopes +* Patch #12611: Login link unexpected logs you out +* Patch #12626: Updated Japanese translations for button_view and permission_commit_access +* Patch #12640: Russian "about_x_hours" translation change +* Patch #12645: Russian numeric translation +* Patch #12660: Consistent German translation for my page +* Patch #12708: Restructured german translation (Cleanup) +* Patch #12721: Optimize MenuManager a bit +* Patch #12725: Change pourcent to percent (#12724) +* Patch #12754: Updated Japanese translation for notice_account_register_done +* Patch #12788: Copyright for 2013 +* Patch #12806: Serbian translation change +* Patch #12810: Swedish Translation change +* Patch #12910: Plugin settings div should perhaps have 'settings' CSS class +* Patch #12911: Fix 500 error for requests to the settings path for non-configurable plugins +* Patch #12926: Bulgarian translation (r11218) +* Patch #12927: Swedish Translation for r11244 +* Patch #12967: Change Spanish login/logout translations +* Patch #12988: Russian translation for trunk +* Patch #13080: German translation of label_in +* Patch #13098: Small datepicker improvements +* Patch #13152: Locale file for Azerbaijanian language +* Patch #13155: Add login to /users/:id API for current user +* Patch #13173: Put source :rubygems url HTTP secure +* Patch #13190: Bulgarian translation (r11404) +* Patch #13198: Traditional Chinese language file (to r11426) +* Patch #13203: German translation change for follow and precedes is inconsitent +* Patch #13206: Portuguese translation file +* Patch #13246: Some german translation patches +* Patch #13280: German translation (r11478) +* Patch #13301: Performance: avoid querying all memberships in User#roles_for_project +* Patch #13309: Add "tracker-[id]" CSS class to issues +* Patch #13324: fixing some pt-br locales +* Patch #13339: Complete language Vietnamese file +* Patch #13391: Czech translation update +* Patch #13399: Fixed some wrong or confusing translation in Korean locale +* Patch #13414: Bulgarian translation (r11567) +* Patch #13420: Korean translation for 2.3 (r11583) +* Patch #13437: German translation of setting_emails_header +* Patch #13438: English translation +* Patch #13447: German translation - some patches +* Patch #13450: Czech translation +* Patch #13475: fixing some pt-br locales +* Patch #13514: fixing some pt-br locales + == 2013-03-19 v2.2.4 * Upgrade to Rails 3.2.13 diff -r d98d22a98252 -r afce8026aaeb doc/INSTALL --- a/doc/INSTALL Wed May 07 14:15:02 2014 +0100 +++ b/doc/INSTALL Tue Sep 09 09:34:53 2014 +0100 @@ -1,20 +1,21 @@ == Redmine installation Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang +Copyright (C) 2006-2014 Jean-Philippe Lang http://www.redmine.org/ == Requirements -* Ruby 1.8.7, 1.9.2 or 1.9.3 +* Ruby 1.8.7, 1.9.2, 1.9.3 or 2.0.0 * RubyGems * Bundler >= 1.0.21 * A database: * MySQL (tested with MySQL 5.1) * PostgreSQL (tested with PostgreSQL 9.1) - * SQLite3 (tested with SQLite 3.6) + * SQLite3 (tested with SQLite 3.7) + * SQLServer (tested with SQLServer 2012) Optional: * SCM binaries (e.g. svn, git...), for repository browsing (must be available in PATH) @@ -24,25 +25,31 @@ 1. Uncompress the program archive -2. Install the required gems by running: +2. Create an empty utf8 encoded database: "redmine" for example + +3. Configure the database parameters in config/database.yml + for the "production" environment (default database is MySQL and ruby1.9) + + If you're running Redmine with MySQL and ruby1.8, replace the adapter name + with `mysql` + +4. Install the required gems by running: bundle install --without development test If ImageMagick is not installed on your system, you should skip the installation of the rmagick gem using: bundle install --without development test rmagick + Only the gems that are needed by the adapters you've specified in your database + configuration file are actually installed (eg. if your config/database.yml + uses the 'mysql2' adapter, then only the mysql2 gem will be installed). Don't + forget to re-run `bundle install` when you change config/database.yml for using + other database adapters. + If you need to load some gems that are not required by Redmine core (eg. fcgi), you can create a file named Gemfile.local at the root of your redmine directory. It will be loaded automatically when running `bundle install`. -3. Create an empty utf8 encoded database: "redmine" for example - -4. Configure the database parameters in config/database.yml - for the "production" environment (default database is MySQL and ruby1.8) - - If you're running Redmine with MySQL and ruby1.9, replace the adapter name - with `mysql2` - 5. Generate a session store secret Redmine stores session data in cookies by default, which requires diff -r d98d22a98252 -r afce8026aaeb doc/README_FOR_APP --- a/doc/README_FOR_APP Wed May 07 14:15:02 2014 +0100 +++ b/doc/README_FOR_APP Tue Sep 09 09:34:53 2014 +0100 @@ -6,7 +6,7 @@ = License -Copyright (C) 2006-2012 Jean-Philippe Lang +Copyright (C) 2006-2014 Jean-Philippe Lang This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb doc/RUNNING_TESTS --- a/doc/RUNNING_TESTS Wed May 07 14:15:02 2014 +0100 +++ b/doc/RUNNING_TESTS Tue Sep 09 09:34:53 2014 +0100 @@ -9,9 +9,10 @@ Run `rake --tasks test` to see available tests. Run `rake test` to run the entire test suite (except the tests for the -Apache perl module Redmine.pm, see below). +Apache perl module Redmine.pm and Capybara tests, see below). -You can run `ruby test/unit/issue_test.rb` for running a single test case. +You can run `ruby test/unit/issue_test.rb` for running a single test case and +`ruby test/unit/issue_test.rb -n test_create` for running a single test. Before running tests, you need to configure both development and test databases. @@ -58,3 +59,12 @@ If you svn server is not running on localhost, you can use the REDMINE_TEST_DAV_SERVER environment variable to specify another host. + +Running Capybara tests +====================== + +You need to have PhantomJS WebDriver listening on port 4444: +`phantomjs --webdriver 4444` + +Capybara tests can be run with: +`rake test:ui` diff -r d98d22a98252 -r afce8026aaeb doc/UPGRADING --- a/doc/UPGRADING Wed May 07 14:15:02 2014 +0100 +++ b/doc/UPGRADING Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ == Redmine upgrade Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang +Copyright (C) 2006-2014 Jean-Philippe Lang http://www.redmine.org/ @@ -32,6 +32,16 @@ of the rmagick gem using: bundle install --without development test rmagick + Only the gems that are needed by the adapters you've specified in your database + configuration file are actually installed (eg. if your config/database.yml + uses the 'mysql2' adapter, then only the mysql2 gem will be installed). Don't + forget to re-run `bundle install` when you change config/database.yml for using + other database adapters. + + If you need to load some gems that are not required by Redmine core (eg. fcgi), + you can create a file named Gemfile.local at the root of your redmine directory. + It will be loaded automatically when running `bundle install`. + 6. Generate a session store secret Redmine stores session data in cookies by default, which requires diff -r d98d22a98252 -r afce8026aaeb extra/mail_handler/rdm-mailhandler.rb --- a/extra/mail_handler/rdm-mailhandler.rb Wed May 07 14:15:02 2014 +0100 +++ b/extra/mail_handler/rdm-mailhandler.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,20 @@ #!/usr/bin/env ruby +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. require 'net/http' require 'net/https' @@ -23,9 +39,10 @@ end class RedmineMailHandler - VERSION = '0.2.1' + VERSION = '0.2.3' - attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :no_permission_check, :url, :key, :no_check_certificate + attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :default_group, :no_permission_check, + :url, :key, :no_check_certificate, :no_account_notice, :no_notification def initialize self.issue_attributes = {} @@ -33,28 +50,36 @@ optparse = OptionParser.new do |opts| opts.banner = "Usage: rdm-mailhandler.rb [options] --url= --key=" opts.separator("") - opts.separator("Reads an email from standard input and forward it to a Redmine server through a HTTP request.") + opts.separator("Reads an email from standard input and forwards it to a Redmine server through a HTTP request.") opts.separator("") opts.separator("Required arguments:") opts.on("-u", "--url URL", "URL of the Redmine server") {|v| self.url = v} opts.on("-k", "--key KEY", "Redmine API key") {|v| self.key = v} opts.separator("") opts.separator("General options:") + opts.on("--no-permission-check", "disable permission checking when receiving", + "the email") {self.no_permission_check = '1'} + opts.on("--key-file FILE", "full path to a file that contains your Redmine", + "API key (use this option instead of --key if", + "you don't want the key to appear in the command", + "line)") {|v| read_key_from_file(v)} + opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true} + opts.on("-h", "--help", "show this help") {puts opts; exit 1} + opts.on("-v", "--verbose", "show extra information") {self.verbose = true} + opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit} + opts.separator("") + opts.separator("User creation options:") opts.on("--unknown-user ACTION", "how to handle emails from an unknown user", "ACTION can be one of the following values:", "* ignore: email is ignored (default)", "* accept: accept as anonymous user", "* create: create a user account") {|v| self.unknown_user = v} - opts.on("--no-permission-check", "disable permission checking when receiving", - "the email") {self.no_permission_check = '1'} - opts.on("--key-file FILE", "path to a file that contains the Redmine", - "API key (use this option instead of --key", - "if you don't the key to appear in the", - "command line)") {|v| read_key_from_file(v)} - opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true} - opts.on("-h", "--help", "show this help") {puts opts; exit 1} - opts.on("-v", "--verbose", "show extra information") {self.verbose = true} - opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit} + opts.on("--default-group GROUP", "add created user to GROUP (none by default)", + "GROUP can be a comma separated list of groups") { |v| self.default_group = v} + opts.on("--no-account-notice", "don't send account information to the newly", + "created user") { |v| self.no_account_notice = '1'} + opts.on("--no-notification", "disable email notifications for the created", + "user") { |v| self.no_notification = '1'} opts.separator("") opts.separator("Issue attributes control options:") opts.on("-p", "--project PROJECT", "identifier of the target project") {|v| self.issue_attributes['project'] = v} @@ -67,7 +92,7 @@ "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v} opts.separator("") opts.separator("Examples:") - opts.separator("No project specified. Emails MUST contain the 'Project' keyword:") + opts.separator("No project specified, emails MUST contain the 'Project' keyword:") opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret") opts.separator("") opts.separator("Fixed project and default tracker specified, but emails can override") @@ -95,11 +120,19 @@ data = { 'key' => key, 'email' => email, 'allow_override' => allow_override, 'unknown_user' => unknown_user, + 'default_group' => default_group, + 'no_account_notice' => no_account_notice, + 'no_notification' => no_notification, 'no_permission_check' => no_permission_check} issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value } debug "Posting to #{uri}..." - response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate) + begin + response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate) + rescue SystemCallError => e # connection refused, etc. + warn "An error occured while contacting your Redmine server: #{e.message}" + return 75 # temporary failure + end debug "Response received: #{response.code}" case response.code.to_i diff -r d98d22a98252 -r afce8026aaeb extra/sample_plugin/test/integration/routing_test.rb --- a/extra/sample_plugin/test/integration/routing_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/extra/sample_plugin/test/integration/routing_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ require File.expand_path(File.dirname(__FILE__) + '../../../../../test/test_helper') -class SamplePluginRoutingTest < ActionController::IntegrationTest +class SamplePluginRoutingTest < ActionDispatch::IntegrationTest def test_example assert_routing( { :method => 'get', :path => "/projects/1234/hello" }, diff -r d98d22a98252 -r afce8026aaeb extra/svn/Redmine.pm --- a/extra/svn/Redmine.pm Wed May 07 14:15:02 2014 +0100 +++ b/extra/svn/Redmine.pm Tue Sep 09 09:34:53 2014 +0100 @@ -388,9 +388,9 @@ $sth->execute($project_id); my $ret = 0; if (my @row = $sth->fetchrow_array) { - if ($row[0] eq "1" || $row[0] eq "t") { - $ret = 1; - } + if ($row[0] eq "1" || $row[0] eq "t") { + $ret = 1; + } } $sth->finish(); undef $sth; @@ -467,9 +467,9 @@ } unless ($auth_source_id) { - my $method = $r->method; + my $method = $r->method; my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest); - if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { + if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { $ret = 1; last; } diff -r d98d22a98252 -r afce8026aaeb lib/SVG/Graph/Graph.rb --- a/lib/SVG/Graph/Graph.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/SVG/Graph/Graph.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,8 +1,7 @@ begin require 'zlib' - @@__have_zlib = true rescue - @@__have_zlib = false + # Zlib not available end require 'rexml/document' @@ -211,7 +210,7 @@ @doc.write( data, 0 ) if @config[:compress] - if @@__have_zlib + if Object.const_defined?(:Zlib) inp, out = IO.pipe gz = Zlib::GzipWriter.new( out ) gz.write data diff -r d98d22a98252 -r afce8026aaeb lib/generators/redmine_plugin/templates/en_rails_i18n.yml --- a/lib/generators/redmine_plugin/templates/en_rails_i18n.yml Wed May 07 14:15:02 2014 +0100 +++ b/lib/generators/redmine_plugin/templates/en_rails_i18n.yml Tue Sep 09 09:34:53 2014 +0100 @@ -1,3 +1,3 @@ # English strings go here for Rails i18n en: - my_label: "My label" + # my_label: "My label" diff -r d98d22a98252 -r afce8026aaeb lib/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb --- a/lib/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -57,26 +57,26 @@ scope = self if from && to - scope = scope.scoped(:conditions => ["#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to]) + scope = scope.where("#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to) end if options[:author] return [] if provider_options[:author_key].nil? - scope = scope.scoped(:conditions => ["#{provider_options[:author_key]} = ?", options[:author].id]) + scope = scope.where("#{provider_options[:author_key]} = ?", options[:author].id) end if options[:limit] # id and creation time should be in same order in most cases - scope = scope.scoped(:order => "#{table_name}.id DESC", :limit => options[:limit]) + scope = scope.reorder("#{table_name}.id DESC").limit(options[:limit]) end if provider_options.has_key?(:permission) - scope = scope.scoped(:conditions => Project.allowed_to_condition(user, provider_options[:permission] || :view_project, options)) + scope = scope.where(Project.allowed_to_condition(user, provider_options[:permission] || :view_project, options)) elsif respond_to?(:visible) scope = scope.visible(user, options) else ActiveSupport::Deprecation.warn "acts_as_activity_provider with implicit :permission option is deprecated. Add a visible scope to the #{self.name} model or use explicit :permission option." - scope = scope.scoped(:conditions => Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym, options)) + scope = scope.where(Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym, options)) end scope.all(provider_options[:find_options].dup) diff -r d98d22a98252 -r afce8026aaeb lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb --- a/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -78,6 +78,7 @@ end if attachments.is_a?(Array) attachments.each do |attachment| + next unless attachment.is_a?(Hash) a = nil if file = attachment['file'] next unless file.size > 0 diff -r d98d22a98252 -r afce8026aaeb lib/plugins/acts_as_customizable/lib/acts_as_customizable.rb --- a/lib/plugins/acts_as_customizable/lib/acts_as_customizable.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/acts_as_customizable/lib/acts_as_customizable.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -32,6 +32,7 @@ :order => "#{CustomField.table_name}.position", :dependent => :delete_all, :validate => false + send :include, Redmine::Acts::Customizable::InstanceMethods validate :validate_custom_field_values after_save :save_custom_field_values @@ -41,11 +42,11 @@ module InstanceMethods def self.included(base) base.extend ClassMethods + base.send :alias_method_chain, :reload, :custom_fields end def available_custom_fields - CustomField.find(:all, :conditions => "type = '#{self.class.name}CustomField'", - :order => 'position') + CustomField.where("type = '#{self.class.name}CustomField'").sorted.all end # Sets the values of the object's custom fields @@ -153,6 +154,12 @@ @custom_field_values_changed = true end + def reload_with_custom_fields(*args) + @custom_field_values = nil + @custom_field_values_changed = false + reload_without_custom_fields(*args) + end + module ClassMethods end end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/acts_as_event/lib/acts_as_event.rb --- a/lib/plugins/acts_as_event/lib/acts_as_event.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/acts_as_event/lib/acts_as_event.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -63,6 +63,11 @@ event_datetime.to_date end + def event_group + group = event_options[:group] ? send(event_options[:group]) : self + group || self + end + def event_url(options = {}) option = event_options[:url] if option.is_a?(Proc) diff -r d98d22a98252 -r afce8026aaeb lib/plugins/acts_as_list/lib/active_record/acts/list.rb --- a/lib/plugins/acts_as_list/lib/active_record/acts/list.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/acts_as_list/lib/active_record/acts/list.rb Tue Sep 09 09:34:53 2014 +0100 @@ -177,17 +177,17 @@ # Return the next higher item in the list. def higher_item return nil unless in_list? - acts_as_list_class.find(:first, :conditions => + acts_as_list_class.where( "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}" - ) + ).first end # Return the next lower item in the list. def lower_item return nil unless in_list? - acts_as_list_class.find(:first, :conditions => + acts_as_list_class.where( "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}" - ) + ).first end # Test if this record is in a list diff -r d98d22a98252 -r afce8026aaeb lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb --- a/lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -72,14 +72,8 @@ tokens = [] << tokens unless tokens.is_a?(Array) projects = [] << projects unless projects.nil? || projects.is_a?(Array) - find_options = {:include => searchable_options[:include]} - find_options[:order] = "#{searchable_options[:order_column]} " + (options[:before] ? 'DESC' : 'ASC') - limit_options = {} limit_options[:limit] = options[:limit] if options[:limit] - if options[:offset] - limit_options[:conditions] = "(#{searchable_options[:date_column]} " + (options[:before] ? '<' : '>') + "'#{connection.quoted_date(options[:offset])}')" - end columns = searchable_options[:columns] columns = columns[0..0] if options[:titles_only] @@ -87,23 +81,21 @@ token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"} if !options[:titles_only] && searchable_options[:search_custom_fields] - searchable_custom_field_ids = CustomField.find(:all, - :select => 'id', - :conditions => { :type => "#{self.name}CustomField", - :searchable => true }).collect(&:id) - if searchable_custom_field_ids.any? - custom_field_sql = "#{table_name}.id IN (SELECT customized_id FROM #{CustomValue.table_name}" + + searchable_custom_fields = CustomField.where(:type => "#{self.name}CustomField", :searchable => true) + searchable_custom_fields.each do |field| + sql = "#{table_name}.id IN (SELECT customized_id FROM #{CustomValue.table_name}" + " WHERE customized_type='#{self.name}' AND customized_id=#{table_name}.id AND LOWER(value) LIKE ?" + - " AND #{CustomValue.table_name}.custom_field_id IN (#{searchable_custom_field_ids.join(',')}))" - token_clauses << custom_field_sql + " AND #{CustomValue.table_name}.custom_field_id = #{field.id})" + + " AND #{field.visibility_by_project_condition(searchable_options[:project_key], user)}" + token_clauses << sql end end sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') - find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] + tokens_conditions = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] - scope = self + scope = self.scoped project_conditions = [] if searchable_options.has_key?(:permission) project_conditions << Project.allowed_to_condition(user, searchable_options[:permission] || :view_project) @@ -120,9 +112,19 @@ results = [] results_count = 0 - scope = scope.scoped({:conditions => project_conditions}).scoped(find_options) - results_count = scope.count(:all) - results = scope.find(:all, limit_options) + scope = scope. + includes(searchable_options[:include]). + order("#{searchable_options[:order_column]} " + (options[:before] ? 'DESC' : 'ASC')). + where(project_conditions). + where(tokens_conditions) + + results_count = scope.count + + scope_with_limit = scope.limit(options[:limit]) + if options[:offset] + scope_with_limit = scope_with_limit.where("#{searchable_options[:date_column]} #{options[:before] ? '<' : '>'} ?", options[:offset]) + end + results = scope_with_limit.all [results, results_count] end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/acts_as_tree/lib/active_record/acts/tree.rb --- a/lib/plugins/acts_as_tree/lib/active_record/acts/tree.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/acts_as_tree/lib/active_record/acts/tree.rb Tue Sep 09 09:34:53 2014 +0100 @@ -46,17 +46,9 @@ belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache] has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => configuration[:dependent] - class_eval <<-EOV - include ActiveRecord::Acts::Tree::InstanceMethods + scope :roots, where("#{configuration[:foreign_key]} IS NULL").order(configuration[:order]) - def self.roots - find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) - end - - def self.root - find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) - end - EOV + send :include, ActiveRecord::Acts::Tree::InstanceMethods end end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb --- a/lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb Tue Sep 09 09:34:53 2014 +0100 @@ -216,12 +216,12 @@ has_many :versions, version_association_options do # finds earliest version of this record def earliest - @earliest ||= find(:first, :order => 'version') + @earliest ||= order('version').first end # find latest version of this record def latest - @latest ||= find(:first, :order => 'version desc') + @latest ||= order('version desc').first end end before_save :set_new_version @@ -248,14 +248,16 @@ def self.reloadable? ; false ; end # find first version before the given version def self.before(version) - find :first, :order => 'version desc', - :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version] + order('version desc'). + where("#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version). + first end # find first version after the given version. def self.after(version) - find :first, :order => 'version', - :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version] + order('version'). + where("#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version). + first end def previous @@ -435,7 +437,7 @@ # Gets the next available version for the current record, or 1 for a new record def next_version return 1 if new_record? - (versions.calculate(:max, :version) || 0) + 1 + (versions.maximum('version') || 0) + 1 end # clears current changed attributes. Called after save. @@ -467,9 +469,9 @@ # Finds versions of a specific model. Takes an options hash like find def find_versions(id, options = {}) - versioned_class.find :all, { + versioned_class.all({ :conditions => ["#{versioned_foreign_key} = ?", id], - :order => 'version' }.merge(options) + :order => 'version' }.merge(options)) end # Returns an array of columns that are versioned. See non_versioned_columns diff -r d98d22a98252 -r afce8026aaeb lib/plugins/acts_as_watchable/lib/acts_as_watchable.rb --- a/lib/plugins/acts_as_watchable/lib/acts_as_watchable.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/acts_as_watchable/lib/acts_as_watchable.rb Tue Sep 09 09:34:53 2014 +0100 @@ -14,8 +14,8 @@ has_many :watcher_users, :through => :watchers, :source => :user, :validate => false scope :watched_by, lambda { |user_id| - { :include => :watchers, - :conditions => ["#{Watcher.table_name}.user_id = ?", user_id] } + joins(:watchers). + where("#{Watcher.table_name}.user_id = ?", user_id) } attr_protected :watcher_ids, :watcher_user_ids end @@ -46,7 +46,7 @@ # Removes user from the watchers list def remove_watcher(user) return nil unless user && user.is_a?(User) - Watcher.delete_all "watchable_type = '#{self.class}' AND watchable_id = #{self.id} AND user_id = #{user.id}" + watchers.where(:user_id => user.id).delete_all end # Adds/removes watcher diff -r d98d22a98252 -r afce8026aaeb lib/plugins/awesome_nested_set/lib/awesome_nested_set/awesome_nested_set.rb --- a/lib/plugins/awesome_nested_set/lib/awesome_nested_set/awesome_nested_set.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/awesome_nested_set/lib/awesome_nested_set/awesome_nested_set.rb Tue Sep 09 09:34:53 2014 +0100 @@ -413,7 +413,7 @@ # on creation, set automatically lft and rgt to the end of the tree def set_default_left_and_right - highest_right_row = nested_set_scope(:order => "#{quoted_right_column_name} desc").find(:first, :limit => 1,:lock => true ) + highest_right_row = nested_set_scope(:order => "#{quoted_right_column_name} desc").limit(1).lock(true).first maxright = highest_right_row ? (highest_right_row[right_column_name] || 0) : 0 # adds the new node to the right of all existing nodes self[left_column_name] = maxright + 1 @@ -443,11 +443,11 @@ in_tenacious_transaction do reload_nested_set # select the rows in the model that extend past the deletion point and apply a lock - self.class.base_class.find(:all, - :select => "id", - :conditions => ["#{quoted_left_column_name} >= ?", left], - :lock => true - ) + self.class.base_class. + select("id"). + where("#{quoted_left_column_name} >= ?", left). + lock(true). + all if acts_as_nested_set_options[:dependent] == :destroy descendants.each do |model| diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/CHANGELOG --- a/lib/plugins/classic_pagination/CHANGELOG Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,152 +0,0 @@ -* Exported the changelog of Pagination code for historical reference. - -* Imported some patches from Rails Trac (others closed as "wontfix"): - #8176, #7325, #7028, #4113. Documentation is much cleaner now and there - are some new unobtrusive features! - -* Extracted Pagination from Rails trunk (r6795) - -# -# ChangeLog for /trunk/actionpack/lib/action_controller/pagination.rb -# -# Generated by Trac 0.10.3 -# 05/20/07 23:48:02 -# - -09/03/06 23:28:54 david [4953] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Docs and deprecation - -08/07/06 12:40:14 bitsweat [4715] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Deprecate direct usage of @params. Update ActionView::Base for - instance var deprecation. - -06/21/06 02:16:11 rick [4476] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Fix indent in pagination documentation. Closes #4990. [Kevin Clark] - -04/25/06 17:42:48 marcel [4268] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Remove all remaining references to @params in the documentation. - -03/16/06 06:38:08 rick [3899] - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - trivial documentation patch for #pagination_links [Francois - Beausoleil] closes #4258 - -02/20/06 03:15:22 david [3620] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/test/activerecord/pagination_test.rb (modified) - * trunk/activerecord/CHANGELOG (modified) - * trunk/activerecord/lib/active_record/base.rb (modified) - * trunk/activerecord/test/base_test.rb (modified) - Added :count option to pagination that'll make it possible for the - ActiveRecord::Base.count call to using something else than * for the - count. Especially important for count queries using DISTINCT #3839 - [skaes]. Added :select option to Base.count that'll allow you to - select something else than * to be counted on. Especially important - for count queries using DISTINCT (closes #3839) [skaes]. - -02/09/06 09:17:40 nzkoz [3553] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/test/active_record_unit.rb (added) - * trunk/actionpack/test/activerecord (added) - * trunk/actionpack/test/activerecord/active_record_assertions_test.rb (added) - * trunk/actionpack/test/activerecord/pagination_test.rb (added) - * trunk/actionpack/test/controller/active_record_assertions_test.rb (deleted) - * trunk/actionpack/test/fixtures/companies.yml (added) - * trunk/actionpack/test/fixtures/company.rb (added) - * trunk/actionpack/test/fixtures/db_definitions (added) - * trunk/actionpack/test/fixtures/db_definitions/sqlite.sql (added) - * trunk/actionpack/test/fixtures/developer.rb (added) - * trunk/actionpack/test/fixtures/developers_projects.yml (added) - * trunk/actionpack/test/fixtures/developers.yml (added) - * trunk/actionpack/test/fixtures/project.rb (added) - * trunk/actionpack/test/fixtures/projects.yml (added) - * trunk/actionpack/test/fixtures/replies.yml (added) - * trunk/actionpack/test/fixtures/reply.rb (added) - * trunk/actionpack/test/fixtures/topic.rb (added) - * trunk/actionpack/test/fixtures/topics.yml (added) - * Fix pagination problems when using include - * Introduce Unit Tests for pagination - * Allow count to work with :include by using count distinct. - - [Kevin Clark & Jeremy Hopple] - -11/05/05 02:10:29 bitsweat [2878] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Update paginator docs. Closes #2744. - -10/16/05 15:42:03 minam [2649] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Update/clean up AP documentation (rdoc) - -08/31/05 00:13:10 ulysses [2078] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Add option to specify the singular name used by pagination. Closes - #1960 - -08/23/05 14:24:15 minam [2041] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Add support for :include with pagination (subject to existing - constraints for :include with :limit and :offset) #1478 - [michael@schubert.cx] - -07/15/05 20:27:38 david [1839] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - More pagination speed #1334 [Stefan Kaes] - -07/14/05 08:02:01 david [1832] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - * trunk/actionpack/test/controller/addresses_render_test.rb (modified) - Made pagination faster #1334 [Stefan Kaes] - -04/13/05 05:40:22 david [1159] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/activerecord/lib/active_record/base.rb (modified) - Fixed pagination to work with joins #1034 [scott@sigkill.org] - -04/02/05 09:11:17 david [1067] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_controller/scaffolding.rb (modified) - * trunk/actionpack/lib/action_controller/templates/scaffolds/list.rhtml (modified) - * trunk/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb (modified) - * trunk/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml (modified) - Added pagination for scaffolding (10 items per page) #964 - [mortonda@dgrmm.net] - -03/31/05 14:46:11 david [1048] - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Improved the message display on the exception handler pages #963 - [Johan Sorensen] - -03/27/05 00:04:07 david [1017] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Fixed that pagination_helper would ignore :params #947 [Sebastian - Kanthak] - -03/22/05 13:09:44 david [976] - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Fixed documentation and prepared for 0.11.0 release - -03/21/05 14:35:36 david [967] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Tweaked the documentation - -03/20/05 23:12:05 david [949] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller.rb (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (added) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (added) - * trunk/activesupport/lib/active_support/core_ext/kernel.rb (added) - Added pagination support through both a controller and helper add-on - #817 [Sam Stephenson] diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/README --- a/lib/plugins/classic_pagination/README Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -Pagination -========== - -To install: - - script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination - -This code was extracted from Rails trunk after the release 1.2.3. -WARNING: this code is dead. It is unmaintained, untested and full of cruft. - -There is a much better pagination plugin called will_paginate. -Install it like this and glance through the README: - - script/plugin install svn://errtheblog.com/svn/plugins/will_paginate - -It doesn't have the same API, but is in fact much nicer. You can -have both plugins installed until you change your controller/view code that -handles pagination. Then, simply uninstall classic_pagination. diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/Rakefile --- a/lib/plugins/classic_pagination/Rakefile Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -require 'rake' -require 'rake/testtask' -require 'rake/rdoctask' - -desc 'Default: run unit tests.' -task :default => :test - -desc 'Test the classic_pagination plugin.' -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.pattern = 'test/**/*_test.rb' - t.verbose = true -end - -desc 'Generate documentation for the classic_pagination plugin.' -Rake::RDocTask.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'Pagination' - rdoc.options << '--line-numbers' << '--inline-source' - rdoc.rdoc_files.include('README') - rdoc.rdoc_files.include('lib/**/*.rb') -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/init.rb --- a/lib/plugins/classic_pagination/init.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -#-- -# Copyright (c) 2004-2006 David Heinemeier Hansson -# -# 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 'pagination' -require 'pagination_helper' - -ActionController::Base.class_eval do - include ActionController::Pagination -end - -ActionView::Base.class_eval do - include ActionView::Helpers::PaginationHelper -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/install.rb --- a/lib/plugins/classic_pagination/install.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -puts "\n\n" + File.read(File.dirname(__FILE__) + '/README') diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/lib/pagination.rb --- a/lib/plugins/classic_pagination/lib/pagination.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,405 +0,0 @@ -module ActionController - # === Action Pack pagination for Active Record collections - # - # The Pagination module aids in the process of paging large collections of - # Active Record objects. It offers macro-style automatic fetching of your - # model for multiple views, or explicit fetching for single actions. And if - # the magic isn't flexible enough for your needs, you can create your own - # paginators with a minimal amount of code. - # - # The Pagination module can handle as much or as little as you wish. In the - # controller, have it automatically query your model for pagination; or, - # if you prefer, create Paginator objects yourself. - # - # Pagination is included automatically for all controllers. - # - # For help rendering pagination links, see - # ActionView::Helpers::PaginationHelper. - # - # ==== Automatic pagination for every action in a controller - # - # class PersonController < ApplicationController - # model :person - # - # paginate :people, :order => 'last_name, first_name', - # :per_page => 20 - # - # # ... - # end - # - # Each action in this controller now has access to a @people - # instance variable, which is an ordered collection of model objects for the - # current page (at most 20, sorted by last name and first name), and a - # @person_pages Paginator instance. The current page is determined - # by the params[:page] variable. - # - # ==== Pagination for a single action - # - # def list - # @person_pages, @people = - # paginate :people, :order => 'last_name, first_name' - # end - # - # Like the previous example, but explicitly creates @person_pages - # and @people for a single action, and uses the default of 10 items - # per page. - # - # ==== Custom/"classic" pagination - # - # def list - # @person_pages = Paginator.new self, Person.count, 10, params[:page] - # @people = Person.find :all, :order => 'last_name, first_name', - # :limit => @person_pages.items_per_page, - # :offset => @person_pages.current.offset - # end - # - # Explicitly creates the paginator from the previous example and uses - # Paginator#to_sql to retrieve @people from the model. - # - module Pagination - unless const_defined?(:OPTIONS) - # A hash holding options for controllers using macro-style pagination - OPTIONS = Hash.new - - # The default options for pagination - DEFAULT_OPTIONS = { - :class_name => nil, - :singular_name => nil, - :per_page => 10, - :conditions => nil, - :order_by => nil, - :order => nil, - :join => nil, - :joins => nil, - :count => nil, - :include => nil, - :select => nil, - :group => nil, - :parameter => 'page' - } - else - DEFAULT_OPTIONS[:group] = nil - end - - def self.included(base) #:nodoc: - super - base.extend(ClassMethods) - end - - def self.validate_options!(collection_id, options, in_action) #:nodoc: - options.merge!(DEFAULT_OPTIONS) {|key, old, new| old} - - valid_options = DEFAULT_OPTIONS.keys - valid_options << :actions unless in_action - - unknown_option_keys = options.keys - valid_options - raise ActionController::ActionControllerError, - "Unknown options: #{unknown_option_keys.join(', ')}" unless - unknown_option_keys.empty? - - options[:singular_name] ||= ActiveSupport::Inflector.singularize(collection_id.to_s) - options[:class_name] ||= ActiveSupport::Inflector.camelize(options[:singular_name]) - end - - # Returns a paginator and a collection of Active Record model instances - # for the paginator's current page. This is designed to be used in a - # single action; to automatically paginate multiple actions, consider - # ClassMethods#paginate. - # - # +options+ are: - # :singular_name:: the singular name to use, if it can't be inferred by singularizing the collection name - # :class_name:: the class name to use, if it can't be inferred by - # camelizing the singular name - # :per_page:: the maximum number of items to include in a - # single page. Defaults to 10 - # :conditions:: optional conditions passed to Model.find(:all, *params) and - # Model.count - # :order:: optional order parameter passed to Model.find(:all, *params) - # :order_by:: (deprecated, used :order) optional order parameter passed to Model.find(:all, *params) - # :joins:: optional joins parameter passed to Model.find(:all, *params) - # and Model.count - # :join:: (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params) - # and Model.count - # :include:: optional eager loading parameter passed to Model.find(:all, *params) - # and Model.count - # :select:: :select parameter passed to Model.find(:all, *params) - # - # :count:: parameter passed as :select option to Model.count(*params) - # - # :group:: :group parameter passed to Model.find(:all, *params). It forces the use of DISTINCT instead of plain COUNT to come up with the total number of records - # - def paginate(collection_id, options={}) - Pagination.validate_options!(collection_id, options, true) - paginator_and_collection_for(collection_id, options) - end - - # These methods become class methods on any controller - module ClassMethods - # Creates a +before_filter+ which automatically paginates an Active - # Record model for all actions in a controller (or certain actions if - # specified with the :actions option). - # - # +options+ are the same as PaginationHelper#paginate, with the addition - # of: - # :actions:: an array of actions for which the pagination is - # active. Defaults to +nil+ (i.e., every action) - def paginate(collection_id, options={}) - Pagination.validate_options!(collection_id, options, false) - module_eval do - before_filter :create_paginators_and_retrieve_collections - OPTIONS[self] ||= Hash.new - OPTIONS[self][collection_id] = options - end - end - end - - def create_paginators_and_retrieve_collections #:nodoc: - Pagination::OPTIONS[self.class].each do |collection_id, options| - next unless options[:actions].include? action_name if - options[:actions] - - paginator, collection = - paginator_and_collection_for(collection_id, options) - - paginator_name = "@#{options[:singular_name]}_pages" - self.instance_variable_set(paginator_name, paginator) - - collection_name = "@#{collection_id.to_s}" - self.instance_variable_set(collection_name, collection) - end - end - - # Returns the total number of items in the collection to be paginated for - # the +model+ and given +conditions+. Override this method to implement a - # custom counter. - def count_collection_for_pagination(model, options) - model.count(:conditions => options[:conditions], - :joins => options[:join] || options[:joins], - :include => options[:include], - :select => (options[:group] ? "DISTINCT #{options[:group]}" : options[:count])) - end - - # Returns a collection of items for the given +model+ and +options[conditions]+, - # ordered by +options[order]+, for the current page in the given +paginator+. - # Override this method to implement a custom finder. - def find_collection_for_pagination(model, options, paginator) - model.find(:all, :conditions => options[:conditions], - :order => options[:order_by] || options[:order], - :joins => options[:join] || options[:joins], :include => options[:include], - :select => options[:select], :limit => options[:per_page], - :group => options[:group], :offset => paginator.current.offset) - end - - protected :create_paginators_and_retrieve_collections, - :count_collection_for_pagination, - :find_collection_for_pagination - - def paginator_and_collection_for(collection_id, options) #:nodoc: - klass = options[:class_name].constantize - page = params[options[:parameter]] - count = count_collection_for_pagination(klass, options) - paginator = Paginator.new(self, count, options[:per_page], page) - collection = find_collection_for_pagination(klass, options, paginator) - - return paginator, collection - end - - private :paginator_and_collection_for - - # A class representing a paginator for an Active Record collection. - class Paginator - include Enumerable - - # Creates a new Paginator on the given +controller+ for a set of items - # of size +item_count+ and having +items_per_page+ items per page. - # Raises ArgumentError if items_per_page is out of bounds (i.e., less - # than or equal to zero). The page CGI parameter for links defaults to - # "page" and can be overridden with +page_parameter+. - def initialize(controller, item_count, items_per_page, current_page=1) - raise ArgumentError, 'must have at least one item per page' if - items_per_page <= 0 - - @controller = controller - @item_count = item_count || 0 - @items_per_page = items_per_page - @pages = {} - - self.current_page = current_page - end - attr_reader :controller, :item_count, :items_per_page - - # Sets the current page number of this paginator. If +page+ is a Page - # object, its +number+ attribute is used as the value; if the page does - # not belong to this Paginator, an ArgumentError is raised. - def current_page=(page) - if page.is_a? Page - raise ArgumentError, 'Page/Paginator mismatch' unless - page.paginator == self - end - page = page.to_i - @current_page_number = has_page_number?(page) ? page : 1 - end - - # Returns a Page object representing this paginator's current page. - def current_page - @current_page ||= self[@current_page_number] - end - alias current :current_page - - # Returns a new Page representing the first page in this paginator. - def first_page - @first_page ||= self[1] - end - alias first :first_page - - # Returns a new Page representing the last page in this paginator. - def last_page - @last_page ||= self[page_count] - end - alias last :last_page - - # Returns the number of pages in this paginator. - def page_count - @page_count ||= @item_count.zero? ? 1 : - (q,r=@item_count.divmod(@items_per_page); r==0? q : q+1) - end - - alias length :page_count - - # Returns true if this paginator contains the page of index +number+. - def has_page_number?(number) - number >= 1 and number <= page_count - end - - # Returns a new Page representing the page with the given index - # +number+. - def [](number) - @pages[number] ||= Page.new(self, number) - end - - # Successively yields all the paginator's pages to the given block. - def each(&block) - page_count.times do |n| - yield self[n+1] - end - end - - # A class representing a single page in a paginator. - class Page - include Comparable - - # Creates a new Page for the given +paginator+ with the index - # +number+. If +number+ is not in the range of valid page numbers or - # is not a number at all, it defaults to 1. - def initialize(paginator, number) - @paginator = paginator - @number = number.to_i - @number = 1 unless @paginator.has_page_number? @number - end - attr_reader :paginator, :number - alias to_i :number - - # Compares two Page objects and returns true when they represent the - # same page (i.e., their paginators are the same and they have the - # same page number). - def ==(page) - return false if page.nil? - @paginator == page.paginator and - @number == page.number - end - - # Compares two Page objects and returns -1 if the left-hand page comes - # before the right-hand page, 0 if the pages are equal, and 1 if the - # left-hand page comes after the right-hand page. Raises ArgumentError - # if the pages do not belong to the same Paginator object. - def <=>(page) - raise ArgumentError unless @paginator == page.paginator - @number <=> page.number - end - - # Returns the item offset for the first item in this page. - def offset - @paginator.items_per_page * (@number - 1) - end - - # Returns the number of the first item displayed. - def first_item - offset + 1 - end - - # Returns the number of the last item displayed. - def last_item - [@paginator.items_per_page * @number, @paginator.item_count].min - end - - # Returns true if this page is the first page in the paginator. - def first? - self == @paginator.first - end - - # Returns true if this page is the last page in the paginator. - def last? - self == @paginator.last - end - - # Returns a new Page object representing the page just before this - # page, or nil if this is the first page. - def previous - if first? then nil else @paginator[@number - 1] end - end - - # Returns a new Page object representing the page just after this - # page, or nil if this is the last page. - def next - if last? then nil else @paginator[@number + 1] end - end - - # Returns a new Window object for this page with the specified - # +padding+. - def window(padding=2) - Window.new(self, padding) - end - - # Returns the limit/offset array for this page. - def to_sql - [@paginator.items_per_page, offset] - end - - def to_param #:nodoc: - @number.to_s - end - end - - # A class for representing ranges around a given page. - class Window - # Creates a new Window object for the given +page+ with the specified - # +padding+. - def initialize(page, padding=2) - @paginator = page.paginator - @page = page - self.padding = padding - end - attr_reader :paginator, :page - - # Sets the window's padding (the number of pages on either side of the - # window page). - def padding=(padding) - @padding = padding < 0 ? 0 : padding - # Find the beginning and end pages of the window - @first = @paginator.has_page_number?(@page.number - @padding) ? - @paginator[@page.number - @padding] : @paginator.first - @last = @paginator.has_page_number?(@page.number + @padding) ? - @paginator[@page.number + @padding] : @paginator.last - end - attr_reader :padding, :first, :last - - # Returns an array of Page objects in the current window. - def pages - (@first.number..@last.number).to_a.collect! {|n| @paginator[n]} - end - alias to_a :pages - end - end - - end -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/lib/pagination_helper.rb --- a/lib/plugins/classic_pagination/lib/pagination_helper.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -module ActionView - module Helpers - # Provides methods for linking to ActionController::Pagination objects using a simple generator API. You can optionally - # also build your links manually using ActionView::Helpers::AssetHelper#link_to like so: - # - # <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %> - # <%= link_to "Next page", { :page => paginator.current.next } if paginator.current.next %> - module PaginationHelper - unless const_defined?(:DEFAULT_OPTIONS) - DEFAULT_OPTIONS = { - :name => :page, - :window_size => 2, - :always_show_anchors => true, - :link_to_current_page => false, - :params => {} - } - end - - # Creates a basic HTML link bar for the given +paginator+. Links will be created - # for the next and/or previous page and for a number of other pages around the current - # pages position. The +html_options+ hash is passed to +link_to+ when the links are created. - # - # ==== Options - # :name:: the routing name for this paginator - # (defaults to +page+) - # :prefix:: prefix for pagination links - # (i.e. Older Pages: 1 2 3 4) - # :suffix:: suffix for pagination links - # (i.e. 1 2 3 4 <- Older Pages) - # :window_size:: the number of pages to show around - # the current page (defaults to 2) - # :always_show_anchors:: whether or not the first and last - # pages should always be shown - # (defaults to +true+) - # :link_to_current_page:: whether or not the current page - # should be linked to (defaults to - # +false+) - # :params:: any additional routing parameters - # for page URLs - # - # ==== Examples - # # We'll assume we have a paginator setup in @person_pages... - # - # pagination_links(@person_pages) - # # => 1 2 3 ... 10 - # - # pagination_links(@person_pages, :link_to_current_page => true) - # # => 1 2 3 ... 10 - # - # pagination_links(@person_pages, :always_show_anchors => false) - # # => 1 2 3 - # - # pagination_links(@person_pages, :window_size => 1) - # # => 1 2 ... 10 - # - # pagination_links(@person_pages, :params => { :viewer => "flash" }) - # # => 1 2 3 ... - # # 10 - def pagination_links(paginator, options={}, html_options={}) - name = options[:name] || DEFAULT_OPTIONS[:name] - params = (options[:params] || DEFAULT_OPTIONS[:params]).clone - - prefix = options[:prefix] || '' - suffix = options[:suffix] || '' - - pagination_links_each(paginator, options, prefix, suffix) do |n| - params[name] = n - link_to(n.to_s, params, html_options) - end - end - - # Iterate through the pages of a given +paginator+, invoking a - # block for each page number that needs to be rendered as a link. - # - # ==== Options - # :window_size:: the number of pages to show around - # the current page (defaults to +2+) - # :always_show_anchors:: whether or not the first and last - # pages should always be shown - # (defaults to +true+) - # :link_to_current_page:: whether or not the current page - # should be linked to (defaults to - # +false+) - # - # ==== Example - # # Turn paginated links into an Ajax call - # pagination_links_each(paginator, page_options) do |link| - # options = { :url => {:action => 'list'}, :update => 'results' } - # html_options = { :href => url_for(:action => 'list') } - # - # link_to_remote(link.to_s, options, html_options) - # end - def pagination_links_each(paginator, options, prefix = nil, suffix = nil) - options = DEFAULT_OPTIONS.merge(options) - link_to_current_page = options[:link_to_current_page] - always_show_anchors = options[:always_show_anchors] - - current_page = paginator.current_page - window_pages = current_page.window(options[:window_size]).pages - return if window_pages.length <= 1 unless link_to_current_page - - first, last = paginator.first, paginator.last - - html = '' - - html << prefix if prefix - - if always_show_anchors and not (wp_first = window_pages[0]).first? - html << yield(first.number) - html << ' ... ' if wp_first.number - first.number > 1 - html << ' ' - end - - window_pages.each do |page| - if current_page == page && !link_to_current_page - html << page.number.to_s - else - html << yield(page.number) - end - html << ' ' - end - - if always_show_anchors and not (wp_last = window_pages[-1]).last? - html << ' ... ' if last.number - wp_last.number > 1 - html << yield(last.number) - end - - html << suffix if suffix - - html - end - - end # PaginationHelper - end # Helpers -end # ActionView diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/companies.yml --- a/lib/plugins/classic_pagination/test/fixtures/companies.yml Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -thirty_seven_signals: - id: 1 - name: 37Signals - rating: 4 - -TextDrive: - id: 2 - name: TextDrive - rating: 4 - -PlanetArgon: - id: 3 - name: Planet Argon - rating: 4 - -Google: - id: 4 - name: Google - rating: 4 - -Ionist: - id: 5 - name: Ioni.st - rating: 4 \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/company.rb --- a/lib/plugins/classic_pagination/test/fixtures/company.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -class Company < ActiveRecord::Base - attr_protected :rating - set_sequence_name :companies_nonstd_seq - - validates_presence_of :name - def validate - errors.add('rating', 'rating should not be 2') if rating == 2 - end -end \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/developer.rb --- a/lib/plugins/classic_pagination/test/fixtures/developer.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -class Developer < ActiveRecord::Base - has_and_belongs_to_many :projects -end - -class DeVeLoPeR < ActiveRecord::Base - self.table_name = "developers" -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/developers.yml --- a/lib/plugins/classic_pagination/test/fixtures/developers.yml Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -david: - id: 1 - name: David - salary: 80000 - -jamis: - id: 2 - name: Jamis - salary: 150000 - -<% for digit in 3..10 %> -dev_<%= digit %>: - id: <%= digit %> - name: fixture_<%= digit %> - salary: 100000 -<% end %> - -poor_jamis: - id: 11 - name: Jamis - salary: 9000 \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/developers_projects.yml --- a/lib/plugins/classic_pagination/test/fixtures/developers_projects.yml Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -david_action_controller: - developer_id: 1 - project_id: 2 - joined_on: 2004-10-10 - -david_active_record: - developer_id: 1 - project_id: 1 - joined_on: 2004-10-10 - -jamis_active_record: - developer_id: 2 - project_id: 1 \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/project.rb --- a/lib/plugins/classic_pagination/test/fixtures/project.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class Project < ActiveRecord::Base - has_and_belongs_to_many :developers, :uniq => true -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/projects.yml --- a/lib/plugins/classic_pagination/test/fixtures/projects.yml Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -action_controller: - id: 2 - name: Active Controller - -active_record: - id: 1 - name: Active Record diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/replies.yml --- a/lib/plugins/classic_pagination/test/fixtures/replies.yml Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -witty_retort: - id: 1 - topic_id: 1 - content: Birdman is better! - created_at: <%= 6.hours.ago.to_s(:db) %> - updated_at: nil - -another: - id: 2 - topic_id: 2 - content: Nuh uh! - created_at: <%= 1.hour.ago.to_s(:db) %> - updated_at: nil \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/reply.rb --- a/lib/plugins/classic_pagination/test/fixtures/reply.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class Reply < ActiveRecord::Base - belongs_to :topic, :include => [:replies] - - validates_presence_of :content -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/schema.sql --- a/lib/plugins/classic_pagination/test/fixtures/schema.sql Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -CREATE TABLE 'companies' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'name' TEXT DEFAULT NULL, - 'rating' INTEGER DEFAULT 1 -); - -CREATE TABLE 'replies' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'content' text, - 'created_at' datetime, - 'updated_at' datetime, - 'topic_id' integer -); - -CREATE TABLE 'topics' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'title' varchar(255), - 'subtitle' varchar(255), - 'content' text, - 'created_at' datetime, - 'updated_at' datetime -); - -CREATE TABLE 'developers' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'name' TEXT DEFAULT NULL, - 'salary' INTEGER DEFAULT 70000, - 'created_at' DATETIME DEFAULT NULL, - 'updated_at' DATETIME DEFAULT NULL -); - -CREATE TABLE 'projects' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'name' TEXT DEFAULT NULL -); - -CREATE TABLE 'developers_projects' ( - 'developer_id' INTEGER NOT NULL, - 'project_id' INTEGER NOT NULL, - 'joined_on' DATE DEFAULT NULL, - 'access_level' INTEGER DEFAULT 1 -); diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/topic.rb --- a/lib/plugins/classic_pagination/test/fixtures/topic.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class Topic < ActiveRecord::Base - has_many :replies, :include => [:user], :dependent => :destroy -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/fixtures/topics.yml --- a/lib/plugins/classic_pagination/test/fixtures/topics.yml Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -futurama: - id: 1 - title: Isnt futurama awesome? - subtitle: It really is, isnt it. - content: I like futurama - created_at: <%= 1.day.ago.to_s(:db) %> - updated_at: - -harvey_birdman: - id: 2 - title: Harvey Birdman is the king of all men - subtitle: yup - content: It really is - created_at: <%= 2.hours.ago.to_s(:db) %> - updated_at: - -rails: - id: 3 - title: Rails is nice - subtitle: It makes me happy - content: except when I have to hack internals to fix pagination. even then really. - created_at: <%= 20.minutes.ago.to_s(:db) %> diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/helper.rb --- a/lib/plugins/classic_pagination/test/helper.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -require 'test/unit' - -unless defined?(ActiveRecord) - plugin_root = File.join(File.dirname(__FILE__), '..') - - # first look for a symlink to a copy of the framework - if framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p } - puts "found framework root: #{framework_root}" - # this allows for a plugin to be tested outside an app - $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib" - else - # is the plugin installed in an application? - app_root = plugin_root + '/../../..' - - if File.directory? app_root + '/config' - puts 'using config/boot.rb' - ENV['RAILS_ENV'] = 'test' - require File.expand_path(app_root + '/config/boot') - else - # simply use installed gems if available - puts 'using rubygems' - require 'rubygems' - gem 'actionpack'; gem 'activerecord' - end - end - - %w(action_pack active_record action_controller active_record/fixtures action_controller/test_process).each {|f| require f} - - Dependencies.load_paths.unshift "#{plugin_root}/lib" -end - -# Define the connector -class ActiveRecordTestConnector - cattr_accessor :able_to_connect - cattr_accessor :connected - - # Set our defaults - self.connected = false - self.able_to_connect = true - - class << self - def setup - unless self.connected || !self.able_to_connect - setup_connection - load_schema - require_fixture_models - self.connected = true - end - rescue Exception => e # errors from ActiveRecord setup - $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}" - #$stderr.puts " #{e.backtrace.join("\n ")}\n" - self.able_to_connect = false - end - - private - - def setup_connection - if Object.const_defined?(:ActiveRecord) - defaults = { :database => ':memory:' } - begin - options = defaults.merge :adapter => 'sqlite3', :timeout => 500 - ActiveRecord::Base.establish_connection(options) - ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options } - ActiveRecord::Base.connection - rescue Exception # errors from establishing a connection - $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.' - options = defaults.merge :adapter => 'sqlite' - ActiveRecord::Base.establish_connection(options) - ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options } - ActiveRecord::Base.connection - end - - Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE) - else - raise "Can't setup connection since ActiveRecord isn't loaded." - end - end - - # Load actionpack sqlite tables - def load_schema - File.read(File.dirname(__FILE__) + "/fixtures/schema.sql").split(';').each do |sql| - ActiveRecord::Base.connection.execute(sql) unless sql.blank? - end - end - - def require_fixture_models - Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f} - end - end -end - -# Test case for inheritance -class ActiveRecordTestCase < Test::Unit::TestCase - # Set our fixture path - if ActiveRecordTestConnector.able_to_connect - self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/" - self.use_transactional_fixtures = false - end - - def self.fixtures(*args) - super if ActiveRecordTestConnector.connected - end - - def run(*args) - super if ActiveRecordTestConnector.connected - end - - # Default so Test::Unit::TestCase doesn't complain - def test_truth - end -end - -ActiveRecordTestConnector.setup -ActionController::Routing::Routes.reload rescue nil -ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/pagination_helper_test.rb --- a/lib/plugins/classic_pagination/test/pagination_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -require File.dirname(__FILE__) + '/helper' -require File.dirname(__FILE__) + '/../init' - -class PaginationHelperTest < Test::Unit::TestCase - include ActionController::Pagination - include ActionView::Helpers::PaginationHelper - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::TagHelper - - def setup - @controller = Class.new do - attr_accessor :url, :request - def url_for(options, *parameters_for_method_reference) - url - end - end - @controller = @controller.new - @controller.url = "http://www.example.com" - end - - def test_pagination_links - total, per_page, page = 30, 10, 1 - output = pagination_links Paginator.new(@controller, total, per_page, page) - assert_equal "1 2 3 ", output - end - - def test_pagination_links_with_prefix - total, per_page, page = 30, 10, 1 - output = pagination_links Paginator.new(@controller, total, per_page, page), :prefix => 'Newer ' - assert_equal "Newer 1 2 3 ", output - end - - def test_pagination_links_with_suffix - total, per_page, page = 30, 10, 1 - output = pagination_links Paginator.new(@controller, total, per_page, page), :suffix => 'Older' - assert_equal "1 2 3 Older", output - end -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/classic_pagination/test/pagination_test.rb --- a/lib/plugins/classic_pagination/test/pagination_test.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,177 +0,0 @@ -require File.dirname(__FILE__) + '/helper' -require File.dirname(__FILE__) + '/../init' - -class PaginationTest < ActiveRecordTestCase - fixtures :topics, :replies, :developers, :projects, :developers_projects - - class PaginationController < ActionController::Base - if respond_to? :view_paths= - self.view_paths = [ "#{File.dirname(__FILE__)}/../fixtures/" ] - else - self.template_root = [ "#{File.dirname(__FILE__)}/../fixtures/" ] - end - - def simple_paginate - @topic_pages, @topics = paginate(:topics) - render :nothing => true - end - - def paginate_with_per_page - @topic_pages, @topics = paginate(:topics, :per_page => 1) - render :nothing => true - end - - def paginate_with_order - @topic_pages, @topics = paginate(:topics, :order => 'created_at asc') - render :nothing => true - end - - def paginate_with_order_by - @topic_pages, @topics = paginate(:topics, :order_by => 'created_at asc') - render :nothing => true - end - - def paginate_with_include_and_order - @topic_pages, @topics = paginate(:topics, :include => :replies, :order => 'replies.created_at asc, topics.created_at asc') - render :nothing => true - end - - def paginate_with_conditions - @topic_pages, @topics = paginate(:topics, :conditions => ["created_at > ?", 30.minutes.ago]) - render :nothing => true - end - - def paginate_with_class_name - @developer_pages, @developers = paginate(:developers, :class_name => "DeVeLoPeR") - render :nothing => true - end - - def paginate_with_singular_name - @developer_pages, @developers = paginate() - render :nothing => true - end - - def paginate_with_joins - @developer_pages, @developers = paginate(:developers, - :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1') - render :nothing => true - end - - def paginate_with_join - @developer_pages, @developers = paginate(:developers, - :join => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1') - render :nothing => true - end - - def paginate_with_join_and_count - @developer_pages, @developers = paginate(:developers, - :join => 'd LEFT JOIN developers_projects ON d.id = developers_projects.developer_id', - :conditions => 'project_id=1', - :count => "d.id") - render :nothing => true - end - - def paginate_with_join_and_group - @developer_pages, @developers = paginate(:developers, - :join => 'INNER JOIN developers_projects ON developers.id = developers_projects.developer_id', - :group => 'developers.id') - render :nothing => true - end - - def rescue_errors(e) raise e end - - def rescue_action(e) raise end - - end - - def setup - @controller = PaginationController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - super - end - - # Single Action Pagination Tests - - def test_simple_paginate - get :simple_paginate - assert_equal 1, assigns(:topic_pages).page_count - assert_equal 3, assigns(:topics).size - end - - def test_paginate_with_per_page - get :paginate_with_per_page - assert_equal 1, assigns(:topics).size - assert_equal 3, assigns(:topic_pages).page_count - end - - def test_paginate_with_order - get :paginate_with_order - expected = [topics(:futurama), - topics(:harvey_birdman), - topics(:rails)] - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_order_by - get :paginate_with_order - expected = assigns(:topics) - get :paginate_with_order_by - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_conditions - get :paginate_with_conditions - expected = [topics(:rails)] - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_class_name - get :paginate_with_class_name - - assert assigns(:developers).size > 0 - assert_equal DeVeLoPeR, assigns(:developers).first.class - end - - def test_paginate_with_joins - get :paginate_with_joins - assert_equal 2, assigns(:developers).size - developer_names = assigns(:developers).map { |d| d.name } - assert developer_names.include?('David') - assert developer_names.include?('Jamis') - end - - def test_paginate_with_join_and_conditions - get :paginate_with_joins - expected = assigns(:developers) - get :paginate_with_join - assert_equal expected, assigns(:developers) - end - - def test_paginate_with_join_and_count - get :paginate_with_joins - expected = assigns(:developers) - get :paginate_with_join_and_count - assert_equal expected, assigns(:developers) - end - - def test_paginate_with_include_and_order - get :paginate_with_include_and_order - expected = Topic.find(:all, :include => 'replies', :order => 'replies.created_at asc, topics.created_at asc', :limit => 10) - assert_equal expected, assigns(:topics) - end - - def test_paginate_with_join_and_group - get :paginate_with_join_and_group - assert_equal 2, assigns(:developers).size - assert_equal 2, assigns(:developer_pages).item_count - developer_names = assigns(:developers).map { |d| d.name } - assert developer_names.include?('David') - assert developer_names.include?('Jamis') - end -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/open_id_authentication/test/test_helper.rb --- a/lib/plugins/open_id_authentication/test/test_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/open_id_authentication/test/test_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -8,7 +8,7 @@ require 'action_controller' gem 'mocha' -require 'mocha' +require 'mocha/setup' gem 'ruby-openid' require 'openid' diff -r d98d22a98252 -r afce8026aaeb lib/plugins/rfpdf/lib/fpdf/fpdf_eps.rb --- a/lib/plugins/rfpdf/lib/fpdf/fpdf_eps.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -# Information -# -# PDF_EPS class from Valentin Schmidt ported to ruby by Thiago Jackiw (tjackiw@gmail.com) -# working for Mingle LLC (www.mingle.com) -# Release Date: July 13th, 2006 -# -# Description -# -# This script allows to embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files. -# Only vector drawing is supported, not text or bitmap. Although the script was successfully -# tested with various AI format versions, best results are probably achieved with files that -# were exported in the AI3 format (tested with Illustrator CS2, Freehand MX and Photoshop CS2). -# -# ImageEps(string file, float x, float y [, float w [, float h [, string link [, boolean useBoundingBox]]]]) -# -# Same parameters as for regular FPDF::Image() method, with an additional one: -# -# useBoundingBox: specifies whether to position the bounding box (true) or the complete canvas (false) -# at location (x,y). Default value is true. -# -# First added to the Ruby FPDF distribution in 1.53c -# -# Usage is as follows: -# -# require 'fpdf' -# require 'fpdf_eps' -# pdf = FPDF.new -# pdf.extend(PDF_EPS) -# pdf.ImageEps(...) -# -# This allows it to be combined with other extensions, such as the bookmark -# module. - -module PDF_EPS - def ImageEps(file, x, y, w=0, h=0, link='', use_bounding_box=true) - data = nil - if File.exists?(file) - File.open(file, 'rb') do |f| - data = f.read() - end - else - Error('EPS file not found: '+file) - end - - # Find BoundingBox param - regs = data.scan(/%%BoundingBox: [^\r\n]*/m) - regs << regs[0].gsub(/%%BoundingBox: /, '') - if regs.size > 1 - tmp = regs[1].to_s.split(' ') - @x1 = tmp[0].to_i - @y1 = tmp[1].to_i - @x2 = tmp[2].to_i - @y2 = tmp[3].to_i - else - Error('No BoundingBox found in EPS file: '+file) - end - f_start = data.index('%%EndSetup') - f_start = data.index('%%EndProlog') if f_start === false - f_start = data.index('%%BoundingBox') if f_start === false - - data = data.slice(f_start, data.length) - - f_end = data.index('%%PageTrailer') - f_end = data.index('showpage') if f_end === false - data = data.slice(0, f_end) if f_end - - # save the current graphic state - out('q') - - k = @k - - # Translate - if use_bounding_box - dx = x*k-@x1 - dy = @hPt-@y2-y*k - else - dx = x*k - dy = -y*k - end - tm = [1,0,0,1,dx,dy] - out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', - tm[0], tm[1], tm[2], tm[3], tm[4], tm[5])) - - if w > 0 - scale_x = w/((@x2-@x1)/k) - if h > 0 - scale_y = h/((@y2-@y1)/k) - else - scale_y = scale_x - h = (@y2-@y1)/k * scale_y - end - else - if h > 0 - scale_y = $h/((@y2-@y1)/$k) - scale_x = scale_y - w = (@x2-@x1)/k * scale_x - else - w = (@x2-@x1)/k - h = (@y2-@y1)/k - end - end - - if !scale_x.nil? - # Scale - tm = [scale_x,0,0,scale_y,0,@hPt*(1-scale_y)] - out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', - tm[0], tm[1], tm[2], tm[3], tm[4], tm[5])) - end - - data.split(/\r\n|[\r\n]/).each do |line| - next if line == '' || line[0,1] == '%' - len = line.length - # next if (len > 2 && line[len-2,len] != ' ') - cmd = line[len-2,len].strip - case cmd - when 'm', 'l', 'v', 'y', 'c', 'k', 'K', 'g', 'G', 's', 'S', 'J', 'j', 'w', 'M', 'd': - out(line) - - when 'L': - line[len-1,len]='l' - out(line) - - when 'C': - line[len-1,len]='c' - out(line) - - when 'f', 'F': - out('f*') - - when 'b', 'B': - out(cmd + '*') - end - end - - # restore previous graphic state - out('Q') - Link(x,y,w,h,link) if link - end -end diff -r d98d22a98252 -r afce8026aaeb lib/plugins/rfpdf/lib/tcpdf.rb --- a/lib/plugins/rfpdf/lib/tcpdf.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/plugins/rfpdf/lib/tcpdf.rb Tue Sep 09 09:34:53 2014 +0100 @@ -39,9 +39,6 @@ # @package com.tecnick.tcpdf # -@@version = "1.53.0.TC031" -@@fpdf_charwidths = {} - PDF_PRODUCER = 'TCPDF via RFPDF 1.53.0.TC031 (http://tcpdf.sourceforge.net)' module TCPDFFontDescriptor @@ -79,6 +76,9 @@ Rails.logger end + @@version = "1.53.0.TC031" + @@fpdf_charwidths = {} + cattr_accessor :k_cell_height_ratio @@k_cell_height_ratio = 1.25 @@ -2431,7 +2431,7 @@ out('1 0 obj'); out('<>'); + out('<>') out('stream'); out('/CIDInit /ProcSet findresource begin'); out('12 dict begin'); diff -r d98d22a98252 -r afce8026aaeb lib/redcloth3.rb --- a/lib/redcloth3.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redcloth3.rb Tue Sep 09 09:34:53 2014 +0100 @@ -129,7 +129,7 @@ # # Will become: # -# ACLU +# ACLU # # == Adding Tables # @@ -457,7 +457,7 @@ # text.gsub! re, resub #end text.gsub!(/\b([A-Z][A-Z0-9]{1,})\b(?:[(]([^)]*)[)])/) do |m| - "#{$1}" + "#{$1}" end end @@ -525,7 +525,7 @@ tatts = pba( tatts, 'table' ) tatts = shelve( tatts ) if tatts rows = [] - + fullrow.gsub!(/([^|])\n/, "\\1
    ") fullrow.each_line do |row| ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m cells = [] diff -r d98d22a98252 -r afce8026aaeb lib/redmine.rb --- a/lib/redmine.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,16 +1,21 @@ -require 'redmine/access_control' -require 'redmine/menu_manager' -require 'redmine/activity' -require 'redmine/search' -require 'redmine/custom_field_format' -require 'redmine/mime_type' +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require 'redmine/core_ext' -require 'redmine/themes' -require 'redmine/hook' -require 'redmine/plugin' -require 'redmine/notifiable' -require 'redmine/wiki_formatting' -require 'redmine/scm/base' begin require 'RMagick' unless Object.const_defined?(:Magick) @@ -18,6 +23,41 @@ # RMagick is not available end +require 'redmine/scm/base' +require 'redmine/access_control' +require 'redmine/access_keys' +require 'redmine/activity' +require 'redmine/activity/fetcher' +require 'redmine/ciphering' +require 'redmine/codeset_util' +require 'redmine/custom_field_format' +require 'redmine/i18n' +require 'redmine/menu_manager' +require 'redmine/notifiable' +require 'redmine/platform' +require 'redmine/mime_type' +require 'redmine/notifiable' +require 'redmine/search' +require 'redmine/syntax_highlighting' +require 'redmine/thumbnail' +require 'redmine/unified_diff' +require 'redmine/utils' +require 'redmine/version' +require 'redmine/wiki_formatting' + +require 'redmine/default_data/loader' +require 'redmine/helpers/calendar' +require 'redmine/helpers/diff' +require 'redmine/helpers/gantt' +require 'redmine/helpers/time_report' +require 'redmine/views/other_formats_builder' +require 'redmine/views/labelled_form_builder' +require 'redmine/views/builders' + +require 'redmine/themes' +require 'redmine/hook' +require 'redmine/plugin' + if RUBY_VERSION < '1.9' require 'fastercsv' else @@ -75,7 +115,7 @@ map.permission :manage_subtasks, {} map.permission :set_issues_private, {} map.permission :set_own_issues_private, {}, :require => :loggedin - map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload} + map.permission :add_issue_notes, {:issues => [:edit, :update, :update_form], :journals => [:new], :attachments => :upload} map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :view_private_notes, {}, :read => true, :require => :member @@ -87,7 +127,7 @@ map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin # Watchers map.permission :view_issue_watchers, {}, :read => true - map.permission :add_issue_watchers, {:watchers => :new} + map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]} map.permission :delete_issue_watchers, {:watchers => :destroy} end @@ -100,18 +140,20 @@ end map.project_module :news do |map| - map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member + map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy], :attachments => :upload}, :require => :member map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true map.permission :comment_news, {:comments => :create} end map.project_module :documents do |map| - map.permission :manage_documents, {:documents => [:new, :create, :edit, :update, :destroy, :add_attachment]}, :require => :loggedin + map.permission :add_documents, {:documents => [:new, :create, :add_attachment], :attachments => :upload}, :require => :loggedin + map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment], :attachments => :upload}, :require => :loggedin + map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true end map.project_module :files do |map| - map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin + map.permission :manage_files, {:files => [:new, :create], :attachments => :upload}, :require => :loggedin map.permission :view_files, {:files => :index, :versions => :download}, :read => true end @@ -122,7 +164,7 @@ map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true - map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment] + map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment], :attachments => :upload map.permission :delete_wiki_pages_attachments, {} map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member end @@ -138,9 +180,9 @@ map.project_module :boards do |map| map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => true - map.permission :add_messages, {:messages => [:new, :reply, :quote]} - map.permission :edit_messages, {:messages => :edit}, :require => :member - map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin + map.permission :add_messages, {:messages => [:new, :reply, :quote], :attachments => :upload} + map.permission :edit_messages, {:messages => :edit, :attachments => :upload}, :require => :member + map.permission :edit_own_messages, {:messages => :edit, :attachments => :upload}, :require => :loggedin map.permission :delete_messages, {:messages => :destroy}, :require => :member map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin end @@ -166,7 +208,7 @@ menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? } menu.push :register, :register_path, :if => Proc.new { !User.current.logged? && Setting.self_registration? } menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? } - menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? } + menu.push :logout, :signout_path, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? } end Redmine::MenuManager.map :application_menu do |menu| diff -r d98d22a98252 -r afce8026aaeb lib/redmine/about.rb --- a/lib/redmine/about.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -module Redmine - class About - def self.print_plugin_info - plugins = Redmine::Plugin.registered_plugins - - if !plugins.empty? - column_with = plugins.map {|internal_name, plugin| plugin.name.length}.max - puts "\nAbout your Redmine plugins" - - plugins.each do |internal_name, plugin| - puts sprintf("%-#{column_with}s %s", plugin.name, plugin.version) - end - end - end - end -end diff -r d98d22a98252 -r afce8026aaeb lib/redmine/access_control.rb --- a/lib/redmine/access_control.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/access_control.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/access_keys.rb --- a/lib/redmine/access_keys.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/access_keys.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/activity.rb --- a/lib/redmine/activity.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/activity.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/activity/fetcher.rb --- a/lib/redmine/activity/fetcher.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/activity/fetcher.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/ciphering.rb --- a/lib/redmine/ciphering.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/ciphering.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/codeset_util.rb --- a/lib/redmine/codeset_util.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/codeset_util.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,6 @@ -require 'iconv' +if RUBY_VERSION < '1.9' + require 'iconv' +end module Redmine module CodesetUtil @@ -100,10 +102,20 @@ end encodings = Setting.repositories_encodings.split(',').collect(&:strip) encodings.each do |encoding| - begin - return Iconv.conv('UTF-8', encoding, str) - rescue Iconv::Failure - # do nothing here and try the next encoding + if str.respond_to?(:force_encoding) + begin + str.force_encoding(encoding) + utf8 = str.encode('UTF-8') + return utf8 if utf8.valid_encoding? + rescue + # do nothing here and try the next encoding + end + else + begin + return Iconv.conv('UTF-8', encoding, str) + rescue Iconv::Failure + # do nothing here and try the next encoding + end end end str = self.replace_invalid_utf8(str) diff -r d98d22a98252 -r afce8026aaeb lib/redmine/configuration.rb --- a/lib/redmine/configuration.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/configuration.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,7 +20,8 @@ # Configuration default values @defaults = { - 'email_delivery' => nil + 'email_delivery' => nil, + 'max_concurrent_ajax_uploads' => 2 } @config = nil diff -r d98d22a98252 -r afce8026aaeb lib/redmine/core_ext/active_record.rb --- a/lib/redmine/core_ext/active_record.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/core_ext/active_record.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -38,3 +38,15 @@ end end end + +class DateValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + before_type_cast = record.attributes_before_type_cast[attribute.to_s] + if before_type_cast.is_a?(String) && before_type_cast.present? + # TODO: #*_date_before_type_cast returns a Mysql::Time with ruby1.8+mysql gem + unless before_type_cast =~ /\A\d{4}-\d{2}-\d{2}( 00:00:00)?\z/ && value + record.errors.add attribute, :not_a_date + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb lib/redmine/core_ext/date/calculations.rb --- a/lib/redmine/core_ext/date/calculations.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/core_ext/date/calculations.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/core_ext/string/conversions.rb --- a/lib/redmine/core_ext/string/conversions.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/core_ext/string/conversions.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/core_ext/string/inflections.rb --- a/lib/redmine/core_ext/string/inflections.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/core_ext/string/inflections.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/custom_field_format.rb --- a/lib/redmine/custom_field_format.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/custom_field_format.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -70,6 +70,13 @@ @@available[custom_field_format.name] = custom_field_format unless @@available.keys.include?(custom_field_format.name) end + def delete(format) + if format.is_a?(Redmine::CustomFieldFormat) + format = format.name + end + @@available.delete(format) + end + def available_formats @@available.keys end diff -r d98d22a98252 -r afce8026aaeb lib/redmine/default_data/loader.rb --- a/lib/redmine/default_data/loader.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/default_data/loader.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,10 +26,10 @@ # Returns true if no data is already loaded in the database # otherwise false def no_data? - !Role.find(:first, :conditions => {:builtin => 0}) && - !Tracker.find(:first) && - !IssueStatus.find(:first) && - !Enumeration.find(:first) + !Role.where(:builtin => 0).exists? && + !Tracker.exists? && + !IssueStatus.exists? && + !Enumeration.exists? end # Loads the default data @@ -139,15 +139,15 @@ rejected = IssueStatus.create!(:name => l(:default_issue_status_rejected), :is_closed => true, :is_default => false, :position => 6) # Workflow - Tracker.find(:all).each { |t| - IssueStatus.find(:all).each { |os| - IssueStatus.find(:all).each { |ns| + Tracker.all.each { |t| + IssueStatus.all.each { |os| + IssueStatus.all.each { |ns| WorkflowTransition.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns } } } - Tracker.find(:all).each { |t| + Tracker.all.each { |t| [new, in_progress, resolved, feedback].each { |os| [in_progress, resolved, feedback, closed].each { |ns| WorkflowTransition.create!(:tracker_id => t.id, :role_id => developer.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns @@ -155,7 +155,7 @@ } } - Tracker.find(:all).each { |t| + Tracker.all.each { |t| [new, in_progress, resolved, feedback].each { |os| [closed].each { |ns| WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns diff -r d98d22a98252 -r afce8026aaeb lib/redmine/export/pdf.rb --- a/lib/redmine/export/pdf.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/export/pdf.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,12 +17,15 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'iconv' require 'tcpdf' require 'fpdf/chinese' require 'fpdf/japanese' require 'fpdf/korean' +if RUBY_VERSION < '1.9' + require 'iconv' +end + module Redmine module Export module PDF @@ -86,7 +89,7 @@ def SetTitle(txt) txt = begin - utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) + utf16txt = to_utf16(txt) hextxt = "" @@ -116,6 +119,15 @@ html end + # Encodes an UTF-8 string to UTF-16BE + def to_utf16(str) + if str.respond_to?(:encode) + str.encode('UTF-16BE') + else + Iconv.conv('UTF-16BE', 'UTF-8', str) + end + end + def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='') Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link) end @@ -160,7 +172,7 @@ def bookmark_title(txt) txt = begin - utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) + utf16txt = to_utf16(txt) hextxt = "" @@ -244,7 +256,7 @@ def fetch_row_values(issue, query, level) query.inline_columns.collect do |column| s = if column.is_a?(QueryCustomFieldColumn) - cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} + cv = issue.visible_custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} show_value(cv) else value = issue.send(column.name) @@ -302,11 +314,11 @@ col_width_avg.map! {|x| x / k} # calculate columns width - ratio = table_width / col_width_avg.inject(0) {|s,w| s += w} + ratio = table_width / col_width_avg.inject(0, :+) col_width = col_width_avg.map {|w| w * ratio} # correct max word width if too many columns - ratio = table_width / word_width_max.inject(0) {|s,w| s += w} + ratio = table_width / word_width_max.inject(0, :+) word_width_max.map! {|v| v * ratio} if ratio < 1 # correct and lock width of some columns @@ -342,7 +354,7 @@ # calculate column normalizing ratio if free_col_width == 0 - ratio = table_width / col_width.inject(0) {|s,w| s += w} + ratio = table_width / col_width.inject(0, :+) else ratio = (table_width - fix_col_width) / free_col_width end @@ -368,7 +380,7 @@ col_width end - def render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) + def render_table_header(pdf, query, col_width, row_height, table_width) # headers pdf.SetFontStyle('B',8) pdf.SetFillColor(230, 230, 230) @@ -377,13 +389,12 @@ base_x = pdf.GetX base_y = pdf.GetY max_height = issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) - pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD'); + pdf.Rect(base_x, base_y, table_width, max_height, 'FD'); pdf.SetXY(base_x, base_y); # write the cells on page - pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1) issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) - issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) + issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) pdf.SetY(base_y + max_height); # rows @@ -405,30 +416,30 @@ # Landscape A4 = 210 x 297 mm page_height = 210 page_width = 297 + left_margin = 10 right_margin = 10 bottom_margin = 20 - col_id_width = 10 row_height = 4 # column widths - table_width = page_width - right_margin - 10 # fixed left margin + table_width = page_width - right_margin - left_margin col_width = [] unless query.inline_columns.empty? - col_width = calc_col_width(issues, query, table_width - col_id_width, pdf) - table_width = col_width.inject(0) {|s,v| s += v} + col_width = calc_col_width(issues, query, table_width, pdf) + table_width = col_width.inject(0, :+) end - # use full width if the description is displayed + # use full width if the description is displayed if table_width > 0 && query.has_column?(:description) - col_width = col_width.map {|w| w = w * (page_width - right_margin - 10 - col_id_width) / table_width} - table_width = col_width.inject(0) {|s,v| s += v} + col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width} + table_width = col_width.inject(0, :+) end # title pdf.SetFontStyle('B',11) pdf.RDMCell(190,10, title) pdf.Ln - render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) + render_table_header(pdf, query, col_width, row_height, table_width) previous_group = false issue_list(issues) do |issue, level| if query.grouped? && @@ -437,7 +448,7 @@ group_label = group.blank? ? 'None' : group.to_s.dup group_label << " (#{query.issue_count_by_group[group]})" pdf.Bookmark group_label, 0, -1 - pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L') + pdf.RDMCell(table_width, row_height * 2, group_label, 1, 1, 'L') pdf.SetFontStyle('',8) previous_group = group end @@ -456,15 +467,14 @@ space_left = page_height - base_y - bottom_margin if max_height > space_left pdf.AddPage("L") - render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) + render_table_header(pdf, query, col_width, row_height, table_width) base_x = pdf.GetX base_y = pdf.GetY end # write the cells on page - pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1) issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) - issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) + issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) pdf.SetY(base_y + max_height); if query.has_column?(:description) && issue.description? @@ -483,8 +493,7 @@ end # Renders MultiCells and returns the maximum height used - def issues_to_pdf_write_cells(pdf, col_values, col_widths, - row_height, head=false) + def issues_to_pdf_write_cells(pdf, col_values, col_widths, row_height, head=false) base_y = pdf.GetY max_height = row_height col_values.each_with_index do |column, i| @@ -501,9 +510,11 @@ end # Draw lines to close the row (MultiCell border drawing in not uniform) + # + # parameter "col_id_width" is not used. it is kept for compatibility. def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y, - id_width, col_widths) - col_x = top_x + id_width + col_id_width, col_widths) + col_x = top_x pdf.Line(col_x, top_y, col_x, lower_y) # id right border col_widths.each do |width| col_x += width @@ -560,8 +571,8 @@ right << nil end - half = (issue.custom_field_values.size / 2.0).ceil - issue.custom_field_values.each_with_index do |custom_value, i| + half = (issue.visible_custom_field_values.size / 2.0).ceil + issue.visible_custom_field_values.each_with_index do |custom_value, i| (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)] end @@ -672,7 +683,7 @@ pdf.RDMCell(190,5, title) pdf.Ln pdf.SetFontStyle('I',8) - details_to_strings(journal.details, true).each do |string| + details_to_strings(journal.visible_details, true).each do |string| pdf.RDMMultiCell(190,5, "- " + string) end if journal.notes? diff -r d98d22a98252 -r afce8026aaeb lib/redmine/helpers/calendar.rb --- a/lib/redmine/helpers/calendar.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/helpers/calendar.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/helpers/diff.rb --- a/lib/redmine/helpers/diff.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/helpers/diff.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -15,6 +15,8 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +require 'diff' + module Redmine module Helpers class Diff diff -r d98d22a98252 -r afce8026aaeb lib/redmine/helpers/gantt.rb --- a/lib/redmine/helpers/gantt.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/helpers/gantt.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,6 +23,12 @@ include Redmine::I18n include Redmine::Utils::DateCalculation + # Relation types that are rendered + DRAW_TYPES = { + IssueRelation::TYPE_BLOCKS => { :landscape_margin => 16, :color => '#F34F4F' }, + IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' } + }.freeze + # :nodoc: # Some utility methods for the PDF export class PDF @@ -136,17 +142,32 @@ ) end + # Returns a hash of the relations between the issues that are present on the gantt + # and that should be displayed, grouped by issue ids. + def relations + return @relations if @relations + if issues.any? + issue_ids = issues.map(&:id) + @relations = IssueRelation. + where(:issue_from_id => issue_ids, :issue_to_id => issue_ids, :relation_type => DRAW_TYPES.keys). + group_by(&:issue_from_id) + else + @relations = {} + end + end + # Return all the project nodes that will be displayed def projects return @projects if @projects ids = issues.collect(&:project).uniq.collect(&:id) if ids.any? # All issues projects and their visible ancestors - @projects = Project.visible.all( - :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt", - :conditions => ["child.id IN (?)", ids], - :order => "#{Project.table_name}.lft ASC" - ).uniq + @projects = Project.visible. + joins("LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt"). + where("child.id IN (?)", ids). + order("#{Project.table_name}.lft ASC"). + uniq. + all else @projects = [] end @@ -194,12 +215,13 @@ @number_of_rows += 1 return if abort? issues = project_issues(project).select {|i| i.fixed_version.nil?} - sort_issues!(issues) + self.class.sort_issues!(issues) if issues render_issues(issues, options) return if abort? end versions = project_versions(project) + self.class.sort_versions!(versions) versions.each do |version| render_version(project, version, options) end @@ -228,7 +250,7 @@ return if abort? issues = version_issues(project, version) if issues - sort_issues!(issues) + self.class.sort_issues!(issues) # Indent issues options[:indent] += options[:indent_increment] render_issues(issues, options) @@ -277,7 +299,6 @@ pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) end else - ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date" '' end end @@ -289,10 +310,18 @@ html_class << 'icon icon-package ' html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " " html_class << (version.overdue? ? 'version-overdue' : '') + html_class << ' version-closed' unless version.open? + if version.start_date && version.due_date && version.completed_pourcent + progress_date = calc_progress_date(version.start_date, + version.due_date, version.completed_pourcent) + html_class << ' behind-start-date' if progress_date < self.date_from + html_class << ' over-end-date' if progress_date > self.date_to + end s = view.link_to_version(version).html_safe subject = view.content_tag(:span, s, :class => html_class).html_safe - html_subject(options, subject, :css => "version-name") + html_subject(options, subject, :css => "version-name", + :id => "version-#{version.id}") when :image image_subject(options, version.to_s_with_project) when :pdf @@ -303,24 +332,24 @@ def line_for_version(version, options) # Skip versions that don't have a start_date - if version.is_a?(Version) && version.start_date && version.due_date + if version.is_a?(Version) && version.due_date && version.start_date options[:zoom] ||= 1 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] coords = coordinates(version.start_date, - version.due_date, version.completed_pourcent, + version.due_date, version.completed_percent, options[:zoom]) - label = "#{h version} #{h version.completed_pourcent.to_i.to_s}%" + label = "#{h version} #{h version.completed_percent.to_i.to_s}%" label = h("#{version.project} -") + label unless @project && @project == version.project case options[:format] when :html - html_task(options, coords, :css => "version task", :label => label, :markers => true) + html_task(options, coords, :css => "version task", + :label => label, :markers => true, :version => version) when :image image_task(options, coords, :label => label, :markers => true, :height => 3) when :pdf pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) end else - ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date" '' end end @@ -336,6 +365,13 @@ css_classes << ' issue-overdue' if issue.overdue? css_classes << ' issue-behind-schedule' if issue.behind_schedule? css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to + css_classes << ' issue-closed' if issue.closed? + if issue.start_date && issue.due_before && issue.done_ratio + progress_date = calc_progress_date(issue.start_date, + issue.due_before, issue.done_ratio) + css_classes << ' behind-start-date' if progress_date < self.date_from + css_classes << ' over-end-date' if progress_date > self.date_to + end s = "".html_safe if issue.assigned_to.present? assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name @@ -347,7 +383,7 @@ s << view.link_to_issue(issue).html_safe subject = view.content_tag(:span, s, :class => css_classes).html_safe html_subject(options, subject, :css => "issue-subject", - :title => issue.subject) + "\n" + :title => issue.subject, :id => "issue-#{issue.id}") + "\n" when :image image_subject(options, issue.subject) when :pdf @@ -378,7 +414,6 @@ pdf_task(options, coords, :label => label) end else - ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before" '' end end @@ -611,7 +646,7 @@ coords[:bar_end] = self.date_to - self.date_from + 1 end if progress - progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0) + progress_date = calc_progress_date(start_date, end_date, progress) if progress_date > self.date_from && progress_date > start_date if progress_date < self.date_to coords[:bar_progress_end] = progress_date - self.date_from @@ -638,18 +673,27 @@ coords end - # Sorts a collection of issues by start_date, due_date, id for gantt rendering - def sort_issues!(issues) - issues.sort! { |a, b| gantt_issue_compare(a, b) } + def calc_progress_date(start_date, end_date, progress) + start_date + (end_date - start_date + 1) * (progress / 100.0) end - # TODO: top level issues should be sorted by start date - def gantt_issue_compare(x, y) - if x.root_id == y.root_id - x.lft <=> y.lft - else - x.root_id <=> y.root_id - end + def self.sort_issues!(issues) + issues.sort! {|a, b| sort_issue_logic(a) <=> sort_issue_logic(b)} + end + + def self.sort_issue_logic(issue) + julian_date = Date.new() + ancesters_start_date = [] + current_issue = issue + begin + ancesters_start_date.unshift([current_issue.start_date || julian_date, current_issue.id]) + current_issue = current_issue.parent + end while (current_issue) + ancesters_start_date + end + + def self.sort_versions!(versions) + versions.sort! end def current_limit @@ -678,9 +722,10 @@ def html_subject(params, subject, options={}) style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] - output = view.content_tag('div', subject, + output = view.content_tag(:div, subject, :class => options[:css], :style => style, - :title => options[:title]) + :title => options[:title], + :id => options[:id]) @subjects << output output end @@ -705,6 +750,16 @@ params[:image].text(params[:indent], params[:top] + 2, subject) end + def issue_relations(issue) + rels = {} + if relations[issue.id] + relations[issue.id].each do |relation| + (rels[relation.relation_type] ||= []) << relation.issue_to_id + end + end + rels + end + def html_task(params, coords, options={}) output = '' # Renders the task bar, with progress and late @@ -714,9 +769,18 @@ style << "top:#{params[:top]}px;" style << "left:#{coords[:bar_start]}px;" style << "width:#{width}px;" - output << view.content_tag(:div, ' '.html_safe, - :style => style, - :class => "#{options[:css]} task_todo") + html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue] + html_id = "task-todo-version-#{options[:version].id}" if options[:version] + content_opt = {:style => style, + :class => "#{options[:css]} task_todo", + :id => html_id} + if options[:issue] + rels = issue_relations(options[:issue]) + if rels.present? + content_opt[:data] = {"rels" => rels.to_json} + end + end + output << view.content_tag(:div, ' '.html_safe, content_opt) if coords[:bar_late_end] width = coords[:bar_late_end] - coords[:bar_start] - 2 style = "" @@ -733,9 +797,12 @@ style << "top:#{params[:top]}px;" style << "left:#{coords[:bar_start]}px;" style << "width:#{width}px;" + html_id = "task-done-issue-#{options[:issue].id}" if options[:issue] + html_id = "task-done-version-#{options[:version].id}" if options[:version] output << view.content_tag(:div, ' '.html_safe, :style => style, - :class => "#{options[:css]} task_done") + :class => "#{options[:css]} task_done", + :id => html_id) end end # Renders the markers diff -r d98d22a98252 -r afce8026aaeb lib/redmine/helpers/time_report.rb --- a/lib/redmine/helpers/time_report.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/helpers/time_report.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,9 +18,9 @@ module Redmine module Helpers class TimeReport - attr_reader :criteria, :columns, :from, :to, :hours, :total_hours, :periods + attr_reader :criteria, :columns, :hours, :total_hours, :periods - def initialize(project, issue, criteria, columns, from, to) + def initialize(project, issue, criteria, columns, time_entry_scope) @project = project @issue = issue @@ -30,8 +30,7 @@ @criteria = @criteria[0,3] @columns = (columns && %w(year month week day).include?(columns)) ? columns : 'month' - @from = from - @to = to + @scope = time_entry_scope run end @@ -44,15 +43,12 @@ def run unless @criteria.empty? - scope = TimeEntry.visible.spent_between(@from, @to) - if @issue - scope = scope.on_issue(@issue) - elsif @project - scope = scope.on_project(@project, Setting.display_subprojects_issues?) - end time_columns = %w(tyear tmonth tweek spent_on) @hours = [] - scope.sum(:hours, :include => :issue, :group => @criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns).each do |hash, hours| + @scope.sum(:hours, + :include => [:issue, :activity], + :group => @criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns, + :joins => @criteria.collect{|criteria| @available_criteria[criteria][:joins]}.compact).each do |hash, hours| h = {'hours' => hours} (@criteria + time_columns).each_with_index do |name, i| h[name] = hash[i] @@ -67,21 +63,17 @@ when 'month' row['month'] = "#{row['tyear']}-#{row['tmonth']}" when 'week' - row['week'] = "#{row['tyear']}-#{row['tweek']}" + row['week'] = "#{row['spent_on'].cwyear}-#{row['tweek']}" when 'day' row['day'] = "#{row['spent_on']}" end end - if @from.nil? - min = @hours.collect {|row| row['spent_on']}.min - @from = min ? min.to_date : Date.today - end + min = @hours.collect {|row| row['spent_on']}.min + @from = min ? min.to_date : Date.today - if @to.nil? - max = @hours.collect {|row| row['spent_on']}.max - @to = max ? max.to_date : Date.today - end + max = @hours.collect {|row| row['spent_on']}.max + @to = max ? max.to_date : Date.today @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f} @@ -98,7 +90,7 @@ @periods << "#{date_from.year}-#{date_from.month}" date_from = (date_from + 1.month).at_beginning_of_month when 'week' - @periods << "#{date_from.year}-#{date_from.to_date.cweek}" + @periods << "#{date_from.to_date.cwyear}-#{date_from.to_date.cweek}" date_from = (date_from + 7.day).at_beginning_of_week when 'day' @periods << "#{date_from.to_date}" @@ -121,9 +113,9 @@ 'category' => {:sql => "#{Issue.table_name}.category_id", :klass => IssueCategory, :label => :field_category}, - 'member' => {:sql => "#{TimeEntry.table_name}.user_id", + 'user' => {:sql => "#{TimeEntry.table_name}.user_id", :klass => User, - :label => :label_member}, + :label => :label_user}, 'tracker' => {:sql => "#{Issue.table_name}.tracker_id", :klass => Tracker, :label => :label_tracker}, @@ -135,24 +127,19 @@ :label => :label_issue} } + # Add time entry custom fields + custom_fields = TimeEntryCustomField.all + # Add project custom fields + custom_fields += ProjectCustomField.all + # Add issue custom fields + custom_fields += (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields) + # Add time entry activity custom fields + custom_fields += TimeEntryActivityCustomField.all + # Add list and boolean custom fields as available criteria - custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields) custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id ORDER BY c.value LIMIT 1)", - :format => cf.field_format, - :label => cf.name} - end if @project - - # Add list and boolean time entry custom fields - TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id ORDER BY c.value LIMIT 1)", - :format => cf.field_format, - :label => cf.name} - end - - # Add list and boolean time entry activity custom fields - TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id ORDER BY c.value LIMIT 1)", + @available_criteria["cf_#{cf.id}"] = {:sql => "#{cf.join_alias}.value", + :joins => cf.join_for_order_statement, :format => cf.field_format, :label => cf.name} end diff -r d98d22a98252 -r afce8026aaeb lib/redmine/hook.rb --- a/lib/redmine/hook.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/hook.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/i18n.rb --- a/lib/redmine/i18n.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/i18n.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/imap.rb --- a/lib/redmine/imap.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/imap.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -29,25 +29,27 @@ imap = Net::IMAP.new(host, port, ssl) imap.login(imap_options[:username], imap_options[:password]) unless imap_options[:username].nil? imap.select(folder) - imap.search(['NOT', 'SEEN']).each do |message_id| - msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822'] - logger.debug "Receiving message #{message_id}" if logger && logger.debug? + imap.uid_search(['NOT', 'SEEN']).each do |uid| + msg = imap.uid_fetch(uid,'RFC822')[0].attr['RFC822'] + logger.debug "Receiving message #{uid}" if logger && logger.debug? if MailHandler.receive(msg, options) - logger.debug "Message #{message_id} successfully received" if logger && logger.debug? + logger.debug "Message #{uid} successfully received" if logger && logger.debug? if imap_options[:move_on_success] - imap.copy(message_id, imap_options[:move_on_success]) + imap.uid_copy(uid, imap_options[:move_on_success]) end - imap.store(message_id, "+FLAGS", [:Seen, :Deleted]) + imap.uid_store(uid, "+FLAGS", [:Seen, :Deleted]) else - logger.debug "Message #{message_id} can not be processed" if logger && logger.debug? - imap.store(message_id, "+FLAGS", [:Seen]) + logger.debug "Message #{uid} can not be processed" if logger && logger.debug? + imap.uid_store(uid, "+FLAGS", [:Seen]) if imap_options[:move_on_failure] - imap.copy(message_id, imap_options[:move_on_failure]) - imap.store(message_id, "+FLAGS", [:Deleted]) + imap.uid_copy(uid, imap_options[:move_on_failure]) + imap.uid_store(uid, "+FLAGS", [:Deleted]) end end end imap.expunge + imap.logout + imap.disconnect end private diff -r d98d22a98252 -r afce8026aaeb lib/redmine/info.rb --- a/lib/redmine/info.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/info.rb Tue Sep 09 09:34:53 2014 +0100 @@ -10,16 +10,24 @@ s = "Environment:\n" s << [ ["Redmine version", Redmine::VERSION], - ["Ruby version", "#{RUBY_VERSION} (#{RUBY_PLATFORM})"], + ["Ruby version", "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"], ["Rails version", Rails::VERSION::STRING], ["Environment", Rails.env], ["Database adapter", ActiveRecord::Base.connection.adapter_name] - ].map {|info| " %-40s %s" % info}.join("\n") - s << "\nRedmine plugins:\n" + ].map {|info| " %-30s %s" % info}.join("\n") + "\n" + s << "SCM:\n" + Redmine::Scm::Base.all.each do |scm| + scm_class = "Repository::#{scm}".constantize + if scm_class.scm_available + s << " %-30s %s\n" % [scm, scm_class.scm_version_string] + end + end + + s << "Redmine plugins:\n" plugins = Redmine::Plugin.all if plugins.any? - s << plugins.map {|plugin| " %-40s %s" % [plugin.id.to_s, plugin.version.to_s]}.join("\n") + s << plugins.map {|plugin| " %-30s %s" % [plugin.id.to_s, plugin.version.to_s]}.join("\n") else s << " no plugin installed" end diff -r d98d22a98252 -r afce8026aaeb lib/redmine/menu_manager.rb --- a/lib/redmine/menu_manager.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/menu_manager.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -190,20 +190,17 @@ # Checks if a user is allowed to access the menu item by: # + # * Checking the url target (project only) # * Checking the conditions of the item - # * Checking the url target (project only) def allowed_node?(node, user, project) + if project && user && !user.allowed_to?(node.url, project) + return false + end if node.condition && !node.condition.call(project) # Condition that doesn't pass return false end - - if project - return user && user.allowed_to?(node.url, project) - else - # outside a project, all menu items allowed - return true - end + return true end end @@ -224,6 +221,8 @@ end class Mapper + attr_reader :menu, :menu_items + def initialize(menu, items) items[menu] ||= MenuNode.new(:root, {}) @menu = menu diff -r d98d22a98252 -r afce8026aaeb lib/redmine/mime_type.rb --- a/lib/redmine/mime_type.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/mime_type.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/pagination.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/redmine/pagination.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,244 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Pagination + class Paginator + attr_reader :item_count, :per_page, :page, :page_param + + def initialize(*args) + if args.first.is_a?(ActionController::Base) + args.shift + ActiveSupport::Deprecation.warn "Paginator no longer takes a controller instance as the first argument. Remove it from #new arguments." + end + item_count, per_page, page, page_param = *args + + @item_count = item_count + @per_page = per_page + page = (page || 1).to_i + if page < 1 + page = 1 + end + @page = page + @page_param = page_param || :page + end + + def offset + (page - 1) * per_page + end + + def first_page + if item_count > 0 + 1 + end + end + + def previous_page + if page > 1 + page - 1 + end + end + + def next_page + if last_item < item_count + page + 1 + end + end + + def last_page + if item_count > 0 + (item_count - 1) / per_page + 1 + end + end + + def first_item + item_count == 0 ? 0 : (offset + 1) + end + + def last_item + l = first_item + per_page - 1 + l > item_count ? item_count : l + end + + def linked_pages + pages = [] + if item_count > 0 + pages += [first_page, page, last_page] + pages += ((page-2)..(page+2)).to_a.select {|p| p > first_page && p < last_page} + end + pages = pages.compact.uniq.sort + if pages.size > 1 + pages + else + [] + end + end + + def items_per_page + ActiveSupport::Deprecation.warn "Paginator#items_per_page will be removed. Use #per_page instead." + per_page + end + + def current + ActiveSupport::Deprecation.warn "Paginator#current will be removed. Use .offset instead of .current.offset." + self + end + end + + # Paginates the given scope or model. Returns a Paginator instance and + # the collection of objects for the current page. + # + # Options: + # :parameter name of the page parameter + # + # Examples: + # @user_pages, @users = paginate User.where(:status => 1) + # + def paginate(scope, options={}) + options = options.dup + finder_options = options.extract!( + :conditions, + :order, + :joins, + :include, + :select + ) + if scope.is_a?(Symbol) || finder_options.values.compact.any? + return deprecated_paginate(scope, finder_options, options) + end + + paginator = paginator(scope.count, options) + collection = scope.limit(paginator.per_page).offset(paginator.offset).to_a + + return paginator, collection + end + + def deprecated_paginate(arg, finder_options, options={}) + ActiveSupport::Deprecation.warn "#paginate with a Symbol and/or find options is depreceted and will be removed. Use a scope instead." + klass = arg.is_a?(Symbol) ? arg.to_s.classify.constantize : arg + scope = klass.scoped(finder_options) + paginate(scope, options) + end + + def paginator(item_count, options={}) + options.assert_valid_keys :parameter, :per_page + + page_param = options[:parameter] || :page + page = (params[page_param] || 1).to_i + per_page = options[:per_page] || per_page_option + Paginator.new(item_count, per_page, page, page_param) + end + + module Helper + include Redmine::I18n + + # Renders the pagination links for the given paginator. + # + # Options: + # :per_page_links if set to false, the "Per page" links are not rendered + # + def pagination_links_full(*args) + pagination_links_each(*args) do |text, parameters, options| + if block_given? + yield text, parameters, options + else + link_to text, params.merge(parameters), options + end + end + end + + # Yields the given block with the text and parameters + # for each pagination link and returns a string that represents the links + def pagination_links_each(paginator, count=nil, options={}, &block) + options.assert_valid_keys :per_page_links + + per_page_links = options.delete(:per_page_links) + per_page_links = false if count.nil? + page_param = paginator.page_param + + html = '' + if paginator.previous_page + # \xc2\xab(utf-8) = « + text = "\xc2\xab " + l(:label_previous) + html << yield(text, {page_param => paginator.previous_page}, :class => 'previous') + ' ' + end + + previous = nil + paginator.linked_pages.each do |page| + if previous && previous != page - 1 + html << content_tag('span', '...', :class => 'spacer') + ' ' + end + if page == paginator.page + html << content_tag('span', page.to_s, :class => 'current page') + else + html << yield(page.to_s, {page_param => page}, :class => 'page') + end + html << ' ' + previous = page + end + + if paginator.next_page + # \xc2\xbb(utf-8) = » + text = l(:label_next) + " \xc2\xbb" + html << yield(text, {page_param => paginator.next_page}, :class => 'next') + ' ' + end + + html << content_tag('span', "(#{paginator.first_item}-#{paginator.last_item}/#{paginator.item_count})", :class => 'items') + ' ' + + if per_page_links != false && links = per_page_links(paginator, &block) + html << content_tag('span', links.to_s, :class => 'per-page') + end + + html.html_safe + end + + # Renders the "Per page" links. + def per_page_links(paginator, &block) + values = per_page_options(paginator.per_page, paginator.item_count) + if values.any? + links = values.collect do |n| + if n == paginator.per_page + content_tag('span', n.to_s) + else + yield(n, :per_page => n, paginator.page_param => nil) + end + end + l(:label_display_per_page, links.join(', ')).html_safe + end + end + + def per_page_options(selected=nil, item_count=nil) + options = Setting.per_page_options_array + if item_count && options.any? + if item_count > options.first + max = options.detect {|value| value >= item_count} || item_count + else + max = item_count + end + options = options.select {|value| value <= max || value == selected} + end + if options.empty? || (options.size == 1 && options.first == selected) + [] + else + options + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb lib/redmine/platform.rb --- a/lib/redmine/platform.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/platform.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/plugin.rb --- a/lib/redmine/plugin.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/plugin.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -64,15 +64,18 @@ end end end - def_field :name, :description, :url, :author, :author_url, :version, :settings + def_field :name, :description, :url, :author, :author_url, :version, :settings, :directory attr_reader :id # Plugin constructor def self.register(id, &block) p = new(id) p.instance_eval(&block) + # Set a default name if it was not provided during registration p.name(id.to_s.humanize) if p.name.nil? + # Set a default directory if it was not provided during registration + p.directory(File.join(self.directory, id.to_s)) if p.directory.nil? # Adds plugin locales if any # YAML translation files should be found under /config/locales/ @@ -137,12 +140,12 @@ @id = id.to_sym end - def directory - File.join(self.class.directory, id.to_s) + def public_directory + File.join(self.class.public_directory, id.to_s) end - def public_directory - File.join(self.class.public_directory, id.to_s) + def to_param + id end def assets_directory @@ -440,7 +443,7 @@ class Migrator < ActiveRecord::Migrator # We need to be able to set the 'current' plugin being migrated. cattr_accessor :current_plugin - + class << self # Runs the migrations from a plugin, up (or down) to the version given def migrate_plugin(plugin, version) @@ -448,7 +451,7 @@ return if current_version(plugin) == version migrate(plugin.migration_directory, version) end - + def current_version(plugin=current_plugin) # Delete migrations that don't match .. to_i will work because the number comes first ::ActiveRecord::Base.connection.select_values( @@ -456,14 +459,14 @@ ).delete_if{ |v| v.match(/-#{plugin.id}/) == nil }.map(&:to_i).max || 0 end end - + def migrated sm_table = self.class.schema_migrations_table_name ::ActiveRecord::Base.connection.select_values( "SELECT version FROM #{sm_table}" ).delete_if{ |v| v.match(/-#{current_plugin.id}/) == nil }.map(&:to_i).sort end - + def record_version_state_after_migrating(version) super(version.to_s + "-" + current_plugin.id.to_s) end diff -r d98d22a98252 -r afce8026aaeb lib/redmine/pop3.rb --- a/lib/redmine/pop3.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/pop3.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/safe_attributes.rb --- a/lib/redmine/safe_attributes.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/safe_attributes.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/redmine/scm/adapters.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,25 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +module Redmine + module Scm + module Adapters + class CommandFailed < StandardError #:nodoc: + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters/abstract_adapter.rb --- a/lib/redmine/scm/adapters/abstract_adapter.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/adapters/abstract_adapter.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,13 +16,15 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'cgi' +require 'redmine/scm/adapters' + +if RUBY_VERSION < '1.9' + require 'iconv' +end module Redmine module Scm module Adapters - class CommandFailed < StandardError #:nodoc: - end - class AbstractAdapter #:nodoc: # raised if scm command exited with error, e.g. unknown revision. @@ -214,13 +216,39 @@ Rails.logger end + # Path to the file where scm stderr output is logged + # Returns nil if the log file is not writable + def self.stderr_log_file + if @stderr_log_file.nil? + writable = false + path = Redmine::Configuration['scm_stderr_log_file'].presence + path ||= Rails.root.join("log/#{Rails.env}.scm.stderr.log").to_s + if File.exists?(path) + if File.file?(path) && File.writable?(path) + writable = true + else + logger.warn("SCM log file (#{path}) is not writable") + end + else + begin + File.open(path, "w") {} + writable = true + rescue => e + logger.warn("SCM log file (#{path}) cannot be created: #{e.message}") + end + end + @stderr_log_file = writable ? path : false + end + @stderr_log_file || nil + end + def self.shellout(cmd, options = {}, &block) if logger && logger.debug? logger.debug "Shelling out: #{strip_credential(cmd)}" - end - if Rails.env == 'development' - # Capture stderr when running in dev environment - cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}" + # Capture stderr in a log file + if stderr_log_file + cmd = "#{cmd} 2>>#{shell_quote(stderr_log_file)}" + end end begin mode = "r+" diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters/bazaar_adapter.rb --- a/lib/redmine/scm/adapters/bazaar_adapter.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/adapters/bazaar_adapter.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -104,13 +104,13 @@ re = %r{^V\s+(#{Regexp.escape(prefix)})?(\/?)([^\/]+)(\/?)\s+(\S+)\r?$} io.each_line do |line| next unless line =~ re - name_locale = $3.strip + name_locale, slash, revision = $3.strip, $4, $5.strip name = scm_iconv('UTF-8', @path_encoding, name_locale) entries << Entry.new({:name => name, :path => ((path.empty? ? "" : "#{path}/") + name), - :kind => ($4.blank? ? 'file' : 'dir'), + :kind => (slash.blank? ? 'file' : 'dir'), :size => nil, - :lastrev => Revision.new(:revision => $5.strip) + :lastrev => Revision.new(:revision => revision) }) end end diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters/cvs_adapter.rb --- a/lib/redmine/scm/adapters/cvs_adapter.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/adapters/cvs_adapter.rb Tue Sep 09 09:34:53 2014 +0100 @@ -335,7 +335,7 @@ # :pserver:anonymous@foo.bar:/path => /path # :ext:cvsservername:/path => /path def root_url_path - root_url.to_s.gsub(/^:.+:\d*/, '') + root_url.to_s.gsub(%r{^:.+?(?=/)}, '') end # convert a date/time into the CVS-format diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters/darcs_adapter.rb --- a/lib/redmine/scm/adapters/darcs_adapter.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/adapters/darcs_adapter.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters/filesystem_adapter.rb --- a/lib/redmine/scm/adapters/filesystem_adapter.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/adapters/filesystem_adapter.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # FileSystem adapter # File written by Paul Rivier, at Demotera. diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters/git_adapter.rb --- a/lib/redmine/scm/adapters/git_adapter.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/adapters/git_adapter.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -333,7 +333,7 @@ def annotate(path, identifier=nil) identifier = 'HEAD' if identifier.blank? - cmd_args = %w|blame| + cmd_args = %w|blame --encoding=UTF-8| cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path) blame = Annotate.new content = nil diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters/mercurial/redminehelper.py --- a/lib/redmine/scm/adapters/mercurial/redminehelper.py Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/adapters/mercurial/redminehelper.py Tue Sep 09 09:34:53 2014 +0100 @@ -79,8 +79,13 @@ def _branches(ui, repo): # see mercurial/commands.py:branches def iterbranches(): - for t, n in repo.branchtags().iteritems(): - yield t, n, repo.changelog.rev(n) + if getattr(repo, 'branchtags', None) is not None: + # Mercurial < 2.9 + for t, n in repo.branchtags().iteritems(): + yield t, n, repo.changelog.rev(n) + else: + for tag, heads, tip, isclosed in repo.branchmap().iterbranches(): + yield tag, tip, repo.changelog.rev(tip) def branchheads(branch): try: return repo.branchheads(branch, closed=False) diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters/mercurial_adapter.rb --- a/lib/redmine/scm/adapters/mercurial_adapter.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/adapters/subversion_adapter.rb --- a/lib/redmine/scm/adapters/subversion_adapter.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/adapters/subversion_adapter.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/scm/base.rb --- a/lib/redmine/scm/base.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/scm/base.rb Tue Sep 09 09:34:53 2014 +0100 @@ -4,7 +4,7 @@ class << self def all - @scms + @scms || [] end # Add a new SCM adapter and repository diff -r d98d22a98252 -r afce8026aaeb lib/redmine/search.rb --- a/lib/redmine/search.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/search.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/subclass_factory.rb --- a/lib/redmine/subclass_factory.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/subclass_factory.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/syntax_highlighting.rb --- a/lib/redmine/syntax_highlighting.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/syntax_highlighting.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -33,7 +33,6 @@ module CodeRay require 'coderay' - require 'coderay/helpers/file_type' class << self # Highlights +text+ as the content of +filename+ diff -r d98d22a98252 -r afce8026aaeb lib/redmine/themes.rb --- a/lib/redmine/themes.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/themes.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/thumbnail.rb --- a/lib/redmine/thumbnail.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/thumbnail.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/unified_diff.rb --- a/lib/redmine/unified_diff.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/unified_diff.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -28,17 +28,9 @@ lines = 0 @truncated = false diff_table = DiffTable.new(diff_type, diff_style) - diff.each do |line| - line_encoding = nil - if line.respond_to?(:force_encoding) - line_encoding = line.encoding - # TODO: UTF-16 and Japanese CP932 which is imcompatible with ASCII - # In Japan, diffrence between file path encoding - # and file contents encoding is popular. - line.force_encoding('ASCII-8BIT') - end - unless diff_table.add_line line - line.force_encoding(line_encoding) if line_encoding + diff.each do |line_raw| + line = Redmine::CodesetUtil.to_utf8_by_setting(line_raw) + unless diff_table.add_line(line) self << diff_table if diff_table.length > 0 diff_table = DiffTable.new(diff_type, diff_style) end @@ -83,7 +75,7 @@ @parsing = true end else - if line =~ /^[^\+\-\s@\\]/ + if line =~ %r{^[^\+\-\s@\\]} @parsing = false return false elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ @@ -207,10 +199,28 @@ while starting < max && line_left[starting] == line_right[starting] starting += 1 end + if (! "".respond_to?(:force_encoding)) && starting < line_left.size + while line_left[starting].ord.between?(128, 191) && starting > 0 + starting -= 1 + end + end ending = -1 - while ending >= -(max - starting) && line_left[ending] == line_right[ending] + while ending >= -(max - starting) && (line_left[ending] == line_right[ending]) ending -= 1 end + if (! "".respond_to?(:force_encoding)) && ending > (-1 * line_left.size) + while line_left[ending].ord.between?(128, 255) && ending < -1 + if line_left[ending].ord.between?(128, 191) + if line_left[ending + 1].ord.between?(128, 191) + ending += 1 + else + break + end + else + ending += 1 + end + end + end unless starting == 0 && ending == -1 [starting, ending] end @@ -268,6 +278,12 @@ private def line_to_html(line, offsets) + html = line_to_html_raw(line, offsets) + html.force_encoding('UTF-8') if html.respond_to?(:force_encoding) + html + end + + def line_to_html_raw(line, offsets) if offsets s = '' unless offsets.first == 0 diff -r d98d22a98252 -r afce8026aaeb lib/redmine/utils.rb --- a/lib/redmine/utils.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/utils.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/version.rb --- a/lib/redmine/version.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/version.rb Tue Sep 09 09:34:53 2014 +0100 @@ -3,8 +3,8 @@ module Redmine module VERSION #:nodoc: MAJOR = 2 - MINOR = 2 - TINY = 4 + MINOR = 4 + TINY = 6 # Branch values: # * official release: nil diff -r d98d22a98252 -r afce8026aaeb lib/redmine/views/api_template_handler.rb --- a/lib/redmine/views/api_template_handler.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/views/api_template_handler.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/views/builders.rb --- a/lib/redmine/views/builders.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/views/builders.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -15,6 +15,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +require 'redmine/views/builders/json' +require 'redmine/views/builders/xml' + module Redmine module Views module Builders diff -r d98d22a98252 -r afce8026aaeb lib/redmine/views/builders/json.rb --- a/lib/redmine/views/builders/json.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/views/builders/json.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'blankslate' +require 'redmine/views/builders/structure' module Redmine module Views @@ -25,7 +25,10 @@ def initialize(request, response) super - self.jsonp = (request.params[:callback] || request.params[:jsonp]).to_s.gsub(/[^a-zA-Z0-9_]/, '') + callback = request.params[:callback] || request.params[:jsonp] + if callback && Setting.jsonp_enabled? + self.jsonp = callback.to_s.gsub(/[^a-zA-Z0-9_]/, '') + end end def output diff -r d98d22a98252 -r afce8026aaeb lib/redmine/views/builders/structure.rb --- a/lib/redmine/views/builders/structure.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/views/builders/structure.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/views/builders/xml.rb --- a/lib/redmine/views/builders/xml.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/views/builders/xml.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/views/labelled_form_builder.rb --- a/lib/redmine/views/labelled_form_builder.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/views/labelled_form_builder.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/views/my_page/block.rb --- a/lib/redmine/views/my_page/block.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/views/my_page/block.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/views/other_formats_builder.rb --- a/lib/redmine/views/other_formats_builder.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/views/other_formats_builder.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/wiki_formatting.rb --- a/lib/redmine/wiki_formatting.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/wiki_formatting.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -86,7 +86,7 @@ AUTO_LINK_RE = %r{ ( # leading text <\w+.*?>| # leading HTML tag, or - [^=<>!:'"/]| # leading punctuation, or + [\s\(\[,;]| # leading punctuation, or ^ # beginning of line ) ( @@ -95,7 +95,7 @@ (?:www\.) # www.* ) ( - (\S+?) # url + ([^<]\S*?) # url (\/)? # slash ) ((?:>)?|[^[:alnum:]_\=\/;\(\)]*?) # post diff -r d98d22a98252 -r afce8026aaeb lib/redmine/wiki_formatting/macros.rb --- a/lib/redmine/wiki_formatting/macros.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/wiki_formatting/macros.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/wiki_formatting/textile/formatter.rb --- a/lib/redmine/wiki_formatting/textile/formatter.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/wiki_formatting/textile/formatter.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/redmine/wiki_formatting/textile/helper.rb --- a/lib/redmine/wiki_formatting/textile/helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/lib/redmine/wiki_formatting/textile/helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -22,11 +22,8 @@ def wikitoolbar_for(field_id) heads_for_wiki_formatter # Is there a simple way to link to a public resource? - url = "#{Redmine::Utils.relative_url_root}/help/wiki_syntax.html" - help_link = link_to(l(:setting_text_formatting), url, - :onclick => "window.open(\"#{ url }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;") - - javascript_tag("var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript help_link}'); wikiToolbar.draw();") + url = "#{Redmine::Utils.relative_url_root}/help/#{current_language.to_s.downcase}/wiki_syntax.html" + javascript_tag("var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript url}'); wikiToolbar.draw();") end def initial_page_content(page) diff -r d98d22a98252 -r afce8026aaeb lib/tasks/ci.rake --- a/lib/tasks/ci.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/ci.rake Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,4 @@ -desc "Run the Continous Integration tests for Redmine" +desc "Run the Continuous Integration tests for Redmine" task :ci do # RAILS_ENV and ENV[] can diverge so force them both to test ENV['RAILS_ENV'] = 'test' @@ -8,72 +8,85 @@ Rake::Task["ci:teardown"].invoke end -# Tasks can be hooked into by redefining them in a plugin namespace :ci do - desc "Setup Redmine for a new build." + desc "Setup Redmine for a new build" task :setup do - Rake::Task["ci:dump_environment"].invoke Rake::Task["tmp:clear"].invoke - Rake::Task["db:create"].invoke + Rake::Task["log:clear"].invoke + Rake::Task["db:create:all"].invoke Rake::Task["db:migrate"].invoke Rake::Task["db:schema:dump"].invoke + if scms = ENV['SCMS'] + scms.split(',').each do |scm| + Rake::Task["test:scm:setup:#{scm}"].invoke + end + else + Rake::Task["test:scm:setup:all"].invoke + end Rake::Task["test:scm:update"].invoke end desc "Build Redmine" task :build do - Rake::Task["test"].invoke + if test_suite = ENV['TEST_SUITE'] + Rake::Task["test:#{test_suite}"].invoke + else + Rake::Task["test"].invoke + end + # Rake::Task["test:ui"].invoke if RUBY_VERSION >= '1.9.3' end - # Use this to cleanup after building or run post-build analysis. desc "Finish the build" task :teardown do end +end - desc "Creates and configures the databases for the CI server" - task :database do - path = 'config/database.yml' - unless File.exists?(path) - database = ENV['DATABASE_ADAPTER'] - ruby = ENV['RUBY_VER'].gsub('.', '').gsub('-', '') - branch = ENV['BRANCH'].gsub('.', '').gsub('-', '') - dev_db_name = "ci_#{branch}_#{ruby}_dev" - test_db_name = "ci_#{branch}_#{ruby}_test" +desc "Creates database.yml for the CI server" +file 'config/database.yml' do + require 'yaml' + database = ENV['DATABASE_ADAPTER'] + ruby = ENV['RUBY_VER'].gsub('.', '').gsub('-', '') + branch = ENV['BRANCH'].gsub('.', '').gsub('-', '') + dev_db_name = "ci_#{branch}_#{ruby}_dev" + test_db_name = "ci_#{branch}_#{ruby}_test" - case database - when 'mysql' - raise "Error creating databases" unless - system(%|mysql -u jenkins --password=jenkins -e 'create database #{dev_db_name} character set utf8;'|) && - system(%|mysql -u jenkins --password=jenkins -e 'create database #{test_db_name} character set utf8;'|) - dev_conf = { 'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins', 'encoding' => 'utf8' } - test_conf = { 'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins', 'encoding' => 'utf8' } - when 'postgresql' - raise "Error creating databases" unless - system(%|psql -U jenkins -d postgres -c "create database #{dev_db_name} owner jenkins encoding 'UTF8';"|) && - system(%|psql -U jenkins -d postgres -c "create database #{test_db_name} owner jenkins encoding 'UTF8';"|) - dev_conf = { 'adapter' => 'postgresql', 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' } - test_conf = { 'adapter' => 'postgresql', 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' } - when 'sqlite3' - dev_conf = { 'adapter' => 'sqlite3', 'database' => "db/#{dev_db_name}.sqlite3" } - test_conf = { 'adapter' => 'sqlite3', 'database' => "db/#{test_db_name}.sqlite3" } - else - raise "Unknown database" - end - - File.open(path, 'w') do |f| - f.write YAML.dump({'development' => dev_conf, 'test' => test_conf}) - end + case database + when 'mysql' + dev_conf = {'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), + 'database' => dev_db_name, 'host' => 'localhost', + 'encoding' => 'utf8'} + if ENV['RUN_ON_NOT_OFFICIAL'] + dev_conf['username'] = 'root' + else + dev_conf['username'] = 'jenkins' + dev_conf['password'] = 'jenkins' end + test_conf = dev_conf.merge('database' => test_db_name) + when 'postgresql' + dev_conf = {'adapter' => 'postgresql', 'database' => dev_db_name, + 'host' => 'localhost'} + if ENV['RUN_ON_NOT_OFFICIAL'] + dev_conf['username'] = 'postgres' + else + dev_conf['username'] = 'jenkins' + dev_conf['password'] = 'jenkins' + end + test_conf = dev_conf.merge('database' => test_db_name) + when /sqlite3/ + dev_conf = {'adapter' => (Object.const_defined?(:JRUBY_VERSION) ? + 'jdbcsqlite3' : 'sqlite3'), + 'database' => "db/#{dev_db_name}.sqlite3"} + test_conf = dev_conf.merge('database' => "db/#{test_db_name}.sqlite3") + when 'sqlserver' + dev_conf = {'adapter' => 'sqlserver', 'database' => dev_db_name, + 'host' => 'mssqlserver', 'port' => 1433, + 'username' => 'jenkins', 'password' => 'jenkins'} + test_conf = dev_conf.merge('database' => test_db_name) + else + abort "Unknown database" end - desc "Dump the environment information to a BUILD_ENVIRONMENT ENV variable for debugging" - task :dump_environment do - - ENV['BUILD_ENVIRONMENT'] = ['ruby -v', 'gem -v', 'gem list'].collect do |command| - result = `#{command}` - "$ #{command}\n#{result}" - end.join("\n") - + File.open('config/database.yml', 'w') do |f| + f.write YAML.dump({'development' => dev_conf, 'test' => test_conf}) end end - diff -r d98d22a98252 -r afce8026aaeb lib/tasks/ciphering.rake --- a/lib/tasks/ciphering.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/ciphering.rake Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/tasks/email.rake --- a/lib/tasks/email.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/email.rake Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -29,6 +29,8 @@ create: create a user account no_permission_check=1 disable permission checking when receiving the email + no_account_notice=1 disable new user account notification + default_group=foo,bar adds created user to foo and bar groups Issue attributes control options: project=PROJECT identifier of the target project @@ -53,13 +55,7 @@ END_DESC task :read => :environment do - options = { :issue => {} } - %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } - options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] - options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] - options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] - - MailHandler.receive(STDIN.read, options) + MailHandler.receive(STDIN.read, MailHandler.extract_options_from_env(ENV)) end desc <<-END_DESC @@ -73,6 +69,8 @@ create: create a user account no_permission_check=1 disable permission checking when receiving the email + no_account_notice=1 disable new user account notification + default_group=foo,bar adds created user to foo and bar groups Available IMAP options: host=HOST IMAP server host (default: 127.0.0.1) @@ -124,13 +122,7 @@ :move_on_success => ENV['move_on_success'], :move_on_failure => ENV['move_on_failure']} - options = { :issue => {} } - %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } - options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] - options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] - options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] - - Redmine::IMAP.check(imap_options, options) + Redmine::IMAP.check(imap_options, MailHandler.extract_options_from_env(ENV)) end desc <<-END_DESC @@ -157,13 +149,7 @@ :password => ENV['password'], :delete_unprocessed => ENV['delete_unprocessed']} - options = { :issue => {} } - %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } - options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] - options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] - options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] - - Redmine::POP3.check(pop_options, options) + Redmine::POP3.check(pop_options, MailHandler.extract_options_from_env(ENV)) end desc "Send a test email to the user with the provided login name" diff -r d98d22a98252 -r afce8026aaeb lib/tasks/jdbc.rake --- a/lib/tasks/jdbc.rake Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -# This file was generated by the "jdbc" generator, which is provided -# by the activerecord-jdbc-adapter gem. -# -# This file allows you to use Rails' various db:* tasks with JDBC. -if defined?(JRUBY_VERSION) - require 'jdbc_adapter' - require 'jdbc_adapter/rake_tasks' -end diff -r d98d22a98252 -r afce8026aaeb lib/tasks/load_default_data.rake --- a/lib/tasks/load_default_data.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/load_default_data.rake Tue Sep 09 09:34:53 2014 +0100 @@ -2,6 +2,7 @@ namespace :redmine do task :load_default_data => :environment do + require 'custom_field' include Redmine::I18n set_language_if_valid('en') diff -r d98d22a98252 -r afce8026aaeb lib/tasks/locales.rake --- a/lib/tasks/locales.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/locales.rake Tue Sep 09 09:34:53 2014 +0100 @@ -43,7 +43,15 @@ files = Dir.glob(File.join(dir,'*.{yaml,yml}')) files.sort.each do |file| puts "parsing #{file}..." - file_strings = YAML.load(File.read(file)) + file_strings = YAML.load_file(file) + unless file_strings.is_a?(Hash) + puts "#{file}: content is not a Hash (#{file_strings.class.name})" + next + end + unless file_strings.keys.size == 1 + puts "#{file}: content has multiple keys (#{file_strings.keys.size})" + next + end file_strings = file_strings[file_strings.keys.first] file_strings.each do |key, string| diff -r d98d22a98252 -r afce8026aaeb lib/tasks/migrate_from_mantis.rake --- a/lib/tasks/migrate_from_mantis.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/migrate_from_mantis.rake Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,7 +18,7 @@ desc 'Mantis migration script' require 'active_record' -require 'iconv' +require 'iconv' if RUBY_VERSION < '1.9' require 'pp' namespace :redmine do @@ -30,7 +30,7 @@ assigned_status = IssueStatus.find_by_position(2) resolved_status = IssueStatus.find_by_position(3) feedback_status = IssueStatus.find_by_position(4) - closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } + closed_status = IssueStatus.where(:is_closed => true).first STATUS_MAPPING = {10 => DEFAULT_STATUS, # new 20 => feedback_status, # feedback 30 => DEFAULT_STATUS, # acknowledged @@ -53,7 +53,7 @@ TRACKER_BUG = Tracker.find_by_position(1) TRACKER_FEATURE = Tracker.find_by_position(2) - roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') + roles = Role.where(:builtin => 0).order('position ASC').all manager_role = roles[0] developer_role = roles[1] DEFAULT_ROLE = roles.last @@ -119,7 +119,7 @@ has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id def identifier - read_attribute(:name).gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH) + read_attribute(:name).downcase.gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH) end end @@ -241,7 +241,7 @@ User.delete_all "login <> 'admin'" users_map = {} users_migrated = 0 - MantisUser.find(:all).each do |user| + MantisUser.all.each do |user| u = User.new :firstname => encode(user.firstname), :lastname => encode(user.lastname), :mail => user.email, @@ -263,7 +263,7 @@ projects_map = {} versions_map = {} categories_map = {} - MantisProject.find(:all).each do |project| + MantisProject.all.each do |project| p = Project.new :name => encode(project.name), :description => encode(project.description) p.identifier = project.identifier @@ -347,7 +347,7 @@ bug.bug_files.each do |file| a = Attachment.new :created_on => file.date_added a.file = file - a.author = User.find :first + a.author = User.first a.container = i a.save end @@ -365,7 +365,7 @@ # Bug relationships print "Migrating bug relations" - MantisBugRelationship.find(:all).each do |relation| + MantisBugRelationship.all.each do |relation| next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id] r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type] r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id]) @@ -379,7 +379,7 @@ # News print "Migrating news" News.destroy_all - MantisNews.find(:all, :conditions => 'project_id > 0').each do |news| + MantisNews.where('project_id > 0').all.each do |news| next unless projects_map[news.project_id] n = News.new :project_id => projects_map[news.project_id], :title => encode(news.headline[0..59]), @@ -395,7 +395,7 @@ # Custom fields print "Migrating custom fields" IssueCustomField.destroy_all - MantisCustomField.find(:all).each do |field| + MantisCustomField.all.each do |field| f = IssueCustomField.new :name => field.name[0..29], :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format], :min_length => field.length_min, @@ -407,7 +407,7 @@ print '.' STDOUT.flush # Trackers association - f.trackers = Tracker.find :all + f.trackers = Tracker.all # Projects association field.projects.each do |project| @@ -440,9 +440,7 @@ end def self.encoding(charset) - @ic = Iconv.new('UTF-8', charset) - rescue Iconv::InvalidEncoding - return false + @charset = charset end def self.establish_connection(params) @@ -454,9 +452,12 @@ end def self.encode(text) - @ic.iconv text - rescue - text + if RUBY_VERSION < '1.9' + @ic ||= Iconv.new('UTF-8', @charset) + @ic.iconv text + else + text.to_s.force_encoding(@charset).encode('UTF-8') + end end end @@ -502,10 +503,20 @@ # Make sure bugs can refer bugs in other projects Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations' - # Turn off email notifications - Setting.notified_events = [] + old_notified_events = Setting.notified_events + old_password_min_length = Setting.password_min_length + begin + # Turn off email notifications temporarily + Setting.notified_events = [] + Setting.password_min_length = 4 + # Run the migration + MantisMigrate.establish_connection db_params + MantisMigrate.migrate + ensure + # Restore previous settings + Setting.notified_events = old_notified_events + Setting.password_min_length = old_password_min_length + end - MantisMigrate.establish_connection db_params - MantisMigrate.migrate end end diff -r d98d22a98252 -r afce8026aaeb lib/tasks/migrate_from_trac.rake --- a/lib/tasks/migrate_from_trac.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/migrate_from_trac.rake Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,7 +16,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'active_record' -require 'iconv' +require 'iconv' if RUBY_VERSION < '1.9' require 'pp' namespace :redmine do @@ -30,7 +30,7 @@ assigned_status = IssueStatus.find_by_position(2) resolved_status = IssueStatus.find_by_position(3) feedback_status = IssueStatus.find_by_position(4) - closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } + closed_status = IssueStatus.where(:is_closed => true).first STATUS_MAPPING = {'new' => DEFAULT_STATUS, 'reopened' => feedback_status, 'assigned' => assigned_status, @@ -61,7 +61,7 @@ 'patch' =>TRACKER_FEATURE } - roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') + roles = Role.where(:builtin => 0).order('position ASC').all manager_role = roles[0] developer_role = roles[1] DEFAULT_ROLE = roles.last @@ -153,7 +153,11 @@ private def trac_fullpath attachment_type = read_attribute(:type) - trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) } + #replace exotic characters with their hex representation to avoid invalid filenames + trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) do |x| + codepoint = RUBY_VERSION < '1.9' ? x[0] : x.codepoints.to_a[0] + sprintf('%%%02x', codepoint) + end "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}" end end @@ -245,8 +249,8 @@ if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name') name = name_attr.value end - name =~ (/(.*)(\s+\w+)?/) - fn = $1.strip + name =~ (/(\w+)(\s+\w+)?/) + fn = ($1 || "-").strip ln = ($2 || '-').strip u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), @@ -257,9 +261,9 @@ u.password = 'trac' u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') # finally, a default user is used if the new user is not valid - u = User.find(:first) unless u.save + u = User.first unless u.save end - # Make sure he is a member of the project + # Make sure user is a member of the project if project_member && !u.member_of?(@target_project) role = DEFAULT_ROLE if u.admin @@ -390,7 +394,7 @@ # Components print "Migrating components" issues_category_map = {} - TracComponent.find(:all).each do |component| + TracComponent.all.each do |component| print '.' STDOUT.flush c = IssueCategory.new :project => @target_project, @@ -404,7 +408,7 @@ # Milestones print "Migrating milestones" version_map = {} - TracMilestone.find(:all).each do |milestone| + TracMilestone.all.each do |milestone| print '.' STDOUT.flush # First we try to find the wiki page... @@ -443,18 +447,18 @@ :field_format => 'string') next if f.new_record? - f.trackers = Tracker.find(:all) + f.trackers = Tracker.all f.projects << @target_project custom_field_map[field.name] = f end puts # Trac 'resolution' field as a Redmine custom field - r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" }) + r = IssueCustomField.where(:name => "Resolution").first r = IssueCustomField.new(:name => 'Resolution', :field_format => 'list', :is_filter => true) if r.nil? - r.trackers = Tracker.find(:all) + r.trackers = Tracker.all r.projects << @target_project r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq r.save! @@ -549,7 +553,7 @@ # Wiki print "Migrating wiki" if wiki.save - TracWikiPage.find(:all, :order => 'name, version').each do |page| + TracWikiPage.order('name, version').all.each do |page| # Do not migrate Trac manual wiki pages next if TRAC_WIKI_PAGES.include?(page.name) wiki_edit_count += 1 @@ -603,10 +607,7 @@ end def self.encoding(charset) - @ic = Iconv.new('UTF-8', charset) - rescue Iconv::InvalidEncoding - puts "Invalid encoding!" - return false + @charset = charset end def self.set_trac_directory(path) @@ -713,11 +714,13 @@ end end - private def self.encode(text) - @ic.iconv text - rescue - text + if RUBY_VERSION < '1.9' + @ic ||= Iconv.new('UTF-8', @charset) + @ic.iconv text + else + text.to_s.force_encoding(@charset).encode('UTF-8') + end end end @@ -763,10 +766,19 @@ prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier} puts - # Turn off email notifications - Setting.notified_events = [] - - TracMigrate.migrate + old_notified_events = Setting.notified_events + old_password_min_length = Setting.password_min_length + begin + # Turn off email notifications temporarily + Setting.notified_events = [] + Setting.password_min_length = 4 + # Run the migration + TracMigrate.migrate + ensure + # Restore previous settings + Setting.notified_events = old_notified_events + Setting.password_min_length = old_password_min_length + end end end diff -r d98d22a98252 -r afce8026aaeb lib/tasks/redmine.rake --- a/lib/tasks/redmine.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/redmine.rake Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -21,6 +21,11 @@ task :prune => :environment do Attachment.prune end + + desc 'Moves attachments stored at the root of the file directory (ie. created before Redmine 2.3) to their subdirectories' + task :move_to_subdirectories => :environment do + Attachment.move_from_root_to_target_directory + end end namespace :tokens do diff -r d98d22a98252 -r afce8026aaeb lib/tasks/reminder.rake --- a/lib/tasks/reminder.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/reminder.rake Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb lib/tasks/testing.rake --- a/lib/tasks/testing.rake Wed May 07 14:15:02 2014 +0100 +++ b/lib/tasks/testing.rake Tue Sep 09 09:34:53 2014 +0100 @@ -100,4 +100,11 @@ t.test_files = FileList['test/integration/routing/*_test.rb'] end Rake::Task['test:rdm_routing'].comment = "Run the routing tests" + + Rake::TestTask.new(:ui => "db:test:prepare") do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/ui/**/*_test.rb'] + end + Rake::Task['test:ui'].comment = "Run the UI tests with Capybara (PhantomJS listening on port 4444 is required)" end diff -r d98d22a98252 -r afce8026aaeb plugins/redmine_bibliography/app/views/activities/index.html.erb --- a/plugins/redmine_bibliography/app/views/activities/index.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/plugins/redmine_bibliography/app/views/activities/index.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -15,15 +15,15 @@ <% @events_by_day.keys.sort.reverse.each do |day| %>

    <%= format_activity_day(day) %>

    -<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> +<% sort_activity_events(@events_by_day[day]).each do |e, in_group| -%> <%- if e.class != Publication -%> -
    +
    <%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>"> <%= avatar(e.event_author, :size => "24") if e.respond_to?(:event_author) %> <%= format_time(e.event_datetime, false) %> <%= content_tag('span', h(e.project), :class => 'project') if @project.nil? || @project != e.project %> <%= link_to format_activity_title(e.event_title), e.event_url %>
    -
    +
    "> <%= format_activity_description(e.event_description) %> <%= link_to_user(e.event_author) if e.respond_to?(:event_author) %>
    @@ -74,11 +74,19 @@ <% content_for :sidebar do %> <% form_tag({}, :method => :get) do %>

    <%= l(:label_activity) %>

    -

    <% @activity.event_types.each do |t| %> -<%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> - -
    -<% end %>

    + +
      +<% @activity.event_types.each do |t| %> +
    • + <%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> + +
    • +<% end %> +
    + <% if @project && @project.descendants.active.any? %> <%= hidden_field_tag 'with_subprojects', 0 %>

    diff -r d98d22a98252 -r afce8026aaeb plugins/redmine_bibliography/app/views/projects/show.html.erb --- a/plugins/redmine_bibliography/app/views/projects/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/plugins/redmine_bibliography/app/views/projects/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@
    <% if User.current.allowed_to?(:add_subprojects, @project) %> - <%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'new', :parent_id => @project}, :class => 'icon icon-add' %> + <%= link_to l(:label_subproject_new), new_project_path(:parent_id => @project), :class => 'icon icon-add' %> <% end %> <% if User.current.allowed_to?(:close_project, @project) %> @@ -37,7 +37,7 @@ <% unless @project.homepage.blank? %>
  • <%=l(:field_homepage)%>: <%= link_to h(@project.homepage), @project.homepage %>
  • <% end %> <% if @subprojects.any? %>
  • <%=l(:label_subproject_plural)%>: - <%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ").html_safe %>
  • + <%= @subprojects.collect{|p| link_to p, project_path(p)}.join(", ").html_safe %> <% end %>
    @@ -77,23 +77,21 @@

    <%=l(:label_issue_tracking)%>

      <% for tracker in @trackers %> -
    • <%= link_to h(tracker.name), :controller => 'issues', :action => 'index', :project_id => @project, - :set_filter => 1, - "tracker_id" => tracker.id %>: +
    • <%= link_to h(tracker.name), project_issues_path(@project, :set_filter => 1, :tracker_id => tracker.id) %>: <%= l(:label_x_open_issues_abbr_on_total, :count => @open_issues_by_tracker[tracker].to_i, :total => @total_issues_by_tracker[tracker].to_i) %>
    • <% end %>

    - <%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %> + <%= link_to l(:label_issue_view_all), project_issues_path(@project, :set_filter => 1) %> <% if User.current.allowed_to?(:view_calendar, @project, :global => true) %> - | <%= link_to(l(:label_calendar), :controller => 'calendars', :action => 'show', :project_id => @project) %> - <% end %> - <% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> - | <%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %> - <% end %> -

    + | <%= link_to l(:label_calendar), project_calendar_path(@project) %> + <% end %> + <% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> + | <%= link_to l(:label_gantt), project_gantt_path(@project) %> + <% end %> +

    <% end %> <%= call_hook(:view_projects_show_left, :project => @project) %> @@ -109,7 +107,7 @@

    <%=l(:label_news_latest)%>

    <%= render :partial => 'news/news', :collection => @news %> -

    <%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %>

    +

    <%= link_to l(:label_news_view_all), project_news_index_path(@project) %>

    <% end %> diff -r d98d22a98252 -r afce8026aaeb plugins/redmine_bibliography/app/views/users/show.html.erb --- a/plugins/redmine_bibliography/app/views/users/show.html.erb Wed May 07 14:15:02 2014 +0100 +++ b/plugins/redmine_bibliography/app/views/users/show.html.erb Tue Sep 09 09:34:53 2014 +0100 @@ -72,7 +72,7 @@

    <%= link_to l(:label_activity), :controller => 'activities', :action => 'index', :id => nil, :user_id => @user, :from => @events_by_day.keys.first %>

    -<%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %> +<%=l(:label_reported_issues)%>: <%= Issue.where(:author_id => @user.id).count %>

    diff -r d98d22a98252 -r afce8026aaeb plugins/redmine_tags/lib/redmine_tags/patches/projects_controller_patch.rb --- a/plugins/redmine_tags/lib/redmine_tags/patches/projects_controller_patch.rb Wed May 07 14:15:02 2014 +0100 +++ b/plugins/redmine_tags/lib/redmine_tags/patches/projects_controller_patch.rb Tue Sep 09 09:34:53 2014 +0100 @@ -27,40 +27,10 @@ # Project.visible_roots.find(@projects).count - @project_pages = ActionController::Pagination::Paginator.new self, @project_count, @limit, params['page'] + @project_pages = Redmine::Pagination::Paginator.new @project_count, @limit, params['page'] @offset ||= @project_pages.current.offset end - # def set_fieldset_status -# - # # luisf. test for missing parameters……… - # field = params[:field_id] - # status = params[:status] -# - # session[(field + "_status").to_sym] = status - # render :nothing => true - # end - - # gets the status of the collabsible fieldsets - # def get_fieldset_statuses - # if session[:my_projects_fieldset_status].nil? - # @myproj_status = "true" - # else - # @myproj_status = session[:my_projects_fieldset_status] - # end -# - # if session[:filters_fieldset_status].nil? - # @filter_status = "false" - # else - # @filter_status = session[:filters_fieldset_status] - # end -# - # if params && params[:project] && !params[:project][:tag_list].# nil? - # @filter_status = "true" - # end -# - # end - # Lists visible projects. Paginator is for top-level projects only # (subprojects belong to them) def filtered_index diff -r d98d22a98252 -r afce8026aaeb public/404.html --- a/public/404.html Wed May 07 14:15:02 2014 +0100 +++ b/public/404.html Tue Sep 09 09:34:53 2014 +0100 @@ -4,7 +4,7 @@ Redmine 404 error + + + +

    Wiki Syntax Quick Reference

    + +
    + <%=l(:label_issue_status)%><%=l(:label_issue_status)%>
    + <%=h status.name %>
    + <%=h name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %> - <%= field_permission_tag(@permissions, status, field) %> + + <%= field_permission_tag(@permissions, status, field, @role) %> + <% unless status == @statuses.last %>»<% end %>
    + <%=h field.name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %> - <%= field_permission_tag(@permissions, status, field) %> + + <%= field_permission_tag(@permissions, status, field, @role) %> + <% unless status == @statuses.last %>»<% end %>
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/ar/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ar/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/az/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/az/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/az/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/az/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/bg/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/bg/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/bg/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/bg/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/bs/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/bs/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/bs/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/bs/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/ca/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ca/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/ca/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ca/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/cs/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/cs/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Syntaxe Wiki - rychlý náhled

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Styly písma
    TuÄný*TuÄný*TuÄný
    Kurzívou_Kurzívou_Kurzívou
    Podtržený+Podtržený+Podtržený
    Smazaný-Smazaný-Smazaný
    ??Citace??Citace
    Vnořený kód@Vnořený kód@Vnořený kód
    Předformátovaný text<pre>
     Å™Ã¡dky
     kódu
    </pre>
    +
    + řádky
    + kódu
    +
    +
    Seznamy
    Nesetříděný seznam* Položka 1
    * Položka 2
    • Položka 1
    • Položka 2
    Setříděný seznam# Položka 1
    # Položka 2
    1. Položka 1
    2. Položka 2
    Nadpisy
    Nadpis 1h1. Nadpis 1

    Nadpis 1

    Nadpis 2h2. Nadpis 2

    Nadpis 2

    Nadpis 3h3. Nadpis 3

    Nadpis 3

    Odkazy
    http://foo.barhttp://foo.bar
    "Odkaz":http://foo.barOdkaz
    Redmine odkazy
    Odkaz na Wiki stránku[[Wiki stránka]]Wiki stránka
    Úkol #12Úkol #12
    Revize r43Revize r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Vnořené obrázky
    Obrázek!url_obrázku!
    !vnořený_obrázek!
    + +

    Více informací

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/cs/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/cs/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +Formátování Wiki v Redminu + + + + + +

    Formátování Wiki

    + +

    Odkazy

    + +

    Odkazy Redmine

    + +

    Redmine umožňuje hypertextové odkazy mezi jednotlivými zdroji (úkoly, revize, wiki stránky...) kdekoli, kde je použito Wiki formátování.

    +
      +
    • Odkaz na úkol: #124 (zobrazí #124, odkaz je pÅ™eÅ¡krtnutý, jestliže je úkol uzavÅ™en)
    • +
    • Odkaz na poznámku k úkolu: #124-6, nebo #124#note-6
    • +
    + +

    Odkazy Wiki:

    + +
      +
    • [[PříruÄka]] zobrazí odkaz na stránku nazvanou "PříruÄka": PříruÄka.
    • +
    • [[PříruÄka#ÄtÄ›te-více]] Vás pÅ™enese ke kotvÄ› "ÄtÄ›te-více". Nadpisy mají automaticky pÅ™iÅ™azené kotvy, na které se můžete odkazovat: PříruÄka.
    • +
    • [[PříruÄka|Uživatelský manuál]] zobrazí odkaz na tu samou stránku, ale s jiným textem: Uživatelský manuál.
    • +
    + +

    Můžete se také odkazovat na Wiki stránky jiného projektu:

    + +
      +
    • [[projekt_test:NÄ›jaká stránka]] zobrazí odkaz na stránku s názvem "NÄ›jaká stránka" na Wiki projektu projekt_test.
    • +
    • [[projekt_test:]] zobrazí odkaz na hlavní Wiki stránku projektu projekt_test.
    • +
    + +

    Odkazy na Wiki stránky jsou zobrazeny ÄervenÄ› v případÄ›, že odkazovaná stránka dosud neexistuje, napÅ™.: Neexistující stránka.

    + +

    Odkazy na další zdroje:

    + +
      +
    • Dokumenty: +
        +
      • document#17 (odkaz na dokument s ID 17)
      • +
      • document:Úvod (odkaz na dokument s názvem "Úvod")
      • +
      • document:"NÄ›jaký dokument" (Uvozovky se mohou použít v případÄ›, že název obsahuje mezery.)
      • +
      • projekt_test:document:"NÄ›jaký dokument" (odkaz na dokument s názvem "NÄ›jaký dokument" v jiném projektu "projekt_test")
      • +
    • +
    + +
      +
    • Verze: +
        +
      • version#3 (odkaz na verzi s ID 3)
      • +
      • version:1.0.0 odkaz na verzi s názvem "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • projekt_test:version:1.0.0 (odkaz na verzi "1.0.0" jiného projektu "projekt_test")
      • +
    • +
    + +
      +
    • Přílohy: +
        +
      • attachment:soubor.zip (odkaz na přílohu aktuálního objektu s názvem soubor.zip)
      • +
      • AktuálnÄ› mohou být odkazovány pouze přílohy aktuálního objektu (u úkolu mohou být odkazy pouze na přílohy danného úkolu).
      • +
    • +
    + +
      +
    • Revize: +
        +
      • r758 (odkaz na revizi)
      • +
      • commit:c6f4d0fd (odkaz na revizi s neÄíselným oznaÄním revize)
      • +
      • svn1|r758 (odkaz na revizi urÄitého repozitáře, pro projekty s více repozitáři)
      • +
      • commit:hg|c6f4d0fd (odkaz na revizi s neÄíselným oznaÄním revize urÄitého repozitáře, pro projekty s více repozitáři)
      • +
      • projekt_test:r758 (odkaz na revizi jiného projektu)
      • +
      • projekt_test:commit:c6f4d0fd (odkaz na revizi s neÄíselným oznaÄním revize jiného projektu)
      • +
    • +
    + +
      +
    • Soubory repositáře: +
        +
      • source:some/file (odkaz na soubor umístÄ›ný v /some/file respozitáře projektu)
      • +
      • source:some/file@52 (odkaz na revizi souboru Ä. 52)
      • +
      • source:some/file#L120 (odkaz na 120. řádek souboru)
      • +
      • source:some/file@52#L120 (odkaz na 120. řádek revize souboru Ä. 52)
      • +
      • source:"some file@52#L120" (použijte uvozovky, když URL obsahuje mezery)
      • +
      • export:some/file (vynutit stažení souboru)
      • +
      • source:svn1|some/file (odkaz na soubor urÄitého repozitáře, pro projekty s více repositáři)
      • +
      • projekt_test:source:some/file (odkaz na soubor umístÄ›ný v /some/file repositáře projektu "projekt_test")
      • +
      • projekt_test:export:some/file (vynutit stažení souboru umístÄ›ného v /some/file repositáře projektu "projekt_test")
      • +
    • +
    + +
      +
    • PříspÄ›vky diskuzního fóra: +
        +
      • message#1218 (odkaz na příspÄ›vek s ID 1218)
      • +
    • +
    + +
      +
    • Projekty: +
        +
      • project#3 (odkaz na projekt s ID 3)
      • +
      • project:projekt_test (odkaz na projekt pojmenovaný "projekt_test")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • Zabránit parsování Redmine odkazů, lze vložením vykÅ™iÄníku pÅ™ed odkaz: !
    • +
    + + +

    Externí odkazy

    + +

    URL odkazy a emaily jsou automaticky zobrazeny jako klikací odkaz:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    zobrazí: http://www.redmine.org,

    + +

    Jestliže chcete zobrazit urÄitý text místo URL, můžete použít standardní syntaxi textile:

    + +
    +"Webová stránka Redmine":http://www.redmine.org
    +
    + +

    zobrazí: Webová stránka Redmine

    + + +

    Formátování textu

    + + +

    Pro nadpisy, tuÄný text, tabulky a seznamy, Redmine podporuje syntaxi Textile. Podívejte se na http://en.wikipedia.org/wiki/Textile_(markup_language) pro informace o využití tÄ›chto vlastností. NÄ›kolik příkladů je uvedeno níže, ale Textile toho dokáže mnohem víc.

    + +

    Styly písma

    + +
    +* *tuÄný*
    +* _kurzíva_
    +* _*tuÄná kurzíva*_
    +* +podtržený+
    +* -přeškrtnutý-
    +
    + +

    Zobrazí:

    + +
      +
    • tuÄný
    • +
    • kurzíva
    • +
    • tuÄná kurzíva
    • +
    • podtržený
    • +
    • pÅ™eÅ¡krtnutý
    • +
    + +

    Vložené obrázky

    + +
      +
    • !image_url! zobrazí obrázek z odkazu (syntaxe textile)
    • +
    • !>image_url! obrázek zarovnaný napravo
    • +
    • Jestliže máte obrázek pÅ™iložený k Wiki stránce, může být zobrazen jako vložený obrázek pomocí jeho jména: !prilozeny_obrazek.png!
    • +
    + +

    Nadpisy

    + +
    +h1. Nadpis 1. úrovně
    +h2. Nadpis 2. úrovně
    +h3. Nadpis 3. úrovně
    +
    + +

    Redmine přiřadí kotvu ke každému nadpisu, takže se na ně lze odkazovat pomocí "#Nadpis", "#Podnadpis" atd.

    + + +

    Odstavce

    + +
    +p>. zarovnaný doprava
    +p=. zarovnaný na střed
    +
    + +

    Toto je odstavec zarovnaný na střed.

    + + +

    Citace

    + +

    ZaÄnÄ›te odstavec s bq.

    + +
    +bq. Rails je framework pro vývoj webových aplikací podle modelu Model-View-Control.
    +Vše, co je potřeba, je databázový a webový server.
    +
    + +

    Zobrazí:

    + +
    +

    Rails je framework pro vývoj webových aplikací podle modelu Model-View-Control.
    Vše, co je potřeba, je databázový a webový server.

    +
    + + +

    Obsah

    + +
    +{{toc}} => obsah zarovnaný doleva
    +{{>toc}} => obsah zarovnaný doprava
    +
    + +

    Vodorovná Äára

    + +
    +---
    +
    + +

    Makra

    + +

    Redmine obsahuje následující vestavěná makra:

    + +

    hello_world

    Jednoduché makro.

    include

    Vloží Wiki stránku. Např.:

    + +
    {{include(Foo)}}
    macro_list

    Zobrazí seznam vÅ¡ech dostupných maker, vÄetnÄ› jejich popisu, existuje-li.

    + + +

    Zvýrazňování kódu

    + +

    Výchozí zvýrazňování kódu zavisí na CodeRay, což je rychlá zvýrazňovací knihovna napsaná v Ruby. Aktuálně podporuje jazyky C/C++, CSS, Delphi, Groovy, HTML, Java, Javascript, JSON, PHP, Python, RHTML, Ruby, Scheme, SQL, XML a YAML.

    + +

    Kód můžete na stránce zvýraznit pomocí následující syntaxe:

    + +
    +<pre><code class="ruby">
    +  Váš kód vložte zde.
    +</code></pre>
    +
    + +

    NapÅ™.:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/da/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/da/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/da/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/da/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/de/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/de/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wikiformatierung + + + + +

    Wiki Syntax Schnellreferenz

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Schriftarten
    Fett*Fett*Fett
    Kursiv_Kursiv_Kursiv
    Unterstrichen+Unterstrichen+Unterstrichen
    Durchgestrichen-Durchgestrichen-Durchgestrichen
    ??Zitat??Zitat
    Inline-Code@Inline-Code@Inline-Code
    Vorformatierter Text<pre>
     vorformatierte
     Codezeilen
    </pre>
    +
    + vorformartierte
    + Codezeilen
    +
    +
    Listen
    Unsortierte Liste* Element 1
    * Element 2
    • Element 1
    • Element 2
    Sortierte Liste# Element 1
    # Element 2
    1. Element 1
    2. Element 2
    Überschriften
    Überschrift 1h1. Überschrift 1

    Überschrift 1

    Überschrift 2h2. Überschrift 2

    Überschrift 2

    Überschrift 3h3. Überschrift 3

    Überschrift 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine Links
    Link zu einer Wiki Seite[[Wiki Seite]]Wiki Seite
    Ticket #12Ticket #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    eingebettete Bilder
    Bild!URL_zu_dem_Bild!
    !angehängtes_Bild!
    + +

    weitere Informationen

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/de/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/de/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/el/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/el/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/el/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/el/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/en-gb/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/en-gb/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/en-gb/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/en-gb/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/en/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/en/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/en/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/en/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/es/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/es/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/es/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/es/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/et/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/et/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/et/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/et/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/eu/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/eu/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/eu/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/eu/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/fa/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/fa/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/fa/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/fa/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/fi/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/fi/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/fi/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/fi/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/fr/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/fr/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Syntaxe rapide des Wikis

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Gras*Gras
    Italic_Italique_Italique
    Underline+Sous-ligné+Sous-ligné
    Deleted-Barré-Barré
    ??Citation??Citation
    Inline Code@Code en ligne@Code en ligne
    Preformatted text<pre>
     lignes
     de code
    </pre>
    +
    + lignes
    + de code
    +
    +
    Listes
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Titres
    Heading 1h1. Titre 1

    Titre 1

    Heading 2h2. Titre 2

    Titre 2

    Heading 3h3. Titre 3

    Titre 3

    Liens
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Liens Redmine
    Link to a Wiki page[[Wiki page]]Wiki page
    Demande #12Demande #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Images en ligne
    Image!url_de_l_image!
    !image_en_pièce_jointe!
    + +

    Plus d'informations

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/fr/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/fr/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Mise en page Wiki

    + +

    Liens

    + +

    Liens Redmine

    + +

    Redmine autorise les hyperliens entre différentes ressources (Demandes, révisions, pages wiki...) n'importe où la mise en page Wiki est utilisée.

    +
      +
    • Lien vers une demande: #124 (affiche #124, le lien est barré si la demande est fermée)
    • +
    • Lien vers une note d'une demande: #124-6, ou #124#note-6
    • +
    + +

    Liens entre Wiki:

    + +
      +
    • [[Guide]] affiche un lien vers la page nommé 'Guide': Guide
    • +
    • [[Guide#balise-avancée]] vous emmène à la balise "balise-avancée". Les titres ont automatiquement une balise assignée afin de pouvoir s'y référer: Guide
    • +
    • [[Guide|Manuel Utilisateur]] affiche un lien vers la même page mais avec un texte différent: Manuel Utilisateur
    • +
    + +

    Vous pouvez aussi faire des liens vers des pages du Wiki d'un autre projet:

    + +
      +
    • [[sandbox:une page]] affiche un lien vers une page nommée 'Une page' du Wiki du projet Sandbox
    • +
    • [[sandbox:]] affiche un lien vers la page principal du Wiki du projet Sandbox
    • +
    + +

    Les liens Wiki sont affichés en rouge si la page n'existe pas encore, ie: Page inexistante.

    + +

    Liens vers d'autres ressources:

    + +
      +
    • Documents: +
        +
      • document#17 (lien vers le document dont l'id est 17)
      • +
      • document:Salutations (lien vers le document dont le titre est "Salutations")
      • +
      • document:"Un document" (Les guillements peuvent être utilisé quand le titre du document comporte des espaces)
      • +
      • sandbox:document:"Un document" (Lien vers le document dont le titre est "Un document" dans le projet différent "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (lien vers la version dont l'id est 3)
      • +
      • version:1.0.0 (lien vers la version nommée "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (lien vers la version nommée "1.0.0" dans le projet "sandbox")
      • +
    • +
    + +
      +
    • Pièces jointes: +
        +
      • attachment:file.zip (lien vers la pièce jointe de l'objet nommée file.zip)
      • +
      • Pour le moment, seules les pièces jointes de l'objet peuvent être référencées (si vous êtes sur une demande, il est possibe de faire référence aux pièces jointes de cette demande uniquement)
      • +
    • +
    + +
      +
    • Révisions: +
        +
      • r758 (lien vers une révision)
      • +
      • commit:c6f4d0fd (lien vers une révision sans référence numérique)
      • +
      • svn1|r758 (lien vers un dépôt spécifique, pour les projets ayant plusieurs dépôts)
      • +
      • commit:hg|c6f4d0fd (lien vers une révision sans référence numérique d'un dépôt spécifique)
      • +
      • sandbox:r758 (Lien vers une révision d'un projet différent)
      • +
      • sandbox:commit:c6f4d0fd (lien vers une révision sans référence numérique d'un autre projet)
      • +
    • +
    + +
      +
    • Fichier de dépôt: +
        +
      • source:un/fichier (Lien vers le fichier situé dans /un/fichier dans le dépôt du projet)
      • +
      • source:un/fichier@52 (Lien vers le fichier de la révison 52)
      • +
      • source:un/fichier#L120 (Lien vers la ligne 120 du fichier fichier)
      • +
      • source:un/fichier@52#L120 (Lien vers la ligne 120 du fichier de la révison 52)
      • +
      • source:"un fichier@52#L120" (Utilisez des guillemets quand l'url contient des espaces)
      • +
      • export:un/fichier (Force le téléchargement du fichier)
      • +
      • source:svn1|un/fichier (Lien vers le fichier dans un dépôt spécifique, pour les projets contenant plusieurs dépôts)
      • +
      • sandbox:source:un/fichier (Lien vers le fichier situé dans /un/fichier dans le dépôt du projet "sandbox")
      • +
      • sandbox:export:un/fichier (Force le téléchargement du fichier dans le dépôt du projet "sandbox")
      • +
    • +
    + +
      +
    • Messages du forum: +
        +
      • message#1218 (Lien vers le message dont l'id est 1218)
      • +
    • +
    + +
      +
    • Projet: +
        +
      • project#3 (Lien vers le projet dont l'id est 3)
      • +
      • project:unprojet (Lien vers le projet nommé "unprojet")
      • +
    • +
    + + +

    Eviter ces lien:

    + +
      +
    • Vous pouvez empêcher les liens Redmine de se faire en les précédant d'un point d'exclamaion : !
    • +
    + + +

    Liens externes

    + +

    Les URLs HTTP et les adresses email sont automatiquement transformé en liens:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    affiche: http://www.redmine.org,

    + +

    Si vous voulez afficher un texte spécifique à la place de l'URL, vous pouvez utilisez la syntaxe standard textile:

    + +
    +"Site Web Redmine":http://www.redmine.org
    +
    + +

    affiche: Site Web Redmine

    + + +

    Formatage du texte

    + + +

    Pour les éléments tel que, gras, tableau, listes, Redmine utilise la syntaxe Textile. Voir http://fr.wikipedia.org/wiki/Textile_(langage) pour les informations sur l'utilisation de ces fonctionnalités. Quelques exemples sont inclus ci-dessous, mais le moteur est capable de beaucoup plus.

    + +

    Police d'écriture

    + +
    +* *gras*
    +* _italique_
    +* _*gras _italique_*_
    +* +sous-ligné+
    +* -barré-
    +
    + +

    Affiche:

    + +
      +
    • gras
    • +
    • _italique_
    • +
    • gras italique
    • +
    • sous-ligné
    • +
    • barré
    • +
    + +

    Afficher une image

    + +
      +
    • !url_de_l_image! affiche une image situé à l'adresse displays an image located at url_de_l_image (syntaxe Textile)
    • +
    • !>url_de_l_image! Image affichée à droite
    • +
    • Si vous avez une image en pièce jointe de votre page Wiki, elle peut être affiché en utilisant simplement sont nom: !image_en_piece_jointe.png!
    • +
    + +

    Titre

    + +
    +h1. Titre
    +h2. Sous-titre
    +h3. Sous-sous-titre
    +
    + +

    Redmine assigne une balise à chacun de ses titres, vous pouvez donc les lier avec "#Titre", "#Sous-titre" et ainsi de suite.

    + + +

    Paragraphes

    + +
    +p>. aligné à droite
    +p=. centré
    +
    + +

    Ceci est un paragraphe centré.

    + + +

    Blockquotes

    + +

    Commencer le paragraphe par bq.

    + +
    +bq. Ruby on Rails, également appelé RoR ou Rails est un framework web libre écrit en Ruby. Il suit le motif de conception Modèle-Vue-Contrôleur aussi nommé MVC.
    +Pour commencer à l'utiliser, il ne vous faut qu'un serveur web et une base de données.
    +
    + +

    Affiche

    + +
    +

    Ruby on Rails, également appelé RoR ou Rails est un framework web libre écrit en Ruby. Il suit le motif de conception Modèle-Vue-Contrôleur aussi nommé MVC.
    Pour commencer à l'utiliser, il ne vous faut qu'un serveur web et une base de données.

    +
    + + +

    Table des matières

    + +
    +{{toc}} =>  table des matières centrées à gauche
    +{{>toc}} => table des matières centrées à droite
    +
    + +

    Règle horizontale

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine possède les macros suivantes:

    + +

    hello_world

    Macro d'exemple.

    include

    Inclue une page Wiki. Exemple:

    + +
    {{include(Foo)}}
    macro_list

    Affiche une liste de toute les macros disponilbes, les descriptions sont inclues si celles-ci sont disponibles.

    + + +

    Coloration syntaxique

    + +

    La coloration syntaxique par défaut repose sur CodeRay, une librairie rapide de coloration syntaxique complètement codée en Ruby. Elle supporte actuellement les langages C, C++, CSS, delphi, groovy, HTML, java, javascript, json, PHP, python, RHTML, ruby, scheme, SQL, XML et YAML.

    + +

    Vous pouvez colorer votre code dans vos pages Wiki en utilisant la syntaxe suivante:

    + +
    +<pre><code class="ruby">
    +  Placez votre code ici.
    +</code></pre>
    +
    + +

    Exemple:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/gl/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/gl/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/gl/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/gl/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/he/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/he/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/he/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/he/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/hr/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/hr/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/hr/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/hr/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/hu/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/hu/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/hu/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/hu/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/id/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/id/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/id/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/id/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/it/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/it/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/it/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/it/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/ja/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ja/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki記法 クイックリファレンス

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    フォントスタイル
    太字*太字*太字
    斜体_斜体_斜体
    下線+下線+下線
    å–り消ã—ç·š-å–り消ã—ç·š-å–り消ã—ç·š
    ??引用??引用
    コード@コード@コード
    整形済ã¿ãƒ†ã‚­ã‚¹ãƒˆ<pre>
     è¤‡æ•°è¡Œã®
     ã‚³ãƒ¼ãƒ‰
    </pre>
    +
    +複数行ã®
    +コード
    +
    +
    リスト
    リスト* 項目1
    * é …ç›®2
    • é …ç›®1
    • é …ç›®2
    é †åºä»˜ãリスト# é …ç›®1
    # é …ç›®2
    1. é …ç›®1
    2. é …ç›®2
    見出ã—
    見出ã—1h1. タイトル1

    タイトル1

    見出ã—2h2. タイトル2

    タイトル2

    見出ã—3h3. タイトル3

    タイトル3

    リンク
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine内ã®ãƒªãƒ³ã‚¯
    Wikiページã¸ã®ãƒªãƒ³ã‚¯[[Wiki page]]Wiki page
    ãƒã‚±ãƒƒãƒˆ #12ãƒã‚±ãƒƒãƒˆ #12
    リビジョン r43リビジョン r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    ç”»åƒ
    Image!ç”»åƒURL!
    !添付ファイルå!
    + +

    より詳細ãªãƒªãƒ•ァレンス

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/ja/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ja/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki記法

    + +

    リンク

    + +

    Redmine内ã®ãƒªãƒ³ã‚¯

    + +

    Redmineã¯Wiki記法ãŒä½¿ãˆã‚‹ç®‡æ‰€ã®ã©ã“ã‹ã‚‰ã§ã‚‚ã€ãƒã‚±ãƒƒãƒˆãƒ»ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆãƒ»Wikiページãªã©ãƒªã‚½ãƒ¼ã‚¹é–“ã¸ãƒªãƒ³ã‚¯ã‚’行ã†ã“ã¨ãŒã§ãã¾ã™ã€‚

    +
      +
    • ãƒã‚±ãƒƒãƒˆã¸ã®ãƒªãƒ³ã‚¯: #124 (終了ã—ãŸãƒã‚±ãƒƒãƒˆã¯ #124 ã®ã‚ˆã†ã«å–り消ã—線付ãã§è¡¨ç¤ºã•れã¾ã™)
    • +
    • ãƒã‚±ãƒƒãƒˆã®æ³¨è¨˜ã¸ã®ãƒªãƒ³ã‚¯: #124-6 ã¾ãŸã¯ #124#note-6
    • +
    + +

    Wikiã¸ã®ãƒªãƒ³ã‚¯:

    + +
      +
    • [[Guide]] "Guide"ã¨ã„ã†åå‰ã®ãƒšãƒ¼ã‚¸ã¸ã®ãƒªãƒ³ã‚¯ã§ã™: Guide
    • +
    • [[Guide#further-reading]] "Guide"ã¨ã„ã†ãƒšãƒ¼ã‚¸å†…ã®"further-reading"ã¨ã„ã†ã‚¢ãƒ³ã‚«ãƒ¼ã«é£›ã³ã¾ã™ã€‚見出ã—ã«ã¯è‡ªå‹•çš„ã«ã‚¢ãƒ³ã‚«ãƒ¼ãŒè¨­å®šã•れるã®ã§ãƒªãƒ³ã‚¯å…ˆã¨ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™: Guide
    • +
    • [[Guide|User manual]] "Guide"ã¨ã„ã†ãƒšãƒ¼ã‚¸ã¸ã®ãƒªãƒ³ã‚¯ã‚’ç•°ãªã‚‹ãƒ†ã‚­ã‚¹ãƒˆã§è¡¨ç¤º: User manual
    • +
    + +

    別ã®ãƒ—ロジェクトã®wikiã¸ã®ãƒªãƒ³ã‚¯ã‚‚å¯èƒ½ã§ã™:

    + +
      +
    • [[sandbox:some page]] sandboxã¨ã„ã†åå‰ã®ãƒ—ロジェクトã®wikiã®"some page"ã¨ã„ã†åå‰ã®ãƒšãƒ¼ã‚¸ã¸ã®ãƒªãƒ³ã‚¯
    • +
    • [[sandbox:]] sanbdoxã¨ã„ã†åå‰ã®ãƒ—ロジェクトã®wikiã®ãƒ¡ã‚¤ãƒ³ãƒšãƒ¼ã‚¸ã¸ã®ãƒªãƒ³ã‚¯
    • +
    + +

    存在ã—ãªã„wikiページã¸ã®ãƒªãƒ³ã‚¯ã¯èµ¤ã§è¡¨ç¤ºã•れã¾ã™ã€‚ 例: Nonexistent page.

    + +

    ãã®ã»ã‹ã®ãƒªã‚½ãƒ¼ã‚¹ã¸ã®ãƒªãƒ³ã‚¯:

    + +
      +
    • Documents: +
        +
      • document#17 (id 17ã®æ–‡æ›¸ã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • document:Greetings ("Greetings" ã¨ã„ã†ã‚¿ã‚¤ãƒˆãƒ«ã®æ–‡æ›¸ã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • document:"Some document" (文書ã®ã‚¿ã‚¤ãƒˆãƒ«ã«ç©ºç™½ãŒå«ã¾ã‚Œã‚‹å ´åˆã¯ãƒ€ãƒ–ルクォーテーションã§å›²ã‚“ã§ãã ã•ã„)
      • +
      • sandbox:document:"Some document" ("sandbox" ã¨ã„ã†ãƒ—ロジェクト㮠"Some document" ã¨ã„ã†ã‚¿ã‚¤ãƒˆãƒ«ã®æ–‡æ›¸ã¸ã®ãƒªãƒ³ã‚¯)
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (id 3ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • version:1.0.0 ("1.0.0"ã¨ã„ã†åç§°ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 ("sandbox"ã¨ã„ã†ãƒ—ロジェクト㮠"1.0.0" ã¨ã„ã†åç§°ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¸ã®ãƒªãƒ³ã‚¯)
      • +
    • +
    + +
      +
    • 添付ファイル: +
        +
      • attachment:file.zip (ç¾åœ¨ã®ã‚ªãƒ–ã‚¸ã‚§ã‚¯ãƒˆã«æ·»ä»˜ã•れ㟠file.zip ã¨ã„ã†ãƒ•ァイルã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • ç¾åœ¨ã®ã‚ªãƒ–ã‚¸ã‚§ã‚¯ãƒˆä¸Šã®æ·»ä»˜ãƒ•ァイルã®ã¿ãƒªãƒ³ã‚¯å…ˆã¨ã—ã¦æŒ‡å®šå¯èƒ½ã§ã™ (例ãˆã°ã‚ã‚‹ãƒã‚±ãƒƒãƒˆã‹ã‚‰ã¯ã€ãã®ãƒã‚±ãƒƒãƒˆã«æ·»ä»˜ã•れãŸãƒ•ァイルã®ã¿ãƒªãƒ³ã‚¯å…ˆã«ã§ãã¾ã™)
      • +
    • +
    + +
      +
    • ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆ: +
        +
      • r758 (ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • commit:c6f4d0fd (ãƒãƒƒã‚·ãƒ¥å€¤ã«ã‚ˆã‚‹ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • svn1|r758 (複数ã®ãƒªãƒã‚¸ãƒˆãƒªãŒè¨­å®šã•れãŸãƒ—ロジェクトã§ã€ç‰¹å®šã®ãƒªãƒã‚¸ãƒˆãƒªã®ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • commit:hg|c6f4d0fd (ãƒãƒƒã‚·ãƒ¥å€¤ã«ã‚ˆã‚‹ã€ç‰¹å®šã®ãƒªãƒã‚¸ãƒˆãƒªã®ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • sandbox:r758 (ä»–ã®ãƒ—ロジェクトã®ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • sandbox:commit:c6f4d0fd (ãƒãƒƒã‚·ãƒ¥å€¤ã«ã‚ˆã‚‹ã€ä»–ã®ãƒ—ロジェクトã®ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã¸ã®ãƒªãƒ³ã‚¯)
      • +
    • +
    + +
      +
    • リãƒã‚¸ãƒˆãƒªå†…ã®ãƒ•ァイル: +
        +
      • source:some/file (プロジェクトã®ãƒªãƒã‚¸ãƒˆãƒªå†…ã® /some/file ã¨ã„ã†ãƒ•ァイルã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • source:some/file@52 (ファイルã®ãƒªãƒ“ジョン52ã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • source:some/file#L120 (ファイルã®120行目ã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • source:some/file@52#L120 (リビジョン52ã®ãƒ•ァイルã®120行目ã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • source:"some file@52#L120" (URLã«ã‚¹ãƒšãƒ¼ã‚¹ãŒå«ã¾ã‚Œã‚‹å ´åˆã¯ãƒ€ãƒ–ルクォーテーションã§å›²ã‚“ã§ãã ã•ã„)
      • +
      • export:some/file (ファイルã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚’強制)
      • +
      • source:svn1|some/file (複数ã®ãƒªãƒã‚¸ãƒˆãƒªãŒè¨­å®šã•れãŸãƒ—ロジェクトã§ã€ç‰¹å®šã®ãƒªãƒã‚¸ãƒˆãƒªã®ãƒ•ァイルã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • sandbox:source:some/file ("sandbox" ã¨ã„ã†ãƒ—ロジェクトã®ãƒªãƒã‚¸ãƒˆãƒªä¸Šã® /some/file ã¨ã„ã†ãƒ•ァイルã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • sandbox:export:some/file (ファイルã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚’強制)
      • +
    • +
    + +
      +
    • フォーラムã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸: +
        +
      • message#1218 (id 1218ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã¸ã®ãƒªãƒ³ã‚¯)
      • +
    • +
    + +
      +
    • プロジェクト: +
        +
      • project#3 (id 3ã®ãƒ—ロジェクトã¸ã®ãƒªãƒ³ã‚¯)
      • +
      • project:someproject ("someproject"ã¨ã„ã†åå‰ã®ãƒ—ロジェクトã¸ã®ãƒªãƒ³ã‚¯)
      • +
    • +
    + + +

    エスケープ:

    + +
      +
    • テキストをRedmineã®ãƒªãƒ³ã‚¯ã¨ã—ã¦è§£é‡ˆã•ã›ãŸããªã„å ´åˆã¯æ„Ÿå˜†ç¬¦ ! ã‚’å‰ã«ã¤ã‘ã¦ãã ã•ã„。
    • +
    + + +

    外部リンク

    + +

    HTTP URLã¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¯è‡ªå‹•çš„ã«ãƒªãƒ³ã‚¯ã«ãªã‚Šã¾ã™:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    上記ã®è¨˜è¿°ã¯æ¬¡ã®ã‚ˆã†ã«è¡¨ç¤ºã•れã¾ã™: http://www.redmine.org,

    + +

    URLã®ã‹ã‚りã«åˆ¥ã®ãƒ†ã‚­ã‚¹ãƒˆã‚’表示ã•ã›ãŸã„å ´åˆã¯ã€æ¨™æº–çš„ãªtextile記法ãŒåˆ©ç”¨ã§ãã¾ã™:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    上記ã®è¨˜è¿°ã¯æ¬¡ã®ã‚ˆã†ã«è¡¨ç¤ºã•れã¾ã™: Redmine web site

    + + +

    ãƒ†ã‚­ã‚¹ãƒˆã®æ›¸å¼

    + + +

    見出ã—ã€å¤ªå­—ã€ãƒ†ãƒ¼ãƒ–ルã€ãƒªã‚¹ãƒˆç­‰ã¯ã€Redmineã¯Textile記法ã§ã®è¨˜è¿°ã«å¯¾å¿œã—ã¦ã„ã¾ã™ã€‚Textile記法ã®è©³ç´°ã¯ http://en.wikipedia.org/wiki/Textile_(markup_language) ã‚’å‚ç…§ã—ã¦ãã ã•ã„。Textileã®ä¸€ä¾‹ã‚’以下ã«ç¤ºã—ã¾ã™ãŒã€å®Ÿéš›ã«ã¯ã“ã“ã§å–り上ã’ãŸä»¥å¤–ã®è¨˜æ³•ã«ã‚‚対応ã—ã¦ã„ã¾ã™ã€‚

    + +

    æ–‡å­—ã®æ›¸å¼

    + +
    +* *太字*
    +* _斜体_
    +* _*å¤ªå­—ã§æ–œä½“*_
    +* +下線+
    +* -å–り消ã—ç·š-
    +
    + +

    表示例:

    + +
      +
    • 太字
    • +
    • 斜体
    • +
    • å¤ªå­—ã§æ–œä½“
    • +
    • 下線
    • +
    • å–り消ã—ç·š
    • +
    + +

    ç”»åƒ

    + +
      +
    • !image_url! image_urlã§æŒ‡å®šã•れãŸURLã®ç”»åƒã‚’表示 (Textile記法)
    • +
    • !>image_url! ç”»åƒã‚’å³å¯„ã›ãƒ»ãƒ†ã‚­ã‚¹ãƒˆå›žã‚Šè¾¼ã¿ã‚りã§è¡¨ç¤º
    • +
    • Wikiãƒšãƒ¼ã‚¸ã«æ·»ä»˜ã•れãŸç”»åƒãŒã‚れã°ã€ãƒ•ァイルåを指定ã—ã¦ç”»åƒã‚’表示ã•ã›ã‚‹ã“ã¨ãŒã§ãã¾ã™: !attached_image.png!
    • +
    + +

    見出ã—

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmineã¯è¦‹å‡ºã—ã«ã‚¢ãƒ³ã‚«ãƒ¼ã‚’設定ã™ã‚‹ã®ã§ã€"#Heading", "#Subheading"ãªã©ã‚’指定ã—ã¦ãƒªãƒ³ã‚¯ãŒè¡Œãˆã¾ã™ã€‚

    + + +

    段è½

    + +
    +p>. å³å¯„ã›
    +p=. センタリング
    +
    + +

    センタリングã•ã‚ŒãŸæ®µè½

    + + +

    引用

    + +

    段è½ã‚’ bq. ã§é–‹å§‹ã—ã¦ãã ã•ã„。

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    表示例:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    目次

    + +
    +{{toc}} => 目次(左寄ã›)
    +{{>toc}} => 目次(å³å¯„ã›)
    +
    + +

    区切り線

    + +
    +---
    +
    + +

    マクロ

    + +

    Redmineã«ã¯ä»¥ä¸‹ã®çµ„ã¿è¾¼ã¿ãƒžã‚¯ãƒ­ãŒç”¨æ„ã•れã¦ã„ã¾ã™:

    + +

    hello_world

    サンプルã®ãƒžã‚¯ãƒ­ã§ã™ã€‚

    include

    別ã®Wikiページã®å†…容を挿入ã—ã¾ã™ã€‚ 以下ã¯ä½¿ç”¨ä¾‹ã§ã™:

    + +
    {{include(Foo)}}
    macro_list

    上記ã®ãƒžã‚¯ãƒ­ã‚’å«ã‚ã€åˆ©ç”¨å¯èƒ½ãªãƒžã‚¯ãƒ­ã®ä¸€è¦§ã‚’表示ã—ã¾ã™ã€‚

    + + +

    コードãƒã‚¤ãƒ©ã‚¤ãƒˆ

    + +

    Redmineã®ã‚³ãƒ¼ãƒ‰ãƒã‚¤ãƒ©ã‚¤ãƒˆã¯ CodeRay ã¨ã„ã†ã€Rubyã§è¨˜è¿°ã•れãŸé«˜é€Ÿãªãƒ©ã‚¤ãƒ–ラリを使用ã—ã¦ã„ã¾ã™ã€‚Coderayã¯c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml, yamlã«å¯¾å¿œã—ã¦ã„ã¾ã™ã€‚

    + +

    Wikiページã§ã‚³ãƒ¼ãƒ‰ãƒã‚¤ãƒ©ã‚¤ãƒˆã‚’利用ã™ã‚‹ã«ã¯æ¬¡ã®ã‚ˆã†ã«è¨˜è¿°ã—ã¾ã™:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    表示例:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/ko/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ko/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/ko/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ko/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/lt/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/lt/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/lt/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/lt/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/lv/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/lv/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/lv/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/lv/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/mk/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/mk/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/mk/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/mk/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/mn/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/mn/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/mn/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/mn/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/nl/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/nl/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/nl/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/nl/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/no/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/no/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/no/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/no/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/pl/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/pl/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/pl/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/pl/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/pt-br/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/pt-br/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/pt-br/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/pt-br/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/pt/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/pt/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/pt/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/pt/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/ro/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ro/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/ro/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ro/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/ru/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ru/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,171 @@ + + + + + +Форматирование Wiki + + + + +

    СинтакÑÐ¸Ñ Wiki ÐšÑ€Ð°Ñ‚ÐºÐ°Ñ Ð¡Ð¿Ñ€Ð°Ð²ÐºÐ°

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Стили Шрифтов
    Выделенный*Выделенный*Выделенный
    Ðаклонный_Ðаклонный_Ðаклонный
    Подчёркнутый+Подчёркнутый+ + Подчёркнутый +
    Зачёркнутый-Зачёркнутый- + Зачёркнутый +
    ??Цитата??Цитата
    Ð’Ñтавка Кода@Ð’Ñтавка Кода@Ð’Ñтавка Кода
    Отформатированный текÑÑ‚<pre>
     Ñтроки
     ÐºÐ¾Ð´Ð°
    </pre>
    +
    + Ñтроки
    + кода
    +            
    +
    СпиÑки
    ÐеÑортированный ÑпиÑок* Элемент 1
    * Элемент 2
    +
      +
    • Элемент 1
    • +
    • Элемент 2
    • +
    +
    Сортированный ÑпиÑок# Элемент 1
    # Элемент 2
    +
      +
    1. Элемент 1
    2. +
    3. Элемент 2
    4. +
    +
    Заголовки
    Заголовок 1h1. Ðазвание 1

    Ðазвание 1

    Заголовок 2h2. Ðазвание 2

    Ðазвание 2

    Заголовок 3h3. Ðазвание 3

    Ðазвание 3

    СÑылки
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    СÑылки Redmine
    СÑылка на Wiki Ñтраницу[[Wiki Ñтраница]]Wiki Ñтраница
    Задача #12Задача #12
    ФикÑÐ°Ñ†Ð¸Ñ r43ФикÑÐ°Ñ†Ð¸Ñ r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Ð’Ñтавка изображений
    Изображение!url_картинки!
    !вложенный_файл!
    + +

    Больше информации

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/ru/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/ru/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,329 @@ + + + +Форматирование Wiki Redmine + + + + + +

    Форматирование Wiki

    + +

    СÑылки

    + +

    СÑылки Redmine

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • СÑылка на задачу: #124 + ( + #124 + - ÑÑылка зачёркнута, еÑли задача закрыта) +
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki ÑÑылки:

    + +
      +
    • [[РуководÑтво]] выводит ÑÑылку на Ñтраницу Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸ÐµÐ¼ 'РуководÑтво': РуководÑтво +
    • +
    • [[РуководÑтво#дальнейшее-чтение]] направлÑет на метку "дальнейшее-чтение". Заголовкам + автоматичеÑки + метки, таким образом, вы можете на них ÑÑылатьÑÑ: РуководÑтво
    • +
    • [[РуководÑтво|РуководÑтво пользователÑ]] выводит ÑÑылку на Ñаму Ñтраницу, но Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼ текÑтом: + РуководÑтво Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ +
    • +
    + +

    Также вы можете ÑÑылатьÑÑ Ð½Ð° wiki:

    + +
      +
    • [[sandbox:ÐÐµÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ñтраница]] выводит ÑÑылку на Ñтраницу Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸ÐµÐ¼ 'ÐÐµÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ñтраница' wiki + проекта Sandbox +
    • +
    • [[sandbox:]] выводит ÑÑылку на главную Ñтраницу wiki проекта Sandbox
    • +
    + +

    СÑылки на wiki окрашены в краÑный, еÑли Ñтраница ещё не Ñоздана, пример: ÐеÑущеÑÑ‚Ð²ÑƒÑŽÑ‰Ð°Ñ + Ñтраница.

    + +

    ССылки на другие реÑурÑÑ‹:

    + +
      +
    • Документы: +
        +
      • document#17 (ÑÑылка на документ Ñ id 17)
      • +
      • document:ПриветÑтвие (ÑÑылка на документ Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸ÐµÐ¼ "ПриветÑтвие")
      • +
      • document:"Ðекоторый документ" (двойные кавычки иÑпользоютÑÑ Ð² Ñлучае, когда название + документа Ñодержит пробелы) +
      • +
      • sandbox:document:"ПриветÑтвие" (ÑÑылка на документ Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸ÐµÐ¼ "ПриветÑтвие" в проекте + "sandbox") +
      • +
    • +
    + +
      +
    • Этапы: +
        +
      • version#3 (ÑÑылка на Ñтап Ñ id 3)
      • +
      • version:1.0.0 (ÑÑылка на Ñтап Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸ÐµÐ¼ "1.0.0")
      • +
      • version:"1.0 beta 2" (двойные кавычки иÑпользоютÑÑ Ð² Ñлучае, когда название + Ñтапа Ñодержит пробелы) +
      • +
      • sandbox:version:1.0.0 (ÑÑылка на Ñтап "1.0.0" проекта "sandbox")
      • +
    • +
    + +
      +
    • ВложениÑ: +
        +
      • attachment:file.zip (ÑÑылка на вложение текущего объекта Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ file.zip)
      • +
      • Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¼Ð¾Ð¶Ð½Ð¾ ÑÑылатьÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ на Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ³Ð¾ объекта (еÑли вы проÑматриваете задачу, то возможно + ÑÑылатьÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ на Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ñтой задачи) +
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Файлы хранилища: +
        +
      • source:some/file (ÑÑылка на файл /some/file, раÑположенный в хранилище проекта) +
      • +
      • source:some/file@52 (ÑÑылка на 52 ревизию файла)
      • +
      • source:some/file#L120 (ÑÑылка на 120 Ñтроку файла)
      • +
      • source:some/file@52#L120 (ÑÑылка на 120 Ñтроку в 52 ревизии файла)
      • +
      • source:"some file@52#L120" (иÑпользуйте use double quotes when the URL contains spaces
      • +
      • export:some/file (ÑÑылка на загрузку файла)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (ÑÑылка на файл /some/file, раÑположенный в хранилище проекта + "sandbox") +
      • +
      • sandbox:export:some/file (ÑÑылка на загрузку файла)
      • +
    • +
    + +
      +
    • Ð¡Ð¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ„Ð¾Ñ€ÑƒÐ¼Ð°: +
        +
      • message#1218 (ÑÑылка на Ñообщение Ñ id 1218)
      • +
    • +
    + +
      +
    • Проекты: +
        +
      • project#3 (ÑÑылка на проект Ñ id 3)
      • +
      • project:someproject (ÑÑылка на проект "someproject")
      • +
    • +
    + + +

    ИÑключениÑ:

    + +
      +
    • Ð’Ñ‹ можете отменить обработку ÑÑылок Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ воÑклицательного знака перед ÑÑылкой: !http://foo.bar
    • +
    + +

    Внешние ÑÑылки

    + +

    HTTP и почтовые адреÑа автоматичеÑки транÑлируютÑÑ Ð² ÑÑылки:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    выводитÑÑ: http://www.redmine.org,

    + +

    ЕÑли же вы хотите, чтобы отобразилÑÑ Ñ‚ÐµÐºÑÑ‚ вмеÑто адреÑа URL, вы можете иÑпольовать Ñтандартный ÑинтакÑÐ¸Ñ + Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ‚ÐµÐºÑта:

    + +
    +"Сайт Redmine":http://www.redmine.org
    +
    + +

    выводитÑÑ: Сайт Redmine

    + + +

    Форматирование текÑта

    + +

    Ð”Ð»Ñ Ñ‚Ð°ÐºÐ¸Ñ… вещей, как заголовки, выделение, таблицы и ÑпиÑки, Redmine поддерживает ÑÐ¸Ð½Ñ‚Ð°ÐºÑ Textile. ОбратитеÑÑŒ за + руководÑтвом к Ñтранице http://en.wikipedia.org/wiki/Textile_(markup_language) + . ÐеÑколько примеров приведены ниже, Ðо Ñам текÑтовый процеÑÑор ÑпоÑобен на гораздо большее.

    + +

    Стиль шрифта

    + +
    +* *выделенный*
    +* _наклонный_
    +* _*выделенный наклонный*_
    +* +подчёркнутый+
    +* -зачёркнутый-
    +
    + +

    ВыводитÑÑ:

    + +
      +
    • выделенный
    • +
    • наклонный
    • +
    • выделенный наклонный
    • +
    • подчёркнутый
    • +
    • зачёркнутый
    • +
    + +

    Ð’Ñтавка изображений

    + +
      +
    • !url_изображениÑ! выводит изображение, раÑположенное по адреÑу url_Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ (ÑÐ¸Ð½Ñ‚Ð°ÐºÑ textile)
    • +
    • !>url_изображениÑ! выводит изображение, выровненное по правому краю
    • +
    • Прикреплённое к wiki-Ñтранице изображение можно отобразить в текÑте, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°: + !вложенное_изображение.png! +
    • +
    + +

    Заголовки

    + +
    +h1. Заголовок
    +h2. Подзаголовок
    +h3. Подзаголовок подзаголовка
    +
    + +

    Redmine приÑваивает Ñкорь каждому заголовку, поÑтому вы можете легко ÑоÑлатьÑÑ Ð½Ð° любой, указав в текÑте "#Заголовок", + "#Подзаголовок" и Ñ‚.д.

    + + +

    Параграфы

    + +
    +p>. выровненный по правому краю
    +p=. выровненный по центру
    +
    + +

    Это - выровненный по центру параграф.

    + + +

    Цитаты

    + +

    Ðачните параграф Ñ bq.

    + +
    +bq. Rails - Ñто полноценный, многоуровневый фреймворк Ð´Ð»Ñ Ð¿Ð¾ÑÑ‚Ñ€Ð¾ÐµÐ½Ð¸Ñ Ð²ÐµÐ±-приложений, иÑпользующих базы данных,
    +    который оÑнован на архитектуре Модель-ПредÑтавление-Контроллер (Model-View-Controller, MVC).
    +
    + +

    ВыводитÑÑ:

    + +
    +

    Rails - Ñто полноценный, многоуровневый фреймворк Ð´Ð»Ñ Ð¿Ð¾ÑÑ‚Ñ€Ð¾ÐµÐ½Ð¸Ñ Ð²ÐµÐ±-приложений, иÑпользующих базы данных, + который оÑнован на архитектуре Модель-ПредÑтавление-Контроллер (Model-View-Controller, MVC).

    +
    + + +

    Содержание

    + +
    +{{Содержание}} => Ñодержание, выровненное по левому краю
    +{{>Содержание}} => Ñодержание, выровненное по правому краю
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    МакроÑÑ‹

    + +

    Ð’ Redmine ÑущеÑтвуют Ñледующие вÑтроенные макроÑÑ‹:

    + +

    +

    +
    hello_world
    +

    Ðекоторый макроÑ.

    +
    include
    +

    Ð’Ñтавить wiki Ñтраницу. Пример:

    + +
    {{include(Foo)}}
    +
    +
    macro_list
    +

    Выводит ÑпиÑок доÑтупных макроÑов Ñ Ð¾Ð¿Ð¸ÑаниÑми, еÑли они имеютÑÑ.

    +
    +

    + + +

    ПодÑветка кода

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    Ð’Ñ‹ можете включить подÑветку кода иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð´Ð°Ð½Ð½Ñ‹Ð¹ ÑинтакÑиÑ:

    + +
    +<pre><code class="ruby">
    +  ПомеÑтите Ñвой код Ñюда.
    +</code></pre>
    +
    + +

    Пример:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/sk/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sk/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/sk/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sk/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/sl/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sl/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/sl/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sl/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/sq/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sq/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/sq/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sq/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/sr-yu/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sr-yu/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/sr-yu/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sr-yu/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/sr/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sr/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/sr/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sr/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/sv/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sv/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/sv/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/sv/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/th/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/th/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/th/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/th/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/tr/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/tr/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/tr/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/tr/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/uk/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/uk/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/uk/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/uk/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/vi/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/vi/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/vi/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/vi/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/wiki_syntax.html --- a/public/help/wiki_syntax.html Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ - - - - - -Wiki formatting - - - - -

    Wiki Syntax Quick Reference

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    -
    - lines
    - of code
    -
    -
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Table of contents
    {{toc}}
    Left-aligned table of contents
    {{>toc}}
    Right-aligned table of contents
    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Magic links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    - -

    More Information

    - - - diff -r d98d22a98252 -r afce8026aaeb public/help/wiki_syntax_detailed.html --- a/public/help/wiki_syntax_detailed.html Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,353 +0,0 @@ - - - -RedmineWikiFormatting - - - - - -

    Wiki formatting

    - -

    Links

    - -

    Redmine links

    - -

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    -
      -
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • -
    • Link to an issue note: #124-6, or #124#note-6
    • -
    - -

    Wiki links:

    - -
      -
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • -
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • -
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • -
    - -

    You can also link to pages of an other project wiki:

    - -
      -
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • -
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • -
    - -

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    - -

    Links to other resources:

    - -
      -
    • Documents: -
        -
      • document#17 (link to document with id 17)
      • -
      • document:Greetings (link to the document with title "Greetings")
      • -
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • -
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • -
    • -
    - -
      -
    • Versions: -
        -
      • version#3 (link to version with id 3)
      • -
      • version:1.0.0 (link to version named "1.0.0")
      • -
      • version:"1.0 beta 2"
      • -
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • -
    • -
    - -
      -
    • Attachments: -
        -
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • -
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • -
    • -
    - -
      -
    • Changesets: -
        -
      • r758 (link to a changeset)
      • -
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • -
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • -
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • -
      • sandbox:r758 (link to a changeset of another project)
      • -
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • -
    • -
    - -
      -
    • Repository files: -
        -
      • source:some/file (link to the file located at /some/file in the project's repository)
      • -
      • source:some/file@52 (link to the file's revision 52)
      • -
      • source:some/file#L120 (link to line 120 of the file)
      • -
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • -
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • -
      • export:some/file (force the download of the file)
      • -
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • -
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • -
      • sandbox:export:some/file (force the download of the file)
      • -
    • -
    - -
      -
    • Forum messages: -
        -
      • message#1218 (link to message with id 1218)
      • -
    • -
    - -
      -
    • Projects: -
        -
      • project#3 (link to project with id 3)
      • -
      • project:someproject (link to project named "someproject")
      • -
    • -
    - - -

    Escaping:

    - -
      -
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • -
    - - -

    External links

    - -

    HTTP URLs and email addresses are automatically turned into clickable links:

    - -
    -http://www.redmine.org, someone@foo.bar
    -
    - -

    displays: http://www.redmine.org,

    - -

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    - -
    -"Redmine web site":http://www.redmine.org
    -
    - -

    displays: Redmine web site

    - - -

    Text formatting

    - - -

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    - -

    Font style

    - -
    -* *bold*
    -* _italic_
    -* _*bold italic*_
    -* +underline+
    -* -strike-through-
    -
    - -

    Display:

    - -
      -
    • bold
    • -
    • italic
    • -
    • *bold italic*
    • -
    • underline
    • -
    • strike-through
    • -
    - -

    Inline images

    - -
      -
    • !image_url! displays an image located at image_url (textile syntax)
    • -
    • !>image_url! right floating image
    • -
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • -
    - -

    Headings

    - -
    -h1. Heading
    -h2. Subheading
    -h3. Subsubheading
    -
    - -

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    - - - - -

    Bullets and Numbering

    - -
    -* First Level Bullet
    -** Second Level Bullet
    -** Another Second Level Bullet
    -*** Third Level Bullet
    -** Back to 2nd Level Bullet
    -* Back to 1st Level Bullet
    -
    - -
      -
    • First Level Bullet
    • -
        -
      • Second Level Bullet
      • -
      • Another Second Level Bullet
      • -
          -
        • Third Level Bullet
        • -
        -
      • Back to 2nd Level Bullet
      • -
      - -
    • Back to 1st Level Bullet
    • -
    - -
    -# First Level Numbering
    -## Second Level Numbering
    -## Another Second Level Numbering
    -### Third Level Numbering
    -## Back to 2nd Level Numbering
    -# Back to 1st Level Numbering
    -
    - -
      -
    1. First Level Numbering
    2. -
        -
      1. Second Level Numbering
      2. -
      3. Another Second Level Numbering
      4. -
          -
        1. Third Level Numbering
        2. -
        -
      5. Back to 2nd Level Numbering
      6. -
      -
    3. Back to 1st Level Numbering
    4. -
    - -
    -# First Level Numbering
    -#* Bullet inside numbering environment
    -#* Another Bullet inside numbering environment
    -# Back to 1st Level Numbering
    -
    - -
      -
    1. First Level Numbering
    2. -
        -
      • Bullet inside numbering environment
      • -
      • Another Bullet inside numbering environment
      • -
      - -
    3. Back to 1st Level Numbering
    4. -
    - - - - - - - -

    Paragraphs

    - -
    -p>. right aligned
    -p=. centered
    -
    - -

    This is a centered paragraph.

    - - -

    Blockquotes

    - -

    Start the paragraph with bq.

    - -
    -bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    -To go live, all you need to add is a database and a web server.
    -
    - -

    Display:

    - -
    -

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    -
    - - -

    Table of content

    - -
    -{{toc}} => left aligned toc
    -{{>toc}} => right aligned toc
    -
    - -

    Macros

    - -

    Redmine has the following builtin macros:

    - -

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    - -
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    - - -

    Code highlighting

    - -

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    - -

    You can highlight code in your wiki page using this syntax:

    - -
    -<pre><code class="ruby">
    -  Place you code here.
    -</code></pre>
    -
    - -

    Example:

    - -
     1 # The Greeter class
    - 2 class Greeter
    - 3   def initialize(name)
    - 4     @name = name.capitalize
    - 5   end
    - 6 
    - 7   def salute
    - 8     puts "Hello #{@name}!" 
    - 9   end
    -10 end
    -
    - - diff -r d98d22a98252 -r afce8026aaeb public/help/zh-tw/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/zh-tw/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki æ ¼å¼è¨­å®š + + + + +

    Wiki 語法快速å°ç…§è¡¨

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    字型樣å¼
    粗體(加強語氣)*粗體(加強語氣)*粗體(加強語氣)
    斜體_斜體_斜體
    底線+底線+底線
    刪除線-刪除線-刪除線
    ??引文??引文
    內嵌程å¼ç¢¼@內嵌程å¼ç¢¼@內嵌程å¼ç¢¼ (inline code)
    é å…ˆæ ¼å¼åŒ–çš„æ®µè½æ–‡å­—<pre>
     æ ¼å¼åŒ–
     çš„æ®µè½
    </pre>
    +
    + æ ¼å¼åŒ–
    + 的段è½
    +
    +
    清單
    ä¸æŽ’åºæ¸…å–®* 清單項目 1
    * 清單項目 2
    • 清單項目 1
    • 清單項目 2
    æŽ’åºæ¸…å–®# 清單項目 1
    # 清單項目 2
    1. 清單項目 1
    2. 清單項目 2
    標題
    標題 1h1. 標題 1

    標題 1

    標題 2h2. 標題 2

    標題 2

    標題 3h3. 標題 3

    標題 3

    連çµ
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine 連çµ
    連çµè‡³ä¸€å€‹ Wiki é é¢[[Wiki é é¢]]Wiki é é¢
    å•題 #12å•題 #12
    版次 r43版次 r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    內置圖åƒ
    圖åƒ!圖åƒ_url!
    !附加_圖åƒ!
    + +

    更多資訊

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/zh-tw/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/zh-tw/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki æ ¼å¼è¨­å®š

    + +

    連çµ

    + +

    Redmine 連çµ

    + +

    在任何å¯ä»¥ä½¿ç”¨ Wiki æ ¼å¼è¨­å®šçš„地方, Redmine 都å…許在資æºï¼ˆå•題ã€è®Šæ›´é›†ã€ Wiki é é¢...)間建立超連çµã€‚

    +
      +
    • 連çµè‡³ä¸€å€‹å•題: #124 (若該å•é¡Œå·²ç¶“çµæŸï¼Œå‰‡ä½¿ç”¨åˆªé™¤ç·šé¡¯ç¤ºé€£çµï¼š #124)
    • +
    • 連çµè‡³ä¸€å€‹å•題的筆記: #124-6, or #124#note-6
    • +
    + +

    Wiki 連çµï¼š

    + +
      +
    • [[Guide]] 顯示一個é é¢å稱為 'Guide' 的連çµï¼š Guide
    • +
    • [[Guide#further-reading]] 會連çµè‡³ä¸€å€‹ "further-reading" çš„ HTML 錨定 (anchor) 。æ¯å€‹æ¨™é¡Œæ–‡å­—都會被自動指定一個 HTML 錨定,以便您å¯ä»¥ç”¨ä¾†é€£çµå®ƒå€‘: Guide
    • +
    • [[Guide|User manual]] 使用ä¸åŒçš„æ–‡å­—來顯示一個é é¢å稱為 'Guide' 的連çµï¼š User manual
    • +
    + +

    您也å¯ä»¥é€£çµè‡³å…¶ä»–專案的 Wiki é é¢ï¼š

    + +
      +
    • [[sandbox:some page]] 顯示一個 Sanbox wiki 中é é¢å稱為 'Some page' 的連çµ
    • +
    • [[sandbox:]] 顯示 Sandbox wiki 首é é é¢çš„連çµ
    • +
    + +

    ç•¶é é¢ä¸å­˜åœ¨çš„æ™‚候, Wiki é€£çµæœƒä»¥ç´…色的方å¼é¡¯ç¤ºï¼Œä¾‹å¦‚: Nonexistent page.

    + +

    連çµè‡³å…¶ä»–資æºï¼š

    + +
      +
    • 文件: +
        +
      • document#17 (連çµåˆ°ç·¨è™Ÿç‚º 17 的文件)
      • +
      • document:Greetings (連çµè‡³æ–‡ä»¶æ¨™é¡Œç‚º "Greetings" 的文件)
      • +
      • document:"Some document" (文件標題包å«ç©ºç™½å­—元時å¯ä»¥ä½¿ç”¨é›™å¼•號來標示)
      • +
      • sandbox:document:"Some document" (連çµè‡³å¦å¤–一個 "sandbox" 專案中,文件標題為 "Some document" 的文件)
      • +
    • +
    + +
      +
    • 版本: +
        +
      • version#3 (連çµè‡³ç·¨è™Ÿç‚º 3 的版本)
      • +
      • version:1.0.0 (連çµè‡³å稱為 "1.0.0" 的版本)
      • +
      • version:"1.0 beta 2" (版本å稱包å«ç©ºç™½å­—元時å¯ä»¥ä½¿ç”¨é›™å¼•號來標示)
      • +
      • sandbox:version:1.0.0 (連çµè‡³ "sandbox" 專案中,å稱為 "1.0.0" 的版本)
      • +
    • +
    + +
      +
    • 附加檔案: +
        +
      • attachment:file.zip (連çµè‡³ç›®å‰ç‰©ä»¶ä¸­ï¼Œå稱為 file.zip 的附加檔案)
      • +
      • ç›®å‰åƒ…æä¾›åƒè€ƒåˆ°ç›®å‰ç‰©ä»¶ä¸­çš„附加檔案 (è‹¥æ‚¨æ­£ä½æ–¼ä¸€å€‹å•題中,僅å¯åƒè€ƒä½æ–¼æ­¤å•題中之附加檔案)
      • +
    • +
    + +
      +
    • 變更集: +
        +
      • r758 (連çµè‡³ä¸€å€‹è®Šæ›´é›†)
      • +
      • commit:c6f4d0fd (連çµè‡³ä¸€å€‹ä½¿ç”¨éžæ•¸å­—雜湊的變更集)
      • +
      • svn1|r758 (連çµè‡³æŒ‡å®šå„²å­˜æ©Ÿåˆ¶ä¸­ä¹‹è®Šæ›´é›†ï¼Œç”¨æ–¼å°ˆæ¡ˆä½¿ç”¨å¤šå€‹å„²å­˜æ©Ÿåˆ¶æ™‚之情æ³)
      • +
      • commit:hg|c6f4d0fd (連çµè‡³æŒ‡å®šå„²å­˜æ©Ÿåˆ¶ä¸­ï¼Œä½¿ç”¨éžæ•¸å­—雜湊的變更集)
      • +
      • sandbox:r758 (連çµè‡³å…¶ä»–專案的變更集)
      • +
      • sandbox:commit:c6f4d0fd (連çµè‡³å…¶ä»–å°ˆæ¡ˆä¸­ï¼Œä½¿ç”¨éžæ•¸å­—雜湊的變更集)
      • +
    • +
    + +
      +
    • 儲存機制中之檔案: +
        +
      • source:some/file (連çµè‡³å°ˆæ¡ˆå„²å­˜æ©Ÿåˆ¶ä¸­ï¼Œä½æ–¼ /some/file 的檔案)
      • +
      • source:some/file@52 (連çµè‡³æ­¤æª”案的 52 版次)
      • +
      • source:some/file#L120 (連çµè‡³æ­¤æª”案的第 120 行)
      • +
      • source:some/file@52#L120 (連çµè‡³æ­¤æª”案的 52 版刺中之第 120 行)
      • +
      • source:"some file@52#L120" (ç•¶ URL 中包å«ç©ºç™½å­—元時,使用雙引號來標示)
      • +
      • export:some/file (強制下載此檔案)
      • +
      • source:svn1|some/file (連çµè‡³æŒ‡å®šå„²å­˜æ©Ÿåˆ¶ä¸­çš„æ­¤æª”案,用於專案使用多個儲存機制時之情æ³)
      • +
      • sandbox:source:some/file (連çµè‡³ "sandbox" å°ˆæ¡ˆçš„å„²å­˜æ©Ÿåˆ¶ä¸­ï¼Œä½æ–¼ /some/file 的檔案)
      • +
      • sandbox:export:some/file (強迫下載該檔案)
      • +
    • +
    + +
      +
    • 論壇訊æ¯ï¼š +
        +
      • message#1218 (連çµè‡³ç·¨è™Ÿ 1218 的訊æ¯)
      • +
    • +
    + +
      +
    • 專案: +
        +
      • project#3 (連çµè‡³ç·¨è™Ÿç‚º 3 的專案)
      • +
      • project:someproject (連çµè‡³å稱為 "someproject" 的專案)
      • +
    • +
    + + +

    逸出字元:

    + +
      +
    • 您å¯ä»¥åœ¨æ–‡å­—çš„å‰é¢åŠ ä¸Šé©šå˜†è™Ÿ (!) 來é¿å…è©²æ–‡å­—è¢«å‰–æžæˆ Redmine 連çµ
    • +
    + + +

    外部連çµ

    + +

    HTTP URLs 與電å­éƒµä»¶åœ°å€æœƒè‡ªå‹•è¢«è½‰æ›æˆå¯è¢«é»žæ“Šçš„連çµï¼š

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    顯示為: http://www.redmine.org,

    + +

    若您想è¦é¡¯ç¤ºæŒ‡å®šçš„æ–‡å­—而éžè©² URL ,您å¯ä»¥ä½¿ç”¨ä¸‹åˆ—標準的 textile 語法:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    顯示為: Redmine web site

    + + +

    文字格å¼è¨­å®š

    + + +

    å°æ–¼è«¸å¦‚標題ã€ç²—é«”ã€è¡¨æ ¼ã€æ¸…單等項目, Redmine 支æ´ä½¿ç”¨ Textile 語法。å¯åƒè€ƒ http://en.wikipedia.org/wiki/Textile_(markup_language) 中關於使用這些格å¼åŒ–功能的說明資訊。 下é¢åŒ…å«äº†ä¸€äº›ä½¿ç”¨ç¯„例,但格å¼åŒ–引擎的處ç†èƒ½åŠ›é å¤šæ–¼é€™äº›ç°¡å–®çš„使用範例。

    + +

    字型樣å¼

    + +
    +* *ç²—é«”*
    +* _斜體_
    +* _*粗斜體*_
    +* +底線+
    +* -刪除線-
    +
    + +

    顯示為:

    + +
      +
    • ç²—é«”
    • +
    • 斜體
    • +
    • 粗斜體
    • +
    • 底線
    • +
    • 刪除線
    • +
    + +

    內置圖åƒ

    + +
      +
    • !圖åƒ_url! é¡¯ç¤ºä¸€å€‹ä½æ–¼ 圖åƒ_url ä½å€çš„圖åƒ(textile 語法)
    • +
    • !>image_url! å³å´æµ®å‹•圖åƒ
    • +
    • 若您附加了一個圖åƒåˆ° Wiki é é¢ä¸­ï¼Œå¯ä»¥ä½¿ç”¨ä»–的檔案å稱來顯示æˆå…§ç½®åœ–åƒï¼š !attached_image.png!
    • +
    + +

    標題

    + +
    +h1. 標題
    +h2. 次標題
    +h3. 次次標題
    +
    + +

    Redmine 為æ¯ä¸€ç¨®æ¨™é¡ŒæŒ‡å®šä¸€å€‹ HTML 錨定 (anchor) ,因此您å¯ä½¿ç”¨ "#Heading" 〠"#Subheading" 等方å¼é€£çµè‡³é€™äº›æ¨™é¡Œã€‚

    + + +

    段è½

    + +
    +p>. é å³å°é½Š
    +p=. 置中å°é½Š
    +
    + +

    這是一個置中å°é½Šçš„æ®µè½ã€‚

    + + +

    引用文字

    + +

    使用 bq. 開始一個引文的段è½

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    顯示為:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    目錄

    + +
    +{{toc}} => é å·¦å°é½Šç›®éŒ„
    +{{>toc}} => é å³å°é½Šç›®éŒ„
    +
    + +

    水平線

    + +
    +---
    +
    + +

    巨集

    + +

    Redmine 內建下列巨集:

    + +

    hello_world

    範例巨集。

    include

    引入一個 wiki é é¢ã€‚例å­ï¼š

    + +
    {{include(Foo)}}
    macro_list

    顯示所有å¯ç”¨å·¨é›†çš„æ¸…單,若巨集有æä¾›èªªæ˜Žä¹Ÿæœƒä¸€ä½µé¡¯ç¤ºã€‚

    + + +

    程å¼ç¢¼é†’ç›®æç¤º

    + +

    é è¨­ä½¿ç”¨ CodeRay 作為程å¼ç¢¼é†’ç›®æç¤ºçš„æ©Ÿåˆ¶ï¼Œå®ƒæ˜¯ä¸€å€‹ä½¿ç”¨ Ruby 撰寫的語法醒目æç¤ºå‡½å¼åº«ã€‚å®ƒç›®å‰æ”¯æ´ c 〠cpp 〠css 〠delphi 〠groovy 〠html 〠java 〠javascript 〠json 〠php 〠python 〠rhtml 〠ruby 〠scheme 〠sql 〠xml 與 yaml 等程å¼èªžè¨€ã€‚

    + +

    您å¯ä»¥ä½¿ç”¨ä¸‹åˆ—語法,在 Wiki é é¢ä¸­å°‡ç¨‹å¼ç¢¼æ¨™ç¤ºç‚ºé†’ç›®æç¤ºï¼š

    + +
    +<pre><code class="ruby">
    +  將程å¼ç¢¼æ”¾åœ¨é€™è£¡ã€‚
    +</code></pre>
    +
    + +

    例å­ï¼š

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/help/zh/wiki_syntax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/zh/wiki_syntax.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,66 @@ + + + + + +Wiki formatting + + + + +

    Wiki Syntax Quick Reference

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    + lines
    + of code
    +
    +
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!
    + +

    More Information

    + + + diff -r d98d22a98252 -r afce8026aaeb public/help/zh/wiki_syntax_detailed.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/help/zh/wiki_syntax_detailed.html Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

    Wiki formatting

    + +

    Links

    + +

    Redmine links

    + +

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    +
      +
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • +
    • Link to an issue note: #124-6, or #124#note-6
    • +
    + +

    Wiki links:

    + +
      +
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • +
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • +
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • +
    + +

    You can also link to pages of an other project wiki:

    + +
      +
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • +
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • +
    + +

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    + +

    Links to other resources:

    + +
      +
    • Documents: +
        +
      • document#17 (link to document with id 17)
      • +
      • document:Greetings (link to the document with title "Greetings")
      • +
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • +
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • +
    • +
    + +
      +
    • Versions: +
        +
      • version#3 (link to version with id 3)
      • +
      • version:1.0.0 (link to version named "1.0.0")
      • +
      • version:"1.0 beta 2"
      • +
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • +
    • +
    + +
      +
    • Attachments: +
        +
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • +
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • +
    • +
    + +
      +
    • Changesets: +
        +
      • r758 (link to a changeset)
      • +
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • +
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • +
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • +
      • sandbox:r758 (link to a changeset of another project)
      • +
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • +
    • +
    + +
      +
    • Repository files: +
        +
      • source:some/file (link to the file located at /some/file in the project's repository)
      • +
      • source:some/file@52 (link to the file's revision 52)
      • +
      • source:some/file#L120 (link to line 120 of the file)
      • +
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • +
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • +
      • export:some/file (force the download of the file)
      • +
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • +
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • +
      • sandbox:export:some/file (force the download of the file)
      • +
    • +
    + +
      +
    • Forum messages: +
        +
      • message#1218 (link to message with id 1218)
      • +
    • +
    + +
      +
    • Projects: +
        +
      • project#3 (link to project with id 3)
      • +
      • project:someproject (link to project named "someproject")
      • +
    • +
    + + +

    Escaping:

    + +
      +
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • +
    + + +

    External links

    + +

    HTTP URLs and email addresses are automatically turned into clickable links:

    + +
    +http://www.redmine.org, someone@foo.bar
    +
    + +

    displays: http://www.redmine.org,

    + +

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    + +
    +"Redmine web site":http://www.redmine.org
    +
    + +

    displays: Redmine web site

    + + +

    Text formatting

    + + +

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    + +

    Font style

    + +
    +* *bold*
    +* _italic_
    +* _*bold italic*_
    +* +underline+
    +* -strike-through-
    +
    + +

    Display:

    + +
      +
    • bold
    • +
    • italic
    • +
    • bold italic
    • +
    • underline
    • +
    • strike-through
    • +
    + +

    Inline images

    + +
      +
    • !image_url! displays an image located at image_url (textile syntax)
    • +
    • !>image_url! right floating image
    • +
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • +
    + +

    Headings

    + +
    +h1. Heading
    +h2. Subheading
    +h3. Subsubheading
    +
    + +

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    + + +

    Paragraphs

    + +
    +p>. right aligned
    +p=. centered
    +
    + +

    This is a centered paragraph.

    + + +

    Blockquotes

    + +

    Start the paragraph with bq.

    + +
    +bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    +To go live, all you need to add is a database and a web server.
    +
    + +

    Display:

    + +
    +

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    +
    + + +

    Table of content

    + +
    +{{toc}} => left aligned toc
    +{{>toc}} => right aligned toc
    +
    + +

    Horizontal Rule

    + +
    +---
    +
    + +

    Macros

    + +

    Redmine has the following builtin macros:

    + +

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    + +
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    + + +

    Code highlighting

    + +

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    + +

    You can highlight code in your wiki page using this syntax:

    + +
    +<pre><code class="ruby">
    +  Place you code here.
    +</code></pre>
    +
    + +

    Example:

    + +
     1 # The Greeter class
    + 2 class Greeter
    + 3   def initialize(name)
    + 4     @name = name.capitalize
    + 5   end
    + 6
    + 7   def salute
    + 8     puts "Hello #{@name}!"
    + 9   end
    +10 end
    +
    + + diff -r d98d22a98252 -r afce8026aaeb public/images/document.png Binary file public/images/document.png has changed diff -r d98d22a98252 -r afce8026aaeb public/images/hourglass.png Binary file public/images/hourglass.png has changed diff -r d98d22a98252 -r afce8026aaeb public/images/message.png Binary file public/images/message.png has changed diff -r d98d22a98252 -r afce8026aaeb public/images/wiki_edit.png Binary file public/images/wiki_edit.png has changed diff -r d98d22a98252 -r afce8026aaeb public/javascripts/application.js --- a/public/javascripts/application.js Wed May 07 14:15:02 2014 +0100 +++ b/public/javascripts/application.js Tue Sep 09 09:34:53 2014 +0100 @@ -1,12 +1,8 @@ /* Redmine - project management software - Copyright (C) 2006-2012 Jean-Philippe Lang */ + Copyright (C) 2006-2014 Jean-Philippe Lang */ function checkAll(id, checked) { - if (checked) { - $('#'+id).find('input[type=checkbox]').attr('checked', true); - } else { - $('#'+id).find('input[type=checkbox]').removeAttr('checked'); - } + $('#'+id).find('input[type=checkbox]:enabled').attr('checked', checked); } function toggleCheckboxesBySelector(selector) { @@ -14,12 +10,12 @@ $(selector).each(function(index) { if (!$(this).is(':checked')) { all_checked = false; } }); - $(selector).attr('checked', !all_checked) + $(selector).attr('checked', !all_checked); } function showAndScrollTo(id, focus) { $('#'+id).show(); - if (focus!=null) { + if (focus !== null) { $('#'+focus).focus(); } $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100); @@ -78,20 +74,20 @@ fieldset.children('div').hide(); } -function initFilters(){ - $('#add_filter_select').change(function(){ +function initFilters() { + $('#add_filter_select').change(function() { addFilter($(this).val(), '', []); }); - $('#filters-table td.field input[type=checkbox]').each(function(){ + $('#filters-table td.field input[type=checkbox]').each(function() { toggleFilter($(this).val()); }); - $('#filters-table td.field input[type=checkbox]').live('click',function(){ + $('#filters-table td.field input[type=checkbox]').live('click', function() { toggleFilter($(this).val()); }); - $('#filters-table .toggle-multiselect').live('click',function(){ + $('#filters-table .toggle-multiselect').live('click', function() { toggleMultiSelect($(this).siblings('select')); }); - $('#filters-table input[type=text]').live('keypress', function(e){ + $('#filters-table input[type=text]').live('keypress', function(e) { if (e.keyCode == 13) submit_query_form("query_form"); }); } @@ -106,7 +102,7 @@ } $('#cb_'+fieldId).attr('checked', true); toggleFilter(field); - $('#add_filter_select').val('').children('option').each(function(){ + $('#add_filter_select').val('').children('option').each(function() { if ($(this).attr('value') == field) { $(this).attr('disabled', true); } @@ -117,6 +113,7 @@ var fieldId = field.replace('.', '_'); var filterTable = $("#filters-table"); var filterOptions = availableFilters[field]; + if (!filterOptions) return; var operators = operatorByType[filterOptions['type']]; var filterValues = filterOptions['values']; var i, select; @@ -129,14 +126,14 @@ filterTable.append(tr); select = tr.find('td.operator select'); - for (i=0;i').val(operators[i]).text(operatorLabels[operators[i]]); - if (operators[i] == operator) {option.attr('selected', true)}; + if (operators[i] == operator) { option.attr('selected', true); } select.append(option); } - select.change(function(){toggleOperator(field)}); + select.change(function(){ toggleOperator(field); }); - switch (filterOptions['type']){ + switch (filterOptions['type']) { case "list": case "list_optional": case "list_status": @@ -146,8 +143,8 @@ '  ' ); select = tr.find('td.values select'); - if (values.length > 1) {select.attr('multiple', true)}; - for (i=0;i 1) { select.attr('multiple', true); } + for (i = 0; i < filterValues.length; i++) { var filterValue = filterValues[i]; var option = $('
    ","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
    ","
    "]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f -.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
    ").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); - -/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.core.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.21",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a=9||!!b.button?this._mouseStarted?(this._mouseDrag(b),b.preventDefault()):(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b)),!this._mouseStarted):this._mouseUp(b)},_mouseUp:function(b){return a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b)),!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.position.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;return i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1],this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.draggable.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!this.element.data("draggable"))return;return this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy(),this},_mouseCapture:function(b){var c=this.options;return this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(b),this.handle?(c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('
    ').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(b){var c=this.options;return this.helper=this._createHelper(b),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment(),this._trigger("start",b)===!1?(this._clear(),!1):(this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b),!0)},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1)return this._mouseUp({}),!1;this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);var d=this.element[0],e=!1;while(d&&(d=d.parentNode))d==document&&(e=!0);if(!e&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var f=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){f._trigger("stop",b)!==!1&&f._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){return this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b),a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;return a(this.options.handle,this.element).find("*").andSelf().each(function(){this==b.target&&(c=!0)}),c},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo),d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute"),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[b.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,b.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(b.containment=="document"?0:a(window).scrollLeft())+a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(b.containment=="document"?0:a(window).scrollTop())+(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)&&b.containment.constructor!=Array){var c=a(b.containment),d=c[0];if(!d)return;var e=c.offset(),f=a(d).css("overflow")!="hidden";this.containment=[(parseInt(a(d).css("borderLeftWidth"),10)||0)+(parseInt(a(d).css("paddingLeft"),10)||0),(parseInt(a(d).css("borderTopWidth"),10)||0)+(parseInt(a(d).css("paddingTop"),10)||0),(f?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(a(d).css("borderLeftWidth"),10)||0)-(parseInt(a(d).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(a(d).css("borderTopWidth"),10)||0)-(parseInt(a(d).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=c}else b.containment.constructor==Array&&(this.containment=b.containment)},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName),f=b.pageX,g=b.pageY;if(this.originalPosition){var h;if(this.containment){if(this.relative_container){var i=this.relative_container.offset();h=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else h=this.containment;b.pageX-this.offset.click.lefth[2]&&(f=h[2]+this.offset.click.left),b.pageY-this.offset.click.top>h[3]&&(g=h[3]+this.offset.click.top)}if(c.grid){var j=c.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;g=h?j-this.offset.click.toph[3]?j-this.offset.click.toph[2]?k-this.offset.click.left=0;k--){var l=d.snapElements[k].left,m=l+d.snapElements[k].width,n=d.snapElements[k].top,o=n+d.snapElements[k].height;if(!(l-f=k&&g<=l||h>=k&&h<=l||gl)&&(e>=i&&e<=j||f>=i&&f<=j||ej);default:return!1}},a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(b,c){var d=a.ui.ddmanager.droppables[b.options.scope]||[],e=c?c.type:null,f=(b.currentItem||b.element).find(":data(droppable)").andSelf();g:for(var h=0;h
    ').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=c.handles||(a(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var d=this.handles.split(",");this.handles={};for(var e=0;e');h.css({zIndex:c.zIndex}),"se"==f&&h.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[f]=".ui-resizable-"+f,this.element.append(h)}}this._renderAxis=function(b){b=b||this.element;for(var c in this.handles){this.handles[c].constructor==String&&(this.handles[c]=a(this.handles[c],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var d=a(this.handles[c],this.element),e=0;e=/sw|ne|nw|se|n|s/.test(c)?d.outerHeight():d.outerWidth();var f=["padding",/ne|nw|n/.test(c)?"Top":/se|sw|s/.test(c)?"Bottom":/^e$/.test(c)?"Right":"Left"].join("");b.css(f,e),this._proportionallyResize()}if(!a(this.handles[c]).length)continue}},this._renderAxis(this.element),this._handles=a(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!b.resizing){if(this.className)var a=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=a&&a[1]?a[1]:"se"}}),c.autoHide&&(this._handles.hide(),a(this.element).addClass("ui-resizable-autohide").hover(function(){if(c.disabled)return;a(this).removeClass("ui-resizable-autohide"),b._handles.show()},function(){if(c.disabled)return;b.resizing||(a(this).addClass("ui-resizable-autohide"),b._handles.hide())})),this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(b){a(b).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var c=this.element;c.after(this.originalElement.css({position:c.css("position"),width:c.outerWidth(),height:c.outerHeight(),top:c.css("top"),left:c.css("left")})).remove()}return this.originalElement.css("resize",this.originalResizeStyle),b(this.originalElement),this},_mouseCapture:function(b){var c=!1;for(var d in this.handles)a(this.handles[d])[0]==b.target&&(c=!0);return!this.options.disabled&&c},_mouseStart:function(b){var d=this.options,e=this.element.position(),f=this.element;this.resizing=!0,this.documentScroll={top:a(document).scrollTop(),left:a(document).scrollLeft()},(f.is(".ui-draggable")||/absolute/.test(f.css("position")))&&f.css({position:"absolute",top:e.top,left:e.left}),this._renderProxy();var g=c(this.helper.css("left")),h=c(this.helper.css("top"));d.containment&&(g+=a(d.containment).scrollLeft()||0,h+=a(d.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:g,top:h},this.size=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalSize=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalPosition={left:g,top:h},this.sizeDiff={width:f.outerWidth()-f.width(),height:f.outerHeight()-f.height()},this.originalMousePosition={left:b.pageX,top:b.pageY},this.aspectRatio=typeof d.aspectRatio=="number"?d.aspectRatio:this.originalSize.width/this.originalSize.height||1;var i=a(".ui-resizable-"+this.axis).css("cursor");return a("body").css("cursor",i=="auto"?this.axis+"-resize":i),f.addClass("ui-resizable-resizing"),this._propagate("start",b),!0},_mouseDrag:function(b){var c=this.helper,d=this.options,e={},f=this,g=this.originalMousePosition,h=this.axis,i=b.pageX-g.left||0,j=b.pageY-g.top||0,k=this._change[h];if(!k)return!1;var l=k.apply(this,[b,i,j]),m=a.browser.msie&&a.browser.version<7,n=this.sizeDiff;this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)l=this._updateRatio(l,b);return l=this._respectSize(l,b),this._propagate("resize",b),c.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",b,this.ui()),!1},_mouseStop:function(b){this.resizing=!1;var c=this.options,d=this;if(this._helper){var e=this._proportionallyResizeElements,f=e.length&&/textarea/i.test(e[0].nodeName),g=f&&a.ui.hasScroll(e[0],"left")?0:d.sizeDiff.height,h=f?0:d.sizeDiff.width,i={width:d.helper.width()-h,height:d.helper.height()-g},j=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,k=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;c.animate||this.element.css(a.extend(i,{top:k,left:j})),d.helper.height(d.size.height),d.helper.width(d.size.width),this._helper&&!c.animate&&this._proportionallyResize()}return a("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",b),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(a){var b=this.options,c,e,f,g,h;h={minWidth:d(b.minWidth)?b.minWidth:0,maxWidth:d(b.maxWidth)?b.maxWidth:Infinity,minHeight:d(b.minHeight)?b.minHeight:0,maxHeight:d(b.maxHeight)?b.maxHeight:Infinity};if(this._aspectRatio||a)c=h.minHeight*this.aspectRatio,f=h.minWidth/this.aspectRatio,e=h.maxHeight*this.aspectRatio,g=h.maxWidth/this.aspectRatio,c>h.minWidth&&(h.minWidth=c),f>h.minHeight&&(h.minHeight=f),ea.width,k=d(a.height)&&e.minHeight&&e.minHeight>a.height;j&&(a.width=e.minWidth),k&&(a.height=e.minHeight),h&&(a.width=e.maxWidth),i&&(a.height=e.maxHeight);var l=this.originalPosition.left+this.originalSize.width,m=this.position.top+this.size.height,n=/sw|nw|w/.test(g),o=/nw|ne|n/.test(g);j&&n&&(a.left=l-e.minWidth),h&&n&&(a.left=l-e.maxWidth),k&&o&&(a.top=m-e.minHeight),i&&o&&(a.top=m-e.maxHeight);var p=!a.width&&!a.height;return p&&!a.left&&a.top?a.top=null:p&&!a.top&&a.left&&(a.left=null),a},_proportionallyResize:function(){var b=this.options;if(!this._proportionallyResizeElements.length)return;var c=this.helper||this.element;for(var d=0;d');var d=a.browser.msie&&a.browser.version<7,e=d?1:0,f=d?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+f,height:this.element.outerHeight()+f,position:"absolute",left:this.elementOffset.left-e+"px",top:this.elementOffset.top-e+"px",zIndex:++c.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(a,b,c){return{width:this.originalSize.width+b}},w:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{left:f.left+b,width:e.width-b}},n:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{top:f.top+c,height:e.height-c}},s:function(a,b,c){return{height:this.originalSize.height+c}},se:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},sw:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,c,d]))},ne:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},nw:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,c,d]))}},_propagate:function(b,c){a.ui.plugin.call(this,b,[c,this.ui()]),b!="resize"&&this._trigger(b,c,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),a.extend(a.ui.resizable,{version:"1.8.21"}),a.ui.plugin.add("resizable","alsoResize",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=function(b){a(b).each(function(){var b=a(this);b.data("resizable-alsoresize",{width:parseInt(b.width(),10),height:parseInt(b.height(),10),left:parseInt(b.css("left"),10),top:parseInt(b.css("top"),10)})})};typeof e.alsoResize=="object"&&!e.alsoResize.parentNode?e.alsoResize.length?(e.alsoResize=e.alsoResize[0],f(e.alsoResize)):a.each(e.alsoResize,function(a){f(a)}):f(e.alsoResize)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.originalSize,g=d.originalPosition,h={height:d.size.height-f.height||0,width:d.size.width-f.width||0,top:d.position.top-g.top||0,left:d.position.left-g.left||0},i=function(b,d){a(b).each(function(){var b=a(this),e=a(this).data("resizable-alsoresize"),f={},g=d&&d.length?d:b.parents(c.originalElement[0]).length?["width","height"]:["width","height","top","left"];a.each(g,function(a,b){var c=(e[b]||0)+(h[b]||0);c&&c>=0&&(f[b]=c||null)}),b.css(f)})};typeof e.alsoResize=="object"&&!e.alsoResize.nodeType?a.each(e.alsoResize,function(a,b){i(a,b)}):i(e.alsoResize)},stop:function(b,c){a(this).removeData("resizable-alsoresize")}}),a.ui.plugin.add("resizable","animate",{stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d._proportionallyResizeElements,g=f.length&&/textarea/i.test(f[0].nodeName),h=g&&a.ui.hasScroll(f[0],"left")?0:d.sizeDiff.height,i=g?0:d.sizeDiff.width,j={width:d.size.width-i,height:d.size.height-h},k=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,l=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;d.element.animate(a.extend(j,l&&k?{top:l,left:k}:{}),{duration:e.animateDuration,easing:e.animateEasing,step:function(){var c={width:parseInt(d.element.css("width"),10),height:parseInt(d.element.css("height"),10),top:parseInt(d.element.css("top"),10),left:parseInt(d.element.css("left"),10)};f&&f.length&&a(f[0]).css({width:c.width,height:c.height}),d._updateCache(c),d._propagate("resize",b)}})}}),a.ui.plugin.add("resizable","containment",{start:function(b,d){var e=a(this).data("resizable"),f=e.options,g=e.element,h=f.containment,i=h instanceof a?h.get(0):/parent/.test(h)?g.parent().get(0):h;if(!i)return;e.containerElement=a(i);if(/document/.test(h)||h==document)e.containerOffset={left:0,top:0},e.containerPosition={left:0,top:0},e.parentData={element:a(document),left:0,top:0,width:a(document).width(),height:a(document).height()||document.body.parentNode.scrollHeight};else{var j=a(i),k=[];a(["Top","Right","Left","Bottom"]).each(function(a,b){k[a]=c(j.css("padding"+b))}),e.containerOffset=j.offset(),e.containerPosition=j.position(),e.containerSize={height:j.innerHeight()-k[3],width:j.innerWidth()-k[1]};var l=e.containerOffset,m=e.containerSize.height,n=e.containerSize.width,o=a.ui.hasScroll(i,"left")?i.scrollWidth:n,p=a.ui.hasScroll(i)?i.scrollHeight:m;e.parentData={element:i,left:l.left,top:l.top,width:o,height:p}}},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.containerSize,g=d.containerOffset,h=d.size,i=d.position,j=d._aspectRatio||b.shiftKey,k={top:0,left:0},l=d.containerElement;l[0]!=document&&/static/.test(l.css("position"))&&(k=g),i.left<(d._helper?g.left:0)&&(d.size.width=d.size.width+(d._helper?d.position.left-g.left:d.position.left-k.left),j&&(d.size.height=d.size.width/d.aspectRatio),d.position.left=e.helper?g.left:0),i.top<(d._helper?g.top:0)&&(d.size.height=d.size.height+(d._helper?d.position.top-g.top:d.position.top),j&&(d.size.width=d.size.height*d.aspectRatio),d.position.top=d._helper?g.top:0),d.offset.left=d.parentData.left+d.position.left,d.offset.top=d.parentData.top+d.position.top;var m=Math.abs((d._helper?d.offset.left-k.left:d.offset.left-k.left)+d.sizeDiff.width),n=Math.abs((d._helper?d.offset.top-k.top:d.offset.top-g.top)+d.sizeDiff.height),o=d.containerElement.get(0)==d.element.parent().get(0),p=/relative|absolute/.test(d.containerElement.css("position"));o&&p&&(m-=d.parentData.left),m+d.size.width>=d.parentData.width&&(d.size.width=d.parentData.width-m,j&&(d.size.height=d.size.width/d.aspectRatio)),n+d.size.height>=d.parentData.height&&(d.size.height=d.parentData.height-n,j&&(d.size.width=d.size.height*d.aspectRatio))},stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.position,g=d.containerOffset,h=d.containerPosition,i=d.containerElement,j=a(d.helper),k=j.offset(),l=j.outerWidth()-d.sizeDiff.width,m=j.outerHeight()-d.sizeDiff.height;d._helper&&!e.animate&&/relative/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m}),d._helper&&!e.animate&&/static/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m})}}),a.ui.plugin.add("resizable","ghost",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size;d.ghost=d.originalElement.clone(),d.ghost.css({opacity:.25,display:"block",position:"relative",height:f.height,width:f.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof e.ghost=="string"?e.ghost:""),d.ghost.appendTo(d.helper)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})},stop:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.helper&&d.helper.get(0).removeChild(d.ghost.get(0))}}),a.ui.plugin.add("resizable","grid",{resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size,g=d.originalSize,h=d.originalPosition,i=d.axis,j=e._aspectRatio||b.shiftKey;e.grid=typeof e.grid=="number"?[e.grid,e.grid]:e.grid;var k=Math.round((f.width-g.width)/(e.grid[0]||1))*(e.grid[0]||1),l=Math.round((f.height-g.height)/(e.grid[1]||1))*(e.grid[1]||1);/^(se|s|e)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l):/^(ne)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l):/^(sw)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.left=h.left-k):(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l,d.position.left=h.left-k)}});var c=function(a){return parseInt(a,10)||0},d=function(a){return!isNaN(parseInt(a,10))}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.selectable.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var b=this;this.element.addClass("ui-selectable"),this.dragged=!1;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]),c.addClass("ui-selectee"),c.each(function(){var b=a(this),c=b.offset();a.data(this,"selectable-item",{element:this,$element:b,left:c.left,top:c.top,right:c.left+b.outerWidth(),bottom:c.top+b.outerHeight(),startselected:!1,selected:b.hasClass("ui-selected"),selecting:b.hasClass("ui-selecting"),unselecting:b.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=c.addClass("ui-selectee"),this._mouseInit(),this.helper=a("
    ")},destroy:function(){return this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable"),this._mouseDestroy(),this},_mouseStart:function(b){var c=this;this.opos=[b.pageX,b.pageY];if(this.options.disabled)return;var d=this.options;this.selectees=a(d.filter,this.element[0]),this._trigger("start",b),a(d.appendTo).append(this.helper),this.helper.css({left:b.clientX,top:b.clientY,width:0,height:0}),d.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var d=a.data(this,"selectable-item");d.startselected=!0,!b.metaKey&&!b.ctrlKey&&(d.$element.removeClass("ui-selected"),d.selected=!1,d.$element.addClass("ui-unselecting"),d.unselecting=!0,c._trigger("unselecting",b,{unselecting:d.element}))}),a(b.target).parents().andSelf().each(function(){var d=a.data(this,"selectable-item");if(d){var e=!b.metaKey&&!b.ctrlKey||!d.$element.hasClass("ui-selected");return d.$element.removeClass(e?"ui-unselecting":"ui-selected").addClass(e?"ui-selecting":"ui-unselecting"),d.unselecting=!e,d.selecting=e,d.selected=e,e?c._trigger("selecting",b,{selecting:d.element}):c._trigger("unselecting",b,{unselecting:d.element}),!1}})},_mouseDrag:function(b){var c=this;this.dragged=!0;if(this.options.disabled)return;var d=this.options,e=this.opos[0],f=this.opos[1],g=b.pageX,h=b.pageY;if(e>g){var i=g;g=e,e=i}if(f>h){var i=h;h=f,f=i}return this.helper.css({left:e,top:f,width:g-e,height:h-f}),this.selectees.each(function(){var i=a.data(this,"selectable-item");if(!i||i.element==c.element[0])return;var j=!1;d.tolerance=="touch"?j=!(i.left>g||i.righth||i.bottome&&i.rightf&&i.bottom *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var a=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},destroy:function(){a.Widget.prototype.destroy.call(this),this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var b=this.items.length-1;b>=0;b--)this.items[b].item.removeData(this.widgetName+"-item");return this},_setOption:function(b,c){b==="disabled"?(this.options[b]=c,this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")):a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(b,c){var d=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(b);var e=null,f=this,g=a(b.target).parents().each(function(){if(a.data(this,d.widgetName+"-item")==f)return e=a(this),!1});a.data(b.target,d.widgetName+"-item")==f&&(e=a(b.target));if(!e)return!1;if(this.options.handle&&!c){var h=!1;a(this.options.handle,e).find("*").andSelf().each(function(){this==b.target&&(h=!0)});if(!h)return!1}return this.currentItem=e,this._removeCurrentsFromItems(),!0},_mouseStart:function(b,c,d){var e=this.options,f=this;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(b),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,e.cursorAt&&this._adjustOffsetFromHelper(e.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),e.containment&&this._setContainment(),e.cursor&&(a("body").css("cursor")&&(this._storedCursor=a("body").css("cursor")),a("body").css("cursor",e.cursor)),e.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",e.opacity)),e.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",e.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",b,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!d)for(var g=this.containers.length-1;g>=0;g--)this.containers[g]._trigger("activate",b,f._uiHash(this));return a.ui.ddmanager&&(a.ui.ddmanager.current=this),a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(b),!0},_mouseDrag:function(b){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var c=this.options,d=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-b.pageY=0;e--){var f=this.items[e],g=f.item[0],h=this._intersectsWithPointer(f);if(!h)continue;if(g!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=g&&!a.ui.contains(this.placeholder[0],g)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],g):!0)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(f))this._rearrange(b,f);else break;this._trigger("change",b,this._uiHash());break}}return this._contactContainers(b),a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),this._trigger("sort",b,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(b,c){if(!b)return;a.ui.ddmanager&&!this.options.dropBehaviour&&a.ui.ddmanager.drop(this,b);if(this.options.revert){var d=this,e=d.placeholder.offset();d.reverting=!0,a(this.helper).animate({left:e.left-this.offset.parent.left-d.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-d.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){d._clear(b)})}else this._clear(b,c);return!1},cancel:function(){var b=this;if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("deactivate",null,b._uiHash(this)),this.containers[c].containerCache.over&&(this.containers[c]._trigger("out",null,b._uiHash(this)),this.containers[c].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),a.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?a(this.domPosition.prev).after(this.currentItem):a(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},a(c).each(function(){var c=(a(b.item||this).attr(b.attribute||"id")||"").match(b.expression||/(.+)[-=_](.+)/);c&&d.push((b.key||c[1]+"[]")+"="+(b.key&&b.expression?c[1]:c[2]))}),!d.length&&b.key&&d.push(b.key+"="),d.join("&")},toArray:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},c.each(function(){d.push(a(b.item||this).attr(b.attribute||"id")||"")}),d},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,d=this.positionAbs.top,e=d+this.helperProportions.height,f=a.left,g=f+a.width,h=a.top,i=h+a.height,j=this.offset.click.top,k=this.offset.click.left,l=d+j>h&&d+jf&&b+ka[this.floating?"width":"height"]?l:f0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){return this._refreshItems(a),this.refreshPositions(),this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(b){var c=this,d=[],e=[],f=this._connectWith();if(f&&b)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&e.push([a.isFunction(j.options.items)?j.options.items.call(j.element):a(j.options.items,j.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),j])}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var g=e.length-1;g>=0;g--)e[g][0].each(function(){d.push(this)});return a(d)},_removeCurrentsFromItems:function(){var a=this.currentItem.find(":data("+this.widgetName+"-item)");for(var b=0;b=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&(e.push([a.isFunction(j.options.items)?j.options.items.call(j.element[0],b,{item:this.currentItem}):a(j.options.items,j.element),j]),this.containers.push(j))}}for(var g=e.length-1;g>=0;g--){var k=e[g][1],l=e[g][0];for(var i=0,m=l.length;i=0;c--){var d=this.items[c];if(d.instance!=this.currentContainer&&this.currentContainer&&d.item[0]!=this.currentItem[0])continue;var e=this.options.toleranceElement?a(this.options.toleranceElement,d.item):d.item;b||(d.width=e.outerWidth(),d.height=e.outerHeight());var f=e.offset();d.left=f.left,d.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var c=this.containers.length-1;c>=0;c--){var f=this.containers[c].element.offset();this.containers[c].containerCache.left=f.left,this.containers[c].containerCache.top=f.top,this.containers[c].containerCache.width=this.containers[c].element.outerWidth(),this.containers[c].containerCache.height=this.containers[c].element.outerHeight()}return this},_createPlaceholder:function(b){var c=b||this,d=c.options;if(!d.placeholder||d.placeholder.constructor==String){var e=d.placeholder;d.placeholder={element:function(){var b=a(document.createElement(c.currentItem[0].nodeName)).addClass(e||c.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return e||(b.style.visibility="hidden"),b},update:function(a,b){if(e&&!d.forcePlaceholderSize)return;b.height()||b.height(c.currentItem.innerHeight()-parseInt(c.currentItem.css("paddingTop")||0,10)-parseInt(c.currentItem.css("paddingBottom")||0,10)),b.width()||b.width(c.currentItem.innerWidth()-parseInt(c.currentItem.css("paddingLeft")||0,10)-parseInt(c.currentItem.css("paddingRight")||0,10))}}}c.placeholder=a(d.placeholder.element.call(c.element,c.currentItem)),c.currentItem.after(c.placeholder),d.placeholder.update(c,c.placeholder)},_contactContainers:function(b){var c=null,d=null;for(var e=this.containers.length-1;e>=0;e--){if(a.ui.contains(this.currentItem[0],this.containers[e].element[0]))continue;if(this._intersectsWith(this.containers[e].containerCache)){if(c&&a.ui.contains(this.containers[e].element[0],c.element[0]))continue;c=this.containers[e],d=e}else this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",b,this._uiHash(this)),this.containers[e].containerCache.over=0)}if(!c)return;if(this.containers.length===1)this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1;else if(this.currentContainer!=this.containers[d]){var f=1e4,g=null,h=this.positionAbs[this.containers[d].floating?"left":"top"];for(var i=this.items.length-1;i>=0;i--){if(!a.ui.contains(this.containers[d].element[0],this.items[i].item[0]))continue;var j=this.containers[d].floating?this.items[i].item.offset().left:this.items[i].item.offset().top;Math.abs(j-h)0?"down":"up")}if(!g&&!this.options.dropOnEmpty)return;this.currentContainer=this.containers[d],g?this._rearrange(b,g,null,!0):this._rearrange(b,null,this.containers[d].element,!0),this._trigger("change",b,this._uiHash()),this.containers[d]._trigger("change",b,this._uiHash(this)),this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1}},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b,this.currentItem])):c.helper=="clone"?this.currentItem.clone():this.currentItem;return d.parents("body").length||a(c.appendTo!="parent"?c.appendTo:this.currentItem[0].parentNode)[0].appendChild(d[0]),d[0]==this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(d[0].style.width==""||c.forceHelperSize)&&d.width(this.currentItem.width()),(d[0].style.height==""||c.forceHelperSize)&&d.height(this.currentItem.height()),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.currentItem.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)){var c=a(b.containment)[0],d=a(b.containment).offset(),e=a(c).css("overflow")!="hidden";this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(e?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(e?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName);this.cssPosition=="relative"&&(this.scrollParent[0]==document||this.scrollParent[0]==this.offsetParent[0])&&(this.offset.relative=this._getRelativeOffset());var f=b.pageX,g=b.pageY;if(this.originalPosition){this.containment&&(b.pageX-this.offset.click.leftthis.containment[2]&&(f=this.containment[2]+this.offset.click.left),b.pageY-this.offset.click.top>this.containment[3]&&(g=this.containment[3]+this.offset.click.top));if(c.grid){var h=this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1];g=this.containment?h-this.offset.click.topthis.containment[3]?h-this.offset.click.topthis.containment[2]?i-this.offset.click.left=0;f--)a.ui.contains(this.containers[f].element[0],this.currentItem[0])&&!c&&(d.push(function(a){return function(b){a._trigger("receive",b,this._uiHash(this))}}.call(this,this.containers[f])),d.push(function(a){return function(b){a._trigger("update",b,this._uiHash(this))}}.call(this,this.containers[f])))}for(var f=this.containers.length-1;f>=0;f--)c||d.push(function(a){return function(b){a._trigger("deactivate",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over&&(d.push(function(a){return function(b){a._trigger("out",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over=0);this._storedCursor&&a("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!c){this._trigger("beforeStop",b,this._uiHash());for(var f=0;f li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var b=this,c=b.options;b.running=0,b.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"),b.headers=b.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-focus")}),b.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");if(c.navigation){var d=b.element.find("a").filter(c.navigationFilter).eq(0);if(d.length){var e=d.closest(".ui-accordion-header");e.length?b.active=e:b.active=d.closest(".ui-accordion-content").prev()}}b.active=b._findActive(b.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top"),b.active.next().addClass("ui-accordion-content-active"),b._createIcons(),b.resize(),b.element.attr("role","tablist"),b.headers.attr("role","tab").bind("keydown.accordion",function(a){return b._keydown(a)}).next().attr("role","tabpanel"),b.headers.not(b.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide(),b.active.length?b.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):b.headers.eq(0).attr("tabIndex",0),a.browser.safari||b.headers.find("a").attr("tabIndex",-1),c.event&&b.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(a){b._clickHandler.call(b,a,this),a.preventDefault()})},_createIcons:function(){var b=this.options;b.icons&&(a("").addClass("ui-icon "+b.icons.header).prependTo(this.headers),this.active.children(".ui-icon").toggleClass(b.icons.header).toggleClass(b.icons.headerSelected),this.element.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.children(".ui-icon").remove(),this.element.removeClass("ui-accordion-icons")},destroy:function(){var b=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"),this.headers.find("a").removeAttr("tabIndex"),this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");return(b.autoHeight||b.fillHeight)&&c.css("height",""),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b=="active"&&this.activate(c),b=="icons"&&(this._destroyIcons(),c&&this._createIcons()),b=="disabled"&&this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(b){if(this.options.disabled||b.altKey||b.ctrlKey)return;var c=a.ui.keyCode,d=this.headers.length,e=this.headers.index(b.target),f=!1;switch(b.keyCode){case c.RIGHT:case c.DOWN:f=this.headers[(e+1)%d];break;case c.LEFT:case c.UP:f=this.headers[(e-1+d)%d];break;case c.SPACE:case c.ENTER:this._clickHandler({target:b.target},b.target),b.preventDefault()}return f?(a(b.target).attr("tabIndex",-1),a(f).attr("tabIndex",0),f.focus(),!1):!0},resize:function(){var b=this.options,c;if(b.fillSpace){if(a.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height(),a.browser.msie&&this.element.parent().css("overflow",d),this.headers.each(function(){c-=a(this).outerHeight(!0)}),this.headers.next().each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+a(this).height()))}).css("overflow","auto")}else b.autoHeight&&(c=0,this.headers.next().each(function(){c=Math.max(c,a(this).height("").height())}).height(c));return this},activate:function(a){this.options.active=a;var b=this._findActive(a)[0];return this._clickHandler({target:b},b),this},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===!1?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,c){var d=this.options;if(d.disabled)return;if(!b.target){if(!d.collapsible)return;this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),this.active.next().addClass("ui-accordion-content-active");var e=this.active.next(),f={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:e},g=this.active=a([]);this._toggle(g,e,f);return}var h=a(b.currentTarget||c),i=h[0]===this.active[0];d.active=d.collapsible&&i?!1:this.headers.index(h);if(this.running||!d.collapsible&&i)return;var j=this.active,g=h.next(),e=this.active.next(),f={options:d,newHeader:i&&d.collapsible?a([]):h,oldHeader:this.active,newContent:i&&d.collapsible?a([]):g,oldContent:e},k=this.headers.index(this.active[0])>this.headers.index(h[0]);this.active=i?a([]):h,this._toggle(g,e,f,i,k),j.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),i||(h.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected),h.next().addClass("ui-accordion-content-active"));return},_toggle:function(b,c,d,e,f){var g=this,h=g.options;g.toShow=b,g.toHide=c,g.data=d;var i=function(){if(!g)return;return g._completed.apply(g,arguments)};g._trigger("changestart",null,g.data),g.running=c.size()===0?b.size():c.size();if(h.animated){var j={};h.collapsible&&e?j={toShow:a([]),toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace}:j={toShow:b,toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace},h.proxied||(h.proxied=h.animated),h.proxiedDuration||(h.proxiedDuration=h.duration),h.animated=a.isFunction(h.proxied)?h.proxied(j):h.proxied,h.duration=a.isFunction(h.proxiedDuration)?h.proxiedDuration(j):h.proxiedDuration;var k=a.ui.accordion.animations,l=h.duration,m=h.animated;m&&!k[m]&&!a.easing[m]&&(m="slide"),k[m]||(k[m]=function(a){this.slide(a,{easing:m,duration:l||700})}),k[m](j)}else h.collapsible&&e?b.toggle():(c.hide(),b.show()),i(!0);c.prev().attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).blur(),b.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(this.running)return;this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""}),this.toHide.removeClass("ui-accordion-content-active"),this.toHide.length&&(this.toHide.parent()[0].className=this.toHide.parent()[0].className),this._trigger("change",null,this.data)}}),a.extend(a.ui.accordion,{version:"1.8.21",animations:{slide:function(b,c){b=a.extend({easing:"swing",duration:300},b,c);if(!b.toHide.size()){b.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},b);return}if(!b.toShow.size()){b.toHide.animate({height:"hide",paddingTop:"hide",paddingBottom:"hide"},b);return}var d=b.toShow.css("overflow"),e=0,f={},g={},h=["height","paddingTop","paddingBottom"],i,j=b.toShow;i=j[0].style.width,j.width(j.parent().width()-parseFloat(j.css("paddingLeft"))-parseFloat(j.css("paddingRight"))-(parseFloat(j.css("borderLeftWidth"))||0)-(parseFloat(j.css("borderRightWidth"))||0)),a.each(h,function(c,d){g[d]="hide";var e=(""+a.css(b.toShow[0],d)).match(/^([\d+-.]+)(.*)$/);f[d]={value:e[1],unit:e[2]||"px"}}),b.toShow.css({height:0,overflow:"hidden"}).show(),b.toHide.filter(":hidden").each(b.complete).end().filter(":visible").animate(g,{step:function(a,c){c.prop=="height"&&(e=c.end-c.start===0?0:(c.now-c.start)/(c.end-c.start)),b.toShow[0].style[c.prop]=e*f[c.prop].value+f[c.prop].unit},duration:b.duration,easing:b.easing,complete:function(){b.autoHeight||b.toShow.css("height",""),b.toShow.css({width:i,overflow:d}),b.complete()}})},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1e3:200})}}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.autocomplete.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){var c=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var b=this,c=this.element[0].ownerDocument,d;this.isMultiLine=this.element.is("textarea"),this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(b.options.disabled||b.element.propAttr("readOnly"))return;d=!1;var e=a.ui.keyCode;switch(c.keyCode){case e.PAGE_UP:b._move("previousPage",c);break;case e.PAGE_DOWN:b._move("nextPage",c);break;case e.UP:b._keyEvent("previous",c);break;case e.DOWN:b._keyEvent("next",c);break;case e.ENTER:case e.NUMPAD_ENTER:b.menu.active&&(d=!0,c.preventDefault());case e.TAB:if(!b.menu.active)return;b.menu.select(c);break;case e.ESCAPE:b.element.val(b.term),b.close(c);break;default:clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}}).bind("keypress.autocomplete",function(a){d&&(d=!1,a.preventDefault())}).bind("focus.autocomplete",function(){if(b.options.disabled)return;b.selectedItem=null,b.previous=b.element.val()}).bind("blur.autocomplete",function(a){if(b.options.disabled)return;clearTimeout(b.searching),b.closing=setTimeout(function(){b.close(a),b._change(a)},150)}),this._initSource(),this.menu=a("
      ").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",c)[0]).mousedown(function(c){var d=b.menu.element[0];a(c.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(c){c.target!==b.element[0]&&c.target!==d&&!a.ui.contains(d,c.target)&&b.close()})},1),setTimeout(function(){clearTimeout(b.closing)},13)}).menu({focus:function(a,c){var d=c.item.data("item.autocomplete");!1!==b._trigger("focus",a,{item:d})&&/^key/.test(a.originalEvent.type)&&b.element.val(d.value)},selected:function(a,d){var e=d.item.data("item.autocomplete"),f=b.previous;b.element[0]!==c.activeElement&&(b.element.focus(),b.previous=f,setTimeout(function(){b.previous=f,b.selectedItem=e},1)),!1!==b._trigger("select",a,{item:e})&&b.element.val(e.value),b.term=b.element.val(),b.close(a),b.selectedItem=e},blur:function(a,c){b.menu.element.is(":visible")&&b.element.val()!==b.term&&b.element.val(b.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"),a.fn.bgiframe&&this.menu.element.bgiframe(),b.beforeunloadHandler=function(){b.element.removeAttr("autocomplete")},a(window).bind("beforeunload",b.beforeunloadHandler)},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"),this.menu.element.remove(),a(window).unbind("beforeunload",this.beforeunloadHandler),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b==="source"&&this._initSource(),b==="appendTo"&&this.menu.element.appendTo(a(c||"body",this.element[0].ownerDocument)[0]),b==="disabled"&&c&&this.xhr&&this.xhr.abort()},_initSource:function(){var b=this,c,d;a.isArray(this.options.source)?(c=this.options.source,this.source=function(b,d){d(a.ui.autocomplete.filter(c,b.term))}):typeof this.options.source=="string"?(d=this.options.source,this.source=function(c,e){b.xhr&&b.xhr.abort(),b.xhr=a.ajax({url:d,data:c,dataType:"json",success:function(a,b){e(a)},error:function(){e([])}})}):this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val(),this.term=this.element.val();if(a.length").data("item.autocomplete",c).append(a("").text(c.label)).appendTo(b)},_move:function(a,b){if(!this.menu.element.is(":visible")){this.search(null,b);return}if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term),this.menu.deactivate();return}this.menu[a](b)},widget:function(){return this.menu.element},_keyEvent:function(a,b){if(!this.isMultiLine||this.menu.element.is(":visible"))this._move(a,b),b.preventDefault()}}),a.extend(a.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(b,c){var d=new RegExp(a.ui.autocomplete.escapeRegex(c),"i");return a.grep(b,function(a){return d.test(a.label||a.value||a)})}})})(jQuery),function(a){a.widget("ui.menu",{_create:function(){var b=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){if(!a(c.target).closest(".ui-menu-item a").length)return;c.preventDefault(),b.select(c)}),this.refresh()},refresh:function(){var b=this,c=this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem");c.children("a").addClass("ui-corner-all").attr("tabindex",-1).mouseenter(function(c){b.activate(c,a(this).parent())}).mouseleave(function(){b.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.scrollTop(),e=this.element.height();c<0?this.element.scrollTop(d+c):c>=e&&this.element.scrollTop(d+c-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end(),this._trigger("focus",a,{item:b})},deactivate:function(){if(!this.active)return;this.active.children("a").removeClass("ui-state-hover").removeAttr("id"),this._trigger("blur"),this.active=null},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(!this.active){this.activate(c,this.element.children(b));return}var d=this.active[a+"All"](".ui-menu-item").eq(0);d.length?this.activate(c,d):this.activate(c,this.element.children(b))},nextPage:function(b){if(this.hasScroll()){if(!this.active||this.last()){this.activate(b,this.element.children(".ui-menu-item:first"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c-d+a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:last")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(b){if(this.hasScroll()){if(!this.active||this.first()){this.activate(b,this.element.children(".ui-menu-item:last"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c+d-a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:first")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()",this.element[0].ownerDocument).addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary,f=[];d.primary||d.secondary?(this.options.text&&f.push("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary")),d.primary&&b.prepend(""),d.secondary&&b.append(""),this.options.text||(f.push(e?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||b.attr("title",c))):f.push("ui-button-text-only"),b.addClass(f.join(" "))}}),a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c),a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var b=this.element.css("direction")==="rtl";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(b?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(b?"ui-corner-left":"ui-corner-right").end().end()},destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy"),a.Widget.prototype.destroy.call(this)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.dialog.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||" ",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("
      ")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("
      ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){return b.close(a),!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;return a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle),a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1===c._trigger("beforeClose",b))return;return c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d),c},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;return e.modal&&!b||!e.stack&&!e.modal?d._trigger("focus",c):(e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c),d)},open:function(){if(this._isOpen)return;var b=this,c=b.options,d=b.uiDialog;return b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode!==a.ui.keyCode.TAB)return;var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey)return d.focus(1),!1;if(b.target===d[0]&&b.shiftKey)return e.focus(1),!1}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open"),b},_createButtons:function(b){var c=this,d=!1,e=a("
      ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("
      ").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(g);a.each(d,function(a,b){if(a==="click")return;a in f?e[a](b):e.attr(a,b)}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||" "))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.21",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");return b||(this.uuid+=1,b=this.uuid),"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});return a.fn.bgiframe&&c.bgiframe(),this.instances.push(c),c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;return a.browser.msie&&a.browser.version<7?(b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),b").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(d.range==="min"||d.range==="max"?" ui-slider-range-"+d.range:"")));for(var i=e.length;ic&&(f=c,g=a(this),i=b)}),c.range===!0&&this.values(1)===c.min&&(i+=1,g=a(this.handles[i])),j=this._start(b,i),j===!1?!1:(this._mouseSliding=!0,h._handleIndex=i,g.addClass("ui-state-active").focus(),k=g.offset(),l=!a(b.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:b.pageX-k.left-g.width()/2,top:b.pageY-k.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(b,i,e),this._animateOff=!0,!0))},_mouseStart:function(a){return!0},_mouseDrag:function(a){var b={x:a.pageX,y:a.pageY},c=this._normValueFromMouse(b);return this._slide(a,this._handleIndex,c),!1},_mouseStop:function(a){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(a,this._handleIndex),this._change(a,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b,c,d,e,f;return this.orientation==="horizontal"?(b=this.elementSize.width,c=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(b=this.elementSize.height,c=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),d=c/b,d>1&&(d=1),d<0&&(d=0),this.orientation==="vertical"&&(d=1-d),e=this._valueMax()-this._valueMin(),f=this._valueMin()+d*e,this._trimAlignValue(f)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};return this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("start",a,c)},_slide:function(a,b,c){var d,e,f;this.options.values&&this.options.values.length?(d=this.values(b?0:1),this.options.values.length===2&&this.options.range===!0&&(b===0&&c>d||b===1&&c1){this.options.values[b]=this._trimAlignValue(c),this._refreshValue(),this._change(null,b);return}if(!arguments.length)return this._values();if(!a.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(b):this.value();d=this.options.values,e=arguments[0];for(f=0;f=this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=(a-this._valueMin())%b,d=a-c;return Math.abs(c)*2>=b&&(d+=c>0?b:-b),parseFloat(d.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var b=this.options.range,c=this.options,d=this,e=this._animateOff?!1:c.animate,f,g={},h,i,j,k;this.options.values&&this.options.values.length?this.handles.each(function(b,i){f=(d.values(b)-d._valueMin())/(d._valueMax()-d._valueMin())*100,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",a(this).stop(1,1)[e?"animate":"css"](g,c.animate),d.options.range===!0&&(d.orientation==="horizontal"?(b===0&&d.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({width:f-h+"%"},{queue:!1,duration:c.animate})):(b===0&&d.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({height:f-h+"%"},{queue:!1,duration:c.animate}))),h=f}):(i=this.value(),j=this._valueMin(),k=this._valueMax(),f=k!==j?(i-j)/(k-j)*100:0,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",this.handle.stop(1,1)[e?"animate":"css"](g,c.animate),b==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"},c.animate),b==="max"&&this.orientation==="horizontal"&&this.range[e?"animate":"css"]({width:100-f+"%"},{queue:!1,duration:c.animate}),b==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},c.animate),b==="max"&&this.orientation==="vertical"&&this.range[e?"animate":"css"]({height:100-f+"%"},{queue:!1,duration:c.animate}))}}),a.extend(a.ui.slider,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.tabs.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){function e(){return++c}function f(){return++d}var c=0,d=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:!1,cookie:null,collapsible:!1,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
      ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
    • #{label}
    • "},_create:function(){this._tabify(!0)},_setOption:function(a,b){if(a=="selected"){if(this.options.collapsible&&b==this.options.selected)return;this.select(b)}else this.options[a]=b,this._tabify()},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+e()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+f());return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(a,b){return{tab:a,panel:b,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function m(b,c){b.css("display",""),!a.support.opacity&&c.opacity&&b[0].style.removeAttribute("filter")}var d=this,e=this.options,f=/^#.+/;this.list=this.element.find("ol,ul").eq(0),this.lis=a(" > li:has(a[href])",this.list),this.anchors=this.lis.map(function(){return a("a",this)[0]}),this.panels=a([]),this.anchors.each(function(b,c){var g=a(c).attr("href"),h=g.split("#")[0],i;h&&(h===location.toString().split("#")[0]||(i=a("base")[0])&&h===i.href)&&(g=c.hash,c.href=g);if(f.test(g))d.panels=d.panels.add(d.element.find(d._sanitizeSelector(g)));else if(g&&g!=="#"){a.data(c,"href.tabs",g),a.data(c,"load.tabs",g.replace(/#.*$/,""));var j=d._tabId(c);c.href="#"+j;var k=d.element.find("#"+j);k.length||(k=a(e.panelTemplate).attr("id",j).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(d.panels[b-1]||d.list),k.data("destroy.tabs",!0)),d.panels=d.panels.add(k)}else e.disabled.push(b)}),c?(this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"),this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.lis.addClass("ui-state-default ui-corner-top"),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom"),e.selected===b?(location.hash&&this.anchors.each(function(a,b){if(b.hash==location.hash)return e.selected=a,!1}),typeof e.selected!="number"&&e.cookie&&(e.selected=parseInt(d._cookie(),10)),typeof e.selected!="number"&&this.lis.filter(".ui-tabs-selected").length&&(e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))),e.selected=e.selected||(this.lis.length?0:-1)):e.selected===null&&(e.selected=-1),e.selected=e.selected>=0&&this.anchors[e.selected]||e.selected<0?e.selected:0,e.disabled=a.unique(e.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(a,b){return d.lis.index(a)}))).sort(),a.inArray(e.selected,e.disabled)!=-1&&e.disabled.splice(a.inArray(e.selected,e.disabled),1),this.panels.addClass("ui-tabs-hide"),this.lis.removeClass("ui-tabs-selected ui-state-active"),e.selected>=0&&this.anchors.length&&(d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash)).removeClass("ui-tabs-hide"),this.lis.eq(e.selected).addClass("ui-tabs-selected ui-state-active"),d.element.queue("tabs",function(){d._trigger("show",null,d._ui(d.anchors[e.selected],d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash))[0]))}),this.load(e.selected)),a(window).bind("unload",function(){d.lis.add(d.anchors).unbind(".tabs"),d.lis=d.anchors=d.panels=null})):e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")),this.element[e.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible"),e.cookie&&this._cookie(e.selected,e.cookie);for(var g=0,h;h=this.lis[g];g++)a(h)[a.inArray(g,e.disabled)!=-1&&!a(h).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");e.cache===!1&&this.anchors.removeData("cache.tabs"),this.lis.add(this.anchors).unbind(".tabs");if(e.event!=="mouseover"){var i=function(a,b){b.is(":not(.ui-state-disabled)")&&b.addClass("ui-state-"+a)},j=function(a,b){b.removeClass("ui-state-"+a)};this.lis.bind("mouseover.tabs",function(){i("hover",a(this))}),this.lis.bind("mouseout.tabs",function(){j("hover",a(this))}),this.anchors.bind("focus.tabs",function(){i("focus",a(this).closest("li"))}),this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var k,l;e.fx&&(a.isArray(e.fx)?(k=e.fx[0],l=e.fx[1]):k=l=e.fx);var n=l?function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.hide().removeClass("ui-tabs-hide").animate(l,l.duration||"normal",function(){m(c,l),d._trigger("show",null,d._ui(b,c[0]))})}:function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.removeClass("ui-tabs-hide"),d._trigger("show",null,d._ui(b,c[0]))},o=k?function(a,b){b.animate(k,k.duration||"normal",function(){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),m(b,k),d.element.dequeue("tabs")})}:function(a,b,c){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),d.element.dequeue("tabs")};this.anchors.bind(e.event+".tabs",function(){var b=this,c=a(b).closest("li"),f=d.panels.filter(":not(.ui-tabs-hide)"),g=d.element.find(d._sanitizeSelector(b.hash));if(c.hasClass("ui-tabs-selected")&&!e.collapsible||c.hasClass("ui-state-disabled")||c.hasClass("ui-state-processing")||d.panels.filter(":animated").length||d._trigger("select",null,d._ui(this,g[0]))===!1)return this.blur(),!1;e.selected=d.anchors.index(this),d.abort();if(e.collapsible){if(c.hasClass("ui-tabs-selected"))return e.selected=-1,e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){o(b,f)}).dequeue("tabs"),this.blur(),!1;if(!f.length)return e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this)),this.blur(),!1}e.cookie&&d._cookie(e.selected,e.cookie);if(g.length)f.length&&d.element.queue("tabs",function(){o(b,f)}),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this));else throw"jQuery UI Tabs: Mismatching fragment identifier.";a.browser.msie&&this.blur()}),this.anchors.bind("click.tabs",function(){return!1})},_getIndex:function(a){return typeof a=="string"&&(a=this.anchors.index(this.anchors.filter("[href$='"+a+"']"))),a},destroy:function(){var b=this.options;return this.abort(),this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs"),this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.anchors.each(function(){var b=a.data(this,"href.tabs");b&&(this.href=b);var c=a(this).unbind(".tabs");a.each(["href","load","cache"],function(a,b){c.removeData(b+".tabs")})}),this.lis.unbind(".tabs").add(this.panels).each(function(){a.data(this,"destroy.tabs")?a(this).remove():a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}),b.cookie&&this._cookie(null,b.cookie),this},add:function(c,d,e){e===b&&(e=this.anchors.length);var f=this,g=this.options,h=a(g.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,d)),i=c.indexOf("#")?this._tabId(a("a",h)[0]):c.replace("#","");h.addClass("ui-state-default ui-corner-top").data("destroy.tabs",!0);var j=f.element.find("#"+i);return j.length||(j=a(g.panelTemplate).attr("id",i).data("destroy.tabs",!0)),j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide"),e>=this.lis.length?(h.appendTo(this.list),j.appendTo(this.list[0].parentNode)):(h.insertBefore(this.lis[e]),j.insertBefore(this.panels[e])),g.disabled=a.map(g.disabled,function(a,b){return a>=e?++a:a}),this._tabify(),this.anchors.length==1&&(g.selected=0,h.addClass("ui-tabs-selected ui-state-active"),j.removeClass("ui-tabs-hide"),this.element.queue("tabs",function(){f._trigger("show",null,f._ui(f.anchors[0],f.panels[0]))}),this.load(0)),this._trigger("add",null,this._ui(this.anchors[e],this.panels[e])),this},remove:function(b){b=this._getIndex(b);var c=this.options,d=this.lis.eq(b).remove(),e=this.panels.eq(b).remove();return d.hasClass("ui-tabs-selected")&&this.anchors.length>1&&this.select(b+(b+1=b?--a:a}),this._tabify(),this._trigger("remove",null,this._ui(d.find("a")[0],e[0])),this},enable:function(b){b=this._getIndex(b);var c=this.options;if(a.inArray(b,c.disabled)==-1)return;return this.lis.eq(b).removeClass("ui-state-disabled"),c.disabled=a.grep(c.disabled,function(a,c){return a!=b}),this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b])),this},disable:function(a){a=this._getIndex(a);var b=this,c=this.options;return a!=c.selected&&(this.lis.eq(a).addClass("ui-state-disabled"),c.disabled.push(a),c.disabled.sort(),this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a]))),this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;return this.anchors.eq(a).trigger(this.options.event+".tabs"),this},load:function(b){b=this._getIndex(b);var c=this,d=this.options,e=this.anchors.eq(b)[0],f=a.data(e,"load.tabs");this.abort();if(!f||this.element.queue("tabs").length!==0&&a.data(e,"cache.tabs")){this.element.dequeue("tabs");return}this.lis.eq(b).addClass("ui-state-processing");if(d.spinner){var g=a("span",e);g.data("label.tabs",g.html()).html(d.spinner)}return this.xhr=a.ajax(a.extend({},d.ajaxOptions,{url:f,success:function(f,g){c.element.find(c._sanitizeSelector(e.hash)).html(f),c._cleanup(),d.cache&&a.data(e,"cache.tabs",!0),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.success(f,g)}catch(h){}},error:function(a,f,g){c._cleanup(),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.error(a,f,b,e)}catch(g){}}})),c.element.dequeue("tabs"),this},abort:function(){return this.element.queue([]),this.panels.stop(!1,!0),this.element.queue("tabs",this.element.queue("tabs").splice(-2,2)),this.xhr&&(this.xhr.abort(),delete this.xhr),this._cleanup(),this},url:function(a,b){return this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",b),this},length:function(){return this.anchors.length}}),a.extend(a.ui.tabs,{version:"1.8.21"}),a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(a,b){var c=this,d=this.options,e=c._rotate||(c._rotate=function(b){clearTimeout(c.rotation),c.rotation=setTimeout(function(){var a=d.selected;c.select(++a'))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.21"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('
      ')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$(''+c+""),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('').addClass(this._triggerClass).html(g==""?f:$("").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;db&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$(''),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+112?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&pp)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?''+q+"":e?"":''+q+"",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?''+s+"":e?"":''+s+"",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'",x=d?'
      '+(c?w:"")+(this._isInRange(a,v)?'":"")+(c?"":w)+"
      ":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='
      '+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'
      '+"";var R=z?'":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="=5?' class="ui-datepicker-week-end"':"")+">"+''+C[T]+""}Q+=R+"";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z";var _=z?'":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Ym;_+='",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+""}n++,n>11&&(n=0,o++),Q+="
      '+this._get(a,"weekHeader")+"
      '+this._get(a,"calculateWeek")(Y)+""+(bb&&!G?" ":bc?''+Y.getDate()+"":''+Y.getDate()+"")+"
      "+(j?"
      "+(g[0]>0&&N==g[1]-1?'
      ':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='
      ',m="";if(f||!i)m+=''+g[b]+"";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='"}k||(l+=m+(f||!i||!j?" ":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+=''+c+"";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?" ":"")+m),l+="
      ",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&bd?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.21",window["DP_jQuery_"+dpuuid]=$})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.progressbar.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=a("
      ").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove(),a.Widget.prototype.destroy.apply(this,arguments)},value:function(a){return a===b?this._value():(this._setOption("value",a),this)},_setOption:function(b,c){b==="value"&&(this.options.value=c,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),a.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;return typeof a!="number"&&(a=0),Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var a=this.value(),b=this._percentage();this.oldValue!==a&&(this.oldValue=a,this._trigger("change")),this.valueDiv.toggle(a>this.min).toggleClass("ui-corner-right",a===this.options.max).width(b.toFixed(0)+"%"),this.element.attr("aria-valuenow",a)}}),a.extend(a.ui.progressbar,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.core.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -jQuery.effects||function(a,b){function c(b){var c;return b&&b.constructor==Array&&b.length==3?b:(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))?[parseInt(c[1],10),parseInt(c[2],10),parseInt(c[3],10)]:(c=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))?[parseFloat(c[1])*2.55,parseFloat(c[2])*2.55,parseFloat(c[3])*2.55]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))?[parseInt(c[1],16),parseInt(c[2],16),parseInt(c[3],16)]:(c=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))?[parseInt(c[1]+c[1],16),parseInt(c[2]+c[2],16),parseInt(c[3]+c[3],16)]:(c=/rgba\(0, 0, 0, 0\)/.exec(b))?e.transparent:e[a.trim(b).toLowerCase()]}function d(b,d){var e;do{e=a.curCSS(b,d);if(e!=""&&e!="transparent"||a.nodeName(b,"body"))break;d="backgroundColor"}while(b=b.parentNode);return c(e)}function h(){var a=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,b={},c,d;if(a&&a.length&&a[0]&&a[a[0]]){var e=a.length;while(e--)c=a[e],typeof a[c]=="string"&&(d=c.replace(/\-(\w)/g,function(a,b){return b.toUpperCase()}),b[d]=a[c])}else for(c in a)typeof a[c]=="string"&&(b[c]=a[c]);return b}function i(b){var c,d;for(c in b)d=b[c],(d==null||a.isFunction(d)||c in g||/scrollbar/.test(c)||!/color/i.test(c)&&isNaN(parseFloat(d)))&&delete b[c];return b}function j(a,b){var c={_:0},d;for(d in b)a[d]!=b[d]&&(c[d]=b[d]);return c}function k(b,c,d,e){typeof b=="object"&&(e=c,d=null,c=b,b=c.effect),a.isFunction(c)&&(e=c,d=null,c={});if(typeof c=="number"||a.fx.speeds[c])e=d,d=c,c={};return a.isFunction(d)&&(e=d,d=null),c=c||{},d=d||c.duration,d=a.fx.off?0:typeof d=="number"?d:d in a.fx.speeds?a.fx.speeds[d]:a.fx.speeds._default,e=e||c.complete,[b,c,d,e]}function l(b){return!b||typeof b=="number"||a.fx.speeds[b]?!0:typeof b=="string"&&!a.effects[b]?!0:!1}a.effects={},a.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","borderColor","color","outlineColor"],function(b,e){a.fx.step[e]=function(a){a.colorInit||(a.start=d(a.elem,e),a.end=c(a.end),a.colorInit=!0),a.elem.style[e]="rgb("+Math.max(Math.min(parseInt(a.pos*(a.end[0]-a.start[0])+a.start[0],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[1]-a.start[1])+a.start[1],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[2]-a.start[2])+a.start[2],10),255),0)+")"}});var e={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},f=["add","remove","toggle"],g={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.effects.animateClass=function(b,c,d,e){return a.isFunction(d)&&(e=d,d=null),this.queue(function(){var g=a(this),k=g.attr("style")||" ",l=i(h.call(this)),m,n=g.attr("class")||"";a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),m=i(h.call(this)),g.attr("class",n),g.animate(j(l,m),{queue:!1,duration:c,easing:d,complete:function(){a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),typeof g.attr("style")=="object"?(g.attr("style").cssText="",g.attr("style").cssText=k):g.attr("style",k),e&&e.apply(this,arguments),a.dequeue(this)}})})},a.fn.extend({_addClass:a.fn.addClass,addClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{add:b},c,d,e]):this._addClass(b)},_removeClass:a.fn.removeClass,removeClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{remove:b},c,d,e]):this._removeClass(b)},_toggleClass:a.fn.toggleClass,toggleClass:function(c,d,e,f,g){return typeof d=="boolean"||d===b?e?a.effects.animateClass.apply(this,[d?{add:c}:{remove:c},e,f,g]):this._toggleClass(c,d):a.effects.animateClass.apply(this,[{toggle:c},d,e,f])},switchClass:function(b,c,d,e,f){return a.effects.animateClass.apply(this,[{add:c,remove:b},d,e,f])}}),a.extend(a.effects,{version:"1.8.21",save:function(a,b){for(var c=0;c").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e=document.activeElement;try{e.id}catch(f){e=document.body}return b.wrap(d),(b[0]===e||a.contains(b[0],e))&&a(e).focus(),d=b.parent(),b.css("position")=="static"?(d.css({position:"relative"}),b.css({position:"relative"})):(a.extend(c,{position:b.css("position"),zIndex:b.css("z-index")}),a.each(["top","left","bottom","right"],function(a,d){c[d]=b.css(d),isNaN(parseInt(c[d],10))&&(c[d]="auto")}),b.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),d.css(c).show()},removeWrapper:function(b){var c,d=document.activeElement;return b.parent().is(".ui-effects-wrapper")?(c=b.parent().replaceWith(b),(b[0]===d||a.contains(b[0],d))&&a(d).focus(),c):b},setTransition:function(b,c,d,e){return e=e||{},a.each(c,function(a,c){var f=b.cssUnit(c);f[0]>0&&(e[c]=f[0]*d+f[1])}),e}}),a.fn.extend({effect:function(b,c,d,e){var f=k.apply(this,arguments),g={options:f[1],duration:f[2],callback:f[3]},h=g.options.mode,i=a.effects[b];return a.fx.off||!i?h?this[h](g.duration,g.callback):this.each(function(){g.callback&&g.callback.call(this)}):i.call(this,g)},_show:a.fn.show,show:function(a){if(l(a))return this._show.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="show",this.effect.apply(this,b)},_hide:a.fn.hide,hide:function(a){if(l(a))return this._hide.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="hide",this.effect.apply(this,b)},__toggle:a.fn.toggle,toggle:function(b){if(l(b)||typeof b=="boolean"||a.isFunction(b))return this.__toggle.apply(this,arguments);var c=k.apply(this,arguments);return c[1].mode="toggle",this.effect.apply(this,c)},cssUnit:function(b){var c=this.css(b),d=[];return a.each(["em","px","%","pt"],function(a,b){c.indexOf(b)>0&&(d=[parseFloat(c),b])}),d}}),a.easing.jswing=a.easing.swing,a.extend(a.easing,{def:"easeOutQuad",swing:function(b,c,d,e,f){return a.easing[a.easing.def](b,c,d,e,f)},easeInQuad:function(a,b,c,d,e){return d*(b/=e)*b+c},easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c},easeInOutQuad:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b+c:-d/2*(--b*(b-2)-1)+c},easeInCubic:function(a,b,c,d,e){return d*(b/=e)*b*b+c},easeOutCubic:function(a,b,c,d,e){return d*((b=b/e-1)*b*b+1)+c},easeInOutCubic:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b+c:d/2*((b-=2)*b*b+2)+c},easeInQuart:function(a,b,c,d,e){return d*(b/=e)*b*b*b+c},easeOutQuart:function(a,b,c,d,e){return-d*((b=b/e-1)*b*b*b-1)+c},easeInOutQuart:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b+c:-d/2*((b-=2)*b*b*b-2)+c},easeInQuint:function(a,b,c,d,e){return d*(b/=e)*b*b*b*b+c},easeOutQuint:function(a,b,c,d,e){return d*((b=b/e-1)*b*b*b*b+1)+c},easeInOutQuint:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b*b+c:d/2*((b-=2)*b*b*b*b+2)+c},easeInSine:function(a,b,c,d,e){return-d*Math.cos(b/e*(Math.PI/2))+d+c},easeOutSine:function(a,b,c,d,e){return d*Math.sin(b/e*(Math.PI/2))+c},easeInOutSine:function(a,b,c,d,e){return-d/2*(Math.cos(Math.PI*b/e)-1)+c},easeInExpo:function(a,b,c,d,e){return b==0?c:d*Math.pow(2,10*(b/e-1))+c},easeOutExpo:function(a,b,c,d,e){return b==e?c+d:d*(-Math.pow(2,-10*b/e)+1)+c},easeInOutExpo:function(a,b,c,d,e){return b==0?c:b==e?c+d:(b/=e/2)<1?d/2*Math.pow(2,10*(b-1))+c:d/2*(-Math.pow(2,-10*--b)+2)+c},easeInCirc:function(a,b,c,d,e){return-d*(Math.sqrt(1-(b/=e)*b)-1)+c},easeOutCirc:function(a,b,c,d,e){return d*Math.sqrt(1-(b=b/e-1)*b)+c},easeInOutCirc:function(a,b,c,d,e){return(b/=e/2)<1?-d/2*(Math.sqrt(1-b*b)-1)+c:d/2*(Math.sqrt(1-(b-=2)*b)+1)+c},easeInElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e)==1)return c+d;g||(g=e*.3);if(h").css({position:"absolute",visibility:"visible",left:-j*(g/d),top:-i*(h/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/d,height:h/c,left:f.left+j*(g/d)+(b.options.mode=="show"?(j-Math.floor(d/2))*(g/d):0),top:f.top+i*(h/c)+(b.options.mode=="show"?(i-Math.floor(c/2))*(h/c):0),opacity:b.options.mode=="show"?0:1}).animate({left:f.left+j*(g/d)+(b.options.mode=="show"?0:(j-Math.floor(d/2))*(g/d)),top:f.top+i*(h/c)+(b.options.mode=="show"?0:(i-Math.floor(c/2))*(h/c)),opacity:b.options.mode=="show"?1:0},b.duration||500);setTimeout(function(){b.options.mode=="show"?e.css({visibility:"visible"}):e.css({visibility:"visible"}).hide(),b.callback&&b.callback.apply(e[0]),e.dequeue(),a("div.ui-effects-explode").remove()},b.duration||500)})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.fade.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.effects.fade=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide");c.animate({opacity:d},{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.fold.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.effects.fold=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.size||15,g=!!b.options.horizFirst,h=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(c,d),c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),j=e=="show"!=g,k=j?["width","height"]:["height","width"],l=j?[i.width(),i.height()]:[i.height(),i.width()],m=/([0-9]+)%/.exec(f);m&&(f=parseInt(m[1],10)/100*l[e=="hide"?0:1]),e=="show"&&i.css(g?{height:0,width:f}:{height:f,width:0});var n={},p={};n[k[0]]=e=="show"?l[0]:f,p[k[1]]=e=="show"?l[1]:0,i.animate(n,h,b.options.easing).animate(p,h,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.highlight.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.effects.highlight=function(b){return this.queue(function(){var c=a(this),d=["backgroundImage","backgroundColor","opacity"],e=a.effects.setMode(c,b.options.mode||"show"),f={backgroundColor:c.css("backgroundColor")};e=="hide"&&(f.opacity=0),a.effects.save(c,d),c.show().css({backgroundImage:"none",backgroundColor:b.options.color||"#ffff99"}).animate(f,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),e=="show"&&!a.support.opacity&&this.style.removeAttribute("filter"),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.pulsate.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.effects.pulsate=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"show"),e=(b.options.times||5)*2-1,f=b.duration?b.duration/2:a.fx.speeds._default/2,g=c.is(":visible"),h=0;g||(c.css("opacity",0).show(),h=1),(d=="hide"&&g||d=="show"&&!g)&&e--;for(var i=0;i').appendTo(document.body).addClass(b.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(f,b.duration,b.options.easing,function(){h.remove(),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);; - -/* JQuery UJS 2.0.3 */ -(function(a,b){var c=function(){var b=a(document).data("events");return b&&b.click&&a.grep(b.click,function(a){return a.namespace==="rails"}).length};if(c()){a.error("jquery-ujs has already been loaded!")}var d;a.rails=d={linkClickSelector:"a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]",inputChangeSelector:"select[data-remote], input[data-remote], textarea[data-remote]",formSubmitSelector:"form",formInputClickSelector:"form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])",disableSelector:"input[data-disable-with], button[data-disable-with], textarea[data-disable-with]",enableSelector:"input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled",requiredInputSelector:"input[name][required]:not([disabled]),textarea[name][required]:not([disabled])",fileInputSelector:"input:file",linkDisableSelector:"a[data-disable-with]",CSRFProtection:function(b){var c=a('meta[name="csrf-token"]').attr("content");if(c)b.setRequestHeader("X-CSRF-Token",c)},fire:function(b,c,d){var e=a.Event(c);b.trigger(e,d);return e.result!==false},confirm:function(a){return confirm(a)},ajax:function(b){return a.ajax(b)},href:function(a){return a.attr("href")},handleRemote:function(c){var e,f,g,h,i,j,k,l;if(d.fire(c,"ajax:before")){h=c.data("cross-domain");i=h===b?null:h;j=c.data("with-credentials")||null;k=c.data("type")||a.ajaxSettings&&a.ajaxSettings.dataType;if(c.is("form")){e=c.attr("method");f=c.attr("action");g=c.serializeArray();var m=c.data("ujs:submit-button");if(m){g.push(m);c.data("ujs:submit-button",null)}}else if(c.is(d.inputChangeSelector)){e=c.data("method");f=c.data("url");g=c.serialize();if(c.data("params"))g=g+"&"+c.data("params")}else{e=c.data("method");f=d.href(c);g=c.data("params")||null}l={type:e||"GET",data:g,dataType:k,beforeSend:function(a,e){if(e.dataType===b){a.setRequestHeader("accept","*/*;q=0.5, "+e.accepts.script)}return d.fire(c,"ajax:beforeSend",[a,e])},success:function(a,b,d){c.trigger("ajax:success",[a,b,d])},complete:function(a,b){c.trigger("ajax:complete",[a,b])},error:function(a,b,d){c.trigger("ajax:error",[a,b,d])},xhrFields:{withCredentials:j},crossDomain:i};if(f){l.url=f}var n=d.ajax(l);c.trigger("ajax:send",n);return n}else{return false}},handleMethod:function(c){var e=d.href(c),f=c.data("method"),g=c.attr("target"),h=a("meta[name=csrf-token]").attr("content"),i=a("meta[name=csrf-param]").attr("content"),j=a('
      '),k='';if(i!==b&&h!==b){k+=''}if(g){j.attr("target",g)}j.hide().append(k).appendTo("body");j.submit()},disableFormElements:function(b){b.find(d.disableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";b.data("ujs:enable-with",b[c]());b[c](b.data("disable-with"));b.prop("disabled",true)})},enableFormElements:function(b){b.find(d.enableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";if(b.data("ujs:enable-with"))b[c](b.data("ujs:enable-with"));b.prop("disabled",false)})},allowAction:function(a){var b=a.data("confirm"),c=false,e;if(!b){return true}if(d.fire(a,"confirm")){c=d.confirm(b);e=d.fire(a,"confirm:complete",[c])}return c&&e},blankInputs:function(b,c,d){var e=a(),f,g,h=c||"input,textarea";b.find(h).each(function(){f=a(this);g=f.is(":checkbox,:radio")?f.is(":checked"):f.val();if(g==!!d){e=e.add(f)}});return e.length?e:false},nonBlankInputs:function(a,b){return d.blankInputs(a,b,true)},stopEverything:function(b){a(b.target).trigger("ujs:everythingStopped");b.stopImmediatePropagation();return false},callFormSubmitBindings:function(c,d){var e=c.data("events"),f=true;if(e!==b&&e["submit"]!==b){a.each(e["submit"],function(a,b){if(typeof b.handler==="function")return f=b.handler(d)})}return f},disableElement:function(a){a.data("ujs:enable-with",a.html());a.html(a.data("disable-with"));a.bind("click.railsDisable",function(a){return d.stopEverything(a)})},enableElement:function(a){if(a.data("ujs:enable-with")!==b){a.html(a.data("ujs:enable-with"));a.data("ujs:enable-with",false)}a.unbind("click.railsDisable")}};if(d.fire(a(document),"rails:attachBindings")){a.ajaxPrefilter(function(a,b,c){if(!a.crossDomain){d.CSRFProtection(c)}});a(document).delegate(d.linkDisableSelector,"ajax:complete",function(){d.enableElement(a(this))});a(document).delegate(d.linkClickSelector,"click.rails",function(c){var e=a(this),f=e.data("method"),g=e.data("params");if(!d.allowAction(e))return d.stopEverything(c);if(e.is(d.linkDisableSelector))d.disableElement(e);if(e.data("remote")!==b){if((c.metaKey||c.ctrlKey)&&(!f||f==="GET")&&!g){return true}if(d.handleRemote(e)===false){d.enableElement(e)}return false}else if(e.data("method")){d.handleMethod(e);return false}});a(document).delegate(d.inputChangeSelector,"change.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);d.handleRemote(c);return false});a(document).delegate(d.formSubmitSelector,"submit.rails",function(c){var e=a(this),f=e.data("remote")!==b,g=d.blankInputs(e,d.requiredInputSelector),h=d.nonBlankInputs(e,d.fileInputSelector);if(!d.allowAction(e))return d.stopEverything(c);if(g&&e.attr("novalidate")==b&&d.fire(e,"ajax:aborted:required",[g])){return d.stopEverything(c)}if(f){if(h){setTimeout(function(){d.disableFormElements(e)},13);return d.fire(e,"ajax:aborted:file",[h])}if(!a.support.submitBubbles&&a().jquery<"1.7"&&d.callFormSubmitBindings(e,c)===false)return d.stopEverything(c);d.handleRemote(e);return false}else{setTimeout(function(){d.disableFormElements(e)},13)}});a(document).delegate(d.formInputClickSelector,"click.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);var e=c.attr("name"),f=e?{name:e,value:c.val()}:null;c.closest("form").data("ujs:submit-button",f)});a(document).delegate(d.formSubmitSelector,"ajax:beforeSend.rails",function(b){if(this==b.target)d.disableFormElements(a(this))});a(document).delegate(d.formSubmitSelector,"ajax:complete.rails",function(b){if(this==b.target)d.enableFormElements(a(this))});a(function(){csrf_token=a("meta[name=csrf-token]").attr("content");csrf_param=a("meta[name=csrf-param]").attr("content");a('form input[name="'+csrf_param+'"]').val(csrf_token)})}})(jQuery) diff -r d98d22a98252 -r afce8026aaeb public/javascripts/jquery-1.8.3-ui-1.9.2-ujs-2.0.3.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/jquery-1.8.3-ui-1.9.2-ujs-2.0.3.js Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,11 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
      a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
      t
      ",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
      ",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
      ",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

      ",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/
      ","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
      ","
      "]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
      ").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); + +/*! jQuery UI - v1.9.2 - 2012-12-26 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js +* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */ +(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return"area"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!=="map"?!1:(o=e("img[usemap=#"+i+"]")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:"a"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().andSelf().filter(function(){return e.css(this,"visibility")==="hidden"}).length}var n=0,r=/^ui-id-\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:"1.9.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t=="number"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css("zIndex",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css("position");if(i==="absolute"||i==="relative"||i==="fixed"){s=parseInt(r.css("zIndex"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e(function(){var t=document.body,n=t.appendChild(n=document.createElement("div"));n.offsetHeight,e.extend(n.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),e.support.minHeight=n.offsetHeight===100,e.support.selectstart="onselectstart"in n,t.removeChild(n).style.display="none"}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,"padding"+this))||0,r&&(n-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(n-=parseFloat(e.css(t,"margin"+this))||0)}),n}var i=r==="Width"?["Left","Right"]:["Top","Bottom"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+r]=function(n){return n===t?o["inner"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+"px")})},e.fn["outer"+r]=function(t,n){return typeof t!="number"?o["outer"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+"px")})}}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(n){return arguments.length?t.call(this,e.camelCase(n)):t.call(this)}}(e.fn.removeData)),function(){var t=/msie ([\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];e.ui.ie=t.length?!0:!1,e.ui.ie6=parseFloat(t[1],10)===6}(),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)},isOverAxis:function(e,t,n){return e>t&&e",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetName,this),e.data(r,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n=="string"){i={},s=n.split("."),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u=9||!!t.button?this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted):this._mouseUp(t)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(e){return this.mouseDelayMet},_mouseStart:function(e){},_mouseDrag:function(e){},_mouseStop:function(e){},_mouseCapture:function(e){return!0}})})(jQuery);(function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\+\-]\d+%?/,f=/^\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e("
      "),o=s.children()[0];return e("body").append(s),r=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?"":t.element.css("overflow-x"),r=t.isWindow?"":t.element.css("overflow-y"),i=n==="scroll"||n==="auto"&&t.width0?"right":"center",vertical:u<0?"top":o>0?"bottom":"middle"};lr(i(o),i(u))?h.important="horizontal":h.important="vertical",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]==="left"?-t.elemWidth:t.my[0]==="right"?t.elemWidth:0,c=t.at[0]==="left"?t.targetWidth:t.at[0]==="right"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)a&&(v<0||v0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)10&&i<11,t.innerHTML="",n.removeChild(t)}(),e.uiBackCompat!==!1&&function(e){var n=e.fn.position;e.fn.position=function(r){if(!r||!r.offset)return n.call(this,r);var i=r.offset.split(" "),s=r.at.split(" ");return i.length===1&&(i[1]=i[0]),/^\d/.test(i[0])&&(i[0]="+"+i[0]),/^\d/.test(i[1])&&(i[1]="+"+i[1]),s.length===1&&(/left|center|right/.test(s[0])?s[1]="center":(s[1]=s[0],s[0]="center")),n.call(this,e.extend(r,{at:s[0]+i[0]+" "+s[1]+i[1],offset:t}))}}(jQuery)})(jQuery);(function(e,t){e.widget("ui.draggable",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(t){var n=this.options;return this.helper||n.disabled||e(t.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(t),this.handle?(e(n.iframeFix===!0?"iframe":n.iframeFix).each(function(){e('
      ').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(e(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(t){var n=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,n.cursorAt&&this._adjustOffsetFromHelper(n.cursorAt),n.containment&&this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_mouseDrag:function(t,n){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute");if(!n){var r=this._uiHash();if(this._trigger("drag",t,r)===!1)return this._mouseUp({}),!1;this.position=r.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var n=!1;e.ui.ddmanager&&!this.options.dropBehaviour&&(n=e.ui.ddmanager.drop(this,t)),this.dropped&&(n=this.dropped,this.dropped=!1);var r=this.element[0],i=!1;while(r&&(r=r.parentNode))r==document&&(i=!0);if(!i&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!n||this.options.revert=="valid"&&n||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,n)){var s=this;e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){s._trigger("stop",t)!==!1&&s._clear()})}else this._trigger("stop",t)!==!1&&this._clear();return!1},_mouseUp:function(t){return e("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){var n=!this.options.handle||!e(this.options.handle,this.element).length?!0:!1;return e(this.options.handle,this.element).find("*").andSelf().each(function(){this==t.target&&(n=!0)}),n},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t])):n.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return r.parents("body").length||r.appendTo(n.appendTo=="parent"?this.element[0].parentNode:n.appendTo),r[0]!=this.element[0]&&!/(fixed|absolute)/.test(r.css("position"))&&r.css("position","absolute"),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var e=this.element.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t=this.options;t.containment=="parent"&&(t.containment=this.helper[0].parentNode);if(t.containment=="document"||t.containment=="window")this.containment=[t.containment=="document"?0:e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t.containment=="document"?0:e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(t.containment=="document"?0:e(window).scrollLeft())+e(t.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(t.containment=="document"?0:e(window).scrollTop())+(e(t.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(t.containment)&&t.containment.constructor!=Array){var n=e(t.containment),r=n[0];if(!r)return;var i=n.offset(),s=e(r).css("overflow")!="hidden";this.containment=[(parseInt(e(r).css("borderLeftWidth"),10)||0)+(parseInt(e(r).css("paddingLeft"),10)||0),(parseInt(e(r).css("borderTopWidth"),10)||0)+(parseInt(e(r).css("paddingTop"),10)||0),(s?Math.max(r.scrollWidth,r.offsetWidth):r.offsetWidth)-(parseInt(e(r).css("borderLeftWidth"),10)||0)-(parseInt(e(r).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(s?Math.max(r.scrollHeight,r.offsetHeight):r.offsetHeight)-(parseInt(e(r).css("borderTopWidth"),10)||0)-(parseInt(e(r).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=n}else t.containment.constructor==Array&&(this.containment=t.containment)},_convertPositionTo:function(t,n){n||(n=this.position);var r=t=="absolute"?1:-1,i=this.options,s=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(s[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():o?0:s.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():o?0:s.scrollLeft())*r}},_generatePosition:function(t){var n=this.options,r=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,i=/(html|body)/i.test(r[0].tagName),s=t.pageX,o=t.pageY;if(this.originalPosition){var u;if(this.containment){if(this.relative_container){var a=this.relative_container.offset();u=[this.containment[0]+a.left,this.containment[1]+a.top,this.containment[2]+a.left,this.containment[3]+a.top]}else u=this.containment;t.pageX-this.offset.click.leftu[2]&&(s=u[2]+this.offset.click.left),t.pageY-this.offset.click.top>u[3]&&(o=u[3]+this.offset.click.top)}if(n.grid){var f=n.grid[1]?this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1]:this.originalPageY;o=u?f-this.offset.click.topu[3]?f-this.offset.click.topu[2]?l-this.offset.click.left=0;l--){var c=r.snapElements[l].left,h=c+r.snapElements[l].width,p=r.snapElements[l].top,d=p+r.snapElements[l].height;if(!(c-s=l&&o<=c||u>=l&&u<=c||oc)&&(i>=a&&i<=f||s>=a&&s<=f||if);default:return!1}},e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,n){var r=e.ui.ddmanager.droppables[t.options.scope]||[],i=n?n.type:null,s=(t.currentItem||t.element).find(":data(droppable)").andSelf();e:for(var o=0;o
      ').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=n.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var r=this.handles.split(",");this.handles={};for(var i=0;i');u.css({zIndex:n.zIndex}),"se"==s&&u.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(u)}}this._renderAxis=function(t){t=t||this.element;for(var n in this.handles){this.handles[n].constructor==String&&(this.handles[n]=e(this.handles[n],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var r=e(this.handles[n],this.element),i=0;i=/sw|ne|nw|se|n|s/.test(n)?r.outerHeight():r.outerWidth();var s=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join("");t.css(s,i),this._proportionallyResize()}if(!e(this.handles[n]).length)continue}},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!t.resizing){if(this.className)var e=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);t.axis=e&&e[1]?e[1]:"se"}}),n.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){if(n.disabled)return;e(this).removeClass("ui-resizable-autohide"),t._handles.show()}).mouseleave(function(){if(n.disabled)return;t.resizing||(e(this).addClass("ui-resizable-autohide"),t._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){t(this.element);var n=this.element;this.originalElement.css({position:n.css("position"),width:n.outerWidth(),height:n.outerHeight(),top:n.css("top"),left:n.css("left")}).insertAfter(n),n.remove()}return this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_mouseCapture:function(t){var n=!1;for(var r in this.handles)e(this.handles[r])[0]==t.target&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(t){var r=this.options,i=this.element.position(),s=this.element;this.resizing=!0,this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()},(s.is(".ui-draggable")||/absolute/.test(s.css("position")))&&s.css({position:"absolute",top:i.top,left:i.left}),this._renderProxy();var o=n(this.helper.css("left")),u=n(this.helper.css("top"));r.containment&&(o+=e(r.containment).scrollLeft()||0,u+=e(r.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:o,top:u},this.size=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalSize=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalPosition={left:o,top:u},this.sizeDiff={width:s.outerWidth()-s.width(),height:s.outerHeight()-s.height()},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio=typeof r.aspectRatio=="number"?r.aspectRatio:this.originalSize.width/this.originalSize.height||1;var a=e(".ui-resizable-"+this.axis).css("cursor");return e("body").css("cursor",a=="auto"?this.axis+"-resize":a),s.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(e){var t=this.helper,n=this.options,r={},i=this,s=this.originalMousePosition,o=this.axis,u=e.pageX-s.left||0,a=e.pageY-s.top||0,f=this._change[o];if(!f)return!1;var l=f.apply(this,[e,u,a]);this._updateVirtualBoundaries(e.shiftKey);if(this._aspectRatio||e.shiftKey)l=this._updateRatio(l,e);return l=this._respectSize(l,e),this._propagate("resize",e),t.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",e,this.ui()),!1},_mouseStop:function(t){this.resizing=!1;var n=this.options,r=this;if(this._helper){var i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),o=s&&e.ui.hasScroll(i[0],"left")?0:r.sizeDiff.height,u=s?0:r.sizeDiff.width,a={width:r.helper.width()-u,height:r.helper.height()-o},f=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,l=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;n.animate||this.element.css(e.extend(a,{top:l,left:f})),r.helper.height(r.size.height),r.helper.width(r.size.width),this._helper&&!n.animate&&this._proportionallyResize()}return e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(e){var t=this.options,n,i,s,o,u;u={minWidth:r(t.minWidth)?t.minWidth:0,maxWidth:r(t.maxWidth)?t.maxWidth:Infinity,minHeight:r(t.minHeight)?t.minHeight:0,maxHeight:r(t.maxHeight)?t.maxHeight:Infinity};if(this._aspectRatio||e)n=u.minHeight*this.aspectRatio,s=u.minWidth/this.aspectRatio,i=u.maxHeight*this.aspectRatio,o=u.maxWidth/this.aspectRatio,n>u.minWidth&&(u.minWidth=n),s>u.minHeight&&(u.minHeight=s),ie.width,l=r(e.height)&&i.minHeight&&i.minHeight>e.height;f&&(e.width=i.minWidth),l&&(e.height=i.minHeight),u&&(e.width=i.maxWidth),a&&(e.height=i.maxHeight);var c=this.originalPosition.left+this.originalSize.width,h=this.position.top+this.size.height,p=/sw|nw|w/.test(o),d=/nw|ne|n/.test(o);f&&p&&(e.left=c-i.minWidth),u&&p&&(e.left=c-i.maxWidth),l&&d&&(e.top=h-i.minHeight),a&&d&&(e.top=h-i.maxHeight);var v=!e.width&&!e.height;return v&&!e.left&&e.top?e.top=null:v&&!e.top&&e.left&&(e.left=null),e},_proportionallyResize:function(){var t=this.options;if(!this._proportionallyResizeElements.length)return;var n=this.helper||this.element;for(var r=0;r');var r=e.ui.ie6?1:0,i=e.ui.ie6?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+i,height:this.element.outerHeight()+i,position:"absolute",left:this.elementOffset.left-r+"px",top:this.elementOffset.top-r+"px",zIndex:++n.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(e,t,n){return{width:this.originalSize.width+t}},w:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{top:s.top+n,height:i.height-n}},s:function(e,t,n){return{height:this.originalSize.height+n}},se:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},sw:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,n,r]))},ne:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},nw:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,n,r]))}},_propagate:function(t,n){e.ui.plugin.call(this,t,[n,this.ui()]),t!="resize"&&this._trigger(t,n,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","alsoResize",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=function(t){e(t).each(function(){var t=e(this);t.data("resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};typeof i.alsoResize=="object"&&!i.alsoResize.parentNode?i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):e.each(i.alsoResize,function(e){s(e)}):s(i.alsoResize)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.originalSize,o=r.originalPosition,u={height:r.size.height-s.height||0,width:r.size.width-s.width||0,top:r.position.top-o.top||0,left:r.position.left-o.left||0},a=function(t,r){e(t).each(function(){var t=e(this),i=e(this).data("resizable-alsoresize"),s={},o=r&&r.length?r:t.parents(n.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(o,function(e,t){var n=(i[t]||0)+(u[t]||0);n&&n>=0&&(s[t]=n||null)}),t.css(s)})};typeof i.alsoResize=="object"&&!i.alsoResize.nodeType?e.each(i.alsoResize,function(e,t){a(e,t)}):a(i.alsoResize)},stop:function(t,n){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","animate",{stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r._proportionallyResizeElements,o=s.length&&/textarea/i.test(s[0].nodeName),u=o&&e.ui.hasScroll(s[0],"left")?0:r.sizeDiff.height,a=o?0:r.sizeDiff.width,f={width:r.size.width-a,height:r.size.height-u},l=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,c=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;r.element.animate(e.extend(f,c&&l?{top:c,left:l}:{}),{duration:i.animateDuration,easing:i.animateEasing,step:function(){var n={width:parseInt(r.element.css("width"),10),height:parseInt(r.element.css("height"),10),top:parseInt(r.element.css("top"),10),left:parseInt(r.element.css("left"),10)};s&&s.length&&e(s[0]).css({width:n.width,height:n.height}),r._updateCache(n),r._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(t,r){var i=e(this).data("resizable"),s=i.options,o=i.element,u=s.containment,a=u instanceof e?u.get(0):/parent/.test(u)?o.parent().get(0):u;if(!a)return;i.containerElement=e(a);if(/document/.test(u)||u==document)i.containerOffset={left:0,top:0},i.containerPosition={left:0,top:0},i.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight};else{var f=e(a),l=[];e(["Top","Right","Left","Bottom"]).each(function(e,t){l[e]=n(f.css("padding"+t))}),i.containerOffset=f.offset(),i.containerPosition=f.position(),i.containerSize={height:f.innerHeight()-l[3],width:f.innerWidth()-l[1]};var c=i.containerOffset,h=i.containerSize.height,p=i.containerSize.width,d=e.ui.hasScroll(a,"left")?a.scrollWidth:p,v=e.ui.hasScroll(a)?a.scrollHeight:h;i.parentData={element:a,left:c.left,top:c.top,width:d,height:v}}},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.containerSize,o=r.containerOffset,u=r.size,a=r.position,f=r._aspectRatio||t.shiftKey,l={top:0,left:0},c=r.containerElement;c[0]!=document&&/static/.test(c.css("position"))&&(l=o),a.left<(r._helper?o.left:0)&&(r.size.width=r.size.width+(r._helper?r.position.left-o.left:r.position.left-l.left),f&&(r.size.height=r.size.width/r.aspectRatio),r.position.left=i.helper?o.left:0),a.top<(r._helper?o.top:0)&&(r.size.height=r.size.height+(r._helper?r.position.top-o.top:r.position.top),f&&(r.size.width=r.size.height*r.aspectRatio),r.position.top=r._helper?o.top:0),r.offset.left=r.parentData.left+r.position.left,r.offset.top=r.parentData.top+r.position.top;var h=Math.abs((r._helper?r.offset.left-l.left:r.offset.left-l.left)+r.sizeDiff.width),p=Math.abs((r._helper?r.offset.top-l.top:r.offset.top-o.top)+r.sizeDiff.height),d=r.containerElement.get(0)==r.element.parent().get(0),v=/relative|absolute/.test(r.containerElement.css("position"));d&&v&&(h-=r.parentData.left),h+r.size.width>=r.parentData.width&&(r.size.width=r.parentData.width-h,f&&(r.size.height=r.size.width/r.aspectRatio)),p+r.size.height>=r.parentData.height&&(r.size.height=r.parentData.height-p,f&&(r.size.width=r.size.height*r.aspectRatio))},stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.position,o=r.containerOffset,u=r.containerPosition,a=r.containerElement,f=e(r.helper),l=f.offset(),c=f.outerWidth()-r.sizeDiff.width,h=f.outerHeight()-r.sizeDiff.height;r._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h}),r._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h})}}),e.ui.plugin.add("resizable","ghost",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size;r.ghost=r.originalElement.clone(),r.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof i.ghost=="string"?i.ghost:""),r.ghost.appendTo(r.helper)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.ghost.css({position:"relative",height:r.size.height,width:r.size.width})},stop:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.helper&&r.helper.get(0).removeChild(r.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size,o=r.originalSize,u=r.originalPosition,a=r.axis,f=i._aspectRatio||t.shiftKey;i.grid=typeof i.grid=="number"?[i.grid,i.grid]:i.grid;var l=Math.round((s.width-o.width)/(i.grid[0]||1))*(i.grid[0]||1),c=Math.round((s.height-o.height)/(i.grid[1]||1))*(i.grid[1]||1);/^(se|s|e)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c):/^(ne)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c):/^(sw)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.left=u.left-l):(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c,r.position.left=u.left-l)}});var n=function(e){return parseInt(e,10)||0},r=function(e){return!isNaN(parseInt(e,10))}})(jQuery);(function(e,t){e.widget("ui.selectable",e.ui.mouse,{version:"1.9.2",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var t=this;this.element.addClass("ui-selectable"),this.dragged=!1;var n;this.refresh=function(){n=e(t.options.filter,t.element[0]),n.addClass("ui-selectee"),n.each(function(){var t=e(this),n=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:n.left,top:n.top,right:n.left+t.outerWidth(),bottom:n.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=n.addClass("ui-selectee"),this._mouseInit(),this.helper=e("
      ")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var n=this;this.opos=[t.pageX,t.pageY];if(this.options.disabled)return;var r=this.options;this.selectees=e(r.filter,this.element[0]),this._trigger("start",t),e(r.appendTo).append(this.helper),this.helper.css({left:t.clientX,top:t.clientY,width:0,height:0}),r.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var r=e.data(this,"selectable-item");r.startselected=!0,!t.metaKey&&!t.ctrlKey&&(r.$element.removeClass("ui-selected"),r.selected=!1,r.$element.addClass("ui-unselecting"),r.unselecting=!0,n._trigger("unselecting",t,{unselecting:r.element}))}),e(t.target).parents().andSelf().each(function(){var r=e.data(this,"selectable-item");if(r){var i=!t.metaKey&&!t.ctrlKey||!r.$element.hasClass("ui-selected");return r.$element.removeClass(i?"ui-unselecting":"ui-selected").addClass(i?"ui-selecting":"ui-unselecting"),r.unselecting=!i,r.selecting=i,r.selected=i,i?n._trigger("selecting",t,{selecting:r.element}):n._trigger("unselecting",t,{unselecting:r.element}),!1}})},_mouseDrag:function(t){var n=this;this.dragged=!0;if(this.options.disabled)return;var r=this.options,i=this.opos[0],s=this.opos[1],o=t.pageX,u=t.pageY;if(i>o){var a=o;o=i,i=a}if(s>u){var a=u;u=s,s=a}return this.helper.css({left:i,top:s,width:o-i,height:u-s}),this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!a||a.element==n.element[0])return;var f=!1;r.tolerance=="touch"?f=!(a.left>o||a.rightu||a.bottomi&&a.rights&&a.bottom *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var e=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?e.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_setOption:function(t,n){t==="disabled"?(this.options[t]=n,this.widget().toggleClass("ui-sortable-disabled",!!n)):e.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(t,n){var r=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(t);var i=null,s=e(t.target).parents().each(function(){if(e.data(this,r.widgetName+"-item")==r)return i=e(this),!1});e.data(t.target,r.widgetName+"-item")==r&&(i=e(t.target));if(!i)return!1;if(this.options.handle&&!n){var o=!1;e(this.options.handle,i).find("*").andSelf().each(function(){this==t.target&&(o=!0)});if(!o)return!1}return this.currentItem=i,this._removeCurrentsFromItems(),!0},_mouseStart:function(t,n,r){var i=this.options;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),i.containment&&this._setContainment(),i.cursor&&(e("body").css("cursor")&&(this._storedCursor=e("body").css("cursor")),e("body").css("cursor",i.cursor)),i.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",i.opacity)),i.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",i.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!r)for(var s=this.containers.length-1;s>=0;s--)this.containers[s]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var n=this.options,r=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY=0;i--){var s=this.items[i],o=s.item[0],u=this._intersectsWithPointer(s);if(!u)continue;if(s.instance!==this.currentContainer)continue;if(o!=this.currentItem[0]&&this.placeholder[u==1?"next":"prev"]()[0]!=o&&!e.contains(this.placeholder[0],o)&&(this.options.type=="semi-dynamic"?!e.contains(this.element[0],o):!0)){this.direction=u==1?"down":"up";if(this.options.tolerance!="pointer"&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,n){if(!t)return;e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t);if(this.options.revert){var r=this,i=this.placeholder.offset();this.reverting=!0,e(this.helper).animate({left:i.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:i.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){r._clear(t)})}else this._clear(t,n);return!1},cancel:function(){if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},e(n).each(function(){var n=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[-=_](.+)/);n&&r.push((t.key||n[1]+"[]")+"="+(t.key&&t.expression?n[1]:n[2]))}),!r.length&&t.key&&r.push(t.key+"="),r.join("&")},toArray:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},n.each(function(){r.push(e(t.item||this).attr(t.attribute||"id")||"")}),r},_intersectsWith:function(e){var t=this.positionAbs.left,n=t+this.helperProportions.width,r=this.positionAbs.top,i=r+this.helperProportions.height,s=e.left,o=s+e.width,u=e.top,a=u+e.height,f=this.offset.click.top,l=this.offset.click.left,c=r+f>u&&r+fs&&t+le[this.floating?"width":"height"]?c:s0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return e!=0&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor==String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){var n=[],r=[],i=this._connectWith();if(i&&t)for(var s=i.length-1;s>=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&r.push([e.isFunction(a.options.items)?a.options.items.call(a.element):e(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a])}}r.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var s=r.length-1;s>=0;s--)r[s][0].each(function(){n.push(this)});return e(n)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var n=0;n=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&(r.push([e.isFunction(a.options.items)?a.options.items.call(a.element[0],t,{item:this.currentItem}):e(a.options.items,a.element),a]),this.containers.push(a))}}for(var s=r.length-1;s>=0;s--){var f=r[s][1],l=r[s][0];for(var u=0,c=l.length;u=0;n--){var r=this.items[n];if(r.instance!=this.currentContainer&&this.currentContainer&&r.item[0]!=this.currentItem[0])continue;var i=this.options.toleranceElement?e(this.options.toleranceElement,r.item):r.item;t||(r.width=i.outerWidth(),r.height=i.outerHeight());var s=i.offset();r.left=s.left,r.top=s.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var n=this.containers.length-1;n>=0;n--){var s=this.containers[n].element.offset();this.containers[n].containerCache.left=s.left,this.containers[n].containerCache.top=s.top,this.containers[n].containerCache.width=this.containers[n].element.outerWidth(),this.containers[n].containerCache.height=this.containers[n].element.outerHeight()}return this},_createPlaceholder:function(t){t=t||this;var n=t.options;if(!n.placeholder||n.placeholder.constructor==String){var r=n.placeholder;n.placeholder={element:function(){var n=e(document.createElement(t.currentItem[0].nodeName)).addClass(r||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return r||(n.style.visibility="hidden"),n},update:function(e,i){if(r&&!n.forcePlaceholderSize)return;i.height()||i.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),i.width()||i.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10))}}}t.placeholder=e(n.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),n.placeholder.update(t,t.placeholder)},_contactContainers:function(t){var n=null,r=null;for(var i=this.containers.length-1;i>=0;i--){if(e.contains(this.currentItem[0],this.containers[i].element[0]))continue;if(this._intersectsWith(this.containers[i].containerCache)){if(n&&e.contains(this.containers[i].element[0],n.element[0]))continue;n=this.containers[i],r=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",t,this._uiHash(this)),this.containers[i].containerCache.over=0)}if(!n)return;if(this.containers.length===1)this.containers[r]._trigger("over",t,this._uiHash(this)),this.containers[r].containerCache.over=1;else{var s=1e4,o=null,u=this.containers[r].floating?"left":"top",a=this.containers[r].floating?"width":"height",f=this.positionAbs[u]+this.offset.click[u];for(var l=this.items.length-1;l>=0;l--){if(!e.contains(this.containers[r].element[0],this.items[l].item[0]))continue;if(this.items[l].item[0]==this.currentItem[0])continue;var c=this.items[l].item.offset()[u],h=!1;Math.abs(c-f)>Math.abs(c+this.items[l][a]-f)&&(h=!0,c+=this.items[l][a]),Math.abs(c-f)this.containment[2]&&(s=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(o=this.containment[3]+this.offset.click.top));if(n.grid){var u=this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1];o=this.containment?u-this.offset.click.topthis.containment[3]?u-this.offset.click.topthis.containment[2]?a-this.offset.click.left=0;i--)n||r.push(function(e){return function(t){e._trigger("deactivate",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(r.push(function(e){return function(t){e._trigger("out",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);this._storedCursor&&e("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!n){this._trigger("beforeStop",t,this._uiHash());for(var i=0;i li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var t=this.accordionId="ui-accordion-"+(this.element.attr("id")||++n),r=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset"),this.headers=this.element.find(r.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all"),this._hoverable(this.headers),this._focusable(this.headers),this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").hide(),!r.collapsible&&(r.active===!1||r.active==null)&&(r.active=0),r.active<0&&(r.active+=this.headers.length),this.active=this._findActive(r.active).addClass("ui-accordion-header-active ui-state-active").toggleClass("ui-corner-all ui-corner-top"),this.active.next().addClass("ui-accordion-content-active").show(),this._createIcons(),this.refresh(),this.element.attr("role","tablist"),this.headers.attr("role","tab").each(function(n){var r=e(this),i=r.attr("id"),s=r.next(),o=s.attr("id");i||(i=t+"-header-"+n,r.attr("id",i)),o||(o=t+"-panel-"+n,s.attr("id",o)),r.attr("aria-controls",o),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false",tabIndex:-1}).next().attr({"aria-expanded":"false","aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true",tabIndex:0}).next().attr({"aria-expanded":"true","aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._on(this.headers,{keydown:"_keydown"}),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._setupEvents(r.event)},_getCreateEventData:function(){return{header:this.active,content:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this._destroyIcons(),e=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this.options.heightStyle!=="content"&&e.css("height","")},_setOption:function(e,t){if(e==="active"){this._activate(t);return}e==="event"&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),e==="collapsible"&&!t&&this.options.active===!1&&this._activate(0),e==="icons"&&(this._destroyIcons(),t&&this._createIcons()),e==="disabled"&&this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t)},_keydown:function(t){if(t.altKey||t.ctrlKey)return;var n=e.ui.keyCode,r=this.headers.length,i=this.headers.index(t.target),s=!1;switch(t.keyCode){case n.RIGHT:case n.DOWN:s=this.headers[(i+1)%r];break;case n.LEFT:case n.UP:s=this.headers[(i-1+r)%r];break;case n.SPACE:case n.ENTER:this._eventHandler(t);break;case n.HOME:s=this.headers[0];break;case n.END:s=this.headers[r-1]}s&&(e(t.target).attr("tabIndex",-1),e(s).attr("tabIndex",0),s.focus(),t.preventDefault())},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t,n,r=this.options.heightStyle,i=this.element.parent();r==="fill"?(e.support.minHeight||(n=i.css("overflow"),i.css("overflow","hidden")),t=i.height(),this.element.siblings(":visible").each(function(){var n=e(this),r=n.css("position");if(r==="absolute"||r==="fixed")return;t-=n.outerHeight(!0)}),n&&i.css("overflow",n),this.headers.each(function(){t-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,t-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):r==="auto"&&(t=0,this.headers.next().each(function(){t=Math.max(t,e(this).css("height","").height())}).height(t))},_activate:function(t){var n=this._findActive(t)[0];if(n===this.active[0])return;n=n||this.active[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return typeof t=="number"?this.headers.eq(t):e()},_setupEvents:function(t){var n={};if(!t)return;e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._on(this.headers,n)},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i[0]===r[0],o=s&&n.collapsible,u=o?e():i.next(),a=r.next(),f={oldHeader:r,oldPanel:a,newHeader:o?e():i,newPanel:u};t.preventDefault();if(s&&!n.collapsible||this._trigger("beforeActivate",t,f)===!1)return;n.active=o?!1:this.headers.index(i),this.active=s?e():i,this._toggle(f),r.removeClass("ui-accordion-header-active ui-state-active"),n.icons&&r.children(".ui-accordion-header-icon").removeClass(n.icons.activeHeader).addClass(n.icons.header),s||(i.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),n.icons&&i.children(".ui-accordion-header-icon").removeClass(n.icons.header).addClass(n.icons.activeHeader),i.next().addClass("ui-accordion-content-active"))},_toggle:function(t){var n=t.newPanel,r=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=n,this.prevHide=r,this.options.animate?this._animate(n,r,t):(r.hide(),n.show(),this._toggleComplete(t)),r.attr({"aria-expanded":"false","aria-hidden":"true"}),r.prev().attr("aria-selected","false"),n.length&&r.length?r.prev().attr("tabIndex",-1):n.length&&this.headers.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),n.attr({"aria-expanded":"true","aria-hidden":"false"}).prev().attr({"aria-selected":"true",tabIndex:0})},_animate:function(e,t,n){var s,o,u,a=this,f=0,l=e.length&&(!t.length||e.index()",options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,n,r;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is("input,textarea")?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(i){if(this.element.prop("readOnly")){t=!0,r=!0,n=!0;return}t=!1,r=!1,n=!1;var s=e.ui.keyCode;switch(i.keyCode){case s.PAGE_UP:t=!0,this._move("previousPage",i);break;case s.PAGE_DOWN:t=!0,this._move("nextPage",i);break;case s.UP:t=!0,this._keyEvent("previous",i);break;case s.DOWN:t=!0,this._keyEvent("next",i);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(t=!0,i.preventDefault(),this.menu.select(i));break;case s.TAB:this.menu.active&&this.menu.select(i);break;case s.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(i),i.preventDefault());break;default:n=!0,this._searchTimeout(i)}},keypress:function(r){if(t){t=!1,r.preventDefault();return}if(n)return;var i=e.ui.keyCode;switch(r.keyCode){case i.PAGE_UP:this._move("previousPage",r);break;case i.PAGE_DOWN:this._move("nextPage",r);break;case i.UP:this._keyEvent("previous",r);break;case i.DOWN:this._keyEvent("next",r)}},input:function(e){if(r){r=!1,e.preventDefault();return}this._searchTimeout(e)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching),this.close(e),this._change(e)}}),this._initSource(),this.menu=e("
      '+"";var z=N?'":"";for(var W=0;W<7;W++){var X=(W+T)%7;z+="=5?' class="ui-datepicker-week-end"':"")+">"+''+L[X]+""}U+=z+"";var V=this._getDaysInMonth(d,p);d==e.selectedYear&&p==e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,V));var J=(this._getFirstDayOfMonth(d,p)-T+7)%7,K=Math.ceil((J+V)/7),Q=f?this.maxRows>K?this.maxRows:K:K;this.maxRows=Q;var G=this._daylightSavingAdjust(new Date(d,p,1-J));for(var Y=0;Y";var Z=N?'":"";for(var W=0;W<7;W++){var et=M?M.apply(e.input?e.input[0]:null,[G]):[!0,""],tt=G.getMonth()!=p,nt=tt&&!D||!et[0]||c&&Gh;Z+='",G.setDate(G.getDate()+1),G=this._daylightSavingAdjust(G)}U+=Z+""}p++,p>11&&(p=0,d++),U+="
      '+this._get(e,"weekHeader")+"
      '+this._get(e,"calculateWeek")(G)+""+(tt&&!_?" ":nt?''+G.getDate()+"":''+G.getDate()+"")+"
      "+(f?"
      "+(o[0]>0&&I==o[1]-1?'
      ':""):""),F+=U}B+=F}return B+=x+($.ui.ie6&&!e.inline?'':""),e._keyEvent=!1,B},_generateMonthYearHeader:function(e,t,n,r,i,s,o,u){var a=this._get(e,"changeMonth"),f=this._get(e,"changeYear"),l=this._get(e,"showMonthAfterYear"),c='
      ',h="";if(s||!a)h+=''+o[t]+"";else{var p=r&&r.getFullYear()==n,d=i&&i.getFullYear()==n;h+='"}l||(c+=h+(s||!a||!f?" ":""));if(!e.yearshtml){e.yearshtml="";if(s||!f)c+=''+n+"";else{var m=this._get(e,"yearRange").split(":"),g=(new Date).getFullYear(),y=function(e){var t=e.match(/c[+-].*/)?n+parseInt(e.substring(1),10):e.match(/[+-].*/)?g+parseInt(e,10):parseInt(e,10);return isNaN(t)?g:t},b=y(m[0]),w=Math.max(b,y(m[1]||""));b=r?Math.max(b,r.getFullYear()):b,w=i?Math.min(w,i.getFullYear()):w,e.yearshtml+='",c+=e.yearshtml,e.yearshtml=null}}return c+=this._get(e,"yearSuffix"),l&&(c+=(s||!a||!f?" ":"")+h),c+="
      ",c},_adjustInstDate:function(e,t,n){var r=e.drawYear+(n=="Y"?t:0),i=e.drawMonth+(n=="M"?t:0),s=Math.min(e.selectedDay,this._getDaysInMonth(r,i))+(n=="D"?t:0),o=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(r,i,s)));e.selectedDay=o.getDate(),e.drawMonth=e.selectedMonth=o.getMonth(),e.drawYear=e.selectedYear=o.getFullYear(),(n=="M"||n=="Y")&&this._notifyChange(e)},_restrictMinMax:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max"),i=n&&tr?r:i,i},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return t==null?[1,1]:typeof t=="number"?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return(new Date(e,t,1)).getDay()},_canAdjustMonth:function(e,t,n,r){var i=this._getNumberOfMonths(e),s=this._daylightSavingAdjust(new Date(n,r+(t<0?t:i[0]*i[1]),1));return t<0&&s.setDate(this._getDaysInMonth(s.getFullYear(),s.getMonth())),this._isInRange(e,s)},_isInRange:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max");return(!n||t.getTime()>=n.getTime())&&(!r||t.getTime()<=r.getTime())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t=typeof t!="string"?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,n,r){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var i=t?typeof t=="object"?t:this._daylightSavingAdjust(new Date(r,n,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),i,this._getFormatConfig(e))}}),$.fn.datepicker=function(e){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find(document.body).append($.datepicker.dpDiv),$.datepicker.initialized=!0);var t=Array.prototype.slice.call(arguments,1);return typeof e!="string"||e!="isDisabled"&&e!="getDate"&&e!="widget"?e=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t)):this.each(function(){typeof e=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this].concat(t)):$.datepicker._attachDatepicker(this,e)}):$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.9.2",window["DP_jQuery_"+dpuuid]=$})(jQuery);(function(e,t){var n="ui-dialog ui-widget ui-widget-content ui-corner-all ",r={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},i={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0};e.widget("ui.dialog",{version:"1.9.2",options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var n=e(this).css(t).offset().top;n<0&&e(this).css("top",t.top-n)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.oldPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.options.title=this.options.title||this.originalTitle;var t=this,r=this.options,i=r.title||" ",s,o,u,a,f;s=(this.uiDialog=e("
      ")).addClass(n+r.dialogClass).css({display:"none",outline:0,zIndex:r.zIndex}).attr("tabIndex",-1).keydown(function(n){r.closeOnEscape&&!n.isDefaultPrevented()&&n.keyCode&&n.keyCode===e.ui.keyCode.ESCAPE&&(t.close(n),n.preventDefault())}).mousedown(function(e){t.moveToTop(!1,e)}).appendTo("body"),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(s),o=(this.uiDialogTitlebar=e("
      ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").bind("mousedown",function(){s.focus()}).prependTo(s),u=e("").addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").click(function(e){e.preventDefault(),t.close(e)}).appendTo(o),(this.uiDialogTitlebarCloseText=e("")).addClass("ui-icon ui-icon-closethick").text(r.closeText).appendTo(u),a=e("").uniqueId().addClass("ui-dialog-title").html(i).prependTo(o),f=(this.uiDialogButtonPane=e("
      ")).addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),(this.uiButtonSet=e("
      ")).addClass("ui-dialog-buttonset").appendTo(f),s.attr({role:"dialog","aria-labelledby":a.attr("id")}),o.find("*").add(o).disableSelection(),this._hoverable(u),this._focusable(u),r.draggable&&e.fn.draggable&&this._makeDraggable(),r.resizable&&e.fn.resizable&&this._makeResizable(),this._createButtons(r.buttons),this._isOpen=!1,e.fn.bgiframe&&s.bgiframe(),this._on(s,{keydown:function(t){if(!r.modal||t.keyCode!==e.ui.keyCode.TAB)return;var n=e(":tabbable",s),i=n.filter(":first"),o=n.filter(":last");if(t.target===o[0]&&!t.shiftKey)return i.focus(1),!1;if(t.target===i[0]&&t.shiftKey)return o.focus(1),!1}})},_init:function(){this.options.autoOpen&&this.open()},_destroy:function(){var e,t=this.oldPosition;this.overlay&&this.overlay.destroy(),this.uiDialog.hide(),this.element.removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),this.uiDialog.remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},close:function(t){var n=this,r,i;if(!this._isOpen)return;if(!1===this._trigger("beforeClose",t))return;return this._isOpen=!1,this.overlay&&this.overlay.destroy(),this.options.hide?this._hide(this.uiDialog,this.options.hide,function(){n._trigger("close",t)}):(this.uiDialog.hide(),this._trigger("close",t)),e.ui.dialog.overlay.resize(),this.options.modal&&(r=0,e(".ui-dialog").each(function(){this!==n.uiDialog[0]&&(i=e(this).css("z-index"),isNaN(i)||(r=Math.max(r,i)))}),e.ui.dialog.maxZ=r),this},isOpen:function(){return this._isOpen},moveToTop:function(t,n){var r=this.options,i;return r.modal&&!t||!r.stack&&!r.modal?this._trigger("focus",n):(r.zIndex>e.ui.dialog.maxZ&&(e.ui.dialog.maxZ=r.zIndex),this.overlay&&(e.ui.dialog.maxZ+=1,e.ui.dialog.overlay.maxZ=e.ui.dialog.maxZ,this.overlay.$el.css("z-index",e.ui.dialog.overlay.maxZ)),i={scrollTop:this.element.scrollTop(),scrollLeft:this.element.scrollLeft()},e.ui.dialog.maxZ+=1,this.uiDialog.css("z-index",e.ui.dialog.maxZ),this.element.attr(i),this._trigger("focus",n),this)},open:function(){if(this._isOpen)return;var t,n=this.options,r=this.uiDialog;return this._size(),this._position(n.position),r.show(n.show),this.overlay=n.modal?new e.ui.dialog.overlay(this):null,this.moveToTop(!0),t=this.element.find(":tabbable"),t.length||(t=this.uiDialogButtonPane.find(":tabbable"),t.length||(t=r)),t.eq(0).focus(),this._isOpen=!0,this._trigger("open"),this},_createButtons:function(t){var n=this,r=!1;this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),typeof t=="object"&&t!==null&&e.each(t,function(){return!(r=!0)}),r?(e.each(t,function(t,r){var i,s;r=e.isFunction(r)?{click:r,text:t}:r,r=e.extend({type:"button"},r),s=r.click,r.click=function(){s.apply(n.element[0],arguments)},i=e("",r).appendTo(n.uiButtonSet),e.fn.button&&i.button()}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog)):this.uiDialog.removeClass("ui-dialog-buttons")},_makeDraggable:function(){function r(e){return{position:e.position,offset:e.offset}}var t=this,n=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(n,i){e(this).addClass("ui-dialog-dragging"),t._trigger("dragStart",n,r(i))},drag:function(e,n){t._trigger("drag",e,r(n))},stop:function(i,s){n.position=[s.position.left-t.document.scrollLeft(),s.position.top-t.document.scrollTop()],e(this).removeClass("ui-dialog-dragging"),t._trigger("dragStop",i,r(s)),e.ui.dialog.overlay.resize()}})},_makeResizable:function(n){function u(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}n=n===t?this.options.resizable:n;var r=this,i=this.options,s=this.uiDialog.css("position"),o=typeof n=="string"?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:i.maxWidth,maxHeight:i.maxHeight,minWidth:i.minWidth,minHeight:this._minHeight(),handles:o,start:function(t,n){e(this).addClass("ui-dialog-resizing"),r._trigger("resizeStart",t,u(n))},resize:function(e,t){r._trigger("resize",e,u(t))},stop:function(t,n){e(this).removeClass("ui-dialog-resizing"),i.height=e(this).height(),i.width=e(this).width(),r._trigger("resizeStop",t,u(n)),e.ui.dialog.overlay.resize()}}).css("position",s).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var e=this.options;return e.height==="auto"?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(t){var n=[],r=[0,0],i;if(t){if(typeof t=="string"||typeof t=="object"&&"0"in t)n=t.split?t.split(" "):[t[0],t[1]],n.length===1&&(n[1]=n[0]),e.each(["left","top"],function(e,t){+n[e]===n[e]&&(r[e]=n[e],n[e]=t)}),t={my:n[0]+(r[0]<0?r[0]:"+"+r[0])+" "+n[1]+(r[1]<0?r[1]:"+"+r[1]),at:n.join(" ")};t=e.extend({},e.ui.dialog.prototype.options.position,t)}else t=e.ui.dialog.prototype.options.position;i=this.uiDialog.is(":visible"),i||this.uiDialog.show(),this.uiDialog.position(t),i||this.uiDialog.hide()},_setOptions:function(t){var n=this,s={},o=!1;e.each(t,function(e,t){n._setOption(e,t),e in r&&(o=!0),e in i&&(s[e]=t)}),o&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",s)},_setOption:function(t,r){var i,s,o=this.uiDialog;switch(t){case"buttons":this._createButtons(r);break;case"closeText":this.uiDialogTitlebarCloseText.text(""+r);break;case"dialogClass":o.removeClass(this.options.dialogClass).addClass(n+r);break;case"disabled":r?o.addClass("ui-dialog-disabled"):o.removeClass("ui-dialog-disabled");break;case"draggable":i=o.is(":data(draggable)"),i&&!r&&o.draggable("destroy"),!i&&r&&this._makeDraggable();break;case"position":this._position(r);break;case"resizable":s=o.is(":data(resizable)"),s&&!r&&o.resizable("destroy"),s&&typeof r=="string"&&o.resizable("option","handles",r),!s&&r!==!1&&this._makeResizable(r);break;case"title":e(".ui-dialog-title",this.uiDialogTitlebar).html(""+(r||" "))}this._super(t,r)},_size:function(){var t,n,r,i=this.options,s=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),i.minWidth>i.width&&(i.width=i.minWidth),t=this.uiDialog.css({height:"auto",width:i.width}).outerHeight(),n=Math.max(0,i.minHeight-t),i.height==="auto"?e.support.minHeight?this.element.css({minHeight:n,height:"auto"}):(this.uiDialog.show(),r=this.element.css("height","auto").height(),s||this.uiDialog.hide(),this.element.height(Math.max(r,n))):this.element.height(Math.max(i.height-t,0)),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),e.extend(e.ui.dialog,{uuid:0,maxZ:0,getTitleId:function(e){var t=e.attr("id");return t||(this.uuid+=1,t=this.uuid),"ui-dialog-title-"+t},overlay:function(t){this.$el=e.ui.dialog.overlay.create(t)}}),e.extend(e.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:e.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(e){return e+".dialog-overlay"}).join(" "),create:function(t){this.instances.length===0&&(setTimeout(function(){e.ui.dialog.overlay.instances.length&&e(document).bind(e.ui.dialog.overlay.events,function(t){if(e(t.target).zIndex()").addClass("ui-widget-overlay");return e(document).bind("keydown.dialog-overlay",function(r){var i=e.ui.dialog.overlay.instances;i.length!==0&&i[i.length-1]===n&&t.options.closeOnEscape&&!r.isDefaultPrevented()&&r.keyCode&&r.keyCode===e.ui.keyCode.ESCAPE&&(t.close(r),r.preventDefault())}),n.appendTo(document.body).css({width:this.width(),height:this.height()}),e.fn.bgiframe&&n.bgiframe(),this.instances.push(n),n},destroy:function(t){var n=e.inArray(t,this.instances),r=0;n!==-1&&this.oldInstances.push(this.instances.splice(n,1)[0]),this.instances.length===0&&e([document,window]).unbind(".dialog-overlay"),t.height(0).width(0).remove(),e.each(this.instances,function(){r=Math.max(r,this.css("z-index"))}),this.maxZ=r},height:function(){var t,n;return e.ui.ie?(t=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),n=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),t",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,e.proxy(function(e){this.options.disabled&&e.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(e){e.preventDefault()},"click .ui-state-disabled > a":function(e){e.preventDefault()},"click .ui-menu-item:has(a)":function(t){var r=e(t.target).closest(".ui-menu-item");!n&&r.not(".ui-state-disabled").length&&(n=!0,this.select(t),r.has(".ui-menu").length?this.expand(t):this.element.is(":focus")||(this.element.trigger("focus",[!0]),this.active&&this.active.parents(".ui-menu").length===1&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(t){var n=e(t.currentTarget);n.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(t,n)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,t){var n=this.active||this.element.children(".ui-menu-item").eq(0);t||this.focus(e,n)},blur:function(t){this._delay(function(){e.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){e(t.target).closest(".ui-menu").length||this.collapseAll(t),n=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").andSelf().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var t=e(this);t.data("ui-menu-submenu-carat")&&t.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(t){function a(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var n,r,i,s,o,u=!0;switch(t.keyCode){case e.ui.keyCode.PAGE_UP:this.previousPage(t);break;case e.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case e.ui.keyCode.HOME:this._move("first","first",t);break;case e.ui.keyCode.END:this._move("last","last",t);break;case e.ui.keyCode.UP:this.previous(t);break;case e.ui.keyCode.DOWN:this.next(t);break;case e.ui.keyCode.LEFT:this.collapse(t);break;case e.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case e.ui.keyCode.ENTER:case e.ui.keyCode.SPACE:this._activate(t);break;case e.ui.keyCode.ESCAPE:this.collapse(t);break;default:u=!1,r=this.previousFilter||"",i=String.fromCharCode(t.keyCode),s=!1,clearTimeout(this.filterTimer),i===r?s=!0:i=r+i,o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())}),n=s&&n.index(this.active.next())!==-1?this.active.nextAll(".ui-menu-item"):n,n.length||(i=String.fromCharCode(t.keyCode),o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())})),n.length?(this.focus(t,n),n.length>1?(this.previousFilter=i,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}u&&t.preventDefault()},_activate:function(e){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(e):this.select(e))},refresh:function(){var t,n=this.options.icons.submenu,r=this.element.find(this.options.menus);r.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=e(this),r=t.prev("a"),i=e("").addClass("ui-menu-icon ui-icon "+n).data("ui-menu-submenu-carat",!0);r.attr("aria-haspopup","true").prepend(i),t.attr("aria-labelledby",r.attr("id"))}),t=r.add(this.element),t.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),t.children(":not(.ui-menu-item)").each(function(){var t=e(this);/[^\-â€â€Ã¢â‚¬â€œ\s]/.test(t.text())||t.addClass("ui-widget-content ui-menu-divider")}),t.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!e.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},focus:function(e,t){var n,r;this.blur(e,e&&e.type==="focus"),this._scrollIntoView(t),this.active=t.first(),r=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",r.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),e&&e.type==="keydown"?this._close():this.timer=this._delay(function(){this._close()},this.delay),n=t.children(".ui-menu"),n.length&&/^mouse/.test(e.type)&&this._startOpening(n),this.activeMenu=t.parent(),this._trigger("focus",e,{item:t})},_scrollIntoView:function(t){var n,r,i,s,o,u;this._hasScroll()&&(n=parseFloat(e.css(this.activeMenu[0],"borderTopWidth"))||0,r=parseFloat(e.css(this.activeMenu[0],"paddingTop"))||0,i=t.offset().top-this.activeMenu.offset().top-n-r,s=this.activeMenu.scrollTop(),o=this.activeMenu.height(),u=t.height(),i<0?this.activeMenu.scrollTop(s+i):i+u>o&&this.activeMenu.scrollTop(s+i-o+u))},blur:function(e,t){t||clearTimeout(this.timer);if(!this.active)return;this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",e,{item:this.active})},_startOpening:function(e){clearTimeout(this.timer);if(e.attr("aria-hidden")!=="true")return;this.timer=this._delay(function(){this._close(),this._open(e)},this.delay)},_open:function(t){var n=e.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(t.parents(".ui-menu")).hide().attr("aria-hidden","true"),t.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(n)},collapseAll:function(t,n){clearTimeout(this.timer),this.timer=this._delay(function(){var r=n?this.element:e(t&&t.target).closest(this.element.find(".ui-menu"));r.length||(r=this.element),this._close(r),this.blur(t),this.activeMenu=r},this.delay)},_close:function(e){e||(e=this.active?this.active.parent():this.element),e.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(e){var t=this.active&&this.active.parent().closest(".ui-menu-item",this.element);t&&t.length&&(this._close(),this.focus(e,t))},expand:function(e){var t=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();t&&t.length&&(this._open(t.parent()),this._delay(function(){this.focus(e,t)}))},next:function(e){this._move("next","first",e)},previous:function(e){this._move("prev","last",e)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(e,t,n){var r;this.active&&(e==="first"||e==="last"?r=this.active[e==="first"?"prevAll":"nextAll"](".ui-menu-item").eq(-1):r=this.active[e+"All"](".ui-menu-item").eq(0));if(!r||!r.length||!this.active)r=this.activeMenu.children(".ui-menu-item")[t]();this.focus(n,r)},nextPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isLastItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r-i<0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())},previousPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isFirstItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r+i>0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item").first())},_hasScroll:function(){return this.element.outerHeight()
      ").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(e){return e===t?this._value():(this._setOption("value",e),this)},_setOption:function(e,t){e==="value"&&(this.options.value=t,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),this._super(e,t)},_value:function(){var e=this.options.value;return typeof e!="number"&&(e=0),Math.min(this.options.max,Math.max(this.min,e))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var e=this.value(),t=this._percentage();this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),this.valueDiv.toggle(e>this.min).toggleClass("ui-corner-right",e===this.options.max).width(t.toFixed(0)+"%"),this.element.attr("aria-valuenow",e)}})})(jQuery);(function(e,t){var n=5;e.widget("ui.slider",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var t,r,i=this.options,s=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),o="",u=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"+(i.disabled?" ui-slider-disabled ui-disabled":"")),this.range=e([]),i.range&&(i.range===!0&&(i.values||(i.values=[this._valueMin(),this._valueMin()]),i.values.length&&i.values.length!==2&&(i.values=[i.values[0],i.values[0]])),this.range=e("
      ").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(i.range==="min"||i.range==="max"?" ui-slider-range-"+i.range:""))),r=i.values&&i.values.length||1;for(t=s.length;tn&&(i=n,s=e(this),o=t)}),c.range===!0&&this.values(1)===c.min&&(o+=1,s=e(this.handles[o])),u=this._start(t,o),u===!1?!1:(this._mouseSliding=!0,this._handleIndex=o,s.addClass("ui-state-active").focus(),a=s.offset(),f=!e(t.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=f?{left:0,top:0}:{left:t.pageX-a.left-s.width()/2,top:t.pageY-a.top-s.height()/2-(parseInt(s.css("borderTopWidth"),10)||0)-(parseInt(s.css("borderBottomWidth"),10)||0)+(parseInt(s.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(t,o,r),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(e){var t={x:e.pageX,y:e.pageY},n=this._normValueFromMouse(t);return this._slide(e,this._handleIndex,n),!1},_mouseStop:function(e){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(e,this._handleIndex),this._change(e,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(e){var t,n,r,i,s;return this.orientation==="horizontal"?(t=this.elementSize.width,n=e.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(t=this.elementSize.height,n=e.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),r=n/t,r>1&&(r=1),r<0&&(r=0),this.orientation==="vertical"&&(r=1-r),i=this._valueMax()-this._valueMin(),s=this._valueMin()+r*i,this._trimAlignValue(s)},_start:function(e,t){var n={handle:this.handles[t],value:this.value()};return this.options.values&&this.options.values.length&&(n.value=this.values(t),n.values=this.values()),this._trigger("start",e,n)},_slide:function(e,t,n){var r,i,s;this.options.values&&this.options.values.length?(r=this.values(t?0:1),this.options.values.length===2&&this.options.range===!0&&(t===0&&n>r||t===1&&n1){this.options.values[t]=this._trimAlignValue(n),this._refreshValue(),this._change(null,t);return}if(!arguments.length)return this._values();if(!e.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(t):this.value();r=this.options.values,i=arguments[0];for(s=0;s=this._valueMax())return this._valueMax();var t=this.options.step>0?this.options.step:1,n=(e-this._valueMin())%t,r=e-n;return Math.abs(n)*2>=t&&(r+=n>0?t:-t),parseFloat(r.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var t,n,r,i,s,o=this.options.range,u=this.options,a=this,f=this._animateOff?!1:u.animate,l={};this.options.values&&this.options.values.length?this.handles.each(function(r){n=(a.values(r)-a._valueMin())/(a._valueMax()-a._valueMin())*100,l[a.orientation==="horizontal"?"left":"bottom"]=n+"%",e(this).stop(1,1)[f?"animate":"css"](l,u.animate),a.options.range===!0&&(a.orientation==="horizontal"?(r===0&&a.range.stop(1,1)[f?"animate":"css"]({left:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({width:n-t+"%"},{queue:!1,duration:u.animate})):(r===0&&a.range.stop(1,1)[f?"animate":"css"]({bottom:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({height:n-t+"%"},{queue:!1,duration:u.animate}))),t=n}):(r=this.value(),i=this._valueMin(),s=this._valueMax(),n=s!==i?(r-i)/(s-i)*100:0,l[this.orientation==="horizontal"?"left":"bottom"]=n+"%",this.handle.stop(1,1)[f?"animate":"css"](l,u.animate),o==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[f?"animate":"css"]({width:n+"%"},u.animate),o==="max"&&this.orientation==="horizontal"&&this.range[f?"animate":"css"]({width:100-n+"%"},{queue:!1,duration:u.animate}),o==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[f?"animate":"css"]({height:n+"%"},u.animate),o==="max"&&this.orientation==="vertical"&&this.range[f?"animate":"css"]({height:100-n+"%"},{queue:!1,duration:u.animate}))}})})(jQuery);(function(e){function t(e){return function(){var t=this.element.val();e.apply(this,arguments),this._refresh(),t!==this.element.val()&&this._trigger("change")}}e.widget("ui.spinner",{version:"1.9.2",defaultElement:"",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},n=this.element;return e.each(["min","max","step"],function(e,r){var i=n.attr(r);i!==undefined&&i.length&&(t[r]=i)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e)},mousewheel:function(e,t){if(!t)return;if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()},"mousedown .ui-spinner-button":function(t){function r(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=n,this._delay(function(){this.previous=n}))}var n;n=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),r.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,r.call(this)});if(this._start(t)===!1)return;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){if(!e(t.currentTarget).hasClass("ui-state-active"))return;if(this._start(t)===!1)return!1;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(e.height()*.5)&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()},_keydown:function(t){var n=this.options,r=e.ui.keyCode;switch(t.keyCode){case r.UP:return this._repeat(null,1,t),!0;case r.DOWN:return this._repeat(null,-1,t),!0;case r.PAGE_UP:return this._repeat(null,n.page,t),!0;case r.PAGE_DOWN:return this._repeat(null,-n.page,t),!0}return!1},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""+""+""+""+""},_start:function(e){return!this.spinning&&this._trigger("start",e)===!1?!1:(this.counter||(this.counter=1),this.spinning=!0,!0)},_repeat:function(e,t,n){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,n)},e),this._spin(t*this.options.step,n)},_spin:function(e,t){var n=this.value()||0;this.counter||(this.counter=1),n=this._adjustValue(n+e*this._increment(this.counter));if(!this.spinning||this._trigger("spin",t,{value:n})!==!1)this._value(n),this.counter++},_increment:function(t){var n=this.options.incremental;return n?e.isFunction(n)?n(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return this.options.min!==null&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=e.toString(),n=t.indexOf(".");return n===-1?0:t.length-n-1},_adjustValue:function(e){var t,n,r=this.options;return t=r.min!==null?r.min:0,n=e-t,n=Math.round(n/r.step)*r.step,e=t+n,e=parseFloat(e.toFixed(this._precision())),r.max!==null&&e>r.max?r.max:r.min!==null&&e1&&e.href.replace(r,"")===location.href.replace(r,"").replace(/\s/g,"%20")}var n=0,r=/#.*$/;e.widget("ui.tabs",{version:"1.9.2",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var t=this,n=this.options,r=n.active,i=location.hash.substring(1);this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",n.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this._processTabs();if(r===null){i&&this.tabs.each(function(t,n){if(e(n).attr("aria-controls")===i)return r=t,!1}),r===null&&(r=this.tabs.index(this.tabs.filter(".ui-tabs-active")));if(r===null||r===-1)r=this.tabs.length?0:!1}r!==!1&&(r=this.tabs.index(this.tabs.eq(r)),r===-1&&(r=n.collapsible?!1:0)),n.active=r,!n.collapsible&&n.active===!1&&this.anchors.length&&(n.active=0),e.isArray(n.disabled)&&(n.disabled=e.unique(n.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.options.active!==!1&&this.anchors.length?this.active=this._findActive(this.options.active):this.active=e(),this._refresh(),this.active.length&&this.load(n.active)},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var n=e(this.document[0].activeElement).closest("li"),r=this.tabs.index(n),i=!0;if(this._handlePageNav(t))return;switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:r++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:i=!1,r--;break;case e.ui.keyCode.END:r=this.anchors.length-1;break;case e.ui.keyCode.HOME:r=0;break;case e.ui.keyCode.SPACE:t.preventDefault(),clearTimeout(this.activating),this._activate(r);return;case e.ui.keyCode.ENTER:t.preventDefault(),clearTimeout(this.activating),this._activate(r===this.options.active?!1:r);return;default:return}t.preventDefault(),clearTimeout(this.activating),r=this._focusNextTab(r,i),t.ctrlKey||(n.attr("aria-selected","false"),this.tabs.eq(r).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",r)},this.delay))},_panelKeydown:function(t){if(this._handlePageNav(t))return;t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP)return this._activate(this._focusNextTab(this.options.active-1,!1)),!0;if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN)return this._activate(this._focusNextTab(this.options.active+1,!0)),!0},_findNextTab:function(t,n){function i(){return t>r&&(t=0),t<0&&(t=r),t}var r=this.tabs.length-1;while(e.inArray(i(),this.options.disabled)!==-1)t=n?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){if(e==="active"){this._activate(t);return}if(e==="disabled"){this._setupDisabled(t);return}this._super(e,t),e==="collapsible"&&(this.element.toggleClass("ui-tabs-collapsible",t),!t&&this.options.active===!1&&this._activate(0)),e==="event"&&this._setupEvents(t),e==="heightStyle"&&this._setupHeightStyle(t)},_tabId:function(e){return e.attr("aria-controls")||"ui-tabs-"+i()},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,n=this.tablist.children(":has(a[href])");t.disabled=e.map(n.filter(".ui-state-disabled"),function(e){return n.index(e)}),this._processTabs(),t.active===!1||!this.anchors.length?(t.active=!1,this.active=e()):this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist"),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(n,r){var i,o,u,a=e(r).uniqueId().attr("id"),f=e(r).closest("li"),l=f.attr("aria-controls");s(r)?(i=r.hash,o=t.element.find(t._sanitizeSelector(i))):(u=t._tabId(f),i="#"+u,o=t.element.find(i),o.length||(o=t._createPanel(u),o.insertAfter(t.panels[n-1]||t.tablist)),o.attr("aria-live","polite")),o.length&&(t.panels=t.panels.add(o)),l&&f.data("ui-tabs-aria-controls",l),f.attr({"aria-controls":i.substring(1),"aria-labelledby":a}),o.attr("aria-labelledby",a)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("
      ").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var n=0,r;r=this.tabs[n];n++)t===!0||e.inArray(n,t)!==-1?e(r).addClass("ui-state-disabled").attr("aria-disabled","true"):e(r).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var n={click:function(e){e.preventDefault()}};t&&e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(this.anchors,n),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var n,r,i=this.element.parent();t==="fill"?(e.support.minHeight||(r=i.css("overflow"),i.css("overflow","hidden")),n=i.height(),this.element.siblings(":visible").each(function(){var t=e(this),r=t.css("position");if(r==="absolute"||r==="fixed")return;n-=t.outerHeight(!0)}),r&&i.css("overflow",r),this.element.children().not(this.panels).each(function(){n-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,n-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):t==="auto"&&(n=0,this.panels.each(function(){n=Math.max(n,e(this).height("").height())}).height(n))},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i.closest("li"),o=s[0]===r[0],u=o&&n.collapsible,a=u?e():this._getPanelForTab(s),f=r.length?this._getPanelForTab(r):e(),l={oldTab:r,oldPanel:f,newTab:u?e():s,newPanel:a};t.preventDefault();if(s.hasClass("ui-state-disabled")||s.hasClass("ui-tabs-loading")||this.running||o&&!n.collapsible||this._trigger("beforeActivate",t,l)===!1)return;n.active=u?!1:this.tabs.index(s),this.active=o?e():s,this.xhr&&this.xhr.abort(),!f.length&&!a.length&&e.error("jQuery UI Tabs: Mismatching fragment identifier."),a.length&&this.load(this.tabs.index(s),t),this._toggle(t,l)},_toggle:function(t,n){function o(){r.running=!1,r._trigger("activate",t,n)}function u(){n.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),i.length&&r.options.show?r._show(i,r.options.show,o):(i.show(),o())}var r=this,i=n.newPanel,s=n.oldPanel;this.running=!0,s.length&&this.options.hide?this._hide(s,this.options.hide,function(){n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),s.hide(),u()),s.attr({"aria-expanded":"false","aria-hidden":"true"}),n.oldTab.attr("aria-selected","false"),i.length&&s.length?n.oldTab.attr("tabIndex",-1):i.length&&this.tabs.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),i.attr({"aria-expanded":"true","aria-hidden":"false"}),n.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(t){var n,r=this._findActive(t);if(r[0]===this.active[0])return;r.length||(r=this.active),n=r.find(".ui-tabs-anchor")[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return typeof e=="string"&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeData("href.tabs").removeData("load.tabs").removeUniqueId(),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),n=t.data("ui-tabs-aria-controls");n?t.attr("aria-controls",n):t.removeAttr("aria-controls")}),this.panels.show(),this.options.heightStyle!=="content"&&this.panels.css("height","")},enable:function(n){var r=this.options.disabled;if(r===!1)return;n===t?r=!1:(n=this._getIndex(n),e.isArray(r)?r=e.map(r,function(e){return e!==n?e:null}):r=e.map(this.tabs,function(e,t){return t!==n?t:null})),this._setupDisabled(r)},disable:function(n){var r=this.options.disabled;if(r===!0)return;if(n===t)r=!0;else{n=this._getIndex(n);if(e.inArray(n,r)!==-1)return;e.isArray(r)?r=e.merge([n],r).sort():r=[n]}this._setupDisabled(r)},load:function(t,n){t=this._getIndex(t);var r=this,i=this.tabs.eq(t),o=i.find(".ui-tabs-anchor"),u=this._getPanelForTab(i),a={tab:i,panel:u};if(s(o[0]))return;this.xhr=e.ajax(this._ajaxSettings(o,n,a)),this.xhr&&this.xhr.statusText!=="canceled"&&(i.addClass("ui-tabs-loading"),u.attr("aria-busy","true"),this.xhr.success(function(e){setTimeout(function(){u.html(e),r._trigger("load",n,a)},1)}).complete(function(e,t){setTimeout(function(){t==="abort"&&r.panels.stop(!1,!0),i.removeClass("ui-tabs-loading"),u.removeAttr("aria-busy"),e===r.xhr&&delete r.xhr},1)}))},_ajaxSettings:function(t,n,r){var i=this;return{url:t.attr("href"),beforeSend:function(t,s){return i._trigger("beforeLoad",n,e.extend({jqXHR:t,ajaxSettings:s},r))}}},_getPanelForTab:function(t){var n=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+n))}}),e.uiBackCompat!==!1&&(e.ui.tabs.prototype._ui=function(e,t){return{tab:e,panel:t,index:this.anchors.index(e)}},e.widget("ui.tabs",e.ui.tabs,{url:function(e,t){this.anchors.eq(e).attr("href",t)}}),e.widget("ui.tabs",e.ui.tabs,{options:{ajaxOptions:null,cache:!1},_create:function(){this._super();var t=this;this._on({tabsbeforeload:function(n,r){if(e.data(r.tab[0],"cache.tabs")){n.preventDefault();return}r.jqXHR.success(function(){t.options.cache&&e.data(r.tab[0],"cache.tabs",!0)})}})},_ajaxSettings:function(t,n,r){var i=this.options.ajaxOptions;return e.extend({},i,{error:function(e,t){try{i.error(e,t,r.tab.closest("li").index(),r.tab[0])}catch(n){}}},this._superApply(arguments))},_setOption:function(e,t){e==="cache"&&t===!1&&this.anchors.removeData("cache.tabs"),this._super(e,t)},_destroy:function(){this.anchors.removeData("cache.tabs"),this._super()},url:function(e){this.anchors.eq(e).removeData("cache.tabs"),this._superApply(arguments)}}),e.widget("ui.tabs",e.ui.tabs,{abort:function(){this.xhr&&this.xhr.abort()}}),e.widget("ui.tabs",e.ui.tabs,{options:{spinner:"Loading…"},_create:function(){this._super(),this._on({tabsbeforeload:function(e,t){if(e.target!==this.element[0]||!this.options.spinner)return;var n=t.tab.find("span"),r=n.html();n.html(this.options.spinner),t.jqXHR.complete(function(){n.html(r)})}})}}),e.widget("ui.tabs",e.ui.tabs,{options:{enable:null,disable:null},enable:function(t){var n=this.options,r;if(t&&n.disabled===!0||e.isArray(n.disabled)&&e.inArray(t,n.disabled)!==-1)r=!0;this._superApply(arguments),r&&this._trigger("enable",null,this._ui(this.anchors[t],this.panels[t]))},disable:function(t){var n=this.options,r;if(t&&n.disabled===!1||e.isArray(n.disabled)&&e.inArray(t,n.disabled)===-1)r=!0;this._superApply(arguments),r&&this._trigger("disable",null,this._ui(this.anchors[t],this.panels[t]))}}),e.widget("ui.tabs",e.ui.tabs,{options:{add:null,remove:null,tabTemplate:"
    • #{label}
    • "},add:function(n,r,i){i===t&&(i=this.anchors.length);var s,o,u=this.options,a=e(u.tabTemplate.replace(/#\{href\}/g,n).replace(/#\{label\}/g,r)),f=n.indexOf("#")?this._tabId(a):n.replace("#","");return a.addClass("ui-state-default ui-corner-top").data("ui-tabs-destroy",!0),a.attr("aria-controls",f),s=i>=this.tabs.length,o=this.element.find("#"+f),o.length||(o=this._createPanel(f),s?i>0?o.insertAfter(this.panels.eq(-1)):o.appendTo(this.element):o.insertBefore(this.panels[i])),o.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").hide(),s?a.appendTo(this.tablist):a.insertBefore(this.tabs[i]),u.disabled=e.map(u.disabled,function(e){return e>=i?++e:e}),this.refresh(),this.tabs.length===1&&u.active===!1&&this.option("active",0),this._trigger("add",null,this._ui(this.anchors[i],this.panels[i])),this},remove:function(t){t=this._getIndex(t);var n=this.options,r=this.tabs.eq(t).remove(),i=this._getPanelForTab(r).remove();return r.hasClass("ui-tabs-active")&&this.anchors.length>2&&this._activate(t+(t+1=t?--e:e}),this.refresh(),this._trigger("remove",null,this._ui(r.find("a")[0],i[0])),this}}),e.widget("ui.tabs",e.ui.tabs,{length:function(){return this.anchors.length}}),e.widget("ui.tabs",e.ui.tabs,{options:{idPrefix:"ui-tabs-"},_tabId:function(t){var n=t.is("li")?t.find("a[href]"):t;return n=n[0],e(n).closest("li").attr("aria-controls")||n.title&&n.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF\-]/g,"")||this.options.idPrefix+i()}}),e.widget("ui.tabs",e.ui.tabs,{options:{panelTemplate:"
      "},_createPanel:function(t){return e(this.options.panelTemplate).attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)}}),e.widget("ui.tabs",e.ui.tabs,{_create:function(){var e=this.options;e.active===null&&e.selected!==t&&(e.active=e.selected===-1?!1:e.selected),this._super(),e.selected=e.active,e.selected===!1&&(e.selected=-1)},_setOption:function(e,t){if(e!=="selected")return this._super(e,t);var n=this.options;this._super("active",t===-1?!1:t),n.selected=n.active,n.selected===!1&&(n.selected=-1)},_eventHandler:function(){this._superApply(arguments),this.options.selected=this.options.active,this.options.selected===!1&&(this.options.selected=-1)}}),e.widget("ui.tabs",e.ui.tabs,{options:{show:null,select:null},_create:function(){this._super(),this.options.active!==!1&&this._trigger("show",null,this._ui(this.active.find(".ui-tabs-anchor")[0],this._getPanelForTab(this.active)[0]))},_trigger:function(e,t,n){var r,i,s=this._superApply(arguments);return s?(e==="beforeActivate"?(r=n.newTab.length?n.newTab:n.oldTab,i=n.newPanel.length?n.newPanel:n.oldPanel,s=this._super("select",t,{tab:r.find(".ui-tabs-anchor")[0],panel:i[0],index:r.closest("li").index()})):e==="activate"&&n.newTab.length&&(s=this._super("show",t,{tab:n.newTab.find(".ui-tabs-anchor")[0],panel:n.newPanel[0],index:n.newTab.closest("li").index()})),s):!1}}),e.widget("ui.tabs",e.ui.tabs,{select:function(e){e=this._getIndex(e);if(e===-1){if(!this.options.collapsible||this.options.selected===-1)return;e=this.options.selected}this.anchors.eq(e).trigger(this.options.event+this.eventNamespace)}}),function(){var t=0;e.widget("ui.tabs",e.ui.tabs,{options:{cookie:null},_create:function(){var e=this.options,t;e.active==null&&e.cookie&&(t=parseInt(this._cookie(),10),t===-1&&(t=!1),e.active=t),this._super()},_cookie:function(n){var r=[this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+ ++t)];return arguments.length&&(r.push(n===!1?-1:n),r.push(this.options.cookie)),e.cookie.apply(null,r)},_refresh:function(){this._super(),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_eventHandler:function(){this._superApply(arguments),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_destroy:function(){this._super(),this.options.cookie&&this._cookie(null,this.options.cookie)}})}(),e.widget("ui.tabs",e.ui.tabs,{_trigger:function(t,n,r){var i=e.extend({},r);return t==="load"&&(i.panel=i.panel[0],i.tab=i.tab.find(".ui-tabs-anchor")[0]),this._super(t,n,i)}}),e.widget("ui.tabs",e.ui.tabs,{options:{fx:null},_getFx:function(){var t,n,r=this.options.fx;return r&&(e.isArray(r)?(t=r[0],n=r[1]):t=n=r),r?{show:n,hide:t}:null},_toggle:function(e,t){function o(){n.running=!1,n._trigger("activate",e,t)}function u(){t.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),r.length&&s.show?r.animate(s.show,s.show.duration,function(){o()}):(r.show(),o())}var n=this,r=t.newPanel,i=t.oldPanel,s=this._getFx();if(!s)return this._super(e,t);n.running=!0,i.length&&s.hide?i.animate(s.hide,s.hide.duration,function(){t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),i.hide(),u())}}))})(jQuery);(function(e){function n(t,n){var r=(t.attr("aria-describedby")||"").split(/\s+/);r.push(n),t.data("ui-tooltip-id",n).attr("aria-describedby",e.trim(r.join(" ")))}function r(t){var n=t.data("ui-tooltip-id"),r=(t.attr("aria-describedby")||"").split(/\s+/),i=e.inArray(n,r);i!==-1&&r.splice(i,1),t.removeData("ui-tooltip-id"),r=e.trim(r.join(" ")),r?t.attr("aria-describedby",r):t.removeAttr("aria-describedby")}var t=0;e.widget("ui.tooltip",{version:"1.9.2",options:{content:function(){return e(this).attr("title")},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable()},_setOption:function(t,n){var r=this;if(t==="disabled"){this[n?"_disable":"_enable"](),this.options[t]=n;return}this._super(t,n),t==="content"&&e.each(this.tooltips,function(e,t){r._updateContent(t)})},_disable:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0)}),this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).attr("title","")})},_enable:function(){this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var n=this,r=e(t?t.target:this.element).closest(this.options.items);if(!r.length||r.data("ui-tooltip-id"))return;r.attr("title")&&r.data("ui-tooltip-title",r.attr("title")),r.data("ui-tooltip-open",!0),t&&t.type==="mouseover"&&r.parents().each(function(){var t=e(this),r;t.data("ui-tooltip-open")&&(r=e.Event("blur"),r.target=r.currentTarget=this,n.close(r,!0)),t.attr("title")&&(t.uniqueId(),n.parents[this.id]={element:this,title:t.attr("title")},t.attr("title",""))}),this._updateContent(r,t)},_updateContent:function(e,t){var n,r=this.options.content,i=this,s=t?t.type:null;if(typeof r=="string")return this._open(t,e,r);n=r.call(e[0],function(n){if(!e.data("ui-tooltip-open"))return;i._delay(function(){t&&(t.type=s),this._open(t,e,n)})}),n&&this._open(t,e,n)},_open:function(t,r,i){function f(e){a.of=e;if(s.is(":hidden"))return;s.position(a)}var s,o,u,a=e.extend({},this.options.position);if(!i)return;s=this._find(r);if(s.length){s.find(".ui-tooltip-content").html(i);return}r.is("[title]")&&(t&&t.type==="mouseover"?r.attr("title",""):r.removeAttr("title")),s=this._tooltip(r),n(r,s.attr("id")),s.find(".ui-tooltip-content").html(i),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:f}),f(t)):s.position(e.extend({of:r},this.options.position)),s.hide(),this._show(s,this.options.show),this.options.show&&this.options.show.delay&&(u=setInterval(function(){s.is(":visible")&&(f(a.of),clearInterval(u))},e.fx.interval)),this._trigger("open",t,{tooltip:s}),o={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var n=e.Event(t);n.currentTarget=r[0],this.close(n,!0)}},remove:function(){this._removeTooltip(s)}};if(!t||t.type==="mouseover")o.mouseleave="close";if(!t||t.type==="focusin")o.focusout="close";this._on(!0,r,o)},close:function(t){var n=this,i=e(t?t.currentTarget:this.element),s=this._find(i);if(this.closing)return;i.data("ui-tooltip-title")&&i.attr("title",i.data("ui-tooltip-title")),r(i),s.stop(!0),this._hide(s,this.options.hide,function(){n._removeTooltip(e(this))}),i.removeData("ui-tooltip-open"),this._off(i,"mouseleave focusout keyup"),i[0]!==this.element[0]&&this._off(i,"remove"),this._off(this.document,"mousemove"),t&&t.type==="mouseleave"&&e.each(this.parents,function(t,r){e(r.element).attr("title",r.title),delete n.parents[t]}),this.closing=!0,this._trigger("close",t,{tooltip:s}),this.closing=!1},_tooltip:function(n){var r="ui-tooltip-"+t++,i=e("
      ").attr({id:r,role:"tooltip"}).addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||""));return e("
      ").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),e.fn.bgiframe&&i.bgiframe(),this.tooltips[r]=n,i},_find:function(t){var n=t.data("ui-tooltip-id");return n?e("#"+n):e()},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0),e("#"+n).remove(),r.data("ui-tooltip-title")&&(r.attr("title",r.data("ui-tooltip-title")),r.removeData("ui-tooltip-title"))})}})})(jQuery);jQuery.effects||function(e,t){var n=e.uiBackCompat!==!1,r="ui-effects-";e.effects={effect:{}},function(t,n){function p(e,t,n){var r=a[t.type]||{};return e==null?n||!t.def?null:t.def:(e=r.floor?~~e:parseFloat(e),isNaN(e)?t.def:r.mod?(e+r.mod)%r.mod:0>e?0:r.max")[0],c,h=t.each;l.style.cssText="background-color:rgba(1,1,1,.5)",f.rgba=l.style.backgroundColor.indexOf("rgba")>-1,h(u,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),o.fn=t.extend(o.prototype,{parse:function(r,i,s,a){if(r===n)return this._rgba=[null,null,null,null],this;if(r.jquery||r.nodeType)r=t(r).css(i),i=n;var f=this,l=t.type(r),v=this._rgba=[];i!==n&&(r=[r,i,s,a],l="array");if(l==="string")return this.parse(d(r)||c._default);if(l==="array")return h(u.rgba.props,function(e,t){v[t.idx]=p(r[t.idx],t)}),this;if(l==="object")return r instanceof o?h(u,function(e,t){r[t.cache]&&(f[t.cache]=r[t.cache].slice())}):h(u,function(t,n){var i=n.cache;h(n.props,function(e,t){if(!f[i]&&n.to){if(e==="alpha"||r[e]==null)return;f[i]=n.to(f._rgba)}f[i][t.idx]=p(r[e],t,!0)}),f[i]&&e.inArray(null,f[i].slice(0,3))<0&&(f[i][3]=1,n.from&&(f._rgba=n.from(f[i])))}),this},is:function(e){var t=o(e),n=!0,r=this;return h(u,function(e,i){var s,o=t[i.cache];return o&&(s=r[i.cache]||i.to&&i.to(r._rgba)||[],h(i.props,function(e,t){if(o[t.idx]!=null)return n=o[t.idx]===s[t.idx],n})),n}),n},_space:function(){var e=[],t=this;return h(u,function(n,r){t[r.cache]&&e.push(n)}),e.pop()},transition:function(e,t){var n=o(e),r=n._space(),i=u[r],s=this.alpha()===0?o("transparent"):this,f=s[i.cache]||i.to(s._rgba),l=f.slice();return n=n[i.cache],h(i.props,function(e,r){var i=r.idx,s=f[i],o=n[i],u=a[r.type]||{};if(o===null)return;s===null?l[i]=o:(u.mod&&(o-s>u.mod/2?s+=u.mod:s-o>u.mod/2&&(s-=u.mod)),l[i]=p((o-s)*t+s,r))}),this[r](l)},blend:function(e){if(this._rgba[3]===1)return this;var n=this._rgba.slice(),r=n.pop(),i=o(e)._rgba;return o(t.map(n,function(e,t){return(1-r)*i[t]+r*e}))},toRgbaString:function(){var e="rgba(",n=t.map(this._rgba,function(e,t){return e==null?t>2?1:0:e});return n[3]===1&&(n.pop(),e="rgb("),e+n.join()+")"},toHslaString:function(){var e="hsla(",n=t.map(this.hsla(),function(e,t){return e==null&&(e=t>2?1:0),t&&t<3&&(e=Math.round(e*100)+"%"),e});return n[3]===1&&(n.pop(),e="hsl("),e+n.join()+")"},toHexString:function(e){var n=this._rgba.slice(),r=n.pop();return e&&n.push(~~(r*255)),"#"+t.map(n,function(e){return e=(e||0).toString(16),e.length===1?"0"+e:e}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}}),o.fn.parse.prototype=o.fn,u.hsla.to=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/255,n=e[1]/255,r=e[2]/255,i=e[3],s=Math.max(t,n,r),o=Math.min(t,n,r),u=s-o,a=s+o,f=a*.5,l,c;return o===s?l=0:t===s?l=60*(n-r)/u+360:n===s?l=60*(r-t)/u+120:l=60*(t-n)/u+240,f===0||f===1?c=f:f<=.5?c=u/a:c=u/(2-a),[Math.round(l)%360,c,f,i==null?1:i]},u.hsla.from=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/360,n=e[1],r=e[2],i=e[3],s=r<=.5?r*(1+n):r+n-r*n,o=2*r-s;return[Math.round(v(o,s,t+1/3)*255),Math.round(v(o,s,t)*255),Math.round(v(o,s,t-1/3)*255),i]},h(u,function(e,r){var s=r.props,u=r.cache,a=r.to,f=r.from;o.fn[e]=function(e){a&&!this[u]&&(this[u]=a(this._rgba));if(e===n)return this[u].slice();var r,i=t.type(e),l=i==="array"||i==="object"?e:arguments,c=this[u].slice();return h(s,function(e,t){var n=l[i==="object"?e:t.idx];n==null&&(n=c[t.idx]),c[t.idx]=p(n,t)}),f?(r=o(f(c)),r[u]=c,r):o(c)},h(s,function(n,r){if(o.fn[n])return;o.fn[n]=function(s){var o=t.type(s),u=n==="alpha"?this._hsla?"hsla":"rgba":e,a=this[u](),f=a[r.idx],l;return o==="undefined"?f:(o==="function"&&(s=s.call(this,f),o=t.type(s)),s==null&&r.empty?this:(o==="string"&&(l=i.exec(s),l&&(s=f+parseFloat(l[2])*(l[1]==="+"?1:-1))),a[r.idx]=s,this[u](a)))}})}),h(r,function(e,n){t.cssHooks[n]={set:function(e,r){var i,s,u="";if(t.type(r)!=="string"||(i=d(r))){r=o(i||r);if(!f.rgba&&r._rgba[3]!==1){s=n==="backgroundColor"?e.parentNode:e;while((u===""||u==="transparent")&&s&&s.style)try{u=t.css(s,"backgroundColor"),s=s.parentNode}catch(a){}r=r.blend(u&&u!=="transparent"?u:"_default")}r=r.toRgbaString()}try{e.style[n]=r}catch(l){}}},t.fx.step[n]=function(e){e.colorInit||(e.start=o(e.elem,n),e.end=o(e.end),e.colorInit=!0),t.cssHooks[n].set(e.elem,e.start.transition(e.end,e.pos))}}),t.cssHooks.borderColor={expand:function(e){var t={};return h(["Top","Right","Bottom","Left"],function(n,r){t["border"+r+"Color"]=e}),t}},c=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(){var t=this.ownerDocument.defaultView?this.ownerDocument.defaultView.getComputedStyle(this,null):this.currentStyle,n={},r,i;if(t&&t.length&&t[0]&&t[t[0]]){i=t.length;while(i--)r=t[i],typeof t[r]=="string"&&(n[e.camelCase(r)]=t[r])}else for(r in t)typeof t[r]=="string"&&(n[r]=t[r]);return n}function s(t,n){var i={},s,o;for(s in n)o=n[s],t[s]!==o&&!r[s]&&(e.fx.step[s]||!isNaN(parseFloat(o)))&&(i[s]=o);return i}var n=["add","remove","toggle"],r={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,n){e.fx.step[n]=function(e){if(e.end!=="none"&&!e.setAttr||e.pos===1&&!e.setAttr)jQuery.style(e.elem,n,e.end),e.setAttr=!0}}),e.effects.animateClass=function(t,r,o,u){var a=e.speed(r,o,u);return this.queue(function(){var r=e(this),o=r.attr("class")||"",u,f=a.children?r.find("*").andSelf():r;f=f.map(function(){var t=e(this);return{el:t,start:i.call(this)}}),u=function(){e.each(n,function(e,n){t[n]&&r[n+"Class"](t[n])})},u(),f=f.map(function(){return this.end=i.call(this.el[0]),this.diff=s(this.start,this.end),this}),r.attr("class",o),f=f.map(function(){var t=this,n=e.Deferred(),r=jQuery.extend({},a,{queue:!1,complete:function(){n.resolve(t)}});return this.el.animate(this.diff,r),n.promise()}),e.when.apply(e,f.get()).done(function(){u(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),a.complete.call(r[0])})})},e.fn.extend({_addClass:e.fn.addClass,addClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{add:t},n,r,i):this._addClass(t)},_removeClass:e.fn.removeClass,removeClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{remove:t},n,r,i):this._removeClass(t)},_toggleClass:e.fn.toggleClass,toggleClass:function(n,r,i,s,o){return typeof r=="boolean"||r===t?i?e.effects.animateClass.call(this,r?{add:n}:{remove:n},i,s,o):this._toggleClass(n,r):e.effects.animateClass.call(this,{toggle:n},r,i,s)},switchClass:function(t,n,r,i,s){return e.effects.animateClass.call(this,{add:n,remove:t},r,i,s)}})}(),function(){function i(t,n,r,i){e.isPlainObject(t)&&(n=t,t=t.effect),t={effect:t},n==null&&(n={}),e.isFunction(n)&&(i=n,r=null,n={});if(typeof n=="number"||e.fx.speeds[n])i=r,r=n,n={};return e.isFunction(r)&&(i=r,r=null),n&&e.extend(t,n),r=r||n.duration,t.duration=e.fx.off?0:typeof r=="number"?r:r in e.fx.speeds?e.fx.speeds[r]:e.fx.speeds._default,t.complete=i||n.complete,t}function s(t){return!t||typeof t=="number"||e.fx.speeds[t]?!0:typeof t=="string"&&!e.effects.effect[t]?n&&e.effects[t]?!1:!0:!1}e.extend(e.effects,{version:"1.9.2",save:function(e,t){for(var n=0;n
      ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),i={width:t.width(),height:t.height()},s=document.activeElement;try{s.id}catch(o){s=document.body}return t.wrap(r),(t[0]===s||e.contains(t[0],s))&&e(s).focus(),r=t.parent(),t.css("position")==="static"?(r.css({position:"relative"}),t.css({position:"relative"})):(e.extend(n,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,r){n[r]=t.css(r),isNaN(parseInt(n[r],10))&&(n[r]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(i),r.css(n).show()},removeWrapper:function(t){var n=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===n||e.contains(t[0],n))&&e(n).focus()),t},setTransition:function(t,n,r,i){return i=i||{},e.each(n,function(e,n){var s=t.cssUnit(n);s[0]>0&&(i[n]=s[0]*r+s[1])}),i}}),e.fn.extend({effect:function(){function a(n){function u(){e.isFunction(i)&&i.call(r[0]),e.isFunction(n)&&n()}var r=e(this),i=t.complete,s=t.mode;(r.is(":hidden")?s==="hide":s==="show")?u():o.call(r[0],t,u)}var t=i.apply(this,arguments),r=t.mode,s=t.queue,o=e.effects.effect[t.effect],u=!o&&n&&e.effects[t.effect];return e.fx.off||!o&&!u?r?this[r](t.duration,t.complete):this.each(function(){t.complete&&t.complete.call(this)}):o?s===!1?this.each(a):this.queue(s||"fx",a):u.call(this,{options:t,duration:t.duration,callback:t.complete,mode:t.mode})},_show:e.fn.show,show:function(e){if(s(e))return this._show.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="show",this.effect.call(this,t)},_hide:e.fn.hide,hide:function(e){if(s(e))return this._hide.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="hide",this.effect.call(this,t)},__toggle:e.fn.toggle,toggle:function(t){if(s(t)||typeof t=="boolean"||e.isFunction(t))return this.__toggle.apply(this,arguments);var n=i.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)},cssUnit:function(t){var n=this.css(t),r=[];return e.each(["em","px","%","pt"],function(e,t){n.indexOf(t)>0&&(r=[parseFloat(n),t])}),r}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,n){t[n]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return e===0||e===1?e:-Math.pow(2,8*(e-1))*Math.sin(((e-1)*80-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){var t,n=4;while(e<((t=Math.pow(2,--n))-1)/11);return 1/Math.pow(4,3-n)-7.5625*Math.pow((t*3-2)/22-e,2)}}),e.each(t,function(t,n){e.easing["easeIn"+t]=n,e.easing["easeOut"+t]=function(e){return 1-n(1-e)},e.easing["easeInOut"+t]=function(e){return e<.5?n(e*2)/2:1-n(e*-2+2)/2}})}()}(jQuery);(function(e,t){var n=/up|down|vertical/,r=/up|left|vertical|horizontal/;e.effects.effect.blind=function(t,i){var s=e(this),o=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(s,t.mode||"hide"),a=t.direction||"up",f=n.test(a),l=f?"height":"width",c=f?"top":"left",h=r.test(a),p={},d=u==="show",v,m,g;s.parent().is(".ui-effects-wrapper")?e.effects.save(s.parent(),o):e.effects.save(s,o),s.show(),v=e.effects.createWrapper(s).css({overflow:"hidden"}),m=v[l](),g=parseFloat(v.css(c))||0,p[l]=d?m:0,h||(s.css(f?"bottom":"right",0).css(f?"top":"left","auto").css({position:"absolute"}),p[c]=d?g:m+g),d&&(v.css(l,0),h||v.css(c,g+m)),v.animate(p,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){u==="hide"&&s.hide(),e.effects.restore(s,o),e.effects.removeWrapper(s),i()}})}})(jQuery);(function(e,t){e.effects.effect.bounce=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=s==="hide",u=s==="show",a=t.direction||"up",f=t.distance,l=t.times||5,c=l*2+(u||o?1:0),h=t.duration/c,p=t.easing,d=a==="up"||a==="down"?"top":"left",v=a==="up"||a==="left",m,g,y,b=r.queue(),w=b.length;(u||o)&&i.push("opacity"),e.effects.save(r,i),r.show(),e.effects.createWrapper(r),f||(f=r[d==="top"?"outerHeight":"outerWidth"]()/3),u&&(y={opacity:1},y[d]=0,r.css("opacity",0).css(d,v?-f*2:f*2).animate(y,h,p)),o&&(f/=Math.pow(2,l-1)),y={},y[d]=0;for(m=0;m1&&b.splice.apply(b,[1,0].concat(b.splice(w,c+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.clip=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"vertical",a=u==="vertical",f=a?"height":"width",l=a?"top":"left",c={},h,p,d;e.effects.save(r,i),r.show(),h=e.effects.createWrapper(r).css({overflow:"hidden"}),p=r[0].tagName==="IMG"?h:r,d=p[f](),o&&(p.css(f,0),p.css(l,d/2)),c[f]=o?d:0,c[l]=o?0:d/2,p.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o||r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.drop=function(t,n){var r=e(this),i=["position","top","bottom","left","right","opacity","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left"?"pos":"neg",l={opacity:o?1:0},c;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),c=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0)/2,o&&r.css("opacity",0).css(a,f==="pos"?-c:c),l[a]=(o?f==="pos"?"+=":"-=":f==="pos"?"-=":"+=")+c,r.animate(l,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.explode=function(t,n){function y(){c.push(this),c.length===r*i&&b()}function b(){s.css({visibility:"visible"}),e(c).remove(),u||s.hide(),n()}var r=t.pieces?Math.round(Math.sqrt(t.pieces)):3,i=r,s=e(this),o=e.effects.setMode(s,t.mode||"hide"),u=o==="show",a=s.show().css("visibility","hidden").offset(),f=Math.ceil(s.outerWidth()/i),l=Math.ceil(s.outerHeight()/r),c=[],h,p,d,v,m,g;for(h=0;h
      ").css({position:"absolute",visibility:"visible",left:-p*f,top:-h*l}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:f,height:l,left:d+(u?m*f:0),top:v+(u?g*l:0),opacity:u?0:1}).animate({left:d+(u?0:m*f),top:v+(u?0:g*l),opacity:u?1:0},t.duration||500,t.easing,y)}}})(jQuery);(function(e,t){e.effects.effect.fade=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"toggle");r.animate({opacity:i},{queue:!1,duration:t.duration,easing:t.easing,complete:n})}})(jQuery);(function(e,t){e.effects.effect.fold=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=s==="hide",a=t.size||15,f=/([0-9]+)%/.exec(a),l=!!t.horizFirst,c=o!==l,h=c?["width","height"]:["height","width"],p=t.duration/2,d,v,m={},g={};e.effects.save(r,i),r.show(),d=e.effects.createWrapper(r).css({overflow:"hidden"}),v=c?[d.width(),d.height()]:[d.height(),d.width()],f&&(a=parseInt(f[1],10)/100*v[u?0:1]),o&&d.css(l?{height:0,width:a}:{height:a,width:0}),m[h[0]]=o?v[0]:a,g[h[1]]=o?v[1]:0,d.animate(m,p,t.easing).animate(g,p,t.easing,function(){u&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()})}})(jQuery);(function(e,t){e.effects.effect.highlight=function(t,n){var r=e(this),i=["backgroundImage","backgroundColor","opacity"],s=e.effects.setMode(r,t.mode||"show"),o={backgroundColor:r.css("backgroundColor")};s==="hide"&&(o.opacity=0),e.effects.save(r,i),r.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),n()}})}})(jQuery);(function(e,t){e.effects.effect.pulsate=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"show"),s=i==="show",o=i==="hide",u=s||i==="hide",a=(t.times||5)*2+(u?1:0),f=t.duration/a,l=0,c=r.queue(),h=c.length,p;if(s||!r.is(":visible"))r.css("opacity",0).show(),l=1;for(p=1;p1&&c.splice.apply(c,[1,0].concat(c.splice(h,a+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.puff=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"hide"),s=i==="hide",o=parseInt(t.percent,10)||150,u=o/100,a={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:i,complete:n,percent:s?o:100,from:s?a:{height:a.height*u,width:a.width*u,outerHeight:a.outerHeight*u,outerWidth:a.outerWidth*u}}),r.effect(t)},e.effects.effect.scale=function(t,n){var r=e(this),i=e.extend(!0,{},t),s=e.effects.setMode(r,t.mode||"effect"),o=parseInt(t.percent,10)||(parseInt(t.percent,10)===0?0:s==="hide"?0:100),u=t.direction||"both",a=t.origin,f={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()},l={y:u!=="horizontal"?o/100:1,x:u!=="vertical"?o/100:1};i.effect="size",i.queue=!1,i.complete=n,s!=="effect"&&(i.origin=a||["middle","center"],i.restore=!0),i.from=t.from||(s==="show"?{height:0,width:0,outerHeight:0,outerWidth:0}:f),i.to={height:f.height*l.y,width:f.width*l.x,outerHeight:f.outerHeight*l.y,outerWidth:f.outerWidth*l.x},i.fade&&(s==="show"&&(i.from.opacity=0,i.to.opacity=1),s==="hide"&&(i.from.opacity=1,i.to.opacity=0)),r.effect(i)},e.effects.effect.size=function(t,n){var r,i,s,o=e(this),u=["position","top","bottom","left","right","width","height","overflow","opacity"],a=["position","top","bottom","left","right","overflow","opacity"],f=["width","height","overflow"],l=["fontSize"],c=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],h=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),d=t.restore||p!=="effect",v=t.scale||"both",m=t.origin||["middle","center"],g=o.css("position"),y=d?u:a,b={height:0,width:0,outerHeight:0,outerWidth:0};p==="show"&&o.show(),r={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},t.mode==="toggle"&&p==="show"?(o.from=t.to||b,o.to=t.from||r):(o.from=t.from||(p==="show"?b:r),o.to=t.to||(p==="hide"?b:r)),s={from:{y:o.from.height/r.height,x:o.from.width/r.width},to:{y:o.to.height/r.height,x:o.to.width/r.width}};if(v==="box"||v==="both")s.from.y!==s.to.y&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,s.from.y,o.from),o.to=e.effects.setTransition(o,c,s.to.y,o.to)),s.from.x!==s.to.x&&(y=y.concat(h),o.from=e.effects.setTransition(o,h,s.from.x,o.from),o.to=e.effects.setTransition(o,h,s.to.x,o.to));(v==="content"||v==="both")&&s.from.y!==s.to.y&&(y=y.concat(l).concat(f),o.from=e.effects.setTransition(o,l,s.from.y,o.from),o.to=e.effects.setTransition(o,l,s.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),m&&(i=e.effects.getBaseline(m,r),o.from.top=(r.outerHeight-o.outerHeight())*i.y,o.from.left=(r.outerWidth-o.outerWidth())*i.x,o.to.top=(r.outerHeight-o.to.outerHeight)*i.y,o.to.left=(r.outerWidth-o.to.outerWidth)*i.x),o.css(o.from);if(v==="content"||v==="both")c=c.concat(["marginTop","marginBottom"]).concat(l),h=h.concat(["marginLeft","marginRight"]),f=u.concat(c).concat(h),o.find("*[width]").each(function(){var n=e(this),r={height:n.height(),width:n.width(),outerHeight:n.outerHeight(),outerWidth:n.outerWidth()};d&&e.effects.save(n,f),n.from={height:r.height*s.from.y,width:r.width*s.from.x,outerHeight:r.outerHeight*s.from.y,outerWidth:r.outerWidth*s.from.x},n.to={height:r.height*s.to.y,width:r.width*s.to.x,outerHeight:r.height*s.to.y,outerWidth:r.width*s.to.x},s.from.y!==s.to.y&&(n.from=e.effects.setTransition(n,c,s.from.y,n.from),n.to=e.effects.setTransition(n,c,s.to.y,n.to)),s.from.x!==s.to.x&&(n.from=e.effects.setTransition(n,h,s.from.x,n.from),n.to=e.effects.setTransition(n,h,s.to.x,n.to)),n.css(n.from),n.animate(n.to,t.duration,t.easing,function(){d&&e.effects.restore(n,f)})});o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o.to.opacity===0&&o.css("opacity",o.from.opacity),p==="hide"&&o.hide(),e.effects.restore(o,y),d||(g==="static"?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,n){var r=parseInt(n,10),i=e?o.to.left:o.to.top;return n==="auto"?i+"px":r+i+"px"})})),e.effects.removeWrapper(o),n()}})}})(jQuery);(function(e,t){e.effects.effect.shake=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=t.direction||"left",u=t.distance||20,a=t.times||3,f=a*2+1,l=Math.round(t.duration/f),c=o==="up"||o==="down"?"top":"left",h=o==="up"||o==="left",p={},d={},v={},m,g=r.queue(),y=g.length;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),p[c]=(h?"-=":"+=")+u,d[c]=(h?"+=":"-=")+u*2,v[c]=(h?"-=":"+=")+u*2,r.animate(p,l,t.easing);for(m=1;m1&&g.splice.apply(g,[1,0].concat(g.splice(y,f+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.slide=function(t,n){var r=e(this),i=["position","top","bottom","left","right","width","height"],s=e.effects.setMode(r,t.mode||"show"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left",l,c={};e.effects.save(r,i),r.show(),l=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(r).css({overflow:"hidden"}),o&&r.css(a,f?isNaN(l)?"-"+l:-l:l),c[a]=(o?f?"+=":"-=":f?"-=":"+=")+l,r.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.transfer=function(t,n){var r=e(this),i=e(t.to),s=i.css("position")==="fixed",o=e("body"),u=s?o.scrollTop():0,a=s?o.scrollLeft():0,f=i.offset(),l={top:f.top-u,left:f.left-a,height:i.innerHeight(),width:i.innerWidth()},c=r.offset(),h=e('
      ').appendTo(document.body).addClass(t.className).css({top:c.top-u,left:c.left-a,height:r.innerHeight(),width:r.innerWidth(),position:s?"fixed":"absolute"}).animate(l,t.duration,t.easing,function(){h.remove(),n()})}})(jQuery); + +/* JQuery UJS 2.0.3 */ +(function(a,b){var c=function(){var b=a(document).data("events");return b&&b.click&&a.grep(b.click,function(a){return a.namespace==="rails"}).length};if(c()){a.error("jquery-ujs has already been loaded!")}var d;a.rails=d={linkClickSelector:"a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]",inputChangeSelector:"select[data-remote], input[data-remote], textarea[data-remote]",formSubmitSelector:"form",formInputClickSelector:"form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])",disableSelector:"input[data-disable-with], button[data-disable-with], textarea[data-disable-with]",enableSelector:"input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled",requiredInputSelector:"input[name][required]:not([disabled]),textarea[name][required]:not([disabled])",fileInputSelector:"input:file",linkDisableSelector:"a[data-disable-with]",CSRFProtection:function(b){var c=a('meta[name="csrf-token"]').attr("content");if(c)b.setRequestHeader("X-CSRF-Token",c)},fire:function(b,c,d){var e=a.Event(c);b.trigger(e,d);return e.result!==false},confirm:function(a){return confirm(a)},ajax:function(b){return a.ajax(b)},href:function(a){return a.attr("href")},handleRemote:function(c){var e,f,g,h,i,j,k,l;if(d.fire(c,"ajax:before")){h=c.data("cross-domain");i=h===b?null:h;j=c.data("with-credentials")||null;k=c.data("type")||a.ajaxSettings&&a.ajaxSettings.dataType;if(c.is("form")){e=c.attr("method");f=c.attr("action");g=c.serializeArray();var m=c.data("ujs:submit-button");if(m){g.push(m);c.data("ujs:submit-button",null)}}else if(c.is(d.inputChangeSelector)){e=c.data("method");f=c.data("url");g=c.serialize();if(c.data("params"))g=g+"&"+c.data("params")}else{e=c.data("method");f=d.href(c);g=c.data("params")||null}l={type:e||"GET",data:g,dataType:k,beforeSend:function(a,e){if(e.dataType===b){a.setRequestHeader("accept","*/*;q=0.5, "+e.accepts.script)}return d.fire(c,"ajax:beforeSend",[a,e])},success:function(a,b,d){c.trigger("ajax:success",[a,b,d])},complete:function(a,b){c.trigger("ajax:complete",[a,b])},error:function(a,b,d){c.trigger("ajax:error",[a,b,d])},xhrFields:{withCredentials:j},crossDomain:i};if(f){l.url=f}var n=d.ajax(l);c.trigger("ajax:send",n);return n}else{return false}},handleMethod:function(c){var e=d.href(c),f=c.data("method"),g=c.attr("target"),h=a("meta[name=csrf-token]").attr("content"),i=a("meta[name=csrf-param]").attr("content"),j=a('
      '),k='';if(i!==b&&h!==b){k+=''}if(g){j.attr("target",g)}j.hide().append(k).appendTo("body");j.submit()},disableFormElements:function(b){b.find(d.disableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";b.data("ujs:enable-with",b[c]());b[c](b.data("disable-with"));b.prop("disabled",true)})},enableFormElements:function(b){b.find(d.enableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";if(b.data("ujs:enable-with"))b[c](b.data("ujs:enable-with"));b.prop("disabled",false)})},allowAction:function(a){var b=a.data("confirm"),c=false,e;if(!b){return true}if(d.fire(a,"confirm")){c=d.confirm(b);e=d.fire(a,"confirm:complete",[c])}return c&&e},blankInputs:function(b,c,d){var e=a(),f,g,h=c||"input,textarea";b.find(h).each(function(){f=a(this);g=f.is(":checkbox,:radio")?f.is(":checked"):f.val();if(g==!!d){e=e.add(f)}});return e.length?e:false},nonBlankInputs:function(a,b){return d.blankInputs(a,b,true)},stopEverything:function(b){a(b.target).trigger("ujs:everythingStopped");b.stopImmediatePropagation();return false},callFormSubmitBindings:function(c,d){var e=c.data("events"),f=true;if(e!==b&&e["submit"]!==b){a.each(e["submit"],function(a,b){if(typeof b.handler==="function")return f=b.handler(d)})}return f},disableElement:function(a){a.data("ujs:enable-with",a.html());a.html(a.data("disable-with"));a.bind("click.railsDisable",function(a){return d.stopEverything(a)})},enableElement:function(a){if(a.data("ujs:enable-with")!==b){a.html(a.data("ujs:enable-with"));a.data("ujs:enable-with",false)}a.unbind("click.railsDisable")}};if(d.fire(a(document),"rails:attachBindings")){a.ajaxPrefilter(function(a,b,c){if(!a.crossDomain){d.CSRFProtection(c)}});a(document).delegate(d.linkDisableSelector,"ajax:complete",function(){d.enableElement(a(this))});a(document).delegate(d.linkClickSelector,"click.rails",function(c){var e=a(this),f=e.data("method"),g=e.data("params");if(!d.allowAction(e))return d.stopEverything(c);if(e.is(d.linkDisableSelector))d.disableElement(e);if(e.data("remote")!==b){if((c.metaKey||c.ctrlKey)&&(!f||f==="GET")&&!g){return true}if(d.handleRemote(e)===false){d.enableElement(e)}return false}else if(e.data("method")){d.handleMethod(e);return false}});a(document).delegate(d.inputChangeSelector,"change.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);d.handleRemote(c);return false});a(document).delegate(d.formSubmitSelector,"submit.rails",function(c){var e=a(this),f=e.data("remote")!==b,g=d.blankInputs(e,d.requiredInputSelector),h=d.nonBlankInputs(e,d.fileInputSelector);if(!d.allowAction(e))return d.stopEverything(c);if(g&&e.attr("novalidate")==b&&d.fire(e,"ajax:aborted:required",[g])){return d.stopEverything(c)}if(f){if(h){setTimeout(function(){d.disableFormElements(e)},13);return d.fire(e,"ajax:aborted:file",[h])}if(!a.support.submitBubbles&&a().jquery<"1.7"&&d.callFormSubmitBindings(e,c)===false)return d.stopEverything(c);d.handleRemote(e);return false}else{setTimeout(function(){d.disableFormElements(e)},13)}});a(document).delegate(d.formInputClickSelector,"click.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);var e=c.attr("name"),f=e?{name:e,value:c.val()}:null;c.closest("form").data("ujs:submit-button",f)});a(document).delegate(d.formSubmitSelector,"ajax:beforeSend.rails",function(b){if(this==b.target)d.disableFormElements(a(this))});a(document).delegate(d.formSubmitSelector,"ajax:complete.rails",function(b){if(this==b.target)d.enableFormElements(a(this))});a(function(){csrf_token=a("meta[name=csrf-token]").attr("content");csrf_param=a("meta[name=csrf-param]").attr("content");a('form input[name="'+csrf_param+'"]').val(csrf_token)})}})(jQuery) diff -r d98d22a98252 -r afce8026aaeb public/javascripts/jstoolbar/jstoolbar-textile.min.js --- a/public/javascripts/jstoolbar/jstoolbar-textile.min.js Wed May 07 14:15:02 2014 +0100 +++ b/public/javascripts/jstoolbar/jstoolbar-textile.min.js Tue Sep 09 09:34:53 2014 +0100 @@ -1,1 +1,2 @@ -function jsToolBar(e){if(!document.createElement){return}if(!e){return}if(typeof document["selection"]=="undefined"&&typeof e["setSelectionRange"]=="undefined"){return}this.textarea=e;this.editor=document.createElement("div");this.editor.className="jstEditor";this.textarea.parentNode.insertBefore(this.editor,this.textarea);this.editor.appendChild(this.textarea);this.toolbar=document.createElement("div");this.toolbar.className="jstElements";this.editor.parentNode.insertBefore(this.toolbar,this.editor);if(this.editor.addEventListener&&navigator.appVersion.match(/\bMSIE\b/)){this.handle=document.createElement("div");this.handle.className="jstHandle";var t=this.resizeDragStart;var n=this;this.handle.addEventListener("mousedown",function(e){t.call(n,e)},false);window.addEventListener("unload",function(){var e=n.handle.parentNode.removeChild(n.handle);delete n.handle},false);this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling)}this.context=null;this.toolNodes={}}function jsButton(e,t,n,r){if(typeof jsToolBar.strings=="undefined"){this.title=e||null}else{this.title=jsToolBar.strings[e]||e||null}this.fn=t||function(){};this.scope=n||null;this.className=r||null}function jsSpace(e){this.id=e||null;this.width=null}function jsCombo(e,t,n,r,i){this.title=e||null;this.options=t||null;this.scope=n||null;this.fn=r||function(){};this.className=i||null}jsButton.prototype.draw=function(){if(!this.scope)return null;var e=document.createElement("button");e.setAttribute("type","button");e.tabIndex=200;if(this.className)e.className=this.className;e.title=this.title;var t=document.createElement("span");t.appendChild(document.createTextNode(this.title));e.appendChild(t);if(this.icon!=undefined){e.style.backgroundImage="url("+this.icon+")"}if(typeof this.fn=="function"){var n=this;e.onclick=function(){try{n.fn.apply(n.scope,arguments)}catch(e){}return false}}return e};jsSpace.prototype.draw=function(){var e=document.createElement("span");if(this.id)e.id=this.id;e.appendChild(document.createTextNode(String.fromCharCode(160)));e.className="jstSpacer";if(this.width)e.style.marginRight=this.width+"px";return e};jsCombo.prototype.draw=function(){if(!this.scope||!this.options)return null;var e=document.createElement("select");if(this.className)e.className=className;e.title=this.title;for(var t in this.options){var n=document.createElement("option");n.value=t;n.appendChild(document.createTextNode(this.options[t]));e.appendChild(n)}var r=this;e.onchange=function(){try{r.fn.call(r.scope,this.value)}catch(e){alert(e)}return false};return e};jsToolBar.prototype={base_url:"",mode:"wiki",elements:{},help_link:"",getMode:function(){return this.mode},setMode:function(e){this.mode=e||"wiki"},switchMode:function(e){e=e||"wiki";this.draw(e)},setHelpLink:function(e){this.help_link=e},button:function(e){var t=this.elements[e];if(typeof t.fn[this.mode]!="function")return null;var n=new jsButton(t.title,t.fn[this.mode],this,"jstb_"+e);if(t.icon!=undefined)n.icon=t.icon;return n},space:function(e){var t=new jsSpace(e);if(this.elements[e].width!==undefined)t.width=this.elements[e].width;return t},combo:function(e){var t=this.elements[e];var n=t[this.mode].list.length;if(typeof t[this.mode].fn!="function"||n==0){return null}else{var r={};for(var i=0;iAide";this.toolbar.appendChild(t);var n,r,i;for(var s in this.elements){n=this.elements[s];var o=n.type==undefined||n.type==""||n.disabled!=undefined&&n.disabled||n.context!=undefined&&n.context!=null&&n.context!=this.context;if(!o&&typeof this[n.type]=="function"){r=this[n.type](s);if(r)i=r.draw();if(i){this.toolNodes[s]=i;this.toolbar.appendChild(i)}}}},singleTag:function(e,t){e=e||null;t=t||e;if(!e||!t){return}this.encloseSelection(e,t)},encloseLineSelection:function(e,t,n){this.textarea.focus();e=e||"";t=t||"";var r,i,s,o,u,a;if(typeof document["selection"]!="undefined"){s=document.selection.createRange().text}else if(typeof this.textarea["setSelectionRange"]!="undefined"){r=this.textarea.selectionStart;i=this.textarea.selectionEnd;o=this.textarea.scrollTop;r=this.textarea.value.substring(0,r).replace(/[^\r\n]*$/g,"").length;i=this.textarea.value.length-this.textarea.value.substring(i,this.textarea.value.length).replace(/^[^\r\n]*/,"").length;s=this.textarea.value.substring(r,i)}if(s.match(/ $/)){s=s.substring(0,s.length-1);t=t+" "}if(typeof n=="function"){a=s?n.call(this,s):n("")}else{a=s?s:""}u=e+a+t;if(typeof document["selection"]!="undefined"){document.selection.createRange().text=u;var f=this.textarea.createTextRange();f.collapse(false);f.move("character",-t.length);f.select()}else if(typeof this.textarea["setSelectionRange"]!="undefined"){this.textarea.value=this.textarea.value.substring(0,r)+u+this.textarea.value.substring(i);if(s){this.textarea.setSelectionRange(r+u.length,r+u.length)}else{this.textarea.setSelectionRange(r+e.length,r+e.length)}this.textarea.scrollTop=o}},encloseSelection:function(e,t,n){this.textarea.focus();e=e||"";t=t||"";var r,i,s,o,u,a;if(typeof document["selection"]!="undefined"){s=document.selection.createRange().text}else if(typeof this.textarea["setSelectionRange"]!="undefined"){r=this.textarea.selectionStart;i=this.textarea.selectionEnd;o=this.textarea.scrollTop;s=this.textarea.value.substring(r,i)}if(s.match(/ $/)){s=s.substring(0,s.length-1);t=t+" "}if(typeof n=="function"){a=s?n.call(this,s):n("")}else{a=s?s:""}u=e+a+t;if(typeof document["selection"]!="undefined"){document.selection.createRange().text=u;var f=this.textarea.createTextRange();f.collapse(false);f.move("character",-t.length);f.select()}else if(typeof this.textarea["setSelectionRange"]!="undefined"){this.textarea.value=this.textarea.value.substring(0,r)+u+this.textarea.value.substring(i);if(s){this.textarea.setSelectionRange(r+u.length,r+u.length)}else{this.textarea.setSelectionRange(r+e.length,r+e.length)}this.textarea.scrollTop=o}},stripBaseURL:function(e){if(this.base_url!=""){var t=e.indexOf(this.base_url);if(t==0){e=e.substr(this.base_url.length)}}return e}};jsToolBar.prototype.resizeSetStartH=function(){this.dragStartH=this.textarea.offsetHeight+0};jsToolBar.prototype.resizeDragStart=function(e){var t=this;this.dragStartY=e.clientY;this.resizeSetStartH();document.addEventListener("mousemove",this.dragMoveHdlr=function(e){t.resizeDragMove(e)},false);document.addEventListener("mouseup",this.dragStopHdlr=function(e){t.resizeDragStop(e)},false)};jsToolBar.prototype.resizeDragMove=function(e){this.textarea.style.height=this.dragStartH+e.clientY-this.dragStartY+"px"};jsToolBar.prototype.resizeDragStop=function(e){document.removeEventListener("mousemove",this.dragMoveHdlr,false);document.removeEventListener("mouseup",this.dragStopHdlr,false)};jsToolBar.prototype.elements.strong={type:"button",title:"Strong",fn:{wiki:function(){this.singleTag("*")}}};jsToolBar.prototype.elements.em={type:"button",title:"Italic",fn:{wiki:function(){this.singleTag("_")}}};jsToolBar.prototype.elements.ins={type:"button",title:"Underline",fn:{wiki:function(){this.singleTag("+")}}};jsToolBar.prototype.elements.del={type:"button",title:"Deleted",fn:{wiki:function(){this.singleTag("-")}}};jsToolBar.prototype.elements.code={type:"button",title:"Code",fn:{wiki:function(){this.singleTag("@")}}};jsToolBar.prototype.elements.space1={type:"space"};jsToolBar.prototype.elements.h1={type:"button",title:"Heading 1",fn:{wiki:function(){this.encloseLineSelection("h1. ","",function(e){e=e.replace(/^h\d+\.\s+/,"");return e})}}};jsToolBar.prototype.elements.h2={type:"button",title:"Heading 2",fn:{wiki:function(){this.encloseLineSelection("h2. ","",function(e){e=e.replace(/^h\d+\.\s+/,"");return e})}}};jsToolBar.prototype.elements.h3={type:"button",title:"Heading 3",fn:{wiki:function(){this.encloseLineSelection("h3. ","",function(e){e=e.replace(/^h\d+\.\s+/,"");return e})}}};jsToolBar.prototype.elements.space2={type:"space"};jsToolBar.prototype.elements.ul={type:"button",title:"Unordered list",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^)[#-]?\s*/g,"$1* ")})}}};jsToolBar.prototype.elements.ol={type:"button",title:"Ordered list",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^)[*-]?\s*/g,"$1# ")})}}};jsToolBar.prototype.elements.space3={type:"space"};jsToolBar.prototype.elements.bq={type:"button",title:"Quote",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^) *([^\n]*)/g,"$1> $2")})}}};jsToolBar.prototype.elements.unbq={type:"button",title:"Unquote",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2")})}}};jsToolBar.prototype.elements.pre={type:"button",title:"Preformatted text",fn:{wiki:function(){this.encloseLineSelection("
      \n","\n
      ")}}};jsToolBar.prototype.elements.space4={type:"space"};jsToolBar.prototype.elements.link={type:"button",title:"Wiki link",fn:{wiki:function(){this.encloseSelection("[[","]]")}}};jsToolBar.prototype.elements.img={type:"button",title:"Image",fn:{wiki:function(){this.encloseSelection("!","!")}}} \ No newline at end of file +function jsToolBar(e){if(!document.createElement){return}if(!e){return}if(typeof document["selection"]=="undefined"&&typeof e["setSelectionRange"]=="undefined"){return}this.textarea=e;this.editor=document.createElement("div");this.editor.className="jstEditor";this.textarea.parentNode.insertBefore(this.editor,this.textarea);this.editor.appendChild(this.textarea);this.toolbar=document.createElement("div");this.toolbar.className="jstElements";this.editor.parentNode.insertBefore(this.toolbar,this.editor);if(this.editor.addEventListener&&navigator.appVersion.match(/\bMSIE\b/)){this.handle=document.createElement("div");this.handle.className="jstHandle";var t=this.resizeDragStart;var n=this;this.handle.addEventListener("mousedown",function(e){t.call(n,e)},false);window.addEventListener("unload",function(){var e=n.handle.parentNode.removeChild(n.handle);delete n.handle},false);this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling)}this.context=null;this.toolNodes={}}function jsButton(e,t,n,r){if(typeof jsToolBar.strings=="undefined"){this.title=e||null}else{this.title=jsToolBar.strings[e]||e||null}this.fn=t||function(){};this.scope=n||null;this.className=r||null}function jsSpace(e){this.id=e||null;this.width=null}function jsCombo(e,t,n,r,i){this.title=e||null;this.options=t||null;this.scope=n||null;this.fn=r||function(){};this.className=i||null}jsButton.prototype.draw=function(){if(!this.scope)return null;var e=document.createElement("button");e.setAttribute("type","button");e.tabIndex=200;if(this.className)e.className=this.className;e.title=this.title;var t=document.createElement("span");t.appendChild(document.createTextNode(this.title));e.appendChild(t);if(this.icon!=undefined){e.style.backgroundImage="url("+this.icon+")"}if(typeof this.fn=="function"){var n=this;e.onclick=function(){try{n.fn.apply(n.scope,arguments)}catch(e){}return false}}return e};jsSpace.prototype.draw=function(){var e=document.createElement("span");if(this.id)e.id=this.id;e.appendChild(document.createTextNode(String.fromCharCode(160)));e.className="jstSpacer";if(this.width)e.style.marginRight=this.width+"px";return e};jsCombo.prototype.draw=function(){if(!this.scope||!this.options)return null;var e=document.createElement("select");if(this.className)e.className=className;e.title=this.title;for(var t in this.options){var n=document.createElement("option");n.value=t;n.appendChild(document.createTextNode(this.options[t]));e.appendChild(n)}var r=this;e.onchange=function(){try{r.fn.call(r.scope,this.value)}catch(e){alert(e)}return false};return e};jsToolBar.prototype={base_url:"",mode:"wiki",elements:{},help_link:"",getMode:function(){return this.mode},setMode:function(e){this.mode=e||"wiki"},switchMode:function(e){e=e||"wiki";this.draw(e)},setHelpLink:function(e){this.help_link=e},button:function(e){var t=this.elements[e];if(typeof t.fn[this.mode]!="function")return null;var n=new jsButton(t.title,t.fn[this.mode],this,"jstb_"+e);if(t.icon!=undefined)n.icon=t.icon;return n},space:function(e){var t=new jsSpace(e);if(this.elements[e].width!==undefined)t.width=this.elements[e].width;return t},combo:function(e){var t=this.elements[e];var n=t[this.mode].list.length;if(typeof t[this.mode].fn!="function"||n==0){return null}else{var r={};for(var i=0;i $2")})}}};jsToolBar.prototype.elements.unbq={type:"button",title:"Unquote",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2")})}}};jsToolBar.prototype.elements.pre={type:"button",title:"Preformatted text",fn:{wiki:function(){this.encloseLineSelection("
      \n","\n
      ")}}};jsToolBar.prototype.elements.space4={type:"space"};jsToolBar.prototype.elements.link={type:"button",title:"Wiki link",fn:{wiki:function(){this.encloseSelection("[[","]]")}}};jsToolBar.prototype.elements.img={type:"button",title:"Image",fn:{wiki:function(){this.encloseSelection("!","!")}}};jsToolBar.prototype.elements.space5={type:"space"};jsToolBar.prototype.elements.help={type:"button",title:"Help",fn:{wiki:function(){window.open(this.help_link,"","resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes")}}} diff -r d98d22a98252 -r afce8026aaeb public/javascripts/jstoolbar/jstoolbar.js --- a/public/javascripts/jstoolbar/jstoolbar.js Wed May 07 14:15:02 2014 +0100 +++ b/public/javascripts/jstoolbar/jstoolbar.js Tue Sep 09 09:34:53 2014 +0100 @@ -207,12 +207,6 @@ } this.toolNodes = {}; // vide les raccourcis DOM/**/ - var h = document.createElement('div'); - h.className = 'help' - h.innerHTML = this.help_link; - 'Aide'; - this.toolbar.appendChild(h); - // Draw toolbar elements var b, tool, newTool; diff -r d98d22a98252 -r afce8026aaeb public/javascripts/jstoolbar/lang/jstoolbar-az.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/jstoolbar/lang/jstoolbar-az.js Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,16 @@ +jsToolBar.strings = {}; +jsToolBar.strings['Strong'] = 'Strong'; +jsToolBar.strings['Italic'] = 'Italic'; +jsToolBar.strings['Underline'] = 'Underline'; +jsToolBar.strings['Deleted'] = 'Deleted'; +jsToolBar.strings['Code'] = 'Inline Code'; +jsToolBar.strings['Heading 1'] = 'Heading 1'; +jsToolBar.strings['Heading 2'] = 'Heading 2'; +jsToolBar.strings['Heading 3'] = 'Heading 3'; +jsToolBar.strings['Unordered list'] = 'Unordered list'; +jsToolBar.strings['Ordered list'] = 'Ordered list'; +jsToolBar.strings['Quote'] = 'Quote'; +jsToolBar.strings['Unquote'] = 'Remove Quote'; +jsToolBar.strings['Preformatted text'] = 'Preformatted text'; +jsToolBar.strings['Wiki link'] = 'Link to a Wiki page'; +jsToolBar.strings['Image'] = 'Image'; diff -r d98d22a98252 -r afce8026aaeb public/javascripts/jstoolbar/lang/jstoolbar-lt.js --- a/public/javascripts/jstoolbar/lang/jstoolbar-lt.js Wed May 07 14:15:02 2014 +0100 +++ b/public/javascripts/jstoolbar/lang/jstoolbar-lt.js Tue Sep 09 09:34:53 2014 +0100 @@ -9,8 +9,8 @@ jsToolBar.strings['Heading 3'] = 'Heading 3'; jsToolBar.strings['Unordered list'] = 'Nenumeruotas sÄ…raÅ¡as'; jsToolBar.strings['Ordered list'] = 'Numeruotas sÄ…raÅ¡as'; -jsToolBar.strings['Quote'] = 'Quote'; -jsToolBar.strings['Unquote'] = 'Remove Quote'; +jsToolBar.strings['Quote'] = 'Cituoti'; +jsToolBar.strings['Unquote'] = 'PaÅ¡alinti citavimÄ…'; jsToolBar.strings['Preformatted text'] = 'Preformatuotas tekstas'; jsToolBar.strings['Wiki link'] = 'Nuoroda į Wiki puslapį'; jsToolBar.strings['Image'] = 'Paveikslas'; diff -r d98d22a98252 -r afce8026aaeb public/javascripts/jstoolbar/lang/jstoolbar-sk.js --- a/public/javascripts/jstoolbar/lang/jstoolbar-sk.js Wed May 07 14:15:02 2014 +0100 +++ b/public/javascripts/jstoolbar/lang/jstoolbar-sk.js Tue Sep 09 09:34:53 2014 +0100 @@ -4,13 +4,13 @@ jsToolBar.strings['Underline'] = 'PodÄiarknuté'; jsToolBar.strings['Deleted'] = 'PreÅ¡krtnuté'; jsToolBar.strings['Code'] = 'Zobrazenie kódu'; -jsToolBar.strings['Heading 1'] = 'Záhlavie 1'; -jsToolBar.strings['Heading 2'] = 'Záhlavie 2'; -jsToolBar.strings['Heading 3'] = 'Záhlavie 3'; -jsToolBar.strings['Unordered list'] = 'Zoznam'; -jsToolBar.strings['Ordered list'] = 'Zoradený zoznam'; -jsToolBar.strings['Quote'] = 'Citácia'; -jsToolBar.strings['Unquote'] = 'Odstránenie citácie'; +jsToolBar.strings['Heading 1'] = 'Nadpis 1'; +jsToolBar.strings['Heading 2'] = 'Nadpis 2'; +jsToolBar.strings['Heading 3'] = 'Nadpis 3'; +jsToolBar.strings['Unordered list'] = 'Odrážkový zoznam'; +jsToolBar.strings['Ordered list'] = 'Číslovaný zoznam'; +jsToolBar.strings['Quote'] = 'Odsadenie'; +jsToolBar.strings['Unquote'] = 'ZruÅ¡iÅ¥ odsadenie'; jsToolBar.strings['Preformatted text'] = 'Predformátovaný text'; -jsToolBar.strings['Wiki link'] = 'Link na Wiki stránku'; +jsToolBar.strings['Wiki link'] = 'Odkaz na wikistránku'; jsToolBar.strings['Image'] = 'Obrázok'; diff -r d98d22a98252 -r afce8026aaeb public/javascripts/jstoolbar/textile.js --- a/public/javascripts/jstoolbar/textile.js Wed May 07 14:15:02 2014 +0100 +++ b/public/javascripts/jstoolbar/textile.js Tue Sep 09 09:34:53 2014 +0100 @@ -198,3 +198,14 @@ wiki: function() { this.encloseSelection("!", "!") } } } + +// spacer +jsToolBar.prototype.elements.space5 = {type: 'space'} +// help +jsToolBar.prototype.elements.help = { + type: 'button', + title: 'Help', + fn: { + wiki: function() { window.open(this.help_link, '', 'resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes') } + } +} diff -r d98d22a98252 -r afce8026aaeb public/javascripts/project_identifier.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/project_identifier.js Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,78 @@ +// Automatic project identifier generation + +function generateProjectIdentifier(identifier, maxlength) { + var diacriticsMap = [ + {'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g}, + {'base':'aa','letters':/[\uA733\uA732]/g}, + {'base':'ae','letters':/[\u00E4\u00E6\u01FD\u01E3\u00C4\u00C6\u01FC\u01E2]/g}, + {'base':'ao','letters':/[\uA735\uA734]/g}, + {'base':'au','letters':/[\uA737\uA736]/g}, + {'base':'av','letters':/[\uA739\uA73B\uA738\uA73A]/g}, + {'base':'ay','letters':/[\uA73D\uA73C]/g}, + {'base':'b', 'letters':/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g}, + {'base':'c', 'letters':/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g}, + {'base':'d', 'letters':/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g}, + {'base':'dz','letters':/[\u01F3\u01C6\u01F1\u01C4\u01F2\u01C5]/g}, + {'base':'e', 'letters':/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g}, + {'base':'f', 'letters':/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g}, + {'base':'g', 'letters':/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g}, + {'base':'h', 'letters':/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g}, + {'base':'hv','letters':/[\u0195]/g}, + {'base':'i', 'letters':/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g}, + {'base':'j', 'letters':/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249\u004A\u24BF\uFF2A\u0134\u0248]/g}, + {'base':'k', 'letters':/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g}, + {'base':'l', 'letters':/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g}, + {'base':'lj','letters':/[\u01C9\u01C7\u01C8]/g}, + {'base':'m', 'letters':/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g}, + {'base':'n', 'letters':/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g}, + {'base':'nj','letters':/[\u01CC\u01CA\u01CB]/g}, + {'base':'o', 'letters':/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g}, + {'base':'oe','letters': /[\u00F6\u0153\u00D6\u0152]/g}, + {'base':'oi','letters':/[\u01A3\u01A2]/g}, + {'base':'ou','letters':/[\u0223\u0222]/g}, + {'base':'oo','letters':/[\uA74F\uA74E]/g}, + {'base':'p','letters':/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g}, + {'base':'q','letters':/[\u0071\u24E0\uFF51\u024B\uA757\uA759\u0051\u24C6\uFF31\uA756\uA758\u024A]/g}, + {'base':'r','letters':/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g}, + {'base':'s','letters':/[\u0073\u24E2\uFF53\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g}, + {'base':'ss','letters':/[\u00DF]/g}, + {'base':'t','letters':/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g}, + {'base':'tz','letters':/[\uA729\uA728]/g}, + {'base':'u','letters':/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g}, + {'base':'ue','letters':/[\u00FC\u00DC]/g}, + {'base':'v','letters':/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g}, + {'base':'vy','letters':/[\uA761\uA760]/g}, + {'base':'w','letters':/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g}, + {'base':'x','letters':/[\u0078\u24E7\uFF58\u1E8B\u1E8D\u0058\u24CD\uFF38\u1E8A\u1E8C]/g}, + {'base':'y','letters':/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g}, + {'base':'z','letters':/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g} + ]; + + for(var i=0; i hyphen + identifier = identifier.replace(/^[-_\d]*|[-_]*$/g, ''); // remove hyphens/underscores and numbers at beginning and hyphens/underscores at end + identifier = identifier.toLowerCase(); // to lower + identifier = identifier.substr(0, maxlength); // max characters + return identifier; +} + +function autoFillProjectIdentifier() { + var locked = ($('#project_identifier').val() != ''); + var maxlength = parseInt($('#project_identifier').attr('maxlength')); + + $('#project_name').keyup(function(){ + if(!locked) { + $('#project_identifier').val(generateProjectIdentifier($('#project_name').val(), maxlength)); + } + }); + + $('#project_identifier').keyup(function(){ + locked = ($('#project_identifier').val() != '' && $('#project_identifier').val() != generateProjectIdentifier($('#project_name').val(), maxlength)); + }); +} + +$(document).ready(function(){ + autoFillProjectIdentifier(); +}); diff -r d98d22a98252 -r afce8026aaeb public/javascripts/revision_graph.js --- a/public/javascripts/revision_graph.js Wed May 07 14:15:02 2014 +0100 +++ b/public/javascripts/revision_graph.js Tue Sep 09 09:34:53 2014 +0100 @@ -43,7 +43,7 @@ revisionGraph.circle(x, y, 3) .attr({ fill: colors[commit.space], - stroke: 'none', + stroke: 'none' }).toFront(); // paths to parents $.each(commit.parent_scmids, function(index, parent_scmid) { diff -r d98d22a98252 -r afce8026aaeb public/javascripts/select_list_move.js --- a/public/javascripts/select_list_move.js Wed May 07 14:15:02 2014 +0100 +++ b/public/javascripts/select_list_move.js Tue Sep 09 09:34:53 2014 +0100 @@ -1,14 +1,12 @@ var NS4 = (navigator.appName == "Netscape" && parseInt(navigator.appVersion) < 5); -function addOption(theSel, theText, theValue) -{ +function addOption(theSel, theText, theValue) { var newOpt = new Option(theText, theValue); var selLength = theSel.length; theSel.options[selLength] = newOpt; } -function swapOptions(theSel, index1, index2) -{ +function swapOptions(theSel, index1, index2) { var text, value; text = theSel.options[index1].text; value = theSel.options[index1].value; @@ -18,42 +16,31 @@ theSel.options[index2].value = value; } -function deleteOption(theSel, theIndex) -{ +function deleteOption(theSel, theIndex) { var selLength = theSel.length; - if(selLength>0) - { + if (selLength > 0) { theSel.options[theIndex] = null; } } -function moveOptions(theSelFrom, theSelTo) -{ - +function moveOptions(theSelFrom, theSelTo) { var selLength = theSelFrom.length; var selectedText = new Array(); var selectedValues = new Array(); var selectedCount = 0; - var i; - - for(i=selLength-1; i>=0; i--) - { - if(theSelFrom.options[i].selected) - { + for (i = selLength - 1; i >= 0; i--) { + if (theSelFrom.options[i].selected) { selectedText[selectedCount] = theSelFrom.options[i].text; selectedValues[selectedCount] = theSelFrom.options[i].value; deleteOption(theSelFrom, i); selectedCount++; } } - - for(i=selectedCount-1; i>=0; i--) - { + for (i = selectedCount - 1; i >= 0; i--) { addOption(theSelTo, selectedText[i], selectedValues[i]); } - - if(NS4) history.go(0); + if (NS4) history.go(0); } function moveOptionUp(theSel) { @@ -73,11 +60,7 @@ } // OK -function selectAllOptions(id) -{ - var select = $('#'+id);/* - for (var i=0; ilegend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; } +fieldset.collapsible.collapsed>legend { background-image: url(../images/arrow_collapsed.png); } fieldset#date-range p { margin: 2px 0 2px 0; } fieldset#filters table { border-collapse: collapse; } @@ -388,6 +393,8 @@ div#activity dd .description, #search-results dd .description { font-style: italic; } div#activity span.project:after, div#news span.project:after, #search-results span.project:after { content: " -"; } div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; } +div#activity dt.grouped {margin-left:5em;} +div#activity dd.grouped {margin-left:9em;} div#members dl { margin-left: 2em; } div#members dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; } @@ -480,7 +487,7 @@ /* .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; } */ -#notified-projects ul, #tracker_project_ids ul {max-height:250px; overflow-y:auto;} +#notified-projects>ul, #tracker_project_ids>ul, #custom_field_project_ids>ul {max-height:250px; overflow-y:auto;} #related-issues li img {vertical-align:middle;} @@ -501,10 +508,11 @@ table.fields_permissions td.required {background:#d88;} textarea#custom_field_possible_values {width: 99%} +textarea#custom_field_default_value {width: 99%} + input#content_comments {width: 99%} -.pagination {font-size: 90%} -p.pagination {margin-top:8px;} +p.pagination {margin-top:8px; font-size: 90%} /***** Tabular forms ******/ .tabular p{ @@ -586,11 +594,19 @@ span.required {color: #bb0000;} .summary {font-style: italic;} -#attachments_fields input.description {margin-left: 8px; width:340px;} +#attachments_fields input.description {margin-left:4px; width:340px;} #attachments_fields span {display:block; white-space:nowrap;} +#attachments_fields input.filename {border:0; height:1.8em; width:250px; color:#555; background-color:inherit; background:url(../images/attachment.png) no-repeat 1px 50%; padding-left:18px;} +#attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;} +#attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;} +#attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; } #attachments_fields input[type=text] {margin-left: 8px; } +#attachments_fields img {vertical-align: middle;} -#attachments_fields img {vertical-align: middle;} +a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;} +a.remove-upload:hover {text-decoration:none !important;} + +div.fileover { background-color: lavender; } div.attachments { margin-top: 12px; } div.attachments p { margin:4px 0 2px 0; } @@ -612,29 +628,33 @@ textarea.text_cf {width:90%;} -/* Project members tab */ -div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% } -div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% } -div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; } -div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; } -div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; } -div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; } +#tab-content-modules fieldset p {margin:3px 0 4px 0;} + +#tab-content-members .splitcontentleft, #tab-content-memberships .splitcontentleft, #tab-content-users .splitcontentleft {width: 64%;} +#tab-content-members .splitcontentright, #tab-content-memberships .splitcontentright, #tab-content-users .splitcontentright {width: 34%;} +#tab-content-members fieldset, #tab-content-memberships fieldset, #tab-content-users fieldset {padding:1em; margin-bottom: 1em;} +#tab-content-members fieldset legend, #tab-content-memberships fieldset legend, #tab-content-users fieldset legend {font-weight: bold;} +#tab-content-members fieldset label, #tab-content-memberships fieldset label, #tab-content-users fieldset label {display: block;} +#tab-content-members #principals, #tab-content-users #principals {max-height: 400px; overflow: auto;} + +#tab-content-memberships .splitcontentright select {width:90%} #users_for_watcher {height: 200px; overflow:auto;} #users_for_watcher label {display: block;} table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; } -input#principal_search, input#user_search {width:100%} -input#principal_search, input#user_search { - background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px; - border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%; +input#principal_search, input#user_search {width:90%} + +input.autocomplete { + background: #fff url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px !important; + border:1px solid #9EB1C2; border-radius:2px; height:1.5em; } -input#principal_search.ajax-loading, input#user_search.ajax-loading { +input.autocomplete.ajax-loading { background-image: url(../images/loading.gif); } -* html div#tab-content-members fieldset div { height: 450px; } +.role-visibility {padding-left:2em;} /***** Flash & error messages ****/ #errorExplanation, div.flash, .nodata, .warning, .conflict { @@ -757,7 +777,7 @@ table.progress td.closed { background: #BAE0BA none repeat scroll 0%; } table.progress td.done { background: #D3EDD3 none repeat scroll 0%; } table.progress td.todo { background: #eee none repeat scroll 0%; } -p.pourcent {font-size: 80%;} +p.percent {font-size: 80%;} p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;} #roadmap table.progress td { height: 1.2em; } @@ -1113,6 +1133,10 @@ .hascontextmenu { cursor: context-menu; } +/* Custom JQuery styles */ +.ui-datepicker-title select {width:70px !important; margin-top:-2px !important; margin-right:4px !important;} + + /************* CodeRay styles *************/ .syntaxhl div {display: inline;} .syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;} @@ -1188,13 +1212,13 @@ .syntaxhl .type { color:#339; font-weight:bold } .syntaxhl .value { color: #088; } .syntaxhl .variable { color:#037 } - + .syntaxhl .insert { background: hsla(120,100%,50%,0.12) } .syntaxhl .delete { background: hsla(0,100%,50%,0.12) } .syntaxhl .change { color: #bbf; background: #007; } .syntaxhl .head { color: #f8f; background: #505 } .syntaxhl .head .filename { color: white; } - + .syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; } .syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; } diff -r d98d22a98252 -r afce8026aaeb public/stylesheets/jquery/images/ui-bg_gloss-wave_35_759fcf_500x100.png Binary file public/stylesheets/jquery/images/ui-bg_gloss-wave_35_759fcf_500x100.png has changed diff -r d98d22a98252 -r afce8026aaeb public/stylesheets/jquery/jquery-ui-1.8.21.css --- a/public/stylesheets/jquery/jquery-ui-1.8.21.css Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,563 +0,0 @@ -/*! - * jQuery UI CSS Framework 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Theming/API - */ - -/* Layout helpers -----------------------------------*/ -.ui-helper-hidden { display: none; } -.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } -.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } -.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; } -.ui-helper-clearfix:after { clear: both; } -.ui-helper-clearfix { zoom: 1; } -.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } - - -/* Interaction Cues -----------------------------------*/ -.ui-state-disabled { cursor: default !important; } - - -/* Icons -----------------------------------*/ - -/* states and images */ -.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } - - -/* Misc visuals -----------------------------------*/ - -/* Overlays */ -.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } - - -/*! - * jQuery UI CSS Framework 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Theming/API - * - * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=759fcf&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=628db6&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=628db6&iconColorDefault=759fcf&bgColorHover=eef5fd&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=628db6&fcHover=628db6&iconColorHover=759fcf&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=628db6&fcActive=628db6&iconColorActive=759fcf&bgColorHighlight=759fcf&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=628db6&fcHighlight=363636&iconColorHighlight=759fcf&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px - */ - - -/* Component containers -----------------------------------*/ -.ui-widget { font-family: Verdana, sans-serif; font-size: 1.1em; } -.ui-widget .ui-widget { font-size: 1em; } -.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana, sans-serif; font-size: 1em; } -.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; } -.ui-widget-content a { color: #333333; } -.ui-widget-header { border: 1px solid #628db6; background: #759fcf url(images/ui-bg_gloss-wave_35_759fcf_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } -.ui-widget-header a { color: #ffffff; } - -/* Interaction states -----------------------------------*/ -.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #628db6; } -.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #628db6; text-decoration: none; } -.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #628db6; background: #eef5fd url(images/ui-bg_glass_100_eef5fd_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #628db6; } -.ui-state-hover a, .ui-state-hover a:hover { color: #628db6; text-decoration: none; } -.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #628db6; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #628db6; } -.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #628db6; text-decoration: none; } -.ui-widget :active { outline: none; } - -/* Interaction Cues -----------------------------------*/ -.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #628db6; background: #759fcf url(images/ui-bg_highlight-soft_75_759fcf_1x100.png) 50% top repeat-x; color: #363636; } -.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } -.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; } -.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; } -.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; } -.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } -.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } -.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } - -/* Icons -----------------------------------*/ - -/* states and images */ -.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } -.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } -.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } -.ui-state-default .ui-icon { background-image: url(images/ui-icons_759fcf_256x240.png); } -.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_759fcf_256x240.png); } -.ui-state-active .ui-icon {background-image: url(images/ui-icons_759fcf_256x240.png); } -.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_759fcf_256x240.png); } -.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); } - -/* positioning */ -.ui-icon-carat-1-n { background-position: 0 0; } -.ui-icon-carat-1-ne { background-position: -16px 0; } -.ui-icon-carat-1-e { background-position: -32px 0; } -.ui-icon-carat-1-se { background-position: -48px 0; } -.ui-icon-carat-1-s { background-position: -64px 0; } -.ui-icon-carat-1-sw { background-position: -80px 0; } -.ui-icon-carat-1-w { background-position: -96px 0; } -.ui-icon-carat-1-nw { background-position: -112px 0; } -.ui-icon-carat-2-n-s { background-position: -128px 0; } -.ui-icon-carat-2-e-w { background-position: -144px 0; } -.ui-icon-triangle-1-n { background-position: 0 -16px; } -.ui-icon-triangle-1-ne { background-position: -16px -16px; } -.ui-icon-triangle-1-e { background-position: -32px -16px; } -.ui-icon-triangle-1-se { background-position: -48px -16px; } -.ui-icon-triangle-1-s { background-position: -64px -16px; } -.ui-icon-triangle-1-sw { background-position: -80px -16px; } -.ui-icon-triangle-1-w { background-position: -96px -16px; } -.ui-icon-triangle-1-nw { background-position: -112px -16px; } -.ui-icon-triangle-2-n-s { background-position: -128px -16px; } -.ui-icon-triangle-2-e-w { background-position: -144px -16px; } -.ui-icon-arrow-1-n { background-position: 0 -32px; } -.ui-icon-arrow-1-ne { background-position: -16px -32px; } -.ui-icon-arrow-1-e { background-position: -32px -32px; } -.ui-icon-arrow-1-se { background-position: -48px -32px; } -.ui-icon-arrow-1-s { background-position: -64px -32px; } -.ui-icon-arrow-1-sw { background-position: -80px -32px; } -.ui-icon-arrow-1-w { background-position: -96px -32px; } -.ui-icon-arrow-1-nw { background-position: -112px -32px; } -.ui-icon-arrow-2-n-s { background-position: -128px -32px; } -.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } -.ui-icon-arrow-2-e-w { background-position: -160px -32px; } -.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } -.ui-icon-arrowstop-1-n { background-position: -192px -32px; } -.ui-icon-arrowstop-1-e { background-position: -208px -32px; } -.ui-icon-arrowstop-1-s { background-position: -224px -32px; } -.ui-icon-arrowstop-1-w { background-position: -240px -32px; } -.ui-icon-arrowthick-1-n { background-position: 0 -48px; } -.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } -.ui-icon-arrowthick-1-e { background-position: -32px -48px; } -.ui-icon-arrowthick-1-se { background-position: -48px -48px; } -.ui-icon-arrowthick-1-s { background-position: -64px -48px; } -.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } -.ui-icon-arrowthick-1-w { background-position: -96px -48px; } -.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } -.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } -.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } -.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } -.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } -.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } -.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } -.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } -.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } -.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } -.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } -.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } -.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } -.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } -.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } -.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } -.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } -.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } -.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } -.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } -.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } -.ui-icon-arrow-4 { background-position: 0 -80px; } -.ui-icon-arrow-4-diag { background-position: -16px -80px; } -.ui-icon-extlink { background-position: -32px -80px; } -.ui-icon-newwin { background-position: -48px -80px; } -.ui-icon-refresh { background-position: -64px -80px; } -.ui-icon-shuffle { background-position: -80px -80px; } -.ui-icon-transfer-e-w { background-position: -96px -80px; } -.ui-icon-transferthick-e-w { background-position: -112px -80px; } -.ui-icon-folder-collapsed { background-position: 0 -96px; } -.ui-icon-folder-open { background-position: -16px -96px; } -.ui-icon-document { background-position: -32px -96px; } -.ui-icon-document-b { background-position: -48px -96px; } -.ui-icon-note { background-position: -64px -96px; } -.ui-icon-mail-closed { background-position: -80px -96px; } -.ui-icon-mail-open { background-position: -96px -96px; } -.ui-icon-suitcase { background-position: -112px -96px; } -.ui-icon-comment { background-position: -128px -96px; } -.ui-icon-person { background-position: -144px -96px; } -.ui-icon-print { background-position: -160px -96px; } -.ui-icon-trash { background-position: -176px -96px; } -.ui-icon-locked { background-position: -192px -96px; } -.ui-icon-unlocked { background-position: -208px -96px; } -.ui-icon-bookmark { background-position: -224px -96px; } -.ui-icon-tag { background-position: -240px -96px; } -.ui-icon-home { background-position: 0 -112px; } -.ui-icon-flag { background-position: -16px -112px; } -.ui-icon-calendar { background-position: -32px -112px; } -.ui-icon-cart { background-position: -48px -112px; } -.ui-icon-pencil { background-position: -64px -112px; } -.ui-icon-clock { background-position: -80px -112px; } -.ui-icon-disk { background-position: -96px -112px; } -.ui-icon-calculator { background-position: -112px -112px; } -.ui-icon-zoomin { background-position: -128px -112px; } -.ui-icon-zoomout { background-position: -144px -112px; } -.ui-icon-search { background-position: -160px -112px; } -.ui-icon-wrench { background-position: -176px -112px; } -.ui-icon-gear { background-position: -192px -112px; } -.ui-icon-heart { background-position: -208px -112px; } -.ui-icon-star { background-position: -224px -112px; } -.ui-icon-link { background-position: -240px -112px; } -.ui-icon-cancel { background-position: 0 -128px; } -.ui-icon-plus { background-position: -16px -128px; } -.ui-icon-plusthick { background-position: -32px -128px; } -.ui-icon-minus { background-position: -48px -128px; } -.ui-icon-minusthick { background-position: -64px -128px; } -.ui-icon-close { background-position: -80px -128px; } -.ui-icon-closethick { background-position: -96px -128px; } -.ui-icon-key { background-position: -112px -128px; } -.ui-icon-lightbulb { background-position: -128px -128px; } -.ui-icon-scissors { background-position: -144px -128px; } -.ui-icon-clipboard { background-position: -160px -128px; } -.ui-icon-copy { background-position: -176px -128px; } -.ui-icon-contact { background-position: -192px -128px; } -.ui-icon-image { background-position: -208px -128px; } -.ui-icon-video { background-position: -224px -128px; } -.ui-icon-script { background-position: -240px -128px; } -.ui-icon-alert { background-position: 0 -144px; } -.ui-icon-info { background-position: -16px -144px; } -.ui-icon-notice { background-position: -32px -144px; } -.ui-icon-help { background-position: -48px -144px; } -.ui-icon-check { background-position: -64px -144px; } -.ui-icon-bullet { background-position: -80px -144px; } -.ui-icon-radio-off { background-position: -96px -144px; } -.ui-icon-radio-on { background-position: -112px -144px; } -.ui-icon-pin-w { background-position: -128px -144px; } -.ui-icon-pin-s { background-position: -144px -144px; } -.ui-icon-play { background-position: 0 -160px; } -.ui-icon-pause { background-position: -16px -160px; } -.ui-icon-seek-next { background-position: -32px -160px; } -.ui-icon-seek-prev { background-position: -48px -160px; } -.ui-icon-seek-end { background-position: -64px -160px; } -.ui-icon-seek-start { background-position: -80px -160px; } -/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ -.ui-icon-seek-first { background-position: -80px -160px; } -.ui-icon-stop { background-position: -96px -160px; } -.ui-icon-eject { background-position: -112px -160px; } -.ui-icon-volume-off { background-position: -128px -160px; } -.ui-icon-volume-on { background-position: -144px -160px; } -.ui-icon-power { background-position: 0 -176px; } -.ui-icon-signal-diag { background-position: -16px -176px; } -.ui-icon-signal { background-position: -32px -176px; } -.ui-icon-battery-0 { background-position: -48px -176px; } -.ui-icon-battery-1 { background-position: -64px -176px; } -.ui-icon-battery-2 { background-position: -80px -176px; } -.ui-icon-battery-3 { background-position: -96px -176px; } -.ui-icon-circle-plus { background-position: 0 -192px; } -.ui-icon-circle-minus { background-position: -16px -192px; } -.ui-icon-circle-close { background-position: -32px -192px; } -.ui-icon-circle-triangle-e { background-position: -48px -192px; } -.ui-icon-circle-triangle-s { background-position: -64px -192px; } -.ui-icon-circle-triangle-w { background-position: -80px -192px; } -.ui-icon-circle-triangle-n { background-position: -96px -192px; } -.ui-icon-circle-arrow-e { background-position: -112px -192px; } -.ui-icon-circle-arrow-s { background-position: -128px -192px; } -.ui-icon-circle-arrow-w { background-position: -144px -192px; } -.ui-icon-circle-arrow-n { background-position: -160px -192px; } -.ui-icon-circle-zoomin { background-position: -176px -192px; } -.ui-icon-circle-zoomout { background-position: -192px -192px; } -.ui-icon-circle-check { background-position: -208px -192px; } -.ui-icon-circlesmall-plus { background-position: 0 -208px; } -.ui-icon-circlesmall-minus { background-position: -16px -208px; } -.ui-icon-circlesmall-close { background-position: -32px -208px; } -.ui-icon-squaresmall-plus { background-position: -48px -208px; } -.ui-icon-squaresmall-minus { background-position: -64px -208px; } -.ui-icon-squaresmall-close { background-position: -80px -208px; } -.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } -.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } -.ui-icon-grip-solid-vertical { background-position: -32px -224px; } -.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } -.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } -.ui-icon-grip-diagonal-se { background-position: -80px -224px; } - - -/* Misc visuals -----------------------------------*/ - -/* Corner radius */ -.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; } -.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; } -.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } -.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } - -/* Overlays */ -.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); } -.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*! - * jQuery UI Resizable 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Resizable#theming - */ -.ui-resizable { position: relative;} -.ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; } -.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } -.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } -.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } -.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } -.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } -.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } -.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } -.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } -.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*! - * jQuery UI Selectable 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Selectable#theming - */ -.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } -/*! - * jQuery UI Accordion 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Accordion#theming - */ -/* IE/Win - Fix animation bug - #4615 */ -.ui-accordion { width: 100%; } -.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } -.ui-accordion .ui-accordion-li-fix { display: inline; } -.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } -.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } -.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } -.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } -.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } -.ui-accordion .ui-accordion-content-active { display: block; } -/*! - * jQuery UI Autocomplete 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Autocomplete#theming - */ -.ui-autocomplete { position: absolute; cursor: default; } - -/* workarounds */ -* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ - -/* - * jQuery UI Menu 1.8.22 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Menu#theming - */ -.ui-menu { - list-style:none; - padding: 2px; - margin: 0; - display:block; - float: left; -} -.ui-menu .ui-menu { - margin-top: -3px; -} -.ui-menu .ui-menu-item { - margin:0; - padding: 0; - zoom: 1; - float: left; - clear: left; - width: 100%; -} -.ui-menu .ui-menu-item a { - text-decoration:none; - display:block; - padding:.2em .4em; - line-height:1.5; - zoom:1; -} -.ui-menu .ui-menu-item a.ui-state-hover, -.ui-menu .ui-menu-item a.ui-state-active { - font-weight: normal; - margin: -1px; -} -/*! - * jQuery UI Button 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Button#theming - */ -.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ -.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ -button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ -.ui-button-icons-only { width: 3.4em; } -button.ui-button-icons-only { width: 3.7em; } - -/*button text element */ -.ui-button .ui-button-text { display: block; line-height: 1.4; } -.ui-button-text-only .ui-button-text { padding: .4em 1em; } -.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } -.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } -.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } -.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } -/* no icon support for input elements, provide padding by default */ -input.ui-button { padding: .4em 1em; } - -/*button icon element(s) */ -.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } -.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } -.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } -.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } -.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } - -/*button sets*/ -.ui-buttonset { margin-right: 7px; } -.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } - -/* workarounds */ -button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ -/*! - * jQuery UI Dialog 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Dialog#theming - */ -.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } -.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; } -.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } -.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } -.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } -.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } -.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } -.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } -.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } -.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } -.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } -.ui-draggable .ui-dialog-titlebar { cursor: move; } -/*! - * jQuery UI Slider 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Slider#theming - */ -.ui-slider { position: relative; text-align: left; } -.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } -.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } - -.ui-slider-horizontal { height: .8em; } -.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } -.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } -.ui-slider-horizontal .ui-slider-range-min { left: 0; } -.ui-slider-horizontal .ui-slider-range-max { right: 0; } - -.ui-slider-vertical { width: .8em; height: 100px; } -.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } -.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } -.ui-slider-vertical .ui-slider-range-min { bottom: 0; } -.ui-slider-vertical .ui-slider-range-max { top: 0; }/*! - * jQuery UI Tabs 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Tabs#theming - */ -.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ -.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } -.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } -.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } -.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } -.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } -.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ -.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } -.ui-tabs .ui-tabs-hide { display: none !important; } -/*! - * jQuery UI Datepicker 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Datepicker#theming - */ -.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } -.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } -.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } -.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } -.ui-datepicker .ui-datepicker-prev { left:2px; } -.ui-datepicker .ui-datepicker-next { right:2px; } -.ui-datepicker .ui-datepicker-prev-hover { left:1px; } -.ui-datepicker .ui-datepicker-next-hover { right:1px; } -.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } -.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } -.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } -.ui-datepicker select.ui-datepicker-month-year {width: 100%;} -.ui-datepicker select.ui-datepicker-month, -.ui-datepicker select.ui-datepicker-year { width: 49%;} -.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } -.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } -.ui-datepicker td { border: 0; padding: 1px; } -.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } -.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } -.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } -.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } - -/* with multiple calendars */ -.ui-datepicker.ui-datepicker-multi { width:auto; } -.ui-datepicker-multi .ui-datepicker-group { float:left; } -.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } -.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } -.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } -.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } -.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } -.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } -.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } -.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; } - -/* RTL support */ -.ui-datepicker-rtl { direction: rtl; } -.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } -.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } -.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } -.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } -.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } -.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } -.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } -.ui-datepicker-rtl .ui-datepicker-group { float:right; } -.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } -.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } - -/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ -.ui-datepicker-cover { - position: absolute; /*must have*/ - z-index: -1; /*must have*/ - filter: mask(); /*must have*/ - top: -4px; /*must have*/ - left: -4px; /*must have*/ - width: 200px; /*must have*/ - height: 200px; /*must have*/ -}/*! - * jQuery UI Progressbar 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Progressbar#theming - */ -.ui-progressbar { height:2em; text-align: left; overflow: hidden; } -.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } diff -r d98d22a98252 -r afce8026aaeb public/stylesheets/jquery/jquery-ui-1.9.2.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/stylesheets/jquery/jquery-ui-1.9.2.css Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,5 @@ +/*! jQuery UI - v1.9.2 - 2012-12-26 +* http://jqueryui.com +* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2C%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=759fcf&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=628db6&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=628db6&iconColorDefault=759fcf&bgColorHover=eef5fd&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=628db6&fcHover=628db6&iconColorHover=759fcf&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=628db6&fcActive=628db6&iconColorActive=759fcf&bgColorHighlight=759fcf&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=628db6&fcHighlight=363636&iconColorHighlight=759fcf&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px +* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{zoom:1}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;zoom:1}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto;zoom:1}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}* html .ui-autocomplete{width:1px}.ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0em}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:right}.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-cover{position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .1em 0}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;width:100%}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;zoom:1;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em;zoom:1}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-tabs-loading a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}* html .ui-tooltip{background-image:none}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #628db6;background:#759fcf url(images/ui-bg_gloss-wave_35_759fcf_500x100.png) 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#628db6;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #628db6;background:#eef5fd url(images/ui-bg_glass_100_eef5fd_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited{color:#628db6;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #628db6;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#628db6;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #628db6;background:#759fcf url(images/ui-bg_highlight-soft_75_759fcf_1x100.png) 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px;background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_ffffff_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_ffd27a_256x240.png)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;-khtml-border-top-left-radius:4px;border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;-khtml-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;-khtml-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;-khtml-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);-moz-border-radius:5px;-khtml-border-radius:5px;-webkit-border-radius:5px;border-radius:5px} diff -r d98d22a98252 -r afce8026aaeb public/stylesheets/jstoolbar.css --- a/public/stylesheets/jstoolbar.css Wed May 07 14:15:02 2014 +0100 +++ b/public/stylesheets/jstoolbar.css Tue Sep 09 09:34:53 2014 +0100 @@ -95,3 +95,6 @@ .jstb_img { background-image: url(../images/jstoolbar/bt_img.png); } +.jstb_help { + background-image: url(../images/help.png); +} diff -r d98d22a98252 -r afce8026aaeb public/stylesheets/scm.css --- a/public/stylesheets/scm.css Wed May 07 14:15:02 2014 +0100 +++ b/public/stylesheets/scm.css Tue Sep 09 09:34:53 2014 +0100 @@ -64,6 +64,9 @@ font-family:"SourceCodePro-Regular","Liberation Mono", Courier, monospace; font-size:12px; } +table.filecontent tr:target th.line-num { background-color:#E0E0E0; color: #777; } +table.filecontent tr:target td.line-code { background-color:#DDEEFF; } + /* 12 different colors for the annonate view */ table.annotate tr.bloc-0 {background: #FFFFBF;} table.annotate tr.bloc-1 {background: #EABFFF;} diff -r d98d22a98252 -r afce8026aaeb test/extra/redmine_pm/repository_git_test.rb --- a/test/extra/redmine_pm/repository_git_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/extra/redmine_pm/repository_git_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/extra/redmine_pm/repository_subversion_test.rb --- a/test/extra/redmine_pm/repository_subversion_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/extra/redmine_pm/repository_subversion_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/extra/redmine_pm/test_case.rb --- a/test/extra/redmine_pm/test_case.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/extra/redmine_pm/test_case.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/fixtures/attachments.yml --- a/test/fixtures/attachments.yml Wed May 07 14:15:02 2014 +0100 +++ b/test/fixtures/attachments.yml Tue Sep 09 09:34:53 2014 +0100 @@ -4,6 +4,7 @@ downloads: 0 content_type: text/plain disk_filename: 060719210727_error281.txt + disk_directory: "2006/07" container_id: 3 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 1 @@ -16,6 +17,7 @@ downloads: 0 content_type: text/plain disk_filename: 060719210727_document.txt + disk_directory: "2006/07" container_id: 1 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 2 @@ -28,6 +30,7 @@ downloads: 0 content_type: image/gif disk_filename: 060719210727_logo.gif + disk_directory: "2006/07" container_id: 4 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 3 @@ -42,6 +45,7 @@ container_id: 3 downloads: 0 disk_filename: 060719210727_source.rb + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 4 filesize: 153 @@ -55,6 +59,7 @@ container_id: 3 downloads: 0 disk_filename: 060719210727_changeset_iso8859-1.diff + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 5 filesize: 687 @@ -67,6 +72,7 @@ container_id: 3 downloads: 0 disk_filename: 060719210727_archive.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 6 filesize: 157 @@ -79,6 +85,7 @@ container_id: 4 downloads: 0 disk_filename: 060719210727_archive.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 7 filesize: 157 @@ -91,6 +98,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_project_file.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 8 filesize: 320 @@ -103,6 +111,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_archive.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 9 filesize: 452 @@ -115,6 +124,7 @@ container_id: 2 downloads: 0 disk_filename: 060719210727_picture.jpg + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 10 filesize: 452 @@ -127,6 +137,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_picture.jpg + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 11 filesize: 452 @@ -139,6 +150,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_version_file.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 12 filesize: 452 @@ -151,6 +163,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_foo.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 13 filesize: 452 @@ -163,6 +176,7 @@ container_id: 3 downloads: 0 disk_filename: 060719210727_changeset_utf8.diff + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 14 filesize: 687 @@ -176,6 +190,7 @@ container_id: 14 downloads: 0 disk_filename: 060719210727_changeset_utf8.diff + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 filesize: 687 filename: private.diff @@ -187,6 +202,7 @@ downloads: 0 created_on: 2010-11-23 16:14:50 +09:00 disk_filename: 101123161450_testfile_1.png + disk_directory: "2010/11" container_id: 14 digest: 8e0294de2441577c529f170b6fb8f638 id: 16 @@ -200,6 +216,7 @@ downloads: 0 created_on: 2010-12-23 16:14:50 +09:00 disk_filename: 101223161450_testfile_2.png + disk_directory: "2010/12" container_id: 14 digest: 6bc2963e8d7ea0d3e68d12d1fba3d6ca id: 17 @@ -213,6 +230,7 @@ downloads: 0 created_on: 2011-01-23 16:14:50 +09:00 disk_filename: 101123161450_testfile_1.png + disk_directory: "2010/11" container_id: 14 digest: 8e0294de2441577c529f170b6fb8f638 id: 18 @@ -226,6 +244,7 @@ downloads: 0 created_on: 2011-02-23 16:14:50 +09:00 disk_filename: 101223161450_testfile_2.png + disk_directory: "2010/12" container_id: 14 digest: 6bc2963e8d7ea0d3e68d12d1fba3d6ca id: 19 @@ -234,3 +253,17 @@ filename: Testテスト.PNG filesize: 3582 author_id: 2 +attachments_020: + content_type: text/plain + downloads: 0 + created_on: 2012-05-12 16:14:50 +09:00 + disk_filename: 120512161450_root_attachment.txt + disk_directory: + container_id: 14 + digest: b0fe2abdb2599743d554a61d7da7ff74 + id: 20 + container_type: Issue + description: "" + filename: root_attachment.txt + filesize: 54 + author_id: 2 diff -r d98d22a98252 -r afce8026aaeb test/fixtures/changesets.yml --- a/test/fixtures/changesets.yml Wed May 07 14:15:02 2014 +0100 +++ b/test/fixtures/changesets.yml Tue Sep 09 09:34:53 2014 +0100 @@ -3,8 +3,9 @@ commit_date: 2007-04-11 committed_on: 2007-04-11 15:14:44 +02:00 revision: 1 + scmid: 691322a8eb01e11fd7 id: 100 - comments: My very first commit + comments: 'My very first commit do not escaping #<>&' repository_id: 10 committer: dlopper user_id: 3 diff -r d98d22a98252 -r afce8026aaeb test/fixtures/custom_fields_trackers.yml --- a/test/fixtures/custom_fields_trackers.yml Wed May 07 14:15:02 2014 +0100 +++ b/test/fixtures/custom_fields_trackers.yml Tue Sep 09 09:34:53 2014 +0100 @@ -17,3 +17,12 @@ custom_fields_trackers_006: custom_field_id: 6 tracker_id: 3 +custom_fields_trackers_007: + custom_field_id: 8 + tracker_id: 1 +custom_fields_trackers_008: + custom_field_id: 8 + tracker_id: 2 +custom_fields_trackers_009: + custom_field_id: 8 + tracker_id: 3 diff -r d98d22a98252 -r afce8026aaeb test/fixtures/diffs/issue-12641-ja.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-12641-ja.diff Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,25 @@ +# HG changeset patch +# User tmaruyama +# Date 1362559296 0 +# Node ID ee54942e0289c30bea1b1973750b698b1ee7c466 +# Parent 738777832f379f6f099c25251593fc57bc17f586 +fix some Japanese "issue" translations (#13350) + +Contributed by Go MAEDA. + +diff --git a/config/locales/ja.yml b/config/locales/ja.yml +--- a/config/locales/ja.yml ++++ b/config/locales/ja.yml +@@ -904,9 +904,9 @@ ja: + text_journal_set_to: "%{label} ã‚’ %{value} ã«ã‚»ãƒƒãƒˆ" + text_journal_deleted: "%{label} を削除 (%{old})" + text_journal_added: "%{label} %{value} を追加" +- text_tip_issue_begin_day: ã“ã®æ—¥ã«é–‹å§‹ã™ã‚‹ã‚¿ã‚¹ã‚¯ +- text_tip_issue_end_day: ã“ã®æ—¥ã«çµ‚了ã™ã‚‹ã‚¿ã‚¹ã‚¯ +- text_tip_issue_begin_end_day: ã“ã®æ—¥ã®ã†ã¡ã«é–‹å§‹ã—ã¦çµ‚了ã™ã‚‹ã‚¿ã‚¹ã‚¯ ++ text_tip_issue_begin_day: ã“ã®æ—¥ã«é–‹å§‹ã™ã‚‹ãƒã‚±ãƒƒãƒˆ ++ text_tip_issue_end_day: ã“ã®æ—¥ã«çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ ++ text_tip_issue_begin_end_day: ã“ã®æ—¥ã«é–‹å§‹ãƒ»çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + text_caracters_maximum: "最大%{count}文字ã§ã™ã€‚" + text_caracters_minimum: "最低%{count}文字ã®é•·ã•ãŒå¿…è¦ã§ã™" + text_length_between: "é•·ã•ã¯%{min}ã‹ã‚‰%{max}文字ã¾ã§ã§ã™ã€‚" diff -r d98d22a98252 -r afce8026aaeb test/fixtures/diffs/issue-12641-ru.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-12641-ru.diff Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,19 @@ +# HG changeset patch +# User tmaruyama +# Date 1355872765 0 +# Node ID 8a13ebed1779c2e85fa644ecdd0de81996c969c4 +# Parent 5c3c5f917ae92f278fe42c6978366996595b0796 +Russian "about_x_hours" translation changed by Mikhail Velkin (#12640) + +diff --git a/config/locales/ru.yml b/config/locales/ru.yml +--- a/config/locales/ru.yml ++++ b/config/locales/ru.yml +@@ -115,7 +115,7 @@ ru: + one: "около %{count} чаÑа" + few: "около %{count} чаÑов" + many: "около %{count} чаÑов" +- other: "около %{count} чаÑа" ++ other: "около %{count} чаÑов" + x_hours: + one: "1 чаÑ" + other: "%{count} чаÑов" diff -r d98d22a98252 -r afce8026aaeb test/fixtures/diffs/issue-13644-1.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-13644-1.diff Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本 ++日本語 + bbbb diff -r d98d22a98252 -r afce8026aaeb test/fixtures/diffs/issue-13644-2.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-13644-2.diff Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本 ++ã«ã£ã½ã‚“日本 + bbbb diff -r d98d22a98252 -r afce8026aaeb test/fixtures/diffs/issue-13644-3.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-13644-3.diff Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-07-27 06:03:49.133257759 +0900 ++++ b.txt 2013-07-27 06:03:58.791221118 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本記 ++日本娘 + bbbb diff -r d98d22a98252 -r afce8026aaeb test/fixtures/diffs/issue-13644-4.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-13644-4.diff Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-07-27 04:20:45.973229414 +0900 ++++ b.txt 2013-07-27 04:20:52.366228105 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本記 ++日本誘 + bbbb diff -r d98d22a98252 -r afce8026aaeb test/fixtures/diffs/issue-13644-5.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-13644-5.diff Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-07-27 05:52:11.415223830 +0900 ++++ b.txt 2013-07-27 05:52:18.249190358 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本記ok ++日本誘ok + bbbb diff -r d98d22a98252 -r afce8026aaeb test/fixtures/files/060719210727_archive.zip Binary file test/fixtures/files/060719210727_archive.zip has changed diff -r d98d22a98252 -r afce8026aaeb test/fixtures/files/060719210727_changeset_iso8859-1.diff --- a/test/fixtures/files/060719210727_changeset_iso8859-1.diff Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -Index: trunk/app/controllers/issues_controller.rb -=================================================================== ---- trunk/app/controllers/issues_controller.rb (révision 1483) -+++ trunk/app/controllers/issues_controller.rb (révision 1484) -@@ -149,7 +149,7 @@ - attach_files(@issue, params[:attachments]) - flash[:notice] = 'Demande créée avec succès' - Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') -- redirect_to :controller => 'issues', :action => 'show', :id => @issue, :project_id => @project -+ redirect_to :controller => 'issues', :action => 'show', :id => @issue - return - end - end diff -r d98d22a98252 -r afce8026aaeb test/fixtures/files/060719210727_changeset_utf8.diff --- a/test/fixtures/files/060719210727_changeset_utf8.diff Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -Index: trunk/app/controllers/issues_controller.rb -=================================================================== ---- trunk/app/controllers/issues_controller.rb (révision 1483) -+++ trunk/app/controllers/issues_controller.rb (révision 1484) -@@ -149,7 +149,7 @@ - attach_files(@issue, params[:attachments]) - flash[:notice] = 'Demande créée avec succès' - Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') -- redirect_to :controller => 'issues', :action => 'show', :id => @issue, :project_id => @project -+ redirect_to :controller => 'issues', :action => 'show', :id => @issue - return - end - end diff -r d98d22a98252 -r afce8026aaeb test/fixtures/files/060719210727_source.rb --- a/test/fixtures/files/060719210727_source.rb Wed May 07 14:15:02 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -# The Greeter class -class Greeter - def initialize(name) - @name = name.capitalize - end - - def salute - puts "Hello #{@name}!" - end -end diff -r d98d22a98252 -r afce8026aaeb test/fixtures/files/101123161450_testfile_1.png Binary file test/fixtures/files/101123161450_testfile_1.png has changed diff -r d98d22a98252 -r afce8026aaeb test/fixtures/files/101223161450_testfile_2.png Binary file test/fixtures/files/101223161450_testfile_2.png has changed diff -r d98d22a98252 -r afce8026aaeb test/fixtures/issues.yml --- a/test/fixtures/issues.yml Wed May 07 14:15:02 2014 +0100 +++ b/test/fixtures/issues.yml Tue Sep 09 09:34:53 2014 +0100 @@ -152,6 +152,7 @@ root_id: 8 lft: 1 rgt: 2 + closed_on: <%= 3.days.ago.to_s(:db) %> issues_009: created_on: <%= 1.minute.ago.to_s(:db) %> project_id: 5 @@ -209,6 +210,7 @@ root_id: 11 lft: 1 rgt: 2 + closed_on: <%= 1.day.ago.to_s(:db) %> issues_012: created_on: <%= 3.days.ago.to_s(:db) %> project_id: 1 @@ -228,6 +230,7 @@ root_id: 12 lft: 1 rgt: 2 + closed_on: <%= 1.day.ago.to_s(:db) %> issues_013: created_on: <%= 5.days.ago.to_s(:db) %> project_id: 3 diff -r d98d22a98252 -r afce8026aaeb test/fixtures/journal_details.yml --- a/test/fixtures/journal_details.yml Wed May 07 14:15:02 2014 +0100 +++ b/test/fixtures/journal_details.yml Tue Sep 09 09:34:53 2014 +0100 @@ -14,7 +14,7 @@ prop_key: done_ratio journal_id: 1 journal_details_003: - old_value: nil + old_value: property: attr id: 3 value: "6" @@ -35,7 +35,7 @@ prop_key: 2 journal_id: 3 journal_details_006: - old_value: nil + old_value: property: attachment id: 6 value: 060719210727_picture.jpg diff -r d98d22a98252 -r afce8026aaeb test/fixtures/mail_handler/multiple_text_parts.eml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/mail_handler/multiple_text_parts.eml Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,62 @@ +From JSmith@somenet.foo Fri Mar 22 08:30:28 2013 +From: John Smith +Content-Type: multipart/mixed; boundary="Apple-Mail=_33C8180A-B097-4B87-A925-441300BDB9C9" +Message-Id: +Mime-Version: 1.0 (Mac OS X Mail 6.3 \(1503\)) +Subject: Test with multiple text parts +Date: Fri, 22 Mar 2013 17:30:20 +0200 +To: redmine@somenet.foo +X-Mailer: Apple Mail (2.1503) + + + +--Apple-Mail=_33C8180A-B097-4B87-A925-441300BDB9C9 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; + charset=us-ascii + +The first text part. + +--Apple-Mail=_33C8180A-B097-4B87-A925-441300BDB9C9 +Content-Disposition: inline; + filename=1st.pdf +Content-Type: application/pdf; + x-unix-mode=0644; + name="1st.pdf" +Content-Transfer-Encoding: base64 + +JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPDwgL0xlbmd0aCA1IDAgUiAvRmlsdGVyIC9G + +--Apple-Mail=_33C8180A-B097-4B87-A925-441300BDB9C9 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; + charset=us-ascii + +The second text part. + +--Apple-Mail=_33C8180A-B097-4B87-A925-441300BDB9C9 +Content-Disposition: inline; + filename=2nd.pdf +Content-Type: application/pdf; + x-unix-mode=0644; + name="2nd.pdf" +Content-Transfer-Encoding: base64 + +JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPDwgL0xlbmd0aCA1IDAgUiAvRmlsdGVyIC9G + + +--Apple-Mail=_33C8180A-B097-4B87-A925-441300BDB9C9 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; + charset=us-ascii + +The third one. + +--Apple-Mail=_33C8180A-B097-4B87-A925-441300BDB9C9 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; charset=us-ascii +Content-Disposition: attachment; filename="textfile.txt" + +Plain text attachment + +--Apple-Mail=_33C8180A-B097-4B87-A925-441300BDB9C9-- diff -r d98d22a98252 -r afce8026aaeb test/fixtures/queries.yml --- a/test/fixtures/queries.yml Wed May 07 14:15:02 2014 +0100 +++ b/test/fixtures/queries.yml Tue Sep 09 09:34:53 2014 +0100 @@ -1,8 +1,9 @@ --- queries_001: id: 1 + type: IssueQuery project_id: 1 - is_public: true + visibility: 2 name: Multiple custom fields query filters: | --- @@ -23,8 +24,9 @@ column_names: queries_002: id: 2 + type: IssueQuery project_id: 1 - is_public: false + visibility: 0 name: Private query for cookbook filters: | --- @@ -41,8 +43,9 @@ column_names: queries_003: id: 3 + type: IssueQuery project_id: - is_public: false + visibility: 0 name: Private query for all projects filters: | --- @@ -55,8 +58,9 @@ column_names: queries_004: id: 4 + type: IssueQuery project_id: - is_public: true + visibility: 2 name: Public query for all projects filters: | --- @@ -69,8 +73,9 @@ column_names: queries_005: id: 5 + type: IssueQuery project_id: - is_public: true + visibility: 2 name: Open issues by priority and tracker filters: | --- @@ -89,8 +94,9 @@ - asc queries_006: id: 6 + type: IssueQuery project_id: - is_public: true + visibility: 2 name: Open issues grouped by tracker filters: | --- @@ -108,8 +114,9 @@ - desc queries_007: id: 7 + type: IssueQuery project_id: 2 - is_public: true + visibility: 2 name: Public query for project 2 filters: | --- @@ -122,8 +129,9 @@ column_names: queries_008: id: 8 + type: IssueQuery project_id: 2 - is_public: false + visibility: 0 name: Private query for project 2 filters: | --- @@ -136,8 +144,9 @@ column_names: queries_009: id: 9 + type: IssueQuery project_id: - is_public: true + visibility: 2 name: Open issues grouped by list custom field filters: | --- diff -r d98d22a98252 -r afce8026aaeb test/fixtures/repositories.yml --- a/test/fixtures/repositories.yml Wed May 07 14:15:02 2014 +0100 +++ b/test/fixtures/repositories.yml Tue Sep 09 09:34:53 2014 +0100 @@ -8,6 +8,7 @@ login: "" type: Repository::Subversion is_default: true + created_on: 2006-07-19 19:04:21 +02:00 repositories_002: project_id: 2 url: svn://localhost/test @@ -17,3 +18,4 @@ login: "" type: Repository::Subversion is_default: true + created_on: 2006-07-19 19:04:21 +02:00 diff -r d98d22a98252 -r afce8026aaeb test/fixtures/repositories/bazaar_repository.tar.gz Binary file test/fixtures/repositories/bazaar_repository.tar.gz has changed diff -r d98d22a98252 -r afce8026aaeb test/fixtures/repositories/mercurial_repository.hg Binary file test/fixtures/repositories/mercurial_repository.hg has changed diff -r d98d22a98252 -r afce8026aaeb test/fixtures/roles.yml --- a/test/fixtures/roles.yml Wed May 07 14:15:02 2014 +0100 +++ b/test/fixtures/roles.yml Tue Sep 09 09:34:53 2014 +0100 @@ -38,7 +38,9 @@ - :manage_news - :comment_news - :view_documents - - :manage_documents + - :add_documents + - :edit_documents + - :delete_documents - :view_wiki_pages - :export_wiki_pages - :view_wiki_edits @@ -89,7 +91,9 @@ - :manage_news - :comment_news - :view_documents - - :manage_documents + - :add_documents + - :edit_documents + - :delete_documents - :view_wiki_pages - :view_wiki_edits - :edit_wiki_pages @@ -131,7 +135,9 @@ - :manage_news - :comment_news - :view_documents - - :manage_documents + - :add_documents + - :edit_documents + - :delete_documents - :view_wiki_pages - :view_wiki_edits - :edit_wiki_pages @@ -163,7 +169,6 @@ - :view_time_entries - :comment_news - :view_documents - - :manage_documents - :view_wiki_pages - :view_wiki_edits - :edit_wiki_pages diff -r d98d22a98252 -r afce8026aaeb test/fixtures/users.yml --- a/test/fixtures/users.yml Wed May 07 14:15:02 2014 +0100 +++ b/test/fixtures/users.yml Tue Sep 09 09:34:53 2014 +0100 @@ -29,7 +29,7 @@ admin: true mail: admin@somenet.foo lastname: Admin - firstname: redMine + firstname: Redmine id: 1 auth_source_id: mail_notification: all @@ -105,12 +105,15 @@ login: '' type: AnonymousUser users_007: + # A user who does not belong to any project id: 7 created_on: 2006-07-19 19:33:19 +02:00 status: 1 last_login_on: - language: '' - hashed_password: 1 + language: 'en' + # password = foo + salt: 7599f9963ec07b5a3b55b354407120c0 + hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed updated_on: 2006-07-19 19:33:19 +02:00 admin: false mail: someone@foo.bar diff -r d98d22a98252 -r afce8026aaeb test/functional/account_controller_openid_test.rb --- a/test/functional/account_controller_openid_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/account_controller_openid_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -40,17 +40,17 @@ :identity_url => 'http://openid.example.com/good_user') existing_user.login = 'cool_user' assert existing_user.save! - + post :login, :openid_url => existing_user.identity_url assert_redirected_to '/my/page' end - + def test_login_with_invalid_openid_provider Setting.self_registration = '0' post :login, :openid_url => 'http;//openid.example.com/good_user' assert_redirected_to home_url end - + def test_login_with_openid_for_existing_non_active_user Setting.self_registration = '2' existing_user = User.new(:firstname => 'Cool', @@ -60,11 +60,11 @@ :status => User::STATUS_REGISTERED) existing_user.login = 'cool_user' assert existing_user.save! - + post :login, :openid_url => existing_user.identity_url assert_redirected_to '/login' end - + def test_login_with_openid_with_new_user_created Setting.self_registration = '3' post :login, :openid_url => 'http://openid.example.com/good_user' @@ -74,7 +74,7 @@ assert_equal 'Cool', user.firstname assert_equal 'User', user.lastname end - + def test_login_with_openid_with_new_user_and_self_registration_off Setting.self_registration = '0' post :login, :openid_url => 'http://openid.example.com/good_user' @@ -82,18 +82,18 @@ user = User.find_by_login('cool_user') assert_nil user end - + def test_login_with_openid_with_new_user_created_with_email_activation_should_have_a_token Setting.self_registration = '1' post :login, :openid_url => 'http://openid.example.com/good_user' assert_redirected_to '/login' user = User.find_by_login('cool_user') assert user - + token = Token.find_by_user_id_and_action(user.id, 'register') assert token end - + def test_login_with_openid_with_new_user_created_with_manual_activation Setting.self_registration = '2' post :login, :openid_url => 'http://openid.example.com/good_user' @@ -102,23 +102,23 @@ assert user assert_equal User::STATUS_REGISTERED, user.status end - + def test_login_with_openid_with_new_user_with_conflict_should_register Setting.self_registration = '3' existing_user = User.new(:firstname => 'Cool', :lastname => 'User', :mail => 'user@somedomain.com') existing_user.login = 'cool_user' assert existing_user.save! - + post :login, :openid_url => 'http://openid.example.com/good_user' assert_response :success assert_template 'register' assert assigns(:user) assert_equal 'http://openid.example.com/good_user', assigns(:user)[:identity_url] end - + def test_login_with_openid_with_new_user_with_missing_information_should_register Setting.self_registration = '3' - + post :login, :openid_url => 'http://openid.example.com/good_blank_user' assert_response :success assert_template 'register' @@ -131,6 +131,16 @@ assert_select 'input[name=?][value=?]', 'user[identity_url]', 'http://openid.example.com/good_blank_user' end + def test_post_login_should_not_verify_token_when_using_open_id + ActionController::Base.allow_forgery_protection = true + AccountController.any_instance.stubs(:using_open_id?).returns(true) + AccountController.any_instance.stubs(:authenticate_with_open_id).returns(true) + post :login + assert_response 200 + ensure + ActionController::Base.allow_forgery_protection = false + end + def test_register_after_login_failure_should_not_require_user_to_enter_a_password Setting.self_registration = '3' diff -r d98d22a98252 -r afce8026aaeb test/functional/account_controller_test.rb --- a/test/functional/account_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/account_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'account_controller' - -# Re-raise errors caught by the controller. -class AccountController; def rescue_action(e) raise e end; end class AccountControllerTest < ActionController::TestCase fixtures :users, :roles def setup - @controller = AccountController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -40,15 +33,71 @@ assert_select 'input[name=password]' end + def test_get_login_while_logged_in_should_redirect_to_home + @request.session[:user_id] = 2 + + get :login + assert_redirected_to '/' + assert_equal 2, @request.session[:user_id] + end + def test_login_should_redirect_to_back_url_param # request.uri is "test.host" in test environment - post :login, :username => 'jsmith', :password => 'jsmith', :back_url => 'http://test.host/issues/show/1' - assert_redirected_to '/issues/show/1' + back_urls = [ + 'http://test.host/issues/show/1', + '/' + ] + back_urls.each do |back_url| + post :login, :username => 'jsmith', :password => 'jsmith', :back_url => back_url + assert_redirected_to back_url + end + end + + def test_login_with_suburi_should_redirect_to_back_url_param + @relative_url_root = ApplicationController.relative_url_root + ApplicationController.relative_url_root = '/redmine' + + back_urls = [ + 'http://test.host/redmine/issues/show/1', + '/redmine' + ] + back_urls.each do |back_url| + post :login, :username => 'jsmith', :password => 'jsmith', :back_url => back_url + assert_redirected_to back_url + end + ensure + ApplicationController.relative_url_root = @relative_url_root end def test_login_should_not_redirect_to_another_host - post :login, :username => 'jsmith', :password => 'jsmith', :back_url => 'http://test.foo/fake' - assert_redirected_to '/my/page' + back_urls = [ + 'http://test.foo/fake', + '//test.foo/fake' + ] + back_urls.each do |back_url| + post :login, :username => 'jsmith', :password => 'jsmith', :back_url => back_url + assert_redirected_to '/my/page' + end + end + + def test_login_with_suburi_should_not_redirect_to_another_suburi + @relative_url_root = ApplicationController.relative_url_root + ApplicationController.relative_url_root = '/redmine' + + back_urls = [ + 'http://test.host/', + 'http://test.host/fake', + 'http://test.host/fake/issues', + 'http://test.host/redmine/../fake', + 'http://test.host/redmine/../fake/issues', + 'http://test.host/redmine/%2e%2e/fake' + ] + back_urls.each do |back_url| + post :login, :username => 'jsmith', :password => 'jsmith', :back_url => back_url + assert_redirected_to '/my/page' + end + ensure + ApplicationController.relative_url_root = @relative_url_root end def test_login_with_wrong_password @@ -62,6 +111,36 @@ assert_select 'input[name=password][value]', 0 end + def test_login_with_locked_account_should_fail + User.find(2).update_attribute :status, User::STATUS_LOCKED + + post :login, :username => 'jsmith', :password => 'jsmith' + assert_redirected_to '/login' + assert_include 'locked', flash[:error] + assert_nil @request.session[:user_id] + end + + def test_login_as_registered_user_with_manual_activation_should_inform_user + User.find(2).update_attribute :status, User::STATUS_REGISTERED + + with_settings :self_registration => '2', :default_language => 'en' do + post :login, :username => 'jsmith', :password => 'jsmith' + assert_redirected_to '/login' + assert_include 'pending administrator approval', flash[:error] + end + end + + def test_login_as_registered_user_with_email_activation_should_propose_new_activation_email + User.find(2).update_attribute :status, User::STATUS_REGISTERED + + with_settings :self_registration => '1', :default_language => 'en' do + post :login, :username => 'jsmith', :password => 'jsmith' + assert_redirected_to '/login' + assert_equal 2, @request.session[:registered_user_id] + assert_include 'new activation email', flash[:error] + end + end + def test_login_should_rescue_auth_source_exception source = AuthSource.create!(:name => 'Test') User.find(2).update_attribute :auth_source_id, source.id @@ -79,9 +158,23 @@ assert_response 302 end + def test_get_logout_should_not_logout + @request.session[:user_id] = 2 + get :logout + assert_response :success + assert_template 'logout' + + assert_equal 2, @request.session[:user_id] + end + + def test_get_logout_with_anonymous_should_redirect + get :logout + assert_redirected_to '/' + end + def test_logout @request.session[:user_id] = 2 - get :logout + post :logout assert_redirected_to '/' assert_nil @request.session[:user_id] end @@ -90,7 +183,7 @@ @controller.expects(:reset_session).once @request.session[:user_id] = 2 - get :logout + post :logout assert_response 302 end @@ -101,8 +194,21 @@ assert_template 'register' assert_not_nil assigns(:user) - assert_tag 'input', :attributes => {:name => 'user[password]'} - assert_tag 'input', :attributes => {:name => 'user[password_confirmation]'} + assert_select 'input[name=?]', 'user[password]' + assert_select 'input[name=?]', 'user[password_confirmation]' + end + end + + def test_get_register_should_detect_user_language + with_settings :self_registration => '3' do + @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' + get :register + assert_response :success + assert_not_nil assigns(:user) + assert_equal 'fr', assigns(:user).language + assert_select 'select[name=?]', 'user[language]' do + assert_select 'option[value=fr][selected=selected]' + end end end @@ -194,6 +300,15 @@ assert_no_difference 'Token.count' do post :lost_password, :mail => 'JSmith@somenet.foo' + assert_redirected_to '/account/lost_password' + end + end + + def test_lost_password_for_user_who_cannot_change_password_should_fail + User.any_instance.stubs(:change_password_allowed?).returns(false) + + assert_no_difference 'Token.count' do + post :lost_password, :mail => 'JSmith@somenet.foo' assert_response :success end end @@ -251,4 +366,27 @@ post :lost_password, :token => "abcdef", :new_password => 'newpass', :new_password_confirmation => 'newpass' assert_redirected_to '/' end + + def test_activation_email_should_send_an_activation_email + User.find(2).update_attribute :status, User::STATUS_REGISTERED + @request.session[:registered_user_id] = 2 + + with_settings :self_registration => '1' do + assert_difference 'ActionMailer::Base.deliveries.size' do + get :activation_email + assert_redirected_to '/login' + end + end + end + + def test_activation_email_without_session_data_should_fail + User.find(2).update_attribute :status, User::STATUS_REGISTERED + + with_settings :self_registration => '1' do + assert_no_difference 'ActionMailer::Base.deliveries.size' do + get :activation_email + assert_redirected_to '/' + end + end + end end diff -r d98d22a98252 -r afce8026aaeb test/functional/activities_controller_test.rb --- a/test/functional/activities_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/activities_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../test_helper', __FILE__) class ActivitiesControllerTest < ActionController::TestCase @@ -9,7 +26,6 @@ :members, :groups_users, :enabled_modules, - :workflows, :journals, :journal_details @@ -19,16 +35,8 @@ assert_template 'index' assert_not_nil assigns(:events_by_day) - assert_tag :tag => "h3", - :content => /#{2.days.ago.to_date.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue-edit/ }, - :child => { :tag => "a", - :content => /(#{IssueStatus.find(2).name})/, - } - } - } + assert_select 'h3', :text => /#{2.days.ago.to_date.day}/ + assert_select 'dl dt.issue-edit a', :text => /(#{IssueStatus.find(2).name})/ end def test_project_index_with_invalid_project_id_should_respond_404 @@ -42,16 +50,8 @@ assert_template 'index' assert_not_nil assigns(:events_by_day) - assert_tag :tag => "h3", - :content => /#{3.day.ago.to_date.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue/ }, - :child => { :tag => "a", - :content => /Can't print recipes/, - } - } - } + assert_select 'h3', :text => /#{3.days.ago.to_date.day}/ + assert_select 'dl dt.issue a', :text => /Can't print recipes/ end def test_global_index @@ -63,16 +63,9 @@ i5 = Issue.find(5) d5 = User.find(1).time_to_date(i5.created_on) - assert_tag :tag => "h3", - :content => /#{d5.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue/ }, - :child => { :tag => "a", - :content => /Subproject issue/, - } - } - } + + assert_select 'h3', :text => /#{d5.day}/ + assert_select 'dl dt.issue a', :text => /Subproject issue/ end def test_user_index @@ -87,16 +80,8 @@ i1 = Issue.find(1) d1 = User.find(1).time_to_date(i1.created_on) - assert_tag :tag => "h3", - :content => /#{d1.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue/ }, - :child => { :tag => "a", - :content => /Can't print recipes/, - } - } - } + assert_select 'h3', :text => /#{d1.day}/ + assert_select 'dl dt.issue a', :text => /Can't print recipes/ end def test_user_index_with_invalid_user_id_should_respond_404 @@ -109,14 +94,13 @@ assert_response :success assert_template 'common/feed' - assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil }, - :attributes => {:rel => 'self', :href => 'http://test.host/activity.atom?with_subprojects=0'} - assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil }, - :attributes => {:rel => 'alternate', :href => 'http://test.host/activity?with_subprojects=0'} - - assert_tag :tag => 'entry', :child => { - :tag => 'link', - :attributes => {:href => 'http://test.host/issues/11'}} + assert_select 'feed' do + assert_select 'link[rel=self][href=?]', 'http://test.host/activity.atom?with_subprojects=0' + assert_select 'link[rel=alternate][href=?]', 'http://test.host/activity?with_subprojects=0' + assert_select 'entry' do + assert_select 'link[href=?]', 'http://test.host/issues/11' + end + end end def test_index_atom_feed_with_explicit_selection @@ -133,21 +117,21 @@ assert_response :success assert_template 'common/feed' - assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil }, - :attributes => {:rel => 'self', :href => 'http://test.host/activity.atom?show_changesets=1&show_documents=1&show_files=1&show_issues=1&show_messages=1&show_news=1&show_time_entries=1&show_wiki_edits=1&with_subprojects=0'} - assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil }, - :attributes => {:rel => 'alternate', :href => 'http://test.host/activity?show_changesets=1&show_documents=1&show_files=1&show_issues=1&show_messages=1&show_news=1&show_time_entries=1&show_wiki_edits=1&with_subprojects=0'} - - assert_tag :tag => 'entry', :child => { - :tag => 'link', - :attributes => {:href => 'http://test.host/issues/11'}} + assert_select 'feed' do + assert_select 'link[rel=self][href=?]', 'http://test.host/activity.atom?show_changesets=1&show_documents=1&show_files=1&show_issues=1&show_messages=1&show_news=1&show_time_entries=1&show_wiki_edits=1&with_subprojects=0' + assert_select 'link[rel=alternate][href=?]', 'http://test.host/activity?show_changesets=1&show_documents=1&show_files=1&show_issues=1&show_messages=1&show_news=1&show_time_entries=1&show_wiki_edits=1&with_subprojects=0' + assert_select 'entry' do + assert_select 'link[href=?]', 'http://test.host/issues/11' + end + end end def test_index_atom_feed_with_one_item_type get :index, :format => 'atom', :show_issues => '1' assert_response :success assert_template 'common/feed' - assert_tag :tag => 'title', :content => /Issues/ + + assert_select 'title', :text => /Issues/ end def test_index_should_show_private_notes_with_permission_only diff -r d98d22a98252 -r afce8026aaeb test/functional/admin_controller_test.rb --- a/test/functional/admin_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/admin_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -27,15 +27,13 @@ def test_index get :index - assert_no_tag :tag => 'div', - :attributes => { :class => /nodata/ } + assert_select 'div.nodata', 0 end def test_index_with_no_configuration_data delete_configuration_data get :index - assert_tag :tag => 'div', - :attributes => { :class => /nodata/ } + assert_select 'div.nodata' end def test_projects @@ -85,12 +83,12 @@ def test_test_email user = User.find(1) - user.pref[:no_self_notified] = '1' + user.pref.no_self_notified = '1' user.pref.save! ActionMailer::Base.deliveries.clear get :test_email - assert_redirected_to '/settings/edit?tab=notifications' + assert_redirected_to '/settings?tab=notifications' mail = ActionMailer::Base.deliveries.last assert_not_nil mail user = User.find(1) @@ -100,7 +98,7 @@ def test_test_email_failure_should_display_the_error Mailer.stubs(:test_email).raises(Exception, 'Some error message') get :test_email - assert_redirected_to '/settings/edit?tab=notifications' + assert_redirected_to '/settings?tab=notifications' assert_match /Some error message/, flash[:error] end @@ -128,8 +126,14 @@ assert_response :success assert_template 'plugins' - assert_tag :td, :child => { :tag => 'span', :content => 'Foo plugin' } - assert_tag :td, :child => { :tag => 'span', :content => 'Bar' } + assert_select 'tr#plugin-foo' do + assert_select 'td span.name', :text => 'Foo plugin' + assert_select 'td.configure a[href=/settings/plugin/foo]' + end + assert_select 'tr#plugin-bar' do + assert_select 'td span.name', :text => 'Bar' + assert_select 'td.configure a', 0 + end end def test_info @@ -145,8 +149,7 @@ get :index assert_response :success - assert_tag :a, :attributes => { :href => '/foo/bar' }, - :content => 'Test' + assert_select 'div#admin-menu a[href=/foo/bar]', :text => 'Test' Redmine::MenuManager.map :admin_menu do |menu| menu.delete :test_admin_menu_plugin_extension diff -r d98d22a98252 -r afce8026aaeb test/functional/attachments_controller_test.rb --- a/test/functional/attachments_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/attachments_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,10 +18,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'attachments_controller' - -# Re-raise errors caught by the controller. -class AttachmentsController; def rescue_action(e) raise e end; end class AttachmentsControllerTest < ActionController::TestCase fixtures :users, :projects, :roles, :members, :member_roles, @@ -29,9 +25,6 @@ :versions, :wiki_pages, :wikis, :documents def setup - @controller = AttachmentsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil set_fixtures_attachments_directory end @@ -57,7 +50,7 @@ set_tmp_attachments_directory end - def test_show_diff_replcace_cannot_convert_content + def test_show_diff_replace_cannot_convert_content with_settings :repositories_encodings => 'UTF-8' do ['inline', 'sbs'].each do |dt| # 060719210727_changeset_iso8859-1.diff @@ -159,7 +152,7 @@ :sibling => { :tag => 'td', :content => /#{str_japanese}/ } end - def test_show_text_file_replcace_cannot_convert_content + def test_show_text_file_replace_cannot_convert_content set_tmp_attachments_directory with_settings :repositories_encodings => 'UTF-8' do a = Attachment.new(:container => Issue.find(1), @@ -230,12 +223,21 @@ set_tmp_attachments_directory end - def test_show_file_without_container_should_be_denied + def test_show_file_without_container_should_be_allowed_to_author set_tmp_attachments_directory attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2) @request.session[:user_id] = 2 get :show, :id => attachment.id + assert_response 200 + end + + def test_show_file_without_container_should_be_denied_to_other_users + set_tmp_attachments_directory + attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2) + + @request.session[:user_id] = 3 + get :show, :id => attachment.id assert_response 403 end diff -r d98d22a98252 -r afce8026aaeb test/functional/auth_sources_controller_test.rb --- a/test/functional/auth_sources_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/auth_sources_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -42,8 +42,15 @@ assert_equal AuthSourceLdap, source.class assert source.new_record? - assert_tag 'input', :attributes => {:name => 'type', :value => 'AuthSourceLdap'} - assert_tag 'input', :attributes => {:name => 'auth_source[host]'} + assert_select 'form#auth_source_form' do + assert_select 'input[name=type][value=AuthSourceLdap]' + assert_select 'input[name=?]', 'auth_source[host]' + end + end + + def test_new_with_invalid_type_should_respond_with_404 + get :new, :type => 'foo' + assert_response 404 end def test_create @@ -52,7 +59,7 @@ assert_redirected_to '/auth_sources' end - source = AuthSourceLdap.first(:order => 'id DESC') + source = AuthSourceLdap.order('id DESC').first assert_equal 'Test', source.name assert_equal '127.0.0.1', source.host assert_equal 389, source.port @@ -74,7 +81,23 @@ assert_response :success assert_template 'edit' - assert_tag 'input', :attributes => {:name => 'auth_source[host]'} + assert_select 'form#auth_source_form' do + assert_select 'input[name=?]', 'auth_source[host]' + end + end + + def test_edit_should_not_contain_password + AuthSource.find(1).update_column :account_password, 'secret' + + get :edit, :id => 1 + assert_response :success + assert_select 'input[value=secret]', 0 + assert_select 'input[name=dummy_password][value=?]', /x+/ + end + + def test_edit_invalid_should_respond_with_404 + get :edit, :id => 99 + assert_response 404 end def test_update @@ -96,6 +119,7 @@ def test_destroy assert_difference 'AuthSourceLdap.count', -1 do delete :destroy, :id => 1 + assert_redirected_to '/auth_sources' end end @@ -104,6 +128,7 @@ assert_no_difference 'AuthSourceLdap.count' do delete :destroy, :id => 1 + assert_redirected_to '/auth_sources' end end @@ -124,4 +149,20 @@ assert_not_nil flash[:error] assert_include 'Something went wrong', flash[:error] end + + def test_autocomplete_for_new_user + AuthSource.expects(:search).with('foo').returns([ + {:login => 'foo1', :firstname => 'John', :lastname => 'Smith', :mail => 'foo1@example.net', :auth_source_id => 1}, + {:login => 'Smith', :firstname => 'John', :lastname => 'Doe', :mail => 'foo2@example.net', :auth_source_id => 1} + ]) + + get :autocomplete_for_new_user, :term => 'foo' + assert_response :success + assert_equal 'application/json', response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Array, json + assert_equal 2, json.size + assert_equal 'foo1', json.first['value'] + assert_equal 'foo1 (John Smith)', json.first['label'] + end end diff -r d98d22a98252 -r afce8026aaeb test/functional/auto_completes_controller_test.rb --- a/test/functional/auto_completes_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/auto_completes_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../test_helper', __FILE__) class AutoCompletesControllerTest < ActionController::TestCase @@ -9,7 +26,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :journals, :journal_details def test_issues_should_not_be_case_sensitive @@ -33,6 +49,13 @@ assert assigns(:issues).include?(Issue.find(13)) end + def test_issues_should_return_issue_with_given_id_preceded_with_hash + get :issues, :project_id => 'subproject1', :q => '#13' + assert_response :success + assert_not_nil assigns(:issues) + assert assigns(:issues).include?(Issue.find(13)) + end + def test_auto_complete_with_scope_all_should_search_other_projects get :issues, :project_id => 'ecookbook', :q => '13', :scope => 'all' assert_response :success diff -r d98d22a98252 -r afce8026aaeb test/functional/boards_controller_test.rb --- a/test/functional/boards_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/boards_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -90,8 +90,9 @@ assert_response :success assert_template 'show' - assert_tag 'form', :attributes => {:id => 'message-form'} - assert_tag 'input', :attributes => {:name => 'message[subject]'} + assert_select 'form#message-form' do + assert_select 'input[name=?]', 'message[subject]' + end end def test_show_atom @@ -116,7 +117,7 @@ assert_select 'select[name=?]', 'board[parent_id]' do assert_select 'option', (Project.find(1).boards.size + 1) - assert_select 'option[value=]', :text => '' + assert_select 'option[value=]', :text => ' ' assert_select 'option[value=1]', :text => 'Help' end end diff -r d98d22a98252 -r afce8026aaeb test/functional/calendars_controller_test.rb --- a/test/functional/calendars_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/calendars_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../test_helper', __FILE__) class CalendarsControllerTest < ActionController::TestCase @@ -9,13 +26,20 @@ :members, :enabled_modules - def test_calendar + def test_show get :show, :project_id => 1 assert_response :success assert_template 'calendar' assert_not_nil assigns(:calendar) end + def test_show_should_run_custom_queries + @query = IssueQuery.create!(:name => 'Calendar', :visibility => IssueQuery::VISIBILITY_PUBLIC) + + get :show, :query_id => @query.id + assert_response :success + end + def test_cross_project_calendar get :show assert_response :success @@ -23,58 +47,38 @@ assert_not_nil assigns(:calendar) end - context "GET :show" do - should "run custom queries" do - @query = Query.create!(:name => 'Calendar', :is_public => true) - - get :show, :query_id => @query.id - assert_response :success - end - - end - def test_week_number_calculation Setting.start_of_week = 7 get :show, :month => '1', :year => '2010' assert_response :success - assert_tag :tag => 'tr', - :descendant => {:tag => 'td', - :attributes => {:class => 'week-number'}, :content => '53'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'odd'}, :content => '27'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '2'} + assert_select 'tr' do + assert_select 'td.week-number', :text => '53' + assert_select 'td.odd', :text => '27' + assert_select 'td.even', :text => '2' + end - assert_tag :tag => 'tr', - :descendant => {:tag => 'td', - :attributes => {:class => 'week-number'}, :content => '1'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'odd'}, :content => '3'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '9'} - + assert_select 'tr' do + assert_select 'td.week-number', :text => '1' + assert_select 'td.odd', :text => '3' + assert_select 'td.even', :text => '9' + end Setting.start_of_week = 1 get :show, :month => '1', :year => '2010' assert_response :success - assert_tag :tag => 'tr', - :descendant => {:tag => 'td', - :attributes => {:class => 'week-number'}, :content => '53'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '28'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '3'} + assert_select 'tr' do + assert_select 'td.week-number', :text => '53' + assert_select 'td.even', :text => '28' + assert_select 'td.even', :text => '3' + end - assert_tag :tag => 'tr', - :descendant => {:tag => 'td', - :attributes => {:class => 'week-number'}, :content => '1'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '4'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '10'} - + assert_select 'tr' do + assert_select 'td.week-number', :text => '1' + assert_select 'td.even', :text => '4' + assert_select 'td.even', :text => '10' + end end end diff -r d98d22a98252 -r afce8026aaeb test/functional/comments_controller_test.rb --- a/test/functional/comments_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/comments_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/functional/context_menus_controller_test.rb --- a/test/functional/context_menus_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/context_menus_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../test_helper', __FILE__) class ContextMenusControllerTest < ActionController::TestCase @@ -21,34 +38,21 @@ get :issues, :ids => [1] assert_response :success assert_template 'context_menu' - assert_tag :tag => 'a', :content => 'Edit', - :attributes => { :href => '/issues/1/edit', - :class => 'icon-edit' } - assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bstatus_id%5D=5', - :class => '' } - assert_tag :tag => 'a', :content => 'Immediate', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bpriority_id%5D=8', - :class => '' } - assert_no_tag :tag => 'a', :content => 'Inactive Priority' + + assert_select 'a.icon-edit[href=?]', '/issues/1/edit', :text => 'Edit' + assert_select 'a.icon-copy[href=?]', '/projects/ecookbook/issues/1/copy', :text => 'Copy' + assert_select 'a.icon-del[href=?]', '/issues?ids%5B%5D=1', :text => 'Delete' + + # Statuses + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bstatus_id%5D=5', :text => 'Closed' + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bpriority_id%5D=8', :text => 'Immediate' + # No inactive priorities + assert_select 'a', :text => /Inactive Priority/, :count => 0 # Versions - assert_tag :tag => 'a', :content => '2.0', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=3', - :class => '' } - assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=4', - :class => '' } - - assert_tag :tag => 'a', :content => 'Dave Lopper', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=3', - :class => '' } - assert_tag :tag => 'a', :content => 'Copy', - :attributes => { :href => '/projects/ecookbook/issues/1/copy', - :class => 'icon-copy' } - assert_no_tag :tag => 'a', :content => 'Move' - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => '/issues?ids%5B%5D=1', - :class => 'icon-del' } + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=3', :text => '2.0' + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=4', :text => 'eCookbook Subproject 1 - 2.0' + # Assignees + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper' end def test_context_menu_one_issue_by_anonymous @@ -69,25 +73,14 @@ assert_equal [1, 2], assigns(:issues).map(&:id).sort ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&') - assert_tag :tag => 'a', :content => 'Edit', - :attributes => { :href => "/issues/bulk_edit?#{ids}", - :class => 'icon-edit' } - assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", - :class => '' } - assert_tag :tag => 'a', :content => 'Immediate', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", - :class => '' } - assert_tag :tag => 'a', :content => 'Dave Lopper', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=3", - :class => '' } - assert_tag :tag => 'a', :content => 'Copy', - :attributes => { :href => "/issues/bulk_edit?copy=1&#{ids}", - :class => 'icon-copy' } - assert_no_tag :tag => 'a', :content => 'Move' - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => "/issues?#{ids}", - :class => 'icon-del' } + + assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit' + assert_select 'a.icon-copy[href=?]', "/issues/bulk_edit?copy=1&#{ids}", :text => 'Copy' + assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete' + + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed' + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate' + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=3", :text => 'Dave Lopper' end def test_context_menu_multiple_issues_of_different_projects @@ -99,21 +92,13 @@ assert_equal [1, 2, 6], assigns(:issues).map(&:id).sort ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&') - assert_tag :tag => 'a', :content => 'Edit', - :attributes => { :href => "/issues/bulk_edit?#{ids}", - :class => 'icon-edit' } - assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", - :class => '' } - assert_tag :tag => 'a', :content => 'Immediate', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", - :class => '' } - assert_tag :tag => 'a', :content => 'John Smith', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=2", - :class => '' } - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => "/issues?#{ids}", - :class => 'icon-del' } + + assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit' + assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete' + + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed' + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate' + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=2", :text => 'John Smith' end def test_context_menu_should_include_list_custom_fields @@ -122,17 +107,14 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'List', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => 3}} - - assert_tag 'a', - :content => 'Foo', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo"} - assert_tag 'a', - :content => 'none', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__"} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'List' + assert_select 'ul' do + assert_select 'a', 3 + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo", :text => 'Foo' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' + end + end end def test_context_menu_should_not_include_null_value_for_required_custom_fields @@ -141,10 +123,13 @@ @request.session[:user_id] = 2 get :issues, :ids => [1, 2] - assert_tag 'a', - :content => 'List', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => 2}} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'List' + assert_select 'ul' do + assert_select 'a', 2 + assert_select 'a', :text => 'none', :count => 0 + end + end end def test_context_menu_on_single_issue_should_select_current_custom_field_value @@ -156,13 +141,13 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'List', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => 3}} - assert_tag 'a', - :content => 'Bar', - :attributes => {:class => /icon-checked/} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'List' + assert_select 'ul' do + assert_select 'a', 3 + assert_select 'a.icon-checked', :text => 'Bar' + end + end end def test_context_menu_should_include_bool_custom_fields @@ -171,14 +156,15 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'Bool', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => 3}} - - assert_tag 'a', - :content => 'Yes', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=1"} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'Bool' + assert_select 'ul' do + assert_select 'a', 3 + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=0", :text => 'No' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=1", :text => 'Yes' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' + end + end end def test_context_menu_should_include_user_custom_fields @@ -187,14 +173,14 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'User', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => Project.find(1).members.count + 1}} - - assert_tag 'a', - :content => 'John Smith', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=2"} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'User' + assert_select 'ul' do + assert_select 'a', Project.find(1).members.count + 1 + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=2", :text => 'John Smith' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' + end + end end def test_context_menu_should_include_version_custom_fields @@ -202,14 +188,14 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'Version', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => Project.find(1).shared_versions.count + 1}} - - assert_tag 'a', - :content => '2.0', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=3"} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'Version' + assert_select 'ul' do + assert_select 'a', Project.find(1).shared_versions.count + 1 + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=3", :text => '2.0' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' + end + end end def test_context_menu_by_assignable_user_should_include_assigned_to_me_link @@ -218,9 +204,7 @@ assert_response :success assert_template 'context_menu' - assert_tag :tag => 'a', :content => / me /, - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=2', - :class => '' } + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=2', :text => / me / end def test_context_menu_should_propose_shared_versions_for_issues_from_different_projects @@ -232,25 +216,28 @@ assert_template 'context_menu' assert_include version, assigns(:versions) - assert_tag :tag => 'a', :content => 'eCookbook - Shared' + assert_select 'a', :text => 'eCookbook - Shared' end - def test_context_menu_issue_visibility - get :issues, :ids => [1, 4] - assert_response :success - assert_template 'context_menu' - assert_equal [1], assigns(:issues).collect(&:id) + def test_context_menu_with_issue_that_is_not_visible_should_fail + get :issues, :ids => [1, 4] # issue 4 is not visible + assert_response 302 end - + + def test_should_respond_with_404_without_ids + get :issues + assert_response 404 + end + def test_time_entries_context_menu @request.session[:user_id] = 2 get :time_entries, :ids => [1, 2] assert_response :success assert_template 'time_entries' - assert_tag 'a', :content => 'Edit' - assert_no_tag 'a', :content => 'Edit', :attributes => {:class => /disabled/} + + assert_select 'a:not(.disabled)', :text => 'Edit' end - + def test_time_entries_context_menu_without_edit_permission @request.session[:user_id] = 2 Role.find_by_name('Manager').remove_permission! :edit_time_entries @@ -258,6 +245,6 @@ get :time_entries, :ids => [1, 2] assert_response :success assert_template 'time_entries' - assert_tag 'a', :content => 'Edit', :attributes => {:class => /disabled/} + assert_select 'a.disabled', :text => 'Edit' end end diff -r d98d22a98252 -r afce8026aaeb test/functional/custom_fields_controller_test.rb --- a/test/functional/custom_fields_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/custom_fields_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class CustomFieldsControllerTest < ActionController::TestCase - fixtures :custom_fields, :custom_values, :trackers, :users + fixtures :custom_fields, :custom_values, :trackers, :users, :projects def setup @request.session[:user_id] = 1 @@ -52,10 +52,46 @@ assert_select 'option[value=user]', :text => 'User' assert_select 'option[value=version]', :text => 'Version' end + assert_select 'input[type=checkbox][name=?]', 'custom_field[project_ids][]', Project.count + assert_select 'input[type=hidden][name=?]', 'custom_field[project_ids][]', 1 assert_select 'input[type=hidden][name=type][value=IssueCustomField]' end end + def test_new_time_entry_custom_field_should_not_show_trackers_and_projects + get :new, :type => 'TimeEntryCustomField' + assert_response :success + assert_template 'new' + assert_select 'form#custom_field_form' do + assert_select 'input[name=?]', 'custom_field[tracker_ids][]', 0 + assert_select 'input[name=?]', 'custom_field[project_ids][]', 0 + end + end + + def test_default_value_should_be_an_input_for_string_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'string'} + assert_response :success + assert_select 'input[name=?]', 'custom_field[default_value]' + end + + def test_default_value_should_be_a_textarea_for_text_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'text'} + assert_response :success + assert_select 'textarea[name=?]', 'custom_field[default_value]' + end + + def test_default_value_should_be_a_checkbox_for_bool_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'bool'} + assert_response :success + assert_select 'input[name=?][type=checkbox]', 'custom_field[default_value]' + end + + def test_default_value_should_not_be_present_for_user_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'user'} + assert_response :success + assert_select '[name=?]', 'custom_field[default_value]', 0 + end + def test_new_js get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'list'}, :format => 'js' assert_response :success @@ -94,6 +130,17 @@ assert_equal 1, field.trackers.size end + def test_create_with_project_ids + assert_difference 'CustomField.count' do + post :create, :type => "IssueCustomField", :custom_field => { + :name => "foo", :field_format => "string", :is_for_all => "0", :project_ids => ["1", "3", ""] + } + assert_response 302 + end + field = IssueCustomField.order("id desc").first + assert_equal [1, 3], field.projects.map(&:id).sort + end + def test_create_with_failure assert_no_difference 'CustomField.count' do post :create, :type => "IssueCustomField", :custom_field => {:name => ''} @@ -129,7 +176,7 @@ end def test_destroy - custom_values_count = CustomValue.count(:conditions => {:custom_field_id => 1}) + custom_values_count = CustomValue.where(:custom_field_id => 1).count assert custom_values_count > 0 assert_difference 'CustomField.count', -1 do diff -r d98d22a98252 -r afce8026aaeb test/functional/documents_controller_test.rb --- a/test/functional/documents_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/documents_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/functional/enumerations_controller_test.rb --- a/test/functional/enumerations_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/enumerations_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -117,7 +117,7 @@ end def test_destroy_enumeration_in_use_with_reassignment - issue = Issue.find(:first, :conditions => {:priority_id => 4}) + issue = Issue.where(:priority_id => 4).first assert_difference 'IssuePriority.count', -1 do delete :destroy, :id => 4, :reassign_to_id => 6 end @@ -126,4 +126,11 @@ # check that the issue was reassign assert_equal 6, issue.reload.priority_id end + + def test_destroy_enumeration_in_use_with_blank_reassignment + assert_no_difference 'IssuePriority.count' do + delete :destroy, :id => 4, :reassign_to_id => '' + end + assert_response :success + end end diff -r d98d22a98252 -r afce8026aaeb test/functional/files_controller_test.rb --- a/test/functional/files_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/files_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../test_helper', __FILE__) class FilesControllerTest < ActionController::TestCase @@ -8,15 +25,11 @@ :member_roles, :members, :enabled_modules, - :workflows, :journals, :journal_details, :attachments, :versions def setup - @controller = FilesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new @request.session[:user_id] = nil Setting.default_language = 'en' end @@ -68,7 +81,7 @@ end end assert_redirected_to '/projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') + a = Attachment.order('created_on DESC').first assert_equal 'testfile.txt', a.filename assert_equal Project.find(1), a.container @@ -88,7 +101,7 @@ assert_response :redirect end assert_redirected_to '/projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') + a = Attachment.order('created_on DESC').first assert_equal 'testfile.txt', a.filename assert_equal Version.find(2), a.container end diff -r d98d22a98252 -r afce8026aaeb test/functional/gantts_controller_test.rb --- a/test/functional/gantts_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/gantts_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,83 +25,98 @@ :member_roles, :members, :enabled_modules, - :workflows, :versions - def test_gantt_should_work - i2 = Issue.find(2) - i2.update_attribute(:due_date, 1.month.from_now) - get :show, :project_id => 1 + def test_gantt_should_work + i2 = Issue.find(2) + i2.update_attribute(:due_date, 1.month.from_now) + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + # Issue with start and due dates + i = Issue.find(1) + assert_not_nil i.due_date + assert_select "div a.issue", /##{i.id}/ + # Issue with on a targeted version should not be in the events but loaded in the html + i = Issue.find(2) + assert_select "div a.issue", /##{i.id}/ + end + + def test_gantt_should_work_without_issue_due_dates + Issue.update_all("due_date = NULL") + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_work_without_issue_and_version_due_dates + Issue.update_all("due_date = NULL") + Version.update_all("effective_date = NULL") + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_work_cross_project + get :show + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + assert_not_nil assigns(:gantt).query + assert_nil assigns(:gantt).project + end + + def test_gantt_should_not_disclose_private_projects + get :show + assert_response :success + assert_template 'gantts/show' + assert_tag 'a', :content => /eCookbook/ + # Root private project + assert_no_tag 'a', {:content => /OnlineStore/} + # Private children of a public project + assert_no_tag 'a', :content => /Private child of eCookbook/ + end + + def test_gantt_should_display_relations + IssueRelation.delete_all + issue1 = Issue.generate!(:start_date => 1.day.from_now.to_date, :due_date => 3.day.from_now.to_date) + issue2 = Issue.generate!(:start_date => 1.day.from_now.to_date, :due_date => 3.day.from_now.to_date) + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => 'precedes') + + get :show + assert_response :success + + relations = assigns(:gantt).relations + assert_kind_of Hash, relations + assert relations.present? + assert_select 'div.task_todo[id=?][data-rels*=?]', "task-todo-issue-#{issue1.id}", issue2.id.to_s + assert_select 'div.task_todo[id=?]:not([data-rels])', "task-todo-issue-#{issue2.id}" + end + + def test_gantt_should_export_to_pdf + get :show, :project_id => 1, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_export_to_pdf_cross_project + get :show, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + assert_not_nil assigns(:gantt) + end + + if Object.const_defined?(:Magick) + def test_gantt_should_export_to_png + get :show, :project_id => 1, :format => 'png' assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - # Issue with start and due dates - i = Issue.find(1) - assert_not_nil i.due_date - assert_select "div a.issue", /##{i.id}/ - # Issue with on a targeted version should not be in the events but loaded in the html - i = Issue.find(2) - assert_select "div a.issue", /##{i.id}/ + assert_equal 'image/png', @response.content_type end - - def test_gantt_should_work_without_issue_due_dates - Issue.update_all("due_date = NULL") - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_work_without_issue_and_version_due_dates - Issue.update_all("due_date = NULL") - Version.update_all("effective_date = NULL") - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_work_cross_project - get :show - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - assert_not_nil assigns(:gantt).query - assert_nil assigns(:gantt).project - end - - def test_gantt_should_not_disclose_private_projects - get :show - assert_response :success - assert_template 'gantts/show' - assert_tag 'a', :content => /eCookbook/ - # Root private project - assert_no_tag 'a', {:content => /OnlineStore/} - # Private children of a public project - assert_no_tag 'a', :content => /Private child of eCookbook/ - end - - def test_gantt_should_export_to_pdf - get :show, :project_id => 1, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_export_to_pdf_cross_project - get :show, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - if Object.const_defined?(:Magick) - def test_gantt_should_export_to_png - get :show, :project_id => 1, :format => 'png' - assert_response :success - assert_equal 'image/png', @response.content_type - end - end + end end diff -r d98d22a98252 -r afce8026aaeb test/functional/groups_controller_test.rb --- a/test/functional/groups_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/groups_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -79,8 +79,11 @@ get :edit, :id => 10 assert_response :success assert_template 'edit' - assert_tag 'div', :attributes => {:id => 'tab-content-users'} - assert_tag 'div', :attributes => {:id => 'tab-content-memberships'} + + assert_select 'div#tab-content-users' + assert_select 'div#tab-content-memberships' do + assert_select 'a', :text => 'Private child of eCookbook' + end end def test_update @@ -192,11 +195,8 @@ end def test_autocomplete_for_user - get :autocomplete_for_user, :id => 10, :q => 'mis' + get :autocomplete_for_user, :id => 10, :q => 'smi', :format => 'js' assert_response :success - users = assigns(:users) - assert_not_nil users - assert users.any? - assert !users.include?(Group.find(10).users.first) + assert_include 'John Smith', response.body end end diff -r d98d22a98252 -r afce8026aaeb test/functional/issue_categories_controller_test.rb --- a/test/functional/issue_categories_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/issue_categories_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,19 +16,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'issue_categories_controller' - -# Re-raise errors caught by the controller. -class IssueCategoriesController; def rescue_action(e) raise e end; end class IssueCategoriesControllerTest < ActionController::TestCase fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules, :issue_categories, :issues def setup - @controller = IssueCategoriesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 2 end @@ -133,7 +126,7 @@ end def test_destroy_category_in_use_with_reassignment - issue = Issue.find(:first, :conditions => {:category_id => 1}) + issue = Issue.where(:category_id => 1).first delete :destroy, :id => 1, :todo => 'reassign', :reassign_to_id => 2 assert_redirected_to '/projects/ecookbook/settings/categories' assert_nil IssueCategory.find_by_id(1) @@ -142,7 +135,7 @@ end def test_destroy_category_in_use_without_reassignment - issue = Issue.find(:first, :conditions => {:category_id => 1}) + issue = Issue.where(:category_id => 1).first delete :destroy, :id => 1, :todo => 'nullify' assert_redirected_to '/projects/ecookbook/settings/categories' assert_nil IssueCategory.find_by_id(1) diff -r d98d22a98252 -r afce8026aaeb test/functional/issue_relations_controller_test.rb --- a/test/functional/issue_relations_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/issue_relations_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -28,7 +28,8 @@ :issue_relations, :enabled_modules, :enumerations, - :trackers + :trackers, + :projects_trackers def setup User.current = nil @@ -87,6 +88,17 @@ end end + def test_create_follows_relation_should_update_relations_list + issue1 = Issue.generate!(:subject => 'Followed issue', :start_date => Date.yesterday, :due_date => Date.today) + issue2 = Issue.generate! + + assert_difference 'IssueRelation.count' do + xhr :post, :create, :issue_id => issue2.id, + :relation => {:issue_to_id => issue1.id, :relation_type => 'follows', :delay => ''} + end + assert_match /Followed issue/, response.body + end + def test_should_create_relations_with_visible_issues_only Setting.cross_project_issue_relations = '1' assert_nil Issue.visible(User.find(3)).find_by_id(4) @@ -97,8 +109,6 @@ end end - should "prevent relation creation when there's a circular dependency" - def test_create_xhr_with_failure assert_no_difference 'IssueRelation.count' do xhr :post, :create, :issue_id => 3, :relation => {:issue_to_id => '999', :relation_type => 'relates', :delay => ''} diff -r d98d22a98252 -r afce8026aaeb test/functional/issue_statuses_controller_test.rb --- a/test/functional/issue_statuses_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/issue_statuses_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,17 +1,26 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../test_helper', __FILE__) -require 'issue_statuses_controller' - -# Re-raise errors caught by the controller. -class IssueStatusesController; def rescue_action(e) raise e end; end - class IssueStatusesControllerTest < ActionController::TestCase fixtures :issue_statuses, :issues, :users def setup - @controller = IssueStatusesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end @@ -45,7 +54,7 @@ post :create, :issue_status => {:name => 'New status'} end assert_redirected_to :action => 'index' - status = IssueStatus.find(:first, :order => 'id DESC') + status = IssueStatus.order('id DESC').first assert_equal 'New status', status.name end diff -r d98d22a98252 -r afce8026aaeb test/functional/issues_controller_test.rb --- a/test/functional/issues_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/issues_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'issues_controller' class IssuesControllerTest < ActionController::TestCase fixtures :projects, @@ -48,9 +47,6 @@ include Redmine::I18n def setup - @controller = IssuesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -297,7 +293,7 @@ end end - def test_index_with_query_grouped_by_tracker + def test_index_with_query_grouped_by_tracker_in_normal_order 3.times {|i| Issue.generate!(:tracker_id => (i + 1))} get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc' @@ -331,7 +327,7 @@ end def test_index_with_cross_project_query_in_session_should_show_project_issues - q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil) + q = IssueQuery.create!(:name => "test", :user_id => 2, :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil) @request.session[:query] = {:id => q.id, :project_id => 1} with_settings :display_subprojects_issues => '0' do @@ -345,7 +341,7 @@ end def test_private_query_should_not_be_available_to_other_users - q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil) + q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil) @request.session[:user_id] = 3 get :index, :query_id => q.id @@ -353,7 +349,7 @@ end def test_private_query_should_be_available_to_its_user - q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil) + q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil) @request.session[:user_id] = 2 get :index, :query_id => q.id @@ -361,7 +357,7 @@ end def test_public_query_should_be_available_to_other_users - q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil) + q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PUBLIC, :project => nil) @request.session[:user_id] = 3 get :index, :query_id => q.id @@ -384,7 +380,7 @@ assert_equal 'text/csv; header=present', @response.content_type assert @response.body.starts_with?("#,") lines = @response.body.chomp.split("\n") - assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size + assert_equal assigns(:query).columns.size, lines[0].split(',').size end def test_index_csv_with_project @@ -395,13 +391,18 @@ end def test_index_csv_with_description - get :index, :format => 'csv', :description => '1' - assert_response :success - assert_not_nil assigns(:issues) - assert_equal 'text/csv; header=present', @response.content_type - assert @response.body.starts_with?("#,") - lines = @response.body.chomp.split("\n") - assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size + Issue.generate!(:description => 'test_index_csv_with_description') + + with_settings :default_language => 'en' do + get :index, :format => 'csv', :description => '1' + assert_response :success + assert_not_nil assigns(:issues) + end + + assert_equal 'text/csv; header=present', response.content_type + headers = response.body.chomp.split("\n").first.split(',') + assert_include 'Description', headers + assert_include 'test_index_csv_with_description', response.body end def test_index_csv_with_spent_time_column @@ -420,9 +421,9 @@ assert_response :success assert_not_nil assigns(:issues) assert_equal 'text/csv; header=present', @response.content_type - assert @response.body.starts_with?("#,") - lines = @response.body.chomp.split("\n") - assert_equal assigns(:query).available_inline_columns.size + 1, lines[0].split(',').size + assert_match /\A#,/, response.body + lines = response.body.chomp.split("\n") + assert_equal assigns(:query).available_inline_columns.size, lines[0].split(',').size end def test_index_csv_with_multi_column_field @@ -437,6 +438,25 @@ assert lines.detect {|line| line.include?('"MySQL, Oracle"')} end + def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator + field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float') + issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'}) + + with_settings :default_language => 'fr' do + get :index, :format => 'csv', :columns => 'all' + assert_response :success + issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s} + assert_include '185,60', issue_line + end + + with_settings :default_language => 'en' do + get :index, :format => 'csv', :columns => 'all' + assert_response :success + issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s} + assert_include '185.60', issue_line + end + end + def test_index_csv_big_5 with_settings :default_language => "zh-TW" do str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88" @@ -457,8 +477,8 @@ if str_utf8.respond_to?(:force_encoding) s1.force_encoding('Big5') end - assert lines[0].include?(s1) - assert lines[1].include?(str_big5) + assert_include s1, lines[0] + assert_include str_big5, lines[1] end end @@ -670,7 +690,7 @@ # query should use specified columns query = assigns(:query) - assert_kind_of Query, query + assert_kind_of IssueQuery, query assert_equal columns, query.column_names.map(&:to_s) # columns should be stored in session @@ -692,18 +712,18 @@ # query should use specified columns query = assigns(:query) - assert_kind_of Query, query - assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name) + assert_kind_of IssueQuery, query + assert_equal [:id, :project, :tracker, :subject, :assigned_to], query.columns.map(&:name) end def test_index_without_project_and_explicit_default_columns_should_not_add_project_column Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to'] - columns = ['tracker', 'subject', 'assigned_to'] + columns = ['id', 'tracker', 'subject', 'assigned_to'] get :index, :set_filter => 1, :c => columns # query should use specified columns query = assigns(:query) - assert_kind_of Query, query + assert_kind_of IssueQuery, query assert_equal columns.map(&:to_sym), query.columns.map(&:name) end @@ -714,7 +734,7 @@ # query should use specified columns query = assigns(:query) - assert_kind_of Query, query + assert_kind_of IssueQuery, query assert_equal columns, query.column_names.map(&:to_s) assert_select 'table.issues td.cf_2.string' @@ -753,18 +773,14 @@ def test_index_with_date_column with_settings :date_format => '%d/%m/%Y' do Issue.find(1).update_attribute :start_date, '1987-08-24' - get :index, :set_filter => 1, :c => %w(start_date) - assert_select "table.issues td.start_date", :text => '24/08/1987' end end def test_index_with_done_ratio_column Issue.find(1).update_attribute :done_ratio, 40 - get :index, :set_filter => 1, :c => %w(done_ratio) - assert_select 'table.issues td.done_ratio' do assert_select 'table.progress' do assert_select 'td.closed[style=?]', 'width: 40%;' @@ -774,20 +790,17 @@ def test_index_with_spent_hours_column get :index, :set_filter => 1, :c => %w(subject spent_hours) - assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00' end def test_index_should_not_show_spent_hours_column_without_permission Role.anonymous.remove_permission! :view_time_entries get :index, :set_filter => 1, :c => %w(subject spent_hours) - assert_select 'td.spent_hours', 0 end def test_index_with_fixed_version_column get :index, :set_filter => 1, :c => %w(fixed_version) - assert_select 'table.issues td.fixed_version' do assert_select 'a[href=?]', '/versions/2', :text => '1.0' end @@ -857,9 +870,7 @@ assert_response :success assert_template 'show' assert_equal Issue.find(1), assigns(:issue) - assert_select 'div.issue div.description', :text => /Unable to print recipes/ - # anonymous role is allowed to add a note assert_select 'form#issue-form' do assert_select 'fieldset' do @@ -867,7 +878,6 @@ assert_select 'textarea[name=?]', 'issue[notes]' end end - assert_select 'title', :text => "Bug #1: Can't print recipes - eCookbook - Redmine" end @@ -875,9 +885,7 @@ @request.session[:user_id] = 2 get :show, :id => 1 assert_response :success - assert_select 'a', :text => /Quote/ - assert_select 'form#issue-form' do assert_select 'fieldset' do assert_select 'legend', :text => 'Change properties' @@ -899,24 +907,25 @@ get :show, :id => 1 assert_response :success - assert_tag 'form', :attributes => {:id => 'issue-form'} - assert_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' } - assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} - assert_tag 'textarea', :attributes => {:name => 'issue[notes]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]' + assert_select 'select[name=?]', 'issue[project_id]' + assert_select 'select[name=?]', 'issue[tracker_id]' + assert_select 'input[name=?]', 'issue[subject]' + assert_select 'textarea[name=?]', 'issue[description]' + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]' + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]' + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]' + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]' + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?]', 'issue[custom_field_values][2]' + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + assert_select 'textarea[name=?]', 'issue[notes]' + end end def test_show_should_display_update_form_with_minimal_permissions @@ -927,24 +936,25 @@ get :show, :id => 1 assert_response :success - assert_tag 'form', :attributes => {:id => 'issue-form'} - assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' } - assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} - assert_tag 'textarea', :attributes => {:name => 'issue[notes]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]', 0 + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]', 0 + assert_select 'input[name=?]', 'issue[subject]', 0 + assert_select 'textarea[name=?]', 'issue[description]', 0 + assert_select 'select[name=?]', 'issue[status_id]', 0 + assert_select 'select[name=?]', 'issue[priority_id]', 0 + assert_select 'select[name=?]', 'issue[assigned_to_id]', 0 + assert_select 'select[name=?]', 'issue[category_id]', 0 + assert_select 'select[name=?]', 'issue[fixed_version_id]', 0 + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + assert_select 'input[name=?]', 'issue[start_date]', 0 + assert_select 'input[name=?]', 'issue[due_date]', 0 + assert_select 'select[name=?]', 'issue[done_ratio]', 0 + assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0 + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + assert_select 'textarea[name=?]', 'issue[notes]' + end end def test_show_should_display_update_form_with_workflow_permissions @@ -954,24 +964,25 @@ get :show, :id => 1 assert_response :success - assert_tag 'form', :attributes => {:id => 'issue-form'} - assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' } - assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} - assert_tag 'textarea', :attributes => {:name => 'issue[notes]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]', 0 + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]', 0 + assert_select 'input[name=?]', 'issue[subject]', 0 + assert_select 'textarea[name=?]', 'issue[description]', 0 + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]', 0 + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]', 0 + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + assert_select 'input[name=?]', 'issue[start_date]', 0 + assert_select 'input[name=?]', 'issue[due_date]', 0 + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0 + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + assert_select 'textarea[name=?]', 'issue[notes]' + end end def test_show_should_not_display_update_form_without_permissions @@ -1004,7 +1015,7 @@ get :show, :id => 1 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do - assert_select 'input[type=file][name=?]', 'attachments[1][file]' + assert_select 'input[type=file][name=?]', 'attachments[dummy][file]' end end @@ -1140,7 +1151,7 @@ end def test_show_should_display_prev_next_links_with_saved_query_in_session - query = Query.create!(:name => 'test', :is_public => true, :user_id => 1, + query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1, :filters => {'status_id' => {:values => ['5'], :operator => '='}}, :sort_criteria => [['id', 'asc']]) @request.session[:query] = {:id => query.id, :project_id => nil} @@ -1232,7 +1243,7 @@ CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3') CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '') - query = Query.create!(:name => 'test', :is_public => true, :user_id => 1, :filters => {}, + query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1, :filters => {}, :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']]) @request.session[:query] = {:id => query.id, :project_id => nil} @@ -1420,33 +1431,41 @@ assert @response.body.starts_with?('%PDF') end + def test_show_invalid_should_respond_with_404 + get :show, :id => 999 + assert_response 404 + end + def test_get_new @request.session[:user_id] = 2 get :new, :project_id => 1, :tracker_id => 1 assert_response :success assert_template 'new' - assert_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' } - assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]' + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]' + assert_select 'input[name=?]', 'issue[subject]' + assert_select 'textarea[name=?]', 'issue[description]' + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]' + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]' + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]' + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]' + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string' + assert_select 'input[name=?]', 'issue[watcher_user_ids][]' + end # Be sure we don't display inactive IssuePriorities assert ! IssuePriority.find(15).active? - assert_no_tag :option, :attributes => {:value => '15'}, - :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} } + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=15]', 0 + end end def test_get_new_with_minimal_permissions @@ -1458,22 +1477,24 @@ assert_response :success assert_template 'new' - assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' } - assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]', 0 + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]' + assert_select 'input[name=?]', 'issue[subject]' + assert_select 'textarea[name=?]', 'issue[description]' + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]' + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]' + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]' + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string' + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + end end def test_get_new_with_list_custom_field @@ -1541,26 +1562,25 @@ end def test_get_new_without_default_start_date_is_creation_date - Setting.default_issue_start_date_to_creation_date = 0 - - @request.session[:user_id] = 2 - get :new, :project_id => 1, :tracker_id => 1 - assert_response :success - assert_template 'new' - - assert_select 'input[name=?]', 'issue[start_date]' - assert_select 'input[name=?][value]', 'issue[start_date]', 0 + with_settings :default_issue_start_date_to_creation_date => 0 do + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'new' + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?][value]', 'issue[start_date]', 0 + end end def test_get_new_with_default_start_date_is_creation_date - Setting.default_issue_start_date_to_creation_date = 1 - - @request.session[:user_id] = 2 - get :new, :project_id => 1, :tracker_id => 1 - assert_response :success - assert_template 'new' - - assert_select 'input[name=?][value=?]', 'issue[start_date]', Date.today.to_s + with_settings :default_issue_start_date_to_creation_date => 1 do + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'new' + assert_select 'input[name=?][value=?]', 'issue[start_date]', + Date.today.to_s + end end def test_get_new_form_should_allow_attachment_upload @@ -1568,8 +1588,7 @@ get :new, :project_id => 1, :tracker_id => 1 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do - assert_select 'input[name=?][type=file]', 'attachments[1][file]' - assert_select 'input[name=?][maxlength=255]', 'attachments[1][description]' + assert_select 'input[name=?][type=file]', 'attachments[dummy][file]' end end @@ -1663,9 +1682,9 @@ assert_error_tag :content => /No tracker/ end - def test_update_new_form + def test_update_form_for_new_issue @request.session[:user_id] = 2 - xhr :post, :new, :project_id => 1, + xhr :post, :update_form, :project_id => 1, :issue => {:tracker_id => 2, :subject => 'This is the test_new issue', :description => 'This is the description', @@ -1682,14 +1701,14 @@ assert_equal 'This is the test_new issue', issue.subject end - def test_update_new_form_should_propose_transitions_based_on_initial_status + def test_update_form_for_new_issue_should_propose_transitions_based_on_initial_status @request.session[:user_id] = 2 WorkflowTransition.delete_all WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2) WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5) WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4) - xhr :post, :new, :project_id => 1, + xhr :post, :update_form, :project_id => 1, :issue => {:tracker_id => 1, :status_id => 5, :subject => 'This is an issue'} @@ -1720,7 +1739,7 @@ assert_equal 2, issue.status_id assert_equal Date.parse('2010-11-07'), issue.start_date assert_nil issue.estimated_hours - v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2}) + v = issue.custom_values.where(:custom_field_id => 2).first assert_not_nil v assert_equal 'Value for field 2', v.value end @@ -1748,11 +1767,10 @@ end def test_post_create_without_start_date_and_default_start_date_is_not_creation_date - Setting.default_issue_start_date_to_creation_date = 0 - - @request.session[:user_id] = 2 - assert_difference 'Issue.count' do - post :create, :project_id => 1, + with_settings :default_issue_start_date_to_creation_date => 0 do + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, :issue => {:tracker_id => 3, :status_id => 2, :subject => 'This is the test_new issue', @@ -1760,20 +1778,20 @@ :priority_id => 5, :estimated_hours => '', :custom_field_values => {'2' => 'Value for field 2'}} + end + assert_redirected_to :controller => 'issues', :action => 'show', + :id => Issue.last.id + issue = Issue.find_by_subject('This is the test_new issue') + assert_not_nil issue + assert_nil issue.start_date end - assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id - - issue = Issue.find_by_subject('This is the test_new issue') - assert_not_nil issue - assert_nil issue.start_date end def test_post_create_without_start_date_and_default_start_date_is_creation_date - Setting.default_issue_start_date_to_creation_date = 1 - - @request.session[:user_id] = 2 - assert_difference 'Issue.count' do - post :create, :project_id => 1, + with_settings :default_issue_start_date_to_creation_date => 1 do + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, :issue => {:tracker_id => 3, :status_id => 2, :subject => 'This is the test_new issue', @@ -1781,12 +1799,13 @@ :priority_id => 5, :estimated_hours => '', :custom_field_values => {'2' => 'Value for field 2'}} + end + assert_redirected_to :controller => 'issues', :action => 'show', + :id => Issue.last.id + issue = Issue.find_by_subject('This is the test_new issue') + assert_not_nil issue + assert_equal Date.today, issue.start_date end - assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id - - issue = Issue.find_by_subject('This is the test_new issue') - assert_not_nil issue - assert_equal Date.today, issue.start_date end def test_post_create_and_continue @@ -2082,19 +2101,15 @@ assert_response :success assert_template 'new' - assert_tag :textarea, :attributes => { :name => 'issue[description]' }, - :content => "\nThis is a description" - assert_tag :select, :attributes => { :name => 'issue[priority_id]' }, - :child => { :tag => 'option', :attributes => { :selected => 'selected', - :value => '6' }, - :content => 'High' } + assert_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description' + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=6][selected=selected]', :text => 'High' + end # Custom fields - assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' }, - :child => { :tag => 'option', :attributes => { :selected => 'selected', - :value => 'Oracle' }, - :content => 'Oracle' } - assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]', - :value => 'Value for field 2'} + assert_select 'select[name=?]', 'issue[custom_field_values][1]' do + assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle' + end + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2' end def test_post_create_with_failure_should_preserve_watchers @@ -2107,9 +2122,9 @@ assert_response :success assert_template 'new' - assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '2', :checked => nil} - assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '3', :checked => 'checked'} - assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '8', :checked => 'checked'} + assert_select 'input[name=?][value=2]:not(checked)', 'issue[watcher_user_ids][]' + assert_select 'input[name=?][value=3][checked=checked]', 'issue[watcher_user_ids][]' + assert_select 'input[name=?][value=8][checked=checked]', 'issue[watcher_user_ids][]' end def test_post_create_should_ignore_non_safe_attributes @@ -2144,6 +2159,25 @@ assert_equal 59, File.size(attachment.diskfile) end + def test_post_create_with_attachment_should_notify_with_attachments + ActionMailer::Base.deliveries.clear + set_tmp_attachments_directory + @request.session[:user_id] = 2 + + with_settings :host_name => 'mydomain.foo', :protocol => 'http' do + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => { :tracker_id => '1', :subject => 'With attachment' }, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}} + end + end + + assert_not_nil ActionMailer::Base.deliveries.last + assert_select_email do + assert_select 'a[href^=?]', 'http://mydomain.foo/attachments/download', 'testfile.txt' + end + end + def test_post_create_with_failure_should_save_attachments set_tmp_attachments_directory @request.session[:user_id] = 2 @@ -2163,8 +2197,8 @@ assert File.exists?(attachment.diskfile) assert_nil attachment.container - assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' end def test_post_create_with_failure_should_keep_saved_attachments @@ -2182,8 +2216,8 @@ end end - assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' end def test_post_create_should_attach_saved_attachments @@ -2218,10 +2252,10 @@ get :new, :project_id => 1 assert_response :success assert_template 'new' - assert_tag :tag => 'select', - :attributes => {:name => 'issue[status_id]'}, - :children => {:count => 1}, - :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}} + assert_select 'select[name=?]', 'issue[status_id]' do + assert_select 'option', 1 + assert_select 'option[value=?]', IssueStatus.default.id.to_s + end end should "accept default status" do @@ -2349,13 +2383,13 @@ assert_equal orig.subject, assigns(:issue).subject assert assigns(:issue).copy? - assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'}, - :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}, :content => 'eCookbook'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'}, - :child => {:tag => 'option', :attributes => {:value => '2', :selected => nil}, :content => 'OnlineStore'} - assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'} + assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do + assert_select 'select[name=?]', 'issue[project_id]' do + assert_select 'option[value=1][selected=selected]', :text => 'eCookbook' + assert_select 'option[value=2]:not([selected])', :text => 'OnlineStore' + end + assert_select 'input[name=copy_from][value=1]' + end # "New issue" menu item should not link to copy assert_select '#main-menu a.new-issue[href=/projects/ecookbook/issues/new]' @@ -2367,7 +2401,7 @@ assert issue.attachments.count > 0 get :new, :project_id => 1, :copy_from => 3 - assert_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'} + assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value=1]' end def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox @@ -2376,7 +2410,7 @@ issue.attachments.delete_all get :new, :project_id => 1, :copy_from => 3 - assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'} + assert_select 'input[name=copy_attachments]', 0 end def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox @@ -2415,12 +2449,12 @@ issue = Issue.find(3) count = issue.attachments.count assert count > 0 - assert_difference 'Issue.count' do assert_difference 'Attachment.count', count do - assert_no_difference 'Journal.count' do + assert_difference 'Journal.count', 2 do post :create, :project_id => 1, :copy_from => 3, - :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}, + :issue => {:project_id => '1', :tracker_id => '3', + :status_id => '1', :subject => 'Copy with attachments'}, :copy_attachments => '1' end end @@ -2435,12 +2469,12 @@ issue = Issue.find(3) count = issue.attachments.count assert count > 0 - assert_difference 'Issue.count' do assert_no_difference 'Attachment.count' do - assert_no_difference 'Journal.count' do + assert_difference 'Journal.count', 2 do post :create, :project_id => 1, :copy_from => 3, - :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'} + :issue => {:project_id => '1', :tracker_id => '3', + :status_id => '1', :subject => 'Copy with attachments'} end end end @@ -2453,14 +2487,16 @@ issue = Issue.find(3) count = issue.attachments.count assert count > 0 - assert_difference 'Issue.count' do assert_difference 'Attachment.count', count + 1 do - assert_no_difference 'Journal.count' do + assert_difference 'Journal.count', 2 do post :create, :project_id => 1, :copy_from => 3, - :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}, + :issue => {:project_id => '1', :tracker_id => '3', + :status_id => '1', :subject => 'Copy with attachments'}, :copy_attachments => '1', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}} + :attachments => {'1' => + {'file' => uploaded_test_file('testfile.txt', 'text/plain'), + 'description' => 'test file'}} end end end @@ -2470,11 +2506,11 @@ def test_create_as_copy_should_add_relation_with_copied_issue @request.session[:user_id] = 2 - assert_difference 'Issue.count' do assert_difference 'IssueRelation.count' do post :create, :project_id => 1, :copy_from => 1, - :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy'} + :issue => {:project_id => '1', :tracker_id => '3', + :status_id => '1', :subject => 'Copy'} end end copy = Issue.first(:order => 'id DESC') @@ -2485,11 +2521,11 @@ @request.session[:user_id] = 2 issue = Issue.generate_with_descendants! count = issue.descendants.count - - assert_difference 'Issue.count', count+1 do - assert_no_difference 'Journal.count' do + assert_difference 'Issue.count', count + 1 do + assert_difference 'Journal.count', (count + 1) * 2 do post :create, :project_id => 1, :copy_from => issue.id, - :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'}, + :issue => {:project_id => '1', :tracker_id => '3', + :status_id => '1', :subject => 'Copy with subtasks'}, :copy_subtasks => '1' end end @@ -2501,11 +2537,11 @@ def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks @request.session[:user_id] = 2 issue = Issue.generate_with_descendants! - assert_difference 'Issue.count', 1 do - assert_no_difference 'Journal.count' do + assert_difference 'Journal.count', 2 do post :create, :project_id => 1, :copy_from => 3, - :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'} + :issue => {:project_id => '1', :tracker_id => '3', + :status_id => '1', :subject => 'Copy with subtasks'} end end copy = Issue.where(:parent_id => nil).first(:order => 'id DESC') @@ -2523,13 +2559,13 @@ assert_not_nil assigns(:issue) assert assigns(:issue).copy? - assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'}, - :child => {:tag => 'option', :attributes => {:value => '1', :selected => nil}, :content => 'eCookbook'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'}, - :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}, :content => 'OnlineStore'} - assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'} + assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do + assert_select 'select[name=?]', 'issue[project_id]' do + assert_select 'option[value=1]:not([selected])', :text => 'eCookbook' + assert_select 'option[value=2][selected=selected]', :text => 'OnlineStore' + end + assert_select 'input[name=copy_from][value=1]' + end end def test_create_as_copy_on_project_without_permission_should_ignore_target_project @@ -2554,8 +2590,9 @@ # Be sure we don't display inactive IssuePriorities assert ! IssuePriority.find(15).active? - assert_no_tag :option, :attributes => {:value => '15'}, - :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} } + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=15]', 0 + end end def test_get_edit_should_display_the_time_entry_form_with_log_time_permission @@ -2563,7 +2600,7 @@ Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time] get :edit, :id => 1 - assert_tag 'input', :attributes => {:name => 'time_entry[hours]'} + assert_select 'input[name=?]', 'time_entry[hours]' end def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission @@ -2571,13 +2608,13 @@ Role.find_by_name('Manager').remove_permission! :log_time get :edit, :id => 1 - assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'} + assert_select 'input[name=?]', 'time_entry[hours]', 0 end def test_get_edit_with_params @request.session[:user_id] = 2 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }, - :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id } + :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 } assert_response :success assert_template 'edit' @@ -2585,22 +2622,20 @@ assert_not_nil issue assert_equal 5, issue.status_id - assert_tag :select, :attributes => { :name => 'issue[status_id]' }, - :child => { :tag => 'option', - :content => 'Closed', - :attributes => { :selected => 'selected' } } + assert_select 'select[name=?]', 'issue[status_id]' do + assert_select 'option[value=5][selected=selected]', :text => 'Closed' + end assert_equal 7, issue.priority_id - assert_tag :select, :attributes => { :name => 'issue[priority_id]' }, - :child => { :tag => 'option', - :content => 'Urgent', - :attributes => { :selected => 'selected' } } - - assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' } - assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' }, - :child => { :tag => 'option', - :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } } - assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' } + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=7][selected=selected]', :text => 'Urgent' + end + + assert_select 'input[name=?][value=2.5]', 'time_entry[hours]' + assert_select 'select[name=?]', 'time_entry[activity_id]' do + assert_select 'option[value=10][selected=selected]', :text => 'Development' + end + assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]' end def test_get_edit_with_multi_custom_field @@ -2615,18 +2650,17 @@ assert_response :success assert_template 'edit' - assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'} - assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'}, - :child => {:tag => 'option', :attributes => {:value => 'MySQL', :selected => 'selected'}} - assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'}, - :child => {:tag => 'option', :attributes => {:value => 'PostgreSQL', :selected => nil}} - assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'}, - :child => {:tag => 'option', :attributes => {:value => 'Oracle', :selected => 'selected'}} - end - - def test_update_edit_form + assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do + assert_select 'option', 3 + assert_select 'option[value=MySQL][selected=selected]' + assert_select 'option[value=Oracle][selected=selected]' + assert_select 'option[value=PostgreSQL]:not([selected])' + end + end + + def test_update_form_for_existing_issue @request.session[:user_id] = 2 - xhr :put, :new, :project_id => 1, + xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:tracker_id => 2, :subject => 'This is the test_new issue', @@ -2645,9 +2679,9 @@ assert_equal 'This is the test_new issue', issue.subject end - def test_update_edit_form_should_keep_issue_author + def test_update_form_for_existing_issue_should_keep_issue_author @request.session[:user_id] = 3 - xhr :put, :new, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'} + xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'} assert_response :success assert_equal 'text/javascript', response.content_type @@ -2657,14 +2691,14 @@ assert_not_equal User.current, issue.author end - def test_update_edit_form_should_propose_transitions_based_on_initial_status + def test_update_form_for_existing_issue_should_propose_transitions_based_on_initial_status @request.session[:user_id] = 2 WorkflowTransition.delete_all WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1) WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5) WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4) - xhr :put, :new, :project_id => 1, + xhr :put, :update_form, :project_id => 1, :id => 2, :issue => {:tracker_id => 2, :status_id => 5, @@ -2674,9 +2708,9 @@ assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort end - def test_update_edit_form_with_project_change + def test_update_form_for_existing_issue_with_project_change @request.session[:user_id] = 2 - xhr :put, :new, :project_id => 1, + xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:project_id => 2, :tracker_id => 2, @@ -2694,6 +2728,16 @@ assert_equal 'This is the test_new issue', issue.subject end + def test_update_form_should_propose_default_status_for_existing_issue + @request.session[:user_id] = 2 + WorkflowTransition.delete_all + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3) + + xhr :put, :update_form, :project_id => 1, :id => 2 + assert_response :success + assert_equal [2,3], assigns(:allowed_statuses).map(&:id).sort + end + def test_put_update_without_custom_fields_param @request.session[:user_id] = 2 ActionMailer::Base.deliveries.clear @@ -2831,7 +2875,7 @@ assert_redirected_to :action => 'show', :id => '1' issue.reload assert_equal 2, issue.status_id - j = Journal.find(:first, :order => 'id DESC') + j = Journal.order('id DESC').first assert_equal 'Assigned to dlopper', j.notes assert_equal 2, j.details.size @@ -2848,7 +2892,7 @@ :id => 1, :issue => { :notes => notes } assert_redirected_to :action => 'show', :id => '1' - j = Journal.find(:first, :order => 'id DESC') + j = Journal.order('id DESC').first assert_equal notes, j.notes assert_equal 0, j.details.size assert_equal User.anonymous, j.user @@ -2904,7 +2948,7 @@ issue = Issue.find(1) - j = Journal.find(:first, :order => 'id DESC') + j = Journal.order('id DESC').first assert_equal '2.5 hours added', j.notes assert_equal 0, j.details.size @@ -2943,7 +2987,7 @@ end assert_redirected_to :action => 'show', :id => '1' - j = Issue.find(1).journals.find(:first, :order => 'id DESC') + j = Issue.find(1).journals.reorder('id DESC').first assert j.notes.blank? assert_equal 1, j.details.size assert_equal 'testfile.txt', j.details.first.value @@ -2982,8 +3026,8 @@ assert File.exists?(attachment.diskfile) assert_nil attachment.container - assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' end def test_put_update_with_failure_should_keep_saved_attachments @@ -3001,8 +3045,8 @@ end end - assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' end def test_put_update_should_attach_saved_attachments @@ -3092,8 +3136,8 @@ assert_template 'edit' assert_error_tag :descendant => {:content => /Activity can't be blank/} - assert_tag :textarea, :attributes => { :name => 'issue[notes]' }, :content => "\n"+notes - assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" } + assert_select 'textarea[name=?]', 'issue[notes]', :text => notes + assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z' end def test_put_update_with_invalid_spent_time_comments_only @@ -3111,8 +3155,8 @@ assert_error_tag :descendant => {:content => /Activity can't be blank/} assert_error_tag :descendant => {:content => /Hours can't be blank/} - assert_tag :textarea, :attributes => { :name => 'issue[notes]' }, :content => "\n"+notes - assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" } + assert_select 'textarea[name=?]', 'issue[notes]', :text => notes + assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment' end def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject @@ -3167,23 +3211,34 @@ assert_response :success assert_template 'bulk_edit' - assert_tag :select, :attributes => {:name => 'issue[project_id]'} - assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'} - - # Project specific custom field, date type - field = CustomField.find(9) - assert !field.is_for_all? - assert_equal 'date', field.field_format - assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'} - - # System wide custom field - assert CustomField.find(1).is_for_all? - assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'} - - # Be sure we don't display inactive IssuePriorities - assert ! IssuePriority.find(15).active? - assert_no_tag :option, :attributes => {:value => '15'}, - :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} } + assert_select 'ul#bulk-selection' do + assert_select 'li', 2 + assert_select 'li a', :text => 'Bug #1' + end + + assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do + assert_select 'input[name=?]', 'ids[]', 2 + assert_select 'input[name=?][value=1][type=hidden]', 'ids[]' + + assert_select 'select[name=?]', 'issue[project_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]' + + # Project specific custom field, date type + field = CustomField.find(9) + assert !field.is_for_all? + assert_equal 'date', field.field_format + assert_select 'input[name=?]', 'issue[custom_field_values][9]' + + # System wide custom field + assert CustomField.find(1).is_for_all? + assert_select 'select[name=?]', 'issue[custom_field_values][1]' + + # Be sure we don't display inactive IssuePriorities + assert ! IssuePriority.find(15).active? + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=15]', 0 + end + end end def test_get_bulk_edit_on_different_projects @@ -3193,13 +3248,13 @@ assert_template 'bulk_edit' # Can not set issues from different projects as children of an issue - assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'} + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 # Project specific custom field, date type field = CustomField.find(9) assert !field.is_for_all? assert !field.project_ids.include?(Issue.find(6).project_id) - assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'} + assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0 end def test_get_bulk_edit_with_user_custom_field @@ -3210,12 +3265,9 @@ assert_response :success assert_template 'bulk_edit' - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][#{field.id}]", :class => 'user_cf'}, - :children => { - :only => {:tag => 'option'}, - :count => Project.find(1).users.count + 2 # "no change" + "none" options - } + assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do + assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options + end end def test_get_bulk_edit_with_version_custom_field @@ -3226,12 +3278,9 @@ assert_response :success assert_template 'bulk_edit' - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][#{field.id}]"}, - :children => { - :only => {:tag => 'option'}, - :count => Project.find(1).shared_versions.count + 2 # "no change" + "none" options - } + assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do + assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options + end end def test_get_bulk_edit_with_multi_custom_field @@ -3243,22 +3292,31 @@ assert_response :success assert_template 'bulk_edit' - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][1][]"}, - :children => { - :only => {:tag => 'option'}, - :count => field.possible_values.size + 1 # "none" options - } + assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do + assert_select 'option', field.possible_values.size + 1 # "none" options + end + end + + def test_bulk_edit_should_propose_to_clear_text_custom_fields + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 3] + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', '__none__' end def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues WorkflowTransition.delete_all - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, + :old_status_id => 1, :new_status_id => 1) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, + :old_status_id => 1, :new_status_id => 3) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, + :old_status_id => 1, :new_status_id => 4) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, + :old_status_id => 2, :new_status_id => 1) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, + :old_status_id => 2, :new_status_id => 3) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, + :old_status_id => 2, :new_status_id => 5) @request.session[:user_id] = 2 get :bulk_edit, :ids => [1, 2] @@ -3267,8 +3325,9 @@ assert_not_nil statuses assert_equal [1, 3], statuses.map(&:id).sort - assert_tag 'select', :attributes => {:name => 'issue[status_id]'}, - :children => {:count => 3} # 2 statuses + "no change" option + assert_select 'select[name=?]', 'issue[status_id]' do + assert_select 'option', 3 # 2 statuses + "no change" option + end end def test_bulk_edit_should_propose_target_project_open_shared_versions @@ -3277,9 +3336,10 @@ assert_response :success assert_template 'bulk_edit' assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort - assert_tag 'select', - :attributes => {:name => 'issue[fixed_version_id]'}, - :descendant => {:tag => 'option', :content => '2.0'} + + assert_select 'select[name=?]', 'issue[fixed_version_id]' do + assert_select 'option', :text => '2.0' + end end def test_bulk_edit_should_propose_target_project_categories @@ -3288,9 +3348,10 @@ assert_response :success assert_template 'bulk_edit' assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort - assert_tag 'select', - :attributes => {:name => 'issue[category_id]'}, - :descendant => {:tag => 'option', :content => 'Recipes'} + + assert_select 'select[name=?]', 'issue[category_id]' do + assert_select 'option', :text => 'Recipes' + end end def test_bulk_update @@ -3306,7 +3367,7 @@ assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id} issue = Issue.find(1) - journal = issue.journals.find(:first, :order => 'created_on DESC') + journal = issue.journals.reorder('created_on DESC').first assert_equal '125', issue.custom_value_for(2).value assert_equal 'Bulk editing', journal.notes assert_equal 1, journal.details.size @@ -3341,7 +3402,7 @@ assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id) issue = Issue.find(1) - journal = issue.journals.find(:first, :order => 'created_on DESC') + journal = issue.journals.reorder('created_on DESC').first assert_equal '125', issue.custom_value_for(2).value assert_equal 'Bulk editing', journal.notes assert_equal 1, journal.details.size @@ -3443,11 +3504,12 @@ end def test_bulk_update_parent_id + IssueRelation.delete_all @request.session[:user_id] = 2 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing parent', - :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'} - + :issue => {:priority_id => '', :assigned_to_id => '', + :status_id => '', :parent_issue_id => '2'} assert_response 302 parent = Issue.find(2) assert_equal parent.id, Issue.find(1).parent_id @@ -3466,7 +3528,7 @@ assert_response 302 issue = Issue.find(1) - journal = issue.journals.find(:first, :order => 'created_on DESC') + journal = issue.journals.reorder('created_on DESC').first assert_equal '777', issue.custom_value_for(2).value assert_equal 1, journal.details.size assert_equal '125', journal.details.first.old_value @@ -3555,13 +3617,44 @@ assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier end - def test_bulk_update_with_failure_should_set_flash + def test_bulk_update_with_all_failures_should_show_errors @request.session[:user_id] = 2 - Issue.update_all("subject = ''", "id = 2") # Make it invalid - post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6} - - assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook' - assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error] + post :bulk_update, :ids => [1, 2], :issue => {:start_date => 'foo'} + + assert_response :success + assert_template 'bulk_edit' + assert_select '#errorExplanation span', :text => 'Failed to save 2 issue(s) on 2 selected: #1, #2.' + assert_select '#errorExplanation ul li', :text => 'Start date is not a valid date: #1, #2' + + assert_equal [1, 2], assigns[:issues].map(&:id) + end + + def test_bulk_update_with_some_failures_should_show_errors + issue1 = Issue.generate!(:start_date => '2013-05-12') + issue2 = Issue.generate!(:start_date => '2013-05-15') + issue3 = Issue.generate! + @request.session[:user_id] = 2 + post :bulk_update, :ids => [issue1.id, issue2.id, issue3.id], + :issue => {:due_date => '2013-05-01'} + assert_response :success + assert_template 'bulk_edit' + assert_select '#errorExplanation span', + :text => "Failed to save 2 issue(s) on 3 selected: ##{issue1.id}, ##{issue2.id}." + assert_select '#errorExplanation ul li', + :text => "Due date must be greater than start date: ##{issue1.id}, ##{issue2.id}" + assert_equal [issue1.id, issue2.id], assigns[:issues].map(&:id) + end + + def test_bulk_update_with_failure_should_preserved_form_values + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2', :start_date => 'foo'} + + assert_response :success + assert_template 'bulk_edit' + assert_select 'select[name=?]', 'issue[tracker_id]' do + assert_select 'option[value=2][selected=selected]' + end + assert_select 'input[name=?][value=?]', 'issue[start_date]', 'foo' end def test_get_bulk_copy @@ -3595,10 +3688,13 @@ def test_bulk_copy_should_allow_not_changing_the_issue_attributes @request.session[:user_id] = 2 issues = [ - Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, :priority_id => 2, :subject => 'issue 1', :author_id => 1, :assigned_to_id => nil), - Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2, :priority_id => 1, :subject => 'issue 2', :author_id => 2, :assigned_to_id => 3) + Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, + :priority_id => 2, :subject => 'issue 1', :author_id => 1, + :assigned_to_id => nil), + Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2, + :priority_id => 1, :subject => 'issue 2', :author_id => 2, + :assigned_to_id => 3) ] - assert_difference 'Issue.count', issues.size do post :bulk_update, :ids => issues.map(&:id), :copy => '1', :issue => { @@ -3621,7 +3717,7 @@ def test_bulk_copy_should_allow_changing_the_issue_attributes # Fixes random test failure with Mysql - # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2}) + # where Issue.where(:project_id => 2).limit(2).order('id desc') # doesn't return the expected results Issue.delete_all("project_id=2") @@ -3636,7 +3732,7 @@ end end - copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2}) + copied_issues = Issue.where(:project_id => 2).limit(2).order('id desc').to_a assert_equal 2, copied_issues.size copied_issues.each do |issue| assert_equal 2, issue.project_id, "Project is incorrect" @@ -3657,11 +3753,10 @@ :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31' } end - issue = Issue.first(:order => 'id DESC') assert_equal 1, issue.journals.size journal = issue.journals.first - assert_equal 0, journal.details.size + assert_equal 1, journal.details.size assert_equal 'Copying one issue', journal.notes end @@ -3757,6 +3852,13 @@ assert_redirected_to :controller => 'issues', :action => 'show', :id => issue end + def test_bulk_copy_with_all_failures_should_display_errors + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 2], :copy => '1', :issue => {:start_date => 'foo'} + + assert_response :success + end + def test_destroy_issue_with_no_time_entries assert_nil TimeEntry.find_by_issue_id(2) @request.session[:user_id] = 2 @@ -3778,8 +3880,10 @@ assert_template 'destroy' assert_not_nil assigns(:hours) assert Issue.find_by_id(1) && Issue.find_by_id(3) - assert_tag 'form', - :descendant => {:tag => 'input', :attributes => {:name => '_method', :value => 'delete'}} + + assert_select 'form' do + assert_select 'input[name=_method][value=delete]' + end end def test_destroy_issues_and_destroy_time_entries @@ -3845,10 +3949,19 @@ assert_response 302 end + def test_destroy_invalid_should_respond_with_404 + @request.session[:user_id] = 2 + assert_no_difference 'Issue.count' do + delete :destroy, :id => 999 + end + assert_response 404 + end + def test_default_search_scope get :index - assert_tag :div, :attributes => {:id => 'quick-search'}, - :child => {:tag => 'form', - :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}} + + assert_select 'div#quick-search form' do + assert_select 'input[name=issues][value=1][type=hidden]' + end end end diff -r d98d22a98252 -r afce8026aaeb test/functional/issues_controller_transaction_test.rb --- a/test/functional/issues_controller_transaction_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/issues_controller_transaction_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,6 +19,7 @@ require 'issues_controller' class IssuesControllerTransactionTest < ActionController::TestCase + tests IssuesController fixtures :projects, :users, :roles, @@ -46,9 +47,6 @@ self.use_transactional_fixtures = false def setup - @controller = IssuesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -106,7 +104,7 @@ assert_template 'edit' attachment = Attachment.first(:order => 'id DESC') assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_tag 'input', :attributes => {:name => 'attachments[p0][filename]', :value => 'testfile.txt'} end def test_update_stale_issue_without_notes_should_not_show_add_notes_option @@ -254,7 +252,7 @@ end def test_index_should_rescue_invalid_sql_query - Query.any_instance.stubs(:statement).returns("INVALID STATEMENT") + IssueQuery.any_instance.stubs(:statement).returns("INVALID STATEMENT") get :index assert_response 500 diff -r d98d22a98252 -r afce8026aaeb test/functional/issues_custom_fields_visibility_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/functional/issues_custom_fields_visibility_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,322 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class IssuesCustomFieldsVisibilityTest < ActionController::TestCase + tests IssuesController + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issue_statuses, + :trackers, + :projects_trackers, + :enabled_modules, + :enumerations, + :workflows + + def setup + CustomField.delete_all + Issue.delete_all + field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all} + @fields = [] + @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) + @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) + @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) + @issue = Issue.generate!( + :author_id => 1, + :project_id => 1, + :tracker_id => 1, + :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'} + ) + + @user_with_role_on_other_project = User.generate! + User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3)) + + @users_to_test = { + User.find(1) => [@field1, @field2, @field3], + User.find(3) => [@field1, @field2], + @user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 + User.generate! => [@field1], + User.anonymous => [@field1] + } + + Member.where(:project_id => 1).each do |member| + member.destroy unless @users_to_test.keys.include?(member.principal) + end + end + + def test_show_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :show, :id => @issue.id + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'td', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name}" + else + assert_select 'td', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name}" + end + end + end + end + + def test_show_should_show_visible_custom_fields_only_in_api + @users_to_test.each do |user, fields| + with_settings :rest_api_enabled => '1' do + get :show, :id => @issue.id, :format => 'xml', :include => 'custom_fields', :key => user.api_key + end + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select "custom_field[id=#{field.id}] value", {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} in API" + else + assert_select "custom_field[id=#{field.id}] value", {:text => "Value#{i}", :count => 0}, "User #{user.id} was not able to view #{field.name} in API" + end + end + end + end + + def test_show_should_show_visible_custom_fields_only_in_history + @issue.init_journal(User.find(1)) + @issue.custom_field_values = {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'} + @issue.save! + + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :show, :id => @issue.id + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'ul.details i', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change" + else + assert_select 'ul.details i', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change" + end + end + end + end + + def test_show_should_show_visible_custom_fields_only_in_history_api + @issue.init_journal(User.find(1)) + @issue.custom_field_values = {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'} + @issue.save! + + @users_to_test.each do |user, fields| + with_settings :rest_api_enabled => '1' do + get :show, :id => @issue.id, :format => 'xml', :include => 'journals', :key => user.api_key + end + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'details old_value', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change in API" + else + assert_select 'details old_value', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change in API" + end + end + end + end + + def test_edit_should_show_visible_custom_fields_only + Role.anonymous.add_permission! :edit_issues + + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :edit, :id => @issue.id + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'input[value=?]', "Value#{i}", 1, "User #{user.id} was not able to edit #{field.name}" + else + assert_select 'input[value=?]', "Value#{i}", 0, "User #{user.id} was able to edit #{field.name}" + end + end + end + end + + def test_update_should_update_visible_custom_fields_only + Role.anonymous.add_permission! :edit_issues + + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + put :update, :id => @issue.id, + :issue => {:custom_field_values => { + @field1.id.to_s => "User#{user.id}Value0", + @field2.id.to_s => "User#{user.id}Value1", + @field3.id.to_s => "User#{user.id}Value2", + }} + @issue.reload + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_equal "User#{user.id}Value#{i}", @issue.custom_field_value(field), "User #{user.id} was not able to update #{field.name}" + else + assert_not_equal "User#{user.id}Value#{i}", @issue.custom_field_value(field), "User #{user.id} was able to update #{field.name}" + end + end + end + end + + def test_index_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :c => (["subject"] + @fields.map{|f| "cf_#{f.id}"}) + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'td', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name}" + else + assert_select 'td', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name}" + end + end + end + end + + def test_index_as_csv_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :c => (["subject"] + @fields.map{|f| "cf_#{f.id}"}), :format => 'csv' + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_include "Value#{i}", response.body, "User #{user.id} was not able to view #{field.name} in CSV" + else + assert_not_include "Value#{i}", response.body, "User #{user.id} was able to view #{field.name} in CSV" + end + end + end + end + + def test_index_with_partial_custom_field_visibility + Issue.delete_all + p1 = Project.generate! + p2 = Project.generate! + user = User.generate! + User.add_to_project(user, p1, Role.find_all_by_id(1,3)) + User.add_to_project(user, p2, Role.find_all_by_id(3)) + Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueA'}) + Issue.generate!(:project => p2, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueB'}) + Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueC'}) + + @request.session[:user_id] = user.id + get :index, :c => ["subject", "cf_#{@field2.id}"] + assert_select 'td', :text => 'ValueA' + assert_select 'td', :text => 'ValueB', :count => 0 + assert_select 'td', :text => 'ValueC' + + get :index, :sort => "cf_#{@field2.id}" + # ValueB is not visible to user and ignored while sorting + assert_equal %w(ValueB ValueA ValueC), assigns(:issues).map{|i| i.custom_field_value(@field2)} + + get :index, :set_filter => '1', "cf_#{@field2.id}" => '*' + assert_equal %w(ValueA ValueC), assigns(:issues).map{|i| i.custom_field_value(@field2)} + + CustomField.update_all(:field_format => 'list') + get :index, :group => "cf_#{@field2.id}" + assert_equal %w(ValueA ValueC), assigns(:issues).map{|i| i.custom_field_value(@field2)} + end + + def test_create_should_send_notifications_according_custom_fields_visibility + # anonymous user is never notified + users_to_test = @users_to_test.reject {|k,v| k.anonymous?} + + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 1 + with_settings :bcc_recipients => '1' do + assert_difference 'Issue.count' do + post :create, + :project_id => 1, + :issue => { + :tracker_id => 1, + :status_id => 1, + :subject => 'New issue', + :priority_id => 5, + :custom_field_values => {@field1.id.to_s => 'Value0', @field2.id.to_s => 'Value1', @field3.id.to_s => 'Value2'}, + :watcher_user_ids => users_to_test.keys.map(&:id) + } + assert_response 302 + end + end + assert_equal users_to_test.values.uniq.size, ActionMailer::Base.deliveries.size + # tests that each user receives 1 email with the custom fields he is allowed to see only + users_to_test.each do |user, fields| + mails = ActionMailer::Base.deliveries.select {|m| m.bcc.include? user.mail} + assert_equal 1, mails.size + mail = mails.first + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_mail_body_match "Value#{i}", mail, "User #{user.id} was not able to view #{field.name} in notification" + else + assert_mail_body_no_match "Value#{i}", mail, "User #{user.id} was able to view #{field.name} in notification" + end + end + end + end + + def test_update_should_send_notifications_according_custom_fields_visibility + # anonymous user is never notified + users_to_test = @users_to_test.reject {|k,v| k.anonymous?} + + users_to_test.keys.each do |user| + Watcher.create!(:user => user, :watchable => @issue) + end + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 1 + with_settings :bcc_recipients => '1' do + put :update, + :id => @issue.id, + :issue => { + :custom_field_values => {@field1.id.to_s => 'NewValue0', @field2.id.to_s => 'NewValue1', @field3.id.to_s => 'NewValue2'} + } + assert_response 302 + end + assert_equal users_to_test.values.uniq.size, ActionMailer::Base.deliveries.size + # tests that each user receives 1 email with the custom fields he is allowed to see only + users_to_test.each do |user, fields| + mails = ActionMailer::Base.deliveries.select {|m| m.bcc.include? user.mail} + assert_equal 1, mails.size + mail = mails.first + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_mail_body_match "Value#{i}", mail, "User #{user.id} was not able to view #{field.name} in notification" + else + assert_mail_body_no_match "Value#{i}", mail, "User #{user.id} was able to view #{field.name} in notification" + end + end + end + end + + def test_updating_hidden_custom_fields_only_should_not_notifiy_user + # anonymous user is never notified + users_to_test = @users_to_test.reject {|k,v| k.anonymous?} + + users_to_test.keys.each do |user| + Watcher.create!(:user => user, :watchable => @issue) + end + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 1 + with_settings :bcc_recipients => '1' do + put :update, + :id => @issue.id, + :issue => { + :custom_field_values => {@field2.id.to_s => 'NewValue1', @field3.id.to_s => 'NewValue2'} + } + assert_response 302 + end + users_to_test.each do |user, fields| + mails = ActionMailer::Base.deliveries.select {|m| m.bcc.include? user.mail} + if (fields & [@field2, @field3]).any? + assert_equal 1, mails.size, "User #{user.id} was not notified" + else + assert_equal 0, mails.size, "User #{user.id} was notified" + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb test/functional/journals_controller_test.rb --- a/test/functional/journals_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/journals_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,19 +16,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'journals_controller' - -# Re-raise errors caught by the controller. -class JournalsController; def rescue_action(e) raise e end; end class JournalsControllerTest < ActionController::TestCase fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules, :trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects def setup - @controller = JournalsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r d98d22a98252 -r afce8026aaeb test/functional/mail_handler_controller_test.rb --- a/test/functional/mail_handler_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/mail_handler_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'mail_handler_controller' - -# Re-raise errors caught by the controller. -class MailHandlerController; def rescue_action(e) raise e end; end class MailHandlerControllerTest < ActionController::TestCase fixtures :users, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses, @@ -28,9 +24,6 @@ FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler' def setup - @controller = MailHandlerController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r d98d22a98252 -r afce8026aaeb test/functional/members_controller_test.rb --- a/test/functional/members_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/members_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,19 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'members_controller' - -# Re-raise errors caught by the controller. -class MembersController; def rescue_action(e) raise e end; end - class MembersControllerTest < ActionController::TestCase fixtures :projects, :members, :member_roles, :roles, :users def setup - @controller = MembersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 2 end @@ -112,11 +104,8 @@ end def test_autocomplete - get :autocomplete, :project_id => 1, :q => 'mis' + get :autocomplete, :project_id => 1, :q => 'mis', :format => 'js' assert_response :success - assert_template 'autocomplete' - - assert_tag :label, :content => /User Misc/, - :child => { :tag => 'input', :attributes => { :name => 'membership[user_ids][]', :value => '8' } } + assert_include 'User Misc', response.body end end diff -r d98d22a98252 -r afce8026aaeb test/functional/messages_controller_test.rb --- a/test/functional/messages_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/messages_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'messages_controller' - -# Re-raise errors caught by the controller. -class MessagesController; def rescue_action(e) raise e end; end class MessagesControllerTest < ActionController::TestCase fixtures :projects, :users, :members, :member_roles, :roles, :boards, :messages, :enabled_modules def setup - @controller = MessagesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -93,6 +86,12 @@ assert_template 'new' end + def test_get_new_with_invalid_board + @request.session[:user_id] = 2 + get :new, :board_id => 99 + assert_response 404 + end + def test_post_new @request.session[:user_id] = 2 ActionMailer::Base.deliveries.clear @@ -164,7 +163,7 @@ def test_reply @request.session[:user_id] = 2 post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' } - reply = Message.find(:first, :order => 'id DESC') + reply = Message.order('id DESC').first assert_redirected_to "/boards/1/topics/1?r=#{reply.id}" assert Message.find_by_subject('Test reply') end diff -r d98d22a98252 -r afce8026aaeb test/functional/my_controller_test.rb --- a/test/functional/my_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/my_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,20 +16,13 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'my_controller' - -# Re-raise errors caught by the controller. -class MyController; def rescue_action(e) raise e end; end class MyControllerTest < ActionController::TestCase fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, :auth_sources def setup - @controller = MyController.new - @request = ActionController::TestRequest.new @request.session[:user_id] = 2 - @response = ActionController::TestResponse.new end def test_index @@ -58,6 +51,17 @@ end end + def test_page_with_all_blocks + blocks = MyController::BLOCKS.keys + preferences = User.find(2).pref + preferences[:my_page_layout] = {'top' => blocks} + preferences.save! + + get :page + assert_response :success + assert_select 'div.mypage-box', blocks.size + end + def test_my_account_should_show_editable_custom_fields get :account assert_response :success diff -r d98d22a98252 -r afce8026aaeb test/functional/news_controller_test.rb --- a/test/functional/news_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/news_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'news_controller' - -# Re-raise errors caught by the controller. -class NewsController; def rescue_action(e) raise e end; end class NewsControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news, :comments def setup - @controller = NewsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r d98d22a98252 -r afce8026aaeb test/functional/previews_controller_test.rb --- a/test/functional/previews_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/previews_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :journals, :journal_details, :news diff -r d98d22a98252 -r afce8026aaeb test/functional/project_enumerations_controller_test.rb --- a/test/functional/project_enumerations_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/project_enumerations_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../test_helper', __FILE__) class ProjectEnumerationsControllerTest < ActionController::TestCase @@ -8,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, :time_entries @@ -75,14 +91,14 @@ project_activity = TimeEntryActivity.new({ :name => 'Project Specific', - :parent => TimeEntryActivity.find(:first), + :parent => TimeEntryActivity.first, :project => Project.find(1), :active => true }) assert project_activity.save project_activity_two = TimeEntryActivity.new({ :name => 'Project Specific Two', - :parent => TimeEntryActivity.find(:last), + :parent => TimeEntryActivity.last, :project => Project.find(1), :active => true }) @@ -156,14 +172,14 @@ @request.session[:user_id] = 2 # manager project_activity = TimeEntryActivity.new({ :name => 'Project Specific', - :parent => TimeEntryActivity.find(:first), + :parent => TimeEntryActivity.first, :project => Project.find(1), :active => true }) assert project_activity.save project_activity_two = TimeEntryActivity.new({ :name => 'Project Specific Two', - :parent => TimeEntryActivity.find(:last), + :parent => TimeEntryActivity.last, :project => Project.find(1), :active => true }) diff -r d98d22a98252 -r afce8026aaeb test/functional/projects_controller_test.rb --- a/test/functional/projects_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/projects_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'projects_controller' - -# Re-raise errors caught by the controller. -class ProjectsController; def rescue_action(e) raise e end; end class ProjectsControllerTest < ActionController::TestCase fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, @@ -27,29 +23,27 @@ :attachments, :custom_fields, :custom_values, :time_entries def setup - @controller = ProjectsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new @request.session[:user_id] = nil Setting.default_language = 'en' end - def test_index + def test_index_by_anonymous_should_not_show_private_projects get :index assert_response :success assert_template 'index' - assert_not_nil assigns(:projects) + projects = assigns(:projects) + assert_not_nil projects + assert projects.all?(&:is_public?) - assert_tag :ul, :child => {:tag => 'li', - :descendant => {:tag => 'a', :content => 'eCookbook'}, - :child => { :tag => 'ul', - :descendant => { :tag => 'a', - :content => 'Child of private child' - } - } - } - - assert_no_tag :a, :content => /Private child of eCookbook/ + assert_select 'ul' do + assert_select 'li' do + assert_select 'a', :text => 'eCookbook' + assert_select 'ul' do + assert_select 'a', :text => 'Child of private child' + end + end + end + assert_select 'a', :text => /Private child of eCookbook/, :count => 0 end def test_index_atom @@ -57,242 +51,240 @@ assert_response :success assert_template 'common/feed' assert_select 'feed>title', :text => 'Redmine: Latest projects' - assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_condition(User.current)) + assert_select 'feed>entry', :count => Project.visible(User.current).count end - context "#index" do - context "by non-admin user with view_time_entries permission" do - setup do - @request.session[:user_id] = 3 - end - should "show overall spent time link" do - get :index - assert_template 'index' - assert_tag :a, :attributes => {:href => '/time_entries'} - end - end + test "#index by non-admin user with view_time_entries permission should show overall spent time link" do + @request.session[:user_id] = 3 + get :index + assert_template 'index' + assert_select 'a[href=?]', '/time_entries' + end - context "by non-admin user without view_time_entries permission" do - setup do - Role.find(2).remove_permission! :view_time_entries - Role.non_member.remove_permission! :view_time_entries - Role.anonymous.remove_permission! :view_time_entries - @request.session[:user_id] = 3 - end - should "not show overall spent time link" do - get :index - assert_template 'index' - assert_no_tag :a, :attributes => {:href => '/time_entries'} - end + test "#index by non-admin user without view_time_entries permission should not show overall spent time link" do + Role.find(2).remove_permission! :view_time_entries + Role.non_member.remove_permission! :view_time_entries + Role.anonymous.remove_permission! :view_time_entries + @request.session[:user_id] = 3 + + get :index + assert_template 'index' + assert_select 'a[href=?]', '/time_entries', 0 + end + + test "#new by admin user should accept get" do + @request.session[:user_id] = 1 + + get :new + assert_response :success + assert_template 'new' + end + + test "#new by non-admin user with add_project permission should accept get" do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + + get :new + assert_response :success + assert_template 'new' + assert_select 'select[name=?]', 'project[parent_id]', 0 + end + + test "#new by non-admin user with add_subprojects permission should accept get" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + get :new, :parent_id => 'ecookbook' + assert_response :success + assert_template 'new' + + assert_select 'select[name=?]', 'project[parent_id]' do + # parent project selected + assert_select 'option[value=1][selected=selected]' + # no empty value + assert_select 'option[value=]', 0 end end - context "#new" do - context "by admin user" do - setup do - @request.session[:user_id] = 1 - end + test "#create by admin user should create a new project" do + @request.session[:user_id] = 1 - should "accept get" do - get :new - assert_response :success - assert_template 'new' - end + post :create, + :project => { + :name => "blog", + :description => "weblog", + :homepage => 'http://weblog', + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :tracker_ids => ['1', '3'], + # an issue custom field that is not for all project + :issue_custom_field_ids => ['9'], + :enabled_module_names => ['issue_tracking', 'news', 'repository'] + } + assert_redirected_to '/projects/blog/settings' + project = Project.find_by_name('blog') + assert_kind_of Project, project + assert project.active? + assert_equal 'weblog', project.description + assert_equal 'http://weblog', project.homepage + assert_equal true, project.is_public? + assert_nil project.parent + assert_equal 'Beta', project.custom_value_for(3).value + assert_equal [1, 3], project.trackers.map(&:id).sort + assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort + assert project.issue_custom_fields.include?(IssueCustomField.find(9)) + end + + test "#create by admin user should create a new subproject" do + @request.session[:user_id] = 1 + + assert_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 1 + } + assert_redirected_to '/projects/blog/settings' end - context "by non-admin user with add_project permission" do - setup do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - end + project = Project.find_by_name('blog') + assert_kind_of Project, project + assert_equal Project.find(1), project.parent + end - should "accept get" do - get :new - assert_response :success - assert_template 'new' - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'} - end + test "#create by admin user should continue" do + @request.session[:user_id] = 1 + + assert_difference 'Project.count' do + post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue' + end + assert_redirected_to '/projects/new' + end + + test "#create by non-admin user with add_project permission should create a new project" do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :tracker_ids => ['1', '3'], + :enabled_module_names => ['issue_tracking', 'news', 'repository'] + } + + assert_redirected_to '/projects/blog/settings' + + project = Project.find_by_name('blog') + assert_kind_of Project, project + assert_equal 'weblog', project.description + assert_equal true, project.is_public? + assert_equal [1, 3], project.trackers.map(&:id).sort + assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort + + # User should be added as a project member + assert User.find(9).member_of?(project) + assert_equal 1, project.members.size + end + + test "#create by non-admin user with add_project permission should fail with parent_id" do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + + assert_no_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 1 + } + end + assert_response :success + project = assigns(:project) + assert_kind_of Project, project + assert_not_equal [], project.errors[:parent_id] + end + + test "#create by non-admin user with add_subprojects permission should create a project with a parent_id" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 1 + } + assert_redirected_to '/projects/blog/settings' + project = Project.find_by_name('blog') + end + + test "#create by non-admin user with add_subprojects permission should fail without parent_id" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + assert_no_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' } + } + end + assert_response :success + project = assigns(:project) + assert_kind_of Project, project + assert_not_equal [], project.errors[:parent_id] + end + + test "#create by non-admin user with add_subprojects permission should fail with unauthorized parent_id" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + assert !User.find(2).member_of?(Project.find(6)) + assert_no_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 6 + } + end + assert_response :success + project = assigns(:project) + assert_kind_of Project, project + assert_not_equal [], project.errors[:parent_id] + end + + def test_create_subproject_with_inherit_members_should_inherit_members + Role.find_by_name('Manager').add_permission! :add_subprojects + parent = Project.find(1) + @request.session[:user_id] = 2 + + assert_difference 'Project.count' do + post :create, :project => { + :name => 'inherited', :identifier => 'inherited', :parent_id => parent.id, :inherit_members => '1' + } + assert_response 302 end - context "by non-admin user with add_subprojects permission" do - setup do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - end - - should "accept get" do - get :new, :parent_id => 'ecookbook' - assert_response :success - assert_template 'new' - # parent project selected - assert_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}} - # no empty value - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => ''}} - end - end - - end - - context "POST :create" do - context "by admin user" do - setup do - @request.session[:user_id] = 1 - end - - should "create a new project" do - post :create, - :project => { - :name => "blog", - :description => "weblog", - :homepage => 'http://weblog', - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - # an issue custom field that is not for all project - :issue_custom_field_ids => ['9'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert project.active? - assert_equal 'weblog', project.description - assert_equal 'http://weblog', project.homepage - assert_equal true, project.is_public? - assert_nil project.parent - assert_equal 'Beta', project.custom_value_for(3).value - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - assert project.issue_custom_fields.include?(IssueCustomField.find(9)) - end - - should "create a new subproject" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal Project.find(1), project.parent - end - - should "continue" do - assert_difference 'Project.count' do - post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue' - end - assert_redirected_to '/projects/new?' - end - end - - context "by non-admin user with add_project permission" do - setup do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - end - - should "accept create a Project" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal 'weblog', project.description - assert_equal true, project.is_public? - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - - # User should be added as a project member - assert User.find(9).member_of?(project) - assert_equal 1, project.members.size - end - - should "fail with parent_id" do - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - end - - context "by non-admin user with add_subprojects permission" do - setup do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - end - - should "create a project with a parent_id" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - project = Project.find_by_name('blog') - end - - should "fail without parent_id" do - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' } - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - - should "fail with unauthorized parent_id" do - assert !User.find(2).member_of?(Project.find(6)) - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 6 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - end + project = Project.order('id desc').first + assert_equal 'inherited', project.name + assert_equal parent, project.parent + assert project.memberships.count > 0 + assert_equal parent.memberships.count, project.memberships.count end def test_create_should_preserve_modules_on_validation_failure @@ -325,7 +317,17 @@ assert_not_nil assigns(:project) assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) - assert_tag 'li', :content => /Development status/ + assert_select 'li', :text => /Development status/ + end + + def test_show_should_not_display_empty_sidebar + p = Project.find(1) + p.enabled_module_names = [] + p.save! + + get :show, :id => 'ecookbook' + assert_response :success + assert_select '#main.nosidebar' end def test_show_should_not_display_hidden_custom_fields @@ -335,7 +337,7 @@ assert_template 'show' assert_not_nil assigns(:project) - assert_no_tag 'li', :content => /Development status/ + assert_select 'li', :text => /Development status/, :count => 0 end def test_show_should_not_fail_when_custom_values_are_nil @@ -355,22 +357,22 @@ get :show, :id => 'ecookbook' assert_response 403 assert_nil assigns(:project) - assert_tag :tag => 'p', :content => /archived/ + assert_select 'p', :text => /archived/ end - def test_private_subprojects_hidden + def test_show_should_not_show_private_subprojects_that_are_not_visible get :show, :id => 'ecookbook' assert_response :success assert_template 'show' - assert_no_tag :tag => 'a', :content => /Private child/ + assert_select 'a', :text => /Private child/, :count => 0 end - def test_private_subprojects_visible + def test_show_should_show_private_subprojects_that_are_visible @request.session[:user_id] = 2 # manager who is a member of the private subproject get :show, :id => 'ecookbook' assert_response :success assert_template 'show' - assert_tag :tag => 'a', :content => /Private child/ + assert_select 'a', :text => /Private child/ end def test_settings @@ -380,6 +382,15 @@ assert_template 'settings' end + def test_settings_of_subproject + @request.session[:user_id] = 2 + get :settings, :id => 'private-child' + assert_response :success + assert_template 'settings' + + assert_select 'input[type=checkbox][name=?]', 'project[inherit_members]' + end + def test_settings_should_be_denied_for_member_on_closed_project Project.find(1).close @request.session[:user_id] = 2 # manager @@ -438,22 +449,37 @@ assert_equal ['documents', 'issue_tracking', 'repository'], Project.find(1).enabled_module_names.sort end - def test_destroy_without_confirmation + def test_destroy_leaf_project_without_confirmation_should_show_confirmation @request.session[:user_id] = 1 # admin - delete :destroy, :id => 1 - assert_response :success - assert_template 'destroy' - assert_not_nil Project.find_by_id(1) - assert_tag :tag => 'strong', - :content => ['Private child of eCookbook', + + assert_no_difference 'Project.count' do + delete :destroy, :id => 2 + assert_response :success + assert_template 'destroy' + end + end + + def test_destroy_without_confirmation_should_show_confirmation_with_subprojects + @request.session[:user_id] = 1 # admin + + assert_no_difference 'Project.count' do + delete :destroy, :id => 1 + assert_response :success + assert_template 'destroy' + end + assert_select 'strong', + :text => ['Private child of eCookbook', 'Child of private child, eCookbook Subproject 1', 'eCookbook Subproject 2'].join(', ') end - def test_destroy + def test_destroy_with_confirmation_should_destroy_the_project_and_subprojects @request.session[:user_id] = 1 # admin - delete :destroy, :id => 1, :confirm => 1 - assert_redirected_to '/admin/projects' + + assert_difference 'Project.count', -5 do + delete :destroy, :id => 1, :confirm => 1 + assert_redirected_to '/admin/projects' + end assert_nil Project.find_by_id(1) end @@ -499,12 +525,11 @@ CustomField.delete_all parent = nil 6.times do |i| - p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}") - p.set_parent!(parent) + p = Project.generate_with_parent!(parent) get :show, :id => p - assert_tag :h1, :parent => { :attributes => {:id => 'header'}}, - :children => { :count => [i, 3].min, - :only => { :tag => 'a' } } + assert_select '#header h1' do + assert_select 'a', :count => [i, 3].min + end parent = p end @@ -519,8 +544,7 @@ assert_equal Project.find(1).description, assigns(:project).description assert_nil assigns(:project).id - assert_tag :tag => 'input', - :attributes => {:name => 'project[enabled_module_names][]', :value => 'issue_tracking'} + assert_select 'input[name=?][value=?]', 'project[enabled_module_names][]', 'issue_tracking', 1 end def test_get_copy_with_invalid_source_should_respond_with_404 @@ -575,4 +599,9 @@ assert_response :success assert_template 'show' end + + def test_body_should_have_project_css_class + get :show, :id => 1 + assert_select 'body.project-ecookbook' + end end diff -r d98d22a98252 -r afce8026aaeb test/functional/queries_controller_test.rb --- a/test/functional/queries_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/queries_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,14 +24,18 @@ User.current = nil end + def test_index + get :index + # HTML response not implemented + assert_response 406 + end + def test_new_project_query @request.session[:user_id] = 2 get :new, :project_id => 1 assert_response :success assert_template 'new' - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]', - :checked => nil } + assert_select 'input[name=?][value=0][checked=checked]', 'query[visibility]' assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'query_is_for_all', :checked => nil, @@ -47,8 +51,7 @@ get :new assert_response :success assert_template 'new' - assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]' } + assert_select 'input[name=?]', 'query[visibility]', 0 assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'query_is_for_all', :checked => 'checked', @@ -69,7 +72,7 @@ :f => ["status_id", "assigned_to_id"], :op => {"assigned_to_id" => "=", "status_id" => "o"}, :v => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, - :query => {"name" => "test_new_project_public_query", "is_public" => "1"} + :query => {"name" => "test_new_project_public_query", "visibility" => "2"} q = Query.find_by_name('test_new_project_public_query') assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q @@ -86,7 +89,7 @@ :fields => ["status_id", "assigned_to_id"], :operators => {"assigned_to_id" => "=", "status_id" => "o"}, :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, - :query => {"name" => "test_new_project_private_query", "is_public" => "1"} + :query => {"name" => "test_new_project_private_query", "visibility" => "2"} q = Query.find_by_name('test_new_project_private_query') assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q @@ -101,14 +104,14 @@ :fields => ["status_id", "assigned_to_id"], :operators => {"assigned_to_id" => "=", "status_id" => "o"}, :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, - :query => {"name" => "test_new_global_private_query", "is_public" => "1"}, + :query => {"name" => "test_new_global_private_query", "visibility" => "2"}, :c => ["", "tracker", "subject", "priority", "category"] q = Query.find_by_name('test_new_global_private_query') assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q assert !q.is_public? assert !q.has_default_columns? - assert_equal [:tracker, :subject, :priority, :category], q.columns.collect {|c| c.name} + assert_equal [:id, :tracker, :subject, :priority, :category], q.columns.collect {|c| c.name} assert q.valid? end @@ -134,7 +137,7 @@ :operators => {"status_id" => "o"}, :values => {"status_id" => ["1"]}, :query => {:name => "test_new_with_sort", - :is_public => "1", + :visibility => "2", :sort_criteria => {"0" => ["due_date", "desc"], "1" => ["tracker", ""]}} query = Query.find_by_name("test_new_with_sort") @@ -152,14 +155,49 @@ assert_select 'input[name=?]', 'query[name]' end + def test_create_global_query_from_gantt + @request.session[:user_id] = 1 + assert_difference 'IssueQuery.count' do + post :create, + :gantt => 1, + :operators => {"status_id" => "o"}, + :values => {"status_id" => ["1"]}, + :query => {:name => "test_create_from_gantt", + :draw_relations => '1', + :draw_progress_line => '1'} + assert_response 302 + end + query = IssueQuery.order('id DESC').first + assert_redirected_to "/issues/gantt?query_id=#{query.id}" + assert_equal true, query.draw_relations + assert_equal true, query.draw_progress_line + end + + def test_create_project_query_from_gantt + @request.session[:user_id] = 1 + assert_difference 'IssueQuery.count' do + post :create, + :project_id => 'ecookbook', + :gantt => 1, + :operators => {"status_id" => "o"}, + :values => {"status_id" => ["1"]}, + :query => {:name => "test_create_from_gantt", + :draw_relations => '0', + :draw_progress_line => '0'} + assert_response 302 + end + query = IssueQuery.order('id DESC').first + assert_redirected_to "/projects/ecookbook/issues/gantt?query_id=#{query.id}" + assert_equal false, query.draw_relations + assert_equal false, query.draw_progress_line + end + def test_edit_global_public_query @request.session[:user_id] = 1 get :edit, :id => 4 assert_response :success assert_template 'edit' - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]', - :checked => 'checked' } + assert_select 'input[name=?][value=2][checked=checked]', 'query[visibility]' assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'query_is_for_all', :checked => 'checked', @@ -171,8 +209,7 @@ get :edit, :id => 3 assert_response :success assert_template 'edit' - assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]' } + assert_select 'input[name=?]', 'query[visibility]', 0 assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'query_is_for_all', :checked => 'checked', @@ -184,8 +221,7 @@ get :edit, :id => 2 assert_response :success assert_template 'edit' - assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]' } + assert_select 'input[name=?]', 'query[visibility]', 0 assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'query_is_for_all', :checked => nil, @@ -197,10 +233,7 @@ get :edit, :id => 1 assert_response :success assert_template 'edit' - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]', - :checked => 'checked' - } + assert_select 'input[name=?][value=2][checked=checked]', 'query[visibility]' assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'query_is_for_all', :checked => nil, @@ -234,7 +267,7 @@ :fields => ["status_id", "assigned_to_id"], :operators => {"assigned_to_id" => "=", "status_id" => "o"}, :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, - :query => {"name" => "test_edit_global_private_query", "is_public" => "1"} + :query => {"name" => "test_edit_global_private_query", "visibility" => "2"} assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 3 q = Query.find_by_name('test_edit_global_private_query') @@ -251,7 +284,7 @@ :fields => ["status_id", "assigned_to_id"], :operators => {"assigned_to_id" => "=", "status_id" => "o"}, :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, - :query => {"name" => "test_edit_global_public_query", "is_public" => "1"} + :query => {"name" => "test_edit_global_public_query", "visibility" => "2"} assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 4 q = Query.find_by_name('test_edit_global_public_query') diff -r d98d22a98252 -r afce8026aaeb test/functional/reports_controller_test.rb --- a/test/functional/reports_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/reports_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,12 +25,8 @@ :member_roles, :members, :enabled_modules, - :workflows, :versions - def setup - end - def test_get_issue_report get :issue_report, :id => 1 @@ -58,6 +54,24 @@ end end + def test_get_issue_report_details_by_tracker_should_show_issue_count + Issue.delete_all + Issue.generate!(:tracker_id => 1) + Issue.generate!(:tracker_id => 1) + Issue.generate!(:tracker_id => 1, :status_id => 5) + Issue.generate!(:tracker_id => 2) + + get :issue_report_details, :id => 1, :detail => 'tracker' + assert_select 'table.list tbody :nth-child(1)' do + assert_select 'td', :text => 'Bug' + assert_select ':nth-child(2)', :text => '2' # status:1 + assert_select ':nth-child(3)', :text => '-' # status:2 + assert_select ':nth-child(8)', :text => '2' # open + assert_select ':nth-child(9)', :text => '1' # closed + assert_select ':nth-child(10)', :text => '3' # total + end + end + def test_get_issue_report_details_by_priority get :issue_report_details, :id => 1, :detail => 'priority' assert_equal IssuePriority.all.reverse, assigns(:rows) diff -r d98d22a98252 -r afce8026aaeb test/functional/repositories_bazaar_controller_test.rb --- a/test/functional/repositories_bazaar_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/repositories_bazaar_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,17 +23,23 @@ fixtures :projects, :users, :roles, :members, :member_roles, :repositories, :enabled_modules - REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository/trunk').to_s + REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s + REPOSITORY_PATH_TRUNK = File.join(REPOSITORY_PATH, "trunk") PRJ_ID = 3 + CHAR_1_UTF8_HEX = "\xc3\x9c" def setup User.current = nil @project = Project.find(PRJ_ID) @repository = Repository::Bazaar.create( :project => @project, - :url => REPOSITORY_PATH, + :url => REPOSITORY_PATH_TRUNK, :log_encoding => 'UTF-8') assert @repository + @char_1_utf8 = CHAR_1_UTF8_HEX.dup + if @char_1_utf8.respond_to?(:force_encoding) + @char_1_utf8.force_encoding('UTF-8') + end end if File.directory?(REPOSITORY_PATH) @@ -137,26 +143,68 @@ :path => repository_path_hash(['doc-mkdir.txt'])[:param] assert_response :success assert_template 'annotate' - assert_tag :tag => 'th', :content => '2', - :sibling => { - :tag => 'td', - :child => { - :tag => 'a', - :content => '3' - } - } - assert_tag :tag => 'th', :content => '2', - :sibling => { :tag => 'td', :content => /jsmith/ } - assert_tag :tag => 'th', :content => '2', - :sibling => { - :tag => 'td', - :child => { - :tag => 'a', - :content => '3' - } - } - assert_tag :tag => 'th', :content => '2', - :sibling => { :tag => 'td', :content => /Main purpose/ } + assert_select "th.line-num", :text => '2' do + assert_select "+ td.revision" do + assert_select "a", :text => '3' + assert_select "+ td.author", :text => "jsmith@" do + assert_select "+ td", + :text => "Main purpose:" + end + end + end + end + + def test_annotate_author_escaping + repository = Repository::Bazaar.create( + :project => @project, + :url => File.join(REPOSITORY_PATH, "author_escaping"), + :identifier => 'author_escaping', + :log_encoding => 'UTF-8') + assert repository + get :annotate, :id => PRJ_ID, :repository_id => 'author_escaping', + :path => repository_path_hash(['author-escaping-test.txt'])[:param] + assert_response :success + assert_template 'annotate' + assert_select "th.line-num", :text => '1' do + assert_select "+ td.revision" do + assert_select "a", :text => '2' + assert_select "+ td.author", :text => "test &" do + assert_select "+ td", + :text => "author escaping test" + end + end + end + end + + if REPOSITORY_PATH.respond_to?(:force_encoding) + def test_annotate_author_non_ascii + log_encoding = nil + if Encoding.locale_charmap == "UTF-8" || + Encoding.locale_charmap == "ISO-8859-1" + log_encoding = Encoding.locale_charmap + end + unless log_encoding.nil? + repository = Repository::Bazaar.create( + :project => @project, + :url => File.join(REPOSITORY_PATH, "author_non_ascii"), + :identifier => 'author_non_ascii', + :log_encoding => log_encoding) + assert repository + get :annotate, :id => PRJ_ID, :repository_id => 'author_non_ascii', + :path => repository_path_hash(['author-non-ascii-test.txt'])[:param] + assert_response :success + assert_template 'annotate' + assert_select "th.line-num", :text => '1' do + assert_select "+ td.revision" do + assert_select "a", :text => '2' + assert_select "+ td.author", :text => "test #{@char_1_utf8}" do + assert_select "+ td", + :text => "author non ASCII test" + end + end + end + end + end end def test_destroy_valid_repository diff -r d98d22a98252 -r afce8026aaeb test/functional/repositories_controller_test.rb --- a/test/functional/repositories_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/repositories_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'repositories_controller' - -# Re-raise errors caught by the controller. -class RepositoriesController; def rescue_action(e) raise e end; end class RepositoriesControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, @@ -27,9 +23,6 @@ :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers def setup - @controller = RepositoriesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -118,6 +111,31 @@ assert_nil Repository.find_by_id(11) end + def test_show_with_autofetch_changesets_enabled_should_fetch_changesets + Repository::Subversion.any_instance.expects(:fetch_changesets).once + + with_settings :autofetch_changesets => '1' do + get :show, :id => 1 + end + end + + def test_show_with_autofetch_changesets_disabled_should_not_fetch_changesets + Repository::Subversion.any_instance.expects(:fetch_changesets).never + + with_settings :autofetch_changesets => '0' do + get :show, :id => 1 + end + end + + def test_show_with_closed_project_should_not_fetch_changesets + Repository::Subversion.any_instance.expects(:fetch_changesets).never + Project.find(1).close + + with_settings :autofetch_changesets => '1' do + get :show, :id => 1 + end + end + def test_revisions get :revisions, :id => 1 assert_response :success @@ -181,6 +199,14 @@ assert_include 'Feature request #2', response.body end + def test_add_related_issue_should_accept_issue_id_with_sharp + @request.session[:user_id] = 2 + assert_difference 'Changeset.find(103).issues.size' do + xhr :post, :add_related_issue, :id => 1, :rev => 4, :issue_id => "#2", :format => 'js' + end + assert_equal [2], Changeset.find(103).issue_ids + end + def test_add_related_issue_with_invalid_issue_id @request.session[:user_id] = 2 assert_no_difference 'Changeset.find(103).issues.size' do @@ -262,7 +288,7 @@ :revision => 100, :comments => 'Committed by foo.' ) - assert_no_difference "Changeset.count(:conditions => 'user_id = 3')" do + assert_no_difference "Changeset.where(:user_id => 3).count" do post :committers, :id => 10, :committers => { '0' => ['foo', '2'], '1' => ['dlopper', '3']} assert_response 302 assert_equal User.find(2), c.reload.user diff -r d98d22a98252 -r afce8026aaeb test/functional/repositories_cvs_controller_test.rb --- a/test/functional/repositories_cvs_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/repositories_cvs_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/functional/repositories_darcs_controller_test.rb --- a/test/functional/repositories_darcs_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/repositories_darcs_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/functional/repositories_filesystem_controller_test.rb --- a/test/functional/repositories_filesystem_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/repositories_filesystem_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -109,7 +109,8 @@ end def test_show_utf16 - with_settings :repositories_encodings => 'UTF-16' do + enc = (RUBY_VERSION == "1.9.2" ? 'UTF-16LE' : 'UTF-16') + with_settings :repositories_encodings => enc do get :entry, :id => PRJ_ID, :path => repository_path_hash(['japanese', 'utf-16.txt'])[:param] assert_response :success diff -r d98d22a98252 -r afce8026aaeb test/functional/repositories_git_controller_test.rb --- a/test/functional/repositories_git_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/repositories_git_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -27,6 +27,7 @@ REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? PRJ_ID = 3 CHAR_1_HEX = "\xc3\x9c" + FELIX_HEX = "Felix Sch\xC3\xA4fer" NUM_REV = 28 ## Git, Mercurial and CVS path encodings are binary. @@ -50,8 +51,10 @@ ) assert @repository @char_1 = CHAR_1_HEX.dup + @felix_utf8 = FELIX_HEX.dup if @char_1.respond_to?(:force_encoding) @char_1.force_encoding('UTF-8') + @felix_utf8.force_encoding('UTF-8') end end @@ -532,11 +535,32 @@ get :annotate, :id => PRJ_ID, :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param], :rev => r1 - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', - :content => /test-#{@char_1}.txt/ } + assert_select "th.line-num", :text => '1' do + assert_select "+ td.revision" do + assert_select "a", :text => '57ca437c' + assert_select "+ td.author", :text => "jsmith" do + assert_select "+ td", + :text => "test-#{@char_1}.txt" + end + end + end + end + end + end + end + + def test_annotate_latin_1_author + ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', '83ca5fd546063a'].each do |r1| + get :annotate, :id => PRJ_ID, + :path => repository_path_hash([" filename with a leading space.txt "])[:param], + :rev => r1 + assert_select "th.line-num", :text => '1' do + assert_select "+ td.revision" do + assert_select "a", :text => '83ca5fd5' + assert_select "+ td.author", :text => @felix_utf8 do + assert_select "+ td", + :text => "And this is a file with a leading and trailing space..." + end end end end diff -r d98d22a98252 -r afce8026aaeb test/functional/repositories_mercurial_controller_test.rb --- a/test/functional/repositories_mercurial_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/repositories_mercurial_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,7 +26,7 @@ REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s CHAR_1_HEX = "\xc3\x9c" PRJ_ID = 3 - NUM_REV = 32 + NUM_REV = 34 ruby19_non_utf8_pass = (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8') @@ -432,30 +432,15 @@ :rev => r1 assert_response :success assert_template 'annotate' - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => - { - :tag => 'td', - :attributes => { :class => 'revision' }, - :child => { :tag => 'a', :content => '20:709858aafd1b' } - } - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => - { - :tag => 'td' , - :content => 'jsmith' , - :attributes => { :class => 'author' }, - } - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', - :content => /Mercurial is a distributed version control system/ } - + assert_select "th.line-num", :text => '1' do + assert_select "+ td.revision" do + assert_select "a", :text => '20:709858aafd1b' + assert_select "+ td.author", :text => "jsmith" do + assert_select "+ td", + :text => "Mercurial is a distributed version control system." + end + end + end end end @@ -474,6 +459,22 @@ end end + def test_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['1', '9d5b5b', '9d5b5b004199'].each do |r| + with_settings :default_language => "en" do + get :revision, :id => PRJ_ID, :rev => r + assert_response :success + assert_template 'revision' + assert_select 'title', + :text => 'Revision 1:9d5b5b004199 - Added 2 files and modified one. - eCookbook Subproject 1 - Redmine' + end + end + end + def test_empty_revision assert_equal 0, @repository.changesets.count @repository.fetch_changesets diff -r d98d22a98252 -r afce8026aaeb test/functional/repositories_subversion_controller_test.rb --- a/test/functional/repositories_subversion_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/repositories_subversion_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -326,10 +326,10 @@ @project.reload assert_equal NUM_REV, @repository.changesets.count - get :diff, :id => PRJ_ID, :rev => 3, :format => 'diff' + get :diff, :id => PRJ_ID, :rev => 5, :format => 'diff' assert_response :success assert_equal 'text/x-patch', @response.content_type - assert_equal 'Index: subversion_test/textfile.txt', @response.body.split(/\r?\n/).first + assert_equal 'Index: subversion_test/folder/greeter.rb', @response.body.split(/\r?\n/).first end def test_directory_diff diff -r d98d22a98252 -r afce8026aaeb test/functional/roles_controller_test.rb --- a/test/functional/roles_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/roles_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -21,9 +21,6 @@ fixtures :roles, :users, :members, :member_roles, :workflows, :trackers def setup - @controller = RolesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end @@ -34,7 +31,7 @@ assert_template 'index' assert_not_nil assigns(:roles) - assert_equal Role.find(:all, :order => 'builtin, position'), assigns(:roles) + assert_equal Role.order('builtin, position').all, assigns(:roles) assert_tag :tag => 'a', :attributes => { :href => '/roles/1/edit' }, :content => 'Manager' @@ -163,7 +160,7 @@ assert_template 'permissions' assert_not_nil assigns(:roles) - assert_equal Role.find(:all, :order => 'builtin, position'), assigns(:roles) + assert_equal Role.order('builtin, position').all, assigns(:roles) assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'permissions[3][]', diff -r d98d22a98252 -r afce8026aaeb test/functional/search_controller_test.rb --- a/test/functional/search_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/search_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,8 +1,21 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../test_helper', __FILE__) -require 'search_controller' - -# Re-raise errors caught by the controller. -class SearchController; def rescue_action(e) raise e end; end class SearchControllerTest < ActionController::TestCase fixtures :projects, :enabled_modules, :roles, :users, :members, :member_roles, @@ -11,9 +24,6 @@ :repositories, :changesets def setup - @controller = SearchController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r d98d22a98252 -r afce8026aaeb test/functional/search_custom_fields_visibility_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/functional/search_custom_fields_visibility_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,78 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class SearchCustomFieldsVisibilityTest < ActionController::TestCase + tests SearchController + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issue_statuses, + :trackers, + :projects_trackers, + :enabled_modules, + :enumerations, + :workflows + + def setup + field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :searchable => true, :trackers => Tracker.all} + @fields = [] + @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) + @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) + @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) + @issue = Issue.generate!( + :author_id => 1, + :project_id => 1, + :tracker_id => 1, + :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'} + ) + + @user_with_role_on_other_project = User.generate! + User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3)) + + @users_to_test = { + User.find(1) => [@field1, @field2, @field3], + User.find(3) => [@field1, @field2], + @user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 + User.generate! => [@field1], + User.anonymous => [@field1] + } + + Member.where(:project_id => 1).each do |member| + member.destroy unless @users_to_test.keys.include?(member.principal) + end + end + + def test_search_should_search_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + @fields.each_with_index do |field, i| + get :index, :q => "value#{i}" + assert_response :success + # we should get a result only if the custom field is visible + if fields.include?(field) + assert_equal 1, assigns(:results).size + else + assert_equal 0, assigns(:results).size + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb test/functional/sessions_test.rb --- a/test/functional/sessions_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/sessions_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/functional/settings_controller_test.rb --- a/test/functional/settings_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/settings_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'settings_controller' - -# Re-raise errors caught by the controller. -class SettingsController; def rescue_action(e) raise e end; end class SettingsControllerTest < ActionController::TestCase fixtures :users def setup - @controller = SettingsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end @@ -79,7 +72,7 @@ :notified_events => %w(issue_added issue_updated news_added), :emails_footer => 'Test footer' } - assert_redirected_to '/settings/edit' + assert_redirected_to '/settings' assert_equal 'functional@test.foo', Setting.mail_from assert !Setting.bcc_recipients? assert_equal %w(issue_added issue_updated news_added), Setting.notified_events @@ -87,6 +80,61 @@ Setting.clear_cache end + def test_edit_commit_update_keywords + with_settings :commit_update_keywords => [ + {"keywords" => "fixes, resolves", "status_id" => "3"}, + {"keywords" => "closes", "status_id" => "5", "done_ratio" => "100", "if_tracker_id" => "2"} + ] do + get :edit + end + assert_response :success + assert_select 'tr.commit-keywords', 2 + assert_select 'tr.commit-keywords:nth-child(1)' do + assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'fixes, resolves' + assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do + assert_select 'option[value=3][selected=selected]' + end + end + assert_select 'tr.commit-keywords:nth-child(2)' do + assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'closes' + assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do + assert_select 'option[value=5][selected=selected]', :text => 'Closed' + end + assert_select 'select[name=?]', 'settings[commit_update_keywords][done_ratio][]' do + assert_select 'option[value=100][selected=selected]', :text => '100 %' + end + assert_select 'select[name=?]', 'settings[commit_update_keywords][if_tracker_id][]' do + assert_select 'option[value=2][selected=selected]', :text => 'Feature request' + end + end + end + + def test_edit_without_commit_update_keywords_should_show_blank_line + with_settings :commit_update_keywords => [] do + get :edit + end + assert_response :success + assert_select 'tr.commit-keywords', 1 do + assert_select 'input[name=?]:not([value])', 'settings[commit_update_keywords][keywords][]' + end + end + + def test_post_edit_commit_update_keywords + post :edit, :settings => { + :commit_update_keywords => { + :keywords => ["resolves", "closes"], + :status_id => ["3", "5"], + :done_ratio => ["", "100"], + :if_tracker_id => ["", "2"] + } + } + assert_redirected_to '/settings' + assert_equal([ + {"keywords" => "resolves", "status_id" => "3"}, + {"keywords" => "closes", "status_id" => "5", "done_ratio" => "100", "if_tracker_id" => "2"} + ], Setting.commit_update_keywords) + end + def test_get_plugin_settings Setting.stubs(:plugin_foo).returns({'sample_setting' => 'Plugin setting value'}) ActionController::Base.append_view_path(File.join(Rails.root, "test/fixtures/plugins")) @@ -108,11 +156,31 @@ assert_response 404 end + def test_get_non_configurable_plugin_settings + Redmine::Plugin.register(:foo) {} + + get :plugin, :id => 'foo' + assert_response 404 + + Redmine::Plugin.clear + end + def test_post_plugin_settings Setting.expects(:plugin_foo=).with({'sample_setting' => 'Value'}).returns(true) - Redmine::Plugin.register(:foo) {} + Redmine::Plugin.register(:foo) do + settings :partial => 'not blank' # so that configurable? is true + end post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'} assert_redirected_to '/settings/plugin/foo' end + + def test_post_non_configurable_plugin_settings + Redmine::Plugin.register(:foo) {} + + post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'} + assert_response 404 + + Redmine::Plugin.clear + end end diff -r d98d22a98252 -r afce8026aaeb test/functional/sys_controller_test.rb --- a/test/functional/sys_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/sys_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,19 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'sys_controller' -require 'mocha' - -# Re-raise errors caught by the controller. -class SysController; def rescue_action(e) raise e end; end class SysControllerTest < ActionController::TestCase fixtures :projects, :repositories, :enabled_modules def setup - @controller = SysController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new Setting.sys_api_enabled = '1' Setting.enabled_scm = %w(Subversion Git) end diff -r d98d22a98252 -r afce8026aaeb test/functional/time_entry_reports_controller_test.rb --- a/test/functional/time_entry_reports_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/time_entry_reports_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,4 +1,21 @@ # -*- coding: utf-8 -*- +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../test_helper', __FILE__) class TimeEntryReportsControllerTest < ActionController::TestCase @@ -73,7 +90,7 @@ end def test_report_two_criteria - get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["member", "activity"] + get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["user", "activity"] assert_response :success assert_template 'report' assert_not_nil assigns(:report) @@ -91,7 +108,7 @@ end def test_report_one_day - get :report, :project_id => 1, :columns => 'day', :from => "2007-03-23", :to => "2007-03-23", :criteria => ["member", "activity"] + get :report, :project_id => 1, :columns => 'day', :from => "2007-03-23", :to => "2007-03-23", :criteria => ["user", "activity"] assert_response :success assert_template 'report' assert_not_nil assigns(:report) @@ -99,7 +116,7 @@ end def test_report_at_issue_level - get :report, :project_id => 1, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["member", "activity"] + get :report, :project_id => 1, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["user", "activity"] assert_response :success assert_template 'report' assert_not_nil assigns(:report) @@ -108,22 +125,62 @@ :attributes => {:action => "/projects/ecookbook/issues/1/time_entries/report", :id => 'query_form'} end - def test_report_custom_field_criteria - get :report, :project_id => 1, :criteria => ['project', 'cf_1', 'cf_7'] + def test_report_by_week_should_use_commercial_year + TimeEntry.delete_all + TimeEntry.generate!(:hours => '2', :spent_on => '2009-12-25') # 2009-52 + TimeEntry.generate!(:hours => '4', :spent_on => '2009-12-31') # 2009-53 + TimeEntry.generate!(:hours => '8', :spent_on => '2010-01-01') # 2009-53 + TimeEntry.generate!(:hours => '16', :spent_on => '2010-01-05') # 2010-1 + + get :report, :columns => 'week', :from => "2009-12-25", :to => "2010-01-05", :criteria => ["project"] + assert_response :success + + assert_select '#time-report thead tr' do + assert_select 'th:nth-child(1)', :text => 'Project' + assert_select 'th:nth-child(2)', :text => '2009-52' + assert_select 'th:nth-child(3)', :text => '2009-53' + assert_select 'th:nth-child(4)', :text => '2010-1' + assert_select 'th:nth-child(5)', :text => 'Total time' + end + assert_select '#time-report tbody tr' do + assert_select 'td:nth-child(1)', :text => 'eCookbook' + assert_select 'td:nth-child(2)', :text => '2.00' + assert_select 'td:nth-child(3)', :text => '12.00' + assert_select 'td:nth-child(4)', :text => '16.00' + assert_select 'td:nth-child(5)', :text => '30.00' # Total + end + end + + def test_report_should_propose_association_custom_fields + get :report + assert_response :success + assert_template 'report' + + assert_select 'select[name=?]', 'criteria[]' do + assert_select 'option[value=cf_1]', {:text => 'Database'}, 'Issue custom field not found' + assert_select 'option[value=cf_3]', {:text => 'Development status'}, 'Project custom field not found' + assert_select 'option[value=cf_7]', {:text => 'Billable'}, 'TimeEntryActivity custom field not found' + end + end + + def test_report_with_association_custom_fields + get :report, :criteria => ['cf_1', 'cf_3', 'cf_7'] assert_response :success assert_template 'report' assert_not_nil assigns(:report) assert_equal 3, assigns(:report).criteria.size assert_equal "162.90", "%.2f" % assigns(:report).total_hours - # Custom field column - assert_tag :tag => 'th', :content => 'Database' + + # Custom fields columns + assert_select 'th', :text => 'Database' + assert_select 'th', :text => 'Development status' + assert_select 'th', :text => 'Billable' + # Custom field row - assert_tag :tag => 'td', :content => 'MySQL', - :sibling => { :tag => 'td', :attributes => { :class => 'hours' }, - :child => { :tag => 'span', :attributes => { :class => 'hours hours-int' }, - :content => '1' }} - # Second custom field column - assert_tag :tag => 'th', :content => 'Billable' + assert_select 'tr' do + assert_select 'td', :text => 'MySQL' + assert_select 'td.hours', :text => '1.00' + end end def test_report_one_criteria_no_result @@ -144,29 +201,27 @@ def test_report_all_projects_csv_export get :report, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", - :criteria => ["project", "member", "activity"], :format => "csv" + :criteria => ["project", "user", "activity"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', - lines.first + assert_equal 'Project,User,Activity,2007-3,2007-4,Total time', lines.first # Total row - assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last + assert_equal 'Total time,"","",154.25,8.65,162.90', lines.last end def test_report_csv_export get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", - :criteria => ["project", "member", "activity"], :format => "csv" + :criteria => ["project", "user", "activity"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', - lines.first + assert_equal 'Project,User,Activity,2007-3,2007-4,Total time', lines.first # Total row - assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last + assert_equal 'Total time,"","",154.25,8.65,162.90', lines.last end def test_csv_big_5 @@ -196,13 +251,13 @@ get :report, :project_id => 1, :columns => 'day', :from => "2011-11-11", :to => "2011-11-11", - :criteria => ["member"], :format => "csv" + :criteria => ["user"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - s1 = "\xa6\xa8\xad\xfb,2011-11-11,\xc1`\xadp" - s2 = "\xc1`\xadp" + s1 = "\xa5\xce\xa4\xe1,2011-11-11,\xa4u\xae\xc9\xc1`\xadp" + s2 = "\xa4u\xae\xc9\xc1`\xadp" if s1.respond_to?(:force_encoding) s1.force_encoding('Big5') s2.force_encoding('Big5') @@ -247,12 +302,12 @@ get :report, :project_id => 1, :columns => 'day', :from => "2011-11-11", :to => "2011-11-11", - :criteria => ["member"], :format => "csv" + :criteria => ["user"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - s1 = "\xa6\xa8\xad\xfb,2011-11-11,\xc1`\xadp" + s1 = "\xa5\xce\xa4\xe1,2011-11-11,\xa4u\xae\xc9\xc1`\xadp" if s1.respond_to?(:force_encoding) s1.force_encoding('Big5') end @@ -288,13 +343,13 @@ get :report, :project_id => 1, :columns => 'day', :from => "2011-11-11", :to => "2011-11-11", - :criteria => ["member"], :format => "csv" + :criteria => ["user"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - s1 = "Membre;2011-11-11;Total" - s2 = "Total" + s1 = "Utilisateur;2011-11-11;Temps total" + s2 = "Temps total" if s1.respond_to?(:force_encoding) s1.force_encoding('ISO-8859-1') s2.force_encoding('ISO-8859-1') diff -r d98d22a98252 -r afce8026aaeb test/functional/timelog_controller_test.rb --- a/test/functional/timelog_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/timelog_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,25 +17,17 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'timelog_controller' - -# Re-raise errors caught by the controller. -class TimelogController; def rescue_action(e) raise e end; end class TimelogControllerTest < ActionController::TestCase fixtures :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :time_entries, :users, :trackers, :enumerations, :issue_statuses, - :custom_fields, :custom_values + :custom_fields, :custom_values, + :projects_trackers, :custom_fields_trackers, + :custom_fields_projects include Redmine::I18n - def setup - @controller = TimelogController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - def test_new_with_project_id @request.session[:user_id] = 3 get :new, :project_id => 1 @@ -303,13 +295,20 @@ assert_response :success assert_template 'bulk_edit' - # System wide custom field - assert_tag :select, :attributes => {:name => 'time_entry[custom_field_values][10]'} + assert_select 'ul#bulk-selection' do + assert_select 'li', 2 + assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours' + end - # Activities - assert_select 'select[name=?]', 'time_entry[activity_id]' do - assert_select 'option[value=]', :text => '(No change)' - assert_select 'option[value=9]', :text => 'Design' + assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do + # System wide custom field + assert_select 'select[name=?]', 'time_entry[custom_field_values][10]' + + # Activities + assert_select 'select[name=?]', 'time_entry[activity_id]' do + assert_select 'option[value=]', :text => '(No change)' + assert_select 'option[value=9]', :text => 'Design' + end end end @@ -430,6 +429,14 @@ assert_tag 'a', :attributes => {:href => '/time_entries/new'}, :content => /Log time/ end + def test_index_my_spent_time + @request.session[:user_id] = 2 + get :index, :user_id => 'me' + assert_response :success + assert_template 'index' + assert assigns(:entries).all? {|entry| entry.user_id == 2} + end + def test_index_at_project_level get :index, :project_id => 'ecookbook' assert_response :success @@ -440,14 +447,48 @@ assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort assert_not_nil assigns(:total_hours) assert_equal "162.90", "%.2f" % assigns(:total_hours) - # display all time by default - assert_nil assigns(:from) - assert_nil assigns(:to) assert_tag :form, :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end + def test_index_with_display_subprojects_issues_to_false_should_not_include_subproject_entries + entry = TimeEntry.generate!(:project => Project.find(3)) + + with_settings :display_subprojects_issues => '0' do + get :index, :project_id => 'ecookbook' + assert_response :success + assert_template 'index' + assert_not_include entry, assigns(:entries) + end + end + + def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries + entry = TimeEntry.generate!(:project => Project.find(3)) + + with_settings :display_subprojects_issues => '0' do + get :index, :project_id => 'ecookbook', :subproject_id => 3 + assert_response :success + assert_template 'index' + assert_include entry, assigns(:entries) + end + end + def test_index_at_project_level_with_date_range + get :index, :project_id => 'ecookbook', + :f => ['spent_on'], + :op => {'spent_on' => '><'}, + :v => {'spent_on' => ['2007-03-20', '2007-04-30']} + assert_response :success + assert_template 'index' + assert_not_nil assigns(:entries) + assert_equal 3, assigns(:entries).size + assert_not_nil assigns(:total_hours) + assert_equal "12.90", "%.2f" % assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} + end + + def test_index_at_project_level_with_date_range_using_from_and_to_params get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30' assert_response :success assert_template 'index' @@ -455,116 +496,23 @@ assert_equal 3, assigns(:entries).size assert_not_nil assigns(:total_hours) assert_equal "12.90", "%.2f" % assigns(:total_hours) - assert_equal '2007-03-20'.to_date, assigns(:from) - assert_equal '2007-04-30'.to_date, assigns(:to) assert_tag :form, :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end def test_index_at_project_level_with_period - get :index, :project_id => 'ecookbook', :period => '7_days' + get :index, :project_id => 'ecookbook', + :f => ['spent_on'], + :op => {'spent_on' => '>t-'}, + :v => {'spent_on' => ['7']} assert_response :success assert_template 'index' assert_not_nil assigns(:entries) assert_not_nil assigns(:total_hours) - assert_equal Date.today - 7, assigns(:from) - assert_equal Date.today, assigns(:to) assert_tag :form, :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end - def test_index_one_day - get :index, :project_id => 'ecookbook', :from => "2007-03-23", :to => "2007-03-23" - assert_response :success - assert_template 'index' - assert_not_nil assigns(:total_hours) - assert_equal "4.25", "%.2f" % assigns(:total_hours) - assert_tag :form, - :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} - end - - def test_index_from_a_date - get :index, :project_id => 'ecookbook', :from => "2007-03-23", :to => "" - assert_equal '2007-03-23'.to_date, assigns(:from) - assert_nil assigns(:to) - end - - def test_index_to_a_date - get :index, :project_id => 'ecookbook', :from => "", :to => "2007-03-23" - assert_nil assigns(:from) - assert_equal '2007-03-23'.to_date, assigns(:to) - end - - def test_index_today - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'today' - assert_equal '2011-12-15'.to_date, assigns(:from) - assert_equal '2011-12-15'.to_date, assigns(:to) - end - - def test_index_yesterday - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'yesterday' - assert_equal '2011-12-14'.to_date, assigns(:from) - assert_equal '2011-12-14'.to_date, assigns(:to) - end - - def test_index_current_week - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'current_week' - assert_equal '2011-12-12'.to_date, assigns(:from) - assert_equal '2011-12-18'.to_date, assigns(:to) - end - - def test_index_last_week - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'last_week' - assert_equal '2011-12-05'.to_date, assigns(:from) - assert_equal '2011-12-11'.to_date, assigns(:to) - end - - def test_index_last_2_week - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'last_2_weeks' - assert_equal '2011-11-28'.to_date, assigns(:from) - assert_equal '2011-12-11'.to_date, assigns(:to) - end - - def test_index_7_days - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => '7_days' - assert_equal '2011-12-08'.to_date, assigns(:from) - assert_equal '2011-12-15'.to_date, assigns(:to) - end - - def test_index_current_month - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'current_month' - assert_equal '2011-12-01'.to_date, assigns(:from) - assert_equal '2011-12-31'.to_date, assigns(:to) - end - - def test_index_last_month - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'last_month' - assert_equal '2011-11-01'.to_date, assigns(:from) - assert_equal '2011-11-30'.to_date, assigns(:to) - end - - def test_index_30_days - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => '30_days' - assert_equal '2011-11-15'.to_date, assigns(:from) - assert_equal '2011-12-15'.to_date, assigns(:to) - end - - def test_index_current_year - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'current_year' - assert_equal '2011-01-01'.to_date, assigns(:from) - assert_equal '2011-12-31'.to_date, assigns(:to) - end - def test_index_at_issue_level get :index, :issue_id => 1 assert_response :success @@ -587,15 +535,70 @@ t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10) t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10) - get :index, :project_id => 1, :from => '2012-06-15', :to => '2012-06-16' + get :index, :project_id => 1, + :f => ['spent_on'], + :op => {'spent_on' => '><'}, + :v => {'spent_on' => ['2012-06-15', '2012-06-16']} assert_response :success assert_equal [t2, t1, t3], assigns(:entries) - get :index, :project_id => 1, :from => '2012-06-15', :to => '2012-06-16', :sort => 'spent_on' + get :index, :project_id => 1, + :f => ['spent_on'], + :op => {'spent_on' => '><'}, + :v => {'spent_on' => ['2012-06-15', '2012-06-16']}, + :sort => 'spent_on' assert_response :success assert_equal [t3, t1, t2], assigns(:entries) end + def test_index_with_filter_on_issue_custom_field + issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'}) + entry = TimeEntry.generate!(:issue => issue, :hours => 2.5) + + get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']} + assert_response :success + assert_equal [entry], assigns(:entries) + end + + def test_index_with_issue_custom_field_column + issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'}) + entry = TimeEntry.generate!(:issue => issue, :hours => 2.5) + + get :index, :c => %w(project spent_on issue comments hours issue.cf_2) + assert_response :success + assert_include :'issue.cf_2', assigns(:query).column_names + assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field' + end + + def test_index_with_time_entry_custom_field_column + field = TimeEntryCustomField.generate!(:field_format => 'string') + entry = TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value'}) + field_name = "cf_#{field.id}" + + get :index, :c => ["hours", field_name] + assert_response :success + assert_include field_name.to_sym, assigns(:query).column_names + assert_select "td.#{field_name}", :text => 'CF Value' + end + + def test_index_with_time_entry_custom_field_sorting + field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field') + TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'}) + TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'}) + TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'}) + field_name = "cf_#{field.id}" + + get :index, :c => ["hours", field_name], :sort => field_name + assert_response :success + assert_include field_name.to_sym, assigns(:query).column_names + assert_select "th a.sort", :text => 'String Field' + + # Make sure that values are properly sorted + values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact + assert_equal 3, values.size + assert_equal values.sort, values + end + def test_index_atom_feed get :index, :project_id => 1, :format => 'atom' assert_response :success @@ -604,186 +607,57 @@ assert assigns(:items).first.is_a?(TimeEntry) end - def test_index_all_projects_csv_export + def test_index_at_project_level_should_include_csv_export_dialog + get :index, :project_id => 'ecookbook', + :f => ['spent_on'], + :op => {'spent_on' => '>='}, + :v => {'spent_on' => ['2007-04-01']}, + :c => ['spent_on', 'user'] + assert_response :success + + assert_select '#csv-export-options' do + assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do + # filter + assert_select 'input[name=?][value=?]', 'f[]', 'spent_on' + assert_select 'input[name=?][value=?]', 'op[spent_on]', '>=' + assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01' + # columns + assert_select 'input[name=?][value=?]', 'c[]', 'spent_on' + assert_select 'input[name=?][value=?]', 'c[]', 'user' + assert_select 'input[name=?]', 'c[]', 2 + end + end + end + + def test_index_cross_project_should_include_csv_export_dialog + get :index + assert_response :success + + assert_select '#csv-export-options' do + assert_select 'form[action=?][method=get]', '/time_entries.csv' + end + end + + def test_index_at_issue_level_should_include_csv_export_dialog + get :index, :project_id => 'ecookbook', :issue_id => 3 + assert_response :success + + assert_select '#csv-export-options' do + assert_select 'form[action=?][method=get]', '/projects/ecookbook/issues/3/time_entries.csv' + end + end + + def test_index_csv_all_projects Setting.date_format = '%m/%d/%Y' get :index, :format => 'csv' assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n") - assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n") + assert_equal 'text/csv; header=present', response.content_type end - def test_index_csv_export + def test_index_csv Setting.date_format = '%m/%d/%Y' get :index, :project_id => 1, :format => 'csv' assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n") - assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n") - end - - def test_index_csv_export_with_multi_custom_field - field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'list', - :multiple => true, :possible_values => ['value1', 'value2']) - entry = TimeEntry.find(1) - entry.custom_field_values = {field.id => ['value1', 'value2']} - entry.save! - - get :index, :project_id => 1, :format => 'csv' - assert_response :success - assert_include '"value1, value2"', @response.body - end - - def test_csv_big_5 - user = User.find_by_id(3) - user.language = "zh-TW" - assert user.save - str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88" - str_big5 = "\xa4@\xa4\xeb" - if str_utf8.respond_to?(:force_encoding) - str_utf8.force_encoding('UTF-8') - str_big5.force_encoding('Big5') - end - @request.session[:user_id] = 3 - post :create, :project_id => 1, - :time_entry => {:comments => str_utf8, - # Not the default activity - :activity_id => '11', - :issue_id => '', - :spent_on => '2011-11-10', - :hours => '7.3'} - assert_redirected_to :action => 'index', :project_id => 'ecookbook' - - t = TimeEntry.find_by_comments(str_utf8) - assert_not_nil t - assert_equal 11, t.activity_id - assert_equal 7.3, t.hours - assert_equal 3, t.user_id - - get :index, :project_id => 1, :format => 'csv', - :from => '2011-11-10', :to => '2011-11-10' - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - ar = @response.body.chomp.split("\n") - s1 = "\xa4\xe9\xb4\xc1" - if str_utf8.respond_to?(:force_encoding) - s1.force_encoding('Big5') - end - assert ar[0].include?(s1) - assert ar[1].include?(str_big5) - end - - def test_csv_cannot_convert_should_be_replaced_big_5 - user = User.find_by_id(3) - user.language = "zh-TW" - assert user.save - str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85" - if str_utf8.respond_to?(:force_encoding) - str_utf8.force_encoding('UTF-8') - end - @request.session[:user_id] = 3 - post :create, :project_id => 1, - :time_entry => {:comments => str_utf8, - # Not the default activity - :activity_id => '11', - :issue_id => '', - :spent_on => '2011-11-10', - :hours => '7.3'} - assert_redirected_to :action => 'index', :project_id => 'ecookbook' - - t = TimeEntry.find_by_comments(str_utf8) - assert_not_nil t - assert_equal 11, t.activity_id - assert_equal 7.3, t.hours - assert_equal 3, t.user_id - - get :index, :project_id => 1, :format => 'csv', - :from => '2011-11-10', :to => '2011-11-10' - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - ar = @response.body.chomp.split("\n") - s1 = "\xa4\xe9\xb4\xc1" - if str_utf8.respond_to?(:force_encoding) - s1.force_encoding('Big5') - end - assert ar[0].include?(s1) - s2 = ar[1].split(",")[8] - if s2.respond_to?(:force_encoding) - s3 = "\xa5H?" - s3.force_encoding('Big5') - assert_equal s3, s2 - elsif RUBY_PLATFORM == 'java' - assert_equal "??", s2 - else - assert_equal "\xa5H???", s2 - end - end - - def test_csv_tw - with_settings :default_language => "zh-TW" do - str1 = "test_csv_tw" - user = User.find_by_id(3) - te1 = TimeEntry.create(:spent_on => '2011-11-10', - :hours => 999.9, - :project => Project.find(1), - :user => user, - :activity => TimeEntryActivity.find_by_name('Design'), - :comments => str1) - te2 = TimeEntry.find_by_comments(str1) - assert_not_nil te2 - assert_equal 999.9, te2.hours - assert_equal 3, te2.user_id - - get :index, :project_id => 1, :format => 'csv', - :from => '2011-11-10', :to => '2011-11-10' - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - - ar = @response.body.chomp.split("\n") - s2 = ar[1].split(",")[7] - assert_equal '999.9', s2 - - str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)" - if str_tw.respond_to?(:force_encoding) - str_tw.force_encoding('UTF-8') - end - assert_equal str_tw, l(:general_lang_name) - assert_equal ',', l(:general_csv_separator) - assert_equal '.', l(:general_csv_decimal_separator) - end - end - - def test_csv_fr - with_settings :default_language => "fr" do - str1 = "test_csv_fr" - user = User.find_by_id(3) - te1 = TimeEntry.create(:spent_on => '2011-11-10', - :hours => 999.9, - :project => Project.find(1), - :user => user, - :activity => TimeEntryActivity.find_by_name('Design'), - :comments => str1) - te2 = TimeEntry.find_by_comments(str1) - assert_not_nil te2 - assert_equal 999.9, te2.hours - assert_equal 3, te2.user_id - - get :index, :project_id => 1, :format => 'csv', - :from => '2011-11-10', :to => '2011-11-10' - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - - ar = @response.body.chomp.split("\n") - s2 = ar[1].split(";")[7] - assert_equal '999,9', s2 - - str_fr = "Fran\xc3\xa7ais" - if str_fr.respond_to?(:force_encoding) - str_fr.force_encoding('UTF-8') - end - assert_equal str_fr, l(:general_lang_name) - assert_equal ';', l(:general_csv_separator) - assert_equal ',', l(:general_csv_decimal_separator) - end + assert_equal 'text/csv; header=present', response.content_type end end diff -r d98d22a98252 -r afce8026aaeb test/functional/timelog_custom_fields_visibility_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/functional/timelog_custom_fields_visibility_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,113 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class TimelogCustomFieldsVisibilityTest < ActionController::TestCase + tests TimelogController + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issue_statuses, + :trackers, + :projects_trackers, + :enabled_modules, + :enumerations, + :workflows + + def setup + field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all} + @fields = [] + @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) + @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) + @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) + @issue = Issue.generate!( + :author_id => 1, + :project_id => 1, + :tracker_id => 1, + :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'} + ) + TimeEntry.generate!(:issue => @issue) + + @user_with_role_on_other_project = User.generate! + User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3)) + + @users_to_test = { + User.find(1) => [@field1, @field2, @field3], + User.find(3) => [@field1, @field2], + @user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 + User.generate! => [@field1], + User.anonymous => [@field1] + } + + Member.where(:project_id => 1).each do |member| + member.destroy unless @users_to_test.keys.include?(member.principal) + end + end + + def test_index_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :project_id => 1, :issue_id => @issue.id, :c => (['hours'] + @fields.map{|f| "issue.cf_#{f.id}"}) + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'td', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name}" + else + assert_select 'td', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name}" + end + end + end + end + + def test_index_as_csv_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :project_id => 1, :issue_id => @issue.id, :c => (['hours'] + @fields.map{|f| "issue.cf_#{f.id}"}), :format => 'csv' + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_include "Value#{i}", response.body, "User #{user.id} was not able to view #{field.name} in CSV" + else + assert_not_include "Value#{i}", response.body, "User #{user.id} was able to view #{field.name} in CSV" + end + end + end + end + + def test_index_with_partial_custom_field_visibility_should_show_visible_custom_fields_only + Issue.delete_all + TimeEntry.delete_all + p1 = Project.generate! + p2 = Project.generate! + user = User.generate! + User.add_to_project(user, p1, Role.find_all_by_id(1,3)) + User.add_to_project(user, p2, Role.find_all_by_id(3)) + TimeEntry.generate!(:issue => Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueA'})) + TimeEntry.generate!(:issue => Issue.generate!(:project => p2, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueB'})) + TimeEntry.generate!(:issue => Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueC'})) + + @request.session[:user_id] = user.id + get :index, :c => ["hours", "issue.cf_#{@field2.id}"] + assert_select 'td', :text => 'ValueA' + assert_select 'td', :text => 'ValueB', :count => 0 + assert_select 'td', :text => 'ValueC' + + get :index, :set_filter => '1', "issue.cf_#{@field2.id}" => '*' + assert_equal %w(ValueA ValueC), assigns(:entries).map{|i| i.issue.custom_field_value(@field2)}.sort + end +end diff -r d98d22a98252 -r afce8026aaeb test/functional/trackers_controller_test.rb --- a/test/functional/trackers_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/trackers_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'trackers_controller' - -# Re-raise errors caught by the controller. -class TrackersController; def rescue_action(e) raise e end; end class TrackersControllerTest < ActionController::TestCase fixtures :trackers, :projects, :projects_trackers, :users, :issues, :custom_fields def setup - @controller = TrackersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end diff -r d98d22a98252 -r afce8026aaeb test/functional/users_controller_test.rb --- a/test/functional/users_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/users_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'users_controller' - -# Re-raise errors caught by the controller. -class UsersController; def rescue_action(e) raise e end; end class UsersControllerTest < ActionController::TestCase include Redmine::I18n @@ -29,9 +25,6 @@ :auth_sources def setup - @controller = UsersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end @@ -40,12 +33,6 @@ get :index assert_response :success assert_template 'index' - end - - def test_index - get :index - assert_response :success - assert_template 'index' assert_not_nil assigns(:users) # active users only assert_nil assigns(:users).detect {|u| !u.active?} @@ -225,6 +212,30 @@ assert_equal '0', user.pref[:warn_on_leaving_unsaved] end + def test_create_with_generate_password_should_email_the_password + assert_difference 'User.count' do + post :create, :user => { + :login => 'randompass', + :firstname => 'Random', + :lastname => 'Pass', + :mail => 'randompass@example.net', + :language => 'en', + :generate_password => '1', + :password => '', + :password_confirmation => '' + }, :send_information => 1 + end + user = User.order('id DESC').first + assert_equal 'randompass', user.login + + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/) + assert m + password = m[1] + assert user.check_password?(password) + end + def test_create_with_failure assert_no_difference 'User.count' do post :create, :user => {} @@ -297,6 +308,37 @@ assert_mail_body_match 'newpass123', mail end + def test_update_with_generate_password_should_email_the_password + ActionMailer::Base.deliveries.clear + Setting.bcc_recipients = '1' + + put :update, :id => 2, :user => { + :generate_password => '1', + :password => '', + :password_confirmation => '' + }, :send_information => '1' + + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/) + assert m + password = m[1] + assert User.find(2).check_password?(password) + end + + def test_update_without_generate_password_should_not_change_password + put :update, :id => 2, :user => { + :firstname => 'changed', + :generate_password => '0', + :password => '', + :password_confirmation => '' + }, :send_information => '1' + + user = User.find(2) + assert_equal 'changed', user.firstname + assert user.check_password?('jsmith') + end + def test_update_user_switchin_from_auth_source_to_password_authentication # Configure as auth source u = User.find(2) @@ -316,22 +358,30 @@ u = User.find(2) assert_equal [1, 2, 5], u.projects.collect{|p| p.id}.sort assert_equal [1, 2, 5], u.notified_projects_ids.sort - assert_tag :tag => 'input', - :attributes => { - :id => 'notified_project_ids_', - :value => 1, - } + assert_select 'input[name=?][value=?]', 'user[notified_project_ids][]', '1' assert_equal 'all', u.mail_notification put :update, :id => 2, :user => { - :mail_notification => 'selected', - }, - :notified_project_ids => [1, 2] + :mail_notification => 'selected', + :notified_project_ids => [1, 2] + } u = User.find(2) assert_equal 'selected', u.mail_notification assert_equal [1, 2], u.notified_projects_ids.sort end + def test_update_status_should_not_update_attributes + user = User.find(2) + user.pref[:no_self_notified] = '1' + user.pref.save + + put :update, :id => 2, :user => {:status => 3} + assert_response 302 + user = User.find(2) + assert_equal 3, user.status + assert_equal '1', user.pref[:no_self_notified] + end + def test_destroy assert_difference 'User.count', -1 do delete :destroy, :id => 2 diff -r d98d22a98252 -r afce8026aaeb test/functional/versions_controller_test.rb --- a/test/functional/versions_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/versions_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,18 +16,13 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'versions_controller' - -# Re-raise errors caught by the controller. -class VersionsController; def rescue_action(e) raise e end; end class VersionsControllerTest < ActionController::TestCase - fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules, :issue_statuses, :issue_categories + fixtures :projects, :versions, :issues, :users, :roles, :members, + :member_roles, :enabled_modules, :issue_statuses, + :issue_categories def setup - @controller = VersionsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -42,12 +37,12 @@ assert !assigns(:versions).include?(Version.find(1)) # Context menu on issues assert_select "script", :text => Regexp.new(Regexp.escape("contextMenuInit('/issues/context_menu')")) - # Links to versions anchors - assert_tag 'a', :attributes => {:href => '#2.0'}, - :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}} - # Links to completed versions in the sidebar - assert_tag 'a', :attributes => {:href => '/versions/1'}, - :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}} + assert_select "div#sidebar" do + # Links to versions anchors + assert_select 'a[href=?]', '#2.0' + # Links to completed versions in the sidebar + assert_select 'a[href=?]', '/versions/1' + end end def test_index_with_completed_versions @@ -178,16 +173,18 @@ Version.update_all("status = 'open'") @request.session[:user_id] = 2 put :close_completed, :project_id => 'ecookbook' - assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' + assert_redirected_to :controller => 'projects', :action => 'settings', + :tab => 'versions', :id => 'ecookbook' assert_not_nil Version.find_by_status('closed') end def test_post_update @request.session[:user_id] = 2 put :update, :id => 2, - :version => { :name => 'New version name', - :effective_date => Date.today.strftime("%Y-%m-%d")} - assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' + :version => {:name => 'New version name', + :effective_date => Date.today.strftime("%Y-%m-%d")} + assert_redirected_to :controller => 'projects', :action => 'settings', + :tab => 'versions', :id => 'ecookbook' version = Version.find(2) assert_equal 'New version name', version.name assert_equal Date.today, version.effective_date @@ -207,7 +204,8 @@ assert_difference 'Version.count', -1 do delete :destroy, :id => 3 end - assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' + assert_redirected_to :controller => 'projects', :action => 'settings', + :tab => 'versions', :id => 'ecookbook' assert_nil Version.find_by_id(3) end @@ -216,7 +214,8 @@ assert_no_difference 'Version.count' do delete :destroy, :id => 2 end - assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' + assert_redirected_to :controller => 'projects', :action => 'settings', + :tab => 'versions', :id => 'ecookbook' assert flash[:error].match(/Unable to delete version/) assert Version.find_by_id(2) end diff -r d98d22a98252 -r afce8026aaeb test/functional/watchers_controller_test.rb --- a/test/functional/watchers_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/watchers_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,23 +16,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'watchers_controller' - -# Re-raise errors caught by the controller. -class WatchersController; def rescue_action(e) raise e end; end class WatchersControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :issues, :trackers, :projects_trackers, :issue_statuses, :enumerations, :watchers def setup - @controller = WatchersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end - def test_watch + def test_watch_a_single_object @request.session[:user_id] = 3 assert_difference('Watcher.count') do xhr :post, :watch, :object_type => 'issue', :object_id => '1' @@ -42,6 +35,27 @@ assert Issue.find(1).watched_by?(User.find(3)) end + def test_watch_a_collection_with_a_single_object + @request.session[:user_id] = 3 + assert_difference('Watcher.count') do + xhr :post, :watch, :object_type => 'issue', :object_id => ['1'] + assert_response :success + assert_include '$(".issue-1-watcher")', response.body + end + assert Issue.find(1).watched_by?(User.find(3)) + end + + def test_watch_a_collection_with_multiple_objects + @request.session[:user_id] = 3 + assert_difference('Watcher.count', 2) do + xhr :post, :watch, :object_type => 'issue', :object_id => ['1', '3'] + assert_response :success + assert_include '$(".issue-bulk-watcher")', response.body + end + assert Issue.find(1).watched_by?(User.find(3)) + assert Issue.find(3).watched_by?(User.find(3)) + end + def test_watch_should_be_denied_without_permission Role.find(2).remove_permission! :view_issues @request.session[:user_id] = 3 @@ -70,13 +84,27 @@ def test_unwatch @request.session[:user_id] = 3 assert_difference('Watcher.count', -1) do - xhr :post, :unwatch, :object_type => 'issue', :object_id => '2' + xhr :delete, :unwatch, :object_type => 'issue', :object_id => '2' assert_response :success assert_include '$(".issue-2-watcher")', response.body end assert !Issue.find(1).watched_by?(User.find(3)) end + def test_unwatch_a_collection_with_multiple_objects + @request.session[:user_id] = 3 + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + Watcher.create!(:user_id => 3, :watchable => Issue.find(3)) + + assert_difference('Watcher.count', -2) do + xhr :delete, :unwatch, :object_type => 'issue', :object_id => ['1', '3'] + assert_response :success + assert_include '$(".issue-bulk-watcher")', response.body + end + assert !Issue.find(1).watched_by?(User.find(3)) + assert !Issue.find(3).watched_by?(User.find(3)) + end + def test_new @request.session[:user_id] = 2 xhr :get, :new, :object_type => 'issue', :object_id => '2' @@ -84,7 +112,7 @@ assert_match /ajax-modal/, response.body end - def test_new_for_new_record_with_id + def test_new_for_new_record_with_project_id @request.session[:user_id] = 2 xhr :get, :new, :project_id => 1 assert_response :success @@ -92,7 +120,7 @@ assert_match /ajax-modal/, response.body end - def test_new_for_new_record_with_identifier + def test_new_for_new_record_with_project_identifier @request.session[:user_id] = 2 xhr :get, :new, :project_id => 'ecookbook' assert_response :success @@ -124,7 +152,8 @@ end def test_autocomplete_on_watchable_creation - xhr :get, :autocomplete_for_user, :q => 'mi' + @request.session[:user_id] = 2 + xhr :get, :autocomplete_for_user, :q => 'mi', :project_id => 'ecookbook' assert_response :success assert_select 'input', :count => 4 assert_select 'input[name=?][value=1]', 'watcher[user_ids][]' @@ -134,7 +163,8 @@ end def test_autocomplete_on_watchable_update - xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue' + @request.session[:user_id] = 2 + xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue', :project_id => 'ecookbook' assert_response :success assert_select 'input', :count => 3 assert_select 'input[name=?][value=2]', 'watcher[user_ids][]' @@ -146,7 +176,7 @@ def test_append @request.session[:user_id] = 2 assert_no_difference 'Watcher.count' do - xhr :post, :append, :watcher => {:user_ids => ['4', '7']} + xhr :post, :append, :watcher => {:user_ids => ['4', '7']}, :project_id => 'ecookbook' assert_response :success assert_include 'watchers_inputs', response.body assert_include 'issue[watcher_user_ids][]', response.body @@ -156,7 +186,7 @@ def test_remove_watcher @request.session[:user_id] = 2 assert_difference('Watcher.count', -1) do - xhr :post, :destroy, :object_type => 'issue', :object_id => '2', :user_id => '3' + xhr :delete, :destroy, :object_type => 'issue', :object_id => '2', :user_id => '3' assert_response :success assert_match /watchers/, response.body end diff -r d98d22a98252 -r afce8026aaeb test/functional/welcome_controller_test.rb --- a/test/functional/welcome_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/welcome_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'welcome_controller' - -# Re-raise errors caught by the controller. -class WelcomeController; def rescue_action(e) raise e end; end class WelcomeControllerTest < ActionController::TestCase fixtures :projects, :news, :users, :members def setup - @controller = WelcomeController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -37,7 +30,7 @@ assert_template 'index' assert_not_nil assigns(:news) assert_not_nil assigns(:projects) - assert !assigns(:projects).include?(Project.find(:first, :conditions => {:is_public => false})) + assert !assigns(:projects).include?(Project.where(:is_public => false).first) end def test_browser_language @@ -92,6 +85,13 @@ :content => %r{warnLeavingUnsaved} end + def test_logout_link_should_post + @request.session[:user_id] = 2 + + get :index + assert_select 'a[href=/logout][data-method=post]', :text => 'Sign out' + end + def test_call_hook_mixed_in assert @controller.respond_to?(:call_hook) end @@ -106,60 +106,50 @@ end end - context "test_api_offset_and_limit" do - context "without params" do - should "return 0, 25" do - assert_equal [0, 25], @controller.api_offset_and_limit({}) - end - end + def test_api_offset_and_limit_without_params + assert_equal [0, 25], @controller.api_offset_and_limit({}) + end - context "with limit" do - should "return 0, limit" do - assert_equal [0, 30], @controller.api_offset_and_limit({:limit => 30}) - end + def test_api_offset_and_limit_with_limit + assert_equal [0, 30], @controller.api_offset_and_limit({:limit => 30}) + assert_equal [0, 100], @controller.api_offset_and_limit({:limit => 120}) + assert_equal [0, 25], @controller.api_offset_and_limit({:limit => -10}) + end - should "not exceed 100" do - assert_equal [0, 100], @controller.api_offset_and_limit({:limit => 120}) - end + def test_api_offset_and_limit_with_offset + assert_equal [10, 25], @controller.api_offset_and_limit({:offset => 10}) + assert_equal [0, 25], @controller.api_offset_and_limit({:offset => -10}) + end - should "not be negative" do - assert_equal [0, 25], @controller.api_offset_and_limit({:limit => -10}) - end - end + def test_api_offset_and_limit_with_offset_and_limit + assert_equal [10, 50], @controller.api_offset_and_limit({:offset => 10, :limit => 50}) + end - context "with offset" do - should "return offset, 25" do - assert_equal [10, 25], @controller.api_offset_and_limit({:offset => 10}) - end + def test_api_offset_and_limit_with_page + assert_equal [0, 25], @controller.api_offset_and_limit({:page => 1}) + assert_equal [50, 25], @controller.api_offset_and_limit({:page => 3}) + assert_equal [0, 25], @controller.api_offset_and_limit({:page => 0}) + assert_equal [0, 25], @controller.api_offset_and_limit({:page => -2}) + end - should "not be negative" do - assert_equal [0, 25], @controller.api_offset_and_limit({:offset => -10}) - end + def test_api_offset_and_limit_with_page_and_limit + assert_equal [0, 100], @controller.api_offset_and_limit({:page => 1, :limit => 100}) + assert_equal [200, 100], @controller.api_offset_and_limit({:page => 3, :limit => 100}) + end - context "and limit" do - should "return offset, limit" do - assert_equal [10, 50], @controller.api_offset_and_limit({:offset => 10, :limit => 50}) - end - end - end + def test_unhautorized_exception_with_anonymous_should_redirect_to_login + WelcomeController.any_instance.stubs(:index).raises(::Unauthorized) - context "with page" do - should "return offset, 25" do - assert_equal [0, 25], @controller.api_offset_and_limit({:page => 1}) - assert_equal [50, 25], @controller.api_offset_and_limit({:page => 3}) - end + get :index + assert_response 302 + assert_redirected_to('/login?back_url='+CGI.escape('http://test.host/')) + end - should "not be negative" do - assert_equal [0, 25], @controller.api_offset_and_limit({:page => 0}) - assert_equal [0, 25], @controller.api_offset_and_limit({:page => -2}) - end + def test_unhautorized_exception_with_anonymous_and_xmlhttprequest_should_respond_with_401_to_anonymous + WelcomeController.any_instance.stubs(:index).raises(::Unauthorized) - context "and limit" do - should "return offset, limit" do - assert_equal [0, 100], @controller.api_offset_and_limit({:page => 1, :limit => 100}) - assert_equal [200, 100], @controller.api_offset_and_limit({:page => 3, :limit => 100}) - end - end - end + @request.env["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" + get :index + assert_response 401 end end diff -r d98d22a98252 -r afce8026aaeb test/functional/wiki_controller_test.rb --- a/test/functional/wiki_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/wiki_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'wiki_controller' - -# Re-raise errors caught by the controller. -class WikiController; def rescue_action(e) raise e end; end class WikiControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, @@ -27,9 +23,6 @@ :wiki_content_versions, :attachments def setup - @controller = WikiController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -60,7 +53,7 @@ assert_tag :tag => 'h1', :content => /Another page/ # Included page with an inline image assert_tag :tag => 'p', :content => /This is an inline image/ - assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3', + assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3/logo.gif', :alt => 'This is a logo' } end @@ -184,6 +177,16 @@ assert_response 302 end + def test_show_page_without_content_should_display_the_edit_form + @request.session[:user_id] = 2 + WikiPage.create!(:title => 'NoContent', :wiki => Project.find(1).wiki) + + get :show, :project_id => 1, :id => 'NoContent' + assert_response :success + assert_template 'edit' + assert_select 'textarea[name=?]', 'content[text]' + end + def test_create_page @request.session[:user_id] = 2 assert_difference 'WikiPage.count' do @@ -419,6 +422,19 @@ assert_equal 2, c.version end + def test_update_page_without_content_should_create_content + @request.session[:user_id] = 2 + page = WikiPage.create!(:title => 'NoContent', :wiki => Project.find(1).wiki) + + assert_no_difference 'WikiPage.count' do + assert_difference 'WikiContent.count' do + put :update, :project_id => 1, :id => 'NoContent', :content => {:text => 'Some content'} + assert_response 302 + end + end + assert_equal 'Some content', page.reload.content.text + end + def test_update_section @request.session[:user_id] = 2 page = WikiPage.find_by_title('Page_with_sections') @@ -438,7 +454,7 @@ end end end - assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections' + assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections#section-2' assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.reload.content.text end @@ -461,7 +477,7 @@ end end end - assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections' + assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections#section-2' page.reload assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.content.text assert_equal 4, page.content.version @@ -592,7 +608,7 @@ # Line 5 assert_tag :tag => 'tr', :child => { :tag => 'th', :attributes => {:class => 'line-num'}, :content => '5', :sibling => { - :tag => 'td', :attributes => {:class => 'author'}, :content => /redMine Admin/, :sibling => { + :tag => 'td', :attributes => {:class => 'author'}, :content => /Redmine Admin/, :sibling => { :tag => 'td', :content => /Some updated \[\[documentation\]\] here/ } } diff -r d98d22a98252 -r afce8026aaeb test/functional/wikis_controller_test.rb --- a/test/functional/wikis_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/wikis_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'wikis_controller' - -# Re-raise errors caught by the controller. -class WikisController; def rescue_action(e) raise e end; end class WikisControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :wikis def setup - @controller = WikisController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r d98d22a98252 -r afce8026aaeb test/functional/workflows_controller_test.rb --- a/test/functional/workflows_controller_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/functional/workflows_controller_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'workflows_controller' - -# Re-raise errors caught by the controller. -class WorkflowsController; def rescue_action(e) raise e end; end class WorkflowsControllerTest < ActionController::TestCase fixtures :roles, :trackers, :workflows, :users, :issue_statuses def setup - @controller = WorkflowsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end @@ -37,7 +30,7 @@ assert_response :success assert_template 'index' - count = WorkflowTransition.count(:all, :conditions => 'role_id = 1 AND tracker_id = 2') + count = WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count assert_tag :tag => 'a', :content => count.to_s, :attributes => { :href => '/workflows/edit?role_id=1&tracker_id=2' } end @@ -102,9 +95,9 @@ } assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' - assert_equal 3, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) - assert_not_nil WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2}) - assert_nil WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4}) + assert_equal 3, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count + assert_not_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first + assert_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4).first end def test_post_edit_with_additional_transitions @@ -115,27 +108,27 @@ } assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' - assert_equal 4, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) + assert_equal 4, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5}) + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5).first assert ! w.author assert ! w.assignee - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1}) + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1).first assert w.author assert ! w.assignee - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2}) + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first assert ! w.author assert w.assignee - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4}) + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4).first assert w.author assert w.assignee end def test_clear_workflow - assert WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) > 0 + assert WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count > 0 - post :edit, :role_id => 2, :tracker_id => 1 - assert_equal 0, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) + post :edit, :role_id => 1, :tracker_id => 2 + assert_equal 0, WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count end def test_get_permissions @@ -207,6 +200,23 @@ end end + def test_get_permissions_should_disable_hidden_custom_fields + cf1 = IssueCustomField.generate!(:tracker_ids => [1], :visible => true) + cf2 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1]) + cf3 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1, 2]) + + get :permissions, :role_id => 2, :tracker_id => 1 + assert_response :success + assert_template 'permissions' + + assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf1.id}][1]" + assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf3.id}][1]" + + assert_select 'select[name=?][disabled=disabled]', "permissions[#{cf2.id}][1]" do + assert_select 'option[value=][selected=selected]', :text => 'Hidden' + end + end + def test_get_permissions_with_role_and_tracker_and_all_statuses WorkflowTransition.delete_all @@ -304,9 +314,32 @@ assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 3) end + def test_post_copy_with_incomplete_source_specification_should_fail + assert_no_difference 'WorkflowRule.count' do + post :copy, + :source_tracker_id => '', :source_role_id => '2', + :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] + assert_response 200 + assert_select 'div.flash.error', :text => 'Please select a source tracker or role' + end + end + + def test_post_copy_with_incomplete_target_specification_should_fail + assert_no_difference 'WorkflowRule.count' do + post :copy, + :source_tracker_id => '1', :source_role_id => '2', + :target_tracker_ids => ['2', '3'] + assert_response 200 + assert_select 'div.flash.error', :text => 'Please select target tracker(s) and role(s)' + end + end + # Returns an array of status transitions that can be compared def status_transitions(conditions) - WorkflowTransition.find(:all, :conditions => conditions, - :order => 'tracker_id, role_id, old_status_id, new_status_id').collect {|w| [w.old_status, w.new_status_id]} + WorkflowTransition. + where(conditions). + order('tracker_id, role_id, old_status_id, new_status_id'). + all. + collect {|w| [w.old_status, w.new_status_id]} end end diff -r d98d22a98252 -r afce8026aaeb test/integration/account_test.rb --- a/test/integration/account_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/account_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) begin - require 'mocha' + require 'mocha/setup' rescue # Won't run some tests end @@ -45,7 +45,7 @@ # User logs in with 'autologin' checked post '/login', :username => user.login, :password => 'admin', :autologin => 1 assert_redirected_to '/my/page' - token = Token.find :first + token = Token.first assert_not_nil token assert_equal user, token.user assert_equal 'autologin', token.action @@ -59,7 +59,7 @@ user.update_attribute :last_login_on, nil assert_nil user.reload.last_login_on - # User comes back with his autologin cookie + # User comes back with user's autologin cookie cookies[:autologin] = token.value get '/my/page' assert_response :success @@ -68,6 +68,33 @@ assert_not_nil user.reload.last_login_on end + def test_autologin_should_use_autologin_cookie_name + Token.delete_all + Redmine::Configuration.stubs(:[]).with('autologin_cookie_name').returns('custom_autologin') + Redmine::Configuration.stubs(:[]).with('autologin_cookie_path').returns('/') + Redmine::Configuration.stubs(:[]).with('autologin_cookie_secure').returns(false) + + with_settings :autologin => '7' do + assert_difference 'Token.count' do + post '/login', :username => 'admin', :password => 'admin', :autologin => 1 + end + assert_response 302 + assert cookies['custom_autologin'].present? + token = cookies['custom_autologin'] + + # Session is cleared + reset! + cookies['custom_autologin'] = token + get '/my/page' + assert_response :success + + assert_difference 'Token.count', -1 do + post '/logout' + end + assert cookies['custom_autologin'].blank? + end + end + def test_lost_password Token.delete_all @@ -79,7 +106,7 @@ post "account/lost_password", :mail => 'jSmith@somenet.foo' assert_redirected_to "/login" - token = Token.find(:first) + token = Token.first assert_equal 'recovery', token.action assert_equal 'jsmith@somenet.foo', token.user.mail assert !token.expired? @@ -91,7 +118,9 @@ assert_select 'input[name=new_password]' assert_select 'input[name=new_password_confirmation]' - post "account/lost_password", :token => token.value, :new_password => 'newpass123', :new_password_confirmation => 'newpass123' + post "account/lost_password", + :token => token.value, :new_password => 'newpass123', + :new_password_confirmation => 'newpass123' assert_redirected_to "/login" assert_equal 'Password was successfully updated.', flash[:notice] @@ -99,6 +128,35 @@ assert_equal 0, Token.count end + def test_user_with_must_change_passwd_should_be_forced_to_change_its_password + User.find_by_login('jsmith').update_attribute :must_change_passwd, true + + post '/login', :username => 'jsmith', :password => 'jsmith' + assert_redirected_to '/my/page' + follow_redirect! + assert_redirected_to '/my/password' + + get '/issues' + assert_redirected_to '/my/password' + end + + def test_user_with_must_change_passwd_should_be_able_to_change_its_password + User.find_by_login('jsmith').update_attribute :must_change_passwd, true + + post '/login', :username => 'jsmith', :password => 'jsmith' + assert_redirected_to '/my/page' + follow_redirect! + assert_redirected_to '/my/password' + follow_redirect! + assert_response :success + post '/my/password', :password => 'jsmith', :new_password => 'newpassword', :new_password_confirmation => 'newpassword' + assert_redirected_to '/my/account' + follow_redirect! + assert_response :success + + assert_equal false, User.find_by_login('jsmith').must_change_passwd? + end + def test_register_with_automatic_activation Setting.self_registration = '3' @@ -106,8 +164,10 @@ assert_response :success assert_template 'account/register' - post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", - :password => "newpass123", :password_confirmation => "newpass123"} + post 'account/register', + :user => {:login => "newuser", :language => "en", + :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", + :password => "newpass123", :password_confirmation => "newpass123"} assert_redirected_to '/my/account' follow_redirect! assert_response :success @@ -122,8 +182,10 @@ def test_register_with_manual_activation Setting.self_registration = '2' - post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", - :password => "newpass123", :password_confirmation => "newpass123"} + post 'account/register', + :user => {:login => "newuser", :language => "en", + :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", + :password => "newpass123", :password_confirmation => "newpass123"} assert_redirected_to '/login' assert !User.find_by_login('newuser').active? end @@ -132,12 +194,14 @@ Setting.self_registration = '1' Token.delete_all - post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", - :password => "newpass123", :password_confirmation => "newpass123"} + post 'account/register', + :user => {:login => "newuser", :language => "en", + :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", + :password => "newpass123", :password_confirmation => "newpass123"} assert_redirected_to '/login' assert !User.find_by_login('newuser').active? - token = Token.find(:first) + token = Token.first assert_equal 'register', token.action assert_equal 'newuser@foo.bar', token.user.mail assert !token.expired? @@ -150,7 +214,9 @@ def test_onthefly_registration # disable registration Setting.self_registration = '0' - AuthSource.expects(:authenticate).returns({:login => 'foo', :firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com', :auth_source_id => 66}) + AuthSource.expects(:authenticate).returns( + {:login => 'foo', :firstname => 'Foo', :lastname => 'Smith', + :mail => 'foo@bar.com', :auth_source_id => 66}) post '/login', :username => 'foo', :password => 'bar' assert_redirected_to '/my/page' @@ -164,7 +230,8 @@ def test_onthefly_registration_with_invalid_attributes # disable registration Setting.self_registration = '0' - AuthSource.expects(:authenticate).returns({:login => 'foo', :lastname => 'Smith', :auth_source_id => 66}) + AuthSource.expects(:authenticate).returns( + {:login => 'foo', :lastname => 'Smith', :auth_source_id => 66}) post '/login', :username => 'foo', :password => 'bar' assert_response :success @@ -174,7 +241,8 @@ assert_no_tag :input, :attributes => { :name => 'user[login]' } assert_no_tag :input, :attributes => { :name => 'user[password]' } - post 'account/register', :user => {:firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com'} + post 'account/register', + :user => {:firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com'} assert_redirected_to '/my/account' user = User.find_by_login('foo') @@ -182,4 +250,49 @@ assert_equal 66, user.auth_source_id assert user.hashed_password.blank? end + + def test_registered_user_should_be_able_to_get_a_new_activation_email + Token.delete_all + + with_settings :self_registration => '1', :default_language => 'en' do + # register a new account + assert_difference 'User.count' do + assert_difference 'Token.count' do + post 'account/register', + :user => {:login => "newuser", :language => "en", + :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", + :password => "newpass123", :password_confirmation => "newpass123"} + end + end + user = User.order('id desc').first + assert_equal User::STATUS_REGISTERED, user.status + reset! + + # try to use "lost password" + assert_no_difference 'ActionMailer::Base.deliveries.size' do + post '/account/lost_password', :mail => 'newuser@foo.bar' + end + assert_redirected_to '/account/lost_password' + follow_redirect! + assert_response :success + assert_select 'div.flash', :text => /new activation email/ + assert_select 'div.flash a[href=/account/activation_email]' + + # request a new action activation email + assert_difference 'ActionMailer::Base.deliveries.size' do + get '/account/activation_email' + end + assert_redirected_to '/login' + token = Token.order('id desc').first + activation_path = "/account/activate?token=#{token.value}" + assert_include activation_path, mail_body(ActionMailer::Base.deliveries.last) + + # activate the account + get activation_path + assert_redirected_to '/login' + + post '/login', :username => 'newuser', :password => 'newpass123' + assert_redirected_to '/my/page' + end + end end diff -r d98d22a98252 -r afce8026aaeb test/integration/admin_test.rb --- a/test/integration/admin_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/admin_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,8 +24,7 @@ :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def test_add_user log_user("admin", "admin") diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/api_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/integration/api_test/api_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,41 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::ApiTest < Redmine::ApiTest::Base + fixtures :users + + def setup + Setting.rest_api_enabled = '1' + end + + def test_api_should_work_with_protect_from_forgery + ActionController::Base.allow_forgery_protection = true + assert_difference('User.count') do + post '/users.xml', { + :user => { + :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', + :mail => 'foo@example.net', :password => 'secret123'} + }, + credentials('admin') + assert_response 201 + end + ensure + ActionController::Base.allow_forgery_protection = false + end +end \ No newline at end of file diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/attachments_test.rb --- a/test/integration/api_test/attachments_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/attachments_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::AttachmentsTest < ActionController::IntegrationTest +class Redmine::ApiTest::AttachmentsTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :attachments def setup diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/authentication_test.rb --- a/test/integration/api_test/authentication_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/authentication_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::AuthenticationTest < ActionController::IntegrationTest +class Redmine::ApiTest::AuthenticationTest < Redmine::ApiTest::Base fixtures :users def setup diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/custom_fields_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/integration/api_test/custom_fields_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,43 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::CustomFieldsTest < Redmine::ApiTest::Base + fixtures :users, :custom_fields + + def setup + Setting.rest_api_enabled = '1' + end + + test "GET /custom_fields.xml should return custom fields" do + get '/custom_fields.xml', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'custom_fields' do + assert_select 'custom_field' do + assert_select 'name', :text => 'Database' + assert_select 'id', :text => '2' + assert_select 'customized_type', :text => 'issue' + assert_select 'possible_values[type=array]' do + assert_select 'possible_value>value', :text => 'PostgreSQL' + end + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/disabled_rest_api_test.rb --- a/test/integration/api_test/disabled_rest_api_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/disabled_rest_api_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,14 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::DisabledRestApiTest < ActionController::IntegrationTest +class Redmine::ApiTest::DisabledRestApiTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def setup Setting.rest_api_enabled = '0' diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/enumerations_test.rb --- a/test/integration/api_test/enumerations_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/enumerations_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,27 +17,21 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::EnumerationsTest < ActionController::IntegrationTest +class Redmine::ApiTest::EnumerationsTest < Redmine::ApiTest::Base fixtures :enumerations def setup Setting.rest_api_enabled = '1' end - context "/enumerations/issue_priorities" do - context "GET" do - - should "return priorities" do - get '/enumerations/issue_priorities.xml' - - assert_response :success - assert_equal 'application/xml', response.content_type - assert_select 'issue_priorities[type=array]' do - assert_select 'issue_priority' do - assert_select 'id', :text => '6' - assert_select 'name', :text => 'High' - end - end + test "GET /enumerations/issue_priorities.xml should return priorities" do + get '/enumerations/issue_priorities.xml' + assert_response :success + assert_equal 'application/xml', response.content_type + assert_select 'issue_priorities[type=array]' do + assert_select 'issue_priority' do + assert_select 'id', :text => '6' + assert_select 'name', :text => 'High' end end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/groups_test.rb --- a/test/integration/api_test/groups_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/groups_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,196 +17,154 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::GroupsTest < ActionController::IntegrationTest +class Redmine::ApiTest::GroupsTest < Redmine::ApiTest::Base fixtures :users, :groups_users def setup Setting.rest_api_enabled = '1' end - context "GET /groups" do - context ".xml" do - should "require authentication" do - get '/groups.xml' - assert_response 401 - end + test "GET /groups.xml should require authentication" do + get '/groups.xml' + assert_response 401 + end - should "return groups" do - get '/groups.xml', {}, credentials('admin') - assert_response :success - assert_equal 'application/xml', response.content_type + test "GET /groups.xml should return groups" do + get '/groups.xml', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type - assert_select 'groups' do - assert_select 'group' do - assert_select 'name', :text => 'A Team' - assert_select 'id', :text => '10' - end - end - end - end - - context ".json" do - should "require authentication" do - get '/groups.json' - assert_response 401 - end - - should "return groups" do - get '/groups.json', {}, credentials('admin') - assert_response :success - assert_equal 'application/json', response.content_type - - json = MultiJson.load(response.body) - groups = json['groups'] - assert_kind_of Array, groups - group = groups.detect {|g| g['name'] == 'A Team'} - assert_not_nil group - assert_equal({'id' => 10, 'name' => 'A Team'}, group) + assert_select 'groups' do + assert_select 'group' do + assert_select 'name', :text => 'A Team' + assert_select 'id', :text => '10' end end end - context "GET /groups/:id" do - context ".xml" do - should "return the group with its users" do - get '/groups/10.xml', {}, credentials('admin') - assert_response :success - assert_equal 'application/xml', response.content_type + test "GET /groups.json should require authentication" do + get '/groups.json' + assert_response 401 + end - assert_select 'group' do - assert_select 'name', :text => 'A Team' - assert_select 'id', :text => '10' - end - end + test "GET /groups.json should return groups" do + get '/groups.json', {}, credentials('admin') + assert_response :success + assert_equal 'application/json', response.content_type - should "include users if requested" do - get '/groups/10.xml?include=users', {}, credentials('admin') - assert_response :success - assert_equal 'application/xml', response.content_type + json = MultiJson.load(response.body) + groups = json['groups'] + assert_kind_of Array, groups + group = groups.detect {|g| g['name'] == 'A Team'} + assert_not_nil group + assert_equal({'id' => 10, 'name' => 'A Team'}, group) + end - assert_select 'group' do - assert_select 'users' do - assert_select 'user', Group.find(10).users.count - assert_select 'user[id=8]' - end - end - end + test "GET /groups/:id.xml should return the group with its users" do + get '/groups/10.xml', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type - should "include memberships if requested" do - get '/groups/10.xml?include=memberships', {}, credentials('admin') - assert_response :success - assert_equal 'application/xml', response.content_type + assert_select 'group' do + assert_select 'name', :text => 'A Team' + assert_select 'id', :text => '10' + end + end - assert_select 'group' do - assert_select 'memberships' - end + test "GET /groups/:id.xml should include users if requested" do + get '/groups/10.xml?include=users', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'group' do + assert_select 'users' do + assert_select 'user', Group.find(10).users.count + assert_select 'user[id=8]' end end end - context "POST /groups" do - context "with valid parameters" do - context ".xml" do - should "create groups" do - assert_difference('Group.count') do - post '/groups.xml', {:group => {:name => 'Test', :user_ids => [2, 3]}}, credentials('admin') - assert_response :created - assert_equal 'application/xml', response.content_type - end - - group = Group.order('id DESC').first - assert_equal 'Test', group.name - assert_equal [2, 3], group.users.map(&:id).sort + test "GET /groups/:id.xml include memberships if requested" do + get '/groups/10.xml?include=memberships', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type - assert_select 'group' do - assert_select 'name', :text => 'Test' - end - end - end - end - - context "with invalid parameters" do - context ".xml" do - should "return errors" do - assert_no_difference('Group.count') do - post '/groups.xml', {:group => {:name => ''}}, credentials('admin') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', response.content_type - - assert_select 'errors' do - assert_select 'error', :text => /Name can't be blank/ - end - end - end + assert_select 'group' do + assert_select 'memberships' end end - context "PUT /groups/:id" do - context "with valid parameters" do - context ".xml" do - should "update the group" do - put '/groups/10.xml', {:group => {:name => 'New name', :user_ids => [2, 3]}}, credentials('admin') - assert_response :ok - assert_equal '', @response.body - - group = Group.find(10) - assert_equal 'New name', group.name - assert_equal [2, 3], group.users.map(&:id).sort - end - end + test "POST /groups.xml with valid parameters should create the group" do + assert_difference('Group.count') do + post '/groups.xml', {:group => {:name => 'Test', :user_ids => [2, 3]}}, credentials('admin') + assert_response :created + assert_equal 'application/xml', response.content_type end - context "with invalid parameters" do - context ".xml" do - should "return errors" do - put '/groups/10.xml', {:group => {:name => ''}}, credentials('admin') - assert_response :unprocessable_entity - assert_equal 'application/xml', response.content_type + group = Group.order('id DESC').first + assert_equal 'Test', group.name + assert_equal [2, 3], group.users.map(&:id).sort - assert_select 'errors' do - assert_select 'error', :text => /Name can't be blank/ - end - end - end + assert_select 'group' do + assert_select 'name', :text => 'Test' end end - context "DELETE /groups/:id" do - context ".xml" do - should "delete the group" do - assert_difference 'Group.count', -1 do - delete '/groups/10.xml', {}, credentials('admin') - assert_response :ok - assert_equal '', @response.body - end - end + test "POST /groups.xml with invalid parameters should return errors" do + assert_no_difference('Group.count') do + post '/groups.xml', {:group => {:name => ''}}, credentials('admin') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', response.content_type + + assert_select 'errors' do + assert_select 'error', :text => /Name can't be blank/ end end - context "POST /groups/:id/users" do - context ".xml" do - should "add user to the group" do - assert_difference 'Group.find(10).users.count' do - post '/groups/10/users.xml', {:user_id => 5}, credentials('admin') - assert_response :ok - assert_equal '', @response.body - end - assert_include User.find(5), Group.find(10).users - end + test "PUT /groups/:id.xml with valid parameters should update the group" do + put '/groups/10.xml', {:group => {:name => 'New name', :user_ids => [2, 3]}}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + + group = Group.find(10) + assert_equal 'New name', group.name + assert_equal [2, 3], group.users.map(&:id).sort + end + + test "PUT /groups/:id.xml with invalid parameters should return errors" do + put '/groups/10.xml', {:group => {:name => ''}}, credentials('admin') + assert_response :unprocessable_entity + assert_equal 'application/xml', response.content_type + + assert_select 'errors' do + assert_select 'error', :text => /Name can't be blank/ end end - context "DELETE /groups/:id/users/:user_id" do - context ".xml" do - should "remove user from the group" do - assert_difference 'Group.find(10).users.count', -1 do - delete '/groups/10/users/8.xml', {}, credentials('admin') - assert_response :ok - assert_equal '', @response.body - end - assert_not_include User.find(8), Group.find(10).users - end + test "DELETE /groups/:id.xml should delete the group" do + assert_difference 'Group.count', -1 do + delete '/groups/10.xml', {}, credentials('admin') + assert_response :ok + assert_equal '', @response.body end end + + test "POST /groups/:id/users.xml should add user to the group" do + assert_difference 'Group.find(10).users.count' do + post '/groups/10/users.xml', {:user_id => 5}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + end + assert_include User.find(5), Group.find(10).users + end + + test "DELETE /groups/:id/users/:user_id.xml should remove user from the group" do + assert_difference 'Group.find(10).users.count', -1 do + delete '/groups/10/users/8.xml', {}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + end + assert_not_include User.find(8), Group.find(10).users + end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/http_basic_login_test.rb --- a/test/integration/api_test/http_basic_login_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/http_basic_login_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,14 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::HttpBasicLoginTest < ActionController::IntegrationTest +class Redmine::ApiTest::HttpBasicLoginTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def setup Setting.rest_api_enabled = '1' diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/http_basic_login_with_api_token_test.rb --- a/test/integration/api_test/http_basic_login_with_api_token_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/http_basic_login_with_api_token_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,14 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::HttpBasicLoginWithApiTokenTest < ActionController::IntegrationTest +class Redmine::ApiTest::HttpBasicLoginWithApiTokenTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def setup Setting.rest_api_enabled = '1' diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/issue_categories_test.rb --- a/test/integration/api_test/issue_categories_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/issue_categories_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::IssueCategoriesTest < ActionController::IntegrationTest +class Redmine::ApiTest::IssueCategoriesTest < Redmine::ApiTest::Base fixtures :projects, :users, :issue_categories, :issues, :roles, :member_roles, @@ -28,99 +28,83 @@ Setting.rest_api_enabled = '1' end - context "GET /projects/:project_id/issue_categories.xml" do - should "return issue categories" do - get '/projects/1/issue_categories.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'issue_categories', - :child => {:tag => 'issue_category', :child => {:tag => 'id', :content => '2'}} - end + test "GET /projects/:project_id/issue_categories.xml should return the issue categories" do + get '/projects/1/issue_categories.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'issue_categories', + :child => {:tag => 'issue_category', :child => {:tag => 'id', :content => '2'}} end - context "GET /issue_categories/2.xml" do - should "return requested issue category" do - get '/issue_categories/2.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'issue_category', - :child => {:tag => 'id', :content => '2'} - end + test "GET /issue_categories/:id.xml should return the issue category" do + get '/issue_categories/2.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'issue_category', + :child => {:tag => 'id', :content => '2'} end - context "POST /projects/:project_id/issue_categories.xml" do - should "return create issue category" do - assert_difference 'IssueCategory.count' do - post '/projects/1/issue_categories.xml', {:issue_category => {:name => 'API'}}, credentials('jsmith') - end - assert_response :created - assert_equal 'application/xml', @response.content_type + test "POST /projects/:project_id/issue_categories.xml should return create issue category" do + assert_difference 'IssueCategory.count' do + post '/projects/1/issue_categories.xml', {:issue_category => {:name => 'API'}}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type - category = IssueCategory.first(:order => 'id DESC') - assert_equal 'API', category.name - assert_equal 1, category.project_id + category = IssueCategory.first(:order => 'id DESC') + assert_equal 'API', category.name + assert_equal 1, category.project_id + end + + test "POST /projects/:project_id/issue_categories.xml with invalid parameters should return errors" do + assert_no_difference 'IssueCategory.count' do + post '/projects/1/issue_categories.xml', {:issue_category => {:name => ''}}, credentials('jsmith') end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type - context "with invalid parameters" do - should "return errors" do - assert_no_difference 'IssueCategory.count' do - post '/projects/1/issue_categories.xml', {:issue_category => {:name => ''}}, credentials('jsmith') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} + end - assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} + test "PUT /issue_categories/:id.xml with valid parameters should update the issue category" do + assert_no_difference 'IssueCategory.count' do + put '/issue_categories/2.xml', {:issue_category => {:name => 'API Update'}}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_equal 'API Update', IssueCategory.find(2).name + end + + test "PUT /issue_categories/:id.xml with invalid parameters should return errors" do + assert_no_difference 'IssueCategory.count' do + put '/issue_categories/2.xml', {:issue_category => {:name => ''}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} + end + + test "DELETE /issue_categories/:id.xml should destroy the issue category" do + assert_difference 'IssueCategory.count', -1 do + delete '/issue_categories/1.xml', {}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_nil IssueCategory.find_by_id(1) + end + + test "DELETE /issue_categories/:id.xml should reassign issues with :reassign_to_id param" do + issue_count = Issue.where(:category_id => 1).count + assert issue_count > 0 + + assert_difference 'IssueCategory.count', -1 do + assert_difference 'Issue.where(:category_id => 2).count', 3 do + delete '/issue_categories/1.xml', {:reassign_to_id => 2}, credentials('jsmith') end end - end - - context "PUT /issue_categories/2.xml" do - context "with valid parameters" do - should "update issue category" do - assert_no_difference 'IssueCategory.count' do - put '/issue_categories/2.xml', {:issue_category => {:name => 'API Update'}}, credentials('jsmith') - end - assert_response :ok - assert_equal '', @response.body - assert_equal 'API Update', IssueCategory.find(2).name - end - end - - context "with invalid parameters" do - should "return errors" do - assert_no_difference 'IssueCategory.count' do - put '/issue_categories/2.xml', {:issue_category => {:name => ''}}, credentials('jsmith') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - - assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} - end - end - end - - context "DELETE /issue_categories/1.xml" do - should "destroy issue categories" do - assert_difference 'IssueCategory.count', -1 do - delete '/issue_categories/1.xml', {}, credentials('jsmith') - end - assert_response :ok - assert_equal '', @response.body - assert_nil IssueCategory.find_by_id(1) - end - - should "reassign issues with :reassign_to_id param" do - issue_count = Issue.count(:conditions => {:category_id => 1}) - assert issue_count > 0 - - assert_difference 'IssueCategory.count', -1 do - assert_difference 'Issue.count(:conditions => {:category_id => 2})', 3 do - delete '/issue_categories/1.xml', {:reassign_to_id => 2}, credentials('jsmith') - end - end - assert_response :ok - assert_equal '', @response.body - assert_nil IssueCategory.find_by_id(1) - end + assert_response :ok + assert_equal '', @response.body + assert_nil IssueCategory.find_by_id(1) end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/issue_relations_test.rb --- a/test/integration/api_test/issue_relations_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/issue_relations_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::IssueRelationsTest < ActionController::IntegrationTest +class Redmine::ApiTest::IssueRelationsTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,83 +25,68 @@ :member_roles, :members, :enabled_modules, - :workflows, :issue_relations def setup Setting.rest_api_enabled = '1' end - context "/issues/:issue_id/relations" do - context "GET" do - should "return issue relations" do - get '/issues/9/relations.xml', {}, credentials('jsmith') + test "GET /issues/:issue_id/relations.xml should return issue relations" do + get '/issues/9/relations.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type + assert_response :success + assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'relations', - :attributes => { :type => 'array' }, - :child => { - :tag => 'relation', - :child => { - :tag => 'id', - :content => '1' - } - } - end + assert_tag :tag => 'relations', + :attributes => { :type => 'array' }, + :child => { + :tag => 'relation', + :child => { + :tag => 'id', + :content => '1' + } + } + end + + test "POST /issues/:issue_id/relations.xml should create the relation" do + assert_difference('IssueRelation.count') do + post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'relates'}}, credentials('jsmith') end - context "POST" do - should "create a relation" do - assert_difference('IssueRelation.count') do - post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'relates'}}, credentials('jsmith') - end + relation = IssueRelation.first(:order => 'id DESC') + assert_equal 2, relation.issue_from_id + assert_equal 7, relation.issue_to_id + assert_equal 'relates', relation.relation_type - relation = IssueRelation.first(:order => 'id DESC') - assert_equal 2, relation.issue_from_id - assert_equal 7, relation.issue_to_id - assert_equal 'relates', relation.relation_type - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'relation', :child => {:tag => 'id', :content => relation.id.to_s} - end - - context "with failure" do - should "return the errors" do - assert_no_difference('IssueRelation.count') do - post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'foo'}}, credentials('jsmith') - end - - assert_response :unprocessable_entity - assert_tag :errors, :child => {:tag => 'error', :content => /relation_type is not included in the list/} - end - end - end + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'relation', :child => {:tag => 'id', :content => relation.id.to_s} end - context "/relations/:id" do - context "GET" do - should "return the relation" do - get '/relations/2.xml', {}, credentials('jsmith') - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag 'relation', :child => {:tag => 'id', :content => '2'} - end + test "POST /issues/:issue_id/relations.xml with failure should return errors" do + assert_no_difference('IssueRelation.count') do + post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'foo'}}, credentials('jsmith') end - context "DELETE" do - should "delete the relation" do - assert_difference('IssueRelation.count', -1) do - delete '/relations/2.xml', {}, credentials('jsmith') - end + assert_response :unprocessable_entity + assert_tag :errors, :child => {:tag => 'error', :content => /relation_type is not included in the list/} + end - assert_response :ok - assert_equal '', @response.body - assert_nil IssueRelation.find_by_id(2) - end + test "GET /relations/:id.xml should return the relation" do + get '/relations/2.xml', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag 'relation', :child => {:tag => 'id', :content => '2'} + end + + test "DELETE /relations/:id.xml should delete the relation" do + assert_difference('IssueRelation.count', -1) do + delete '/relations/2.xml', {}, credentials('jsmith') end + + assert_response :ok + assert_equal '', @response.body + assert_nil IssueRelation.find_by_id(2) end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/issue_statuses_test.rb --- a/test/integration/api_test/issue_statuses_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/issue_statuses_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,35 +17,30 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::IssueStatusesTest < ActionController::IntegrationTest +class Redmine::ApiTest::IssueStatusesTest < Redmine::ApiTest::Base fixtures :issue_statuses def setup Setting.rest_api_enabled = '1' end - context "/issue_statuses" do - context "GET" do + test "GET /issue_statuses.xml should return issue statuses" do + get '/issue_statuses.xml' - should "return issue statuses" do - get '/issue_statuses.xml' - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'issue_statuses', - :attributes => {:type => 'array'}, - :child => { - :tag => 'issue_status', - :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'name', - :content => 'Assigned' - } - } + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'issue_statuses', + :attributes => {:type => 'array'}, + :child => { + :tag => 'issue_status', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => 'Assigned' } - end - end + } + } end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/issues_test.rb --- a/test/integration/api_test/issues_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/issues_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::IssuesTest < ActionController::IntegrationTest +class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base fixtures :projects, :users, :roles, @@ -138,9 +138,9 @@ get '/issues.xml', {:set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, :v => {:cf_1 => ['MySQL']}} - expected_ids = Issue.visible.all( - :include => :custom_values, - :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id) + expected_ids = Issue.visible. + joins(:custom_values). + where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id) assert_select 'issues > issue > id', :count => expected_ids.count do |ids| ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } end @@ -151,9 +151,9 @@ should "show only issues with the custom field value" do get '/issues.xml', { :cf_1 => 'MySQL' } - expected_ids = Issue.visible.all( - :include => :custom_values, - :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id) + expected_ids = Issue.visible. + joins(:custom_values). + where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id) assert_select 'issues > issue > id', :count => expected_ids.count do |ids| ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } @@ -170,7 +170,7 @@ should "show only issues with the status_id" do get '/issues.xml?status_id=5' - expected_ids = Issue.visible.all(:conditions => {:status_id => 5}).map(&:id) + expected_ids = Issue.visible.where(:status_id => 5).map(&:id) assert_select 'issues > issue > id', :count => expected_ids.count do |ids| ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } @@ -453,6 +453,21 @@ end end + test "GET /issues/:id.xml?include=watchers should include watchers" do + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + + get '/issues/1.xml?include=watchers', {}, credentials('jsmith') + + assert_response :ok + assert_equal 'application/xml', response.content_type + assert_select 'issue' do + assert_select 'watchers', Issue.find(1).watchers.count + assert_select 'watchers' do + assert_select 'user[id=3]' + end + end + end + context "POST /issues.xml" do should_allow_api_authentication( :post, @@ -478,6 +493,18 @@ end end + test "POST /issues.xml with watcher_user_ids should create issue with watchers" do + assert_difference('Issue.count') do + post '/issues.xml', + {:issue => {:project_id => 1, :subject => 'Watchers', + :tracker_id => 2, :status_id => 3, :watcher_user_ids => [3, 1]}}, credentials('jsmith') + assert_response :created + end + issue = Issue.order('id desc').first + assert_equal 2, issue.watchers.size + assert_equal [1, 3], issue.watcher_user_ids.sort + end + context "POST /issues.xml with failure" do should "have an errors tag" do assert_no_difference('Issue.count') do @@ -720,6 +747,30 @@ end end + test "POST /issues/:id/watchers.xml should add watcher" do + assert_difference 'Watcher.count' do + post '/issues/1/watchers.xml', {:user_id => 3}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + watcher = Watcher.order('id desc').first + assert_equal Issue.find(1), watcher.watchable + assert_equal User.find(3), watcher.user + end + + test "DELETE /issues/:id/watchers/:user_id.xml should remove watcher" do + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + + assert_difference 'Watcher.count', -1 do + delete '/issues/1/watchers/3.xml', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + assert_equal false, Issue.find(1).watched_by?(User.find(3)) + end + def test_create_issue_with_uploaded_file set_tmp_attachments_directory # upload the file diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/jsonp_test.rb --- a/test/integration/api_test/jsonp_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/jsonp_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,11 +17,23 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::JsonpTest < ActionController::IntegrationTest +class Redmine::ApiTest::JsonpTest < Redmine::ApiTest::Base fixtures :trackers + def test_should_ignore_jsonp_callback_with_jsonp_disabled + with_settings :jsonp_enabled => '0' do + get '/trackers.json?jsonp=handler' + end + + assert_response :success + assert_match %r{^\{"trackers":.+\}$}, response.body + assert_equal 'application/json; charset=utf-8', response.headers['Content-Type'] + end + def test_jsonp_should_accept_callback_param - get '/trackers.json?callback=handler' + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=handler' + end assert_response :success assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body @@ -29,7 +41,9 @@ end def test_jsonp_should_accept_jsonp_param - get '/trackers.json?jsonp=handler' + with_settings :jsonp_enabled => '1' do + get '/trackers.json?jsonp=handler' + end assert_response :success assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body @@ -37,7 +51,9 @@ end def test_jsonp_should_strip_invalid_characters_from_callback - get '/trackers.json?callback=+-aA$1_' + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=+-aA$1_' + end assert_response :success assert_match %r{^aA1_\(\{"trackers":.+\}\)$}, response.body @@ -45,7 +61,9 @@ end def test_jsonp_without_callback_should_return_json - get '/trackers.json?callback=' + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=' + end assert_response :success assert_match %r{^\{"trackers":.+\}$}, response.body diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/memberships_test.rb --- a/test/integration/api_test/memberships_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/memberships_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,184 +17,156 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::MembershipsTest < ActionController::IntegrationTest +class Redmine::ApiTest::MembershipsTest < Redmine::ApiTest::Base fixtures :projects, :users, :roles, :members, :member_roles def setup Setting.rest_api_enabled = '1' end - context "/projects/:project_id/memberships" do - context "GET" do - context "xml" do - should "return memberships" do - get '/projects/1/memberships.xml', {}, credentials('jsmith') + test "GET /projects/:project_id/memberships.xml should return memberships" do + get '/projects/1/memberships.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'memberships', - :attributes => {:type => 'array'}, - :child => { - :tag => 'membership', + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'memberships', + :attributes => {:type => 'array'}, + :child => { + :tag => 'membership', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'user', + :attributes => {:id => '3', :name => 'Dave Lopper'}, + :sibling => { + :tag => 'roles', :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'user', - :attributes => {:id => '3', :name => 'Dave Lopper'}, - :sibling => { - :tag => 'roles', - :child => { - :tag => 'role', - :attributes => {:id => '2', :name => 'Developer'} - } - } - } + :tag => 'role', + :attributes => {:id => '2', :name => 'Developer'} } } - end - end + } + } + } + end - context "json" do - should "return memberships" do - get '/projects/1/memberships.json', {}, credentials('jsmith') + test "GET /projects/:project_id/memberships.json should return memberships" do + get '/projects/1/memberships.json', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_equal({ - "memberships" => - [{"id"=>1, - "project" => {"name"=>"eCookbook", "id"=>1}, - "roles" => [{"name"=>"Manager", "id"=>1}], - "user" => {"name"=>"John Smith", "id"=>2}}, - {"id"=>2, - "project" => {"name"=>"eCookbook", "id"=>1}, - "roles" => [{"name"=>"Developer", "id"=>2}], - "user" => {"name"=>"Dave Lopper", "id"=>3}}], - "limit" => 25, - "total_count" => 2, - "offset" => 0}, - json) - end - end - end + assert_response :success + assert_equal 'application/json', @response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_equal({ + "memberships" => + [{"id"=>1, + "project" => {"name"=>"eCookbook", "id"=>1}, + "roles" => [{"name"=>"Manager", "id"=>1}], + "user" => {"name"=>"John Smith", "id"=>2}}, + {"id"=>2, + "project" => {"name"=>"eCookbook", "id"=>1}, + "roles" => [{"name"=>"Developer", "id"=>2}], + "user" => {"name"=>"Dave Lopper", "id"=>3}}], + "limit" => 25, + "total_count" => 2, + "offset" => 0}, + json) + end - context "POST" do - context "xml" do - should "create membership" do - assert_difference 'Member.count' do - post '/projects/1/memberships.xml', {:membership => {:user_id => 7, :role_ids => [2,3]}}, credentials('jsmith') + test "POST /projects/:project_id/memberships.xml should create the membership" do + assert_difference 'Member.count' do + post '/projects/1/memberships.xml', {:membership => {:user_id => 7, :role_ids => [2,3]}}, credentials('jsmith') - assert_response :created - end - end - - should "return errors on failure" do - assert_no_difference 'Member.count' do - post '/projects/1/memberships.xml', {:membership => {:role_ids => [2,3]}}, credentials('jsmith') - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Principal can't be blank"} - end - end - end + assert_response :created end end - context "/memberships/:id" do - context "GET" do - context "xml" do - should "return the membership" do - get '/memberships/2.xml', {}, credentials('jsmith') + test "POST /projects/:project_id/memberships.xml with invalid parameters should return errors" do + assert_no_difference 'Member.count' do + post '/projects/1/memberships.xml', {:membership => {:role_ids => [2,3]}}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'membership', + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => "Principal can't be blank"} + end + end + + test "GET /memberships/:id.xml should return the membership" do + get '/memberships/2.xml', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'membership', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'user', + :attributes => {:id => '3', :name => 'Dave Lopper'}, + :sibling => { + :tag => 'roles', :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'user', - :attributes => {:id => '3', :name => 'Dave Lopper'}, - :sibling => { - :tag => 'roles', - :child => { - :tag => 'role', - :attributes => {:id => '2', :name => 'Developer'} - } - } - } + :tag => 'role', + :attributes => {:id => '2', :name => 'Developer'} } - end - end + } + } + } + end - context "json" do - should "return the membership" do - get '/memberships/2.json', {}, credentials('jsmith') + test "GET /memberships/:id.json should return the membership" do + get '/memberships/2.json', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_equal( - {"membership" => { - "id" => 2, - "project" => {"name"=>"eCookbook", "id"=>1}, - "roles" => [{"name"=>"Developer", "id"=>2}], - "user" => {"name"=>"Dave Lopper", "id"=>3}} - }, - json) - end - end + assert_response :success + assert_equal 'application/json', @response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_equal( + {"membership" => { + "id" => 2, + "project" => {"name"=>"eCookbook", "id"=>1}, + "roles" => [{"name"=>"Developer", "id"=>2}], + "user" => {"name"=>"Dave Lopper", "id"=>3}} + }, + json) + end + + test "PUT /memberships/:id.xml should update the membership" do + assert_not_equal [1,2], Member.find(2).role_ids.sort + assert_no_difference 'Member.count' do + put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [1,2]}}, credentials('jsmith') + + assert_response :ok + assert_equal '', @response.body end + member = Member.find(2) + assert_equal [1,2], member.role_ids.sort + end - context "PUT" do - context "xml" do - should "update membership" do - assert_not_equal [1,2], Member.find(2).role_ids.sort - assert_no_difference 'Member.count' do - put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [1,2]}}, credentials('jsmith') + test "PUT /memberships/:id.xml with invalid parameters should return errors" do + put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [99]}}, credentials('jsmith') - assert_response :ok - assert_equal '', @response.body - end - member = Member.find(2) - assert_equal [1,2], member.role_ids.sort - end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => /member_roles is invalid/} + end - should "return errors on failure" do - put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [99]}}, credentials('jsmith') + test "DELETE /memberships/:id.xml should destroy the membership" do + assert_difference 'Member.count', -1 do + delete '/memberships/2.xml', {}, credentials('jsmith') - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => /member_roles is invalid/} - end - end + assert_response :ok + assert_equal '', @response.body end + assert_nil Member.find_by_id(2) + end - context "DELETE" do - context "xml" do - should "destroy membership" do - assert_difference 'Member.count', -1 do - delete '/memberships/2.xml', {}, credentials('jsmith') + test "DELETE /memberships/:id.xml should respond with 422 on failure" do + assert_no_difference 'Member.count' do + # A membership with an inherited role can't be deleted + Member.find(2).member_roles.first.update_attribute :inherited_from, 99 + delete '/memberships/2.xml', {}, credentials('jsmith') - assert_response :ok - assert_equal '', @response.body - end - assert_nil Member.find_by_id(2) - end - - should "respond with 422 on failure" do - assert_no_difference 'Member.count' do - # A membership with an inherited role can't be deleted - Member.find(2).member_roles.first.update_attribute :inherited_from, 99 - delete '/memberships/2.xml', {}, credentials('jsmith') - - assert_response :unprocessable_entity - end - end - end + assert_response :unprocessable_entity end end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/news_test.rb --- a/test/integration/api_test/news_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/news_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,8 +16,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../../test_helper', __FILE__) -require 'pp' -class ApiTest::NewsTest < ActionController::IntegrationTest + +class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,74 +25,60 @@ :member_roles, :members, :enabled_modules, - :workflows, :news def setup Setting.rest_api_enabled = '1' end - context "GET /news" do - context ".xml" do - should "return news" do - get '/news.xml' + should_allow_api_authentication(:get, "/projects/onlinestore/news.xml") + should_allow_api_authentication(:get, "/projects/onlinestore/news.json") - assert_tag :tag => 'news', - :attributes => {:type => 'array'}, - :child => { - :tag => 'news', - :child => { - :tag => 'id', - :content => '2' - } - } - end - end + test "GET /news.xml should return news" do + get '/news.xml' - context ".json" do - should "return news" do - get '/news.json' - - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Array, json['news'] - assert_kind_of Hash, json['news'].first - assert_equal 2, json['news'].first['id'] - end - end + assert_tag :tag => 'news', + :attributes => {:type => 'array'}, + :child => { + :tag => 'news', + :child => { + :tag => 'id', + :content => '2' + } + } end - context "GET /projects/:project_id/news" do - context ".xml" do - should_allow_api_authentication(:get, "/projects/onlinestore/news.xml") + test "GET /news.json should return news" do + get '/news.json' - should "return news" do - get '/projects/ecookbook/news.xml' + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Array, json['news'] + assert_kind_of Hash, json['news'].first + assert_equal 2, json['news'].first['id'] + end - assert_tag :tag => 'news', - :attributes => {:type => 'array'}, - :child => { - :tag => 'news', - :child => { - :tag => 'id', - :content => '2' - } - } - end - end + test "GET /projects/:project_id/news.xml should return news" do + get '/projects/ecookbook/news.xml' - context ".json" do - should_allow_api_authentication(:get, "/projects/onlinestore/news.json") + assert_tag :tag => 'news', + :attributes => {:type => 'array'}, + :child => { + :tag => 'news', + :child => { + :tag => 'id', + :content => '2' + } + } + end - should "return news" do - get '/projects/ecookbook/news.json' + test "GET /projects/:project_id/news.json should return news" do + get '/projects/ecookbook/news.json' - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Array, json['news'] - assert_kind_of Hash, json['news'].first - assert_equal 2, json['news'].first['id'] - end - end + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Array, json['news'] + assert_kind_of Hash, json['news'].first + assert_equal 2, json['news'].first['id'] end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/projects_test.rb --- a/test/integration/api_test/projects_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/projects_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::ProjectsTest < ActionController::IntegrationTest +class Redmine::ApiTest::ProjectsTest < Redmine::ApiTest::Base fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :attachments, :custom_fields, :custom_values, :time_entries, :issue_categories @@ -27,271 +27,209 @@ set_tmp_attachments_directory end - context "GET /projects" do - context ".xml" do - should "return projects" do - get '/projects.xml' - assert_response :success - assert_equal 'application/xml', @response.content_type + # TODO: A private project is needed because should_allow_api_authentication + # actually tests that authentication is *required*, not just allowed + should_allow_api_authentication(:get, "/projects/2.xml") + should_allow_api_authentication(:get, "/projects/2.json") + should_allow_api_authentication(:post, + '/projects.xml', + {:project => {:name => 'API test', :identifier => 'api-test'}}, + {:success_code => :created}) + should_allow_api_authentication(:put, + '/projects/2.xml', + {:project => {:name => 'API update'}}, + {:success_code => :ok}) + should_allow_api_authentication(:delete, + '/projects/2.xml', + {}, + {:success_code => :ok}) - assert_tag :tag => 'projects', - :child => {:tag => 'project', :child => {:tag => 'id', :content => '1'}} - end + test "GET /projects.xml should return projects" do + get '/projects.xml' + assert_response :success + assert_equal 'application/xml', @response.content_type + + assert_tag :tag => 'projects', + :child => {:tag => 'project', :child => {:tag => 'id', :content => '1'}} + end + + test "GET /projects.json should return projects" do + get '/projects.json' + assert_response :success + assert_equal 'application/json', @response.content_type + + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Array, json['projects'] + assert_kind_of Hash, json['projects'].first + assert json['projects'].first.has_key?('id') + end + + test "GET /projects/:id.xml should return the project" do + get '/projects/1.xml' + assert_response :success + assert_equal 'application/xml', @response.content_type + + assert_tag :tag => 'project', + :child => {:tag => 'id', :content => '1'} + assert_tag :tag => 'custom_field', + :attributes => {:name => 'Development status'}, :content => 'Stable' + + assert_no_tag 'trackers' + assert_no_tag 'issue_categories' + end + + test "GET /projects/:id.json should return the project" do + get '/projects/1.json' + + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Hash, json['project'] + assert_equal 1, json['project']['id'] + end + + test "GET /projects/:id.xml with hidden custom fields should not display hidden custom fields" do + ProjectCustomField.find_by_name('Development status').update_attribute :visible, false + + get '/projects/1.xml' + assert_response :success + assert_equal 'application/xml', @response.content_type + + assert_no_tag 'custom_field', + :attributes => {:name => 'Development status'} + end + + test "GET /projects/:id.xml with include=issue_categories should return categories" do + get '/projects/1.xml?include=issue_categories' + assert_response :success + assert_equal 'application/xml', @response.content_type + + assert_tag 'issue_categories', + :attributes => {:type => 'array'}, + :child => { + :tag => 'issue_category', + :attributes => { + :id => '2', + :name => 'Recipes' + } + } + end + + test "GET /projects/:id.xml with include=trackers should return trackers" do + get '/projects/1.xml?include=trackers' + assert_response :success + assert_equal 'application/xml', @response.content_type + + assert_tag 'trackers', + :attributes => {:type => 'array'}, + :child => { + :tag => 'tracker', + :attributes => { + :id => '2', + :name => 'Feature request' + } + } + end + + test "POST /projects.xml with valid parameters should create the project" do + Setting.default_projects_modules = ['issue_tracking', 'repository'] + + assert_difference('Project.count') do + post '/projects.xml', + {:project => {:name => 'API test', :identifier => 'api-test'}}, + credentials('admin') end - context ".json" do - should "return projects" do - get '/projects.json' - assert_response :success - assert_equal 'application/json', @response.content_type + project = Project.first(:order => 'id DESC') + assert_equal 'API test', project.name + assert_equal 'api-test', project.identifier + assert_equal ['issue_tracking', 'repository'], project.enabled_module_names.sort + assert_equal Tracker.all.size, project.trackers.size - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Array, json['projects'] - assert_kind_of Hash, json['projects'].first - assert json['projects'].first.has_key?('id') - end - end + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'project', :child => {:tag => 'id', :content => project.id.to_s} end - context "GET /projects/:id" do - context ".xml" do - # TODO: A private project is needed because should_allow_api_authentication - # actually tests that authentication is *required*, not just allowed - should_allow_api_authentication(:get, "/projects/2.xml") - - should "return requested project" do - get '/projects/1.xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_tag :tag => 'project', - :child => {:tag => 'id', :content => '1'} - assert_tag :tag => 'custom_field', - :attributes => {:name => 'Development status'}, :content => 'Stable' - - assert_no_tag 'trackers' - assert_no_tag 'issue_categories' - end - - context "with hidden custom fields" do - setup do - ProjectCustomField.find_by_name('Development status').update_attribute :visible, false - end - - should "not display hidden custom fields" do - get '/projects/1.xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_no_tag 'custom_field', - :attributes => {:name => 'Development status'} - end - end - - should "return categories with include=issue_categories" do - get '/projects/1.xml?include=issue_categories' - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_tag 'issue_categories', - :attributes => {:type => 'array'}, - :child => { - :tag => 'issue_category', - :attributes => { - :id => '2', - :name => 'Recipes' - } - } - end - - should "return trackers with include=trackers" do - get '/projects/1.xml?include=trackers' - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_tag 'trackers', - :attributes => {:type => 'array'}, - :child => { - :tag => 'tracker', - :attributes => { - :id => '2', - :name => 'Feature request' - } - } - end + test "POST /projects.xml should accept enabled_module_names attribute" do + assert_difference('Project.count') do + post '/projects.xml', + {:project => {:name => 'API test', :identifier => 'api-test', :enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}}, + credentials('admin') end - context ".json" do - should_allow_api_authentication(:get, "/projects/2.json") - - should "return requested project" do - get '/projects/1.json' - - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Hash, json['project'] - assert_equal 1, json['project']['id'] - end - end + project = Project.first(:order => 'id DESC') + assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort end - context "POST /projects" do - context "with valid parameters" do - setup do - Setting.default_projects_modules = ['issue_tracking', 'repository'] - @parameters = {:project => {:name => 'API test', :identifier => 'api-test'}} - end - - context ".xml" do - should_allow_api_authentication(:post, - '/projects.xml', - {:project => {:name => 'API test', :identifier => 'api-test'}}, - {:success_code => :created}) - - - should "create a project with the attributes" do - assert_difference('Project.count') do - post '/projects.xml', @parameters, credentials('admin') - end - - project = Project.first(:order => 'id DESC') - assert_equal 'API test', project.name - assert_equal 'api-test', project.identifier - assert_equal ['issue_tracking', 'repository'], project.enabled_module_names.sort - assert_equal Tracker.all.size, project.trackers.size - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'project', :child => {:tag => 'id', :content => project.id.to_s} - end - - should "accept enabled_module_names attribute" do - @parameters[:project].merge!({:enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}) - - assert_difference('Project.count') do - post '/projects.xml', @parameters, credentials('admin') - end - - project = Project.first(:order => 'id DESC') - assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort - end - - should "accept tracker_ids attribute" do - @parameters[:project].merge!({:tracker_ids => [1, 3]}) - - assert_difference('Project.count') do - post '/projects.xml', @parameters, credentials('admin') - end - - project = Project.first(:order => 'id DESC') - assert_equal [1, 3], project.trackers.map(&:id).sort - end - end + test "POST /projects.xml should accept tracker_ids attribute" do + assert_difference('Project.count') do + post '/projects.xml', + {:project => {:name => 'API test', :identifier => 'api-test', :tracker_ids => [1, 3]}}, + credentials('admin') end - context "with invalid parameters" do - setup do - @parameters = {:project => {:name => 'API test'}} - end - - context ".xml" do - should "return errors" do - assert_no_difference('Project.count') do - post '/projects.xml', @parameters, credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Identifier can't be blank"} - end - end - end + project = Project.first(:order => 'id DESC') + assert_equal [1, 3], project.trackers.map(&:id).sort end - context "PUT /projects/:id" do - context "with valid parameters" do - setup do - @parameters = {:project => {:name => 'API update'}} - end - - context ".xml" do - should_allow_api_authentication(:put, - '/projects/2.xml', - {:project => {:name => 'API update'}}, - {:success_code => :ok}) - - should "update the project" do - assert_no_difference 'Project.count' do - put '/projects/2.xml', @parameters, credentials('jsmith') - end - assert_response :ok - assert_equal '', @response.body - assert_equal 'application/xml', @response.content_type - project = Project.find(2) - assert_equal 'API update', project.name - end - - should "accept enabled_module_names attribute" do - @parameters[:project].merge!({:enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}) - - assert_no_difference 'Project.count' do - put '/projects/2.xml', @parameters, credentials('admin') - end - assert_response :ok - assert_equal '', @response.body - project = Project.find(2) - assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort - end - - should "accept tracker_ids attribute" do - @parameters[:project].merge!({:tracker_ids => [1, 3]}) - - assert_no_difference 'Project.count' do - put '/projects/2.xml', @parameters, credentials('admin') - end - assert_response :ok - assert_equal '', @response.body - project = Project.find(2) - assert_equal [1, 3], project.trackers.map(&:id).sort - end - end + test "POST /projects.xml with invalid parameters should return errors" do + assert_no_difference('Project.count') do + post '/projects.xml', {:project => {:name => 'API test'}}, credentials('admin') end - context "with invalid parameters" do - setup do - @parameters = {:project => {:name => ''}} - end - - context ".xml" do - should "return errors" do - assert_no_difference('Project.count') do - put '/projects/2.xml', @parameters, credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} - end - end - end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => "Identifier can't be blank"} end - context "DELETE /projects/:id" do - context ".xml" do - should_allow_api_authentication(:delete, - '/projects/2.xml', - {}, - {:success_code => :ok}) + test "PUT /projects/:id.xml with valid parameters should update the project" do + assert_no_difference 'Project.count' do + put '/projects/2.xml', {:project => {:name => 'API update'}}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_equal 'application/xml', @response.content_type + project = Project.find(2) + assert_equal 'API update', project.name + end - should "delete the project" do - assert_difference('Project.count',-1) do - delete '/projects/2.xml', {}, credentials('admin') - end - assert_response :ok - assert_equal '', @response.body - assert_nil Project.find_by_id(2) - end + test "PUT /projects/:id.xml should accept enabled_module_names attribute" do + assert_no_difference 'Project.count' do + put '/projects/2.xml', {:project => {:name => 'API update', :enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}}, credentials('admin') end + assert_response :ok + assert_equal '', @response.body + project = Project.find(2) + assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort + end + + test "PUT /projects/:id.xml should accept tracker_ids attribute" do + assert_no_difference 'Project.count' do + put '/projects/2.xml', {:project => {:name => 'API update', :tracker_ids => [1, 3]}}, credentials('admin') + end + assert_response :ok + assert_equal '', @response.body + project = Project.find(2) + assert_equal [1, 3], project.trackers.map(&:id).sort + end + + test "PUT /projects/:id.xml with invalid parameters should return errors" do + assert_no_difference('Project.count') do + put '/projects/2.xml', {:project => {:name => ''}}, credentials('admin') + end + + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} + end + + test "DELETE /projects/:id.xml should delete the project" do + assert_difference('Project.count',-1) do + delete '/projects/2.xml', {}, credentials('admin') + end + assert_response :ok + assert_equal '', @response.body + assert_nil Project.find_by_id(2) end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/queries_test.rb --- a/test/integration/api_test/queries_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/queries_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::QueriesTest < ActionController::IntegrationTest +class Redmine::ApiTest::QueriesTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,35 +25,29 @@ :member_roles, :members, :enabled_modules, - :workflows, :queries def setup Setting.rest_api_enabled = '1' end - context "/queries" do - context "GET" do + test "GET /queries.xml should return queries" do + get '/queries.xml' - should "return queries" do - get '/queries.xml' - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'queries', - :attributes => {:type => 'array'}, - :child => { - :tag => 'query', - :child => { - :tag => 'id', - :content => '4', - :sibling => { - :tag => 'name', - :content => 'Public query for all projects' - } - } + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'queries', + :attributes => {:type => 'array'}, + :child => { + :tag => 'query', + :child => { + :tag => 'id', + :content => '4', + :sibling => { + :tag => 'name', + :content => 'Public query for all projects' } - end - end + } + } end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/roles_test.rb --- a/test/integration/api_test/roles_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/roles_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,73 +17,59 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::RolesTest < ActionController::IntegrationTest +class Redmine::ApiTest::RolesTest < Redmine::ApiTest::Base fixtures :roles def setup Setting.rest_api_enabled = '1' end - context "/roles" do - context "GET" do - context "xml" do - should "return the roles" do - get '/roles.xml' + test "GET /roles.xml should return the roles" do + get '/roles.xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_equal 3, assigns(:roles).size + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_equal 3, assigns(:roles).size - assert_tag :tag => 'roles', - :attributes => {:type => 'array'}, - :child => { - :tag => 'role', - :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'name', - :content => 'Developer' - } - } - } - end - end - - context "json" do - should "return the roles" do - get '/roles.json' - - assert_response :success - assert_equal 'application/json', @response.content_type - assert_equal 3, assigns(:roles).size - - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Array, json['roles'] - assert_include({'id' => 2, 'name' => 'Developer'}, json['roles']) - end - end - end + assert_tag :tag => 'roles', + :attributes => {:type => 'array'}, + :child => { + :tag => 'role', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => 'Developer' + } + } + } end - context "/roles/:id" do - context "GET" do - context "xml" do - should "return the role" do - get '/roles/1.xml' + test "GET /roles.json should return the roles" do + get '/roles.json' - assert_response :success - assert_equal 'application/xml', @response.content_type + assert_response :success + assert_equal 'application/json', @response.content_type + assert_equal 3, assigns(:roles).size - assert_select 'role' do - assert_select 'name', :text => 'Manager' - assert_select 'role permissions[type=array]' do - assert_select 'permission', Role.find(1).permissions.size - assert_select 'permission', :text => 'view_issues' - end - end - end + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Array, json['roles'] + assert_include({'id' => 2, 'name' => 'Developer'}, json['roles']) + end + + test "GET /roles/:id.xml should return the role" do + get '/roles/1.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + + assert_select 'role' do + assert_select 'name', :text => 'Manager' + assert_select 'role permissions[type=array]' do + assert_select 'permission', Role.find(1).permissions.size + assert_select 'permission', :text => 'view_issues' end end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/time_entries_test.rb --- a/test/integration/api_test/time_entries_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/time_entries_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::TimeEntriesTest < ActionController::IntegrationTest +class Redmine::ApiTest::TimeEntriesTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,141 +25,118 @@ :member_roles, :members, :enabled_modules, - :workflows, :time_entries def setup Setting.rest_api_enabled = '1' end - context "GET /time_entries.xml" do - should "return time entries" do - get '/time_entries.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'time_entries', - :child => {:tag => 'time_entry', :child => {:tag => 'id', :content => '2'}} - end - - context "with limit" do - should "return limited results" do - get '/time_entries.xml?limit=2', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'time_entries', - :children => {:count => 2} - end - end + test "GET /time_entries.xml should return time entries" do + get '/time_entries.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'time_entries', + :child => {:tag => 'time_entry', :child => {:tag => 'id', :content => '2'}} end - context "GET /time_entries/2.xml" do - should "return requested time entry" do - get '/time_entries/2.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'time_entry', - :child => {:tag => 'id', :content => '2'} - end + test "GET /time_entries.xml with limit should return limited results" do + get '/time_entries.xml?limit=2', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'time_entries', + :children => {:count => 2} end - context "POST /time_entries.xml" do - context "with issue_id" do - should "return create time entry" do - assert_difference 'TimeEntry.count' do - post '/time_entries.xml', {:time_entry => {:issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') - end - assert_response :created - assert_equal 'application/xml', @response.content_type - - entry = TimeEntry.first(:order => 'id DESC') - assert_equal 'jsmith', entry.user.login - assert_equal Issue.find(1), entry.issue - assert_equal Project.find(1), entry.project - assert_equal Date.parse('2010-12-02'), entry.spent_on - assert_equal 3.5, entry.hours - assert_equal TimeEntryActivity.find(11), entry.activity - end - - should "accept custom fields" do - field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'string') - - assert_difference 'TimeEntry.count' do - post '/time_entries.xml', {:time_entry => { - :issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11', :custom_fields => [{:id => field.id.to_s, :value => 'accepted'}] - }}, credentials('jsmith') - end - assert_response :created - assert_equal 'application/xml', @response.content_type - - entry = TimeEntry.first(:order => 'id DESC') - assert_equal 'accepted', entry.custom_field_value(field) - end - end - - context "with project_id" do - should "return create time entry" do - assert_difference 'TimeEntry.count' do - post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') - end - assert_response :created - assert_equal 'application/xml', @response.content_type - - entry = TimeEntry.first(:order => 'id DESC') - assert_equal 'jsmith', entry.user.login - assert_nil entry.issue - assert_equal Project.find(1), entry.project - assert_equal Date.parse('2010-12-02'), entry.spent_on - assert_equal 3.5, entry.hours - assert_equal TimeEntryActivity.find(11), entry.activity - end - end - - context "with invalid parameters" do - should "return errors" do - assert_no_difference 'TimeEntry.count' do - post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :activity_id => '11'}}, credentials('jsmith') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - - assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} - end - end + test "GET /time_entries/:id.xml should return the time entry" do + get '/time_entries/2.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'time_entry', + :child => {:tag => 'id', :content => '2'} end - context "PUT /time_entries/2.xml" do - context "with valid parameters" do - should "update time entry" do - assert_no_difference 'TimeEntry.count' do - put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, credentials('jsmith') - end - assert_response :ok - assert_equal '', @response.body - assert_equal 'API Update', TimeEntry.find(2).comments - end + test "POST /time_entries.xml with issue_id should create time entry" do + assert_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => {:issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') end + assert_response :created + assert_equal 'application/xml', @response.content_type - context "with invalid parameters" do - should "return errors" do - assert_no_difference 'TimeEntry.count' do - put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, credentials('jsmith') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - - assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} - end - end + entry = TimeEntry.first(:order => 'id DESC') + assert_equal 'jsmith', entry.user.login + assert_equal Issue.find(1), entry.issue + assert_equal Project.find(1), entry.project + assert_equal Date.parse('2010-12-02'), entry.spent_on + assert_equal 3.5, entry.hours + assert_equal TimeEntryActivity.find(11), entry.activity end - context "DELETE /time_entries/2.xml" do - should "destroy time entry" do - assert_difference 'TimeEntry.count', -1 do - delete '/time_entries/2.xml', {}, credentials('jsmith') - end - assert_response :ok - assert_equal '', @response.body - assert_nil TimeEntry.find_by_id(2) + test "POST /time_entries.xml with issue_id should accept custom fields" do + field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'string') + + assert_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => { + :issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11', :custom_fields => [{:id => field.id.to_s, :value => 'accepted'}] + }}, credentials('jsmith') end + assert_response :created + assert_equal 'application/xml', @response.content_type + + entry = TimeEntry.first(:order => 'id DESC') + assert_equal 'accepted', entry.custom_field_value(field) + end + + test "POST /time_entries.xml with project_id should create time entry" do + assert_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type + + entry = TimeEntry.first(:order => 'id DESC') + assert_equal 'jsmith', entry.user.login + assert_nil entry.issue + assert_equal Project.find(1), entry.project + assert_equal Date.parse('2010-12-02'), entry.spent_on + assert_equal 3.5, entry.hours + assert_equal TimeEntryActivity.find(11), entry.activity + end + + test "POST /time_entries.xml with invalid parameters should return errors" do + assert_no_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :activity_id => '11'}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} + end + + test "PUT /time_entries/:id.xml with valid parameters should update time entry" do + assert_no_difference 'TimeEntry.count' do + put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_equal 'API Update', TimeEntry.find(2).comments + end + + test "PUT /time_entries/:id.xml with invalid parameters should return errors" do + assert_no_difference 'TimeEntry.count' do + put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} + end + + test "DELETE /time_entries/:id.xml should destroy time entry" do + assert_difference 'TimeEntry.count', -1 do + delete '/time_entries/2.xml', {}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_nil TimeEntry.find_by_id(2) end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/token_authentication_test.rb --- a/test/integration/api_test/token_authentication_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/token_authentication_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,14 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::TokenAuthenticationTest < ActionController::IntegrationTest +class Redmine::ApiTest::TokenAuthenticationTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def setup Setting.rest_api_enabled = '1' @@ -21,13 +37,6 @@ end # Using the NewsController because it's a simple API. - context "get /news" do - context "in :xml format" do - should_allow_key_based_auth(:get, "/news.xml") - end - - context "in :json format" do - should_allow_key_based_auth(:get, "/news.json") - end - end + should_allow_key_based_auth(:get, "/news.xml") + should_allow_key_based_auth(:get, "/news.json") end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/trackers_test.rb --- a/test/integration/api_test/trackers_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/trackers_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,35 +17,30 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::TrackersTest < ActionController::IntegrationTest +class Redmine::ApiTest::TrackersTest < Redmine::ApiTest::Base fixtures :trackers def setup Setting.rest_api_enabled = '1' end - context "/trackers" do - context "GET" do + test "GET /trackers.xml should return trackers" do + get '/trackers.xml' - should "return trackers" do - get '/trackers.xml' - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'trackers', - :attributes => {:type => 'array'}, - :child => { - :tag => 'tracker', - :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'name', - :content => 'Feature request' - } - } + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'trackers', + :attributes => {:type => 'array'}, + :child => { + :tag => 'tracker', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => 'Feature request' } - end - end + } + } end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/users_test.rb --- a/test/integration/api_test/users_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/users_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,332 +16,312 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../../test_helper', __FILE__) -require 'pp' -class ApiTest::UsersTest < ActionController::IntegrationTest + +class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base fixtures :users, :members, :member_roles, :roles, :projects def setup Setting.rest_api_enabled = '1' end - context "GET /users" do - should_allow_api_authentication(:get, "/users.xml") - should_allow_api_authentication(:get, "/users.json") + should_allow_api_authentication(:get, "/users.xml") + should_allow_api_authentication(:get, "/users.json") + should_allow_api_authentication(:post, + '/users.xml', + {:user => { + :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', + :mail => 'foo@example.net', :password => 'secret123' + }}, + {:success_code => :created}) + should_allow_api_authentication(:post, + '/users.json', + {:user => { + :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', + :mail => 'foo@example.net' + }}, + {:success_code => :created}) + should_allow_api_authentication(:put, + '/users/2.xml', + {:user => { + :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', + :mail => 'jsmith@somenet.foo' + }}, + {:success_code => :ok}) + should_allow_api_authentication(:put, + '/users/2.json', + {:user => { + :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', + :mail => 'jsmith@somenet.foo' + }}, + {:success_code => :ok}) + should_allow_api_authentication(:delete, + '/users/2.xml', + {}, + {:success_code => :ok}) + should_allow_api_authentication(:delete, + '/users/2.xml', + {}, + {:success_code => :ok}) + + test "GET /users/:id.xml should return the user" do + get '/users/2.xml' + + assert_response :success + assert_tag :tag => 'user', + :child => {:tag => 'id', :content => '2'} end - context "GET /users/2" do - context ".xml" do - should "return requested user" do - get '/users/2.xml' + test "GET /users/:id.json should return the user" do + get '/users/2.json' - assert_response :success - assert_tag :tag => 'user', - :child => {:tag => 'id', :content => '2'} - end + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Hash, json['user'] + assert_equal 2, json['user']['id'] + end - context "with include=memberships" do - should "include memberships" do - get '/users/2.xml?include=memberships' - - assert_response :success - assert_tag :tag => 'memberships', - :parent => {:tag => 'user'}, - :children => {:count => 1} - end - end + test "GET /users/:id.xml with include=memberships should include memberships" do + get '/users/2.xml?include=memberships' + + assert_response :success + assert_tag :tag => 'memberships', + :parent => {:tag => 'user'}, + :children => {:count => 1} + end + + test "GET /users/:id.json with include=memberships should include memberships" do + get '/users/2.json?include=memberships' + + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Array, json['user']['memberships'] + assert_equal [{ + "id"=>1, + "project"=>{"name"=>"eCookbook", "id"=>1}, + "roles"=>[{"name"=>"Manager", "id"=>1}] + }], json['user']['memberships'] + end + + test "GET /users/current.xml should require authentication" do + get '/users/current.xml' + + assert_response 401 + end + + test "GET /users/current.xml should return current user" do + get '/users/current.xml', {}, credentials('jsmith') + + assert_tag :tag => 'user', + :child => {:tag => 'id', :content => '2'} + end + + test "GET /users/:id should not return login for other user" do + get '/users/3.xml', {}, credentials('jsmith') + assert_response :success + assert_no_tag 'user', :child => {:tag => 'login'} + end + + test "GET /users/:id should return login for current user" do + get '/users/2.xml', {}, credentials('jsmith') + assert_response :success + assert_tag 'user', :child => {:tag => 'login', :content => 'jsmith'} + end + + test "GET /users/:id should not return api_key for other user" do + get '/users/3.xml', {}, credentials('jsmith') + assert_response :success + assert_no_tag 'user', :child => {:tag => 'api_key'} + end + + test "GET /users/:id should return api_key for current user" do + get '/users/2.xml', {}, credentials('jsmith') + assert_response :success + assert_tag 'user', :child => {:tag => 'api_key', :content => User.find(2).api_key} + end + + test "GET /users/:id should not return status for standard user" do + get '/users/3.xml', {}, credentials('jsmith') + assert_response :success + assert_no_tag 'user', :child => {:tag => 'status'} + end + + test "GET /users/:id should return status for administrators" do + get '/users/2.xml', {}, credentials('admin') + assert_response :success + assert_tag 'user', :child => {:tag => 'status', :content => User.find(1).status.to_s} + end + + test "POST /users.xml with valid parameters should create the user" do + assert_difference('User.count') do + post '/users.xml', { + :user => { + :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', + :mail => 'foo@example.net', :password => 'secret123', + :mail_notification => 'only_assigned'} + }, + credentials('admin') end - context ".json" do - should "return requested user" do - get '/users/2.json' + user = User.first(:order => 'id DESC') + assert_equal 'foo', user.login + assert_equal 'Firstname', user.firstname + assert_equal 'Lastname', user.lastname + assert_equal 'foo@example.net', user.mail + assert_equal 'only_assigned', user.mail_notification + assert !user.admin? + assert user.check_password?('secret123') - assert_response :success - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Hash, json['user'] - assert_equal 2, json['user']['id'] - end - - context "with include=memberships" do - should "include memberships" do - get '/users/2.json?include=memberships' - - assert_response :success - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Array, json['user']['memberships'] - assert_equal [{ - "id"=>1, - "project"=>{"name"=>"eCookbook", "id"=>1}, - "roles"=>[{"name"=>"Manager", "id"=>1}] - }], json['user']['memberships'] - end - end - end + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'user', :child => {:tag => 'id', :content => user.id.to_s} end - context "GET /users/current" do - context ".xml" do - should "require authentication" do - get '/users/current.xml' + test "POST /users.json with valid parameters should create the user" do + assert_difference('User.count') do + post '/users.json', { + :user => { + :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', + :mail => 'foo@example.net', :password => 'secret123', + :mail_notification => 'only_assigned'} + }, + credentials('admin') + end - assert_response 401 - end + user = User.first(:order => 'id DESC') + assert_equal 'foo', user.login + assert_equal 'Firstname', user.firstname + assert_equal 'Lastname', user.lastname + assert_equal 'foo@example.net', user.mail + assert !user.admin? - should "return current user" do - get '/users/current.xml', {}, credentials('jsmith') - - assert_tag :tag => 'user', - :child => {:tag => 'id', :content => '2'} - end - end + assert_response :created + assert_equal 'application/json', @response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Hash, json['user'] + assert_equal user.id, json['user']['id'] end - context "POST /users" do - context "with valid parameters" do - setup do - @parameters = { - :user => { - :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', - :mail => 'foo@example.net', :password => 'secret123', - :mail_notification => 'only_assigned' - } - } - end - - context ".xml" do - should_allow_api_authentication(:post, - '/users.xml', - {:user => { - :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', - :mail => 'foo@example.net', :password => 'secret123' - }}, - {:success_code => :created}) - - should "create a user with the attributes" do - assert_difference('User.count') do - post '/users.xml', @parameters, credentials('admin') - end - - user = User.first(:order => 'id DESC') - assert_equal 'foo', user.login - assert_equal 'Firstname', user.firstname - assert_equal 'Lastname', user.lastname - assert_equal 'foo@example.net', user.mail - assert_equal 'only_assigned', user.mail_notification - assert !user.admin? - assert user.check_password?('secret123') - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'user', :child => {:tag => 'id', :content => user.id.to_s} - end - end - - context ".json" do - should_allow_api_authentication(:post, - '/users.json', - {:user => { - :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', - :mail => 'foo@example.net' - }}, - {:success_code => :created}) - - should "create a user with the attributes" do - assert_difference('User.count') do - post '/users.json', @parameters, credentials('admin') - end - - user = User.first(:order => 'id DESC') - assert_equal 'foo', user.login - assert_equal 'Firstname', user.firstname - assert_equal 'Lastname', user.lastname - assert_equal 'foo@example.net', user.mail - assert !user.admin? - - assert_response :created - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Hash, json['user'] - assert_equal user.id, json['user']['id'] - end - end + test "POST /users.xml with with invalid parameters should return errors" do + assert_no_difference('User.count') do + post '/users.xml', {:user => {:login => 'foo', :lastname => 'Lastname', :mail => 'foo'}}, credentials('admin') end - context "with invalid parameters" do - setup do - @parameters = {:user => {:login => 'foo', :lastname => 'Lastname', :mail => 'foo'}} - end - - context ".xml" do - should "return errors" do - assert_no_difference('User.count') do - post '/users.xml', @parameters, credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => { - :tag => 'error', - :content => "First name can't be blank" - } - end - end - - context ".json" do - should "return errors" do - assert_no_difference('User.count') do - post '/users.json', @parameters, credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert json.has_key?('errors') - assert_kind_of Array, json['errors'] - end - end - end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => { + :tag => 'error', + :content => "First name can't be blank" + } end - context "PUT /users/2" do - context "with valid parameters" do - setup do - @parameters = { - :user => { - :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', - :mail => 'jsmith@somenet.foo' - } - } - end - - context ".xml" do - should_allow_api_authentication(:put, - '/users/2.xml', - {:user => { - :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', - :mail => 'jsmith@somenet.foo' - }}, - {:success_code => :ok}) - - should "update user with the attributes" do - assert_no_difference('User.count') do - put '/users/2.xml', @parameters, credentials('admin') - end - - user = User.find(2) - assert_equal 'jsmith', user.login - assert_equal 'John', user.firstname - assert_equal 'Renamed', user.lastname - assert_equal 'jsmith@somenet.foo', user.mail - assert !user.admin? - - assert_response :ok - assert_equal '', @response.body - end - end - - context ".json" do - should_allow_api_authentication(:put, - '/users/2.json', - {:user => { - :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', - :mail => 'jsmith@somenet.foo' - }}, - {:success_code => :ok}) - - should "update user with the attributes" do - assert_no_difference('User.count') do - put '/users/2.json', @parameters, credentials('admin') - end - - user = User.find(2) - assert_equal 'jsmith', user.login - assert_equal 'John', user.firstname - assert_equal 'Renamed', user.lastname - assert_equal 'jsmith@somenet.foo', user.mail - assert !user.admin? - - assert_response :ok - assert_equal '', @response.body - end - end + test "POST /users.json with with invalid parameters should return errors" do + assert_no_difference('User.count') do + post '/users.json', {:user => {:login => 'foo', :lastname => 'Lastname', :mail => 'foo'}}, credentials('admin') end - context "with invalid parameters" do - setup do - @parameters = { - :user => { - :login => 'jsmith', :firstname => '', :lastname => 'Lastname', - :mail => 'foo' - } - } - end - - context ".xml" do - should "return errors" do - assert_no_difference('User.count') do - put '/users/2.xml', @parameters, credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => { - :tag => 'error', - :content => "First name can't be blank" - } - end - end - - context ".json" do - should "return errors" do - assert_no_difference('User.count') do - put '/users/2.json', @parameters, credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert json.has_key?('errors') - assert_kind_of Array, json['errors'] - end - end - end + assert_response :unprocessable_entity + assert_equal 'application/json', @response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert json.has_key?('errors') + assert_kind_of Array, json['errors'] end - context "DELETE /users/2" do - context ".xml" do - should_allow_api_authentication(:delete, - '/users/2.xml', - {}, - {:success_code => :ok}) - - should "delete user" do - assert_difference('User.count', -1) do - delete '/users/2.xml', {}, credentials('admin') - end - - assert_response :ok - assert_equal '', @response.body - end + test "PUT /users/:id.xml with valid parameters should update the user" do + assert_no_difference('User.count') do + put '/users/2.xml', { + :user => { + :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', + :mail => 'jsmith@somenet.foo'} + }, + credentials('admin') end - context ".json" do - should_allow_api_authentication(:delete, - '/users/2.xml', - {}, - {:success_code => :ok}) + user = User.find(2) + assert_equal 'jsmith', user.login + assert_equal 'John', user.firstname + assert_equal 'Renamed', user.lastname + assert_equal 'jsmith@somenet.foo', user.mail + assert !user.admin? - should "delete user" do - assert_difference('User.count', -1) do - delete '/users/2.json', {}, credentials('admin') - end + assert_response :ok + assert_equal '', @response.body + end - assert_response :ok - assert_equal '', @response.body - end + test "PUT /users/:id.json with valid parameters should update the user" do + assert_no_difference('User.count') do + put '/users/2.json', { + :user => { + :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', + :mail => 'jsmith@somenet.foo'} + }, + credentials('admin') end + + user = User.find(2) + assert_equal 'jsmith', user.login + assert_equal 'John', user.firstname + assert_equal 'Renamed', user.lastname + assert_equal 'jsmith@somenet.foo', user.mail + assert !user.admin? + + assert_response :ok + assert_equal '', @response.body + end + + test "PUT /users/:id.xml with invalid parameters" do + assert_no_difference('User.count') do + put '/users/2.xml', { + :user => { + :login => 'jsmith', :firstname => '', :lastname => 'Lastname', + :mail => 'foo'} + }, + credentials('admin') + end + + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => { + :tag => 'error', + :content => "First name can't be blank" + } + end + + test "PUT /users/:id.json with invalid parameters" do + assert_no_difference('User.count') do + put '/users/2.json', { + :user => { + :login => 'jsmith', :firstname => '', :lastname => 'Lastname', + :mail => 'foo'} + }, + credentials('admin') + end + + assert_response :unprocessable_entity + assert_equal 'application/json', @response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert json.has_key?('errors') + assert_kind_of Array, json['errors'] + end + + test "DELETE /users/:id.xml should delete the user" do + assert_difference('User.count', -1) do + delete '/users/2.xml', {}, credentials('admin') + end + + assert_response :ok + assert_equal '', @response.body + end + + test "DELETE /users/:id.json should delete the user" do + assert_difference('User.count', -1) do + delete '/users/2.json', {}, credentials('admin') + end + + assert_response :ok + assert_equal '', @response.body end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/versions_test.rb --- a/test/integration/api_test/versions_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/versions_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::VersionsTest < ActionController::IntegrationTest +class Redmine::ApiTest::VersionsTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,112 +25,118 @@ :member_roles, :members, :enabled_modules, - :workflows, :versions def setup Setting.rest_api_enabled = '1' end - context "/projects/:project_id/versions" do - context "GET" do - should "return project versions" do - get '/projects/1/versions.xml' + test "GET /projects/:project_id/versions.xml should return project versions" do + get '/projects/1/versions.xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'versions', - :attributes => {:type => 'array'}, - :child => { - :tag => 'version', - :child => { - :tag => 'id', - :content => '2', - :sibling => { - :tag => 'name', - :content => '1.0' - } - } + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'versions', + :attributes => {:type => 'array'}, + :child => { + :tag => 'version', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => '1.0' } - end + } + } + end + + test "POST /projects/:project_id/versions.xml should create the version" do + assert_difference 'Version.count' do + post '/projects/1/versions.xml', {:version => {:name => 'API test'}}, credentials('jsmith') end - context "POST" do - should "create the version" do - assert_difference 'Version.count' do - post '/projects/1/versions.xml', {:version => {:name => 'API test'}}, credentials('jsmith') - end + version = Version.first(:order => 'id DESC') + assert_equal 'API test', version.name - version = Version.first(:order => 'id DESC') - assert_equal 'API test', version.name + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s} + end - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s} - end + test "POST /projects/:project_id/versions.xml should create the version with due date" do + assert_difference 'Version.count' do + post '/projects/1/versions.xml', {:version => {:name => 'API test', :due_date => '2012-01-24'}}, credentials('jsmith') + end - should "create the version with due date" do - assert_difference 'Version.count' do - post '/projects/1/versions.xml', {:version => {:name => 'API test', :due_date => '2012-01-24'}}, credentials('jsmith') - end + version = Version.first(:order => 'id DESC') + assert_equal 'API test', version.name + assert_equal Date.parse('2012-01-24'), version.due_date - version = Version.first(:order => 'id DESC') - assert_equal 'API test', version.name - assert_equal Date.parse('2012-01-24'), version.due_date + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s} + end - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s} - end + test "POST /projects/:project_id/versions.xml should create the version with custom fields" do + field = VersionCustomField.generate! - context "with failure" do - should "return the errors" do - assert_no_difference('Version.count') do - post '/projects/1/versions.xml', {:version => {:name => ''}}, credentials('jsmith') - end + assert_difference 'Version.count' do + post '/projects/1/versions.xml', { + :version => { + :name => 'API test', + :custom_fields => [ + {'id' => field.id.to_s, 'value' => 'Some value'} + ] + } + }, credentials('jsmith') + end - assert_response :unprocessable_entity - assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"} - end - end + version = Version.first(:order => 'id DESC') + assert_equal 'API test', version.name + assert_equal 'Some value', version.custom_field_value(field) + + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_select 'version>custom_fields>custom_field[id=?]>value', field.id.to_s, 'Some value' + end + + test "POST /projects/:project_id/versions.xml with failure should return the errors" do + assert_no_difference('Version.count') do + post '/projects/1/versions.xml', {:version => {:name => ''}}, credentials('jsmith') + end + + assert_response :unprocessable_entity + assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"} + end + + test "GET /versions/:id.xml should return the version" do + get '/versions/2.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_select 'version' do + assert_select 'id', :text => '2' + assert_select 'name', :text => '1.0' + assert_select 'sharing', :text => 'none' end end - context "/versions/:id" do - context "GET" do - should "return the version" do - get '/versions/2.xml' + test "PUT /versions/:id.xml should update the version" do + put '/versions/2.xml', {:version => {:name => 'API update'}}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_select 'version' do - assert_select 'id', :text => '2' - assert_select 'name', :text => '1.0' - assert_select 'sharing', :text => 'none' - end - end + assert_response :ok + assert_equal '', @response.body + assert_equal 'API update', Version.find(2).name + end + + test "DELETE /versions/:id.xml should destroy the version" do + assert_difference 'Version.count', -1 do + delete '/versions/3.xml', {}, credentials('jsmith') end - context "PUT" do - should "update the version" do - put '/versions/2.xml', {:version => {:name => 'API update'}}, credentials('jsmith') - - assert_response :ok - assert_equal '', @response.body - assert_equal 'API update', Version.find(2).name - end - end - - context "DELETE" do - should "destroy the version" do - assert_difference 'Version.count', -1 do - delete '/versions/3.xml', {}, credentials('jsmith') - end - - assert_response :ok - assert_equal '', @response.body - assert_nil Version.find_by_id(3) - end - end + assert_response :ok + assert_equal '', @response.body + assert_nil Version.find_by_id(3) end end diff -r d98d22a98252 -r afce8026aaeb test/integration/api_test/wiki_pages_test.rb --- a/test/integration/api_test/wiki_pages_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/api_test/wiki_pages_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::WikiPagesTest < ActionController::IntegrationTest +class Redmine::ApiTest::WikiPagesTest < Redmine::ApiTest::Base fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :attachments @@ -90,6 +90,7 @@ assert_select 'version', :text => '2' assert_select 'text' assert_select 'author' + assert_select 'comments', :text => 'Small update' assert_select 'created_on' assert_select 'updated_on' end diff -r d98d22a98252 -r afce8026aaeb test/integration/application_test.rb --- a/test/integration/application_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/application_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,8 +26,7 @@ :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def test_set_localization Setting.default_language = 'en' @@ -37,17 +36,20 @@ assert_response :success assert_tag :tag => 'h2', :content => 'Projets' assert_equal :fr, current_language + assert_select "html[lang=?]", "fr" # then an italien user get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'it;q=0.8,en-us;q=0.5,en;q=0.3' assert_response :success assert_tag :tag => 'h2', :content => 'Progetti' assert_equal :it, current_language + assert_select "html[lang=?]", "it" # not a supported language: default language should be used get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'zz' assert_response :success assert_tag :tag => 'h2', :content => 'Projects' + assert_select "html[lang=?]", "en" end def test_token_based_access_should_not_start_session @@ -65,4 +67,13 @@ get '/login.png' assert_response 404 end + + def test_invalid_token_should_call_custom_handler + ActionController::Base.allow_forgery_protection = true + post '/issues' + assert_response 422 + assert_include "Invalid form authenticity token.", response.body + ensure + ActionController::Base.allow_forgery_protection = false + end end diff -r d98d22a98252 -r afce8026aaeb test/integration/attachments_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/integration/attachments_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,132 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class AttachmentsTest < ActionController::IntegrationTest + fixtures :projects, :enabled_modules, + :users, :roles, :members, :member_roles, + :trackers, :projects_trackers, + :issue_statuses, :enumerations + + def test_upload_as_js_and_attach_to_an_issue + log_user('jsmith', 'jsmith') + + token = ajax_upload('myupload.txt', 'File content') + + assert_difference 'Issue.count' do + post '/projects/ecookbook/issues', { + :issue => {:tracker_id => 1, :subject => 'Issue with upload'}, + :attachments => {'1' => {:filename => 'myupload.txt', :description => 'My uploaded file', :token => token}} + } + assert_response 302 + end + + issue = Issue.order('id DESC').first + assert_equal 'Issue with upload', issue.subject + assert_equal 1, issue.attachments.count + + attachment = issue.attachments.first + assert_equal 'myupload.txt', attachment.filename + assert_equal 'My uploaded file', attachment.description + assert_equal 'File content'.length, attachment.filesize + end + + def test_upload_as_js_and_preview_as_inline_attachment + log_user('jsmith', 'jsmith') + + token = ajax_upload('myupload.jpg', 'JPEG content') + + post '/issues/preview/new/ecookbook', { + :issue => {:tracker_id => 1, :description => 'Inline upload: !myupload.jpg!'}, + :attachments => {'1' => {:filename => 'myupload.jpg', :description => 'My uploaded file', :token => token}} + } + assert_response :success + + attachment_path = response.body.match(%r{ {:tracker_id => 1, :subject => ''}, + :attachments => {'1' => {:filename => 'myupload.txt', :description => 'My uploaded file', :token => token}} + } + assert_response :success + end + assert_select 'input[type=hidden][name=?][value=?]', 'attachments[p0][token]', token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'myupload.txt' + assert_select 'input[name=?][value=?]', 'attachments[p0][description]', 'My uploaded file' + + assert_difference 'Issue.count' do + post '/projects/ecookbook/issues', { + :issue => {:tracker_id => 1, :subject => 'Issue with upload'}, + :attachments => {'p0' => {:filename => 'myupload.txt', :description => 'My uploaded file', :token => token}} + } + assert_response 302 + end + + issue = Issue.order('id DESC').first + assert_equal 'Issue with upload', issue.subject + assert_equal 1, issue.attachments.count + + attachment = issue.attachments.first + assert_equal 'myupload.txt', attachment.filename + assert_equal 'My uploaded file', attachment.description + assert_equal 'File content'.length, attachment.filesize + end + + def test_upload_as_js_and_destroy + log_user('jsmith', 'jsmith') + + token = ajax_upload('myupload.txt', 'File content') + + attachment = Attachment.order('id DESC').first + attachment_path = "/attachments/#{attachment.id}.js?attachment_id=1" + assert_include "href: '#{attachment_path}'", response.body, "Path to attachment: #{attachment_path} not found in response:\n#{response.body}" + + assert_difference 'Attachment.count', -1 do + delete attachment_path + assert_response :success + end + + assert_include "$('#attachments_1').remove();", response.body + end + + private + + def ajax_upload(filename, content, attachment_id=1) + assert_difference 'Attachment.count' do + post "/uploads.js?attachment_id=#{attachment_id}&filename=#{filename}", content, {"CONTENT_TYPE" => 'application/octet-stream'} + assert_response :success + assert_equal 'text/javascript', response.content_type + end + + token = response.body.match(/\.val\('(\d+\.[0-9a-f]+)'\)/)[1] + assert_not_nil token, "No upload token found in response:\n#{response.body}" + token + end +end diff -r d98d22a98252 -r afce8026aaeb test/integration/issues_test.rb --- a/test/integration/issues_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/issues_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -65,15 +65,6 @@ assert_equal 1, issue.status.id end - def test_update_issue_form - log_user('jsmith', 'jsmith') - post 'projects/ecookbook/issues/new', :issue => { :tracker_id => "2"} - assert_response :success - assert_tag 'select', - :attributes => {:name => 'issue[tracker_id]'}, - :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}} - end - # add then remove 2 attachments to an issue def test_issue_attachments log_user('jsmith', 'jsmith') diff -r d98d22a98252 -r afce8026aaeb test/integration/layout_test.rb --- a/test/integration/layout_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/layout_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -24,8 +24,7 @@ :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules test "browsing to a missing page should render the base layout" do get "/users/100000000" @@ -86,6 +85,26 @@ get '/issues' assert_not_include "/javascripts/i18n/jquery.ui.datepicker", response.body end + + with_settings :default_language => 'zh' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-zh-CN.js", response.body + end + + with_settings :default_language => 'zh-TW' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-zh-TW.js", response.body + end + + with_settings :default_language => 'pt' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-pt.js", response.body + end + + with_settings :default_language => 'pt-BR' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-pt-BR.js", response.body + end end def test_search_field_outside_project_should_link_to_global_search diff -r d98d22a98252 -r afce8026aaeb test/integration/lib/redmine/hook_test.rb --- a/test/integration/lib/redmine/hook_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/lib/redmine/hook_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/lib/redmine/menu_manager_test.rb --- a/test/integration/lib/redmine/menu_manager_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/lib/redmine/menu_manager_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,8 +26,7 @@ :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def test_project_menu_with_specific_locale get 'projects/ecookbook/issues', { }, 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' diff -r d98d22a98252 -r afce8026aaeb test/integration/lib/redmine/themes_test.rb --- a/test/integration/lib/redmine/themes_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/lib/redmine/themes_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/projects_test.rb --- a/test/integration/projects_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/projects_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/repositories_git_test.rb --- a/test/integration/repositories_git_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/repositories_git_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/account_test.rb --- a/test/integration/routing/account_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/account_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,10 +25,12 @@ { :controller => 'account', :action => 'login' } ) end - assert_routing( - { :method => 'get', :path => "/logout" }, - { :controller => 'account', :action => 'logout' } - ) + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/logout" }, + { :controller => 'account', :action => 'logout' } + ) + end ["get", "post"].each do |method| assert_routing( { :method => method, :path => "/account/register" }, diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/activities_test.rb --- a/test/integration/routing/activities_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/activities_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/admin_test.rb --- a/test/integration/routing/admin_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/admin_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/attachments_test.rb --- a/test/integration/routing/attachments_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/attachments_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/auth_sources_test.rb --- a/test/integration/routing/auth_sources_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/auth_sources_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -51,5 +51,9 @@ { :controller => 'auth_sources', :action => 'test_connection', :id => '1234' } ) + assert_routing( + { :method => 'get', :path => "/auth_sources/autocomplete_for_new_user" }, + { :controller => 'auth_sources', :action => 'autocomplete_for_new_user' } + ) end end diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/auto_completes_test.rb --- a/test/integration/routing/auto_completes_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/auto_completes_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/boards_test.rb --- a/test/integration/routing/boards_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/boards_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/calendars_test.rb --- a/test/integration/routing/calendars_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/calendars_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/comments_test.rb --- a/test/integration/routing/comments_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/comments_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/context_menus_test.rb --- a/test/integration/routing/context_menus_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/context_menus_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/custom_fields_test.rb --- a/test/integration/routing/custom_fields_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/custom_fields_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -44,4 +44,11 @@ { :controller => 'custom_fields', :action => 'destroy', :id => '2' } ) end + + def test_custom_fields_api + assert_routing( + { :method => 'get', :path => "/custom_fields.xml" }, + { :controller => 'custom_fields', :action => 'index', :format => 'xml' } + ) + end end diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/documents_test.rb --- a/test/integration/routing/documents_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/documents_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/enumerations_test.rb --- a/test/integration/routing/enumerations_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/enumerations_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/files_test.rb --- a/test/integration/routing/files_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/files_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/gantts_test.rb --- a/test/integration/routing/gantts_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/gantts_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/groups_test.rb --- a/test/integration/routing/groups_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/groups_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -48,6 +48,10 @@ { :controller => 'groups', :action => 'autocomplete_for_user', :id => '1' } ) assert_routing( + { :method => 'get', :path => "/groups/1/autocomplete_for_user.js" }, + { :controller => 'groups', :action => 'autocomplete_for_user', :id => '1', :format => 'js' } + ) + assert_routing( { :method => 'get', :path => "/groups/1" }, { :controller => 'groups', :action => 'show', :id => '1' } ) diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/issue_categories_test.rb --- a/test/integration/routing/issue_categories_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/issue_categories_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/issue_relations_test.rb --- a/test/integration/routing/issue_relations_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/issue_relations_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/issue_statuses_test.rb --- a/test/integration/routing/issue_statuses_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/issue_statuses_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/issues_test.rb --- a/test/integration/routing/issues_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/issues_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -107,8 +107,8 @@ def test_issues_form_update ["post", "put"].each do |method| assert_routing( - { :method => method, :path => "/projects/23/issues/new" }, - { :controller => 'issues', :action => 'new', :project_id => '23' } + { :method => method, :path => "/projects/23/issues/update_form" }, + { :controller => 'issues', :action => 'update_form', :project_id => '23' } ) end end diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/journals_test.rb --- a/test/integration/routing/journals_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/journals_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/mail_handler_test.rb --- a/test/integration/routing/mail_handler_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/mail_handler_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/members_test.rb --- a/test/integration/routing/members_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/members_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -55,5 +55,9 @@ { :method => 'get', :path => "/projects/5234/memberships/autocomplete" }, { :controller => 'members', :action => 'autocomplete', :project_id => '5234' } ) + assert_routing( + { :method => 'get', :path => "/projects/5234/memberships/autocomplete.js" }, + { :controller => 'members', :action => 'autocomplete', :project_id => '5234', :format => 'js' } + ) end end diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/messages_test.rb --- a/test/integration/routing/messages_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/messages_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/my_test.rb --- a/test/integration/routing/my_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/my_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/news_test.rb --- a/test/integration/routing/news_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/news_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/previews_test.rb --- a/test/integration/routing/previews_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/previews_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,7 +19,7 @@ class RoutingPreviewsTest < ActionController::IntegrationTest def test_previews - ["get", "post"].each do |method| + ["get", "post", "put"].each do |method| assert_routing( { :method => method, :path => "/issues/preview/new/123" }, { :controller => 'previews', :action => 'issue', :project_id => '123' } diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/project_enumerations_test.rb --- a/test/integration/routing/project_enumerations_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/project_enumerations_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/projects_test.rb --- a/test/integration/routing/projects_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/projects_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/queries_test.rb --- a/test/integration/routing/queries_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/queries_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/reports_test.rb --- a/test/integration/routing/reports_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/reports_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/repositories_test.rb --- a/test/integration/routing/repositories_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/repositories_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/roles_test.rb --- a/test/integration/routing/roles_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/roles_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/search_test.rb --- a/test/integration/routing/search_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/search_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/settings_test.rb --- a/test/integration/routing/settings_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/settings_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/sys_test.rb --- a/test/integration/routing/sys_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/sys_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/timelog_test.rb --- a/test/integration/routing/timelog_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/timelog_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/trackers_test.rb --- a/test/integration/routing/trackers_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/trackers_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/users_test.rb --- a/test/integration/routing/users_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/users_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/versions_test.rb --- a/test/integration/routing/versions_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/versions_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/watchers_test.rb --- a/test/integration/routing/watchers_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/watchers_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -32,7 +32,7 @@ { :controller => 'watchers', :action => 'create' } ) assert_routing( - { :method => 'post', :path => "/watchers/destroy" }, + { :method => 'delete', :path => "/watchers" }, { :controller => 'watchers', :action => 'destroy' } ) assert_routing( @@ -44,8 +44,18 @@ { :controller => 'watchers', :action => 'watch' } ) assert_routing( - { :method => 'post', :path => "/watchers/unwatch" }, + { :method => 'delete', :path => "/watchers/watch" }, { :controller => 'watchers', :action => 'unwatch' } ) + assert_routing( + { :method => 'post', :path => "/issues/12/watchers.xml" }, + { :controller => 'watchers', :action => 'create', + :object_type => 'issue', :object_id => '12', :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/issues/12/watchers/3.xml" }, + { :controller => 'watchers', :action => 'destroy', + :object_type => 'issue', :object_id => '12', :user_id => '3', :format => 'xml'} + ) end end diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/welcome_test.rb --- a/test/integration/routing/welcome_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/welcome_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/wiki_test.rb --- a/test/integration/routing/wiki_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/wiki_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -53,6 +53,10 @@ { :controller => 'wiki', :action => 'annotate', :project_id => '1', :id => 'CookBook_documentation', :version => '2' } ) + # Make sure we don't route wiki page sub-uris to let plugins handle them + assert_raise(ActionController::RoutingError) do + assert_recognizes({}, {:method => 'get', :path => "/projects/1/wiki/CookBook_documentation/whatever"}) + end end def test_wiki_misc diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/wikis_test.rb --- a/test/integration/routing/wikis_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/wikis_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/routing/workflows_test.rb --- a/test/integration/routing/workflows_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/routing/workflows_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/integration/users_test.rb --- a/test/integration/users_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/integration/users_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/object_helpers.rb --- a/test/object_helpers.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/object_helpers.rb Tue Sep 09 09:34:53 2014 +0100 @@ -3,7 +3,7 @@ @generated_user_login ||= 'user0' @generated_user_login.succ! user = User.new(attributes) - user.login = @generated_user_login if user.login.blank? + user.login = @generated_user_login.dup if user.login.blank? user.mail = "#{@generated_user_login}@example.com" if user.mail.blank? user.firstname = "Bob" if user.firstname.blank? user.lastname = "Doe" if user.lastname.blank? @@ -22,7 +22,7 @@ @generated_group_name ||= 'Group 0' @generated_group_name.succ! group = Group.new(attributes) - group.name = @generated_group_name if group.name.blank? + group.name = @generated_group_name.dup if group.name.blank? yield group if block_given? group.save! group @@ -32,18 +32,24 @@ @generated_project_identifier ||= 'project-0000' @generated_project_identifier.succ! project = Project.new(attributes) - project.name = @generated_project_identifier if project.name.blank? - project.identifier = @generated_project_identifier if project.identifier.blank? + project.name = @generated_project_identifier.dup if project.name.blank? + project.identifier = @generated_project_identifier.dup if project.identifier.blank? yield project if block_given? project.save! project end + def Project.generate_with_parent!(parent, attributes={}) + project = Project.generate!(attributes) + project.set_parent!(parent) + project + end + def Tracker.generate!(attributes={}) @generated_tracker_name ||= 'Tracker 0' @generated_tracker_name.succ! tracker = Tracker.new(attributes) - tracker.name = @generated_tracker_name if tracker.name.blank? + tracker.name = @generated_tracker_name.dup if tracker.name.blank? yield tracker if block_given? tracker.save! tracker @@ -53,19 +59,26 @@ @generated_role_name ||= 'Role 0' @generated_role_name.succ! role = Role.new(attributes) - role.name = @generated_role_name if role.name.blank? + role.name = @generated_role_name.dup if role.name.blank? yield role if block_given? role.save! role end - def Issue.generate!(attributes={}) + # Generates an unsaved Issue + def Issue.generate(attributes={}) issue = Issue.new(attributes) issue.project ||= Project.find(1) issue.tracker ||= issue.project.trackers.first issue.subject = 'Generated' if issue.subject.blank? issue.author ||= User.find(2) yield issue if block_given? + issue + end + + # Generates a saved Issue + def Issue.generate!(attributes={}, &block) + issue = Issue.generate(attributes, &block) issue.save! issue end @@ -92,17 +105,29 @@ @generated_version_name ||= 'Version 0' @generated_version_name.succ! version = Version.new(attributes) - version.name = @generated_version_name if version.name.blank? + version.name = @generated_version_name.dup if version.name.blank? yield version if block_given? version.save! version end + def TimeEntry.generate!(attributes={}) + entry = TimeEntry.new(attributes) + entry.user ||= User.find(2) + entry.issue ||= Issue.find(1) unless entry.project + entry.project ||= entry.issue.project + entry.activity ||= TimeEntryActivity.first + entry.spent_on ||= Date.today + entry.hours ||= 1.0 + entry.save! + entry + end + def AuthSource.generate!(attributes={}) @generated_auth_source_name ||= 'Auth 0' @generated_auth_source_name.succ! source = AuthSource.new(attributes) - source.name = @generated_auth_source_name if source.name.blank? + source.name = @generated_auth_source_name.dup if source.name.blank? yield source if block_given? source.save! source @@ -112,8 +137,8 @@ @generated_board_name ||= 'Forum 0' @generated_board_name.succ! board = Board.new(attributes) - board.name = @generated_board_name if board.name.blank? - board.description = @generated_board_name if board.description.blank? + board.name = @generated_board_name.dup if board.name.blank? + board.description = @generated_board_name.dup if board.description.blank? yield board if block_given? board.save! board @@ -126,8 +151,31 @@ attachment = Attachment.new(attributes) attachment.container ||= Issue.find(1) attachment.author ||= User.find(2) - attachment.filename = @generated_filename if attachment.filename.blank? + attachment.filename = @generated_filename.dup if attachment.filename.blank? attachment.save! attachment end + + def CustomField.generate!(attributes={}) + @generated_custom_field_name ||= 'Custom field 0' + @generated_custom_field_name.succ! + field = new(attributes) + field.name = @generated_custom_field_name.dup if field.name.blank? + field.field_format = 'string' if field.field_format.blank? + yield field if block_given? + field.save! + field + end + + def Changeset.generate!(attributes={}) + @generated_changeset_rev ||= '123456' + @generated_changeset_rev.succ! + changeset = new(attributes) + changeset.repository ||= Project.find(1).repository + changeset.revision ||= @generated_changeset_rev + changeset.committed_on ||= Time.now + yield changeset if block_given? + changeset.save! + changeset + end end diff -r d98d22a98252 -r afce8026aaeb test/test_helper.rb --- a/test/test_helper.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/test_helper.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,30 +26,10 @@ class ActiveSupport::TestCase include ActionDispatch::TestProcess - - # Transactional fixtures accelerate your tests by wrapping each test method - # in a transaction that's rolled back on completion. This ensures that the - # test database remains unchanged so your fixtures don't have to be reloaded - # between every test method. Fewer database queries means faster tests. - # - # Read Mike Clark's excellent walkthrough at - # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting - # - # Every Active Record database supports transactions except MyISAM tables - # in MySQL. Turn off transactional fixtures in this case; however, if you - # don't care one way or the other, switching from MyISAM to InnoDB tables - # is recommended. + self.use_transactional_fixtures = true - - # Instantiated fixtures are slow, but give you @david where otherwise you - # would need people(:david). If you don't want to migrate your existing - # test cases which use the @david style and don't mind the speed hit (each - # instantiated fixtures translates to a database query per test method), - # then set this back to true. self.use_instantiated_fixtures = false - # Add more helper methods to be used by all tests here... - def log_user(login, password) User.anonymous get "/login" @@ -107,7 +87,15 @@ end def with_settings(options, &block) - saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].is_a?(Symbol) ? Setting[k] : Setting[k].dup; h} + saved_settings = options.keys.inject({}) do |h, k| + h[k] = case Setting[k] + when Symbol, false, true, nil + Setting[k] + else + Setting[k].dup + end + h + end options.each {|k, v| Setting[k] = v} yield ensure @@ -123,8 +111,16 @@ User.current = saved_user end + def with_locale(locale, &block) + saved_localed = ::I18n.locale + ::I18n.locale = locale + yield + ensure + ::I18n.locale = saved_localed + end + def change_user_password(login, new_password) - user = User.first(:conditions => {:login => login}) + user = User.where(:login => login).first user.password, user.password_confirmation = new_password, new_password user.save! end @@ -181,8 +177,8 @@ assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"") end - def assert_not_include(expected, s) - assert !s.include?(expected), "\"#{expected}\" found in \"#{s}\"" + def assert_not_include(expected, s, message=nil) + assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"") end def assert_select_in(text, *args, &block) @@ -190,305 +186,291 @@ assert_select(d, *args, &block) end - def assert_mail_body_match(expected, mail) + def assert_mail_body_match(expected, mail, message=nil) if expected.is_a?(String) - assert_include expected, mail_body(mail) + assert_include expected, mail_body(mail), message else - assert_match expected, mail_body(mail) + assert_match expected, mail_body(mail), message end end - def assert_mail_body_no_match(expected, mail) + def assert_mail_body_no_match(expected, mail, message=nil) if expected.is_a?(String) - assert_not_include expected, mail_body(mail) + assert_not_include expected, mail_body(mail), message else - assert_no_match expected, mail_body(mail) + assert_no_match expected, mail_body(mail), message end end def mail_body(mail) mail.parts.first.body.encoded end +end - # Shoulda macros - def self.should_render_404 - should_respond_with :not_found - should_render_template 'common/error' - end - - def self.should_have_before_filter(expected_method, options = {}) - should_have_filter('before', expected_method, options) - end - - def self.should_have_after_filter(expected_method, options = {}) - should_have_filter('after', expected_method, options) - end - - def self.should_have_filter(filter_type, expected_method, options) - description = "have #{filter_type}_filter :#{expected_method}" - description << " with #{options.inspect}" unless options.empty? - - should description do - klass = "action_controller/filters/#{filter_type}_filter".classify.constantize - expected = klass.new(:filter, expected_method.to_sym, options) - assert_equal 1, @controller.class.filter_chain.select { |filter| - filter.method == expected.method && filter.kind == expected.kind && - filter.options == expected.options && filter.class == expected.class - }.size - end - end - - # Test that a request allows the three types of API authentication - # - # * HTTP Basic with username and password - # * HTTP Basic with an api key for the username - # * Key based with the key=X parameter - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_api_authentication(http_method, url, parameters={}, options={}) - should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options) - should_allow_http_basic_auth_with_key(http_method, url, parameters, options) - should_allow_key_based_auth(http_method, url, parameters, options) - end - - # Test that a request allows the username and password for HTTP BASIC - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow http basic auth using a username and password for #{http_method} #{url}" do - context "with a valid HTTP authentication" do - setup do - @user = User.generate! do |user| - user.admin = true - user.password = 'my_password' +module Redmine + module ApiTest + # Base class for API tests + class Base < ActionDispatch::IntegrationTest + # Test that a request allows the three types of API authentication + # + # * HTTP Basic with username and password + # * HTTP Basic with an api key for the username + # * Key based with the key=X parameter + # + # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) + # @param [String] url the request url + # @param [optional, Hash] parameters additional request parameters + # @param [optional, Hash] options additional options + # @option options [Symbol] :success_code Successful response code (:success) + # @option options [Symbol] :failure_code Failure response code (:unauthorized) + def self.should_allow_api_authentication(http_method, url, parameters={}, options={}) + should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options) + should_allow_http_basic_auth_with_key(http_method, url, parameters, options) + should_allow_key_based_auth(http_method, url, parameters, options) + end + + # Test that a request allows the username and password for HTTP BASIC + # + # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) + # @param [String] url the request url + # @param [optional, Hash] parameters additional request parameters + # @param [optional, Hash] options additional options + # @option options [Symbol] :success_code Successful response code (:success) + # @option options [Symbol] :failure_code Failure response code (:unauthorized) + def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={}) + success_code = options[:success_code] || :success + failure_code = options[:failure_code] || :unauthorized + + context "should allow http basic auth using a username and password for #{http_method} #{url}" do + context "with a valid HTTP authentication" do + setup do + @user = User.generate! do |user| + user.admin = true + user.password = 'my_password' + end + send(http_method, url, parameters, credentials(@user.login, 'my_password')) + end + + should_respond_with success_code + should_respond_with_content_type_based_on_url(url) + should "login as the user" do + assert_equal @user, User.current + end end - send(http_method, url, parameters, credentials(@user.login, 'my_password')) - end - - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current + + context "with an invalid HTTP authentication" do + setup do + @user = User.generate! + send(http_method, url, parameters, credentials(@user.login, 'wrong_password')) + end + + should_respond_with failure_code + should_respond_with_content_type_based_on_url(url) + should "not login as the user" do + assert_equal User.anonymous, User.current + end + end + + context "without credentials" do + setup do + send(http_method, url, parameters) + end + + should_respond_with failure_code + should_respond_with_content_type_based_on_url(url) + should "include_www_authenticate_header" do + assert @controller.response.headers.has_key?('WWW-Authenticate') + end + end end end - - context "with an invalid HTTP authentication" do - setup do - @user = User.generate! - send(http_method, url, parameters, credentials(@user.login, 'wrong_password')) - end - - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "not login as the user" do - assert_equal User.anonymous, User.current + + # Test that a request allows the API key with HTTP BASIC + # + # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) + # @param [String] url the request url + # @param [optional, Hash] parameters additional request parameters + # @param [optional, Hash] options additional options + # @option options [Symbol] :success_code Successful response code (:success) + # @option options [Symbol] :failure_code Failure response code (:unauthorized) + def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={}) + success_code = options[:success_code] || :success + failure_code = options[:failure_code] || :unauthorized + + context "should allow http basic auth with a key for #{http_method} #{url}" do + context "with a valid HTTP authentication using the API token" do + setup do + @user = User.generate! do |user| + user.admin = true + end + @token = Token.create!(:user => @user, :action => 'api') + send(http_method, url, parameters, credentials(@token.value, 'X')) + end + should_respond_with success_code + should_respond_with_content_type_based_on_url(url) + should_be_a_valid_response_string_based_on_url(url) + should "login as the user" do + assert_equal @user, User.current + end + end + + context "with an invalid HTTP authentication" do + setup do + @user = User.generate! + @token = Token.create!(:user => @user, :action => 'feeds') + send(http_method, url, parameters, credentials(@token.value, 'X')) + end + should_respond_with failure_code + should_respond_with_content_type_based_on_url(url) + should "not login as the user" do + assert_equal User.anonymous, User.current + end + end end end - - context "without credentials" do - setup do - send(http_method, url, parameters) + + # Test that a request allows full key authentication + # + # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) + # @param [String] url the request url, without the key=ZXY parameter + # @param [optional, Hash] parameters additional request parameters + # @param [optional, Hash] options additional options + # @option options [Symbol] :success_code Successful response code (:success) + # @option options [Symbol] :failure_code Failure response code (:unauthorized) + def self.should_allow_key_based_auth(http_method, url, parameters={}, options={}) + success_code = options[:success_code] || :success + failure_code = options[:failure_code] || :unauthorized + + context "should allow key based auth using key=X for #{http_method} #{url}" do + context "with a valid api token" do + setup do + @user = User.generate! do |user| + user.admin = true + end + @token = Token.create!(:user => @user, :action => 'api') + # Simple url parse to add on ?key= or &key= + request_url = if url.match(/\?/) + url + "&key=#{@token.value}" + else + url + "?key=#{@token.value}" + end + send(http_method, request_url, parameters) + end + should_respond_with success_code + should_respond_with_content_type_based_on_url(url) + should_be_a_valid_response_string_based_on_url(url) + should "login as the user" do + assert_equal @user, User.current + end + end + + context "with an invalid api token" do + setup do + @user = User.generate! do |user| + user.admin = true + end + @token = Token.create!(:user => @user, :action => 'feeds') + # Simple url parse to add on ?key= or &key= + request_url = if url.match(/\?/) + url + "&key=#{@token.value}" + else + url + "?key=#{@token.value}" + end + send(http_method, request_url, parameters) + end + should_respond_with failure_code + should_respond_with_content_type_based_on_url(url) + should "not login as the user" do + assert_equal User.anonymous, User.current + end + end end - - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "include_www_authenticate_header" do - assert @controller.response.headers.has_key?('WWW-Authenticate') + + context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do + setup do + @user = User.generate! do |user| + user.admin = true + end + @token = Token.create!(:user => @user, :action => 'api') + send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s}) + end + should_respond_with success_code + should_respond_with_content_type_based_on_url(url) + should_be_a_valid_response_string_based_on_url(url) + should "login as the user" do + assert_equal @user, User.current + end + end + end + + # Uses should_respond_with_content_type based on what's in the url: + # + # '/project/issues.xml' => should_respond_with_content_type :xml + # '/project/issues.json' => should_respond_with_content_type :json + # + # @param [String] url Request + def self.should_respond_with_content_type_based_on_url(url) + case + when url.match(/xml/i) + should "respond with XML" do + assert_equal 'application/xml', @response.content_type + end + when url.match(/json/i) + should "respond with JSON" do + assert_equal 'application/json', @response.content_type + end + else + raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}" + end + end + + # Uses the url to assert which format the response should be in + # + # '/project/issues.xml' => should_be_a_valid_xml_string + # '/project/issues.json' => should_be_a_valid_json_string + # + # @param [String] url Request + def self.should_be_a_valid_response_string_based_on_url(url) + case + when url.match(/xml/i) + should_be_a_valid_xml_string + when url.match(/json/i) + should_be_a_valid_json_string + else + raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}" + end + end + + # Checks that the response is a valid JSON string + def self.should_be_a_valid_json_string + should "be a valid JSON string (or empty)" do + assert(response.body.blank? || ActiveSupport::JSON.decode(response.body)) + end + end + + # Checks that the response is a valid XML string + def self.should_be_a_valid_xml_string + should "be a valid XML string" do + assert REXML::Document.new(response.body) + end + end + + def self.should_respond_with(status) + should "respond with #{status}" do + assert_response status end end end end +end - # Test that a request allows the API key with HTTP BASIC - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow http basic auth with a key for #{http_method} #{url}" do - context "with a valid HTTP authentication using the API token" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'api') - send(http_method, url, parameters, credentials(@token.value, 'X')) - end - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should_be_a_valid_response_string_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - - context "with an invalid HTTP authentication" do - setup do - @user = User.generate! - @token = Token.create!(:user => @user, :action => 'feeds') - send(http_method, url, parameters, credentials(@token.value, 'X')) - end - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - end - end - - # Test that a request allows full key authentication - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url, without the key=ZXY parameter - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_key_based_auth(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow key based auth using key=X for #{http_method} #{url}" do - context "with a valid api token" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'api') - # Simple url parse to add on ?key= or &key= - request_url = if url.match(/\?/) - url + "&key=#{@token.value}" - else - url + "?key=#{@token.value}" - end - send(http_method, request_url, parameters) - end - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should_be_a_valid_response_string_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - - context "with an invalid api token" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'feeds') - # Simple url parse to add on ?key= or &key= - request_url = if url.match(/\?/) - url + "&key=#{@token.value}" - else - url + "?key=#{@token.value}" - end - send(http_method, request_url, parameters) - end - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - end - - context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'api') - send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s}) - end - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should_be_a_valid_response_string_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - end - - # Uses should_respond_with_content_type based on what's in the url: - # - # '/project/issues.xml' => should_respond_with_content_type :xml - # '/project/issues.json' => should_respond_with_content_type :json - # - # @param [String] url Request - def self.should_respond_with_content_type_based_on_url(url) - case - when url.match(/xml/i) - should "respond with XML" do - assert_equal 'application/xml', @response.content_type - end - when url.match(/json/i) - should "respond with JSON" do - assert_equal 'application/json', @response.content_type - end - else - raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}" - end - end - - # Uses the url to assert which format the response should be in - # - # '/project/issues.xml' => should_be_a_valid_xml_string - # '/project/issues.json' => should_be_a_valid_json_string - # - # @param [String] url Request - def self.should_be_a_valid_response_string_based_on_url(url) - case - when url.match(/xml/i) - should_be_a_valid_xml_string - when url.match(/json/i) - should_be_a_valid_json_string - else - raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}" - end - end - - # Checks that the response is a valid JSON string - def self.should_be_a_valid_json_string - should "be a valid JSON string (or empty)" do - assert(response.body.blank? || ActiveSupport::JSON.decode(response.body)) - end - end - - # Checks that the response is a valid XML string - def self.should_be_a_valid_xml_string - should "be a valid XML string" do - assert REXML::Document.new(response.body) - end - end - - def self.should_respond_with(status) - should "respond with #{status}" do - assert_response status - end +# URL helpers do not work with config.threadsafe! +# https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454 +ActionView::TestCase::TestController.instance_eval do + helper Rails.application.routes.url_helpers +end +ActionView::TestCase::TestController.class_eval do + def _routes + Rails.application.routes end end - -# Simple module to "namespace" all of the API tests -module ApiTest -end diff -r d98d22a98252 -r afce8026aaeb test/ui/base.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/ui/base.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,72 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) +require 'capybara/rails' + +Capybara.default_driver = :selenium +Capybara.register_driver :selenium do |app| + # Use the following driver definition to test locally using Chrome + # (also requires chromedriver to be in PATH) + # Capybara::Selenium::Driver.new(app, :browser => :chrome) + # Add :switches => %w[--lang=en] to force default browser locale to English + # Default for Selenium remote driver is to connect to local host on port 4444 + # This can be change using :url => 'http://localhost:9195' if necessary + # PhantomJS 1.8 now directly supports Webdriver Wire API, + # simply run it with `phantomjs --webdriver 4444` + # Add :desired_capabilities => Selenium::WebDriver::Remote::Capabilities.internet_explorer) + # to run on Selenium Grid Hub with IE + Capybara::Selenium::Driver.new(app, :browser => :remote) +end + +# default: 2 +Capybara.default_wait_time = 2 + +DatabaseCleaner.strategy = :truncation + +module Redmine + module UiTest + # Base class for UI tests + class Base < ActionDispatch::IntegrationTest + include Capybara::DSL + + # Stop ActiveRecord from wrapping tests in transactions + # Transactional fixtures do not work with Selenium tests, because Capybara + # uses a separate server thread, which the transactions would be hidden + self.use_transactional_fixtures = false + + # Should not depend on locale since Redmine displays login page + # using default browser locale which depend on system locale for "real" browsers drivers + def log_user(login, password) + visit '/my/page' + assert_equal '/login', current_path + within('#login-form form') do + fill_in 'username', :with => login + fill_in 'password', :with => password + find('input[name=login]').click + end + assert_equal '/my/page', current_path + end + + teardown do + Capybara.reset_sessions! # Forget the (simulated) browser state + Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver + DatabaseCleaner.clean + end + end + end +end diff -r d98d22a98252 -r afce8026aaeb test/ui/issues_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/ui/issues_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,264 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../base', __FILE__) + +class Redmine::UiTest::IssuesTest < Redmine::UiTest::Base + fixtures :projects, :users, :roles, :members, :member_roles, + :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues, + :enumerations, :custom_fields, :custom_values, :custom_fields_trackers, + :watchers + + def test_create_issue + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + within('form#issue-form') do + select 'Bug', :from => 'Tracker' + select 'Low', :from => 'Priority' + fill_in 'Subject', :with => 'new test issue' + fill_in 'Description', :with => 'new issue' + select '0 %', :from => 'Done' + fill_in 'Due date', :with => '' + fill_in 'Searchable field', :with => 'Value for field 2' + # click_button 'Create' would match both 'Create' and 'Create and continue' buttons + find('input[name=commit]').click + end + + # find created issue + issue = Issue.find_by_subject("new test issue") + assert_kind_of Issue, issue + + # check redirection + find 'div#flash_notice', :visible => true, :text => "Issue \##{issue.id} created." + assert_equal issue_path(:id => issue), current_path + + # check issue attributes + assert_equal 'jsmith', issue.author.login + assert_equal 1, issue.project.id + assert_equal IssueStatus.find_by_name('New'), issue.status + assert_equal Tracker.find_by_name('Bug'), issue.tracker + assert_equal IssuePriority.find_by_name('Low'), issue.priority + assert_equal 'Value for field 2', issue.custom_field_value(CustomField.find_by_name('Searchable field')) + end + + def test_create_issue_with_form_update + field1 = IssueCustomField.create!( + :field_format => 'string', + :name => 'Field1', + :is_for_all => true, + :trackers => Tracker.find_all_by_id([1, 2]) + ) + field2 = IssueCustomField.create!( + :field_format => 'string', + :name => 'Field2', + :is_for_all => true, + :trackers => Tracker.find_all_by_id(2) + ) + + Role.non_member.add_permission! :add_issues + Role.non_member.remove_permission! :edit_issues, :add_issue_notes + + log_user('someone', 'foo') + visit '/projects/ecookbook/issues/new' + assert page.has_no_content?(field2.name) + assert page.has_content?(field1.name) + + fill_in 'Subject', :with => 'New test issue' + fill_in 'Description', :with => 'New test issue description' + fill_in field1.name, :with => 'CF1 value' + select 'Low', :from => 'Priority' + + # field2 should show up when changing tracker + select 'Feature request', :from => 'Tracker' + assert page.has_content?(field2.name) + assert page.has_content?(field1.name) + + fill_in field2.name, :with => 'CF2 value' + assert_difference 'Issue.count' do + page.first(:button, 'Create').click + end + + issue = Issue.order('id desc').first + assert_equal 'New test issue', issue.subject + assert_equal 'New test issue description', issue.description + assert_equal 'Low', issue.priority.name + assert_equal 'CF1 value', issue.custom_field_value(field1) + assert_equal 'CF2 value', issue.custom_field_value(field2) + end + + def test_create_issue_with_watchers + user = User.generate!(:firstname => 'Some', :lastname => 'Watcher') + assert_equal 'Some Watcher', user.name + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + fill_in 'Subject', :with => 'Issue with watchers' + # Add a project member as watcher + check 'Dave Lopper' + # Search for another user + assert page.has_no_css?('form#new-watcher-form') + assert page.has_no_content?('Some Watcher') + click_link 'Search for watchers to add' + within('form#new-watcher-form') do + assert page.has_content?('Some One') + fill_in 'user_search', :with => 'watch' + assert page.has_no_content?('Some One') + check 'Some Watcher' + click_button 'Add' + end + assert page.has_css?('form#issue-form') + assert page.has_css?('p#watchers_form') + using_wait_time(30) do + within('span#watchers_inputs') do + within("label#issue_watcher_user_ids_#{user.id}") do + assert has_content?('Some Watcher'), "No watcher content" + end + end + end + assert_difference 'Issue.count' do + find('input[name=commit]').click + end + + issue = Issue.order('id desc').first + assert_equal ['Dave Lopper', 'Some Watcher'], issue.watcher_users.map(&:name).sort + end + + def test_create_issue_start_due_date + with_settings :default_issue_start_date_to_creation_date => 0 do + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + assert_equal "", page.find('input#issue_start_date').value + assert_equal "", page.find('input#issue_due_date').value + page.first('p#start_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal Date.today.to_s, page.find('input#issue_start_date').value + page.first('p#due_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal Date.today.to_s, page.find('input#issue_due_date').value + end + end + + def test_create_issue_start_due_date_default + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + fill_in 'Start date', :with => '2012-04-01' + fill_in 'Due date', :with => '' + page.first('p#due_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal '2012-04-01', page.find('input#issue_due_date').value + + fill_in 'Start date', :with => '' + fill_in 'Due date', :with => '2012-04-01' + page.first('p#start_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal '2012-04-01', page.find('input#issue_start_date').value + end + + def test_preview_issue_description + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + within('form#issue-form') do + fill_in 'Subject', :with => 'new issue subject' + fill_in 'Description', :with => 'new issue description' + click_link 'Preview' + end + find 'div#preview fieldset', :visible => true, :text => 'new issue description' + assert_difference 'Issue.count' do + find('input[name=commit]').click + end + + issue = Issue.order('id desc').first + assert_equal 'new issue description', issue.description + end + + def test_update_issue_with_form_update + field = IssueCustomField.create!( + :field_format => 'string', + :name => 'Form update CF', + :is_for_all => true, + :trackers => Tracker.find_all_by_name('Feature request') + ) + + Role.non_member.add_permission! :edit_issues + Role.non_member.remove_permission! :add_issues, :add_issue_notes + + log_user('someone', 'foo') + visit '/issues/1' + assert page.has_no_content?('Form update CF') + + page.first(:link, 'Update').click + # the custom field should show up when changing tracker + select 'Feature request', :from => 'Tracker' + assert page.has_content?('Form update CF') + + fill_in 'Form update', :with => 'CF value' + assert_no_difference 'Issue.count' do + page.first(:button, 'Submit').click + end + + issue = Issue.find(1) + assert_equal 'CF value', issue.custom_field_value(field) + end + + def test_remove_issue_watcher_from_sidebar + user = User.find(3) + Watcher.create!(:watchable => Issue.find(1), :user => user) + + log_user('jsmith', 'jsmith') + visit '/issues/1' + assert page.first('#sidebar').has_content?('Watchers (1)') + assert page.first('#sidebar').has_content?(user.name) + assert_difference 'Watcher.count', -1 do + page.first('ul.watchers .user-3 a.delete').click + assert page.first('#sidebar').has_content?('Watchers (0)') + end + assert page.first('#sidebar').has_no_content?(user.name) + end + + def test_watch_issue_via_context_menu + log_user('jsmith', 'jsmith') + visit '/issues' + assert page.has_css?('tr#issue-1') + find('tr#issue-1 td.updated_on').click + page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');" + assert_difference 'Watcher.count' do + within('#context-menu') do + click_link 'Watch' + end + assert page.has_css?('tr#issue-1') + end + assert Issue.find(1).watched_by?(User.find_by_login('jsmith')) + end + + def test_bulk_watch_issues_via_context_menu + log_user('jsmith', 'jsmith') + visit '/issues' + assert page.has_css?('tr#issue-1') + assert page.has_css?('tr#issue-4') + find('tr#issue-1 input[type=checkbox]').click + find('tr#issue-4 input[type=checkbox]').click + page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');" + assert_difference 'Watcher.count', 2 do + within('#context-menu') do + click_link 'Watch' + end + assert page.has_css?('tr#issue-1') + assert page.has_css?('tr#issue-4') + end + assert Issue.find(1).watched_by?(User.find_by_login('jsmith')) + assert Issue.find(4).watched_by?(User.find_by_login('jsmith')) + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/activity_test.rb --- a/test/unit/activity_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/activity_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,7 +19,8 @@ class ActivityTest < ActiveSupport::TestCase fixtures :projects, :versions, :attachments, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, - :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages + :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :time_entries, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions def setup @project = Project.find(1) @@ -87,6 +88,39 @@ assert_equal %w(Project Version), events.collect(&:container_type).uniq.sort end + def test_event_group_for_issue + issue = Issue.find(1) + assert_equal issue, issue.event_group + end + + def test_event_group_for_journal + issue = Issue.find(1) + journal = issue.journals.first + assert_equal issue, journal.event_group + end + + def test_event_group_for_issue_time_entry + time = TimeEntry.where(:issue_id => 1).first + assert_equal time.issue, time.event_group + end + + def test_event_group_for_project_time_entry + time = TimeEntry.where(:issue_id => nil).first + assert_equal time, time.event_group + end + + def test_event_group_for_message + message = Message.find(1) + reply = message.children.first + assert_equal message, message.event_group + assert_equal message, reply.event_group + end + + def test_event_group_for_wiki_content_version + content = WikiContent::Version.find(1) + assert_equal content.page, content.event_group + end + private def find_events(user, options={}) diff -r d98d22a98252 -r afce8026aaeb test/unit/attachment_test.rb --- a/test/unit/attachment_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/attachment_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -42,6 +42,13 @@ assert_nil Attachment.new.container end + def test_filename_should_remove_eols + assert_equal "line_feed", Attachment.new(:filename => "line\nfeed").filename + assert_equal "line_feed", Attachment.new(:filename => "some\npath/line\nfeed").filename + assert_equal "carriage_return", Attachment.new(:filename => "carriage\rreturn").filename + assert_equal "carriage_return", Attachment.new(:filename => "some\rpath/carriage\rreturn").filename + end + def test_create a = Attachment.new(:container => Issue.find(1), :file => uploaded_test_file("testfile.txt", "text/plain"), @@ -52,10 +59,25 @@ assert_equal 'text/plain', a.content_type assert_equal 0, a.downloads assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest + + assert a.disk_directory + assert_match %r{\A\d{4}/\d{2}\z}, a.disk_directory + assert File.exist?(a.diskfile) assert_equal 59, File.size(a.diskfile) end + def test_copy_should_preserve_attributes + a = Attachment.find(1) + copy = a.copy + + assert_save copy + copy = Attachment.order('id DESC').first + %w(filename filesize content_type author_id created_on description digest disk_filename disk_directory diskfile).each do |attribute| + assert_equal a.send(attribute), copy.send(attribute), "#{attribute} was different" + end + end + def test_size_should_be_validated_for_new_file with_settings :attachment_max_size => 0 do a = Attachment.new(:container => Issue.find(1), @@ -78,7 +100,7 @@ def test_description_length_should_be_validated a = Attachment.new(:description => 'a' * 300) assert !a.save - assert_not_nil a.errors[:description] + assert_not_equal [], a.errors[:description] end def test_destroy @@ -168,42 +190,55 @@ end end - context "Attachmnet.attach_files" do - should "attach the file" do - issue = Issue.first - assert_difference 'Attachment.count' do - Attachment.attach_files(issue, - '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain'), - 'description' => 'test' - }) - end + def test_move_from_root_to_target_directory_should_move_root_files + a = Attachment.find(20) + assert a.disk_directory.blank? + # Create a real file for this fixture + File.open(a.diskfile, "w") do |f| + f.write "test file at the root of files directory" + end + assert a.readable? + Attachment.move_from_root_to_target_directory - attachment = Attachment.first(:order => 'id DESC') - assert_equal issue, attachment.container - assert_equal 'testfile.txt', attachment.filename - assert_equal 59, attachment.filesize - assert_equal 'test', attachment.description - assert_equal 'text/plain', attachment.content_type - assert File.exists?(attachment.diskfile) - assert_equal 59, File.size(attachment.diskfile) + a.reload + assert_equal '2012/05', a.disk_directory + assert a.readable? + end + + test "Attachmnet.attach_files should attach the file" do + issue = Issue.first + assert_difference 'Attachment.count' do + Attachment.attach_files(issue, + '1' => { + 'file' => uploaded_test_file('testfile.txt', 'text/plain'), + 'description' => 'test' + }) end - should "add unsaved files to the object as unsaved attachments" do - # Max size of 0 to force Attachment creation failures - with_settings(:attachment_max_size => 0) do - @project = Project.find(1) - response = Attachment.attach_files(@project, { - '1' => {'file' => mock_file, 'description' => 'test'}, - '2' => {'file' => mock_file, 'description' => 'test'} - }) + attachment = Attachment.first(:order => 'id DESC') + assert_equal issue, attachment.container + assert_equal 'testfile.txt', attachment.filename + assert_equal 59, attachment.filesize + assert_equal 'test', attachment.description + assert_equal 'text/plain', attachment.content_type + assert File.exists?(attachment.diskfile) + assert_equal 59, File.size(attachment.diskfile) + end - assert response[:unsaved].present? - assert_equal 2, response[:unsaved].length - assert response[:unsaved].first.new_record? - assert response[:unsaved].second.new_record? - assert_equal response[:unsaved], @project.unsaved_attachments - end + test "Attachmnet.attach_files should add unsaved files to the object as unsaved attachments" do + # Max size of 0 to force Attachment creation failures + with_settings(:attachment_max_size => 0) do + @project = Project.find(1) + response = Attachment.attach_files(@project, { + '1' => {'file' => mock_file, 'description' => 'test'}, + '2' => {'file' => mock_file, 'description' => 'test'} + }) + + assert response[:unsaved].present? + assert_equal 2, response[:unsaved].length + assert response[:unsaved].first.new_record? + assert response[:unsaved].second.new_record? + assert_equal response[:unsaved], @project.unsaved_attachments end end diff -r d98d22a98252 -r afce8026aaeb test/unit/auth_source_ldap_test.rb --- a/test/unit/auth_source_ldap_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/auth_source_ldap_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -58,61 +58,48 @@ end if ldap_configured? - context '#authenticate' do - setup do - @auth = AuthSourceLdap.find(1) - @auth.update_attribute :onthefly_register, true + test '#authenticate with a valid LDAP user should return the user attributes' do + auth = AuthSourceLdap.find(1) + auth.update_attribute :onthefly_register, true + + attributes = auth.authenticate('example1','123456') + assert attributes.is_a?(Hash), "An hash was not returned" + assert_equal 'Example', attributes[:firstname] + assert_equal 'One', attributes[:lastname] + assert_equal 'example1@redmine.org', attributes[:mail] + assert_equal auth.id, attributes[:auth_source_id] + attributes.keys.each do |attribute| + assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" end + end - context 'with a valid LDAP user' do - should 'return the user attributes' do - attributes = @auth.authenticate('example1','123456') - assert attributes.is_a?(Hash), "An hash was not returned" - assert_equal 'Example', attributes[:firstname] - assert_equal 'One', attributes[:lastname] - assert_equal 'example1@redmine.org', attributes[:mail] - assert_equal @auth.id, attributes[:auth_source_id] - attributes.keys.each do |attribute| - assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" - end - end - end + test '#authenticate with an invalid LDAP user should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('nouser','123456') + end - context 'with an invalid LDAP user' do - should 'return nil' do - assert_equal nil, @auth.authenticate('nouser','123456') - end - end + test '#authenticate without a login should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('','123456') + end - context 'without a login' do - should 'return nil' do - assert_equal nil, @auth.authenticate('','123456') - end - end + test '#authenticate without a password should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('edavis','') + end - context 'without a password' do - should 'return nil' do - assert_equal nil, @auth.authenticate('edavis','') - end - end + test '#authenticate without filter should return any user' do + auth = AuthSourceLdap.find(1) + assert auth.authenticate('example1','123456') + assert auth.authenticate('edavis', '123456') + end - context 'without filter' do - should 'return any user' do - assert @auth.authenticate('example1','123456') - assert @auth.authenticate('edavis', '123456') - end - end + test '#authenticate with filter should return user who matches the filter only' do + auth = AuthSourceLdap.find(1) + auth.filter = "(mail=*@redmine.org)" - context 'with filter' do - setup do - @auth.filter = "(mail=*@redmine.org)" - end - - should 'return user who matches the filter only' do - assert @auth.authenticate('example1','123456') - assert_nil @auth.authenticate('edavis', '123456') - end - end + assert auth.authenticate('example1','123456') + assert_nil auth.authenticate('edavis', '123456') end def test_authenticate_should_timeout @@ -124,6 +111,30 @@ auth_source.authenticate 'example1', '123456' end end + + def test_search_should_return_matching_entries + results = AuthSource.search("exa") + assert_equal 1, results.size + result = results.first + assert_kind_of Hash, result + assert_equal "example1", result[:login] + assert_equal "Example", result[:firstname] + assert_equal "One", result[:lastname] + assert_equal "example1@redmine.org", result[:mail] + assert_equal 1, result[:auth_source_id] + end + + def test_search_with_no_match_should_return_an_empty_array + results = AuthSource.search("wro") + assert_equal [], results + end + + def test_search_with_exception_should_return_an_empty_array + Net::LDAP.stubs(:new).raises(Net::LDAP::LdapError, 'Cannot connect') + + results = AuthSource.search("exa") + assert_equal [], results + end else puts '(Test LDAP server not configured)' end diff -r d98d22a98252 -r afce8026aaeb test/unit/board_test.rb --- a/test/unit/board_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/board_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -100,7 +100,7 @@ end end end - assert_equal 0, Message.count(:conditions => {:board_id => 1}) + assert_equal 0, Message.where(:board_id => 1).count end def test_destroy_should_nullify_children diff -r d98d22a98252 -r afce8026aaeb test/unit/changeset_test.rb --- a/test/unit/changeset_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/changeset_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,11 +30,8 @@ def test_ref_keywords_any ActionMailer::Base.deliveries.clear - Setting.commit_fix_status_id = IssueStatus.find( - :first, :conditions => ["is_closed = ?", true]).id - Setting.commit_fix_done_ratio = '90' Setting.commit_ref_keywords = '*' - Setting.commit_fix_keywords = 'fixes , closes' + Setting.commit_update_keywords = [{'keywords' => 'fixes , closes', 'status_id' => '5', 'done_ratio' => '90'}] c = Changeset.new(:repository => Project.find(1).repository, :committed_on => Time.now, @@ -50,7 +47,7 @@ def test_ref_keywords Setting.commit_ref_keywords = 'refs' - Setting.commit_fix_keywords = '' + Setting.commit_update_keywords = '' c = Changeset.new(:repository => Project.find(1).repository, :committed_on => Time.now, :comments => 'Ignores #2. Refs #1', @@ -61,7 +58,7 @@ def test_ref_keywords_any_only Setting.commit_ref_keywords = '*' - Setting.commit_fix_keywords = '' + Setting.commit_update_keywords = '' c = Changeset.new(:repository => Project.find(1).repository, :committed_on => Time.now, :comments => 'Ignores #2. Refs #1', @@ -113,10 +110,8 @@ end def test_ref_keywords_closing_with_timelog - Setting.commit_fix_status_id = IssueStatus.find( - :first, :conditions => ["is_closed = ?", true]).id Setting.commit_ref_keywords = '*' - Setting.commit_fix_keywords = 'fixes , closes' + Setting.commit_update_keywords = [{'keywords' => 'fixes , closes', 'status_id' => IssueStatus.where(:is_closed => true).first.id.to_s}] Setting.commit_logtime_enabled = '1' c = Changeset.new(:repository => Project.find(1).repository, @@ -165,6 +160,48 @@ assert_equal [1,2,3], c.issue_ids.sort end + def test_update_keywords_with_multiple_rules + with_settings :commit_update_keywords => [ + {'keywords' => 'fixes, closes', 'status_id' => '5'}, + {'keywords' => 'resolves', 'status_id' => '3'} + ] do + + issue1 = Issue.generate! + issue2 = Issue.generate! + Changeset.generate!(:comments => "Closes ##{issue1.id}\nResolves ##{issue2.id}") + assert_equal 5, issue1.reload.status_id + assert_equal 3, issue2.reload.status_id + end + end + + def test_update_keywords_with_multiple_rules_should_match_tracker + with_settings :commit_update_keywords => [ + {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'}, + {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => ''} + ] do + + issue1 = Issue.generate!(:tracker_id => 2) + issue2 = Issue.generate! + Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}") + assert_equal 5, issue1.reload.status_id + assert_equal 3, issue2.reload.status_id + end + end + + def test_update_keywords_with_multiple_rules_and_no_match + with_settings :commit_update_keywords => [ + {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'}, + {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => '3'} + ] do + + issue1 = Issue.generate!(:tracker_id => 2) + issue2 = Issue.generate! + Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}") + assert_equal 5, issue1.reload.status_id + assert_equal 1, issue2.reload.status_id # no updates + end + end + def test_commit_referencing_a_subproject_issue c = Changeset.new(:repository => Project.find(1).repository, :committed_on => Time.now, @@ -176,7 +213,7 @@ end def test_commit_closing_a_subproject_issue - with_settings :commit_fix_status_id => 5, :commit_fix_keywords => 'closes', + with_settings :commit_update_keywords => [{'keywords' => 'closes', 'status_id' => '5'}], :default_language => 'en' do issue = Issue.find(5) assert !issue.closed? @@ -238,6 +275,28 @@ end end + def test_old_commits_should_not_update_issues_nor_log_time + Setting.commit_ref_keywords = '*' + Setting.commit_update_keywords = {'fixes , closes' => {'status_id' => '5', 'done_ratio' => '90'}} + Setting.commit_logtime_enabled = '1' + + repository = Project.find(1).repository + repository.created_on = Time.now + repository.save! + + c = Changeset.new(:repository => repository, + :committed_on => 1.month.ago, + :comments => 'New commit (#2). Fixes #1 @1h', + :revision => '12345') + assert_no_difference 'TimeEntry.count' do + assert c.save + end + assert_equal [1, 2], c.issue_ids.sort + issue = Issue.find(1) + assert_equal 1, issue.status_id + assert_equal 0, issue.done_ratio + end + def test_text_tag_revision c = Changeset.new(:revision => '520') assert_equal 'r520', c.text_tag @@ -286,6 +345,16 @@ assert_equal 'commit:0123456789', c.text_tag end + def test_text_tag_hash_with_repository_identifier + r = Repository::Subversion.new( + :project_id => 1, + :url => 'svn://localhost/test', + :identifier => 'documents') + c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => r) + assert_equal 'commit:documents|7234cb27', c.text_tag + assert_equal 'ecookbook:commit:documents|7234cb27', c.text_tag(Project.find(2)) + end + def test_previous changeset = Changeset.find_by_revision('3') assert_equal Changeset.find_by_revision('2'), changeset.previous diff -r d98d22a98252 -r afce8026aaeb test/unit/comment_test.rb --- a/test/unit/comment_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/comment_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/custom_field_test.rb --- a/test/unit/custom_field_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/custom_field_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -57,6 +57,20 @@ assert field.valid? end + def test_field_format_should_be_validated + field = CustomField.new(:name => 'Test', :field_format => 'foo') + assert !field.valid? + end + + def test_field_format_validation_should_accept_formats_added_at_runtime + Redmine::CustomFieldFormat.register 'foobar' + + field = CustomField.new(:name => 'Some Custom Field', :field_format => 'foobar') + assert field.valid?, 'field should be valid' + ensure + Redmine::CustomFieldFormat.delete 'foobar' + end + def test_should_not_change_field_format_of_existing_custom_field field = CustomField.find(1) field.field_format = 'int' @@ -132,6 +146,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?('a' * 2) assert !f.valid_field_value?('a') assert !f.valid_field_value?('a' * 6) @@ -142,6 +157,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?('ABC') assert !f.valid_field_value?('abc') end @@ -151,6 +167,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?('1975-07-14') assert !f.valid_field_value?('1975-07-33') assert !f.valid_field_value?('abc') @@ -161,6 +178,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?('value2') assert !f.valid_field_value?('abc') end @@ -170,6 +188,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?('123') assert f.valid_field_value?('+123') assert f.valid_field_value?('-123') @@ -181,6 +200,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?('11.2') assert f.valid_field_value?('-6.250') assert f.valid_field_value?('5') @@ -192,9 +212,11 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?([]) assert f.valid_field_value?([nil]) assert f.valid_field_value?(['']) + assert !f.valid_field_value?([' ']) assert f.valid_field_value?('value2') assert !f.valid_field_value?('abc') @@ -209,6 +231,24 @@ assert !f.valid_field_value?(['value1', 'abc']) end + def test_changing_multiple_to_false_should_delete_multiple_values + field = ProjectCustomField.create!(:name => 'field', :field_format => 'list', :multiple => 'true', :possible_values => ['field1', 'field2']) + other = ProjectCustomField.create!(:name => 'other', :field_format => 'list', :multiple => 'true', :possible_values => ['other1', 'other2']) + + item_with_multiple_values = Project.generate!(:custom_field_values => {field.id => ['field1', 'field2'], other.id => ['other1', 'other2']}) + item_with_single_values = Project.generate!(:custom_field_values => {field.id => ['field1'], other.id => ['other2']}) + + assert_difference 'CustomValue.count', -1 do + field.multiple = false + field.save! + end + + item_with_multiple_values = Project.find(item_with_multiple_values.id) + assert_kind_of String, item_with_multiple_values.custom_field_value(field) + assert_kind_of Array, item_with_multiple_values.custom_field_value(other) + assert_equal 2, item_with_multiple_values.custom_field_value(other).size + end + def test_value_class_should_return_the_class_used_for_fields_values assert_equal User, CustomField.new(:field_format => 'user').value_class assert_equal Version, CustomField.new(:field_format => 'version').value_class @@ -223,4 +263,42 @@ field = CustomField.find(1) assert_equal 'PostgreSQL', field.value_from_keyword('postgresql', Issue.find(1)) end + + def test_visibile_scope_with_admin_should_return_all_custom_fields + CustomField.delete_all + fields = [ + CustomField.generate!(:visible => true), + CustomField.generate!(:visible => false), + CustomField.generate!(:visible => false, :role_ids => [1, 3]), + CustomField.generate!(:visible => false, :role_ids => [1, 2]), + ] + + assert_equal 4, CustomField.visible(User.find(1)).count + end + + def test_visibile_scope_with_non_admin_user_should_return_visible_custom_fields + CustomField.delete_all + fields = [ + CustomField.generate!(:visible => true), + CustomField.generate!(:visible => false), + CustomField.generate!(:visible => false, :role_ids => [1, 3]), + CustomField.generate!(:visible => false, :role_ids => [1, 2]), + ] + user = User.generate! + User.add_to_project(user, Project.first, Role.find(3)) + + assert_equal [fields[0], fields[2]], CustomField.visible(user).order("id").to_a + end + + def test_visibile_scope_with_anonymous_user_should_return_visible_custom_fields + CustomField.delete_all + fields = [ + CustomField.generate!(:visible => true), + CustomField.generate!(:visible => false), + CustomField.generate!(:visible => false, :role_ids => [1, 3]), + CustomField.generate!(:visible => false, :role_ids => [1, 2]), + ] + + assert_equal [fields[0]], CustomField.visible(User.anonymous).order("id").to_a + end end diff -r d98d22a98252 -r afce8026aaeb test/unit/custom_field_user_format_test.rb --- a/test/unit/custom_field_user_format_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/custom_field_user_format_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/custom_field_version_format_test.rb --- a/test/unit/custom_field_version_format_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/custom_field_version_format_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/custom_value_test.rb --- a/test/unit/custom_value_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/custom_value_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/default_data_test.rb --- a/test/unit/default_data_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/default_data_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/document_category_test.rb --- a/test/unit/document_category_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/document_category_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -34,14 +34,14 @@ end def test_default - assert_nil DocumentCategory.find(:first, :conditions => { :is_default => true }) + assert_nil DocumentCategory.where(:is_default => true).first e = Enumeration.find_by_name('Technical documentation') e.update_attributes(:is_default => true) assert_equal 3, DocumentCategory.default.id end def test_force_default - assert_nil DocumentCategory.find(:first, :conditions => { :is_default => true }) + assert_nil DocumentCategory.where(:is_default => true).first assert_equal 1, DocumentCategory.default.id end end diff -r d98d22a98252 -r afce8026aaeb test/unit/document_test.rb --- a/test/unit/document_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/document_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/enabled_module_test.rb --- a/test/unit/enabled_module_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/enabled_module_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/enumeration_test.rb --- a/test/unit/enumeration_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/enumeration_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -86,7 +86,7 @@ def test_destroy_with_reassign Enumeration.find(4).destroy(Enumeration.find(6)) - assert_nil Issue.find(:first, :conditions => {:priority_id => 4}) + assert_nil Issue.where(:priority_id => 4).first assert_equal 6, Enumeration.find(6).objects_count end diff -r d98d22a98252 -r afce8026aaeb test/unit/group_test.rb --- a/test/unit/group_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/group_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,13 +19,11 @@ class GroupTest < ActiveSupport::TestCase fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, + :enumerations, :users, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows, :groups_users include Redmine::I18n @@ -37,6 +35,14 @@ assert_equal 'New group', g.name end + def test_name_should_accept_255_characters + name = 'a' * 255 + g = Group.new(:name => name) + assert g.save + g.reload + assert_equal name, g.name + end + def test_blank_name_error_message set_language_if_valid 'en' g = Group.new diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/activities_helper_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/helpers/activities_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,102 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class ActivitiesHelperTest < ActionView::TestCase + include ActivitiesHelper + include Redmine::I18n + + class MockEvent + attr_reader :event_datetime, :event_group, :name + + def initialize(group=nil) + @@count ||= 0 + @name = "e#{@@count}" + @event_datetime = Time.now + @@count.hours + @event_group = group || self + @@count += 1 + end + + def self.clear + @@count = 0 + end + end + + def setup + MockEvent.clear + end + + def test_sort_activity_events_should_sort_by_datetime + events = [] + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new + + assert_equal [ + ['e2', false], + ['e1', false], + ['e0', false] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_should_group_events + events = [] + events << MockEvent.new + events << MockEvent.new(events[0]) + events << MockEvent.new(events[0]) + + assert_equal [ + ['e2', false], + ['e1', true], + ['e0', true] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_with_group_not_in_set_should_group_events + e = MockEvent.new + events = [] + events << MockEvent.new(e) + events << MockEvent.new(e) + + assert_equal [ + ['e2', false], + ['e1', true] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_should_sort_by_datetime_and_group + events = [] + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new(events[1]) + events << MockEvent.new(events[2]) + events << MockEvent.new + events << MockEvent.new(events[2]) + + assert_equal [ + ['e6', false], + ['e4', true], + ['e2', true], + ['e5', false], + ['e3', false], + ['e1', true], + ['e0', false] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/application_helper_test.rb --- a/test/unit/helpers/application_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/helpers/application_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,7 +20,9 @@ require File.expand_path('../../../test_helper', __FILE__) class ApplicationHelperTest < ActionView::TestCase + include Redmine::I18n include ERB::Util + include Rails.application.routes.url_helpers fixtures :projects, :roles, :enabled_modules, :users, :repositories, :changesets, @@ -32,26 +34,30 @@ def setup super set_tmp_attachments_directory + @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82" + if @russian_test.respond_to?(:force_encoding) + @russian_test.force_encoding('UTF-8') + end end - context "#link_to_if_authorized" do - context "authorized user" do - should "be tested" - end + test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do + User.current = User.find_by_login('admin') - context "unauthorized user" do - should "be tested" - end + @project = Issue.first.project # Used by helper + response = link_to_if_authorized('By controller/actionr', + {:controller => 'issues', :action => 'edit', :id => Issue.first.id}) + assert_match /href/, response + end - should "allow using the :controller and :action for the target link" do - User.current = User.find_by_login('admin') + test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do + User.current = User.find_by_login('dlopper') + @project = Project.find('private-child') + issue = @project.issues.first + assert !issue.visible? - @project = Issue.first.project # Used by helper - response = link_to_if_authorized("By controller/action", - {:controller => 'issues', :action => 'edit', :id => Issue.first.id}) - assert_match /href/, response - end - + response = link_to_if_authorized('Never displayed', + {:controller => 'issues', :action => 'show', :id => issue}) + assert_nil response end def test_auto_links @@ -83,7 +89,11 @@ # escaping 'http://foo"bar' => 'http://foo"bar', # wrap in angle brackets - '' => '<http://foo.bar>' + '' => '<http://foo.bar>', + # invalid urls + 'http://' => 'http://', + 'www.' => 'www.', + 'test-www.bar.com' => 'test-www.bar.com', } to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text) } end @@ -91,7 +101,8 @@ if 'ruby'.respond_to?(:encoding) def test_auto_links_with_non_ascii_characters to_test = { - 'http://foo.bar/теÑÑ‚' => 'http://foo.bar/теÑÑ‚' + "http://foo.bar/#{@russian_test}" => + %|http://foo.bar/#{@russian_test}| } to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text) } end @@ -100,8 +111,11 @@ end def test_auto_mailto - assert_equal '

      ', - textilizable('test@foo.bar') + to_test = { + 'test@foo.bar' => '', + 'test@www.foo.bar' => '', + } + to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text) } end def test_inline_images @@ -131,14 +145,14 @@ def test_attached_images to_test = { - 'Inline image: !logo.gif!' => 'Inline image: This is a logo', - 'Inline image: !logo.GIF!' => 'Inline image: This is a logo', + 'Inline image: !logo.gif!' => 'Inline image: This is a logo', + 'Inline image: !logo.GIF!' => 'Inline image: This is a logo', 'No match: !ogo.gif!' => 'No match: ', 'No match: !ogo.GIF!' => 'No match: ', # link image - '!logo.gif!:http://foo.bar/' => 'This is a logo', + '!logo.gif!:http://foo.bar/' => 'This is a logo', } - attachments = Attachment.find(:all) + attachments = Attachment.all to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text, :attachments => attachments) } end @@ -182,13 +196,13 @@ to_test = { 'Inline image: !testtest.jpg!' => - 'Inline image: ', + 'Inline image: ', 'Inline image: !testtest.jpeg!' => - 'Inline image: ', + 'Inline image: ', 'Inline image: !testtest.jpe!' => - 'Inline image: ', + 'Inline image: ', 'Inline image: !testtest.bmp!' => - 'Inline image: ', + 'Inline image: ', } attachments = [a1, a2, a3, a4] @@ -211,9 +225,9 @@ to_test = { 'Inline image: !testfile.png!' => - 'Inline image: ', + 'Inline image: ', 'Inline image: !Testfile.PNG!' => - 'Inline image: ', + 'Inline image: ', } attachments = [a1, a2] to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text, :attachments => attachments) } @@ -242,7 +256,8 @@ if 'ruby'.respond_to?(:encoding) def test_textile_external_links_with_non_ascii_characters to_test = { - 'This is a "link":http://foo.bar/теÑÑ‚' => 'This is a link' + %|This is a "link":http://foo.bar/#{@russian_test}| => + %|This is a link| } to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text) } end @@ -252,15 +267,21 @@ def test_redmine_links issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3}, - :class => 'issue status-1 priority-4 priority-lowest overdue', :title => 'Error 281 when updating a recipe (New)') - note_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'}, - :class => 'issue status-1 priority-4 priority-lowest overdue', :title => 'Error 281 when updating a recipe (New)') + :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)') + note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'}, + :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)') + note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'}, + :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)') - changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, - :class => 'changeset', :title => 'My very first commit') - changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, + revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, + :class => 'changeset', :title => 'My very first commit do not escaping #<>&') + revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') + changeset_link2 = link_to('691322a8eb01e11fd7', + {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, + :class => 'changeset', :title => 'My very first commit do not escaping #<>&') + document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1}, :class => 'document') @@ -279,25 +300,28 @@ source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file' source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext' source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext' + source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file' export_url = '/projects/ecookbook/repository/raw/some/file' export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file' export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext' export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext' + export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file' to_test = { # tickets '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.", # ticket notes '#3-14' => note_link, - '#3#note-14' => note_link, + '#3#note-14' => note_link2, # should not ignore leading zero '#03' => '#03', # changesets - 'r1' => changeset_link, - 'r1.' => "#{changeset_link}.", - 'r1, r2' => "#{changeset_link}, #{changeset_link2}", - 'r1,r2' => "#{changeset_link},#{changeset_link2}", + 'r1' => revision_link, + 'r1.' => "#{revision_link}.", + 'r1, r2' => "#{revision_link}, #{revision_link2}", + 'r1,r2' => "#{revision_link},#{revision_link2}", + 'commit:691322a8eb01e11fd7' => changeset_link2, # documents 'document#1' => document_link, 'document:"Test document"' => document_link, @@ -314,6 +338,7 @@ 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".", 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",", 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'), + 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'), 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'), 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'), 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'), @@ -323,6 +348,7 @@ 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'), 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'), 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'), + 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'), # forum 'forum#2' => link_to('Discussion', board_url, :class => 'board'), 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'), @@ -553,9 +579,8 @@ end def test_attachment_links - attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment') to_test = { - 'attachment:error281.txt' => attachment_link + 'attachment:error281.txt' => 'error281.txt' } to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" } end @@ -565,11 +590,12 @@ a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago) a2 = Attachment.generate!(:filename => "test.txt") - assert_equal %(

      test.txt

      ), + assert_equal %(

      test.txt

      ), textilizable('attachment:test.txt', :attachments => [a1, a2]) end def test_wiki_links + russian_eacape = CGI.escape(@russian_test) to_test = { '[[CookBook documentation]]' => 'CookBook documentation', '[[Another page|Page]]' => 'Page', @@ -580,7 +606,8 @@ '[[CookBook documentation#One-section]]' => 'CookBook documentation', '[[Another page#anchor|Page]]' => 'Page', # UTF8 anchor - '[[Another_page#ТеÑÑ‚|ТеÑÑ‚]]' => %|ТеÑÑ‚|, + "[[Another_page##{@russian_test}|#{@russian_test}]]" => + %|#{@russian_test}|, # page that doesn't exist '[[Unknown page]]' => 'Unknown page', '[[Unknown page|404]]' => '404', @@ -741,7 +768,7 @@ expected = <<-EXPECTED

      CookBook documentation

      -

      #1

      +

      #1

       [[CookBook documentation]]
       
      @@ -989,14 +1016,14 @@
           result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
       
           # heading that contains inline code
      -    assert_match Regexp.new('
      ' + + assert_match Regexp.new('
      ' + 'Edit
      ' + '' + '

      Subtitle with inline code

      '), result # last heading - assert_match Regexp.new('
      ' + + assert_match Regexp.new('
      ' + 'Edit
      ' + '' + '

      Subtitle after pre tag

      '), @@ -1076,6 +1103,26 @@ assert_equal ::I18n.t(:label_user_anonymous), t end + def test_link_to_attachment + a = Attachment.find(3) + assert_equal 'logo.gif', + link_to_attachment(a) + assert_equal 'Text', + link_to_attachment(a, :text => 'Text') + assert_equal 'logo.gif', + link_to_attachment(a, :class => 'foo') + assert_equal 'logo.gif', + link_to_attachment(a, :download => true) + assert_equal 'logo.gif', + link_to_attachment(a, :only_path => false) + end + + def test_thumbnail_tag + a = Attachment.find(3) + assert_equal '3', + thumbnail_tag(a) + end + def test_link_to_project project = Project.find(1) assert_equal %(eCookbook), @@ -1088,6 +1135,17 @@ link_to_project(project, {:action => 'settings'}, :class => "project") end + def test_link_to_project_settings + project = Project.find(1) + assert_equal 'eCookbook', link_to_project_settings(project) + + project.status = Project::STATUS_CLOSED + assert_equal 'eCookbook', link_to_project_settings(project) + + project.status = Project::STATUS_ARCHIVED + assert_equal 'eCookbook', link_to_project_settings(project) + end + def test_link_to_legacy_project_with_numerical_identifier_should_use_id # numeric identifier are no longer allowed Project.update_all "identifier=25", "id=1" @@ -1164,18 +1222,47 @@ assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo) end - def test_per_page_links_should_show_usefull_values - set_language_if_valid 'en' - stubs(:link_to).returns("[link]") + def test_raw_json_should_escape_closing_tags + s = raw_json(["bar"]) + assert_equal '["bar<\/foo>"]', s + end - with_settings :per_page_options => '10, 25, 50, 100' do - assert_nil per_page_links(10, 3) - assert_nil per_page_links(25, 3) - assert_equal "Per page: 10, [link]", per_page_links(10, 22) - assert_equal "Per page: [link], 25", per_page_links(25, 22) - assert_equal "Per page: [link], [link], 50", per_page_links(50, 22) - assert_equal "Per page: [link], 25, [link]", per_page_links(25, 26) - assert_equal "Per page: [link], 25, [link], [link]", per_page_links(25, 120) - end + def test_raw_json_should_be_html_safe + s = raw_json(["foo"]) + assert s.html_safe? + end + + def test_html_title_should_app_title_if_not_set + assert_equal 'Redmine', html_title + end + + def test_html_title_should_join_items + html_title 'Foo', 'Bar' + assert_equal 'Foo - Bar - Redmine', html_title + end + + def test_html_title_should_append_current_project_name + @project = Project.find(1) + html_title 'Foo', 'Bar' + assert_equal 'Foo - Bar - eCookbook - Redmine', html_title + end + + def test_title_should_return_a_h2_tag + assert_equal '

      Foo

      ', title('Foo') + end + + def test_title_should_set_html_title + title('Foo') + assert_equal 'Foo - Redmine', html_title + end + + def test_title_should_turn_arrays_into_links + assert_equal '

      Foo

      ', title(['Foo', '/foo']) + assert_equal 'Foo - Redmine', html_title + end + + def test_title_should_join_items + assert_equal '

      Foo » Bar

      ', title('Foo', 'Bar') + assert_equal 'Bar - Foo - Redmine', html_title end end diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/custom_fields_helper_test.rb --- a/test/unit/helpers/custom_fields_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/helpers/custom_fields_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -41,7 +41,7 @@ field = CustomField.new(:field_format => 'foo') field.id = 52 - assert_equal '', + assert_include '', custom_field_tag_for_bulk_edit('object', field) end end diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/issues_helper_test.rb --- a/test/unit/helpers/issues_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/helpers/issues_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,6 +19,7 @@ class IssuesHelperTest < ActionView::TestCase include ApplicationHelper + include Redmine::I18n include IssuesHelper include CustomFieldsHelper include ERB::Util @@ -30,7 +31,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :custom_fields, :attachments, :versions @@ -46,182 +46,226 @@ end def test_issues_destroy_confirmation_message_with_one_root_issue - assert_equal l(:text_issues_destroy_confirmation), issues_destroy_confirmation_message(Issue.find(1)) + assert_equal l(:text_issues_destroy_confirmation), + issues_destroy_confirmation_message(Issue.find(1)) end def test_issues_destroy_confirmation_message_with_an_arrayt_of_root_issues - assert_equal l(:text_issues_destroy_confirmation), issues_destroy_confirmation_message(Issue.find([1, 2])) + assert_equal l(:text_issues_destroy_confirmation), + issues_destroy_confirmation_message(Issue.find([1, 2])) end def test_issues_destroy_confirmation_message_with_one_parent_issue Issue.find(2).update_attribute :parent_issue_id, 1 - assert_equal l(:text_issues_destroy_confirmation) + "\n" + l(:text_issues_destroy_descendants_confirmation, :count => 1), - issues_destroy_confirmation_message(Issue.find(1)) + assert_equal l(:text_issues_destroy_confirmation) + "\n" + + l(:text_issues_destroy_descendants_confirmation, :count => 1), + issues_destroy_confirmation_message(Issue.find(1)) end def test_issues_destroy_confirmation_message_with_one_parent_issue_and_its_child Issue.find(2).update_attribute :parent_issue_id, 1 - assert_equal l(:text_issues_destroy_confirmation), issues_destroy_confirmation_message(Issue.find([1, 2])) + assert_equal l(:text_issues_destroy_confirmation), + issues_destroy_confirmation_message(Issue.find([1, 2])) end - context "IssuesHelper#show_detail" do - context "with no_html" do - should 'show a changing attribute' do - @detail = JournalDetail.new(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio') - assert_equal "% Done changed from 40 to 100", show_detail(@detail, true) - end + test 'show_detail with no_html should show a changing attribute' do + detail = JournalDetail.new(:property => 'attr', :old_value => '40', + :value => '100', :prop_key => 'done_ratio') + assert_equal "% Done changed from 40 to 100", show_detail(detail, true) + end - should 'show a new attribute' do - @detail = JournalDetail.new(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio') - assert_equal "% Done set to 100", show_detail(@detail, true) - end + test 'show_detail with no_html should show a new attribute' do + detail = JournalDetail.new(:property => 'attr', :old_value => nil, + :value => '100', :prop_key => 'done_ratio') + assert_equal "% Done set to 100", show_detail(detail, true) + end - should 'show a deleted attribute' do - @detail = JournalDetail.new(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio') - assert_equal "% Done deleted (50)", show_detail(@detail, true) - end - end + test 'show_detail with no_html should show a deleted attribute' do + detail = JournalDetail.new(:property => 'attr', :old_value => '50', + :value => nil, :prop_key => 'done_ratio') + assert_equal "% Done deleted (50)", show_detail(detail, true) + end - context "with html" do - should 'show a changing attribute with HTML highlights' do - @detail = JournalDetail.new(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio') - html = show_detail(@detail, false) + test 'show_detail with html should show a changing attribute with HTML highlights' do + detail = JournalDetail.new(:property => 'attr', :old_value => '40', + :value => '100', :prop_key => 'done_ratio') + html = show_detail(detail, false) + assert_include '% Done', html + assert_include '40', html + assert_include '100', html + end - assert_include '% Done', html - assert_include '40', html - assert_include '100', html - end + test 'show_detail with html should show a new attribute with HTML highlights' do + detail = JournalDetail.new(:property => 'attr', :old_value => nil, + :value => '100', :prop_key => 'done_ratio') + html = show_detail(detail, false) + assert_include '% Done', html + assert_include '100', html + end - should 'show a new attribute with HTML highlights' do - @detail = JournalDetail.new(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio') - html = show_detail(@detail, false) + test 'show_detail with html should show a deleted attribute with HTML highlights' do + detail = JournalDetail.new(:property => 'attr', :old_value => '50', + :value => nil, :prop_key => 'done_ratio') + html = show_detail(detail, false) + assert_include '% Done', html + assert_include '50', html + end - assert_include '% Done', html - assert_include '100', html - end - - should 'show a deleted attribute with HTML highlights' do - @detail = JournalDetail.new(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio') - html = show_detail(@detail, false) - - assert_include '% Done', html - assert_include '50', html - end - end - - context "with a start_date attribute" do - should "format the current date" do - @detail = JournalDetail.new( - :property => 'attr', - :old_value => '2010-01-01', - :value => '2010-01-31', - :prop_key => 'start_date' - ) - with_settings :date_format => '%m/%d/%Y' do - assert_match "01/31/2010", show_detail(@detail, true) - end - end - - should "format the old date" do - @detail = JournalDetail.new( - :property => 'attr', - :old_value => '2010-01-01', - :value => '2010-01-31', - :prop_key => 'start_date' - ) - with_settings :date_format => '%m/%d/%Y' do - assert_match "01/01/2010", show_detail(@detail, true) - end - end - end - - context "with a due_date attribute" do - should "format the current date" do - @detail = JournalDetail.new( - :property => 'attr', - :old_value => '2010-01-01', - :value => '2010-01-31', - :prop_key => 'due_date' - ) - with_settings :date_format => '%m/%d/%Y' do - assert_match "01/31/2010", show_detail(@detail, true) - end - end - - should "format the old date" do - @detail = JournalDetail.new( - :property => 'attr', - :old_value => '2010-01-01', - :value => '2010-01-31', - :prop_key => 'due_date' - ) - with_settings :date_format => '%m/%d/%Y' do - assert_match "01/01/2010", show_detail(@detail, true) - end - end - end - - should "show old and new values with a project attribute" do - detail = JournalDetail.new(:property => 'attr', :prop_key => 'project_id', :old_value => 1, :value => 2) - assert_match 'eCookbook', show_detail(detail, true) - assert_match 'OnlineStore', show_detail(detail, true) - end - - should "show old and new values with a issue status attribute" do - detail = JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :old_value => 1, :value => 2) - assert_match 'New', show_detail(detail, true) - assert_match 'Assigned', show_detail(detail, true) - end - - should "show old and new values with a tracker attribute" do - detail = JournalDetail.new(:property => 'attr', :prop_key => 'tracker_id', :old_value => 1, :value => 2) - assert_match 'Bug', show_detail(detail, true) - assert_match 'Feature request', show_detail(detail, true) - end - - should "show old and new values with a assigned to attribute" do - detail = JournalDetail.new(:property => 'attr', :prop_key => 'assigned_to_id', :old_value => 1, :value => 2) - assert_match 'redMine Admin', show_detail(detail, true) - assert_match 'John Smith', show_detail(detail, true) - end - - should "show old and new values with a priority attribute" do - detail = JournalDetail.new(:property => 'attr', :prop_key => 'priority_id', :old_value => 4, :value => 5) - assert_match 'Low', show_detail(detail, true) - assert_match 'Normal', show_detail(detail, true) - end - - should "show old and new values with a category attribute" do - detail = JournalDetail.new(:property => 'attr', :prop_key => 'category_id', :old_value => 1, :value => 2) - assert_match 'Printing', show_detail(detail, true) - assert_match 'Recipes', show_detail(detail, true) - end - - should "show old and new values with a fixed version attribute" do - detail = JournalDetail.new(:property => 'attr', :prop_key => 'fixed_version_id', :old_value => 1, :value => 2) - assert_match '0.1', show_detail(detail, true) - assert_match '1.0', show_detail(detail, true) - end - - should "show old and new values with a estimated hours attribute" do - detail = JournalDetail.new(:property => 'attr', :prop_key => 'estimated_hours', :old_value => '5', :value => '6.3') - assert_match '5.00', show_detail(detail, true) - assert_match '6.30', show_detail(detail, true) - end - - should "show old and new values with a custom field" do - detail = JournalDetail.new(:property => 'cf', :prop_key => '1', :old_value => 'MySQL', :value => 'PostgreSQL') - assert_equal 'Database changed from MySQL to PostgreSQL', show_detail(detail, true) - end - - should "show added file" do - detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', :old_value => nil, :value => 'error281.txt') - assert_match 'error281.txt', show_detail(detail, true) - end - - should "show removed file" do - detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', :old_value => 'error281.txt', :value => nil) - assert_match 'error281.txt', show_detail(detail, true) + test 'show_detail with a start_date attribute should format the dates' do + detail = JournalDetail.new( + :property => 'attr', + :old_value => '2010-01-01', + :value => '2010-01-31', + :prop_key => 'start_date' + ) + with_settings :date_format => '%m/%d/%Y' do + assert_match "01/31/2010", show_detail(detail, true) + assert_match "01/01/2010", show_detail(detail, true) end end + + test 'show_detail with a due_date attribute should format the dates' do + detail = JournalDetail.new( + :property => 'attr', + :old_value => '2010-01-01', + :value => '2010-01-31', + :prop_key => 'due_date' + ) + with_settings :date_format => '%m/%d/%Y' do + assert_match "01/31/2010", show_detail(detail, true) + assert_match "01/01/2010", show_detail(detail, true) + end + end + + test 'show_detail should show old and new values with a project attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'project_id', + :old_value => 1, :value => 2) + assert_match 'eCookbook', show_detail(detail, true) + assert_match 'OnlineStore', show_detail(detail, true) + end + + test 'show_detail should show old and new values with a issue status attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'status_id', + :old_value => 1, :value => 2) + assert_match 'New', show_detail(detail, true) + assert_match 'Assigned', show_detail(detail, true) + end + + test 'show_detail should show old and new values with a tracker attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'tracker_id', + :old_value => 1, :value => 2) + assert_match 'Bug', show_detail(detail, true) + assert_match 'Feature request', show_detail(detail, true) + end + + test 'show_detail should show old and new values with a assigned to attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'assigned_to_id', + :old_value => 1, :value => 2) + assert_match 'Redmine Admin', show_detail(detail, true) + assert_match 'John Smith', show_detail(detail, true) + end + + test 'show_detail should show old and new values with a priority attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'priority_id', + :old_value => 4, :value => 5) + assert_match 'Low', show_detail(detail, true) + assert_match 'Normal', show_detail(detail, true) + end + + test 'show_detail should show old and new values with a category attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'category_id', + :old_value => 1, :value => 2) + assert_match 'Printing', show_detail(detail, true) + assert_match 'Recipes', show_detail(detail, true) + end + + test 'show_detail should show old and new values with a fixed version attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'fixed_version_id', + :old_value => 1, :value => 2) + assert_match '0.1', show_detail(detail, true) + assert_match '1.0', show_detail(detail, true) + end + + test 'show_detail should show old and new values with a estimated hours attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'estimated_hours', + :old_value => '5', :value => '6.3') + assert_match '5.00', show_detail(detail, true) + assert_match '6.30', show_detail(detail, true) + end + + test 'show_detail should show old and new values with a custom field' do + detail = JournalDetail.new(:property => 'cf', :prop_key => '1', + :old_value => 'MySQL', :value => 'PostgreSQL') + assert_equal 'Database changed from MySQL to PostgreSQL', show_detail(detail, true) + end + + test 'show_detail should show added file' do + detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', + :old_value => nil, :value => 'error281.txt') + assert_match 'error281.txt', show_detail(detail, true) + end + + test 'show_detail should show removed file' do + detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', + :old_value => 'error281.txt', :value => nil) + assert_match 'error281.txt', show_detail(detail, true) + end + + def test_show_detail_relation_added + detail = JournalDetail.new(:property => 'relation', + :prop_key => 'label_precedes', + :value => 1) + assert_equal "Precedes Bug #1: Can't print recipes added", show_detail(detail, true) + assert_match %r{Precedes Bug #1: Can't print recipes added}, + show_detail(detail, false) + end + + def test_show_detail_relation_added_with_inexistant_issue + inexistant_issue_number = 9999 + assert_nil Issue.find_by_id(inexistant_issue_number) + detail = JournalDetail.new(:property => 'relation', + :prop_key => 'label_precedes', + :value => inexistant_issue_number) + assert_equal "Precedes Issue ##{inexistant_issue_number} added", show_detail(detail, true) + assert_equal "Precedes Issue ##{inexistant_issue_number} added", show_detail(detail, false) + end + + def test_show_detail_relation_added_should_not_disclose_issue_that_is_not_visible + issue = Issue.generate!(:is_private => true) + detail = JournalDetail.new(:property => 'relation', + :prop_key => 'label_precedes', + :value => issue.id) + + assert_equal "Precedes Issue ##{issue.id} added", show_detail(detail, true) + assert_equal "Precedes Issue ##{issue.id} added", show_detail(detail, false) + end + + def test_show_detail_relation_deleted + detail = JournalDetail.new(:property => 'relation', + :prop_key => 'label_precedes', + :old_value => 1) + assert_equal "Precedes deleted (Bug #1: Can't print recipes)", show_detail(detail, true) + assert_match %r{Precedes deleted \(Bug #1: Can't print recipes\)}, + show_detail(detail, false) + end + + def test_show_detail_relation_deleted_with_inexistant_issue + inexistant_issue_number = 9999 + assert_nil Issue.find_by_id(inexistant_issue_number) + detail = JournalDetail.new(:property => 'relation', + :prop_key => 'label_precedes', + :old_value => inexistant_issue_number) + assert_equal "Precedes deleted (Issue #9999)", show_detail(detail, true) + assert_equal "Precedes deleted (Issue #9999)", show_detail(detail, false) + end + + def test_show_detail_relation_deleted_should_not_disclose_issue_that_is_not_visible + issue = Issue.generate!(:is_private => true) + detail = JournalDetail.new(:property => 'relation', + :prop_key => 'label_precedes', + :old_value => issue.id) + + assert_equal "Precedes deleted (Issue ##{issue.id})", show_detail(detail, true) + assert_equal "Precedes deleted (Issue ##{issue.id})", show_detail(detail, false) + end end diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/projects_helper_test.rb --- a/test/unit/helpers/projects_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/helpers/projects_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,6 +20,7 @@ class ProjectsHelperTest < ActionView::TestCase include ApplicationHelper include ProjectsHelper + include Redmine::I18n include ERB::Util fixtures :projects, :trackers, :issue_statuses, :issues, @@ -29,8 +30,7 @@ :member_roles, :members, :groups_users, - :enabled_modules, - :workflows + :enabled_modules def setup super diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/queries_helper_test.rb --- a/test/unit/helpers/queries_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/helpers/queries_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -29,37 +29,25 @@ :projects_trackers, :custom_fields_trackers - def test_order - User.current = User.find_by_login('admin') - query = Query.new(:project => nil, :name => '_') - assert_equal 30, query.available_filters.size + def test_filters_options_has_empty_item + query = IssueQuery.new + filter_count = query.available_filters.size fo = filters_options(query) - assert_equal 31, fo.size + assert_equal filter_count + 1, fo.size assert_equal [], fo[0] - assert_equal "status_id", fo[1][1] - assert_equal "project_id", fo[2][1] - assert_equal "tracker_id", fo[3][1] - assert_equal "priority_id", fo[4][1] - assert_equal "watcher_id", fo[17][1] - assert_equal "is_private", fo[18][1] end - def test_order_custom_fields - set_language_if_valid 'en' - field = UserCustomField.new( - :name => 'order test', :field_format => 'string', - :is_for_all => true, :is_filter => true - ) - assert field.save - User.current = User.find_by_login('admin') - query = Query.new(:project => nil, :name => '_') - assert_equal 32, query.available_filters.size - fo = filters_options(query) - assert_equal 33, fo.size - assert_equal "Searchable field", fo[19][0] - assert_equal "Database", fo[20][0] - assert_equal "Project's Development status", fo[21][0] - assert_equal "Assignee's order test", fo[22][0] - assert_equal "Author's order test", fo[23][0] + def test_query_to_csv_should_translate_boolean_custom_field_values + f = IssueCustomField.generate!(:field_format => 'bool', :name => 'Boolean', :is_for_all => true, :trackers => Tracker.all) + issues = [ + Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '0'}), + Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '1'}) + ] + + with_locale 'fr' do + csv = query_to_csv(issues, IssueQuery.new, :columns => 'all') + assert_include "Oui", csv + assert_include "Non", csv + end end end diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/search_helper_test.rb --- a/test/unit/helpers/search_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/helpers/search_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -21,6 +21,7 @@ class SearchHelperTest < ActionView::TestCase include SearchHelper + include Redmine::I18n include ERB::Util def test_highlight_single_token diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/sort_helper_test.rb --- a/test/unit/helpers/sort_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/helpers/sort_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,6 +19,7 @@ class SortHelperTest < ActionView::TestCase include SortHelper + include Redmine::I18n include ERB::Util def setup @@ -30,21 +31,21 @@ sort_init 'attr1', 'desc' sort_update(['attr1', 'attr2']) - assert_equal 'attr1 DESC', sort_clause + assert_equal ['attr1 DESC'], sort_clause end def test_default_sort_clause_with_hash sort_init 'attr1', 'desc' sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'}) - assert_equal 'table1.attr1 DESC', sort_clause + assert_equal ['table1.attr1 DESC'], sort_clause end def test_default_sort_clause_with_multiple_columns sort_init 'attr1', 'desc' sort_update({'attr1' => ['table1.attr1', 'table1.attr2'], 'attr2' => 'table2.attr2'}) - assert_equal 'table1.attr1 DESC, table1.attr2 DESC', sort_clause + assert_equal ['table1.attr1 DESC', 'table1.attr2 DESC'], sort_clause end def test_params_sort @@ -53,7 +54,7 @@ sort_init 'attr1', 'desc' sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'}) - assert_equal 'table1.attr1, table2.attr2 DESC', sort_clause + assert_equal ['table1.attr1', 'table2.attr2 DESC'], sort_clause assert_equal 'attr1,attr2:desc', @session['foo_bar_sort'] end @@ -63,7 +64,7 @@ sort_init 'attr1', 'desc' sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'}) - assert_equal 'table1.attr1 DESC', sort_clause + assert_equal ['table1.attr1 DESC'], sort_clause assert_equal 'attr1:desc', @session['foo_bar_sort'] end @@ -73,7 +74,7 @@ sort_init 'attr1', 'desc' sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'}) - assert_equal 'table1.attr1, table2.attr2', sort_clause + assert_equal ['table1.attr1', 'table2.attr2'], sort_clause assert_equal 'attr1,attr2', @session['foo_bar_sort'] end diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/timelog_helper_test.rb --- a/test/unit/helpers/timelog_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/helpers/timelog_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,6 +19,7 @@ class TimelogHelperTest < ActionView::TestCase include TimelogHelper + include Redmine::I18n include ActionView::Helpers::TextHelper include ActionView::Helpers::DateHelper include ERB::Util diff -r d98d22a98252 -r afce8026aaeb test/unit/helpers/watchers_helper_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/helpers/watchers_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,69 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../test_helper', __FILE__) + +class WatchersHelperTest < ActionView::TestCase + include WatchersHelper + include Redmine::I18n + + fixtures :users, :issues + + def setup + super + set_language_if_valid('en') + User.current = nil + end + + test '#watcher_link with a non-watched object' do + expected = link_to( + "Watch", + "/watchers/watch?object_id=1&object_type=issue", + :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off" + ) + assert_equal expected, watcher_link(Issue.find(1), User.find(1)) + end + + test '#watcher_link with a single objet array' do + expected = link_to( + "Watch", + "/watchers/watch?object_id=1&object_type=issue", + :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off" + ) + assert_equal expected, watcher_link([Issue.find(1)], User.find(1)) + end + + test '#watcher_link with a multiple objets array' do + expected = link_to( + "Watch", + "/watchers/watch?object_id%5B%5D=1&object_id%5B%5D=3&object_type=issue", + :remote => true, :method => 'post', :class => "issue-bulk-watcher icon icon-fav-off" + ) + assert_equal expected, watcher_link([Issue.find(1), Issue.find(3)], User.find(1)) + end + + test '#watcher_link with a watched object' do + Watcher.create!(:watchable => Issue.find(1), :user => User.find(1)) + + expected = link_to( + "Unwatch", + "/watchers/watch?object_id=1&object_type=issue", + :remote => true, :method => 'delete', :class => "issue-1-watcher icon icon-fav" + ) + assert_equal expected, watcher_link(Issue.find(1), User.find(1)) + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/initializers/patches_test.rb --- a/test/unit/initializers/patches_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/initializers/patches_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,21 +20,19 @@ class PatchesTest < ActiveSupport::TestCase include Redmine::I18n - context "ActiveRecord::Base.human_attribute_name" do - setup do - Setting.default_language = 'en' - end + def setup + Setting.default_language = 'en' + end - should "transform name to field_name" do - assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on') - end + test "ActiveRecord::Base.human_attribute_name should transform name to field_name" do + assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on') + end - should "cut extra _id suffix for better validation" do - assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on_id') - end + test "ActiveRecord::Base.human_attribute_name should cut extra _id suffix for better validation" do + assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on_id') + end - should "default to humanized value if no translation has been found (useful for custom fields)" do - assert_equal 'Patch name', ActiveRecord::Base.human_attribute_name('Patch name') - end + test "ActiveRecord::Base.human_attribute_name should default to humanized value if no translation has been found (useful for custom fields)" do + assert_equal 'Patch name', ActiveRecord::Base.human_attribute_name('Patch name') end end diff -r d98d22a98252 -r afce8026aaeb test/unit/issue_category_test.rb --- a/test/unit/issue_category_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/issue_category_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/issue_custom_field_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/issue_custom_field_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,42 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class IssueCustomFieldTest < ActiveSupport::TestCase + include Redmine::I18n + + fixtures :roles + + def test_custom_field_with_visible_set_to_false_should_validate_roles + set_language_if_valid 'en' + field = IssueCustomField.new(:name => 'Field', :field_format => 'string', :visible => false) + assert !field.save + assert_include "Roles can't be blank", field.errors.full_messages + field.role_ids = [1, 2] + assert field.save + end + + def test_changing_visible_to_true_should_clear_roles + field = IssueCustomField.create!(:name => 'Field', :field_format => 'string', :visible => false, :role_ids => [1, 2]) + assert_equal 2, field.roles.count + + field.visible = true + field.save! + assert_equal 0, field.roles.count + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/issue_nested_set_test.rb --- a/test/unit/issue_nested_set_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/issue_nested_set_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,14 +18,11 @@ require File.expand_path('../../test_helper', __FILE__) class IssueNestedSetTest < ActiveSupport::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, + fixtures :projects, :users, :roles, :trackers, :projects_trackers, - :versions, - :issue_statuses, :issue_categories, :issue_relations, :workflows, + :issue_statuses, :issue_categories, :issue_relations, :enumerations, - :issues, - :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, - :time_entries + :issues def test_create_root_issue issue1 = Issue.generate! @@ -60,7 +57,7 @@ child = Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1, :subject => 'child', :parent_issue_id => issue.id) assert !child.save - assert_not_nil child.errors[:parent_issue_id] + assert_not_equal [], child.errors[:parent_issue_id] end def test_move_a_root_to_child @@ -166,23 +163,42 @@ child.reload child.parent_issue_id = grandchild.id assert !child.save - assert_not_nil child.errors[:parent_issue_id] + assert_not_equal [], child.errors[:parent_issue_id] end - def test_moving_an_issue_should_keep_valid_relations_only - issue1 = Issue.generate! - issue2 = Issue.generate! - issue3 = Issue.generate!(:parent_issue_id => issue2.id) - issue4 = Issue.generate! - r1 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES) - r2 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue3, :relation_type => IssueRelation::TYPE_PRECEDES) - r3 = IssueRelation.create!(:issue_from => issue2, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES) - issue2.reload - issue2.parent_issue_id = issue1.id - issue2.save! - assert !IssueRelation.exists?(r1.id) - assert !IssueRelation.exists?(r2.id) - assert IssueRelation.exists?(r3.id) + def test_updating_a_root_issue_should_not_trigger_update_nested_set_attributes_on_parent_change + issue = Issue.find(Issue.generate!.id) + issue.parent_issue_id = "" + issue.expects(:update_nested_set_attributes_on_parent_change).never + issue.save! + end + + def test_updating_a_child_issue_should_not_trigger_update_nested_set_attributes_on_parent_change + issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id) + issue.parent_issue_id = "1" + issue.expects(:update_nested_set_attributes_on_parent_change).never + issue.save! + end + + def test_moving_a_root_issue_should_trigger_update_nested_set_attributes_on_parent_change + issue = Issue.find(Issue.generate!.id) + issue.parent_issue_id = "1" + issue.expects(:update_nested_set_attributes_on_parent_change).once + issue.save! + end + + def test_moving_a_child_issue_to_another_parent_should_trigger_update_nested_set_attributes_on_parent_change + issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id) + issue.parent_issue_id = "2" + issue.expects(:update_nested_set_attributes_on_parent_change).once + issue.save! + end + + def test_moving_a_child_issue_to_root_should_trigger_update_nested_set_attributes_on_parent_change + issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id) + issue.parent_issue_id = "" + issue.expects(:update_nested_set_attributes_on_parent_change).once + issue.save! end def test_destroy_should_destroy_children @@ -321,6 +337,17 @@ assert_equal (50 * 20 + 20 * 10) / 30, parent.reload.done_ratio end + def test_parent_done_ratio_with_child_estimate_to_0_should_reach_100 + parent = Issue.generate! + issue1 = Issue.generate!(:parent_issue_id => parent.id) + issue2 = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 0) + assert_equal 0, parent.reload.done_ratio + issue1.reload.update_attribute :status_id, 5 + assert_equal 50, parent.reload.done_ratio + issue2.reload.update_attribute :status_id, 5 + assert_equal 100, parent.reload.done_ratio + end + def test_parent_estimate_should_be_sum_of_leaves parent = Issue.generate! Issue.generate!(:estimated_hours => nil, :parent_issue_id => parent.id) @@ -331,6 +358,18 @@ assert_equal 12, parent.reload.estimated_hours end + def test_done_ratio_of_parent_with_a_child_with_estimated_time_at_0_should_not_exceed_100 + parent = Issue.generate! + Issue.generate!(:estimated_hours => 40, :parent_issue_id => parent.id) + Issue.generate!(:estimated_hours => 40, :parent_issue_id => parent.id) + Issue.generate!(:estimated_hours => 20, :parent_issue_id => parent.id) + Issue.generate!(:estimated_hours => 0, :parent_issue_id => parent.id) + parent.reload.children.each do |child| + child.update_attribute :status_id, 5 + end + assert_equal 100, parent.reload.done_ratio + end + def test_move_parent_updates_old_parent_attributes first_parent = Issue.generate! second_parent = Issue.generate! @@ -367,7 +406,7 @@ c.reload assert_equal 5, c.issues.count - ic1, ic2, ic3, ic4, ic5 = c.issues.find(:all, :order => 'subject') + ic1, ic2, ic3, ic4, ic5 = c.issues.order('subject').all assert ic1.root? assert_equal ic1, ic2.parent assert_equal ic1, ic3.parent diff -r d98d22a98252 -r afce8026aaeb test/unit/issue_priority_test.rb --- a/test/unit/issue_priority_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/issue_priority_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/issue_relation_test.rb --- a/test/unit/issue_relation_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/issue_relation_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -28,7 +28,10 @@ :issue_relations, :enabled_modules, :enumerations, - :trackers + :trackers, + :projects_trackers + + include Redmine::I18n def test_create from = Issue.find(1) @@ -112,7 +115,40 @@ :relation_type => IssueRelation::TYPE_PRECEDES ) assert !r.save - assert_not_nil r.errors[:base] + assert_not_equal [], r.errors[:base] + end + + def test_validates_circular_dependency_of_subtask + set_language_if_valid 'en' + issue1 = Issue.generate! + issue2 = Issue.generate! + IssueRelation.create!( + :issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_PRECEDES + ) + child = Issue.generate!(:parent_issue_id => issue2.id) + issue1.reload + child.reload + + r = IssueRelation.new( + :issue_from => child, :issue_to => issue1, + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert !r.save + assert_include 'This relation would create a circular dependency', r.errors.full_messages + end + + def test_subtasks_should_allow_precedes_relation + parent = Issue.generate! + child1 = Issue.generate!(:parent_issue_id => parent.id) + child2 = Issue.generate!(:parent_issue_id => parent.id) + + r = IssueRelation.new( + :issue_from => child1, :issue_to => child2, + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert r.valid? + assert r.save end def test_validates_circular_dependency_on_reverse_relations @@ -130,6 +166,51 @@ :relation_type => IssueRelation::TYPE_BLOCKED ) assert !r.save - assert_not_nil r.errors[:base] + assert_not_equal [], r.errors[:base] + end + + def test_create_should_make_journal_entry + from = Issue.find(1) + to = Issue.find(2) + from_journals = from.journals.size + to_journals = to.journals.size + relation = IssueRelation.new(:issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_PRECEDES) + assert relation.save + from.reload + to.reload + relation.reload + assert_equal from.journals.size, (from_journals + 1) + assert_equal to.journals.size, (to_journals + 1) + assert_equal 'relation', from.journals.last.details.last.property + assert_equal 'label_precedes', from.journals.last.details.last.prop_key + assert_equal '2', from.journals.last.details.last.value + assert_nil from.journals.last.details.last.old_value + assert_equal 'relation', to.journals.last.details.last.property + assert_equal 'label_follows', to.journals.last.details.last.prop_key + assert_equal '1', to.journals.last.details.last.value + assert_nil to.journals.last.details.last.old_value + end + + def test_delete_should_make_journal_entry + relation = IssueRelation.find(1) + id = relation.id + from = relation.issue_from + to = relation.issue_to + from_journals = from.journals.size + to_journals = to.journals.size + assert relation.destroy + from.reload + to.reload + assert_equal from.journals.size, (from_journals + 1) + assert_equal to.journals.size, (to_journals + 1) + assert_equal 'relation', from.journals.last.details.last.property + assert_equal 'label_blocks', from.journals.last.details.last.prop_key + assert_equal '9', from.journals.last.details.last.old_value + assert_nil from.journals.last.details.last.value + assert_equal 'relation', to.journals.last.details.last.property + assert_equal 'label_blocked_by', to.journals.last.details.last.prop_key + assert_equal '10', to.journals.last.details.last.old_value + assert_nil to.journals.last.details.last.value end end diff -r d98d22a98252 -r afce8026aaeb test/unit/issue_status_test.rb --- a/test/unit/issue_status_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/issue_status_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -36,8 +36,8 @@ assert_difference 'IssueStatus.count', -1 do assert status.destroy end - assert_nil WorkflowTransition.first(:conditions => {:old_status_id => status.id}) - assert_nil WorkflowTransition.first(:conditions => {:new_status_id => status.id}) + assert_nil WorkflowTransition.where(:old_status_id => status.id).first + assert_nil WorkflowTransition.where(:new_status_id => status.id).first end def test_destroy_status_in_use @@ -98,7 +98,7 @@ with_settings :issue_done_ratio => 'issue_field' do IssueStatus.update_issue_done_ratios - assert_equal 0, Issue.count(:conditions => {:done_ratio => 50}) + assert_equal 0, Issue.where(:done_ratio => 50).count end end @@ -107,7 +107,7 @@ with_settings :issue_done_ratio => 'issue_status' do IssueStatus.update_issue_done_ratios - issues = Issue.all(:conditions => {:status_id => 1}) + issues = Issue.where(:status_id => 1).all assert_equal [50], issues.map {|issue| issue.read_attribute(:done_ratio)}.uniq end end diff -r d98d22a98252 -r afce8026aaeb test/unit/issue_test.rb --- a/test/unit/issue_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/issue_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -35,6 +35,19 @@ User.current = nil end + def test_initialize + issue = Issue.new + + assert_nil issue.project_id + assert_nil issue.tracker_id + assert_nil issue.author_id + assert_nil issue.assigned_to_id + assert_nil issue.category_id + + assert_equal IssueStatus.default, issue.status + assert_equal IssuePriority.default, issue.priority + end + def test_create issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, @@ -79,6 +92,36 @@ assert_include 'Due date must be greater than start date', issue.errors.full_messages end + def test_start_date_lesser_than_soonest_start_should_not_validate_on_create + issue = Issue.generate(:start_date => '2013-06-04') + issue.stubs(:soonest_start).returns(Date.parse('2013-06-10')) + assert !issue.valid? + assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages + end + + def test_start_date_lesser_than_soonest_start_should_not_validate_on_update_if_changed + issue = Issue.generate!(:start_date => '2013-06-04') + issue.stubs(:soonest_start).returns(Date.parse('2013-06-10')) + issue.start_date = '2013-06-07' + assert !issue.valid? + assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages + end + + def test_start_date_lesser_than_soonest_start_should_validate_on_update_if_unchanged + issue = Issue.generate!(:start_date => '2013-06-04') + issue.stubs(:soonest_start).returns(Date.parse('2013-06-10')) + assert issue.valid? + end + + def test_estimated_hours_should_be_validated + set_language_if_valid 'en' + ['-2'].each do |invalid| + issue = Issue.new(:estimated_hours => invalid) + assert !issue.valid? + assert_include 'Estimated time is invalid', issue.errors.full_messages + end + end + def test_create_with_required_custom_field set_language_if_valid 'en' field = IssueCustomField.find_by_name('Database') @@ -224,7 +267,7 @@ def test_visible_scope_for_member user = User.find(9) - # User should see issues of projects for which he has view_issues permissions only + # User should see issues of projects for which user has view_issues permissions only Role.non_member.remove_permission!(:view_issues) Member.create!(:principal => user, :project_id => 3, :role_ids => [2]) issues = Issue.visible(user).all @@ -239,18 +282,18 @@ assert user.groups.any? Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2]) Role.non_member.remove_permission!(:view_issues) - + issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Assignment test', :assigned_to => user.groups.first, :is_private => true) - + Role.find(2).update_attribute :issues_visibility, 'default' issues = Issue.visible(User.find(8)).all assert issues.any? assert issues.include?(issue) - + Role.find(2).update_attribute :issues_visibility, 'own' issues = Issue.visible(User.find(8)).all assert issues.any? @@ -263,7 +306,7 @@ assert user.projects.empty? issues = Issue.visible(user).all assert issues.any? - # Admin should see issues on private projects that he does not belong to + # Admin should see issues on private projects that admin does not belong to assert issues.detect {|issue| !issue.project.is_public?} # Admin should see private issues of other users assert issues.detect {|issue| issue.is_private? && issue.author != user} @@ -300,6 +343,16 @@ assert_equal issues, issues.select(&:closed?) end + def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues + version = Version.find(2) + assert version.fixed_issues.any? + assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort + end + + def test_fixed_version_scope_with_empty_array_should_return_no_result + assert_equal 0, Issue.fixed_version([]).count + end + def test_errors_full_messages_should_include_custom_fields_errors field = IssueCustomField.find_by_name('Database') @@ -402,6 +455,21 @@ assert_equal 'MySQL', issue.custom_field_value(1) end + def test_reload_should_reload_custom_field_values + issue = Issue.generate! + issue.custom_field_values = {'2' => 'Foo'} + issue.save! + + issue = Issue.order('id desc').first + assert_equal 'Foo', issue.custom_field_value(2) + + issue.custom_field_values = {'2' => 'Bar'} + assert_equal 'Bar', issue.custom_field_value(2) + + issue.reload + assert_equal 'Foo', issue.custom_field_value(2) + end + def test_should_update_issue_with_disabled_tracker p = Project.find(1) issue = Issue.find(1) @@ -422,7 +490,7 @@ issue.tracker_id = 2 issue.subject = 'New subject' assert !issue.save - assert_not_nil issue.errors[:tracker_id] + assert_not_equal [], issue.errors[:tracker_id] end def test_category_based_assignment @@ -441,9 +509,9 @@ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, - :new_status_id => 4, :author => false, - :assignee => true) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, + :old_status_id => 1, :new_status_id => 4, + :author => false, :assignee => true) WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true) @@ -469,13 +537,33 @@ :project_id => 1, :author => user, :assigned_to => user) assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id) + + group = Group.generate! + group.users << user + issue = Issue.generate!(:tracker => tracker, :status => status, + :project_id => 1, :author => user, + :assigned_to => group) + assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id) + end + + def test_new_statuses_allowed_to_should_consider_group_assignment + WorkflowTransition.delete_all + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, + :old_status_id => 1, :new_status_id => 4, + :author => false, :assignee => true) + user = User.find(2) + group = Group.generate! + group.users << user + + issue = Issue.generate!(:author_id => 1, :assigned_to => group) + assert_include 4, issue.new_statuses_allowed_to(user).map(&:id) end def test_new_statuses_allowed_to_should_return_all_transitions_for_admin admin = User.find(1) issue = Issue.find(1) assert !admin.member_of?(issue.project) - expected_statuses = [issue.status] + + expected_statuses = [issue.status] + WorkflowTransition.find_all_by_old_status_id( issue.status_id).map(&:new_status).uniq.sort assert_equal expected_statuses, issue.new_statuses_allowed_to(admin) @@ -802,6 +890,49 @@ assert_equal copy.author, child_copy.author end + def test_copy_as_a_child_of_copied_issue_should_not_copy_itself + parent = Issue.generate! + child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1') + child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2') + + copy = parent.reload.copy + copy.parent_issue_id = parent.id + copy.author = User.find(7) + assert_difference 'Issue.count', 3 do + assert copy.save + end + parent.reload + copy.reload + assert_equal parent, copy.parent + assert_equal 3, parent.children.count + assert_equal 5, parent.descendants.count + assert_equal 2, copy.children.count + assert_equal 2, copy.descendants.count + end + + def test_copy_as_a_descendant_of_copied_issue_should_not_copy_itself + parent = Issue.generate! + child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1') + child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2') + + copy = parent.reload.copy + copy.parent_issue_id = child1.id + copy.author = User.find(7) + assert_difference 'Issue.count', 3 do + assert copy.save + end + parent.reload + child1.reload + copy.reload + assert_equal child1, copy.parent + assert_equal 2, parent.children.count + assert_equal 5, parent.descendants.count + assert_equal 1, child1.children.count + assert_equal 3, child1.descendants.count + assert_equal 2, copy.children.count + assert_equal 2, copy.descendants.count + end + def test_copy_should_copy_subtasks_to_target_project issue = Issue.generate_with_descendants! @@ -876,8 +1007,8 @@ assert issue1.reload.duplicates.include?(issue2) # Closing issue 1 - issue1.init_journal(User.find(:first), "Closing issue1") - issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true} + issue1.init_journal(User.first, "Closing issue1") + issue1.status = IssueStatus.where(:is_closed => true).first assert issue1.save # 2 and 3 should be also closed assert issue2.reload.closed? @@ -895,8 +1026,8 @@ assert !issue2.reload.duplicates.include?(issue1) # Closing issue 2 - issue2.init_journal(User.find(:first), "Closing issue2") - issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true} + issue2.init_journal(User.first, "Closing issue2") + issue2.status = IssueStatus.where(:is_closed => true).first assert issue2.save # 1 should not be also closed assert !issue1.reload.closed? @@ -914,7 +1045,7 @@ :status_id => 1, :fixed_version_id => 1, :subject => 'New issue') assert !issue.save - assert_not_nil issue.errors[:fixed_version_id] + assert_not_equal [], issue.errors[:fixed_version_id] end def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version @@ -922,7 +1053,7 @@ :status_id => 1, :fixed_version_id => 2, :subject => 'New issue') assert !issue.save - assert_not_nil issue.errors[:fixed_version_id] + assert_not_equal [], issue.errors[:fixed_version_id] end def test_should_be_able_to_assign_a_new_issue_to_an_open_version @@ -943,7 +1074,7 @@ issue = Issue.find(11) issue.status_id = 1 assert !issue.save - assert_not_nil issue.errors[:base] + assert_not_equal [], issue.errors[:base] end def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version @@ -972,7 +1103,7 @@ def test_should_keep_shared_version_when_changing_project Version.find(2).update_attribute :sharing, 'tree' - + issue = Issue.find(2) assert_equal 2, issue.fixed_version_id issue.project_id = 3 @@ -1110,57 +1241,51 @@ assert_nil copy.custom_value_for(2) end - context "#copy" do - setup do - @issue = Issue.find(1) - end + test "#copy should not create a journal" do + copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3) + copy.save! + assert_equal 0, copy.reload.journals.size + end - should "not create a journal" do - copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3) - copy.save! - assert_equal 0, copy.reload.journals.size - end + test "#copy should allow assigned_to changes" do + copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3) + assert_equal 3, copy.assigned_to_id + end - should "allow assigned_to changes" do - copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3) - assert_equal 3, copy.assigned_to_id - end + test "#copy should allow status changes" do + copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2) + assert_equal 2, copy.status_id + end - should "allow status changes" do - copy = @issue.copy(:project_id => 3, :tracker_id => 2, :status_id => 2) - assert_equal 2, copy.status_id - end + test "#copy should allow start date changes" do + date = Date.today + copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date) + assert_equal date, copy.start_date + end - should "allow start date changes" do - date = Date.today - copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date) - assert_equal date, copy.start_date - end + test "#copy should allow due date changes" do + date = Date.today + copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date) + assert_equal date, copy.due_date + end - should "allow due date changes" do - date = Date.today - copy = @issue.copy(:project_id => 3, :tracker_id => 2, :due_date => date) - assert_equal date, copy.due_date - end + test "#copy should set current user as author" do + User.current = User.find(9) + copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2) + assert_equal User.current, copy.author + end - should "set current user as author" do - User.current = User.find(9) - copy = @issue.copy(:project_id => 3, :tracker_id => 2) - assert_equal User.current, copy.author - end + test "#copy should create a journal with notes" do + date = Date.today + notes = "Notes added when copying" + copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date) + copy.init_journal(User.current, notes) + copy.save! - should "create a journal with notes" do - date = Date.today - notes = "Notes added when copying" - copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date) - copy.init_journal(User.current, notes) - copy.save! - - assert_equal 1, copy.journals.size - journal = copy.journals.first - assert_equal 0, journal.details.size - assert_equal notes, journal.notes - end + assert_equal 1, copy.journals.size + journal = copy.journals.first + assert_equal 0, journal.details.size + assert_equal notes, journal.notes end def test_valid_parent_project @@ -1370,6 +1495,7 @@ :relation_type => IssueRelation::TYPE_PRECEDES) assert_equal Date.parse('2012-10-18'), issue2.reload.start_date + issue1.reload issue1.due_date = '2012-10-23' issue1.save! issue2.reload @@ -1384,6 +1510,7 @@ :relation_type => IssueRelation::TYPE_PRECEDES) assert_equal Date.parse('2012-10-18'), issue2.reload.start_date + issue1.reload issue1.start_date = '2012-09-17' issue1.due_date = '2012-09-18' issue1.save! @@ -1402,6 +1529,7 @@ :relation_type => IssueRelation::TYPE_PRECEDES) assert_equal Date.parse('2012-10-18'), issue2.reload.start_date + issue1.reload issue1.start_date = '2012-09-17' issue1.due_date = '2012-09-18' issue1.save! @@ -1425,99 +1553,139 @@ end end + def test_child_issue_should_consider_parent_soonest_start_on_create + set_language_if_valid 'en' + issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') + issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20') + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_PRECEDES) + issue1.reload + issue2.reload + assert_equal Date.parse('2012-10-18'), issue2.start_date + + child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16', + :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1) + assert !child.valid? + assert_include 'Start date cannot be earlier than 10/18/2012 because of preceding issues', child.errors.full_messages + assert_equal Date.parse('2012-10-18'), child.soonest_start + child.start_date = '2012-10-18' + assert child.save + end + + def test_setting_parent_to_a_dependent_issue_should_not_validate + set_language_if_valid 'en' + issue1 = Issue.generate! + issue2 = Issue.generate! + issue3 = Issue.generate! + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES) + IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES) + issue3.reload + issue3.parent_issue_id = issue2.id + assert !issue3.valid? + assert_include 'Parent task is invalid', issue3.errors.full_messages + end + + def test_setting_parent_should_not_allow_circular_dependency + set_language_if_valid 'en' + issue1 = Issue.generate! + issue2 = Issue.generate! + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES) + issue3 = Issue.generate! + issue2.reload + issue2.parent_issue_id = issue3.id + issue2.save! + issue4 = Issue.generate! + IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES) + issue4.reload + issue4.parent_issue_id = issue1.id + assert !issue4.valid? + assert_include 'Parent task is invalid', issue4.errors.full_messages + end + def test_overdue assert Issue.new(:due_date => 1.day.ago.to_date).overdue? assert !Issue.new(:due_date => Date.today).overdue? assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue? assert !Issue.new(:due_date => nil).overdue? assert !Issue.new(:due_date => 1.day.ago.to_date, - :status => IssueStatus.find(:first, - :conditions => {:is_closed => true}) + :status => IssueStatus.where(:is_closed => true).first ).overdue? end - context "#behind_schedule?" do - should "be false if the issue has no start_date" do - assert !Issue.new(:start_date => nil, - :due_date => 1.day.from_now.to_date, - :done_ratio => 0).behind_schedule? - end + test "#behind_schedule? should be false if the issue has no start_date" do + assert !Issue.new(:start_date => nil, + :due_date => 1.day.from_now.to_date, + :done_ratio => 0).behind_schedule? + end - should "be false if the issue has no end_date" do - assert !Issue.new(:start_date => 1.day.from_now.to_date, - :due_date => nil, - :done_ratio => 0).behind_schedule? - end + test "#behind_schedule? should be false if the issue has no end_date" do + assert !Issue.new(:start_date => 1.day.from_now.to_date, + :due_date => nil, + :done_ratio => 0).behind_schedule? + end - should "be false if the issue has more done than it's calendar time" do - assert !Issue.new(:start_date => 50.days.ago.to_date, - :due_date => 50.days.from_now.to_date, - :done_ratio => 90).behind_schedule? - end + test "#behind_schedule? should be false if the issue has more done than it's calendar time" do + assert !Issue.new(:start_date => 50.days.ago.to_date, + :due_date => 50.days.from_now.to_date, + :done_ratio => 90).behind_schedule? + end - should "be true if the issue hasn't been started at all" do - assert Issue.new(:start_date => 1.day.ago.to_date, - :due_date => 1.day.from_now.to_date, - :done_ratio => 0).behind_schedule? - end + test "#behind_schedule? should be true if the issue hasn't been started at all" do + assert Issue.new(:start_date => 1.day.ago.to_date, + :due_date => 1.day.from_now.to_date, + :done_ratio => 0).behind_schedule? + end - should "be true if the issue has used more calendar time than it's done ratio" do - assert Issue.new(:start_date => 100.days.ago.to_date, - :due_date => Date.today, - :done_ratio => 90).behind_schedule? + test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do + assert Issue.new(:start_date => 100.days.ago.to_date, + :due_date => Date.today, + :done_ratio => 90).behind_schedule? + end + + test "#assignable_users should be Users" do + assert_kind_of User, Issue.find(1).assignable_users.first + end + + test "#assignable_users should include the issue author" do + non_project_member = User.generate! + issue = Issue.generate!(:author => non_project_member) + + assert issue.assignable_users.include?(non_project_member) + end + + test "#assignable_users should include the current assignee" do + user = User.generate! + issue = Issue.generate!(:assigned_to => user) + user.lock! + + assert Issue.find(issue.id).assignable_users.include?(user) + end + + test "#assignable_users should not show the issue author twice" do + assignable_user_ids = Issue.find(1).assignable_users.collect(&:id) + assert_equal 2, assignable_user_ids.length + + assignable_user_ids.each do |user_id| + assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, + "User #{user_id} appears more or less than once" end end - context "#assignable_users" do - should "be Users" do - assert_kind_of User, Issue.find(1).assignable_users.first + test "#assignable_users with issue_group_assignment should include groups" do + issue = Issue.new(:project => Project.find(2)) + + with_settings :issue_group_assignment => '1' do + assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort + assert issue.assignable_users.include?(Group.find(11)) end + end - should "include the issue author" do - non_project_member = User.generate! - issue = Issue.generate!(:author => non_project_member) + test "#assignable_users without issue_group_assignment should not include groups" do + issue = Issue.new(:project => Project.find(2)) - assert issue.assignable_users.include?(non_project_member) - end - - should "include the current assignee" do - user = User.generate! - issue = Issue.generate!(:assigned_to => user) - user.lock! - - assert Issue.find(issue.id).assignable_users.include?(user) - end - - should "not show the issue author twice" do - assignable_user_ids = Issue.find(1).assignable_users.collect(&:id) - assert_equal 2, assignable_user_ids.length - - assignable_user_ids.each do |user_id| - assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, - "User #{user_id} appears more or less than once" - end - end - - context "with issue_group_assignment" do - should "include groups" do - issue = Issue.new(:project => Project.find(2)) - - with_settings :issue_group_assignment => '1' do - assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort - assert issue.assignable_users.include?(Group.find(11)) - end - end - end - - context "without issue_group_assignment" do - should "not include groups" do - issue = Issue.new(:project => Project.find(2)) - - with_settings :issue_group_assignment => '0' do - assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort - assert !issue.assignable_users.include?(Group.find(11)) - end - end + with_settings :issue_group_assignment => '0' do + assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort + assert !issue.assignable_users.include?(Group.find(11)) end end @@ -1532,6 +1700,19 @@ assert_equal 1, ActionMailer::Base.deliveries.size end + def test_update_should_notify_previous_assignee + ActionMailer::Base.deliveries.clear + user = User.find(3) + user.members.update_all ["mail_notification = ?", false] + user.update_attribute :mail_notification, 'only_assigned' + + issue = Issue.find(2) + issue.init_journal User.find(1) + issue.assigned_to = nil + issue.save! + assert_include user.mail, ActionMailer::Base.deliveries.last.bcc + end + def test_stale_issue_should_not_send_email_notification ActionMailer::Base.deliveries.clear issue = Issue.find(1) @@ -1630,7 +1811,7 @@ end def test_saving_twice_should_not_duplicate_journal_details - i = Issue.find(:first) + i = Issue.first i.init_journal(User.find(2), 'Some notes') # initial changes i.subject = 'New subject' @@ -1639,7 +1820,7 @@ assert i.save end # 1 more change - i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id]) + i.priority = IssuePriority.where("id <> ?", i.priority_id).first assert_no_difference 'Journal.count' do assert_difference 'JournalDetail.count', 1 do i.save @@ -1668,6 +1849,136 @@ assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort end + def test_all_dependent_issues_with_subtask + IssueRelation.delete_all + + project = Project.generate!(:name => "testproject") + + parentIssue = Issue.generate!(:project => project) + childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) + childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) + + assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort + end + + def test_all_dependent_issues_does_not_include_self + IssueRelation.delete_all + + project = Project.generate!(:name => "testproject") + + parentIssue = Issue.generate!(:project => project) + childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) + + assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id) + end + + def test_all_dependent_issues_with_parenttask_and_sibling + IssueRelation.delete_all + + project = Project.generate!(:name => "testproject") + + parentIssue = Issue.generate!(:project => project) + childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) + childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) + + assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id) + end + + def test_all_dependent_issues_with_relation_to_leaf_in_other_tree + IssueRelation.delete_all + + project = Project.generate!(:name => "testproject") + + parentIssue1 = Issue.generate!(:project => project) + childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) + childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) + + parentIssue2 = Issue.generate!(:project => project) + childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) + childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) + + + assert IssueRelation.create(:issue_from => parentIssue1, + :issue_to => childIssue2_2, + :relation_type => IssueRelation::TYPE_BLOCKS) + + assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort, + parentIssue1.all_dependent_issues.collect(&:id).uniq.sort + end + + def test_all_dependent_issues_with_relation_to_parent_in_other_tree + IssueRelation.delete_all + + project = Project.generate!(:name => "testproject") + + parentIssue1 = Issue.generate!(:project => project) + childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) + childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) + + parentIssue2 = Issue.generate!(:project => project) + childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) + childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) + + + assert IssueRelation.create(:issue_from => parentIssue1, + :issue_to => parentIssue2, + :relation_type => IssueRelation::TYPE_BLOCKS) + + assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort, + parentIssue1.all_dependent_issues.collect(&:id).uniq.sort + end + + def test_all_dependent_issues_with_transitive_relation + IssueRelation.delete_all + + project = Project.generate!(:name => "testproject") + + parentIssue1 = Issue.generate!(:project => project) + childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) + + parentIssue2 = Issue.generate!(:project => project) + childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) + + independentIssue = Issue.generate!(:project => project) + + assert IssueRelation.create(:issue_from => parentIssue1, + :issue_to => childIssue2_1, + :relation_type => IssueRelation::TYPE_RELATES) + + assert IssueRelation.create(:issue_from => childIssue2_1, + :issue_to => independentIssue, + :relation_type => IssueRelation::TYPE_RELATES) + + assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort, + parentIssue1.all_dependent_issues.collect(&:id).uniq.sort + end + + def test_all_dependent_issues_with_transitive_relation2 + IssueRelation.delete_all + + project = Project.generate!(:name => "testproject") + + parentIssue1 = Issue.generate!(:project => project) + childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) + + parentIssue2 = Issue.generate!(:project => project) + childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) + + independentIssue = Issue.generate!(:project => project) + + assert IssueRelation.create(:issue_from => parentIssue1, + :issue_to => independentIssue, + :relation_type => IssueRelation::TYPE_RELATES) + + assert IssueRelation.create(:issue_from => independentIssue, + :issue_to => childIssue2_1, + :relation_type => IssueRelation::TYPE_RELATES) + + assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort, + parentIssue1.all_dependent_issues.collect(&:id).uniq.sort + + end + def test_all_dependent_issues_with_persistent_circular_dependency IssueRelation.delete_all assert IssueRelation.create!(:issue_from => Issue.find(1), @@ -1681,7 +1992,7 @@ :issue_to => Issue.find(7), :relation_type => IssueRelation::TYPE_PRECEDES) IssueRelation.update_all("issue_to_id = 1", ["id = ?", r.id]) - + assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort end @@ -1701,7 +2012,7 @@ :issue_to => Issue.find(7), :relation_type => IssueRelation::TYPE_RELATES) IssueRelation.update_all("issue_to_id = 2", ["id = ?", r.id]) - + r = IssueRelation.create!(:issue_from => Issue.find(3), :issue_to => Issue.find(7), :relation_type => IssueRelation::TYPE_RELATES) @@ -1710,121 +2021,89 @@ assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort end - context "#done_ratio" do - setup do - @issue = Issue.find(1) - @issue_status = IssueStatus.find(1) - @issue_status.update_attribute(:default_done_ratio, 50) - @issue2 = Issue.find(2) - @issue_status2 = IssueStatus.find(2) - @issue_status2.update_attribute(:default_done_ratio, 0) + test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do + @issue = Issue.find(1) + @issue_status = IssueStatus.find(1) + @issue_status.update_attribute(:default_done_ratio, 50) + @issue2 = Issue.find(2) + @issue_status2 = IssueStatus.find(2) + @issue_status2.update_attribute(:default_done_ratio, 0) + + with_settings :issue_done_ratio => 'issue_field' do + assert_equal 0, @issue.done_ratio + assert_equal 30, @issue2.done_ratio end - teardown do - Setting.issue_done_ratio = 'issue_field' - end - - context "with Setting.issue_done_ratio using the issue_field" do - setup do - Setting.issue_done_ratio = 'issue_field' - end - - should "read the issue's field" do - assert_equal 0, @issue.done_ratio - assert_equal 30, @issue2.done_ratio - end - end - - context "with Setting.issue_done_ratio using the issue_status" do - setup do - Setting.issue_done_ratio = 'issue_status' - end - - should "read the Issue Status's default done ratio" do - assert_equal 50, @issue.done_ratio - assert_equal 0, @issue2.done_ratio - end + with_settings :issue_done_ratio => 'issue_status' do + assert_equal 50, @issue.done_ratio + assert_equal 0, @issue2.done_ratio end end - context "#update_done_ratio_from_issue_status" do - setup do - @issue = Issue.find(1) - @issue_status = IssueStatus.find(1) - @issue_status.update_attribute(:default_done_ratio, 50) - @issue2 = Issue.find(2) - @issue_status2 = IssueStatus.find(2) - @issue_status2.update_attribute(:default_done_ratio, 0) + test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do + @issue = Issue.find(1) + @issue_status = IssueStatus.find(1) + @issue_status.update_attribute(:default_done_ratio, 50) + @issue2 = Issue.find(2) + @issue_status2 = IssueStatus.find(2) + @issue_status2.update_attribute(:default_done_ratio, 0) + + with_settings :issue_done_ratio => 'issue_field' do + @issue.update_done_ratio_from_issue_status + @issue2.update_done_ratio_from_issue_status + + assert_equal 0, @issue.read_attribute(:done_ratio) + assert_equal 30, @issue2.read_attribute(:done_ratio) end - context "with Setting.issue_done_ratio using the issue_field" do - setup do - Setting.issue_done_ratio = 'issue_field' - end + with_settings :issue_done_ratio => 'issue_status' do + @issue.update_done_ratio_from_issue_status + @issue2.update_done_ratio_from_issue_status - should "not change the issue" do - @issue.update_done_ratio_from_issue_status - @issue2.update_done_ratio_from_issue_status - - assert_equal 0, @issue.read_attribute(:done_ratio) - assert_equal 30, @issue2.read_attribute(:done_ratio) - end - end - - context "with Setting.issue_done_ratio using the issue_status" do - setup do - Setting.issue_done_ratio = 'issue_status' - end - - should "change the issue's done ratio" do - @issue.update_done_ratio_from_issue_status - @issue2.update_done_ratio_from_issue_status - - assert_equal 50, @issue.read_attribute(:done_ratio) - assert_equal 0, @issue2.read_attribute(:done_ratio) - end + assert_equal 50, @issue.read_attribute(:done_ratio) + assert_equal 0, @issue2.read_attribute(:done_ratio) end end test "#by_tracker" do User.current = User.anonymous groups = Issue.by_tracker(Project.find(1)) - assert_equal 3, groups.size + assert_equal 3, groups.count assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i} end test "#by_version" do User.current = User.anonymous groups = Issue.by_version(Project.find(1)) - assert_equal 3, groups.size + assert_equal 3, groups.count assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i} end test "#by_priority" do User.current = User.anonymous groups = Issue.by_priority(Project.find(1)) - assert_equal 4, groups.size + assert_equal 4, groups.count assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i} end test "#by_category" do User.current = User.anonymous groups = Issue.by_category(Project.find(1)) - assert_equal 2, groups.size + assert_equal 2, groups.count assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i} end test "#by_assigned_to" do User.current = User.anonymous groups = Issue.by_assigned_to(Project.find(1)) - assert_equal 2, groups.size + assert_equal 2, groups.count assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i} end test "#by_author" do User.current = User.anonymous groups = Issue.by_author(Project.find(1)) - assert_equal 4, groups.size + assert_equal 4, groups.count assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i} end @@ -1832,7 +2111,7 @@ User.current = User.anonymous groups = Issue.by_subproject(Project.find(1)) # Private descendant not visible - assert_equal 1, groups.size + assert_equal 1, groups.count assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i} end @@ -1855,48 +2134,42 @@ assert_equal before, Issue.on_active_project.length end - context "Issue#recipients" do - setup do - @project = Project.find(1) - @author = User.generate! - @assignee = User.generate! - @issue = Issue.generate!(:project => @project, :assigned_to => @assignee, :author => @author) + test "Issue#recipients should include project recipients" do + issue = Issue.generate! + assert issue.project.recipients.present? + issue.project.recipients.each do |project_recipient| + assert issue.recipients.include?(project_recipient) end + end - should "include project recipients" do - assert @project.recipients.present? - @project.recipients.each do |project_recipient| - assert @issue.recipients.include?(project_recipient) - end - end + test "Issue#recipients should include the author if the author is active" do + issue = Issue.generate!(:author => User.generate!) + assert issue.author, "No author set for Issue" + assert issue.recipients.include?(issue.author.mail) + end - should "include the author if the author is active" do - assert @issue.author, "No author set for Issue" - assert @issue.recipients.include?(@issue.author.mail) - end + test "Issue#recipients should include the assigned to user if the assigned to user is active" do + issue = Issue.generate!(:assigned_to => User.generate!) + assert issue.assigned_to, "No assigned_to set for Issue" + assert issue.recipients.include?(issue.assigned_to.mail) + end - should "include the assigned to user if the assigned to user is active" do - assert @issue.assigned_to, "No assigned_to set for Issue" - assert @issue.recipients.include?(@issue.assigned_to.mail) - end + test "Issue#recipients should not include users who opt out of all email" do + issue = Issue.generate!(:author => User.generate!) + issue.author.update_attribute(:mail_notification, :none) + assert !issue.recipients.include?(issue.author.mail) + end - should "not include users who opt out of all email" do - @author.update_attribute(:mail_notification, :none) + test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do + issue = Issue.generate!(:author => User.generate!) + issue.author.update_attribute(:mail_notification, :only_assigned) + assert !issue.recipients.include?(issue.author.mail) + end - assert !@issue.recipients.include?(@issue.author.mail) - end - - should "not include the issue author if they are only notified of assigned issues" do - @author.update_attribute(:mail_notification, :only_assigned) - - assert !@issue.recipients.include?(@issue.author.mail) - end - - should "not include the assigned user if they are only notified of owned issues" do - @assignee.update_attribute(:mail_notification, :only_owner) - - assert !@issue.recipients.include?(@issue.assigned_to.mail) - end + test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do + issue = Issue.generate!(:assigned_to => User.generate!) + issue.assigned_to.update_attribute(:mail_notification, :only_owner) + assert !issue.recipients.include?(issue.assigned_to.mail) end def test_last_journal_id_with_journals_should_return_the_journal_id @@ -1916,6 +2189,12 @@ assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('') end + def test_css_classes_should_include_tracker + issue = Issue.new(:tracker => Tracker.find(2)) + classes = issue.css_classes.split(' ') + assert_include 'tracker-2', classes + end + def test_css_classes_should_include_priority issue = Issue.new(:priority => IssuePriority.find(8)) classes = issue.css_classes.split(' ') @@ -1923,6 +2202,21 @@ assert_include 'priority-highest', classes end + def test_css_classes_should_include_user_and_group_assignment + project = Project.first + user = User.generate! + group = Group.generate! + Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) + group.users << user + assert user.member_of?(project) + issue1 = Issue.generate(:assigned_to_id => group.id) + assert_include 'assigned-to-my-group', issue1.css_classes(user) + assert_not_include 'assigned-to-me', issue1.css_classes(user) + issue2 = Issue.generate(:assigned_to_id => user.id) + assert_not_include 'assigned-to-my-group', issue2.css_classes(user) + assert_include 'assigned-to-me', issue2.css_classes(user) + end + def test_save_attachments_with_hash_should_save_attachments_in_keys_order set_tmp_attachments_directory issue = Issue.generate! @@ -1936,4 +2230,77 @@ assert_equal 3, issue.reload.attachments.count assert_equal %w(upload foo bar), issue.attachments.map(&:filename) end + + def test_closed_on_should_be_nil_when_creating_an_open_issue + issue = Issue.generate!(:status_id => 1).reload + assert !issue.closed? + assert_nil issue.closed_on + end + + def test_closed_on_should_be_set_when_creating_a_closed_issue + issue = Issue.generate!(:status_id => 5).reload + assert issue.closed? + assert_not_nil issue.closed_on + assert_equal issue.updated_on, issue.closed_on + assert_equal issue.created_on, issue.closed_on + end + + def test_closed_on_should_be_nil_when_updating_an_open_issue + issue = Issue.find(1) + issue.subject = 'Not closed yet' + issue.save! + issue.reload + assert_nil issue.closed_on + end + + def test_closed_on_should_be_set_when_closing_an_open_issue + issue = Issue.find(1) + issue.subject = 'Now closed' + issue.status_id = 5 + issue.save! + issue.reload + assert_not_nil issue.closed_on + assert_equal issue.updated_on, issue.closed_on + end + + def test_closed_on_should_not_be_updated_when_updating_a_closed_issue + issue = Issue.open(false).first + was_closed_on = issue.closed_on + assert_not_nil was_closed_on + issue.subject = 'Updating a closed issue' + issue.save! + issue.reload + assert_equal was_closed_on, issue.closed_on + end + + def test_closed_on_should_be_preserved_when_reopening_a_closed_issue + issue = Issue.open(false).first + was_closed_on = issue.closed_on + assert_not_nil was_closed_on + issue.subject = 'Reopening a closed issue' + issue.status_id = 1 + issue.save! + issue.reload + assert !issue.closed? + assert_equal was_closed_on, issue.closed_on + end + + def test_status_was_should_return_nil_for_new_issue + issue = Issue.new + assert_nil issue.status_was + end + + def test_status_was_should_return_status_before_change + issue = Issue.find(1) + issue.status = IssueStatus.find(2) + assert_equal IssueStatus.find(1), issue.status_was + end + + def test_status_was_should_be_reset_on_save + issue = Issue.find(1) + issue.status = IssueStatus.find(2) + assert_equal IssueStatus.find(1), issue.status_was + assert issue.save! + assert_equal IssueStatus.find(2), issue.status_was + end end diff -r d98d22a98252 -r afce8026aaeb test/unit/issue_transaction_test.rb --- a/test/unit/issue_transaction_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/issue_transaction_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/journal_observer_test.rb --- a/test/unit/journal_observer_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/journal_observer_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -29,8 +29,8 @@ # context: issue_updated notified_events def test_create_should_send_email_notification_with_issue_updated - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first journal = issue.init_journal(user, issue) with_settings :notified_events => %w(issue_updated) do @@ -40,8 +40,8 @@ end def test_create_should_not_send_email_notification_with_notify_set_to_false - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first journal = issue.init_journal(user, issue) journal.notify = false @@ -52,8 +52,8 @@ end def test_create_should_not_send_email_notification_without_issue_updated - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first journal = issue.init_journal(user, issue) with_settings :notified_events => [] do @@ -64,8 +64,8 @@ # context: issue_note_added notified_events def test_create_should_send_email_notification_with_issue_note_added - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first journal = issue.init_journal(user, issue) journal.notes = 'This update has a note' @@ -76,8 +76,8 @@ end def test_create_should_not_send_email_notification_without_issue_note_added - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first journal = issue.init_journal(user, issue) journal.notes = 'This update has a note' @@ -89,8 +89,8 @@ # context: issue_status_updated notified_events def test_create_should_send_email_notification_with_issue_status_updated - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first issue.init_journal(user, issue) issue.status = IssueStatus.last @@ -101,8 +101,8 @@ end def test_create_should_not_send_email_notification_without_issue_status_updated - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first issue.init_journal(user, issue) issue.status = IssueStatus.last @@ -114,8 +114,8 @@ # context: issue_priority_updated notified_events def test_create_should_send_email_notification_with_issue_priority_updated - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first issue.init_journal(user, issue) issue.priority = IssuePriority.last @@ -126,8 +126,8 @@ end def test_create_should_not_send_email_notification_without_issue_priority_updated - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first issue.init_journal(user, issue) issue.priority = IssuePriority.last diff -r d98d22a98252 -r afce8026aaeb test/unit/journal_test.rb --- a/test/unit/journal_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/journal_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -41,8 +41,8 @@ def test_create_should_send_email_notification ActionMailer::Base.deliveries.clear - issue = Issue.find(:first) - user = User.find(:first) + issue = Issue.first + user = User.first journal = issue.init_journal(user, issue) assert journal.save @@ -137,7 +137,7 @@ user.reload journals = Journal.visible(user).all assert journals.empty? - # User should see issues of projects for which he has view_issues permissions only + # User should see issues of projects for which user has view_issues permissions only Member.create!(:principal => user, :project_id => 1, :role_ids => [1]) user.reload journals = Journal.visible(user).all @@ -151,7 +151,71 @@ assert user.projects.empty? journals = Journal.visible(user).all assert journals.any? - # Admin should see issues on private projects that he does not belong to + # Admin should see issues on private projects that admin does not belong to assert journals.detect {|journal| !journal.issue.project.is_public?} end + + def test_preload_journals_details_custom_fields_should_set_custom_field_instance_variable + d = JournalDetail.new(:property => 'cf', :prop_key => '2') + journals = [Journal.new(:details => [d])] + + d.expects(:instance_variable_set).with("@custom_field", CustomField.find(2)).once + Journal.preload_journals_details_custom_fields(journals) + end + + def test_preload_journals_details_custom_fields_with_empty_set + assert_nothing_raised do + Journal.preload_journals_details_custom_fields([]) + end + end + + def test_details_should_normalize_dates + j = JournalDetail.create!(:old_value => Date.parse('2012-11-03'), :value => Date.parse('2013-01-02')) + j.reload + assert_equal '2012-11-03', j.old_value + assert_equal '2013-01-02', j.value + end + + def test_details_should_normalize_true_values + j = JournalDetail.create!(:old_value => true, :value => true) + j.reload + assert_equal '1', j.old_value + assert_equal '1', j.value + end + + def test_details_should_normalize_false_values + j = JournalDetail.create!(:old_value => false, :value => false) + j.reload + assert_equal '0', j.old_value + assert_equal '0', j.value + end + + def test_custom_field_should_return_custom_field_for_cf_detail + d = JournalDetail.new(:property => 'cf', :prop_key => '2') + assert_equal CustomField.find(2), d.custom_field + end + + def test_custom_field_should_return_nil_for_non_cf_detail + d = JournalDetail.new(:property => 'subject') + assert_equal nil, d.custom_field + end + + def test_visible_details_should_include_relations_to_visible_issues_only + issue = Issue.generate! + visible_issue = Issue.generate! + IssueRelation.create!(:issue_from => issue, :issue_to => visible_issue, :relation_type => 'relates') + hidden_issue = Issue.generate!(:is_private => true) + IssueRelation.create!(:issue_from => issue, :issue_to => hidden_issue, :relation_type => 'relates') + issue.reload + assert_equal 1, issue.journals.size + journal = issue.journals.first + assert_equal 2, journal.details.size + + visible_details = journal.visible_details(User.anonymous) + assert_equal 1, visible_details.size + assert_equal visible_issue.id.to_s, visible_details.first.value + + visible_details = journal.visible_details(User.find(2)) + assert_equal 2, visible_details.size + end end diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/access_control_test.rb --- a/test/unit/lib/redmine/access_control_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/access_control_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/ciphering_test.rb --- a/test/unit/lib/redmine/ciphering_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/ciphering_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/codeset_util_test.rb --- a/test/unit/lib/redmine/codeset_util_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/codeset_util_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/configuration_test.rb --- a/test/unit/lib/redmine/configuration_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/configuration_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/export/pdf_test.rb --- a/test/unit/lib/redmine/export/pdf_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/export/pdf_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../../../../test_helper', __FILE__) -require 'iconv' class PdfTest < ActiveSupport::TestCase fixtures :users, :projects, :roles, :members, :member_roles, @@ -36,9 +35,9 @@ txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) txt_3 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) - assert_equal "?\x91\xd4", txt_1 - assert_equal "?\x91\xd4?", txt_2 - assert_equal "??\x91\xd4?", txt_3 + assert_equal "?\x91\xd4".force_encoding("ASCII-8BIT"), txt_1 + assert_equal "?\x91\xd4?".force_encoding("ASCII-8BIT"), txt_2 + assert_equal "??\x91\xd4?".force_encoding("ASCII-8BIT"), txt_3 assert_equal "ASCII-8BIT", txt_1.encoding.to_s assert_equal "ASCII-8BIT", txt_2.encoding.to_s assert_equal "ASCII-8BIT", txt_3.encoding.to_s diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/helpers/calendar_test.rb --- a/test/unit/lib/redmine/helpers/calendar_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/helpers/calendar_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/helpers/diff_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/lib/redmine/helpers/diff_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,25 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../../test_helper', __FILE__) + +class DiffTest < ActiveSupport::TestCase + def test_diff + diff = Redmine::Helpers::Diff.new("foo", "bar") + assert_not_nil diff + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/helpers/gantt_test.rb --- a/test/unit/lib/redmine/helpers/gantt_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/helpers/gantt_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,7 +26,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :versions, :groups_users @@ -34,6 +33,7 @@ include ProjectsHelper include IssuesHelper include ERB::Util + include Rails.application.routes.url_helpers def setup setup_with_controller @@ -49,7 +49,7 @@ @project = project @gantt = Redmine::Helpers::Gantt.new(options) @gantt.project = @project - @gantt.query = Query.create!(:project => @project, :name => 'Gantt') + @gantt.query = IssueQuery.create!(:project => @project, :name => 'Gantt') @gantt.view = self @gantt.instance_variable_set('@date_from', options[:date_from] || (today - 14)) @gantt.instance_variable_set('@date_to', options[:date_to] || (today + 14)) @@ -57,11 +57,26 @@ context "#number_of_rows" do context "with one project" do - should "return the number of rows just for that project" + should "return the number of rows just for that project" do + p1, p2 = Project.generate!, Project.generate! + i1, i2 = Issue.generate!(:project => p1), Issue.generate!(:project => p2) + create_gantt(p1) + assert_equal 2, @gantt.number_of_rows + end end context "with no project" do - should "return the total number of rows for all the projects, resursively" + should "return the total number of rows for all the projects, resursively" do + p1, p2 = Project.generate!, Project.generate! + create_gantt(nil) + #fix the return value of #number_of_rows_on_project() to an arbitrary value + #so that we really only test #number_of_rows + @gantt.stubs(:number_of_rows_on_project).returns(7) + #also fix #projects because we want to test #number_of_rows in isolation + @gantt.stubs(:projects).returns(Project.all) + #actual test + assert_equal Project.count*7, @gantt.number_of_rows + end end should "not exceed max_rows option" do @@ -746,4 +761,81 @@ context "#to_pdf" do should "be tested" end + + def test_sort_issues_no_date + project = Project.generate! + issue1 = Issue.generate!(:subject => "test", :project => project) + issue2 = Issue.generate!(:subject => "test", :project => project) + assert issue1.root_id < issue2.root_id + child1 = Issue.generate!(:parent_issue_id => issue1.id, :subject => 'child', + :project => project) + child2 = Issue.generate!(:parent_issue_id => issue1.id, :subject => 'child', + :project => project) + child3 = Issue.generate!(:parent_issue_id => child1.id, :subject => 'child', + :project => project) + assert_equal child1.root_id, child2.root_id + assert child1.lft < child2.lft + assert child3.lft < child2.lft + issues = [child3, child2, child1, issue2, issue1] + Redmine::Helpers::Gantt.sort_issues!(issues) + assert_equal [issue1.id, child1.id, child3.id, child2.id, issue2.id], + issues.map{|v| v.id} + end + + def test_sort_issues_root_only + project = Project.generate! + issue1 = Issue.generate!(:subject => "test", :project => project) + issue2 = Issue.generate!(:subject => "test", :project => project) + issue3 = Issue.generate!(:subject => "test", :project => project, + :start_date => (today - 1)) + issue4 = Issue.generate!(:subject => "test", :project => project, + :start_date => (today - 2)) + issues = [issue4, issue3, issue2, issue1] + Redmine::Helpers::Gantt.sort_issues!(issues) + assert_equal [issue1.id, issue2.id, issue4.id, issue3.id], + issues.map{|v| v.id} + end + + def test_sort_issues_tree + project = Project.generate! + issue1 = Issue.generate!(:subject => "test", :project => project) + issue2 = Issue.generate!(:subject => "test", :project => project, + :start_date => (today - 2)) + issue1_child1 = + Issue.generate!(:parent_issue_id => issue1.id, :subject => 'child', + :project => project) + issue1_child2 = + Issue.generate!(:parent_issue_id => issue1.id, :subject => 'child', + :project => project, :start_date => (today - 10)) + issue1_child1_child1 = + Issue.generate!(:parent_issue_id => issue1_child1.id, :subject => 'child', + :project => project, :start_date => (today - 8)) + issue1_child1_child2 = + Issue.generate!(:parent_issue_id => issue1_child1.id, :subject => 'child', + :project => project, :start_date => (today - 9)) + issue1_child1_child1_logic = Redmine::Helpers::Gantt.sort_issue_logic(issue1_child1_child1) + assert_equal [[today - 10, issue1.id], [today - 9, issue1_child1.id], + [today - 8, issue1_child1_child1.id]], + issue1_child1_child1_logic + issue1_child1_child2_logic = Redmine::Helpers::Gantt.sort_issue_logic(issue1_child1_child2) + assert_equal [[today - 10, issue1.id], [today - 9, issue1_child1.id], + [today - 9, issue1_child1_child2.id]], + issue1_child1_child2_logic + issues = [issue1_child1_child2, issue1_child1_child1, issue1_child2, + issue1_child1, issue2, issue1] + Redmine::Helpers::Gantt.sort_issues!(issues) + assert_equal [issue1.id, issue1_child1.id, issue1_child2.id, + issue1_child1_child2.id, issue1_child1_child1.id, issue2.id], + issues.map{|v| v.id} + end + + def test_sort_versions + project = Project.generate! + version1 = Version.create!(:project => project, :name => 'test1') + version2 = Version.create!(:project => project, :name => 'test2', :effective_date => '2013-10-25') + version3 = Version.create!(:project => project, :name => 'test3') + version4 = Version.create!(:project => project, :name => 'test4', :effective_date => '2013-10-02') + + assert_equal versions.sort, Redmine::Helpers::Gantt.sort_versions!(versions) + end end diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/hook_test.rb --- a/test/unit/lib/redmine/hook_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/hook_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,7 +23,7 @@ :trackers, :projects_trackers, :enabled_modules, :versions, - :issue_statuses, :issue_categories, :issue_relations, :workflows, + :issue_statuses, :issue_categories, :issue_relations, :enumerations, :issues @@ -154,14 +154,14 @@ issue = Issue.find(1) ActionMailer::Base.deliveries.clear - Mailer.issue_add(issue).deliver + Mailer.deliver_issue_add(issue) mail = ActionMailer::Base.deliveries.last @hook_module.add_listener(TestLinkToHook) hook_helper.call_hook(:view_layouts_base_html_head) ActionMailer::Base.deliveries.clear - Mailer.issue_add(issue).deliver + Mailer.deliver_issue_add(issue) mail2 = ActionMailer::Base.deliveries.last assert_equal mail_body(mail), mail_body(mail2) diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/i18n_test.rb --- a/test/unit/lib/redmine/i18n_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/i18n_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -105,12 +105,12 @@ now = Time.parse('2011-02-20 15:45:22') with_settings :time_format => '' do with_settings :date_format => '' do - assert_equal '02/20/2011 03:45 pm', format_time(now) - assert_equal '03:45 pm', format_time(now, false) + assert_equal '02/20/2011 03:45 PM', format_time(now) + assert_equal '03:45 PM', format_time(now, false) end with_settings :date_format => '%Y-%m-%d' do - assert_equal '2011-02-20 03:45 pm', format_time(now) - assert_equal '03:45 pm', format_time(now, false) + assert_equal '2011-02-20 03:45 PM', format_time(now) + assert_equal '03:45 PM', format_time(now, false) end end end @@ -131,15 +131,6 @@ end end - def test_time_format - set_language_if_valid 'en' - now = Time.now - Setting.date_format = '%d %m %Y' - Setting.time_format = '%H %M' - assert_equal now.strftime('%d %m %Y %H %M'), format_time(now) - assert_equal now.strftime('%H %M'), format_time(now, false) - end - def test_utc_time_format set_language_if_valid 'en' now = Time.now @@ -196,13 +187,15 @@ def test_languages_options options = languages_options - assert options.is_a?(Array) assert_equal valid_languages.size, options.size assert_nil options.detect {|option| !option.is_a?(Array)} assert_nil options.detect {|option| option.size != 2} assert_nil options.detect {|option| !option.first.is_a?(String) || !option.last.is_a?(String)} assert_include ["English", "en"], options + ja = "Japanese (\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e)" + ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) + assert_include [ja, "ja"], options end def test_locales_validness diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/info_test.rb --- a/test/unit/lib/redmine/info_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/info_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/menu_manager/mapper_test.rb --- a/test/unit/lib/redmine/menu_manager/mapper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/menu_manager/mapper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,8 +18,17 @@ require File.expand_path('../../../../../test_helper', __FILE__) class Redmine::MenuManager::MapperTest < ActiveSupport::TestCase - context "Mapper#initialize" do - should "be tested" + test "Mapper#initialize should define a root MenuNode if menu is not present in items" do + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + node = menu_mapper.menu_items + assert_not_nil node + assert_equal :root, node.name + end + + test "Mapper#initialize should use existing MenuNode if present" do + node = "foo" # just an arbitrary reference + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {:test_menu => node}) + assert_equal node, menu_mapper.menu_items end def test_push_onto_root diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/menu_manager/menu_helper_test.rb --- a/test/unit/lib/redmine/menu_manager/menu_helper_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/menu_manager/menu_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/menu_manager_test.rb --- a/test/unit/lib/redmine/menu_manager_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/menu_manager_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,11 +18,17 @@ require File.expand_path('../../../../test_helper', __FILE__) class Redmine::MenuManagerTest < ActiveSupport::TestCase - context "MenuManager#map" do - should "be tested" + def test_map_should_yield_a_mapper + assert_difference 'Redmine::MenuManager.items(:project_menu).size' do + Redmine::MenuManager.map :project_menu do |mapper| + assert_kind_of Redmine::MenuManager::Mapper, mapper + mapper.push :new_item, '/' + end + end end - context "MenuManager#items" do - should "be tested" + def test_items_should_return_menu_items + items = Redmine::MenuManager.items(:project_menu) + assert_kind_of Redmine::MenuManager::MenuNode, items.first end end diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/mime_type_test.rb --- a/test/unit/lib/redmine/mime_type_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/mime_type_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/notifiable_test.rb --- a/test/unit/lib/redmine/notifiable_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/notifiable_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/pagination_helper_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/lib/redmine/pagination_helper_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,34 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class ApplicationHelperTest < ActionView::TestCase + include Redmine::Pagination::Helper + + def test_per_page_options_should_return_usefull_values + with_settings :per_page_options => '10, 25, 50, 100' do + assert_equal [], per_page_options(10, 3) + assert_equal [], per_page_options(25, 3) + assert_equal [10, 25], per_page_options(10, 22) + assert_equal [10, 25], per_page_options(25, 22) + assert_equal [10, 25, 50], per_page_options(50, 22) + assert_equal [10, 25, 50], per_page_options(25, 26) + assert_equal [10, 25, 50, 100], per_page_options(25, 120) + end + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/pagination_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/lib/redmine/pagination_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,94 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../../../test_helper', __FILE__) + +class Redmine::PaginationTest < ActiveSupport::TestCase + + def setup + @klass = Redmine::Pagination::Paginator + end + + def test_count_is_zero + p = @klass.new 0, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + %w(first_page previous_page next_page last_page).each do |method| + assert_nil p.send(method), "#{method} was not nil" + end + assert_equal 0, p.first_item + assert_equal 0, p.last_item + assert_equal [], p.linked_pages + end + + def test_count_is_less_than_per_page + p = @klass.new 7, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + assert_equal 1, p.first_page + assert_nil p.previous_page + assert_nil p.next_page + assert_equal 1, p.last_page + assert_equal 1, p.first_item + assert_equal 7, p.last_item + assert_equal [], p.linked_pages + end + + def test_count_is_equal_to_per_page + p = @klass.new 10, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + assert_equal 1, p.first_page + assert_nil p.previous_page + assert_nil p.next_page + assert_equal 1, p.last_page + assert_equal 1, p.first_item + assert_equal 10, p.last_item + assert_equal [], p.linked_pages + end + + def test_2_pages + p = @klass.new 16, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + assert_equal 1, p.first_page + assert_nil p.previous_page + assert_equal 2, p.next_page + assert_equal 2, p.last_page + assert_equal 1, p.first_item + assert_equal 10, p.last_item + assert_equal [1, 2], p.linked_pages + end + + def test_many_pages + p = @klass.new 155, 10, 1 + + assert_equal 0, p.offset + assert_equal 10, p.per_page + assert_equal 1, p.first_page + assert_nil p.previous_page + assert_equal 2, p.next_page + assert_equal 16, p.last_page + assert_equal 1, p.first_item + assert_equal 10, p.last_item + assert_equal [1, 2, 3, 16], p.linked_pages + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/plugin_test.rb --- a/test/unit/lib/redmine/plugin_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/plugin_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,7 +18,6 @@ require File.expand_path('../../../../test_helper', __FILE__) class Redmine::PluginTest < ActiveSupport::TestCase - def setup @klass = Redmine::Plugin # In case some real plugins are installed @@ -55,7 +54,6 @@ def test_installed @klass.register(:foo) {} - assert_equal true, @klass.installed?(:foo) assert_equal false, @klass.installed?(:bar) end @@ -74,7 +72,6 @@ def test_delete_menu_item Redmine::MenuManager.map(:project_menu).push(:foo_menu_item, '/foo', :caption => 'Foo') - assert_difference 'Redmine::MenuManager.items(:project_menu).size', -1 do @klass.register :foo do delete_menu_item :project_menu, :foo_menu_item @@ -83,10 +80,21 @@ assert_nil Redmine::MenuManager.items(:project_menu).detect {|i| i.name == :foo_menu_item} end + def test_directory_with_override + @klass.register(:foo) do + directory '/path/to/foo' + end + assert_equal '/path/to/foo', @klass.find('foo').directory + end + + def test_directory_without_override + @klass.register(:foo) {} + assert_equal File.join(@klass.directory, 'foo'), @klass.find('foo').directory + end + def test_requires_redmine plugin = Redmine::Plugin.register(:foo) {} Redmine::VERSION.stubs(:to_a).returns([2, 1, 3, "stable", 10817]) - # Specific version without hash assert plugin.requires_redmine('2.1.3') assert plugin.requires_redmine('2.1') @@ -96,7 +104,6 @@ assert_raise Redmine::PluginRequirementError do plugin.requires_redmine('2.2') end - # Specific version assert plugin.requires_redmine(:version => '2.1.3') assert plugin.requires_redmine(:version => ['2.1.3', '2.2.0']) @@ -110,7 +117,6 @@ assert_raise Redmine::PluginRequirementError do plugin.requires_redmine(:version => '2.2') end - # Version range assert plugin.requires_redmine(:version => '2.0.0'..'2.2.4') assert plugin.requires_redmine(:version => '2.1.3'..'2.2.4') @@ -121,8 +127,6 @@ assert_raise Redmine::PluginRequirementError do plugin.requires_redmine(:version => '2.1.4'..'2.2.4') end - - # Version or higher assert plugin.requires_redmine(:version_or_higher => '0.1.0') assert plugin.requires_redmine(:version_or_higher => '2.1.3') @@ -138,12 +142,10 @@ def test_requires_redmine_plugin test = self other_version = '0.5.0' - @klass.register :other do name 'Other' version other_version end - @klass.register :foo do test.assert requires_redmine_plugin(:other, :version_or_higher => '0.1.0') test.assert requires_redmine_plugin(:other, :version_or_higher => other_version) @@ -151,7 +153,6 @@ test.assert_raise Redmine::PluginRequirementError do requires_redmine_plugin(:other, :version_or_higher => '99.0.0') end - test.assert requires_redmine_plugin(:other, :version => other_version) test.assert requires_redmine_plugin(:other, :version => [other_version, '99.0.0']) test.assert_raise Redmine::PluginRequirementError do @@ -170,7 +171,6 @@ test.assert_raise Redmine::PluginNotFound do requires_redmine_plugin(:missing, :version => '0.1.0') end - end end end diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/safe_attributes_test.rb --- a/test/unit/lib/redmine/safe_attributes_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/safe_attributes_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../../../../test_helper', __FILE__) begin - require 'mocha' + require 'mocha/setup' class BazaarAdapterTest < ActiveSupport::TestCase REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../../../../test_helper', __FILE__) begin - require 'mocha' + require 'mocha/setup' class CvsAdapterTest < ActiveSupport::TestCase REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s @@ -79,6 +79,22 @@ assert_equal "UTF-8", adpt2.path_encoding end + def test_root_url_path + to_test = { + ':pserver:cvs_user:cvs_password@123.456.789.123:9876/repo' => '/repo', + ':pserver:cvs_user:cvs_password@123.456.789.123/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server:/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server:9876/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server/path/repo' => '/path/repo', + ':ext:cvsservername:/path' => '/path' + } + + to_test.each do |string, expected| + assert_equal expected, Redmine::Scm::Adapters::CvsAdapter.new('foo', string).send(:root_url_path), "#{string} failed" + end + end + private def test_scm_version_for(scm_command_version, version) diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/scm/adapters/darcs_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/darcs_adapter_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/scm/adapters/darcs_adapter_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../../../../test_helper', __FILE__) begin - require 'mocha' + require 'mocha/setup' class DarcsAdapterTest < ActiveSupport::TestCase REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/scm/adapters/git_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../../../../test_helper', __FILE__) begin - require 'mocha' + require 'mocha/setup' class GitAdapterTest < ActiveSupport::TestCase REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s @@ -61,8 +61,10 @@ ) assert @adapter @char_1 = CHAR_1_HEX.dup + @str_felix_hex = FELIX_HEX.dup if @char_1.respond_to?(:force_encoding) @char_1.force_encoding('UTF-8') + @str_felix_hex.force_encoding('ASCII-8BIT') end end @@ -396,14 +398,10 @@ def test_last_rev_with_spaces_in_filename last_rev = @adapter.lastrev("filemane with spaces.txt", "ed5bb786bbda2dee66a2d50faf51429dbc043a7b") - str_felix_hex = FELIX_HEX.dup last_rev_author = last_rev.author - if last_rev_author.respond_to?(:force_encoding) - str_felix_hex.force_encoding('ASCII-8BIT') - end assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.scmid assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.identifier - assert_equal "#{str_felix_hex} ", + assert_equal "#{@str_felix_hex} ", last_rev.author assert_equal "2010-09-18 19:59:46".to_time, last_rev.time end @@ -426,6 +424,19 @@ end end + def test_latin_1_user_annotate + ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', '83ca5fd546063a'].each do |r1| + annotate = @adapter.annotate(" filename with a leading space.txt ", r1) + assert_kind_of Redmine::Scm::Adapters::Annotate, annotate + assert_equal 1, annotate.lines.size + assert_equal "And this is a file with a leading and trailing space...", + annotate.lines[0].strip + assert_equal "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c", + annotate.revisions[0].identifier + assert_equal @str_felix_hex, annotate.revisions[0].author + end + end + def test_entries_tag entries1 = @adapter.entries(nil, 'tag01.annotated', options = {:report_last_commit => true}) diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,7 +17,7 @@ require File.expand_path('../../../../../../test_helper', __FILE__) begin - require 'mocha' + require 'mocha/setup' class MercurialAdapterTest < ActiveSupport::TestCase HELPERS_DIR = Redmine::Scm::Adapters::MercurialAdapter::HELPERS_DIR @@ -89,8 +89,8 @@ adp = Redmine::Scm::Adapters::MercurialAdapter.new(repo) repo_path = adp.info.root_url.gsub(/\\/, "/") assert_equal REPOSITORY_PATH, repo_path - assert_equal '31', adp.info.lastrev.revision - assert_equal '31eeee7395c8',adp.info.lastrev.scmid + assert_equal '33', adp.info.lastrev.revision + assert_equal '2e6d54642923',adp.info.lastrev.scmid end end diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,7 +18,7 @@ require File.expand_path('../../../../../../test_helper', __FILE__) begin - require 'mocha' + require 'mocha/setup' class SubversionAdapterTest < ActiveSupport::TestCase diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/themes_test.rb --- a/test/unit/lib/redmine/themes_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/themes_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/unified_diff_test.rb --- a/test/unit/lib/redmine/unified_diff_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/unified_diff_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -221,6 +221,141 @@ assert_equal "test02.txt", diff[0].file_name end + def test_utf8_ja + ja = " text_tip_issue_end_day: " + ja += "\xe3\x81\x93\xe3\x81\xae\xe6\x97\xa5\xe3\x81\xab\xe7\xb5\x82\xe4\xba\x86\xe3\x81\x99\xe3\x82\x8b\xe3\x82\xbf\xe3\x82\xb9\xe3\x82\xaf" + ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new(read_diff_fixture('issue-12641-ja.diff'), :type => 'inline') + assert_equal 1, diff.size + assert_equal 12, diff.first.size + assert_equal ja, diff.first[4].html_line_left + end + end + + def test_utf8_ru + ru = " other: "\xd0\xbe\xd0\xba\xd0\xbe\xd0\xbb\xd0\xbe %{count} \xd1\x87\xd0\xb0\xd1\x81\xd0\xb0"" + ru.force_encoding('UTF-8') if ru.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new(read_diff_fixture('issue-12641-ru.diff'), :type => 'inline') + assert_equal 1, diff.size + assert_equal 8, diff.first.size + assert_equal ru, diff.first[3].html_line_left + end + end + + def test_offset_range_ascii_1 + raw = <<-DIFF +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-abc ++abcd + bbbb +DIFF + diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal "abc", diff.first[1].html_line_left + assert_equal "abcd", diff.first[1].html_line_right + end + + def test_offset_range_ascii_2 + raw = <<-DIFF +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-abc ++zabc + bbbb +DIFF + diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal "abc", diff.first[1].html_line_left + assert_equal "zabc", diff.first[1].html_line_right + end + + def test_offset_range_japanese_1 + ja1 = "\xe6\x97\xa5\xe6\x9c\xac" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-1.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + def test_offset_range_japanese_2 + ja1 = "\xe6\x97\xa5\xe6\x9c\xac" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe3\x81\xab\xe3\x81\xa3\xe3\x81\xbd\xe3\x82\x93\xe6\x97\xa5\xe6\x9c\xac" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-2.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + def test_offset_range_japanese_3 + # UTF-8 The 1st byte differs. + ja1 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xa8\x98" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe5\xa8\x98" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-3.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + def test_offset_range_japanese_4 + # UTF-8 The 2nd byte differs. + ja1 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xa8\x98" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x98" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-4.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + def test_offset_range_japanese_5 + # UTF-8 The 2nd byte differs. + ja1 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xa8\x98ok" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x98ok" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-5.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + private def read_diff_fixture(filename) diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/utils/date_calculation.rb --- a/test/unit/lib/redmine/utils/date_calculation.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/utils/date_calculation.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/views/builders/json_test.rb --- a/test/unit/lib/redmine/views/builders/json_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/views/builders/json_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/views/builders/xml_test.rb --- a/test/unit/lib/redmine/views/builders/xml_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/views/builders/xml_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/wiki_formatting/macros_test.rb --- a/test/unit/lib/redmine/wiki_formatting/macros_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/wiki_formatting/macros_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb --- a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -159,11 +159,11 @@ ) end - def test_acronyms + def test_abbreviations assert_html_output( - 'this is an acronym: GPL(General Public License)' => 'this is an acronym: GPL', - '2 letters JP(Jean-Philippe) acronym' => '2 letters JP acronym', - 'GPL(This is a double-quoted "title")' => 'GPL' + 'this is an abbreviation: GPL(General Public License)' => 'this is an abbreviation: GPL', + '2 letters JP(Jean-Philippe) abbreviation' => '2 letters JP abbreviation', + 'GPL(This is a double-quoted "title")' => 'GPL' ) end @@ -268,6 +268,42 @@ assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '') end + def test_tables_with_lists + raw = <<-RAW +This is a table with lists: + +|cell11|cell12| +|cell21|ordered list +# item +# item 2| +|cell31|unordered list +* item +* item 2| + +RAW + + expected = <<-EXPECTED +

      This is a table with lists:

      + + + + + + + + + + + + + + +
      cell11cell12
      cell21ordered list
      # item
      # item 2
      cell31unordered list
      * item
      * item 2
      +EXPECTED + + assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '') + end + def test_textile_should_not_mangle_brackets assert_equal '

      [msg1][msg2]

      ', to_html('[msg1][msg2]') end diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine/wiki_formatting_test.rb --- a/test/unit/lib/redmine/wiki_formatting_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine/wiki_formatting_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/lib/redmine_test.rb --- a/test/unit/lib/redmine_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/lib/redmine_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/mail_handler_test.rb --- a/test/unit/mail_handler_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/mail_handler_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -304,6 +304,51 @@ end end + def test_created_user_should_be_added_to_groups + group1 = Group.generate! + group2 = Group.generate! + + assert_difference 'User.count' do + submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'create', + :default_group => "#{group1.name},#{group2.name}" + ) + end + user = User.order('id DESC').first + assert_same_elements [group1, group2], user.groups + end + + def test_created_user_should_not_receive_account_information_with_no_account_info_option + assert_difference 'User.count' do + submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'create', + :no_account_notice => '1' + ) + end + + # only 1 email for the new issue notification + assert_equal 1, ActionMailer::Base.deliveries.size + email = ActionMailer::Base.deliveries.first + assert_include 'Ticket by unknown user', email.subject + end + + def test_created_user_should_have_mail_notification_to_none_with_no_notification_option + assert_difference 'User.count' do + submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'create', + :no_notification => '1' + ) + end + user = User.order('id DESC').first + assert_equal 'none', user.mail_notification + end + def test_add_issue_without_from_header Role.anonymous.add_permission!(:add_issues) assert_equal false, submit_email('ticket_without_from_header.eml') @@ -325,6 +370,15 @@ assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') end + def test_add_issue_with_invalid_project_should_be_assigned_to_default_project + issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email| + email.gsub!(/^Project:.+$/, 'Project: invalid') + end + assert issue.is_a?(Issue) + assert !issue.new_record? + assert_equal 'ecookbook', issue.project.identifier + end + def test_add_issue_with_localized_attributes User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr' issue = submit_email( @@ -447,6 +501,21 @@ assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest end + def test_multiple_inline_text_parts_should_be_appended_to_issue_description + issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'}) + assert_include 'first', issue.description + assert_include 'second', issue.description + assert_include 'third', issue.description + end + + def test_attachment_text_part_should_be_added_as_issue_attachment + issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'}) + assert_not_include 'Plain text attachment', issue.description + attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'} + assert_not_nil attachment + assert_include 'Plain text attachment', File.read(attachment.diskfile) + end + def test_add_issue_with_iso_8859_1_subject issue = submit_email( 'subject_as_iso-8859-1.eml', @@ -646,73 +715,73 @@ assert_equal 'This is a html-only email.', issue.description end - context "truncate emails based on the Setting" do - context "with no setting" do - setup do - Setting.mail_handler_body_delimiters = '' - end + test "truncate emails with no setting should add the entire email into the issue" do + with_settings :mail_handler_body_delimiters => '' do + issue = submit_email('ticket_on_given_project.eml') + assert_issue_created(issue) + assert issue.description.include?('---') + assert issue.description.include?('This paragraph is after the delimiter') + end + end - should "add the entire email into the issue" do - issue = submit_email('ticket_on_given_project.eml') - assert_issue_created(issue) - assert issue.description.include?('---') - assert issue.description.include?('This paragraph is after the delimiter') - end + test "truncate emails with a single string should truncate the email at the delimiter for the issue" do + with_settings :mail_handler_body_delimiters => '---' do + issue = submit_email('ticket_on_given_project.eml') + assert_issue_created(issue) + assert issue.description.include?('This paragraph is before delimiters') + assert issue.description.include?('--- This line starts with a delimiter') + assert !issue.description.match(/^---$/) + assert !issue.description.include?('This paragraph is after the delimiter') end + end - context "with a single string" do - setup do - Setting.mail_handler_body_delimiters = '---' - end - should "truncate the email at the delimiter for the issue" do - issue = submit_email('ticket_on_given_project.eml') - assert_issue_created(issue) - assert issue.description.include?('This paragraph is before delimiters') - assert issue.description.include?('--- This line starts with a delimiter') - assert !issue.description.match(/^---$/) - assert !issue.description.include?('This paragraph is after the delimiter') - end + test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do + with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do + journal = submit_email('issue_update_with_quoted_reply_above.eml') + assert journal.is_a?(Journal) + assert journal.notes.include?('An update to the issue by the sender.') + assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) + assert !journal.notes.include?('Looks like the JSON api for projects was missed.') end + end - context "with a single quoted reply (e.g. reply to a Redmine email notification)" do - setup do - Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---' - end - should "truncate the email at the delimiter with the quoted reply symbols (>)" do - journal = submit_email('issue_update_with_quoted_reply_above.eml') - assert journal.is_a?(Journal) - assert journal.notes.include?('An update to the issue by the sender.') - assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) - assert !journal.notes.include?('Looks like the JSON api for projects was missed.') - end + test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do + with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do + journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml') + assert journal.is_a?(Journal) + assert journal.notes.include?('An update to the issue by the sender.') + assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) + assert !journal.notes.include?('Looks like the JSON api for projects was missed.') end + end - context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do - setup do - Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---' - end - should "truncate the email at the delimiter with the quoted reply symbols (>)" do - journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml') - assert journal.is_a?(Journal) - assert journal.notes.include?('An update to the issue by the sender.') - assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) - assert !journal.notes.include?('Looks like the JSON api for projects was missed.') - end + test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do + with_settings :mail_handler_body_delimiters => "---\nBREAK" do + issue = submit_email('ticket_on_given_project.eml') + assert_issue_created(issue) + assert issue.description.include?('This paragraph is before delimiters') + assert !issue.description.include?('BREAK') + assert !issue.description.include?('This paragraph is between delimiters') + assert !issue.description.match(/^---$/) + assert !issue.description.include?('This paragraph is after the delimiter') end + end - context "with multiple strings" do - setup do - Setting.mail_handler_body_delimiters = "---\nBREAK" - end - should "truncate the email at the first delimiter found (BREAK)" do - issue = submit_email('ticket_on_given_project.eml') - assert_issue_created(issue) - assert issue.description.include?('This paragraph is before delimiters') - assert !issue.description.include?('BREAK') - assert !issue.description.include?('This paragraph is between delimiters') - assert !issue.description.match(/^---$/) - assert !issue.description.include?('This paragraph is after the delimiter') - end + def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored + with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do + issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'}) + assert issue.is_a?(Issue) + assert !issue.new_record? + assert_equal 0, issue.reload.attachments.size + end + end + + def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached + with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do + issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'}) + assert issue.is_a?(Issue) + assert !issue.new_record? + assert_equal 1, issue.reload.attachments.size end end @@ -741,14 +810,7 @@ assert_equal expected[0], user.login assert_equal expected[1], user.firstname assert_equal expected[2], user.lastname - end - end - - def test_new_user_from_attributes_should_respect_minimum_password_length - with_settings :password_min_length => 15 do - user = MailHandler.new_user_from_attributes('jsmith@example.net') - assert user.valid? - assert user.password.length >= 15 + assert_equal 'only_my_events', user.mail_notification end end @@ -778,6 +840,19 @@ assert_equal str2, user.lastname end + def test_extract_options_from_env_should_return_options + options = MailHandler.extract_options_from_env({ + 'tracker' => 'defect', + 'project' => 'foo', + 'unknown_user' => 'create' + }) + + assert_equal({ + :issue => {:tracker => 'defect', :project => 'foo'}, + :unknown_user => 'create' + }, options) + end + private def submit_email(filename, options={}) diff -r d98d22a98252 -r afce8026aaeb test/unit/mailer_test.rb --- a/test/unit/mailer_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/mailer_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -42,7 +42,7 @@ Setting.protocol = 'https' journal = Journal.find(3) - assert Mailer.issue_edit(journal).deliver + assert Mailer.deliver_issue_edit(journal) mail = last_email assert_not_nil mail @@ -81,7 +81,7 @@ Setting.protocol = 'http' journal = Journal.find(3) - assert Mailer.issue_edit(journal).deliver + assert Mailer.deliver_issue_edit(journal) mail = last_email assert_not_nil mail @@ -113,6 +113,16 @@ end end + def test_issue_edit_should_generate_url_with_hostname_for_relations + journal = Journal.new(:journalized => Issue.find(1), :user => User.find(1), :created_on => Time.now) + journal.details << JournalDetail.new(:property => 'relation', :prop_key => 'label_relates_to', :value => 2) + Mailer.deliver_issue_edit(journal) + assert_not_nil last_email + assert_select_email do + assert_select 'a[href=?]', 'http://mydomain.foo/issues/2', :text => 'Feature request #2' + end + end + def test_generated_links_with_prefix_and_no_relative_url_root Setting.default_language = 'en' relative_url_root = Redmine::Utils.relative_url_root @@ -121,7 +131,7 @@ Redmine::Utils.relative_url_root = nil journal = Journal.find(3) - assert Mailer.issue_edit(journal).deliver + assert Mailer.deliver_issue_edit(journal) mail = last_email assert_not_nil mail @@ -158,7 +168,7 @@ def test_email_headers issue = Issue.find(1) - Mailer.issue_add(issue).deliver + Mailer.deliver_issue_add(issue) mail = last_email assert_not_nil mail assert_equal 'OOF', mail.header['X-Auto-Response-Suppress'].to_s @@ -168,7 +178,7 @@ def test_email_headers_should_include_sender issue = Issue.find(1) - Mailer.issue_add(issue).deliver + Mailer.deliver_issue_add(issue) mail = last_email assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s end @@ -176,7 +186,7 @@ def test_plain_text_mail Setting.plain_text_mail = 1 journal = Journal.find(2) - Mailer.issue_edit(journal).deliver + Mailer.deliver_issue_edit(journal) mail = last_email assert_equal "text/plain; charset=UTF-8", mail.content_type assert_equal 0, mail.parts.size @@ -186,7 +196,7 @@ def test_html_mail Setting.plain_text_mail = 0 journal = Journal.find(2) - Mailer.issue_edit(journal).deliver + Mailer.deliver_issue_edit(journal) mail = last_email assert_equal 2, mail.parts.size assert mail.encoded.include?('href') @@ -210,19 +220,19 @@ end def test_should_not_send_email_without_recipient - news = News.find(:first) + news = News.first user = news.author # Remove members except news author news.project.memberships.each {|m| m.destroy unless m.user == user} - user.pref[:no_self_notified] = false + user.pref.no_self_notified = false user.pref.save User.current = user Mailer.news_added(news.reload).deliver assert_equal 1, last_email.bcc.size # nobody to notify - user.pref[:no_self_notified] = true + user.pref.no_self_notified = true user.pref.save User.current = user ActionMailer::Base.deliveries.clear @@ -231,19 +241,21 @@ end def test_issue_add_message_id - issue = Issue.find(1) - Mailer.issue_add(issue).deliver + issue = Issue.find(2) + Mailer.deliver_issue_add(issue) mail = last_email - assert_equal Mailer.message_id_for(issue), mail.message_id - assert_nil mail.references + assert_match /^redmine\.issue-2\.20060719190421\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.issue-2.20060719190421@example.net", mail.references end def test_issue_edit_message_id - journal = Journal.find(1) - Mailer.issue_edit(journal).deliver + journal = Journal.find(3) + journal.issue = Issue.find(2) + + Mailer.deliver_issue_edit(journal) mail = last_email - assert_equal Mailer.message_id_for(journal), mail.message_id - assert_include Mailer.message_id_for(journal.issue), mail.references + assert_match /^redmine\.journal-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.issue-2.20060719190421@example.net", mail.references assert_select_email do # link to the update assert_select "a[href=?]", @@ -255,8 +267,8 @@ message = Message.find(1) Mailer.message_posted(message).deliver mail = last_email - assert_equal Mailer.message_id_for(message), mail.message_id - assert_nil mail.references + assert_match /^redmine\.message-1\.\d+\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.message-1.20070512151532@example.net", mail.references assert_select_email do # link to the message assert_select "a[href=?]", @@ -269,8 +281,8 @@ message = Message.find(3) Mailer.message_posted(message).deliver mail = last_email - assert_equal Mailer.message_id_for(message), mail.message_id - assert_include Mailer.message_id_for(message.parent), mail.references + assert_match /^redmine\.message-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.message-1.20070512151532@example.net", mail.references assert_select_email do # link to the reply assert_select "a[href=?]", @@ -279,43 +291,62 @@ end end - context("#issue_add") do - setup do - ActionMailer::Base.deliveries.clear - Setting.bcc_recipients = '1' - @issue = Issue.find(1) + test "#issue_add should notify project members" do + issue = Issue.find(1) + assert Mailer.deliver_issue_add(issue) + assert last_email.bcc.include?('dlopper@somenet.foo') + end + + test "#issue_add should not notify project members that are not allow to view the issue" do + issue = Issue.find(1) + Role.find(2).remove_permission!(:view_issues) + assert Mailer.deliver_issue_add(issue) + assert !last_email.bcc.include?('dlopper@somenet.foo') + end + + test "#issue_add should notify issue watchers" do + issue = Issue.find(1) + user = User.find(9) + # minimal email notification options + user.pref.no_self_notified = '1' + user.pref.save + user.mail_notification = false + user.save + + Watcher.create!(:watchable => issue, :user => user) + assert Mailer.deliver_issue_add(issue) + assert last_email.bcc.include?(user.mail) + end + + test "#issue_add should not notify watchers not allowed to view the issue" do + issue = Issue.find(1) + user = User.find(9) + Watcher.create!(:watchable => issue, :user => user) + Role.non_member.remove_permission!(:view_issues) + assert Mailer.deliver_issue_add(issue) + assert !last_email.bcc.include?(user.mail) + end + + def test_issue_add_should_include_enabled_fields + Setting.default_language = 'en' + issue = Issue.find(2) + assert Mailer.deliver_issue_add(issue) + assert_mail_body_match '* Target version: 1.0', last_email + assert_select_email do + assert_select 'li', :text => 'Target version: 1.0' end + end - should "notify project members" do - assert Mailer.issue_add(@issue).deliver - assert last_email.bcc.include?('dlopper@somenet.foo') - end - - should "not notify project members that are not allow to view the issue" do - Role.find(2).remove_permission!(:view_issues) - assert Mailer.issue_add(@issue).deliver - assert !last_email.bcc.include?('dlopper@somenet.foo') - end - - should "notify issue watchers" do - user = User.find(9) - # minimal email notification options - user.pref[:no_self_notified] = '1' - user.pref.save - user.mail_notification = false - user.save - - Watcher.create!(:watchable => @issue, :user => user) - assert Mailer.issue_add(@issue).deliver - assert last_email.bcc.include?(user.mail) - end - - should "not notify watchers not allowed to view the issue" do - user = User.find(9) - Watcher.create!(:watchable => @issue, :user => user) - Role.non_member.remove_permission!(:view_issues) - assert Mailer.issue_add(@issue).deliver - assert !last_email.bcc.include?(user.mail) + def test_issue_add_should_not_include_disabled_fields + Setting.default_language = 'en' + issue = Issue.find(2) + tracker = issue.tracker + tracker.core_fields -= ['fixed_version_id'] + tracker.save! + assert Mailer.deliver_issue_add(issue) + assert_mail_body_no_match 'Target version', last_email + assert_select_email do + assert_select 'li', :text => /Target version/, :count => 0 end end @@ -324,7 +355,7 @@ issue = Issue.find(1) valid_languages.each do |lang| Setting.default_language = lang.to_s - assert Mailer.issue_add(issue).deliver + assert Mailer.deliver_issue_add(issue) end end @@ -332,7 +363,7 @@ journal = Journal.find(1) valid_languages.each do |lang| Setting.default_language = lang.to_s - assert Mailer.issue_edit(journal).deliver + assert Mailer.deliver_issue_edit(journal) end end @@ -342,11 +373,11 @@ journal.save! Role.find(2).add_permission! :view_private_notes - Mailer.issue_edit(journal).deliver + Mailer.deliver_issue_edit(journal) assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort Role.find(2).remove_permission! :view_private_notes - Mailer.issue_edit(journal).deliver + Mailer.deliver_issue_edit(journal) assert_equal %w(jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort end @@ -357,14 +388,41 @@ journal.save! Role.non_member.add_permission! :view_private_notes - Mailer.issue_edit(journal).deliver + Mailer.deliver_issue_edit(journal) assert_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort Role.non_member.remove_permission! :view_private_notes - Mailer.issue_edit(journal).deliver + Mailer.deliver_issue_edit(journal) assert_not_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort end + def test_issue_edit_should_mark_private_notes + journal = Journal.find(2) + journal.private_notes = true + journal.save! + + with_settings :default_language => 'en' do + Mailer.deliver_issue_edit(journal) + end + assert_mail_body_match '(Private notes)', last_email + end + + def test_issue_edit_with_relation_should_notify_users_who_can_see_the_related_issue + issue = Issue.generate! + private_issue = Issue.generate!(:is_private => true) + IssueRelation.create!(:issue_from => issue, :issue_to => private_issue, :relation_type => 'relates') + issue.reload + assert_equal 1, issue.journals.size + journal = issue.journals.first + ActionMailer::Base.deliveries.clear + + Mailer.deliver_issue_edit(journal) + last_email.bcc.each do |email| + user = User.find_by_mail(email) + assert private_issue.visible?(user), "Issue was not visible to #{user}" + end + end + def test_document_added document = Document.find(1) valid_languages.each do |lang| @@ -402,7 +460,7 @@ end def test_news_added - news = News.find(:first) + news = News.first valid_languages.each do |lang| Setting.default_language = lang.to_s assert Mailer.news_added(news).deliver @@ -418,7 +476,7 @@ end def test_message_posted - message = Message.find(:first) + message = Message.first recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author} recipients = recipients.compact.uniq valid_languages.each do |lang| @@ -583,12 +641,66 @@ def test_layout_should_include_the_emails_header with_settings :emails_header => "*Header content*" do + with_settings :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".header" do + assert_select "strong", :text => "Header content" + end + end + end + with_settings :plain_text_mail => 1 do + assert Mailer.test_email(User.find(1)).deliver + mail = last_email + assert_not_nil mail + assert_include "*Header content*", mail.body.decoded + end + end + end + + def test_layout_should_not_include_empty_emails_header + with_settings :emails_header => "", :plain_text_mail => 0 do assert Mailer.test_email(User.find(1)).deliver assert_select_email do - assert_select ".header" do - assert_select "strong", :text => "Header content" + assert_select ".header", false + end + end + end + + def test_layout_should_include_the_emails_footer + with_settings :emails_footer => "*Footer content*" do + with_settings :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".footer" do + assert_select "strong", :text => "Footer content" + end end end + with_settings :plain_text_mail => 1 do + assert Mailer.test_email(User.find(1)).deliver + mail = last_email + assert_not_nil mail + assert_include "\n-- \n", mail.body.decoded + assert_include "*Footer content*", mail.body.decoded + end + end + end + + def test_layout_should_not_include_empty_emails_footer + with_settings :emails_footer => "" do + with_settings :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".footer", false + end + end + with_settings :plain_text_mail => 1 do + assert Mailer.test_email(User.find(1)).deliver + mail = last_email + assert_not_nil mail + assert_not_include "\n-- \n", mail.body.decoded + end end end @@ -600,6 +712,33 @@ assert_include '<tag>', html_part.body.encoded end + def test_should_raise_delivery_errors_when_raise_delivery_errors_is_true + mail = Mailer.test_email(User.find(1)) + mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error")) + + ActionMailer::Base.raise_delivery_errors = true + assert_raise Exception, "delivery error" do + mail.deliver + end + ensure + ActionMailer::Base.raise_delivery_errors = false + end + + def test_should_log_delivery_errors_when_raise_delivery_errors_is_false + mail = Mailer.test_email(User.find(1)) + mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error")) + + Rails.logger.expects(:error).with("Email delivery error: delivery error") + ActionMailer::Base.raise_delivery_errors = false + assert_nothing_raised do + mail.deliver + end + end + + def test_mail_should_return_a_mail_message + assert_kind_of ::Mail::Message, Mailer.test_email(User.find(1)) + end + private def last_email diff -r d98d22a98252 -r afce8026aaeb test/unit/member_test.rb --- a/test/unit/member_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/member_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :groups_users, :watchers, :journals, :journal_details, @@ -122,70 +121,4 @@ assert_equal -1, a <=> b assert_equal 1, b <=> a end - - context "removing permissions" do - setup do - Watcher.delete_all("user_id = 9") - user = User.find(9) - # public - Watcher.create!(:watchable => Issue.find(1), :user => user) - # private - Watcher.create!(:watchable => Issue.find(4), :user => user) - Watcher.create!(:watchable => Message.find(7), :user => user) - Watcher.create!(:watchable => Wiki.find(2), :user => user) - Watcher.create!(:watchable => WikiPage.find(3), :user => user) - end - - context "of user" do - setup do - @member = Member.create!(:project => Project.find(2), :principal => User.find(9), :role_ids => [1, 2]) - end - - context "by deleting membership" do - should "prune watchers" do - assert_difference 'Watcher.count', -4 do - @member.destroy - end - end - end - - context "by updating roles" do - should "prune watchers" do - Role.find(2).remove_permission! :view_wiki_pages - member = Member.first(:order => 'id desc') - assert_difference 'Watcher.count', -2 do - member.role_ids = [2] - member.save - end - assert !Message.find(7).watched_by?(@user) - end - end - end - - context "of group" do - setup do - group = Group.find(10) - @member = Member.create!(:project => Project.find(2), :principal => group, :role_ids => [1, 2]) - group.users << User.find(9) - end - - context "by deleting membership" do - should "prune watchers" do - assert_difference 'Watcher.count', -4 do - @member.destroy - end - end - end - - context "by updating roles" do - should "prune watchers" do - Role.find(2).remove_permission! :view_wiki_pages - assert_difference 'Watcher.count', -2 do - @member.role_ids = [2] - @member.save - end - end - end - end - end end diff -r d98d22a98252 -r afce8026aaeb test/unit/message_test.rb --- a/test/unit/message_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/message_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -36,9 +36,9 @@ assert message.save @board.reload # topics count incremented - assert_equal topics_count+1, @board[:topics_count] + assert_equal topics_count + 1, @board[:topics_count] # messages count incremented - assert_equal messages_count+1, @board[:messages_count] + assert_equal messages_count + 1, @board[:messages_count] assert_equal message, @board.last_message # author should be watching the message assert message.watched_by?(@user) @@ -47,13 +47,13 @@ def test_reply topics_count = @board.topics_count messages_count = @board.messages_count - @message = Message.find(1) - replies_count = @message.replies_count + message = Message.find(1) + replies_count = message.replies_count reply_author = User.find(2) reply = Message.new(:board => @board, :subject => 'Test reply', :content => 'Test reply content', - :parent => @message, :author => reply_author) + :parent => message, :author => reply_author) assert reply.save @board.reload # same topics count @@ -61,42 +61,42 @@ # messages count incremented assert_equal messages_count+1, @board[:messages_count] assert_equal reply, @board.last_message - @message.reload + message.reload # replies count incremented - assert_equal replies_count+1, @message[:replies_count] - assert_equal reply, @message.last_reply + assert_equal replies_count+1, message[:replies_count] + assert_equal reply, message.last_reply # author should be watching the message - assert @message.watched_by?(reply_author) + assert message.watched_by?(reply_author) end def test_cannot_reply_to_locked_topic topics_count = @board.topics_count messages_count = @board.messages_count - @message = Message.find(1) - replies_count = @message.replies_count - assert_equal false, @message.locked - @message.locked = true - assert @message.save - assert_equal true, @message.locked + message = Message.find(1) + replies_count = message.replies_count + assert_equal false, message.locked + message.locked = true + assert message.save + assert_equal true, message.locked reply_author = User.find(2) reply = Message.new(:board => @board, :subject => 'Test reply', :content => 'Test reply content', - :parent => @message, :author => reply_author) + :parent => message, :author => reply_author) reply.save assert_equal 1, reply.errors.count end def test_moving_message_should_update_counters - @message = Message.find(1) + message = Message.find(1) assert_no_difference 'Message.count' do # Previous board assert_difference 'Board.find(1).topics_count', -1 do - assert_difference 'Board.find(1).messages_count', -(1 + @message.replies_count) do + assert_difference 'Board.find(1).messages_count', -(1 + message.replies_count) do # New board assert_difference 'Board.find(2).topics_count' do - assert_difference 'Board.find(2).messages_count', (1 + @message.replies_count) do - @message.update_attributes(:board_id => 2) + assert_difference 'Board.find(2).messages_count', (1 + message.replies_count) do + message.update_attributes(:board_id => 2) end end end diff -r d98d22a98252 -r afce8026aaeb test/unit/news_test.rb --- a/test/unit/news_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/news_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -21,7 +21,7 @@ fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news def valid_news - { :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.find(:first) } + { :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.first } end def setup diff -r d98d22a98252 -r afce8026aaeb test/unit/principal_test.rb --- a/test/unit/principal_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/principal_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -49,64 +49,60 @@ assert_equal [], Principal.not_member_of([]).sort end - context "#like" do - setup do - Principal.create!(:login => 'login') - Principal.create!(:login => 'login2') + def test_sorted_scope_should_sort_users_before_groups + scope = Principal.where("type <> ?", 'AnonymousUser') + expected_order = scope.all.sort do |a, b| + if a.is_a?(User) && b.is_a?(Group) + -1 + elsif a.is_a?(Group) && b.is_a?(User) + 1 + else + a.name.downcase <=> b.name.downcase + end + end + assert_equal expected_order.map(&:name).map(&:downcase), scope.sorted.all.map(&:name).map(&:downcase) + end - Principal.create!(:firstname => 'firstname') - Principal.create!(:firstname => 'firstname2') + test "like scope should search login" do + results = Principal.like('jsmi') - Principal.create!(:lastname => 'lastname') - Principal.create!(:lastname => 'lastname2') + assert results.any? + assert results.all? {|u| u.login.match(/jsmi/i) } + end - Principal.create!(:mail => 'mail@example.com') - Principal.create!(:mail => 'mail2@example.com') + test "like scope should search firstname" do + results = Principal.like('john') - @palmer = Principal.create!(:firstname => 'David', :lastname => 'Palmer') - end + assert results.any? + assert results.all? {|u| u.firstname.match(/john/i) } + end - should "search login" do - results = Principal.like('login') + test "like scope should search lastname" do + results = Principal.like('smi') - assert_equal 2, results.count - assert results.all? {|u| u.login.match(/login/) } - end + assert results.any? + assert results.all? {|u| u.lastname.match(/smi/i) } + end - should "search firstname" do - results = Principal.like('firstname') + test "like scope should search mail" do + results = Principal.like('somenet') - assert_equal 2, results.count - assert results.all? {|u| u.firstname.match(/firstname/) } - end + assert results.any? + assert results.all? {|u| u.mail.match(/somenet/i) } + end - should "search lastname" do - results = Principal.like('lastname') + test "like scope should search firstname and lastname" do + results = Principal.like('john smi') - assert_equal 2, results.count - assert results.all? {|u| u.lastname.match(/lastname/) } - end + assert_equal 1, results.count + assert_equal User.find(2), results.first + end - should "search mail" do - results = Principal.like('mail') + test "like scope should search lastname and firstname" do + results = Principal.like('smith joh') - assert_equal 2, results.count - assert results.all? {|u| u.mail.match(/mail/) } - end - - should "search firstname and lastname" do - results = Principal.like('david palm') - - assert_equal 1, results.count - assert_equal @palmer, results.first - end - - should "search lastname and firstname" do - results = Principal.like('palmer davi') - - assert_equal 1, results.count - assert_equal @palmer, results.first - end + assert_equal 1, results.count + assert_equal User.find(2), results.first end def test_like_scope_with_cyrillic_name diff -r d98d22a98252 -r afce8026aaeb test/unit/project_copy_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/project_copy_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,337 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class ProjectCopyTest < ActiveSupport::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :journals, :journal_details, + :enumerations, :users, :issue_categories, + :projects_trackers, + :custom_fields, + :custom_fields_projects, + :custom_fields_trackers, + :custom_values, + :roles, + :member_roles, + :members, + :enabled_modules, + :versions, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, + :groups_users, + :boards, :messages, + :repositories, + :news, :comments, + :documents + + def setup + ProjectCustomField.destroy_all + @source_project = Project.find(2) + @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test') + @project.trackers = @source_project.trackers + @project.enabled_module_names = @source_project.enabled_modules.collect(&:name) + end + + test "#copy should copy issues" do + @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'), + :subject => "copy issue status", + :tracker_id => 1, + :assigned_to_id => 2, + :project_id => @source_project.id) + assert @project.valid? + assert @project.issues.empty? + assert @project.copy(@source_project) + + assert_equal @source_project.issues.size, @project.issues.size + @project.issues.each do |issue| + assert issue.valid? + assert ! issue.assigned_to.blank? + assert_equal @project, issue.project + end + + copied_issue = @project.issues.where(:subject => "copy issue status").first + assert copied_issue + assert copied_issue.status + assert_equal "Closed", copied_issue.status.name + end + + test "#copy should copy issues custom values" do + field = IssueCustomField.generate!(:is_for_all => true, :trackers => Tracker.all) + issue = Issue.generate!(:project => @source_project, :subject => 'Custom field copy') + issue.custom_field_values = {field.id => 'custom'} + issue.save! + assert_equal 'custom', issue.reload.custom_field_value(field) + + assert @project.copy(@source_project) + copy = @project.issues.find_by_subject('Custom field copy') + assert copy + assert_equal 'custom', copy.reload.custom_field_value(field) + end + + test "#copy should copy issues assigned to a locked version" do + User.current = User.find(1) + assigned_version = Version.generate!(:name => "Assigned Issues") + @source_project.versions << assigned_version + Issue.generate!(:project => @source_project, + :fixed_version_id => assigned_version.id, + :subject => "copy issues assigned to a locked version") + assigned_version.update_attribute :status, 'locked' + + assert @project.copy(@source_project) + @project.reload + copied_issue = @project.issues.where(:subject => "copy issues assigned to a locked version").first + + assert copied_issue + assert copied_issue.fixed_version + assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name + assert_equal 'locked', copied_issue.fixed_version.status + end + + test "#copy should change the new issues to use the copied version" do + User.current = User.find(1) + assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open') + @source_project.versions << assigned_version + assert_equal 3, @source_project.versions.size + Issue.generate!(:project => @source_project, + :fixed_version_id => assigned_version.id, + :subject => "change the new issues to use the copied version") + + assert @project.copy(@source_project) + @project.reload + copied_issue = @project.issues.where(:subject => "change the new issues to use the copied version").first + + assert copied_issue + assert copied_issue.fixed_version + assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name + assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record + end + + test "#copy should keep target shared versions from other project" do + assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open', :project_id => 1, :sharing => 'system') + issue = Issue.generate!(:project => @source_project, + :fixed_version => assigned_version, + :subject => "keep target shared versions") + + assert @project.copy(@source_project) + @project.reload + copied_issue = @project.issues.where(:subject => "keep target shared versions").first + + assert copied_issue + assert_equal assigned_version, copied_issue.fixed_version + end + + test "#copy should copy issue relations" do + Setting.cross_project_issue_relations = '1' + + second_issue = Issue.generate!(:status_id => 5, + :subject => "copy issue relation", + :tracker_id => 1, + :assigned_to_id => 2, + :project_id => @source_project.id) + source_relation = IssueRelation.create!(:issue_from => Issue.find(4), + :issue_to => second_issue, + :relation_type => "relates") + source_relation_cross_project = IssueRelation.create!(:issue_from => Issue.find(1), + :issue_to => second_issue, + :relation_type => "duplicates") + + assert @project.copy(@source_project) + assert_equal @source_project.issues.count, @project.issues.count + copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4 + copied_second_issue = @project.issues.find_by_subject("copy issue relation") + + # First issue with a relation on project + assert_equal 1, copied_issue.relations.size, "Relation not copied" + copied_relation = copied_issue.relations.first + assert_equal "relates", copied_relation.relation_type + assert_equal copied_second_issue.id, copied_relation.issue_to_id + assert_not_equal source_relation.id, copied_relation.id + + # Second issue with a cross project relation + assert_equal 2, copied_second_issue.relations.size, "Relation not copied" + copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first + assert_equal "duplicates", copied_relation.relation_type + assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept" + assert_not_equal source_relation_cross_project.id, copied_relation.id + end + + test "#copy should copy issue attachments" do + issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id) + Attachment.create!(:container => issue, :file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 1) + @source_project.issues << issue + assert @project.copy(@source_project) + + copied_issue = @project.issues.where(:subject => "copy with attachment").first + assert_not_nil copied_issue + assert_equal 1, copied_issue.attachments.count, "Attachment not copied" + assert_equal "testfile.txt", copied_issue.attachments.first.filename + end + + test "#copy should copy memberships" do + assert @project.valid? + assert @project.members.empty? + assert @project.copy(@source_project) + + assert_equal @source_project.memberships.size, @project.memberships.size + @project.memberships.each do |membership| + assert membership + assert_equal @project, membership.project + end + end + + test "#copy should copy memberships with groups and additional roles" do + group = Group.create!(:lastname => "Copy group") + user = User.find(7) + group.users << user + # group role + Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2]) + member = Member.find_by_user_id_and_project_id(user.id, @source_project.id) + # additional role + member.role_ids = [1] + + assert @project.copy(@source_project) + member = Member.find_by_user_id_and_project_id(user.id, @project.id) + assert_not_nil member + assert_equal [1, 2], member.role_ids.sort + end + + test "#copy should copy project specific queries" do + assert @project.valid? + assert @project.queries.empty? + assert @project.copy(@source_project) + + assert_equal @source_project.queries.size, @project.queries.size + @project.queries.each do |query| + assert query + assert_equal @project, query.project + end + assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort + end + + test "#copy should copy versions" do + @source_project.versions << Version.generate! + @source_project.versions << Version.generate! + + assert @project.versions.empty? + assert @project.copy(@source_project) + + assert_equal @source_project.versions.size, @project.versions.size + @project.versions.each do |version| + assert version + assert_equal @project, version.project + end + end + + test "#copy should copy wiki" do + assert_difference 'Wiki.count' do + assert @project.copy(@source_project) + end + + assert @project.wiki + assert_not_equal @source_project.wiki, @project.wiki + assert_equal "Start page", @project.wiki.start_page + end + + test "#copy should copy wiki without wiki module" do + project = Project.new(:name => 'Copy Test', :identifier => 'copy-test', :enabled_module_names => []) + assert_difference 'Wiki.count' do + assert project.copy(@source_project) + end + + assert project.wiki + end + + test "#copy should copy wiki pages and content with hierarchy" do + assert_difference 'WikiPage.count', @source_project.wiki.pages.size do + assert @project.copy(@source_project) + end + + assert @project.wiki + assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size + + @project.wiki.pages.each do |wiki_page| + assert wiki_page.content + assert !@source_project.wiki.pages.include?(wiki_page) + end + + parent = @project.wiki.find_page('Parent_page') + child1 = @project.wiki.find_page('Child_page_1') + child2 = @project.wiki.find_page('Child_page_2') + assert_equal parent, child1.parent + assert_equal parent, child2.parent + end + + test "#copy should copy issue categories" do + assert @project.copy(@source_project) + + assert_equal 2, @project.issue_categories.size + @project.issue_categories.each do |issue_category| + assert !@source_project.issue_categories.include?(issue_category) + end + end + + test "#copy should copy boards" do + assert @project.copy(@source_project) + + assert_equal 1, @project.boards.size + @project.boards.each do |board| + assert !@source_project.boards.include?(board) + end + end + + test "#copy should change the new issues to use the copied issue categories" do + issue = Issue.find(4) + issue.update_attribute(:category_id, 3) + + assert @project.copy(@source_project) + + @project.issues.each do |issue| + assert issue.category + assert_equal "Stock management", issue.category.name # Same name + assert_not_equal IssueCategory.find(3), issue.category # Different record + end + end + + test "#copy should limit copy with :only option" do + assert @project.members.empty? + assert @project.issue_categories.empty? + assert @source_project.issues.any? + + assert @project.copy(@source_project, :only => ['members', 'issue_categories']) + + assert @project.members.any? + assert @project.issue_categories.any? + assert @project.issues.empty? + end + + test "#copy should copy subtasks" do + source = Project.generate!(:tracker_ids => [1]) + issue = Issue.generate_with_descendants!(:project => source) + project = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1]) + + assert_difference 'Project.count' do + assert_difference 'Issue.count', 1+issue.descendants.count do + assert project.copy(source.reload) + end + end + copy = Issue.where(:parent_id => nil).order("id DESC").first + assert_equal project, copy.project + assert_equal issue.descendants.count, copy.descendants.count + child_copy = copy.children.detect {|c| c.subject == 'Child1'} + assert child_copy.descendants.any? + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/project_members_inheritance_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/project_members_inheritance_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,263 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class ProjectMembersInheritanceTest < ActiveSupport::TestCase + fixtures :roles, :users + + def setup + @parent = Project.generate! + @member = Member.create!(:principal => User.find(2), :project => @parent, :role_ids => [1, 2]) + assert_equal 2, @member.reload.roles.size + end + + def test_project_created_with_inherit_members_disabled_should_not_inherit_members + assert_no_difference 'Member.count' do + project = Project.generate_with_parent!(@parent, :inherit_members => false) + + assert_equal 0, project.memberships.count + end + end + + def test_project_created_with_inherit_members_should_inherit_members + assert_difference 'Member.count', 1 do + project = Project.generate_with_parent!(@parent, :inherit_members => true) + project.reload + + assert_equal 1, project.memberships.count + member = project.memberships.first + assert_equal @member.principal, member.principal + assert_equal @member.roles.sort, member.roles.sort + end + end + + def test_turning_on_inherit_members_should_inherit_members + Project.generate_with_parent!(@parent, :inherit_members => false) + + assert_difference 'Member.count', 1 do + project = Project.order('id desc').first + project.inherit_members = true + project.save! + project.reload + + assert_equal 1, project.memberships.count + member = project.memberships.first + assert_equal @member.principal, member.principal + assert_equal @member.roles.sort, member.roles.sort + end + end + + def test_turning_off_inherit_members_should_remove_inherited_members + Project.generate_with_parent!(@parent, :inherit_members => true) + + assert_difference 'Member.count', -1 do + project = Project.order('id desc').first + project.inherit_members = false + project.save! + project.reload + + assert_equal 0, project.memberships.count + end + end + + def test_moving_a_root_project_under_a_parent_should_inherit_members + Project.generate!(:inherit_members => true) + project = Project.order('id desc').first + + assert_difference 'Member.count', 1 do + project.set_parent!(@parent) + project.reload + + assert_equal 1, project.memberships.count + member = project.memberships.first + assert_equal @member.principal, member.principal + assert_equal @member.roles.sort, member.roles.sort + end + end + + def test_moving_a_subproject_as_root_should_loose_inherited_members + Project.generate_with_parent!(@parent, :inherit_members => true) + project = Project.order('id desc').first + + assert_difference 'Member.count', -1 do + project.set_parent!(nil) + project.reload + + assert_equal 0, project.memberships.count + end + end + + def test_moving_a_subproject_to_another_parent_should_change_inherited_members + other_parent = Project.generate! + other_member = Member.create!(:principal => User.find(4), :project => other_parent, :role_ids => [3]) + other_member.reload + + Project.generate_with_parent!(@parent, :inherit_members => true) + project = Project.order('id desc').first + project.set_parent!(other_parent.reload) + project.reload + + assert_equal 1, project.memberships.count + member = project.memberships.first + assert_equal other_member.principal, member.principal + assert_equal other_member.roles.sort, member.roles.sort + end + + def test_inheritance_should_propagate_to_subprojects + project = Project.generate_with_parent!(@parent, :inherit_members => false) + subproject = Project.generate_with_parent!(project, :inherit_members => true) + project.reload + + assert_difference 'Member.count', 2 do + project.inherit_members = true + project.save + project.reload + subproject.reload + + assert_equal 1, project.memberships.count + assert_equal 1, subproject.memberships.count + member = subproject.memberships.first + assert_equal @member.principal, member.principal + assert_equal @member.roles.sort, member.roles.sort + end + end + + def test_inheritance_removal_should_propagate_to_subprojects + project = Project.generate_with_parent!(@parent, :inherit_members => true) + subproject = Project.generate_with_parent!(project, :inherit_members => true) + project.reload + + assert_difference 'Member.count', -2 do + project.inherit_members = false + project.save + project.reload + subproject.reload + + assert_equal 0, project.memberships.count + assert_equal 0, subproject.memberships.count + end + end + + def test_adding_a_member_should_propagate + project = Project.generate_with_parent!(@parent, :inherit_members => true) + + assert_difference 'Member.count', 2 do + member = Member.create!(:principal => User.find(4), :project => @parent, :role_ids => [1, 3]) + member.reload + + inherited_member = project.memberships.order('id desc').first + assert_equal member.principal, inherited_member.principal + assert_equal member.roles.sort, inherited_member.roles.sort + end + end + + def test_adding_a_member_should_not_propagate_if_child_does_not_inherit + project = Project.generate_with_parent!(@parent, :inherit_members => false) + + assert_difference 'Member.count', 1 do + member = Member.create!(:principal => User.find(4), :project => @parent, :role_ids => [1, 3]) + + assert_nil project.reload.memberships.detect {|m| m.principal == member.principal} + end + end + + def test_removing_a_member_should_propagate + project = Project.generate_with_parent!(@parent, :inherit_members => true) + + assert_difference 'Member.count', -2 do + @member.reload.destroy + project.reload + + assert_equal 0, project.memberships.count + end + end + + def test_adding_a_group_member_should_propagate_with_its_users + project = Project.generate_with_parent!(@parent, :inherit_members => true) + group = Group.generate! + user = User.find(4) + group.users << user + + assert_difference 'Member.count', 4 do + assert_difference 'MemberRole.count', 8 do + member = Member.create!(:principal => group, :project => @parent, :role_ids => [1, 3]) + project.reload + member.reload + + inherited_group_member = project.memberships.detect {|m| m.principal == group} + assert_not_nil inherited_group_member + assert_equal member.roles.sort, inherited_group_member.roles.sort + + inherited_user_member = project.memberships.detect {|m| m.principal == user} + assert_not_nil inherited_user_member + assert_equal member.roles.sort, inherited_user_member.roles.sort + end + end + end + + def test_removing_a_group_member_should_propagate + project = Project.generate_with_parent!(@parent, :inherit_members => true) + group = Group.generate! + user = User.find(4) + group.users << user + member = Member.create!(:principal => group, :project => @parent, :role_ids => [1, 3]) + + assert_difference 'Member.count', -4 do + assert_difference 'MemberRole.count', -8 do + member.destroy + project.reload + + inherited_group_member = project.memberships.detect {|m| m.principal == group} + assert_nil inherited_group_member + + inherited_user_member = project.memberships.detect {|m| m.principal == user} + assert_nil inherited_user_member + end + end + end + + def test_adding_user_who_use_is_already_a_member_to_parent_project_should_merge_roles + project = Project.generate_with_parent!(@parent, :inherit_members => true) + user = User.find(4) + Member.create!(:principal => user, :project => project, :role_ids => [1, 2]) + + assert_difference 'Member.count', 1 do + Member.create!(:principal => User.find(4), :project => @parent.reload, :role_ids => [1, 3]) + + member = project.reload.memberships.detect {|m| m.principal == user} + assert_not_nil member + assert_equal [1, 2, 3], member.roles.uniq.sort.map(&:id) + end + end + + def test_turning_on_inheritance_with_user_who_is_already_a_member_should_merge_roles + project = Project.generate_with_parent!(@parent) + user = @member.user + Member.create!(:principal => user, :project => project, :role_ids => [1, 3]) + project.reload + + assert_no_difference 'Member.count' do + project.inherit_members = true + project.save! + + member = project.reload.memberships.detect {|m| m.principal == user} + assert_not_nil member + assert_equal [1, 2, 3], member.roles.uniq.sort.map(&:id) + end + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/project_nested_set_test.rb --- a/test/unit/project_nested_set_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/project_nested_set_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/project_test.rb --- a/test/unit/project_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/project_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,7 +30,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :versions, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :groups_users, @@ -75,9 +74,30 @@ with_settings :default_projects_modules => ['issue_tracking', 'repository'] do assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names end + end - assert_equal Tracker.all.sort, Project.new.trackers.sort - assert_equal Tracker.find(1, 3).sort, Project.new(:tracker_ids => [1, 3]).trackers.sort + def test_default_trackers_should_match_default_tracker_ids_setting + with_settings :default_projects_tracker_ids => ['1', '3'] do + assert_equal Tracker.find(1, 3).sort, Project.new.trackers.sort + end + end + + def test_default_trackers_should_be_all_trackers_with_blank_setting + with_settings :default_projects_tracker_ids => nil do + assert_equal Tracker.all.sort, Project.new.trackers.sort + end + end + + def test_default_trackers_should_be_empty_with_empty_setting + with_settings :default_projects_tracker_ids => [] do + assert_equal [], Project.new.trackers + end + end + + def test_default_trackers_should_not_replace_initialized_trackers + with_settings :default_projects_tracker_ids => ['1', '3'] do + assert_equal Tracker.find(1, 2).sort, Project.new(:tracker_ids => [1, 2]).trackers.sort + end end def test_update @@ -155,7 +175,7 @@ # Assign an issue of a project to a version of a child project Issue.find(4).update_attribute :fixed_version_id, 4 - assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do + assert_no_difference "Project.where(:status => Project::STATUS_ARCHIVED).count" do assert_equal false, @ecookbook.archive end @ecookbook.reload @@ -183,7 +203,7 @@ # 2 active members assert_equal 2, @ecookbook.members.size # and 1 is locked - assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size + assert_equal 3, Member.where('project_id = ?', @ecookbook.id).all.size # some boards assert @ecookbook.boards.any? @@ -191,9 +211,9 @@ # make sure that the project non longer exists assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) } # make sure related data was removed - assert_nil Member.first(:conditions => {:project_id => @ecookbook.id}) - assert_nil Board.first(:conditions => {:project_id => @ecookbook.id}) - assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id}) + assert_nil Member.where(:project_id => @ecookbook.id).first + assert_nil Board.where(:project_id => @ecookbook.id).first + assert_nil Issue.where(:project_id => @ecookbook.id).first end def test_destroy_should_destroy_subtasks @@ -226,7 +246,7 @@ assert_equal 0, Board.count assert_equal 0, Message.count assert_equal 0, News.count - assert_equal 0, Query.count(:conditions => "project_id IS NOT NULL") + assert_equal 0, Query.where("project_id IS NOT NULL").count assert_equal 0, Repository.count assert_equal 0, Changeset.count assert_equal 0, Change.count @@ -240,7 +260,7 @@ assert_equal 0, WikiContent::Version.count assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").size assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").size - assert_equal 0, CustomValue.count(:conditions => {:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']}) + assert_equal 0, CustomValue.where(:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']).count end def test_move_an_orphan_project_to_a_root_project @@ -435,56 +455,67 @@ assert_equal [1,2], parent.rolled_up_trackers.collect(&:id) end - context "#rolled_up_versions" do - setup do - @project = Project.generate! - @parent_version_1 = Version.generate!(:project => @project) - @parent_version_2 = Version.generate!(:project => @project) - end + test "#rolled_up_trackers should ignore projects with issue_tracking module disabled" do + parent = Project.generate! + parent.trackers = Tracker.find([1, 2]) + child = Project.generate_with_parent!(parent) + child.trackers = Tracker.find([2, 3]) - should "include the versions for the current project" do - assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions - end + assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id).sort - should "include versions for a subproject" do - @subproject = Project.generate! - @subproject.set_parent!(@project) - @subproject_version = Version.generate!(:project => @subproject) + assert child.disable_module!(:issue_tracking) + parent.reload + assert_equal [1, 2], parent.rolled_up_trackers.collect(&:id).sort + end - assert_same_elements [ - @parent_version_1, - @parent_version_2, - @subproject_version - ], @project.rolled_up_versions - end + test "#rolled_up_versions should include the versions for the current project" do + project = Project.generate! + parent_version_1 = Version.generate!(:project => project) + parent_version_2 = Version.generate!(:project => project) + assert_same_elements [parent_version_1, parent_version_2], project.rolled_up_versions + end - should "include versions for a sub-subproject" do - @subproject = Project.generate! - @subproject.set_parent!(@project) - @sub_subproject = Project.generate! - @sub_subproject.set_parent!(@subproject) - @sub_subproject_version = Version.generate!(:project => @sub_subproject) + test "#rolled_up_versions should include versions for a subproject" do + project = Project.generate! + parent_version_1 = Version.generate!(:project => project) + parent_version_2 = Version.generate!(:project => project) + subproject = Project.generate_with_parent!(project) + subproject_version = Version.generate!(:project => subproject) - @project.reload + assert_same_elements [ + parent_version_1, + parent_version_2, + subproject_version + ], project.rolled_up_versions + end - assert_same_elements [ - @parent_version_1, - @parent_version_2, - @sub_subproject_version - ], @project.rolled_up_versions - end + test "#rolled_up_versions should include versions for a sub-subproject" do + project = Project.generate! + parent_version_1 = Version.generate!(:project => project) + parent_version_2 = Version.generate!(:project => project) + subproject = Project.generate_with_parent!(project) + sub_subproject = Project.generate_with_parent!(subproject) + sub_subproject_version = Version.generate!(:project => sub_subproject) + project.reload - should "only check active projects" do - @subproject = Project.generate! - @subproject.set_parent!(@project) - @subproject_version = Version.generate!(:project => @subproject) - assert @subproject.archive + assert_same_elements [ + parent_version_1, + parent_version_2, + sub_subproject_version + ], project.rolled_up_versions + end - @project.reload + test "#rolled_up_versions should only check active projects" do + project = Project.generate! + parent_version_1 = Version.generate!(:project => project) + parent_version_2 = Version.generate!(:project => project) + subproject = Project.generate_with_parent!(project) + subproject_version = Version.generate!(:project => subproject) + assert subproject.archive + project.reload - assert !@subproject.active? - assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions - end + assert !subproject.active? + assert_same_elements [parent_version_1, parent_version_2], project.rolled_up_versions end def test_shared_versions_none_sharing @@ -611,52 +642,49 @@ end end - context "enabled_modules" do - setup do - @project = Project.find(1) + test "enabled_modules should define module by names and preserve ids" do + @project = Project.find(1) + # Remove one module + modules = @project.enabled_modules.slice(0..-2) + assert modules.any? + assert_difference 'EnabledModule.count', -1 do + @project.enabled_module_names = modules.collect(&:name) end + @project.reload + # Ids should be preserved + assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort + end - should "define module by names and preserve ids" do - # Remove one module - modules = @project.enabled_modules.slice(0..-2) - assert modules.any? - assert_difference 'EnabledModule.count', -1 do - @project.enabled_module_names = modules.collect(&:name) - end - @project.reload - # Ids should be preserved - assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort - end + test "enabled_modules should enable a module" do + @project = Project.find(1) + @project.enabled_module_names = [] + @project.reload + assert_equal [], @project.enabled_module_names + #with string + @project.enable_module!("issue_tracking") + assert_equal ["issue_tracking"], @project.enabled_module_names + #with symbol + @project.enable_module!(:gantt) + assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names + #don't add a module twice + @project.enable_module!("issue_tracking") + assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names + end - should "enable a module" do - @project.enabled_module_names = [] - @project.reload - assert_equal [], @project.enabled_module_names - #with string - @project.enable_module!("issue_tracking") - assert_equal ["issue_tracking"], @project.enabled_module_names - #with symbol - @project.enable_module!(:gantt) - assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names - #don't add a module twice - @project.enable_module!("issue_tracking") - assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names - end - - should "disable a module" do - #with string - assert @project.enabled_module_names.include?("issue_tracking") - @project.disable_module!("issue_tracking") - assert ! @project.reload.enabled_module_names.include?("issue_tracking") - #with symbol - assert @project.enabled_module_names.include?("gantt") - @project.disable_module!(:gantt) - assert ! @project.reload.enabled_module_names.include?("gantt") - #with EnabledModule object - first_module = @project.enabled_modules.first - @project.disable_module!(first_module) - assert ! @project.reload.enabled_module_names.include?(first_module.name) - end + test "enabled_modules should disable a module" do + @project = Project.find(1) + #with string + assert @project.enabled_module_names.include?("issue_tracking") + @project.disable_module!("issue_tracking") + assert ! @project.reload.enabled_module_names.include?("issue_tracking") + #with symbol + assert @project.enabled_module_names.include?("gantt") + @project.disable_module!(:gantt) + assert ! @project.reload.enabled_module_names.include?("gantt") + #with EnabledModule object + first_module = @project.enabled_modules.first + @project.disable_module!(first_module) + assert ! @project.reload.enabled_module_names.include?(first_module.name) end def test_enabled_module_names_should_not_recreate_enabled_modules @@ -693,7 +721,7 @@ def test_activities_should_use_the_system_activities project = Project.find(1) - assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} ) + assert_equal project.activities, TimeEntryActivity.where(:active => true).all end @@ -707,7 +735,7 @@ def test_activities_should_not_include_the_inactive_project_specific_activities project = Project.find(1) - overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false}) + overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false}) assert overridden_activity.save! assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found" @@ -722,7 +750,7 @@ end def test_activities_should_handle_nils - overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)}) + overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.first}) TimeEntryActivity.delete_all # No activities @@ -737,7 +765,7 @@ def test_activities_should_override_system_activities_with_project_activities project = Project.find(1) - parent_activity = TimeEntryActivity.find(:first) + parent_activity = TimeEntryActivity.first overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity}) assert overridden_activity.save! @@ -747,7 +775,7 @@ def test_activities_should_include_inactive_activities_if_specified project = Project.find(1) - overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false}) + overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false}) assert overridden_activity.save! assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found" @@ -775,438 +803,135 @@ assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'} end - context "Project#copy" do - setup do - ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests - Project.destroy_all :identifier => "copy-test" - @source_project = Project.find(2) - @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test') - @project.trackers = @source_project.trackers - @project.enabled_module_names = @source_project.enabled_modules.collect(&:name) - end - - should "copy issues" do - @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'), - :subject => "copy issue status", - :tracker_id => 1, - :assigned_to_id => 2, - :project_id => @source_project.id) - assert @project.valid? - assert @project.issues.empty? - assert @project.copy(@source_project) - - assert_equal @source_project.issues.size, @project.issues.size - @project.issues.each do |issue| - assert issue.valid? - assert ! issue.assigned_to.blank? - assert_equal @project, issue.project - end - - copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"}) - assert copied_issue - assert copied_issue.status - assert_equal "Closed", copied_issue.status.name - end - - should "copy issues assigned to a locked version" do - User.current = User.find(1) - assigned_version = Version.generate!(:name => "Assigned Issues") - @source_project.versions << assigned_version - Issue.generate!(:project => @source_project, - :fixed_version_id => assigned_version.id, - :subject => "copy issues assigned to a locked version") - assigned_version.update_attribute :status, 'locked' - - assert @project.copy(@source_project) - @project.reload - copied_issue = @project.issues.first(:conditions => {:subject => "copy issues assigned to a locked version"}) - - assert copied_issue - assert copied_issue.fixed_version - assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name - assert_equal 'locked', copied_issue.fixed_version.status - end - - should "change the new issues to use the copied version" do - User.current = User.find(1) - assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open') - @source_project.versions << assigned_version - assert_equal 3, @source_project.versions.size - Issue.generate!(:project => @source_project, - :fixed_version_id => assigned_version.id, - :subject => "change the new issues to use the copied version") - - assert @project.copy(@source_project) - @project.reload - copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"}) - - assert copied_issue - assert copied_issue.fixed_version - assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name - assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record - end - - should "keep target shared versions from other project" do - assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open', :project_id => 1, :sharing => 'system') - issue = Issue.generate!(:project => @source_project, - :fixed_version => assigned_version, - :subject => "keep target shared versions") - - assert @project.copy(@source_project) - @project.reload - copied_issue = @project.issues.first(:conditions => {:subject => "keep target shared versions"}) - - assert copied_issue - assert_equal assigned_version, copied_issue.fixed_version - end - - should "copy issue relations" do - Setting.cross_project_issue_relations = '1' - - second_issue = Issue.generate!(:status_id => 5, - :subject => "copy issue relation", - :tracker_id => 1, - :assigned_to_id => 2, - :project_id => @source_project.id) - source_relation = IssueRelation.create!(:issue_from => Issue.find(4), - :issue_to => second_issue, - :relation_type => "relates") - source_relation_cross_project = IssueRelation.create!(:issue_from => Issue.find(1), - :issue_to => second_issue, - :relation_type => "duplicates") - - assert @project.copy(@source_project) - assert_equal @source_project.issues.count, @project.issues.count - copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4 - copied_second_issue = @project.issues.find_by_subject("copy issue relation") - - # First issue with a relation on project - assert_equal 1, copied_issue.relations.size, "Relation not copied" - copied_relation = copied_issue.relations.first - assert_equal "relates", copied_relation.relation_type - assert_equal copied_second_issue.id, copied_relation.issue_to_id - assert_not_equal source_relation.id, copied_relation.id - - # Second issue with a cross project relation - assert_equal 2, copied_second_issue.relations.size, "Relation not copied" - copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first - assert_equal "duplicates", copied_relation.relation_type - assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept" - assert_not_equal source_relation_cross_project.id, copied_relation.id - end - - should "copy issue attachments" do - issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id) - Attachment.create!(:container => issue, :file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 1) - @source_project.issues << issue - assert @project.copy(@source_project) - - copied_issue = @project.issues.first(:conditions => {:subject => "copy with attachment"}) - assert_not_nil copied_issue - assert_equal 1, copied_issue.attachments.count, "Attachment not copied" - assert_equal "testfile.txt", copied_issue.attachments.first.filename - end - - should "copy memberships" do - assert @project.valid? - assert @project.members.empty? - assert @project.copy(@source_project) - - assert_equal @source_project.memberships.size, @project.memberships.size - @project.memberships.each do |membership| - assert membership - assert_equal @project, membership.project - end - end - - should "copy memberships with groups and additional roles" do - group = Group.create!(:lastname => "Copy group") - user = User.find(7) - group.users << user - # group role - Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2]) - member = Member.find_by_user_id_and_project_id(user.id, @source_project.id) - # additional role - member.role_ids = [1] - - assert @project.copy(@source_project) - member = Member.find_by_user_id_and_project_id(user.id, @project.id) - assert_not_nil member - assert_equal [1, 2], member.role_ids.sort - end - - should "copy project specific queries" do - assert @project.valid? - assert @project.queries.empty? - assert @project.copy(@source_project) - - assert_equal @source_project.queries.size, @project.queries.size - @project.queries.each do |query| - assert query - assert_equal @project, query.project - end - assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort - end - - should "copy versions" do - @source_project.versions << Version.generate! - @source_project.versions << Version.generate! - - assert @project.versions.empty? - assert @project.copy(@source_project) - - assert_equal @source_project.versions.size, @project.versions.size - @project.versions.each do |version| - assert version - assert_equal @project, version.project - end - end - - should "copy wiki" do - assert_difference 'Wiki.count' do - assert @project.copy(@source_project) - end - - assert @project.wiki - assert_not_equal @source_project.wiki, @project.wiki - assert_equal "Start page", @project.wiki.start_page - end - - should "copy wiki pages and content with hierarchy" do - assert_difference 'WikiPage.count', @source_project.wiki.pages.size do - assert @project.copy(@source_project) - end - - assert @project.wiki - assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size - - @project.wiki.pages.each do |wiki_page| - assert wiki_page.content - assert !@source_project.wiki.pages.include?(wiki_page) - end - - parent = @project.wiki.find_page('Parent_page') - child1 = @project.wiki.find_page('Child_page_1') - child2 = @project.wiki.find_page('Child_page_2') - assert_equal parent, child1.parent - assert_equal parent, child2.parent - end - - should "copy issue categories" do - assert @project.copy(@source_project) - - assert_equal 2, @project.issue_categories.size - @project.issue_categories.each do |issue_category| - assert !@source_project.issue_categories.include?(issue_category) - end - end - - should "copy boards" do - assert @project.copy(@source_project) - - assert_equal 1, @project.boards.size - @project.boards.each do |board| - assert !@source_project.boards.include?(board) - end - end - - should "change the new issues to use the copied issue categories" do - issue = Issue.find(4) - issue.update_attribute(:category_id, 3) - - assert @project.copy(@source_project) - - @project.issues.each do |issue| - assert issue.category - assert_equal "Stock management", issue.category.name # Same name - assert_not_equal IssueCategory.find(3), issue.category # Different record - end - end - - should "limit copy with :only option" do - assert @project.members.empty? - assert @project.issue_categories.empty? - assert @source_project.issues.any? - - assert @project.copy(@source_project, :only => ['members', 'issue_categories']) - - assert @project.members.any? - assert @project.issue_categories.any? - assert @project.issues.empty? - end + test "#start_date should be nil if there are no issues on the project" do + project = Project.generate! + assert_nil project.start_date end - def test_copy_should_copy_subtasks - source = Project.generate!(:tracker_ids => [1]) - issue = Issue.generate_with_descendants!(:project => source) - project = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1]) + test "#start_date should be nil when issues have no start date" do + project = Project.generate! + project.trackers << Tracker.generate! + early = 7.days.ago.to_date + Issue.generate!(:project => project, :start_date => nil) - assert_difference 'Project.count' do - assert_difference 'Issue.count', 1+issue.descendants.count do - assert project.copy(source.reload) - end - end - copy = Issue.where(:parent_id => nil).order("id DESC").first - assert_equal project, copy.project - assert_equal issue.descendants.count, copy.descendants.count - child_copy = copy.children.detect {|c| c.subject == 'Child1'} - assert child_copy.descendants.any? + assert_nil project.start_date end - context "#start_date" do - setup do - ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests - @project = Project.generate!(:identifier => 'test0') - @project.trackers << Tracker.generate! - end + test "#start_date should be the earliest start date of it's issues" do + project = Project.generate! + project.trackers << Tracker.generate! + early = 7.days.ago.to_date + Issue.generate!(:project => project, :start_date => Date.today) + Issue.generate!(:project => project, :start_date => early) - should "be nil if there are no issues on the project" do - assert_nil @project.start_date - end - - should "be tested when issues have no start date" - - should "be the earliest start date of it's issues" do - early = 7.days.ago.to_date - Issue.generate!(:project => @project, :start_date => Date.today) - Issue.generate!(:project => @project, :start_date => early) - - assert_equal early, @project.start_date - end - + assert_equal early, project.start_date end - context "#due_date" do - setup do - ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests - @project = Project.generate!(:identifier => 'test0') - @project.trackers << Tracker.generate! - end - - should "be nil if there are no issues on the project" do - assert_nil @project.due_date - end - - should "be tested when issues have no due date" - - should "be the latest due date of it's issues" do - future = 7.days.from_now.to_date - Issue.generate!(:project => @project, :due_date => future) - Issue.generate!(:project => @project, :due_date => Date.today) - - assert_equal future, @project.due_date - end - - should "be the latest due date of it's versions" do - future = 7.days.from_now.to_date - @project.versions << Version.generate!(:effective_date => future) - @project.versions << Version.generate!(:effective_date => Date.today) - - - assert_equal future, @project.due_date - - end - - should "pick the latest date from it's issues and versions" do - future = 7.days.from_now.to_date - far_future = 14.days.from_now.to_date - Issue.generate!(:project => @project, :due_date => far_future) - @project.versions << Version.generate!(:effective_date => future) - - assert_equal far_future, @project.due_date - end - + test "#due_date should be nil if there are no issues on the project" do + project = Project.generate! + assert_nil project.due_date end - context "Project#completed_percent" do - setup do - ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests - @project = Project.generate!(:identifier => 'test0') - @project.trackers << Tracker.generate! - end + test "#due_date should be nil if there are no issues with due dates" do + project = Project.generate! + project.trackers << Tracker.generate! + Issue.generate!(:project => project, :due_date => nil) - context "no versions" do - should "be 100" do - assert_equal 100, @project.completed_percent - end - end - - context "with versions" do - should "return 0 if the versions have no issues" do - Version.generate!(:project => @project) - Version.generate!(:project => @project) - - assert_equal 0, @project.completed_percent - end - - should "return 100 if the version has only closed issues" do - v1 = Version.generate!(:project => @project) - Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1) - v2 = Version.generate!(:project => @project) - Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2) - - assert_equal 100, @project.completed_percent - end - - should "return the averaged completed percent of the versions (not weighted)" do - v1 = Version.generate!(:project => @project) - Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1) - v2 = Version.generate!(:project => @project) - Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2) - - assert_equal 50, @project.completed_percent - end - - end + assert_nil project.due_date end - context "#notified_users" do - setup do - @project = Project.generate! - @role = Role.generate! + test "#due_date should be the latest due date of it's issues" do + project = Project.generate! + project.trackers << Tracker.generate! + future = 7.days.from_now.to_date + Issue.generate!(:project => project, :due_date => future) + Issue.generate!(:project => project, :due_date => Date.today) - @user_with_membership_notification = User.generate!(:mail_notification => 'selected') - Member.create!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true) - - @all_events_user = User.generate!(:mail_notification => 'all') - Member.create!(:project => @project, :roles => [@role], :principal => @all_events_user) - - @no_events_user = User.generate!(:mail_notification => 'none') - Member.create!(:project => @project, :roles => [@role], :principal => @no_events_user) - - @only_my_events_user = User.generate!(:mail_notification => 'only_my_events') - Member.create!(:project => @project, :roles => [@role], :principal => @only_my_events_user) - - @only_assigned_user = User.generate!(:mail_notification => 'only_assigned') - Member.create!(:project => @project, :roles => [@role], :principal => @only_assigned_user) - - @only_owned_user = User.generate!(:mail_notification => 'only_owner') - Member.create!(:project => @project, :roles => [@role], :principal => @only_owned_user) - end - - should "include members with a mail notification" do - assert @project.notified_users.include?(@user_with_membership_notification) - end - - should "include users with the 'all' notification option" do - assert @project.notified_users.include?(@all_events_user) - end - - should "not include users with the 'none' notification option" do - assert !@project.notified_users.include?(@no_events_user) - end - - should "not include users with the 'only_my_events' notification option" do - assert !@project.notified_users.include?(@only_my_events_user) - end - - should "not include users with the 'only_assigned' notification option" do - assert !@project.notified_users.include?(@only_assigned_user) - end - - should "not include users with the 'only_owner' notification option" do - assert !@project.notified_users.include?(@only_owned_user) - end + assert_equal future, project.due_date end + test "#due_date should be the latest due date of it's versions" do + project = Project.generate! + future = 7.days.from_now.to_date + project.versions << Version.generate!(:effective_date => future) + project.versions << Version.generate!(:effective_date => Date.today) + + assert_equal future, project.due_date + end + + test "#due_date should pick the latest date from it's issues and versions" do + project = Project.generate! + project.trackers << Tracker.generate! + future = 7.days.from_now.to_date + far_future = 14.days.from_now.to_date + Issue.generate!(:project => project, :due_date => far_future) + project.versions << Version.generate!(:effective_date => future) + + assert_equal far_future, project.due_date + end + + test "#completed_percent with no versions should be 100" do + project = Project.generate! + assert_equal 100, project.completed_percent + end + + test "#completed_percent with versions should return 0 if the versions have no issues" do + project = Project.generate! + Version.generate!(:project => project) + Version.generate!(:project => project) + + assert_equal 0, project.completed_percent + end + + test "#completed_percent with versions should return 100 if the version has only closed issues" do + project = Project.generate! + project.trackers << Tracker.generate! + v1 = Version.generate!(:project => project) + Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1) + v2 = Version.generate!(:project => project) + Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2) + + assert_equal 100, project.completed_percent + end + + test "#completed_percent with versions should return the averaged completed percent of the versions (not weighted)" do + project = Project.generate! + project.trackers << Tracker.generate! + v1 = Version.generate!(:project => project) + Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1) + v2 = Version.generate!(:project => project) + Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2) + + assert_equal 50, project.completed_percent + end + + test "#notified_users" do + project = Project.generate! + role = Role.generate! + + user_with_membership_notification = User.generate!(:mail_notification => 'selected') + Member.create!(:project => project, :roles => [role], :principal => user_with_membership_notification, :mail_notification => true) + + all_events_user = User.generate!(:mail_notification => 'all') + Member.create!(:project => project, :roles => [role], :principal => all_events_user) + + no_events_user = User.generate!(:mail_notification => 'none') + Member.create!(:project => project, :roles => [role], :principal => no_events_user) + + only_my_events_user = User.generate!(:mail_notification => 'only_my_events') + Member.create!(:project => project, :roles => [role], :principal => only_my_events_user) + + only_assigned_user = User.generate!(:mail_notification => 'only_assigned') + Member.create!(:project => project, :roles => [role], :principal => only_assigned_user) + + only_owned_user = User.generate!(:mail_notification => 'only_owner') + Member.create!(:project => project, :roles => [role], :principal => only_owned_user) + + assert project.notified_users.include?(user_with_membership_notification), "should include members with a mail notification" + assert project.notified_users.include?(all_events_user), "should include users with the 'all' notification option" + assert !project.notified_users.include?(no_events_user), "should not include users with the 'none' notification option" + assert !project.notified_users.include?(only_my_events_user), "should not include users with the 'only_my_events' notification option" + assert !project.notified_users.include?(only_assigned_user), "should not include users with the 'only_assigned' notification option" + assert !project.notified_users.include?(only_owned_user), "should not include users with the 'only_owner' notification option" + end end diff -r d98d22a98252 -r afce8026aaeb test/unit/query_test.rb --- a/test/unit/query_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/query_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -28,21 +28,71 @@ :projects_trackers, :custom_fields_trackers + def test_query_with_roles_visibility_should_validate_roles + set_language_if_valid 'en' + query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES) + assert !query.save + assert_include "Roles can't be blank", query.errors.full_messages + query.role_ids = [1, 2] + assert query.save + end + + def test_changing_roles_visibility_should_clear_roles + query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2]) + assert_equal 2, query.roles.count + + query.visibility = IssueQuery::VISIBILITY_PUBLIC + query.save! + assert_equal 0, query.roles.count + end + + def test_available_filters_should_be_ordered + set_language_if_valid 'en' + query = IssueQuery.new + assert_equal 0, query.available_filters.keys.index('status_id') + expected_order = [ + "Status", + "Project", + "Tracker", + "Priority" + ] + assert_equal expected_order, + (query.available_filters.values.map{|v| v[:name]} & expected_order) + end + + def test_available_filters_with_custom_fields_should_be_ordered + set_language_if_valid 'en' + UserCustomField.create!( + :name => 'order test', :field_format => 'string', + :is_for_all => true, :is_filter => true + ) + query = IssueQuery.new + expected_order = [ + "Searchable field", + "Database", + "Project's Development status", + "Author's order test", + "Assignee's order test" + ] + assert_equal expected_order, + (query.available_filters.values.map{|v| v[:name]} & expected_order) + end + def test_custom_fields_for_all_projects_should_be_available_in_global_queries - query = Query.new(:project => nil, :name => '_') + query = IssueQuery.new(:project => nil, :name => '_') assert query.available_filters.has_key?('cf_1') assert !query.available_filters.has_key?('cf_3') end def test_system_shared_versions_should_be_available_in_global_queries Version.find(2).update_attribute :sharing, 'system' - query = Query.new(:project => nil, :name => '_') + query = IssueQuery.new(:project => nil, :name => '_') assert query.available_filters.has_key?('fixed_version_id') assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'} end def test_project_filter_in_global_queries - query = Query.new(:project => nil, :name => '_') + query = IssueQuery.new(:project => nil, :name => '_') project_filter = query.available_filters["project_id"] assert_not_nil project_filter project_ids = project_filter[:values].map{|p| p[1]} @@ -63,9 +113,9 @@ end def assert_query_statement_includes(query, condition) - assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}" + assert_include condition, query.statement end - + def assert_query_result(expected, query) assert_nothing_raised do assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort @@ -75,14 +125,14 @@ def test_query_should_allow_shared_versions_for_a_project_query subproject_version = Version.find(4) - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s]) assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')") end def test_query_with_multiple_custom_fields - query = Query.find(1) + query = IssueQuery.find(1) assert query.valid? assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')") issues = find_issues_with_query(query) @@ -91,7 +141,7 @@ end def test_operator_none - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('fixed_version_id', '!*', ['']) query.add_filter('cf_1', '!*', ['']) assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL") @@ -100,7 +150,7 @@ end def test_operator_none_for_integer - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('estimated_hours', '!*', ['']) issues = find_issues_with_query(query) assert !issues.empty? @@ -108,7 +158,7 @@ end def test_operator_none_for_date - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('start_date', '!*', ['']) issues = find_issues_with_query(query) assert !issues.empty? @@ -116,7 +166,7 @@ end def test_operator_none_for_string_custom_field - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('cf_2', '!*', ['']) assert query.has_filter?('cf_2') issues = find_issues_with_query(query) @@ -125,7 +175,7 @@ end def test_operator_all - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('fixed_version_id', '*', ['']) query.add_filter('cf_1', '*', ['']) assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL") @@ -134,7 +184,7 @@ end def test_operator_all_for_date - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('start_date', '*', ['']) issues = find_issues_with_query(query) assert !issues.empty? @@ -142,7 +192,7 @@ end def test_operator_all_for_string_custom_field - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('cf_2', '*', ['']) assert query.has_filter?('cf_2') issues = find_issues_with_query(query) @@ -151,7 +201,7 @@ end def test_numeric_filter_should_not_accept_non_numeric_values - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter('estimated_hours', '=', ['a']) assert query.has_filter?('estimated_hours') @@ -161,7 +211,7 @@ def test_operator_is_on_float Issue.update_all("estimated_hours = 171.2", "id=2") - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter('estimated_hours', '=', ['171.20']) issues = find_issues_with_query(query) assert_equal 1, issues.size @@ -169,12 +219,12 @@ end def test_operator_is_on_integer_custom_field - f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all) CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter("cf_#{f.id}", '=', ['12']) issues = find_issues_with_query(query) assert_equal 1, issues.size @@ -182,12 +232,12 @@ end def test_operator_is_on_integer_custom_field_should_accept_negative_value - f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all) CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12') CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter("cf_#{f.id}", '=', ['-12']) assert query.valid? issues = find_issues_with_query(query) @@ -196,12 +246,12 @@ end def test_operator_is_on_float_custom_field - f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) + f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all) CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7') CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter("cf_#{f.id}", '=', ['12.7']) issues = find_issues_with_query(query) assert_equal 1, issues.size @@ -209,12 +259,12 @@ end def test_operator_is_on_float_custom_field_should_accept_negative_value - f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) + f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all) CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7') CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter("cf_#{f.id}", '=', ['-12.7']) assert query.valid? issues = find_issues_with_query(query) @@ -224,17 +274,17 @@ def test_operator_is_on_multi_list_custom_field f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, - :possible_values => ['value1', 'value2', 'value3'], :multiple => true) + :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all) CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter("cf_#{f.id}", '=', ['value1']) issues = find_issues_with_query(query) assert_equal [1, 3], issues.map(&:id).sort - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter("cf_#{f.id}", '=', ['value2']) issues = find_issues_with_query(query) assert_equal [1], issues.map(&:id).sort @@ -242,18 +292,18 @@ def test_operator_is_not_on_multi_list_custom_field f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, - :possible_values => ['value1', 'value2', 'value3'], :multiple => true) + :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all) CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter("cf_#{f.id}", '!', ['value1']) issues = find_issues_with_query(query) assert !issues.map(&:id).include?(1) assert !issues.map(&:id).include?(3) - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter("cf_#{f.id}", '!', ['value2']) issues = find_issues_with_query(query) assert !issues.map(&:id).include?(1) @@ -264,7 +314,7 @@ # is_private filter only available for those who can set issues private User.current = User.find(2) - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') assert query.available_filters.key?('is_private') query.add_filter("is_private", '=', ['1']) @@ -279,7 +329,7 @@ # is_private filter only available for those who can set issues private User.current = User.find(2) - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') assert query.available_filters.key?('is_private') query.add_filter("is_private", '!', ['1']) @@ -291,26 +341,26 @@ end def test_operator_greater_than - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('done_ratio', '>=', ['40']) assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0") find_issues_with_query(query) end def test_operator_greater_than_a_float - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('estimated_hours', '>=', ['40.5']) assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5") find_issues_with_query(query) end def test_operator_greater_than_on_int_custom_field - f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true, :trackers => Tracker.all) CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter("cf_#{f.id}", '>=', ['8']) issues = find_issues_with_query(query) assert_equal 1, issues.size @@ -318,7 +368,7 @@ end def test_operator_lesser_than - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('done_ratio', '<=', ['30']) assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0") find_issues_with_query(query) @@ -326,14 +376,28 @@ def test_operator_lesser_than_on_custom_field f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter("cf_#{f.id}", '<=', ['30']) - assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0") + assert_match /CAST.+ <= 30\.0/, query.statement find_issues_with_query(query) end + def test_operator_lesser_than_on_date_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true, :trackers => Tracker.all) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter("cf_#{f.id}", '<=', ['2013-05-01']) + issue_ids = find_issues_with_query(query).map(&:id) + assert_include 1, issue_ids + assert_not_include 2, issue_ids + assert_not_include 3, issue_ids + end + def test_operator_between - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('done_ratio', '><', ['30', '40']) assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement find_issues_with_query(query) @@ -341,14 +405,14 @@ def test_operator_between_on_custom_field f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter("cf_#{f.id}", '><', ['30', '40']) - assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement + assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement find_issues_with_query(query) end def test_date_filter_should_not_accept_non_date_values - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter('created_on', '=', ['a']) assert query.has_filter?('created_on') @@ -356,7 +420,7 @@ end def test_date_filter_should_not_accept_invalid_date_values - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter('created_on', '=', ['2011-01-34']) assert query.has_filter?('created_on') @@ -364,7 +428,7 @@ end def test_relative_date_filter_should_not_accept_non_integer_values - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter('created_on', '>t-', ['a']) assert query.has_filter?('created_on') @@ -372,36 +436,36 @@ end def test_operator_date_equals - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter('due_date', '=', ['2011-07-10']) assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement find_issues_with_query(query) end def test_operator_date_lesser_than - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter('due_date', '<=', ['2011-07-10']) assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement find_issues_with_query(query) end def test_operator_date_greater_than - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter('due_date', '>=', ['2011-07-10']) assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement find_issues_with_query(query) end def test_operator_date_between - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10']) - assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement + assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?'/, query.statement find_issues_with_query(query) end def test_operator_in_more_than Issue.find(7).update_attribute(:due_date, (Date.today + 15)) - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('due_date', '>t+', ['15']) issues = find_issues_with_query(query) assert !issues.empty? @@ -409,7 +473,7 @@ end def test_operator_in_less_than - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('due_date', ' Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('due_date', '> Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('due_date', '>t-', ['3']) issues = find_issues_with_query(query) assert !issues.empty? @@ -435,7 +499,7 @@ def test_operator_in_the_past_days Issue.find(7).update_attribute(:due_date, (Date.today - 3)) - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('due_date', '> Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('due_date', ' Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('due_date', 'w', ['']) assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}" I18n.locale = :en @@ -517,13 +581,13 @@ Date.stubs(:today).returns(Date.parse('2011-04-29')) - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('due_date', 'w', ['']) assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}" end def test_operator_does_not_contains - query = Query.new(:project => Project.find(1), :name => '_') + query = IssueQuery.new(:project => Project.find(1), :name => '_') query.add_filter('subject', '!~', ['uNable']) assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'") find_issues_with_query(query) @@ -538,9 +602,9 @@ i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11)) group.users << user - query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}}) + query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}}) result = query.issues - assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id) + assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id) assert result.include?(i1) assert result.include?(i2) @@ -553,7 +617,7 @@ issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1) issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'}) - query = Query.new(:name => '_', :project => Project.find(1)) + query = IssueQuery.new(:name => '_', :project => Project.find(1)) filter = query.available_filters["cf_#{cf.id}"] assert_not_nil filter assert_include 'me', filter[:values].map{|v| v[1]} @@ -566,7 +630,7 @@ def test_filter_my_projects User.current = User.find(2) - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') filter = query.available_filters['project_id'] assert_not_nil filter assert_include 'mine', filter[:values].map{|v| v[1]} @@ -578,7 +642,7 @@ def test_filter_watched_issues User.current = User.find(1) - query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}}) + query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}}) result = find_issues_with_query(query) assert_not_nil result assert !result.empty? @@ -588,7 +652,7 @@ def test_filter_unwatched_issues User.current = User.find(1) - query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}}) + query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}}) result = find_issues_with_query(query) assert_not_nil result assert !result.empty? @@ -596,12 +660,40 @@ User.current = nil end + def test_filter_on_custom_field_should_ignore_projects_with_field_disabled + field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_filter => true) + Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'}) + Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'}) + + query = IssueQuery.new(:name => '_', :project => Project.find(1)) + query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}} + assert_equal 2, find_issues_with_query(query).size + + field.project_ids = [1, 3] # Disable the field for project 4 + field.save! + assert_equal 1, find_issues_with_query(query).size + end + + def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled + field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true) + Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'}) + Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'}) + + query = IssueQuery.new(:name => '_', :project => Project.find(1)) + query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}} + assert_equal 2, find_issues_with_query(query).size + + field.tracker_ids = [1] # Disable the field for tracker 2 + field.save! + assert_equal 1, find_issues_with_query(query).size + end + def test_filter_on_project_custom_field field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo') CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') filter_name = "project.cf_#{field.id}" assert_include filter_name, query.available_filters.keys query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} @@ -612,7 +704,7 @@ field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') filter_name = "author.cf_#{field.id}" assert_include filter_name, query.available_filters.keys query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} @@ -623,7 +715,7 @@ field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') filter_name = "assigned_to.cf_#{field.id}" assert_include filter_name, query.available_filters.keys query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} @@ -634,7 +726,7 @@ field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo') - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') filter_name = "fixed_version.cf_#{field.id}" assert_include filter_name, query.available_filters.keys query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} @@ -646,11 +738,11 @@ IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.filters = {"relates" => {:operator => '=', :values => ['1']}} assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.filters = {"relates" => {:operator => '=', :values => ['2']}} assert_equal [1], find_issues_with_query(query).map(&:id).sort end @@ -663,15 +755,15 @@ IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) end - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.filters = {"relates" => {:operator => '=p', :values => ['2']}} assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.filters = {"relates" => {:operator => '=p', :values => ['3']}} assert_equal [1], find_issues_with_query(query).map(&:id).sort - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.filters = {"relates" => {:operator => '=p', :values => ['4']}} assert_equal [], find_issues_with_query(query).map(&:id).sort end @@ -684,7 +776,7 @@ IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) end - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.filters = {"relates" => {:operator => '=!p', :values => ['1']}} assert_equal [1], find_issues_with_query(query).map(&:id).sort end @@ -697,7 +789,7 @@ IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3)) end - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.filters = {"relates" => {:operator => '!p', :values => ['2']}} ids = find_issues_with_query(query).map(&:id).sort assert_include 2, ids @@ -710,7 +802,7 @@ IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.filters = {"relates" => {:operator => '!*', :values => ['']}} ids = find_issues_with_query(query).map(&:id) assert_equal [], ids & [1, 2, 3] @@ -722,13 +814,28 @@ IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) - query = Query.new(:name => '_') + query = IssueQuery.new(:name => '_') query.filters = {"relates" => {:operator => '*', :values => ['']}} assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort end + def test_filter_on_relations_should_not_ignore_other_filter + issue = Issue.generate! + issue1 = Issue.generate!(:status_id => 1) + issue2 = Issue.generate!(:status_id => 2) + IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1) + IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2) + + query = IssueQuery.new(:name => '_') + query.filters = { + "status_id" => {:operator => '=', :values => ['1']}, + "relates" => {:operator => '=', :values => [issue.id.to_s]} + } + assert_equal [issue1], find_issues_with_query(query) + end + def test_statement_should_be_nil_with_no_filters - q = Query.new(:name => '_') + q = IssueQuery.new(:name => '_') q.filters = {} assert q.valid? @@ -736,44 +843,62 @@ end def test_default_columns - q = Query.new + q = IssueQuery.new assert q.columns.any? assert q.inline_columns.any? assert q.block_columns.empty? end def test_set_column_names - q = Query.new + q = IssueQuery.new q.column_names = ['tracker', :subject, '', 'unknonw_column'] - assert_equal [:tracker, :subject], q.columns.collect {|c| c.name} - c = q.columns.first - assert q.has_column?(c) + assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name} + end + + def test_has_column_should_accept_a_column_name + q = IssueQuery.new + q.column_names = ['tracker', :subject] + assert q.has_column?(:tracker) + assert !q.has_column?(:category) + end + + def test_has_column_should_accept_a_column + q = IssueQuery.new + q.column_names = ['tracker', :subject] + + tracker_column = q.available_columns.detect {|c| c.name==:tracker} + assert_kind_of QueryColumn, tracker_column + category_column = q.available_columns.detect {|c| c.name==:category} + assert_kind_of QueryColumn, category_column + + assert q.has_column?(tracker_column) + assert !q.has_column?(category_column) end def test_inline_and_block_columns - q = Query.new + q = IssueQuery.new q.column_names = ['subject', 'description', 'tracker'] - assert_equal [:subject, :tracker], q.inline_columns.map(&:name) + assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name) assert_equal [:description], q.block_columns.map(&:name) end def test_custom_field_columns_should_be_inline - q = Query.new + q = IssueQuery.new columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn} assert columns.any? assert_nil columns.detect {|column| !column.inline?} end def test_query_should_preload_spent_hours - q = Query.new(:name => '_', :column_names => [:subject, :spent_hours]) + q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours]) assert q.has_column?(:spent_hours) issues = q.issues assert_not_nil issues.first.instance_variable_get("@spent_hours") end def test_groupable_columns_should_include_custom_fields - q = Query.new + q = IssueQuery.new column = q.groupable_columns.detect {|c| c.name == :cf_1} assert_not_nil column assert_kind_of QueryCustomFieldColumn, column @@ -783,7 +908,7 @@ field = CustomField.find(1) field.update_attribute :multiple, true - q = Query.new + q = IssueQuery.new column = q.groupable_columns.detect {|c| c.name == :cf_1} assert_nil column end @@ -791,19 +916,19 @@ def test_groupable_columns_should_include_user_custom_fields cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user') - q = Query.new + q = IssueQuery.new assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} end def test_groupable_columns_should_include_version_custom_fields cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version') - q = Query.new + q = IssueQuery.new assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} end def test_grouped_with_valid_column - q = Query.new(:group_by => 'status') + q = IssueQuery.new(:group_by => 'status') assert q.grouped? assert_not_nil q.group_by_column assert_equal :status, q.group_by_column.name @@ -812,30 +937,30 @@ end def test_grouped_with_invalid_column - q = Query.new(:group_by => 'foo') + q = IssueQuery.new(:group_by => 'foo') assert !q.grouped? assert_nil q.group_by_column assert_nil q.group_by_statement end - + def test_sortable_columns_should_sort_assignees_according_to_user_format_setting with_settings :user_format => 'lastname_coma_firstname' do - q = Query.new + q = IssueQuery.new assert q.sortable_columns.has_key?('assigned_to') assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to'] end end - + def test_sortable_columns_should_sort_authors_according_to_user_format_setting with_settings :user_format => 'lastname_coma_firstname' do - q = Query.new + q = IssueQuery.new assert q.sortable_columns.has_key?('author') assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author'] end end def test_sortable_columns_should_include_custom_field - q = Query.new + q = IssueQuery.new assert q.sortable_columns['cf_1'] end @@ -843,29 +968,29 @@ field = CustomField.find(1) field.update_attribute :multiple, true - q = Query.new + q = IssueQuery.new assert !q.sortable_columns['cf_1'] end def test_default_sort - q = Query.new + q = IssueQuery.new assert_equal [], q.sort_criteria end def test_set_sort_criteria_with_hash - q = Query.new + q = IssueQuery.new q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']} assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria end def test_set_sort_criteria_with_array - q = Query.new + q = IssueQuery.new q.sort_criteria = [['priority', 'desc'], 'tracker'] assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria end def test_create_query_with_sort - q = Query.new(:name => 'Sorted') + q = IssueQuery.new(:name => 'Sorted') q.sort_criteria = [['priority', 'desc'], 'tracker'] assert q.save q.reload @@ -873,53 +998,47 @@ end def test_sort_by_string_custom_field_asc - q = Query.new + q = IssueQuery.new c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } assert c assert c.sortable - issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( - q.statement - ).order("#{c.sortable} ASC").all + issues = q.issues(:order => "#{c.sortable} ASC") values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} assert !values.empty? assert_equal values.sort, values end def test_sort_by_string_custom_field_desc - q = Query.new + q = IssueQuery.new c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } assert c assert c.sortable - issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( - q.statement - ).order("#{c.sortable} DESC").all + issues = q.issues(:order => "#{c.sortable} DESC") values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} assert !values.empty? assert_equal values.sort.reverse, values end def test_sort_by_float_custom_field_asc - q = Query.new + q = IssueQuery.new c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' } assert c assert c.sortable - issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( - q.statement - ).order("#{c.sortable} ASC").all + issues = q.issues(:order => "#{c.sortable} ASC") values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact assert !values.empty? assert_equal values.sort, values end def test_invalid_query_should_raise_query_statement_invalid_error - q = Query.new + q = IssueQuery.new assert_raise Query::StatementInvalid do q.issues(:conditions => "foo = 1") end end def test_issue_count - q = Query.new(:name => '_') + q = IssueQuery.new(:name => '_') issue_count = q.issue_count assert_equal q.issues.size, issue_count end @@ -935,7 +1054,7 @@ end def test_issue_count_by_association_group - q = Query.new(:name => '_', :group_by => 'assigned_to') + q = IssueQuery.new(:name => '_', :group_by => 'assigned_to') count_by_group = q.issue_count_by_group assert_kind_of Hash, count_by_group assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort @@ -944,7 +1063,7 @@ end def test_issue_count_by_list_custom_field_group - q = Query.new(:name => '_', :group_by => 'cf_1') + q = IssueQuery.new(:name => '_', :group_by => 'cf_1') count_by_group = q.issue_count_by_group assert_kind_of Hash, count_by_group assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort @@ -953,7 +1072,7 @@ end def test_issue_count_by_date_custom_field_group - q = Query.new(:name => '_', :group_by => 'cf_8') + q = IssueQuery.new(:name => '_', :group_by => 'cf_8') count_by_group = q.issue_count_by_group assert_kind_of Hash, count_by_group assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort @@ -963,7 +1082,7 @@ def test_issue_count_with_nil_group_only Issue.update_all("assigned_to_id = NULL") - q = Query.new(:name => '_', :group_by => 'assigned_to') + q = IssueQuery.new(:name => '_', :group_by => 'assigned_to') count_by_group = q.issue_count_by_group assert_kind_of Hash, count_by_group assert_equal 1, count_by_group.keys.size @@ -971,7 +1090,7 @@ end def test_issue_ids - q = Query.new(:name => '_') + q = IssueQuery.new(:name => '_') order = "issues.subject, issues.id" issues = q.issues(:order => order) assert_equal issues.map(&:id), q.issue_ids(:order => order) @@ -979,13 +1098,13 @@ def test_label_for set_language_if_valid 'en' - q = Query.new + q = IssueQuery.new assert_equal 'Assignee', q.label_for('assigned_to_id') end def test_label_for_fr set_language_if_valid 'fr' - q = Query.new + q = IssueQuery.new s = "Assign\xc3\xa9 \xc3\xa0" s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) assert_equal s, q.label_for('assigned_to_id') @@ -997,32 +1116,32 @@ developer = User.find(3) # Public query on project 1 - q = Query.find(1) + q = IssueQuery.find(1) assert q.editable_by?(admin) assert q.editable_by?(manager) assert !q.editable_by?(developer) # Private query on project 1 - q = Query.find(2) + q = IssueQuery.find(2) assert q.editable_by?(admin) assert !q.editable_by?(manager) assert q.editable_by?(developer) # Private query for all projects - q = Query.find(3) + q = IssueQuery.find(3) assert q.editable_by?(admin) assert !q.editable_by?(manager) assert q.editable_by?(developer) # Public query for all projects - q = Query.find(4) + q = IssueQuery.find(4) assert q.editable_by?(admin) assert !q.editable_by?(manager) assert !q.editable_by?(developer) end def test_visible_scope - query_ids = Query.visible(User.anonymous).map(&:id) + query_ids = IssueQuery.visible(User.anonymous).map(&:id) assert query_ids.include?(1), 'public query on public project was not visible' assert query_ids.include?(4), 'public query for all projects was not visible' @@ -1031,81 +1150,120 @@ assert !query_ids.include?(7), 'public query on private project was visible' end - context "#available_filters" do - setup do - @query = Query.new(:name => "_") + def test_query_with_public_visibility_should_be_visible_to_anyone + q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PUBLIC) + + assert q.visible?(User.anonymous) + assert IssueQuery.visible(User.anonymous).find_by_id(q.id) + + assert q.visible?(User.find(7)) + assert IssueQuery.visible(User.find(7)).find_by_id(q.id) + + assert q.visible?(User.find(2)) + assert IssueQuery.visible(User.find(2)).find_by_id(q.id) + + assert q.visible?(User.find(1)) + assert IssueQuery.visible(User.find(1)).find_by_id(q.id) + end + + def test_query_with_roles_visibility_should_be_visible_to_user_with_role + q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1,2]) + + assert !q.visible?(User.anonymous) + assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id) + + assert !q.visible?(User.find(7)) + assert_nil IssueQuery.visible(User.find(7)).find_by_id(q.id) + + assert q.visible?(User.find(2)) + assert IssueQuery.visible(User.find(2)).find_by_id(q.id) + + assert q.visible?(User.find(1)) + assert IssueQuery.visible(User.find(1)).find_by_id(q.id) + end + + def test_query_with_private_visibility_should_be_visible_to_owner + q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PRIVATE, :user => User.find(7)) + + assert !q.visible?(User.anonymous) + assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id) + + assert q.visible?(User.find(7)) + assert IssueQuery.visible(User.find(7)).find_by_id(q.id) + + assert !q.visible?(User.find(2)) + assert_nil IssueQuery.visible(User.find(2)).find_by_id(q.id) + + assert q.visible?(User.find(1)) + assert_nil IssueQuery.visible(User.find(1)).find_by_id(q.id) + end + + test "#available_filters should include users of visible projects in cross-project view" do + users = IssueQuery.new.available_filters["assigned_to_id"] + assert_not_nil users + assert users[:values].map{|u|u[1]}.include?("3") + end + + test "#available_filters should include users of subprojects" do + user1 = User.generate! + user2 = User.generate! + project = Project.find(1) + Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1]) + + users = IssueQuery.new(:project => project).available_filters["assigned_to_id"] + assert_not_nil users + assert users[:values].map{|u|u[1]}.include?(user1.id.to_s) + assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s) + end + + test "#available_filters should include visible projects in cross-project view" do + projects = IssueQuery.new.available_filters["project_id"] + assert_not_nil projects + assert projects[:values].map{|u|u[1]}.include?("1") + end + + test "#available_filters should include 'member_of_group' filter" do + query = IssueQuery.new + assert query.available_filters.keys.include?("member_of_group") + assert_equal :list_optional, query.available_filters["member_of_group"][:type] + assert query.available_filters["member_of_group"][:values].present? + assert_equal Group.all.sort.map {|g| [g.name, g.id.to_s]}, + query.available_filters["member_of_group"][:values].sort + end + + test "#available_filters should include 'assigned_to_role' filter" do + query = IssueQuery.new + assert query.available_filters.keys.include?("assigned_to_role") + assert_equal :list_optional, query.available_filters["assigned_to_role"][:type] + + assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1']) + assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2']) + assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3']) + + assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4']) + assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5']) + end + + def test_available_filters_should_include_custom_field_according_to_user_visibility + visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true) + hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1]) + + with_current_user User.find(3) do + query = IssueQuery.new + assert_include "cf_#{visible_field.id}", query.available_filters.keys + assert_not_include "cf_#{hidden_field.id}", query.available_filters.keys end + end - should "include users of visible projects in cross-project view" do - users = @query.available_filters["assigned_to_id"] - assert_not_nil users - assert users[:values].map{|u|u[1]}.include?("3") + def test_available_columns_should_include_custom_field_according_to_user_visibility + visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true) + hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1]) + + with_current_user User.find(3) do + query = IssueQuery.new + assert_include :"cf_#{visible_field.id}", query.available_columns.map(&:name) + assert_not_include :"cf_#{hidden_field.id}", query.available_columns.map(&:name) end - - should "include users of subprojects" do - user1 = User.generate! - user2 = User.generate! - project = Project.find(1) - Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1]) - @query.project = project - - users = @query.available_filters["assigned_to_id"] - assert_not_nil users - assert users[:values].map{|u|u[1]}.include?(user1.id.to_s) - assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s) - end - - should "include visible projects in cross-project view" do - projects = @query.available_filters["project_id"] - assert_not_nil projects - assert projects[:values].map{|u|u[1]}.include?("1") - end - - context "'member_of_group' filter" do - should "be present" do - assert @query.available_filters.keys.include?("member_of_group") - end - - should "be an optional list" do - assert_equal :list_optional, @query.available_filters["member_of_group"][:type] - end - - should "have a list of the groups as values" do - Group.destroy_all # No fixtures - group1 = Group.generate!.reload - group2 = Group.generate!.reload - - expected_group_list = [ - [group1.name, group1.id.to_s], - [group2.name, group2.id.to_s] - ] - assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort - end - - end - - context "'assigned_to_role' filter" do - should "be present" do - assert @query.available_filters.keys.include?("assigned_to_role") - end - - should "be an optional list" do - assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type] - end - - should "have a list of the Roles as values" do - assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1']) - assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2']) - assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3']) - end - - should "not include the built in Roles as values" do - assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4']) - assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5']) - end - - end - end context "#statement" do @@ -1127,34 +1285,34 @@ end should "search assigned to for users in the group" do - @query = Query.new(:name => '_') + @query = IssueQuery.new(:name => '_') @query.add_filter('member_of_group', '=', [@group.id.to_s]) - assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')" + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@group.id}')" assert_find_issues_with_query_is_successful @query end should "search not assigned to any group member (none)" do - @query = Query.new(:name => '_') + @query = IssueQuery.new(:name => '_') @query.add_filter('member_of_group', '!*', ['']) # Users not in a group - assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')" + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')" assert_find_issues_with_query_is_successful @query end should "search assigned to any group member (all)" do - @query = Query.new(:name => '_') + @query = IssueQuery.new(:name => '_') @query.add_filter('member_of_group', '*', ['']) # Only users in a group - assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')" + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')" assert_find_issues_with_query_is_successful @query end should "return an empty set with = empty group" do @empty_group = Group.generate! - @query = Query.new(:name => '_') + @query = IssueQuery.new(:name => '_') @query.add_filter('member_of_group', '=', [@empty_group.id.to_s]) assert_equal [], find_issues_with_query(@query) @@ -1162,7 +1320,7 @@ should "return issues with ! empty group" do @empty_group = Group.generate! - @query = Query.new(:name => '_') + @query = IssueQuery.new(:name => '_') @query.add_filter('member_of_group', '!', [@empty_group.id.to_s]) assert_find_issues_with_query_is_successful @query @@ -1182,7 +1340,7 @@ User.add_to_project(@manager, @project, @manager_role) User.add_to_project(@developer, @project, @developer_role) User.add_to_project(@boss, @project, [@manager_role, @developer_role]) - + @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id) @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id) @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id) @@ -1191,7 +1349,7 @@ end should "search assigned to for users with the Role" do - @query = Query.new(:name => '_', :project => @project) + @query = IssueQuery.new(:name => '_', :project => @project) @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) assert_query_result [@issue1, @issue3], @query @@ -1200,8 +1358,8 @@ should "search assigned to for users with the Role on the issue project" do other_project = Project.generate! User.add_to_project(@developer, other_project, @manager_role) - - @query = Query.new(:name => '_', :project => @project) + + @query = IssueQuery.new(:name => '_', :project => @project) @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) assert_query_result [@issue1, @issue3], @query @@ -1209,28 +1367,28 @@ should "return an empty set with empty role" do @empty_role = Role.generate! - @query = Query.new(:name => '_', :project => @project) + @query = IssueQuery.new(:name => '_', :project => @project) @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s]) assert_query_result [], @query end should "search assigned to for users without the Role" do - @query = Query.new(:name => '_', :project => @project) + @query = IssueQuery.new(:name => '_', :project => @project) @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s]) assert_query_result [@issue2, @issue4, @issue5], @query end should "search assigned to for users not assigned to any Role (none)" do - @query = Query.new(:name => '_', :project => @project) + @query = IssueQuery.new(:name => '_', :project => @project) @query.add_filter('assigned_to_role', '!*', ['']) assert_query_result [@issue4, @issue5], @query end should "search assigned to for users assigned to any Role (all)" do - @query = Query.new(:name => '_', :project => @project) + @query = IssueQuery.new(:name => '_', :project => @project) @query.add_filter('assigned_to_role', '*', ['']) assert_query_result [@issue1, @issue2, @issue3], @query @@ -1238,12 +1396,11 @@ should "return issues with ! empty role" do @empty_role = Role.generate! - @query = Query.new(:name => '_', :project => @project) + @query = IssueQuery.new(:name => '_', :project => @project) @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s]) assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query end end end - end diff -r d98d22a98252 -r afce8026aaeb test/unit/repository_bazaar_test.rb --- a/test/unit/repository_bazaar_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/repository_bazaar_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -105,7 +105,7 @@ @project.reload assert_equal NUM_REV, @repository.changesets.count # Remove changesets with revision > 5 - @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 2} + @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 2} @project.reload assert_equal 2, @repository.changesets.count diff -r d98d22a98252 -r afce8026aaeb test/unit/repository_cvs_test.rb --- a/test/unit/repository_cvs_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/repository_cvs_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -116,12 +116,12 @@ assert_equal CHANGESETS_NUM, @repository.changesets.count # Remove changesets with revision > 3 - @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3} + @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 3} @project.reload assert_equal 3, @repository.changesets.count assert_equal %w|3 2 1|, @repository.changesets.all.collect(&:revision) - rev3_commit = @repository.changesets.find(:first, :order => 'committed_on DESC') + rev3_commit = @repository.changesets.reorder('committed_on DESC').first assert_equal '3', rev3_commit.revision # 2007-12-14 01:27:22 +0900 rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) diff -r d98d22a98252 -r afce8026aaeb test/unit/repository_darcs_test.rb --- a/test/unit/repository_darcs_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/repository_darcs_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -79,7 +79,7 @@ assert_equal NUM_REV, @repository.changesets.count # Remove changesets with revision > 3 - @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3} + @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 3} @project.reload assert_equal 3, @repository.changesets.count diff -r d98d22a98252 -r afce8026aaeb test/unit/repository_filesystem_test.rb --- a/test/unit/repository_filesystem_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/repository_filesystem_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/repository_git_test.rb --- a/test/unit/repository_git_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/repository_git_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/repository_mercurial_test.rb --- a/test/unit/repository_mercurial_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/repository_mercurial_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,7 +23,7 @@ include Redmine::I18n REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s - NUM_REV = 32 + NUM_REV = 34 CHAR_1_HEX = "\xc3\x9c" def setup @@ -102,7 +102,7 @@ @project.reload assert_equal NUM_REV, @repository.changesets.count # Remove changesets with revision > 2 - @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 2} + @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 2} @project.reload assert_equal 3, @repository.changesets.count @@ -144,7 +144,7 @@ # with_limit changesets = @repository.latest_changesets('', nil, 2) - assert_equal %w|31 30|, changesets.collect(&:revision) + assert_equal ["#{NUM_REV - 1}", "#{NUM_REV - 2}"], changesets.collect(&:revision) # with_filepath changesets = @repository.latest_changesets( @@ -365,7 +365,7 @@ @repository.fetch_changesets @project.reload assert_equal NUM_REV, @repository.changesets.count - %w|31 31eeee7395c8 31eee|.each do |r1| + ["#{NUM_REV - 1}", "2e6d54642923", "2e6d5"].each do |r1| changeset = @repository.find_changeset_by_name(r1) assert_nil changeset.next end diff -r d98d22a98252 -r afce8026aaeb test/unit/repository_subversion_test.rb --- a/test/unit/repository_subversion_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/repository_subversion_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,15 +20,43 @@ class RepositorySubversionTest < ActiveSupport::TestCase fixtures :projects, :repositories, :enabled_modules, :users, :roles + include Redmine::I18n + NUM_REV = 11 def setup @project = Project.find(3) @repository = Repository::Subversion.create(:project => @project, - :url => self.class.subversion_repository_url) + :url => self.class.subversion_repository_url) assert @repository end + def test_invalid_url + set_language_if_valid 'en' + ['invalid', 'http://', 'svn://', 'svn+ssh://', 'file://'].each do |url| + repo = Repository::Subversion.new( + :project => @project, + :identifier => 'test', + :url => url + ) + assert !repo.save + assert_equal ["is invalid"], repo.errors[:url] + end + end + + def test_valid_url + ['http://valid', 'svn://valid', 'svn+ssh://valid', 'file://valid'].each do |url| + repo = Repository::Subversion.new( + :project => @project, + :identifier => 'test', + :url => url + ) + assert repo.save + assert_equal [], repo.errors[:url] + assert repo.destroy + end + end + if repository_configured?('subversion') def test_fetch_changesets_from_scratch assert_equal 0, @repository.changesets.count @@ -47,7 +75,7 @@ assert_equal NUM_REV, @repository.changesets.count # Remove changesets with revision > 5 - @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 5} + @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 5} @project.reload assert_equal 5, @repository.changesets.count diff -r d98d22a98252 -r afce8026aaeb test/unit/repository_test.rb --- a/test/unit/repository_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/repository_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -182,13 +182,10 @@ def test_scan_changesets_for_issue_ids Setting.default_language = 'en' - # choosing a status to apply to fix issues - Setting.commit_fix_status_id = IssueStatus.find( - :first, - :conditions => ["is_closed = ?", true]).id - Setting.commit_fix_done_ratio = "90" Setting.commit_ref_keywords = 'refs , references, IssueID' - Setting.commit_fix_keywords = 'fixes , closes' + Setting.commit_update_keywords = [ + {'keywords' => 'fixes , closes', 'status_id' => IssueStatus.where(:is_closed => true).first.id, 'done_ratio' => '90'} + ] Setting.default_language = 'en' ActionMailer::Base.deliveries.clear @@ -209,7 +206,7 @@ assert_equal [101], fixed_issue.changeset_ids # issue change - journal = fixed_issue.journals.find(:first, :order => 'created_on desc') + journal = fixed_issue.journals.reorder('created_on desc').first assert_equal User.find_by_login('dlopper'), journal.user assert_equal 'Applied in changeset r2.', journal.notes @@ -278,7 +275,7 @@ end def test_manual_user_mapping - assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do + assert_no_difference "Changeset.where('user_id <> 2').count" do c = Changeset.create!( :repository => @repository, :committer => 'foo', @@ -329,6 +326,12 @@ assert_equal true, klass.scm_available end + def test_extra_info_should_not_return_non_hash_value + repo = Repository.new + repo.extra_info = "foo" + assert_nil repo.extra_info + end + def test_merge_extra_info repo = Repository::Subversion.new(:project => Project.find(3)) assert !repo.save diff -r d98d22a98252 -r afce8026aaeb test/unit/role_test.rb --- a/test/unit/role_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/role_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -91,55 +91,39 @@ assert_equal Role.all.reject(&:builtin?).sort, Role.find_all_givable end - context "#anonymous" do - should "return the anonymous role" do + def test_anonymous_should_return_the_anonymous_role + assert_no_difference('Role.count') do role = Role.anonymous assert role.builtin? assert_equal Role::BUILTIN_ANONYMOUS, role.builtin end + end - context "with a missing anonymous role" do - setup do - Role.delete_all("builtin = #{Role::BUILTIN_ANONYMOUS}") - end + def test_anonymous_with_a_missing_anonymous_role_should_return_the_anonymous_role + Role.where(:builtin => Role::BUILTIN_ANONYMOUS).delete_all - should "create a new anonymous role" do - assert_difference('Role.count') do - Role.anonymous - end - end - - should "return the anonymous role" do - role = Role.anonymous - assert role.builtin? - assert_equal Role::BUILTIN_ANONYMOUS, role.builtin - end + assert_difference('Role.count') do + role = Role.anonymous + assert role.builtin? + assert_equal Role::BUILTIN_ANONYMOUS, role.builtin end end - context "#non_member" do - should "return the non-member role" do + def test_non_member_should_return_the_non_member_role + assert_no_difference('Role.count') do role = Role.non_member assert role.builtin? assert_equal Role::BUILTIN_NON_MEMBER, role.builtin end + end - context "with a missing non-member role" do - setup do - Role.delete_all("builtin = #{Role::BUILTIN_NON_MEMBER}") - end + def test_non_member_with_a_missing_non_member_role_should_return_the_non_member_role + Role.where(:builtin => Role::BUILTIN_NON_MEMBER).delete_all - should "create a new non-member role" do - assert_difference('Role.count') do - Role.non_member - end - end - - should "return the non-member role" do - role = Role.non_member - assert role.builtin? - assert_equal Role::BUILTIN_NON_MEMBER, role.builtin - end + assert_difference('Role.count') do + role = Role.non_member + assert role.builtin? + assert_equal Role::BUILTIN_NON_MEMBER, role.builtin end end end diff -r d98d22a98252 -r afce8026aaeb test/unit/search_test.rb --- a/test/unit/search_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/search_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -49,6 +49,7 @@ # Removes the :view_changesets permission from Anonymous role remove_permission Role.anonymous, :view_changesets + User.current = nil r = Issue.search(@issue_keyword).first assert r.include?(@issue) @@ -74,6 +75,7 @@ # Removes the :view_changesets permission from Non member role remove_permission Role.non_member, :view_changesets + User.current = User.find_by_login('rhill') r = Issue.search(@issue_keyword).first assert r.include?(@issue) @@ -128,7 +130,7 @@ def test_search_issue_with_multiple_hits_in_journals i = Issue.find(1) - assert_equal 2, i.journals.count(:all, :conditions => "notes LIKE '%notes%'") + assert_equal 2, i.journals.where("notes LIKE '%notes%'").count r = Issue.search('%notes%').first assert_equal 1, r.size diff -r d98d22a98252 -r afce8026aaeb test/unit/setting_test.rb --- a/test/unit/setting_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/setting_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -46,16 +46,16 @@ assert_equal ['issue_added', 'issue_updated', 'news_added'], Setting.notified_events assert_equal ['issue_added', 'issue_updated', 'news_added'], Setting.find_by_name('notified_events').value end - + def test_setting_should_be_reloaded_after_clear_cache Setting.app_title = "My title" assert_equal "My title", Setting.app_title - + s = Setting.find_by_name("app_title") s.value = 'New title' s.save! assert_equal "My title", Setting.app_title - + Setting.clear_cache assert_equal "New title", Setting.app_title end diff -r d98d22a98252 -r afce8026aaeb test/unit/time_entry_activity_test.rb --- a/test/unit/time_entry_activity_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/time_entry_activity_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -84,5 +84,33 @@ e.reload assert_equal "0", e.custom_value_for(field).value end + + def test_system_activity_with_child_in_use_should_be_in_use + project = Project.generate! + system_activity = TimeEntryActivity.create!(:name => 'Activity') + project_activity = TimeEntryActivity.create!(:name => 'Activity', :project => project, :parent_id => system_activity.id) + + TimeEntry.generate!(:project => project, :activity => project_activity) + + assert project_activity.in_use? + assert system_activity.in_use? + end + + def test_destroying_a_system_activity_should_reassign_children_activities + project = Project.generate! + system_activity = TimeEntryActivity.create!(:name => 'Activity') + project_activity = TimeEntryActivity.create!(:name => 'Activity', :project => project, :parent_id => system_activity.id) + + entries = [ + TimeEntry.generate!(:project => project, :activity => system_activity), + TimeEntry.generate!(:project => project, :activity => project_activity) + ] + + assert_difference 'TimeEntryActivity.count', -2 do + assert_nothing_raised do + assert system_activity.destroy(TimeEntryActivity.find_by_name('Development')) + end + end + assert entries.all? {|entry| entry.reload.activity.name == 'Development'} + end end - diff -r d98d22a98252 -r afce8026aaeb test/unit/time_entry_query_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/time_entry_query_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -0,0 +1,40 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# 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. + +require File.expand_path('../../test_helper', __FILE__) + +class TimeEntryQueryTest < ActiveSupport::TestCase + fixtures :projects, :users, :enumerations + + def test_activity_filter_should_consider_system_and_project_activities + TimeEntry.delete_all + system = TimeEntryActivity.create!(:name => 'Foo') + override = TimeEntryActivity.create!(:name => 'Foo', :parent_id => system.id, :project_id => 1) + other = TimeEntryActivity.create!(:name => 'Bar') + TimeEntry.generate!(:activity => system, :hours => 1.0) + TimeEntry.generate!(:activity => override, :hours => 2.0) + TimeEntry.generate!(:activity => other, :hours => 4.0) + + query = TimeEntryQuery.new(:name => '_') + query.add_filter('activity_id', '=', [system.id.to_s]) + assert_equal 3.0, query.results_scope.sum(:hours) + + query = TimeEntryQuery.new(:name => '_') + query.add_filter('activity_id', '!', [system.id.to_s]) + assert_equal 4.0, query.results_scope.sum(:hours) + end +end diff -r d98d22a98252 -r afce8026aaeb test/unit/time_entry_test.rb --- a/test/unit/time_entry_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/time_entry_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,8 +25,7 @@ :journals, :journal_details, :issue_categories, :enumerations, :groups_users, - :enabled_modules, - :workflows + :enabled_modules def test_hours_format assertions = { "2" => 2.0, @@ -111,6 +110,13 @@ assert_equal 1, te.errors.count end + def test_spent_on_with_2_digits_year_should_not_be_valid + entry = TimeEntry.new(:project => Project.find(1), :user => User.find(1), :activity => TimeEntryActivity.first, :hours => 1) + entry.spent_on = "09-02-04" + assert !entry.valid? + assert_include I18n.translate('activerecord.errors.messages.not_a_date'), entry.errors[:spent_on] + end + def test_set_project_if_nil anon = User.anonymous project = Project.find(1) diff -r d98d22a98252 -r afce8026aaeb test/unit/token_test.rb --- a/test/unit/token_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/token_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -58,4 +58,51 @@ assert_equal 2, Token.destroy_expired end end + + def test_find_active_user_should_return_user + token = Token.create!(:user_id => 1, :action => 'api') + assert_equal User.find(1), Token.find_active_user('api', token.value) + end + + def test_find_active_user_should_return_nil_for_locked_user + token = Token.create!(:user_id => 1, :action => 'api') + User.find(1).lock! + assert_nil Token.find_active_user('api', token.value) + end + + def test_find_user_should_return_user + token = Token.create!(:user_id => 1, :action => 'api') + assert_equal User.find(1), Token.find_user('api', token.value) + end + + def test_find_user_should_return_locked_user + token = Token.create!(:user_id => 1, :action => 'api') + User.find(1).lock! + assert_equal User.find(1), Token.find_user('api', token.value) + end + + def test_find_token_should_return_the_token + token = Token.create!(:user_id => 1, :action => 'api') + assert_equal token, Token.find_token('api', token.value) + end + + def test_find_token_should_return_the_token_with_validity + token = Token.create!(:user_id => 1, :action => 'api', :created_on => 1.hour.ago) + assert_equal token, Token.find_token('api', token.value, 1) + end + + def test_find_token_should_return_nil_with_wrong_action + token = Token.create!(:user_id => 1, :action => 'feeds') + assert_nil Token.find_token('api', token.value) + end + + def test_find_token_should_return_nil_without_user + token = Token.create!(:user_id => 999, :action => 'api') + assert_nil Token.find_token('api', token.value) + end + + def test_find_token_should_return_nil_with_validity_expired + token = Token.create!(:user_id => 999, :action => 'api', :created_on => 2.days.ago) + assert_nil Token.find_token('api', token.value, 1) + end end diff -r d98d22a98252 -r afce8026aaeb test/unit/user_preference_test.rb --- a/test/unit/user_preference_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/user_preference_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -55,6 +55,11 @@ assert_kind_of Hash, up.others end + def test_others_should_be_blank_after_initialization + pref = User.new.pref + assert_equal({}, pref.others) + end + def test_reading_value_from_nil_others_hash up = UserPreference.new(:user => User.new) up.others = nil diff -r d98d22a98252 -r afce8026aaeb test/unit/user_test.rb --- a/test/unit/user_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/user_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,8 +25,7 @@ :issue_categories, :enumerations, :issues, :journals, :journal_details, :groups_users, - :enabled_modules, - :workflows + :enabled_modules def setup @admin = User.find(1) @@ -34,10 +33,14 @@ @dlopper = User.find(3) end + def test_sorted_scope_should_sort_user_by_display_name + assert_equal User.all.map(&:name).map(&:downcase).sort, User.sorted.all.map(&:name).map(&:downcase) + end + def test_generate User.generate!(:firstname => 'Testing connection') User.generate!(:firstname => 'Testing connection') - assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'}) + assert_equal 2, User.where(:firstname => 'Testing connection').count end def test_truth @@ -67,6 +70,41 @@ assert user.save end + def test_generate_password_should_respect_minimum_password_length + with_settings :password_min_length => 15 do + user = User.generate!(:generate_password => true) + assert user.password.length >= 15 + end + end + + def test_generate_password_should_not_generate_password_with_less_than_10_characters + with_settings :password_min_length => 4 do + user = User.generate!(:generate_password => true) + assert user.password.length >= 10 + end + end + + def test_generate_password_on_create_should_set_password + user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") + user.login = "newuser" + user.generate_password = true + assert user.save + + password = user.password + assert user.check_password?(password) + end + + def test_generate_password_on_update_should_update_password + user = User.find(2) + hash = user.hashed_password + user.generate_password = true + assert user.save + + password = user.password + assert user.check_password?(password) + assert_not_equal hash, user.reload.hashed_password + end + def test_create user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") @@ -253,7 +291,7 @@ end def test_destroy_should_delete_private_queries - query = Query.new(:name => 'foo', :is_public => false) + query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PRIVATE) query.project_id = 1 query.user_id = 2 query.save! @@ -264,7 +302,7 @@ end def test_destroy_should_update_public_queries - query = Query.new(:name => 'foo', :is_public => true) + query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PUBLIC) query.project_id = 1 query.user_id = 2 query.save! @@ -363,28 +401,7 @@ u = User.new u.mail_notification = 'foo' u.save - assert_not_nil u.errors[:mail_notification] - end - - context "User#try_to_login" do - should "fall-back to case-insensitive if user login is not found as-typed." do - user = User.try_to_login("AdMin", "admin") - assert_kind_of User, user - assert_equal "admin", user.login - end - - should "select the exact matching user first" do - case_sensitive_user = User.generate! do |user| - user.password = "admin123" - end - # bypass validations to make it appear like existing data - case_sensitive_user.update_attribute(:login, 'ADMIN') - - user = User.try_to_login("ADMIN", "admin123") - assert_kind_of User, user - assert_equal "ADMIN", user.login - - end + assert_not_equal [], u.errors[:mail_notification] end def test_password @@ -459,50 +476,67 @@ assert_equal ['users.lastname', 'users.firstname', 'users.id'], User.fields_for_order_statement end end - + def test_fields_for_order_statement_width_table_name_should_prepend_table_name with_settings :user_format => 'lastname_firstname' do assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'], User.fields_for_order_statement('authors') end end - + def test_fields_for_order_statement_with_blank_format_should_return_default with_settings :user_format => '' do assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement end end - + def test_fields_for_order_statement_with_invalid_format_should_return_default with_settings :user_format => 'foo' do assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement end end - def test_lock - user = User.try_to_login("jsmith", "jsmith") - assert_equal @jsmith, user + test ".try_to_login with good credentials should return the user" do + user = User.try_to_login("admin", "admin") + assert_kind_of User, user + assert_equal "admin", user.login + end + test ".try_to_login with wrong credentials should return nil" do + assert_nil User.try_to_login("admin", "foo") + end + + def test_try_to_login_with_locked_user_should_return_nil @jsmith.status = User::STATUS_LOCKED - assert @jsmith.save + @jsmith.save! user = User.try_to_login("jsmith", "jsmith") assert_equal nil, user end - context ".try_to_login" do - context "with good credentials" do - should "return the user" do - user = User.try_to_login("admin", "admin") - assert_kind_of User, user - assert_equal "admin", user.login - end + def test_try_to_login_with_locked_user_and_not_active_only_should_return_user + @jsmith.status = User::STATUS_LOCKED + @jsmith.save! + + user = User.try_to_login("jsmith", "jsmith", false) + assert_equal @jsmith, user + end + + test ".try_to_login should fall-back to case-insensitive if user login is not found as-typed" do + user = User.try_to_login("AdMin", "admin") + assert_kind_of User, user + assert_equal "admin", user.login + end + + test ".try_to_login should select the exact matching user first" do + case_sensitive_user = User.generate! do |user| + user.password = "admin123" end + # bypass validations to make it appear like existing data + case_sensitive_user.update_attribute(:login, 'ADMIN') - context "with wrong credentials" do - should "return nil" do - assert_nil User.try_to_login("admin", "foo") - end - end + user = User.try_to_login("ADMIN", "admin123") + assert_kind_of User, user + assert_equal "ADMIN", user.login end if ldap_configured? @@ -580,7 +614,7 @@ @auth_source.account_password = '' @auth_source.save! end - + context "with a successful authentication" do should "create a new user account if it doesn't exist" do assert_difference('User.count') do @@ -589,7 +623,7 @@ end end end - + context "with an unsuccessful authentication" do should "return nil" do assert_nil User.try_to_login('example1', '11111') @@ -646,50 +680,46 @@ end end - context "User#api_key" do - should "generate a new one if the user doesn't have one" do - user = User.generate!(:api_token => nil) - assert_nil user.api_token + test "#api_key should generate a new one if the user doesn't have one" do + user = User.generate!(:api_token => nil) + assert_nil user.api_token - key = user.api_key - assert_equal 40, key.length - user.reload - assert_equal key, user.api_key - end - - should "return the existing api token value" do - user = User.generate! - token = Token.create!(:action => 'api') - user.api_token = token - assert user.save - - assert_equal token.value, user.api_key - end + key = user.api_key + assert_equal 40, key.length + user.reload + assert_equal key, user.api_key end - context "User#find_by_api_key" do - should "return nil if no matching key is found" do - assert_nil User.find_by_api_key('zzzzzzzzz') - end + test "#api_key should return the existing api token value" do + user = User.generate! + token = Token.create!(:action => 'api') + user.api_token = token + assert user.save - should "return nil if the key is found for an inactive user" do - user = User.generate! - user.status = User::STATUS_LOCKED - token = Token.create!(:action => 'api') - user.api_token = token - user.save + assert_equal token.value, user.api_key + end - assert_nil User.find_by_api_key(token.value) - end + test "#find_by_api_key should return nil if no matching key is found" do + assert_nil User.find_by_api_key('zzzzzzzzz') + end - should "return the user if the key is found for an active user" do - user = User.generate! - token = Token.create!(:action => 'api') - user.api_token = token - user.save + test "#find_by_api_key should return nil if the key is found for an inactive user" do + user = User.generate! + user.status = User::STATUS_LOCKED + token = Token.create!(:action => 'api') + user.api_token = token + user.save - assert_equal user, User.find_by_api_key(token.value) - end + assert_nil User.find_by_api_key(token.value) + end + + test "#find_by_api_key should return the user if the key is found for an active user" do + user = User.generate! + token = Token.create!(:action => 'api') + user.api_token = token + user.save + + assert_equal user, User.find_by_api_key(token.value) end def test_default_admin_account_changed_should_return_false_if_account_was_not_changed @@ -724,6 +754,32 @@ assert_equal true, User.default_admin_account_changed? end + def test_membership_with_project_should_return_membership + project = Project.find(1) + + membership = @jsmith.membership(project) + assert_kind_of Member, membership + assert_equal @jsmith, membership.user + assert_equal project, membership.project + end + + def test_membership_with_project_id_should_return_membership + project = Project.find(1) + + membership = @jsmith.membership(1) + assert_kind_of Member, membership + assert_equal @jsmith, membership.user + assert_equal project, membership.project + end + + def test_membership_for_non_member_should_return_nil + project = Project.find(1) + + user = User.generate! + membership = user.membership(1) + assert_nil membership + end + def test_roles_for_project # user with a role roles = @jsmith.roles_for_project(Project.find(1)) @@ -816,29 +872,27 @@ assert !u.password_confirmation.blank? end - context "#change_password_allowed?" do - should "be allowed if no auth source is set" do - user = User.generate! - assert user.change_password_allowed? - end + test "#change_password_allowed? should be allowed if no auth source is set" do + user = User.generate! + assert user.change_password_allowed? + end - should "delegate to the auth source" do - user = User.generate! + test "#change_password_allowed? should delegate to the auth source" do + user = User.generate! - allowed_auth_source = AuthSource.generate! - def allowed_auth_source.allow_password_changes?; true; end + allowed_auth_source = AuthSource.generate! + def allowed_auth_source.allow_password_changes?; true; end - denied_auth_source = AuthSource.generate! - def denied_auth_source.allow_password_changes?; false; end + denied_auth_source = AuthSource.generate! + def denied_auth_source.allow_password_changes?; false; end - assert user.change_password_allowed? + assert user.change_password_allowed? - user.auth_source = allowed_auth_source - assert user.change_password_allowed?, "User not allowed to change password, though auth source does" + user.auth_source = allowed_auth_source + assert user.change_password_allowed?, "User not allowed to change password, though auth source does" - user.auth_source = denied_auth_source - assert !user.change_password_allowed?, "User allowed to change password, though auth source does not" - end + user.auth_source = denied_auth_source + assert !user.change_password_allowed?, "User allowed to change password, though auth source does not" end def test_own_account_deletable_should_be_true_with_unsubscrive_enabled @@ -901,7 +955,7 @@ should "authorize nearly everything for admin users" do project = Project.find(1) assert ! @admin.member_of?(project) - %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p| + %w(edit_issues delete_issues manage_news add_documents manage_wiki).each do |p| assert_equal true, @admin.allowed_to?(p.to_sym, project) end end @@ -1014,9 +1068,15 @@ assert ! @user.notify_about?(@issue) end end + end - context "other events" do - should 'be added and tested' + def test_notify_about_news + user = User.generate! + news = News.new + + User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option| + user.mail_notification = option + assert_equal (option != 'none'), user.notify_about?(news) end end diff -r d98d22a98252 -r afce8026aaeb test/unit/version_test.rb --- a/test/unit/version_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/version_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,25 +18,27 @@ require File.expand_path('../../test_helper', __FILE__) class VersionTest < ActiveSupport::TestCase - fixtures :projects, :users, :issues, :issue_statuses, :trackers, :enumerations, :versions, :projects_trackers - - def setup - end + fixtures :projects, :users, :issues, :issue_statuses, :trackers, + :enumerations, :versions, :projects_trackers def test_create - v = Version.new(:project => Project.find(1), :name => '1.1', :effective_date => '2011-03-25') + v = Version.new(:project => Project.find(1), :name => '1.1', + :effective_date => '2011-03-25') assert v.save assert_equal 'open', v.status assert_equal 'none', v.sharing end def test_invalid_effective_date_validation - v = Version.new(:project => Project.find(1), :name => '1.1', :effective_date => '99999-01-01') + v = Version.new(:project => Project.find(1), :name => '1.1', + :effective_date => '99999-01-01') assert !v.valid? v.effective_date = '2012-11-33' assert !v.valid? v.effective_date = '2012-31-11' assert !v.valid? + v.effective_date = '-2012-31-11' + assert !v.valid? v.effective_date = 'ABC' assert !v.valid? assert_include I18n.translate('activerecord.errors.messages.not_a_date'), @@ -46,8 +48,8 @@ def test_progress_should_be_0_with_no_assigned_issues project = Project.find(1) v = Version.create!(:project => project, :name => 'Progress') - assert_equal 0, v.completed_pourcent - assert_equal 0, v.closed_pourcent + assert_equal 0, v.completed_percent + assert_equal 0, v.closed_percent end def test_progress_should_be_0_with_unbegun_assigned_issues @@ -55,20 +57,20 @@ v = Version.create!(:project => project, :name => 'Progress') add_issue(v) add_issue(v, :done_ratio => 0) - assert_progress_equal 0, v.completed_pourcent - assert_progress_equal 0, v.closed_pourcent + assert_progress_equal 0, v.completed_percent + assert_progress_equal 0, v.closed_percent end def test_progress_should_be_100_with_closed_assigned_issues project = Project.find(1) - status = IssueStatus.find(:first, :conditions => {:is_closed => true}) + status = IssueStatus.where(:is_closed => true).first v = Version.create!(:project => project, :name => 'Progress') add_issue(v, :status => status) add_issue(v, :status => status, :done_ratio => 20) add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25) add_issue(v, :status => status, :estimated_hours => 15) - assert_progress_equal 100.0, v.completed_pourcent - assert_progress_equal 100.0, v.closed_pourcent + assert_progress_equal 100.0, v.completed_percent + assert_progress_equal 100.0, v.closed_percent end def test_progress_should_consider_done_ratio_of_open_assigned_issues @@ -77,8 +79,8 @@ add_issue(v) add_issue(v, :done_ratio => 20) add_issue(v, :done_ratio => 70) - assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_pourcent - assert_progress_equal 0, v.closed_pourcent + assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_percent + assert_progress_equal 0, v.closed_percent end def test_progress_should_consider_closed_issues_as_completed @@ -86,9 +88,9 @@ v = Version.create!(:project => project, :name => 'Progress') add_issue(v) add_issue(v, :done_ratio => 20) - add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})) - assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_pourcent - assert_progress_equal (100.0)/3, v.closed_pourcent + add_issue(v, :status => IssueStatus.where(:is_closed => true).first) + assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_percent + assert_progress_equal (100.0)/3, v.closed_percent end def test_progress_should_consider_estimated_hours_to_weigth_issues @@ -97,20 +99,20 @@ add_issue(v, :estimated_hours => 10) add_issue(v, :estimated_hours => 20, :done_ratio => 30) add_issue(v, :estimated_hours => 40, :done_ratio => 10) - add_issue(v, :estimated_hours => 25, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})) - assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_pourcent - assert_progress_equal 25.0/95.0*100, v.closed_pourcent + add_issue(v, :estimated_hours => 25, :status => IssueStatus.where(:is_closed => true).first) + assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_percent + assert_progress_equal 25.0/95.0*100, v.closed_percent end def test_progress_should_consider_average_estimated_hours_to_weigth_unestimated_issues project = Project.find(1) v = Version.create!(:project => project, :name => 'Progress') add_issue(v, :done_ratio => 20) - add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})) + add_issue(v, :status => IssueStatus.where(:is_closed => true).first) add_issue(v, :estimated_hours => 10, :done_ratio => 30) add_issue(v, :estimated_hours => 40, :done_ratio => 10) - assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_pourcent - assert_progress_equal 25.0/100.0*100, v.closed_pourcent + assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_percent + assert_progress_equal 25.0/100.0*100, v.closed_percent end def test_should_sort_scheduled_then_unscheduled_versions @@ -130,75 +132,64 @@ assert_equal false, version.completed? end - context "#behind_schedule?" do - setup do - ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests - @project = Project.create!(:name => 'test0', :identifier => 'test0') - @project.trackers << Tracker.create!(:name => 'track') - - @version = Version.create!(:project => @project, :effective_date => nil, :name => 'version') - end - - should "be false if there are no issues assigned" do - @version.update_attribute(:effective_date, Date.yesterday) - assert_equal false, @version.behind_schedule? - end - - should "be false if there is no effective_date" do - assert_equal false, @version.behind_schedule? - end - - should "be false if all of the issues are ahead of schedule" do - @version.update_attribute(:effective_date, 7.days.from_now.to_date) - add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left - add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left - assert_equal 60, @version.completed_pourcent - assert_equal false, @version.behind_schedule? - end - - should "be true if any of the issues are behind schedule" do - @version.update_attribute(:effective_date, 7.days.from_now.to_date) - add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left - add_issue(@version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left - assert_equal 40, @version.completed_pourcent - assert_equal true, @version.behind_schedule? - end - - should "be false if all of the issues are complete" do - @version.update_attribute(:effective_date, 7.days.from_now.to_date) - add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span - add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span - assert_equal 100, @version.completed_pourcent - assert_equal false, @version.behind_schedule? - end + test "#behind_schedule? should be false if there are no issues assigned" do + version = Version.generate!(:effective_date => Date.yesterday) + assert_equal false, version.behind_schedule? end - context "#estimated_hours" do - setup do - @version = Version.create!(:project_id => 1, :name => '#estimated_hours') - end + test "#behind_schedule? should be false if there is no effective_date" do + version = Version.generate!(:effective_date => nil) + assert_equal false, version.behind_schedule? + end - should "return 0 with no assigned issues" do - assert_equal 0, @version.estimated_hours - end + test "#behind_schedule? should be false if all of the issues are ahead of schedule" do + version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date) + add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left + add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left + assert_equal 60, version.completed_percent + assert_equal false, version.behind_schedule? + end - should "return 0 with no estimated hours" do - add_issue(@version) - assert_equal 0, @version.estimated_hours - end + test "#behind_schedule? should be true if any of the issues are behind schedule" do + version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date) + add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left + add_issue(version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left + assert_equal 40, version.completed_percent + assert_equal true, version.behind_schedule? + end - should "return the sum of estimated hours" do - add_issue(@version, :estimated_hours => 2.5) - add_issue(@version, :estimated_hours => 5) - assert_equal 7.5, @version.estimated_hours - end + test "#behind_schedule? should be false if all of the issues are complete" do + version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date) + add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span + add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span + assert_equal 100, version.completed_percent + assert_equal false, version.behind_schedule? + end - should "return the sum of leaves estimated hours" do - parent = add_issue(@version) - add_issue(@version, :estimated_hours => 2.5, :parent_issue_id => parent.id) - add_issue(@version, :estimated_hours => 5, :parent_issue_id => parent.id) - assert_equal 7.5, @version.estimated_hours - end + test "#estimated_hours should return 0 with no assigned issues" do + version = Version.generate! + assert_equal 0, version.estimated_hours + end + + test "#estimated_hours should return 0 with no estimated hours" do + version = Version.create!(:project_id => 1, :name => 'test') + add_issue(version) + assert_equal 0, version.estimated_hours + end + + test "#estimated_hours should return return the sum of estimated hours" do + version = Version.create!(:project_id => 1, :name => 'test') + add_issue(version, :estimated_hours => 2.5) + add_issue(version, :estimated_hours => 5) + assert_equal 7.5, version.estimated_hours + end + + test "#estimated_hours should return the sum of leaves estimated hours" do + version = Version.create!(:project_id => 1, :name => 'test') + parent = add_issue(version) + add_issue(version, :estimated_hours => 2.5, :parent_issue_id => parent.id) + add_issue(version, :estimated_hours => 5, :parent_issue_id => parent.id) + assert_equal 7.5, version.estimated_hours end test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do @@ -225,11 +216,13 @@ # Project 1 now out of the shared scope project_1_issue.reload - assert_equal nil, project_1_issue.fixed_version, "Fixed version is still set after changing the Version's sharing" + assert_equal nil, project_1_issue.fixed_version, + "Fixed version is still set after changing the Version's sharing" # Project 5 now out of the shared scope project_5_issue.reload - assert_equal nil, project_5_issue.fixed_version, "Fixed version is still set after changing the Version's sharing" + assert_equal nil, project_5_issue.fixed_version, + "Fixed version is still set after changing the Version's sharing" # Project 2 issue remains project_2_issue.reload @@ -242,8 +235,8 @@ Issue.create!({:project => version.project, :fixed_version => version, :subject => 'Test', - :author => User.find(:first), - :tracker => version.project.trackers.find(:first)}.merge(attributes)) + :author => User.first, + :tracker => version.project.trackers.first}.merge(attributes)) end def assert_progress_equal(expected_float, actual_float, message="") diff -r d98d22a98252 -r afce8026aaeb test/unit/watcher_test.rb --- a/test/unit/watcher_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/watcher_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -99,6 +99,23 @@ assert_nil issue.addable_watcher_users.detect {|user| !issue.visible?(user)} end + def test_any_watched_should_return_false_if_no_object_is_watched + objects = (0..2).map {Issue.generate!} + + assert_equal false, Watcher.any_watched?(objects, @user) + end + + def test_any_watched_should_return_true_if_one_object_is_watched + objects = (0..2).map {Issue.generate!} + objects.last.add_watcher(@user) + + assert_equal true, Watcher.any_watched?(objects, @user) + end + + def test_any_watched_should_return_false_with_no_object + assert_equal false, Watcher.any_watched?([], @user) + end + def test_recipients @issue.watchers.delete_all @issue.reload diff -r d98d22a98252 -r afce8026aaeb test/unit/wiki_content_test.rb --- a/test/unit/wiki_content_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/wiki_content_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -48,11 +48,12 @@ page = WikiPage.new(:wiki => @wiki, :title => "A new page") page.content = WikiContent.new(:text => "Content text", :author => User.find(1), :comments => "My comment") - with_settings :notified_events => %w(wiki_content_added) do + with_settings :default_language => 'en', :notified_events => %w(wiki_content_added) do assert page.save end assert_equal 1, ActionMailer::Base.deliveries.size + assert_include 'wiki page has been added', mail_body(ActionMailer::Base.deliveries.last) end def test_update_should_be_versioned @@ -99,6 +100,7 @@ end assert_equal 1, ActionMailer::Base.deliveries.size + assert_include 'wiki page has been updated', mail_body(ActionMailer::Base.deliveries.last) end def test_fetch_history @@ -115,7 +117,7 @@ page.reload assert_equal 500.kilobyte, page.content.text.size end - + def test_current_version content = WikiContent.find(11) assert_equal true, content.current_version? diff -r d98d22a98252 -r afce8026aaeb test/unit/wiki_content_version_test.rb --- a/test/unit/wiki_content_version_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/wiki_content_version_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/wiki_page_test.rb --- a/test/unit/wiki_page_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/wiki_page_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff -r d98d22a98252 -r afce8026aaeb test/unit/wiki_redirect_test.rb --- a/test/unit/wiki_redirect_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/wiki_redirect_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -69,6 +69,6 @@ assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') @original.destroy - assert !@wiki.redirects.find(:first) + assert_nil @wiki.redirects.first end end diff -r d98d22a98252 -r afce8026aaeb test/unit/wiki_test.rb --- a/test/unit/wiki_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/wiki_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -84,22 +84,18 @@ assert_equal ja_test, Wiki.titleize(ja_test) end - context "#sidebar" do - setup do - @wiki = Wiki.find(1) - end + def test_sidebar_should_return_nil_if_undefined + @wiki = Wiki.find(1) + assert_nil @wiki.sidebar + end - should "return nil if undefined" do - assert_nil @wiki.sidebar - end + def test_sidebar_should_return_a_wiki_page_if_defined + @wiki = Wiki.find(1) + page = @wiki.pages.new(:title => 'Sidebar') + page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar') + page.save! - should "return a WikiPage if defined" do - page = @wiki.pages.new(:title => 'Sidebar') - page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar') - page.save! - - assert_kind_of WikiPage, @wiki.sidebar - assert_equal 'Sidebar', @wiki.sidebar.title - end + assert_kind_of WikiPage, @wiki.sidebar + assert_equal 'Sidebar', @wiki.sidebar.title end end diff -r d98d22a98252 -r afce8026aaeb test/unit/workflow_test.rb --- a/test/unit/workflow_test.rb Wed May 07 14:15:02 2014 +0100 +++ b/test/unit/workflow_test.rb Tue Sep 09 09:34:53 2014 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,9 +30,9 @@ WorkflowTransition.copy(Tracker.find(2), Role.find(1), Tracker.find(3), Role.find(2)) end - assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false}) - assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 3, :author => false, :assignee => true}) - assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 4, :author => true, :assignee => false}) + assert WorkflowTransition.where(:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false).first + assert WorkflowTransition.where(:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 3, :author => false, :assignee => true).first + assert WorkflowTransition.where(:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 4, :author => true, :assignee => false).first end def test_workflow_permission_should_validate_rule