Merge branch 'master' into feature-receive

Conflicts:
	app/helpers/finance/order_articles_helper.rb
This commit is contained in:
wvengen 2014-01-09 18:42:29 +01:00
commit b30b424540
26 changed files with 247 additions and 69 deletions

1
.gitignore vendored
View File

@ -18,3 +18,4 @@ Capfile
config/deploy.rb
config/deploy/*
.localeapp
.bundle

View File

@ -23,6 +23,8 @@ PATH
specs:
foodsoft_wiki (0.0.1)
acts_as_versioned
content_for_in_controllers
diffy
rails (~> 3.2.15)
wikicloth
@ -61,12 +63,12 @@ GEM
activerecord (>= 3.0.0)
afm (0.2.0)
arel (3.0.3)
better_errors (1.0.1)
better_errors (1.1.0)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bootstrap-datepicker-rails (1.1.1.10)
bootstrap-datepicker-rails (1.1.1.11)
railties (>= 3.0)
builder (3.0.4)
bullet (4.7.1)
@ -80,7 +82,7 @@ GEM
net-ssh-gateway (>= 1.1.0)
capistrano-ext (1.2.1)
capistrano (>= 1.0.0)
capybara (2.2.0)
capybara (2.2.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
@ -102,6 +104,7 @@ GEM
execjs
coffee-script-source (1.6.3)
commonjs (0.2.7)
content_for_in_controllers (0.0.2)
coveralls (0.7.0)
multi_json (~> 1.3)
rest-client
@ -112,6 +115,7 @@ GEM
database_cleaner (1.2.0)
debug_inspector (0.0.2)
diff-lcs (1.2.5)
diffy (3.0.1)
docile (1.1.1)
erubis (2.7.0)
eventmachine (1.0.3)
@ -128,7 +132,7 @@ GEM
faker (1.2.0)
i18n (~> 0.5)
ffi (1.9.3)
haml (4.0.4)
haml (4.0.5)
tilt
haml-rails (0.4)
actionpack (>= 3.1, < 4.1)
@ -203,8 +207,7 @@ GEM
polyamorous (0.5.0)
activerecord (~> 3.0)
polyglot (0.3.3)
prawn (0.13.0)
afm
prawn (0.13.2)
pdf-reader (~> 1.2)
ruby-rc4
ttfunk (~> 1.0.3)
@ -241,7 +244,7 @@ GEM
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (10.1.0)
rake (10.1.1)
rdoc (3.12.2)
json (~> 1.4)
redis (3.0.6)
@ -258,6 +261,7 @@ GEM
vegas (~> 0.1.2)
rest-client (1.6.7)
mime-types (>= 1.16)
rinku (1.7.3)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
@ -266,8 +270,9 @@ GEM
rspec-expectations (2.14.4)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.4)
rspec-rails (2.14.0)
rspec-rails (2.14.1)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 2.14.0)
@ -275,10 +280,10 @@ GEM
rspec-mocks (~> 2.14.0)
rspec-rerun (0.1.3)
rspec (>= 2.11.0)
ruby-prof (0.13.1)
ruby-prof (0.14.2)
ruby-rc4 (0.1.5)
rubyzip (1.1.0)
sass (3.2.12)
sass (3.2.13)
sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
@ -338,7 +343,7 @@ GEM
rails (>= 3.1)
railties (>= 3.1)
tzinfo (0.3.38)
uglifier (2.3.3)
uglifier (2.4.0)
execjs (>= 0.3.0)
json (>= 1.8.0)
uniform_notifier (1.4.0)
@ -348,9 +353,10 @@ GEM
whenever (0.9.0)
activesupport (>= 2.3.4)
chronic (>= 0.6.3)
wikicloth (0.8.0)
wikicloth (0.8.1)
builder
expression_parser
rinku
xpath (2.0.0)
nokogiri (~> 1.3)

View File

@ -1,6 +1,7 @@
FoodSoft
=========
[![Build Status](https://travis-ci.org/foodcoops/foodsoft.png)](https://travis-ci.org/foodcoops/foodsoft)
[![Build Status](https://travis-ci.org/foodcoops/foodsoft.png?branch=master)](https://travis-ci.org/foodcoops/foodsoft)
[![Coverage Status](https://coveralls.io/repos/foodcoops/foodsoft/badge.png?branch=master)](https://coveralls.io/r/foodcoops/foodsoft?branch=master)
[![Code Climate](https://codeclimate.com/github/foodcoops/foodsoft.png)](https://codeclimate.com/github/foodcoops/foodsoft)
[![Dependency Status](https://gemnasium.com/foodcoops/foodsoft.png)](https://gemnasium.com/foodcoops/foodsoft)
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/foodcoops/foodsoft/trend.png)](https://bitdeli.com/foodcoops "Bitdeli Badge")

View File

@ -26,11 +26,11 @@ class ApplicationController < ActionController::Base
def deny_access
session[:return_to] = request.original_url
redirect_to login_url, :alert => I18n.t('application.controller.error_denied')
redirect_to root_url, alert: I18n.t('application.controller.error_denied', sign_in: ActionController::Base.helpers.link_to(t('application.controller.error_denied_sign_in'), login_path))
end
private
def authenticate(role = 'any')
# Attempt to retrieve authenticated user from controller instance or session...
if !current_user
@ -92,6 +92,18 @@ class ApplicationController < ActionController::Base
end
end
def authenticate_or_token(prefix, role = 'any')
if not params[:token].blank?
begin
TokenVerifier.new(prefix).verify(params[:token])
rescue ActiveSupport::MessageVerifier::InvalidSignature
redirect_to root_path, alert: I18n.t('application.controller.error_token')
end
else
authenticate(role)
end
end
# Stores this controller instance as a thread local varibale to be accessible from outside ActionController/ActionView.
def store_controller
Thread.current[:application_controller] = self

View File

@ -28,6 +28,7 @@ class SessionsController < ApplicationController
def destroy
session[:user_id] = nil
session[:return_to] = nil
redirect_to login_url, :notice => I18n.t('sessions.logged_out')
end
end

View File

@ -7,35 +7,14 @@
.modal-body
= f.input :availability
= f.input :name
.fold-line
= f.input :unit_quantity, label: Article.human_attribute_name(:unit),
input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit_quantity)}
= f.input :unit, label: '&times;'.html_safe,
input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit)}
= render partial: 'shared/article_fields_units', locals: {f: f}
= f.input :note
= f.association :article_category
/ TODO labels
.fold-line
= f.input :price do
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= f.input_field :price, class: 'input-mini'
= f.input :tax do
.input-append
= f.input_field :tax, class: 'input-mini'
%span.add-on %
.fold-line
= f.input :deposit do
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= f.input_field :deposit, class: 'input-mini'
.control-group
%label.control-label{for: 'article_fc_price'}
= Article.human_attribute_name(:fc_price)
.controls.control-text#article_fc_price
= number_to_currency(@article.fc_price) rescue nil
= render partial: 'shared/article_fields_price', locals: {f: f}
= f.input :origin
= f.input :manufacturer
@ -44,14 +23,3 @@
= link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
= f.submit class: 'btn btn-primary'
:javascript
var form = $('form.edit_article, form.new_article');
$('#article_price, #article_tax, #article_deposit', form).on('change keyup', function() {
var price = parseFloat($('#article_price', form).val());
var tax = parseFloat($('#article_tax', form).val());
var deposit = parseFloat($('#article_deposit', form).val());
// Article#gross_price and Article#fc_price
var gross_price = (price + deposit) * (tax / 100 + 1);
var fc_price = gross_price * (#{FoodsoftConfig[:price_markup].to_f} / 100 + 1);
$('#article_fc_price').html($.isNumeric(fc_price) ? I18n.l("currency", fc_price) : '&#133;');
});

View File

@ -8,7 +8,7 @@
= csrf_meta_tags
= stylesheet_link_tag "application", :media => "all"
//%link(href="images/favicon.ico" rel="shortcut icon")
= yield(:head)
%body

View File

@ -1,6 +1,6 @@
- title t('.title')
= raw t('.body', user: h(show_user(@user)))
= simple_form_for @user, :url => update_password_path(@user.id, :token => @user.reset_password_token) do |form|
= simple_form_for @user, :url => {:action => 'update_password', :id => @user.id, :token => @user.reset_password_token} do |form|
= form.input :password
= form.input :password_confirmation
.form-actions

View File

@ -17,18 +17,16 @@
= simple_fields_for :article, @order_article.article do |f|
= f.input :name
= f.input :order_number
= f.input :unit
- if @order_article.article.is_a?(StockArticle)
%div.alert= t '.stock_alert'
- else
= simple_fields_for :article_price, @order_article.article_price do |f|
= f.input :unit_quantity
= f.input :price
= f.input :tax
= f.input :deposit
- if @order_article.article.is_a?(StockArticle)
%div.alert= t '.stock_alert'
- else
= simple_fields_for :article_price, @order_article.article_price do |fprice|
= render partial: 'shared/article_fields_units', locals: {f_unit: f, f_uq: fprice}
= render partial: 'shared/article_fields_price', locals: {f: fprice}
= form.input :update_current_price, as: :boolean
= f.input :order_number
.modal-footer
= link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
= form.submit class: 'btn btn-primary'

View File

@ -0,0 +1,34 @@
.fold-line
= f.input :price do
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= f.input_field :price, class: 'input-mini'
= f.input :tax do
.input-append
= f.input_field :tax, class: 'input-mini'
%span.add-on %
.fold-line
= f.input :deposit do
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= f.input_field :deposit, class: 'input-mini'
.control-group
%label.control-label{for: 'article_fc_price'}
= Article.human_attribute_name(:fc_price)
.controls.control-text#article_fc_price
= number_to_currency(f.object.fc_price) rescue nil
-# do this inline, since it's being used in ajax forms only
- field = f.object.class.model_name.underscore
:javascript
var form = $('#article_fc_price').closest('form');
$('##{field}_price, ##{field}_tax, ##{field}_deposit', form).on('change keyup', function() {
var price = parseFloat($('##{field}_price', form).val());
var tax = parseFloat($('##{field}_tax', form).val());
var deposit = parseFloat($('##{field}_deposit', form).val());
// Article#gross_price and Article#fc_price
var gross_price = (price + deposit) * (tax / 100 + 1);
var fc_price = gross_price * (#{FoodsoftConfig[:price_markup].to_f} / 100 + 1);
$('#article_fc_price').html($.isNumeric(fc_price) ? I18n.l("currency", fc_price) : '&#133;');
});

View File

@ -0,0 +1,6 @@
-# use the local 'f', or supply 'f_uq' and 'f_unit' for more control (like in balancing)
.fold-line
= (f_uq rescue f).input :unit_quantity, label: Article.human_attribute_name(:unit),
input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit_quantity)}
= (f_unit rescue f).input :unit, label: '&times;'.html_safe,
input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit)}

View File

@ -281,7 +281,8 @@ de:
application:
controller:
error_authn: Anmeldung erforderlich!
error_denied: Kein Zugriff!
error_denied: Du darfst die gewünschte Seite nicht sehen. Wenn Du denkst, dass Du dürfen solltest, frage eine Administratorin, dass sie Dir die entsprechenden Rechte einräumt. Falls Du Zugang zu mehreren Benutzerkonten hast, möchtest Du Dich vielleicht %{sign_in}.
error_denied_sign_in: als ein anderer Benutzer anmelden
error_members_only: Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!
article_categories:
create:

View File

@ -287,8 +287,10 @@ en:
application:
controller:
error_authn: Authentication required!
error_denied: Access denied!
error_denied: You are not allowed to view the requested page. If you think you should, ask an administrator to give you appropriate permissions. If you have access to multiple user accounts, you might want to %{sign_in}.
error_denied_sign_in: sign in as another user
error_members_only: This action is only available to members of the group!
error_token: Access denied (invalid token)!
article_categories:
create:
notice: Category was stored

View File

@ -21,7 +21,7 @@ Foodsoft::Application.routes.draw do
get '/login/forgot_password' => 'login#forgot_password', as: :forgot_password
post '/login/reset_password' => 'login#reset_password', as: :reset_password
get '/login/new_password' => 'login#new_password', as: :new_password
get '/login/update_password' => 'login#update_password', as: :update_password
put '/login/update_password' => 'login#update_password', as: :update_password
match '/login/accept_invitation/:token' => 'login#accept_invitation', as: :accept_invitation
resources :sessions, :only => [:new, :create, :destroy]

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,6 +1,14 @@
# encoding: utf-8
class PagesController < ApplicationController
skip_before_filter :authenticate, :only => :all
before_filter :only => :all do
authenticate_or_token(['wiki', 'all'])
end
before_filter do
content_for :head, view_context.rss_meta_tag
end
def index
@page = Page.find_by_permalink "Home"
@ -114,6 +122,10 @@ class PagesController < ApplicationController
end
@pages.order(order)
end
respond_to do |format|
format.html
format.rss { render :layout => false }
end
end
def version

View File

@ -1,6 +1,10 @@
module PagesHelper
include WikiCloth
def rss_meta_tag
tag('link', :rel => "alternate", :type => "application/rss+xml", :title => "RSS", :href => all_pages_rss_url).html_safe
end
def wikified_body(body, title = nil)
render_opts = {:locale => I18n.locale} # workaround for wikicloth 0.8.0 https://github.com/nricciar/wikicloth/pull/59
WikiCloth.new({:data => body+"\n", :link_handler => Wikilink.new, :params => {:referer => title}}).to_html(render_opts).html_safe
@ -59,4 +63,10 @@ module PagesHelper
Array.new
end
end
# return url for all_pages rss feed
def all_pages_rss_url(options={})
token = TokenVerifier.new(['wiki', 'all']).generate
all_pages_url({:format => 'rss', :token => token}.merge(options))
end
end

View File

@ -33,6 +33,24 @@ class Page < ActiveRecord::Base
self.permalink = Page.count == 0 ? "Home" : Page.permalink(title)
end
end
def diff
current = versions.latest
old = versions.where(["page_id = ? and lock_version < ?", current.page_id, current.lock_version]).order('lock_version DESC').first
if old
o = ''
Diffy::Diff.new(old.body, current.body).each do |line|
case line
when /^\+/ then o += "#{line.chomp}<br />" unless line.chomp == "+"
when /^-/ then o += "#{line.chomp}<br />" unless line.chomp == "-"
end
end
o
else
current.body
end
end
protected

View File

@ -9,6 +9,7 @@
%li= link_to t('.recent_changes'), all_pages_path(:view => 'recent_changes')
%li= link_to t('.title_list'), all_pages_path(:view => 'title_list')
%li= link_to t('.site_map'), all_pages_path(:view => 'site_map')
%li= link_to image_tag('icons/feed-icon-14x14.png', :alt => 'RSS Feed'), all_pages_rss_url
= form_tag all_pages_path, method: :get, class: 'form-search pull-right' do
= text_field_tag :name, params[:name], class: 'input-medium search-query',
placeholder: t('.search.placeholder')

View File

@ -0,0 +1,19 @@
xml.instruct! :xml, :version => "1.0"
xml.rss :version => "2.0" do
xml.channel do
xml.title FoodsoftConfig[:name] + " Wiki"
xml.description ""
xml.link FoodsoftConfig[:homepage]
for page in @pages
xml.item do
xml.title page.title
xml.description page.diff, :type => "html"
xml.author User.find(page.updated_by).display
xml.pubDate page.updated_at.to_s(:rfc822)
xml.link wiki_page_path(page.permalink)
xml.guid page.updated_at.to_i
end
end
end
end

View File

@ -19,6 +19,7 @@ Gem::Specification.new do |s|
s.add_dependency "rails", "~> 3.2.15"
s.add_dependency 'wikicloth'
s.add_dependency 'acts_as_versioned' # need git version, make sure that is included in foodsoft's Gemfile
s.add_dependency 'diffy'
s.add_dependency 'content_for_in_controllers'
s.add_development_dependency "sqlite3"
end

View File

@ -1,5 +1,7 @@
require 'wikicloth'
require 'acts_as_versioned'
require 'diffy'
require 'content_for_in_controllers'
require 'foodsoft_wiki/engine'
module FoodsoftWiki

View File

@ -1,3 +1,5 @@
require 'stringio'
# put in here all foodsoft tasks
# => :environment loads the environment an gives easy access to the application
@ -23,8 +25,6 @@ module Colors
end
include Colors
require 'stringio'
namespace :foodsoft do
desc "Setup foodsoft"
task :setup_development do
@ -82,10 +82,11 @@ end
def setup_app_config
file = 'config/app_config.yml'
sample = Rails.root.join("#{file}.SAMPLE")
return nil if skip?(file)
puts yellow "Copying #{file}..."
%x( cp #{Rails.root.join("#{file}.SAMPLE")} #{Rails.root.join(file)} )
%x( cp #{sample} #{Rails.root.join(file)} )
reminder(file)
end
@ -120,6 +121,8 @@ def start_server
puts blue "Start your server running 'bundle exec rails s' and visit http://localhost:3000"
end
# Helper Methods
def ask(question, answers = false)
puts question
input = STDIN.gets.chomp
@ -149,3 +152,4 @@ def capture_stdout
ensure
$stdout = STDOUT
end

37
lib/token_verifier.rb Normal file
View File

@ -0,0 +1,37 @@
class TokenVerifier < ActiveSupport::MessageVerifier
def initialize(prefix)
super(self.class.secret)
@_prefix = prefix.is_a?(Array) ? prefix.join(':') : prefix.to_s
end
def generate(message=nil)
fullmessage = [FoodsoftConfig.scope, @_prefix]
fullmessage.append(message) unless message.nil?
super(fullmessage)
end
def verify(message)
r = super(message)
raise InvalidMessage unless r.is_a?(Array) and r.length >= 2 and r.length <= 3
raise InvalidScope unless r[0] == FoodsoftConfig.scope
raise InvalidPrefix unless r[1] == @_prefix
# return original message
if r.length > 2
r[2]
else
nil
end
end
class InvalidMessage < ActiveSupport::MessageVerifier::InvalidSignature; end
class InvalidScope < ActiveSupport::MessageVerifier::InvalidSignature; end
class InvalidPrefix < ActiveSupport::MessageVerifier::InvalidSignature; end
protected
def self.secret
Foodsoft::Application.config.secret_token
end
end

View File

@ -0,0 +1,44 @@
require_relative '../spec_helper'
describe TokenVerifier do
let (:prefix) { 'xyz' }
let (:v) { TokenVerifier.new(prefix) }
let (:msg) { v.generate }
it 'validates' do
expect{ v.verify(msg) }.to_not raise_error
end
it 'validates when recreated' do
v2 = TokenVerifier.new(prefix)
expect{ v2.verify(msg) }.to_not raise_error
end
it 'does not validate with a different prefix' do
v2 = TokenVerifier.new('abc')
expect { v2.verify(msg) }.to raise_error(TokenVerifier::InvalidPrefix)
end
it 'does not validate in a different foodcoop scope' do
msg
oldscope = FoodsoftConfig.scope
begin
FoodsoftConfig.scope = Faker::Lorem.words(1)
v2 = TokenVerifier.new(prefix)
expect{ v2.verify(msg) }.to raise_error(TokenVerifier::InvalidScope)
ensure
FoodsoftConfig.scope = oldscope
end
end
it 'does not validate a random string' do
expect{ v.verify(Faker::Lorem.characters(100)) }.to raise_error(ActiveSupport::MessageVerifier::InvalidSignature)
end
it 'returns the message' do
data = [5, {'hi' => :there}, 'bye', []]
msg = v.generate(data)
expect(v.verify(msg)).to eq data
end
end