Commit b20a7db0 authored by azul's avatar azul

0.6.0 - core rework

This is the first full release after the rework of the crabgrass codebase.
Some years ago the crabgrass codebase had diverged into different
incompatible development trees. We're about bringing the different forks
back together in a streamlined version.

The 0.6 series was migrated to rails 3.2 and ruby 1.9.3.

The following changes have been included since 0.6.0-beta:

Pull request #131 from azul/bugfix/hide-user-ghosts
 * hide user ghosts in directory - fixes #8750

Pull request #129 from azul/bugfix/hide-page-existance
 * hide existance of a page if it's not visible

Pull request #130 from azul/bugfix/survey-links
 * fix survey page nav - fixes #8453

Pull request #128 from azul/feature/hide-deleted-users
 * make sure user ghosts are valid
 * hide deleted users in page banners
 * do not create unused widgets directory

Pull request #127 from azul/feature/ui-tweaks
 * add explaination to recent pages and page search - fixes #4352
 * public switch on top of page sidebar - fixes #4316

Pull request #126 from azul/bugfix/cookie-warning
 * fix cookie warning in login modal box - fixes #7812

Pull request #125 from azul/bugfix/loose-page-access
 * refactor: ParticipationAccess module for UserPart. and GroupPart
 * revoke page access despite participation - fixes #8502

Pull request #124 from azul/bugfix/keep-user-banner-private
 * keep pages and banners hidden - fixes #8500

Pull request #123 from azul/bugfix/add-multiple-tasks
 * use common/items/new_form for tasks

Pull request #122 from azul/feature/update-notice
 * notify watching users on page updates

Pull request #121 from azul/bugfix/page-stats
 * only display page stats if we track them - fixes #7827

Pull request #120 from azul/bugfix/survey-page
 * more survey page fixes
 * fix survey page paths.

Pull request #119 from azul/feature/submit-shortcut
 * tabindex for wiki forms - fixes #7848
 * fix me profile controllers with strong params
 * submit forms with ctrl+enter from textentries - refs #7848

Pull request #118 from azul/bugfix/greencloth-fixes
 * make wiki headings even more robust, rescue errors

Pull request #117 from azul/bugfix/refresh-page
 * no cache for Page#show - load ajaxy changes - fixes #7849

Pull request #116 from azul/bugfix/greencloth-fixes
 * deal with complex headings in a meaningful way - fixes #4151
 * bring back greencloth tests
 * use dark color for nav on bright background

Pull request #115 from azul/bugfix/image-tweaks
 * keep old banner if new upload fails - fixes #4356

Pull request #113 from azul/bugfix/asset-conversion
 * list other formats on asset page - fixes #7853

Pull request #114 from azul/bugfix/directory-tweaks
 * cache group / people headings separately
 * no scroll bars in directory entries - fixes #7810

Pull request #112 from azul/bugfix/asset-conversion
 * fix asset mime image to not use sprockets for now

Pull request #111 from azul/bugfix/autocomplete
 * prevent autocomplete duplicates in browser - fixes #8405

Pull request #110 from azul/bugfix/ranked-vote-display
 * _html attributes html_safe in format_attribute - fixes #6759

Pull request #109 from azul/bugfix/image-tweaks
 * Paths don't include the asset file extension anymore
 * bring back banner image - fixes #8424
 * simplify Groups::ProfilesController a bit
 * replace deprecated set_table_name

Pull request #108 from azul/bugfix/comment-and-message-tweaks

Pull request #107 from azul/bugfix/comment-and-message-tweaks
 * image of user who created request in notification - fixes #8333

Pull request #105 from azul/bugfix/comment-and-message-tweaks
 * preview without tags for message displays - fixes #6743

Pull request #106 from azul/bugfix/page-updates
 * make sure changing a page updates it - fixes #7883

Pull request #104 from azul/bugfix/security-tweaks
 * No error for comment without edit access - fixes #8410
 * fix error when visiting page anonymously
 * fixup: guard expects method symbols
 * use login_required in People::FriendRequestController
 * prevent non admin from changing page access
parents a9927a26 10ad13a0
(function () {
var selector = '.cookie_warning';
document.observe("dom:loaded", function() {
function showIfCookiesDisabled(elem) {
// two seconds should be enough to read the cookie.
var expires = (new Date(Date.now() + 2000)).toGMTString();
document.cookie = "are_cookies_enabled=1; expires=" + expires;
if (document.cookie.length == 0) {
elem.show();
}
}
$$(selector).each(showIfCookiesDisabled);
document.on('modal:onComplete', function(event, elem) {
var warning = elem.down(selector);
if (warning) showIfCookiesDisabled(warning);
});
});
})();
......@@ -36,6 +36,28 @@
});
})();
// submit enhancements
//
// submit a form with ctrl+enter
//
(function() {
function keyHandler (event, element){
var key = event.which || event.keyCode;
var input = element || event.currentTarget;
var button = input.form.select('.btn-primary').first();
switch (key) {
default:
break;
case Event.KEY_RETURN:
if (event.ctrlKey) button.click();
break;
}
}
document.on('keydown', 'form textarea', keyHandler)
})();
// Toggle the visibility of another element based on if a checkbox is checked or
// not. Additionally, sets the focus to the first input or textarea that is visible.
......
......@@ -373,8 +373,15 @@ Autocomplete.prototype = {
* display.
*/
appendSuggestions: function(response) {
this.suggestions = this.suggestions.concat(response.suggestions);
this.data = this.data.concat(response.data);
var suggestions = this.suggestions;
var data = this.data;
response.suggestions.each( function(value, i) {
var previous = suggestions.indexOf(value);
if (previous == -1) {
suggestions.push(value);
data.push(response.data[i]);
}
});
},
pending: 0,
......
......@@ -432,7 +432,10 @@ Modalbox.Methods = {
event: function(eventName) {
try {
if(this.options[eventName]) {
// cg addition: fire events like for ajax requests.
// these are easy to hook into.
var ev = this.MBwindow.fire("modal:" + eventName);
if(this.options[eventName] && !ev.stopped) {
var returnValue = this.options[eventName](); // Executing callback
this.options[eventName] = null; // Removing callback after execution
if(returnValue != undefined)
......
......@@ -32,6 +32,7 @@ class AssetsController < ApplicationController
def destroy
@asset.destroy
current_user.updated(@asset.page)
respond_to do |format|
format.js {render text: 'if (initAjaxUpload) initAjaxUpload();' }
format.html do
......@@ -64,7 +65,7 @@ class AssetsController < ApplicationController
end
def thumb_name_from_path(path)
$~['thumb'].to_sym if path =~ /#{THUMBNAIL_SEPARATOR}(?<thumb>[a-z]+)\.[^\.]+$/
$~['thumb'].to_sym if path =~ /#{THUMBNAIL_SEPARATOR}(?<thumb>[a-z]+)$/
end
# returns 'inline' for formats that web browsers can display, 'attachment' otherwise.
......
......@@ -8,7 +8,7 @@ module Common::Application::BeforeFilters
before_filter :header_hack_for_ie6
before_filter :redirect_unverified_user
before_filter :enforce_ssl_if_needed
before_filter :setup_theme
before_render :setup_theme
before_render :setup_context
end
......@@ -77,7 +77,7 @@ module Common::Application::BeforeFilters
#
# overwrite if you want to handle permissions differently
def authorized?
check_permissions
@authorized ||= check_permissions
end
#
......
......@@ -3,7 +3,6 @@ class Groups::AvatarsController < Groups::BaseController
include_controllers 'common/avatars'
include_controllers 'common/always_perform_caching'
before_filter :setup
skip_before_filter :login_required
cache_sweeper :group_sweeper
guard :allow
......
class Groups::PermissionsController < Groups::BaseController
before_filter :login_required
helper 'castle_gates'
def index
......
......@@ -9,15 +9,11 @@ class Groups::ProfilesController < Groups::BaseController
def update
if params[:clear_photo]
@profile.picture.destroy
success :profile_saved.t
redirect_to edit_group_profile_url(@group)
else
@profile.save_from_params profile_params
if @profile.valid?
success :profile_saved.t
redirect_to edit_group_profile_url(@group)
end
end
success :profile_saved.t
redirect_to edit_group_profile_url(@group)
end
private
......
......@@ -9,15 +9,11 @@ class Me::ProfileController < Me::BaseController
def update
if params[:clear_photo]
@profile.picture.destroy
success :profile_saved.t
redirect_to edit_me_profile_path
else
@profile.save_from_params params['profile']
if @profile.valid?
success :profile_saved.t
redirect_to edit_me_profile_path
end
@profile.save_from_params profile_params
end
success :profile_saved.t
redirect_to edit_me_profile_path
end
protected
......@@ -26,5 +22,9 @@ class Me::ProfileController < Me::BaseController
@profile = current_user.profiles.public
end
def profile_params
params[:profile].permit :place, :organization, :role, :summary,
{:picture => [:upload]}
end
end
......@@ -22,7 +22,7 @@ class Pages::AttributesController < Pages::SidebarsController
else
owner = Group.find_by_name params[:owner]
end
raise_not_found('owner') unless owner
raise_not_found(:owner.t) unless owner
@page.owner = owner
@page.save!
redirect_to page_path(@page)
......
......@@ -8,6 +8,7 @@ class Pages::BaseController < ApplicationController
before_filter :login_required, except: :show
before_filter :authorization_required
before_filter :bust_cache, only: :show
permissions :pages
guard :may_ACTION_page?
guard print: :may_show_page?
......
......@@ -18,8 +18,8 @@ module Pages::BeforeFilters
#
def default_fetch_data
@page ||= Page.find(params[:page_id] || params[:id])
unless @page
raise_not_found(:thing_not_found.t(thing: :page.t))
unless @page && may_show_page?
raise_not_found(:page.t)
end
if logged_in?
......@@ -74,6 +74,16 @@ module Pages::BeforeFilters
end
end
# ensure the page will be reloaded when navigated to in browser history
# why? because we use a bunch of ajax on the pages - for example when
# adding comments. It's really odd if these disappear when you navigate
# back.
def bust_cache
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
##
## AFTER FILTERS
##
......
......@@ -22,6 +22,7 @@ class Pages::ParticipationsController < Pages::SidebarsController
elsif params[:star]
star
elsif params[:access]
raise_denied unless may_admin_page?
access
end
end
......
......@@ -7,7 +7,7 @@ class Pages::PostsController < ApplicationController
helper 'pages/post'
prepend_before_filter :fetch_data
before_filter :login_required
before_filter :login_required, except: :show
before_filter :authorization_required
guard :may_ALIAS_post?
guard show: :may_show_page?
......
......@@ -5,6 +5,7 @@
class Pages::SidebarsController < ApplicationController
before_filter :fetch_page
before_filter :login_required
before_filter :authorization_required
permissions :pages
guard :may_edit_page?
......
class Pages::TagsController < Pages::SidebarsController
permissions 'pages'
helper 'pages/tags'
def index
......
class People::FriendRequestsController < People::BaseController
before_filter :login_required
guard create: :may_request_contact?,
new: :may_request_contact?,
destroy: :may_remove_contact?
def new
end
......@@ -24,15 +30,4 @@ class People::FriendRequestsController < People::BaseController
redirect_to entity_url(@user)
end
protected
def authorized?
if action?('create', 'new')
may_request_contact?
elsif action?('destroy')
logged_in? &&
current_user.friend_of?(@user)
end
end
end
......@@ -55,6 +55,7 @@ class Wikis::WikisController < Wikis::BaseController
if params[:save] || params[:force_save]
version = params[:save] ? params[:wiki][:version] : nil # disable version checked if force save
@wiki.update_section!(@section, current_user, version, params[:wiki][:body])
current_user.updated(@page)
success
end
end
......
......@@ -82,6 +82,10 @@ class Context
self.breadcrumbs << object
end
def banner_partial
'/layouts/context/normal_banner_content'
end
protected
def define_crumbs()
......@@ -123,6 +127,11 @@ class Context::Committee < Context::Group
push_crumb self.entity
end
end
def banner_partial
'/layouts/context/nested_banner_content'
end
end
class Context::Council < Context::Committee
......@@ -149,6 +158,17 @@ class Context::User < Context
end
end
class Context::UserGhost < Context::User
def define_crumbs
push_crumb :people
end
def banner_partial
'/layouts/context/hidden_banner_content'
end
end
class Context::Me < Context
def self.wrapped_base_class
::User
......
......@@ -2,8 +2,13 @@ module Common::Ui::HelpHelper
protected
def formatting_reference_link
(%Q{<a class="icon help_16" href="/do/static/greencloth" onclick="quickRedReference(); return false;">%s</a>} % :formatting_reference_link.t).html_safe
def formatting_reference_link(options = {})
options.reverse_merge class: "icon help_16",
href: "/do/static/greencloth",
onclick: "quickRedReference(); return false;"
content_tag(:a, options) do
:formatting_reference_link.t
end
end
# returns the related help string, but only if it is translated.
......
......@@ -50,12 +50,6 @@ module Common::Ui::LayoutHelper
lines << optional_stylesheets.collect do |sheet|
stylesheet_link_tag( current_theme.stylesheet_url(sheet) )
end
if context_banner_style || @content_for_style
lines << '<style type="text/css">'
lines << @content_for_style
lines << context_banner_style
lines << '</style>'
end
lines << '<!--[if IE 6]>'
lines << stylesheet_link_tag('ie6')
lines << stylesheet_link_tag('icon_gif')
......
......@@ -49,6 +49,7 @@ module Common::Ui::TextHelper
truncate(text, length: length, omission: omission + link)
end
# def linked_activity_description(activity)
# description = activity.try.safe_description(self)
# expand_links(description)
......
module Common::Utility::ContextHelper
# we only show the context if you either:
# * are allowed to do what you are doing
# * can see the context entity anyway (for error messages)
def visible_context?
@context &&
( @authorized || current_user.may?(:view, @context.entity) )
end
#
# sets up the navigation variables from the current theme.
#
......@@ -33,7 +42,7 @@ module Common::Utility::ContextHelper
end
def context_class
@context.breadcrumbs.first if @context
@context.breadcrumbs.first if visible_context?
end
##
......@@ -56,7 +65,7 @@ module Common::Utility::ContextHelper
end
"#banner_content {background-image: url(#{url}); background-color: #{bg}}\n"+
"#banner_content a.title {color: #{fg}; text-shadow: #{shadow} 0 0 2px}\n"+
"ul#banner_nav_ul li.tab a.tab {background-color: #{nav_shade}}"
"ul#banner_nav_ul li.tab a.tab {color: #{fg}; background-color: #{nav_shade}}"
else
"#banner_content {background-image: url(#{url})}"
end
......
......@@ -7,10 +7,9 @@ module Me::DiscussionsHelper
I18n.t(:message_user_wrote_caption, user: post.created_by.try.display_name)
end
# remove surrounding <p> from body_html
html = post.body_html.try.gsub(/(\A\s*<p>)|(<\/p>\s*\Z)/, "")
preview = strip_tags(post.body_html).truncate(300).html_safe
content_tag(:em, caption, class: "author_caption") + " \n" +
content_tag(:span, truncate(strip_links(html), length: 300), class: "post_body")
content_tag(:span, preview, class: "post_body")
end
##
......
......@@ -4,26 +4,20 @@ module ProfileHelper
formy.heading :banner.t
if @profile.picture
formy.row do |r|
r.input clear_banner_input
end
else
formy.row do |r|
r.label I18n.t(:file)
r.label_for 'profile_picture_upload'
r.input file_field_tag('profile[picture][upload]',
id: 'profile_picture_upload')
r.info :banner_info.t(
optimal_dimensions: "#{banner_width.to_i} x #{banner_height.to_i}"
)
formy.row(class: :current_banner) do |r|
r.input picture_tag(@profile.picture, :medium)
end
end
formy.row do |r|
r.label I18n.t(:file)
r.label_for 'profile_picture_upload'
r.input file_field_tag('profile[picture][upload]',
id: 'profile_picture_upload')
r.info :banner_info.t(
optimal_dimensions: "#{banner_width.to_i} x #{banner_height.to_i}"
)
end
end
def clear_banner_input
[ picture_tag(@profile.picture, :medium),
submit_tag("Clear", name: 'clear_photo')
].join '<br/>'
end
end
......@@ -4,7 +4,7 @@ module Wikis::SectionsHelper
doc = Hpricot(wiki.body_html)
doc.search('h4 a.anchor, h3 a.anchor, h2 a.anchor, h1 a.anchor').each do |anchor|
subsection = anchor['href'].sub(/^.*#/, '')
add_edit_link_to_heading(wiki, anchor, subsection)
add_edit_link_to_heading(wiki, anchor, subsection) if wiki.edit_sections?
wrap_in_div(wiki, doc, subsection, section == :document)
end
doc.to_html.html_safe
......
......@@ -178,6 +178,9 @@ class Asset < ActiveRecord::Base
medium = nil if small && medium && medium.size == small.size
[small, medium, large].compact
end
def other_formats
self.select{|t| !['small', 'medium', 'large'].include?(t.name)}
end
end
base.define_thumbnails( {} ) # root Asset class has no thumbnails
end
......
......@@ -19,6 +19,7 @@ class GroupParticipation < ActiveRecord::Base
# this includes the ability to find featured-pages in GroupParticipation
# include GroupParticipationExtension::Featured
include GroupParticipationExtension::PageHistory
include ParticipationAccess
belongs_to :page, inverse_of: :group_participations
belongs_to :group, inverse_of: :participations
......@@ -31,28 +32,4 @@ class GroupParticipation < ActiveRecord::Base
group
end
def access_sym
ACCESS_TO_SYM[self.access]
end
# can only be used to increase access, not remove it.
def grant_access=(value)
value = ACCESS[value.to_sym] if value.is_a?(Symbol) or value.is_a?(String)
if value
if read_attribute(:access)
if read_attribute(:access) > value
write_attribute(:access, value)
end
else
write_attribute(:access, value)
end
end
end
# can be used to add or remove access
def access=(value)
value = ACCESS[value] if value.is_a? Symbol or value.is_a?(String)
write_attribute(:access, value)
end
end
module ParticipationAccess
def access_sym
ACCESS_TO_SYM[self.access]
end
# can only be used to increase access.
# because access is only increased, you cannot remove access with grant_access.
def grant_access=(value)
value = ACCESS[value] if ACCESS.has_key?(value)
return if value.nil?
current_access = read_attribute(:access) || 100
if value < current_access
write_attribute(:access, value)
end
end
# sets the access level to be value, regardless of what it was before.
# if value is nil, no change is made. If value is :none, then access is removed.
def access=(value)
return if value.nil?
value = ACCESS[value] if ACCESS.has_key?(value)
write_attribute(:access, value)
end
def grants_access?(perm)
asked_access_level = ACCESS[perm] || ACCESS[:view]
return false unless self.access
self.access <= asked_access_level
end
end
......@@ -14,6 +14,8 @@
#
class UserParticipation < ActiveRecord::Base
include ParticipationAccess
belongs_to :page, inverse_of: :user_participations
belongs_to :user, inverse_of: :participations
......@@ -34,31 +36,6 @@ class UserParticipation < ActiveRecord::Base
user
end
def access_sym
ACCESS_TO_SYM[self.access]
end
# can only be used to increase access.
# because access is only increased, you cannot remove access with grant_access.
def grant_access=(value)
value = ACCESS[value] if value.is_a?(Symbol) or value.is_a?(String)
if value
if read_attribute(:access)
write_attribute(:access, [value,read_attribute(:access)].min )
else
write_attribute(:access, value)
end
end
end
# sets the access level to be value, regardless of what it was before.
# if value is nil, no change is made. If value is :none, then access is removed.
def access=(value)
return if value.nil?
value = ACCESS[value] if value.is_a? Symbol or value.is_a?(String)
write_attribute(:access, value)
end
protected
def clear_tag_cache
......
......@@ -143,8 +143,11 @@ class Discussion < ActiveRecord::Base
last_post: post,
replied_by_id: post.user_id,
replied_at: post.updated_at )
PrivateMessageNotice.create!(user: post.discussion.user_talking_to(post.user), message: post.body_html, from: post.user) if post.private?
if post.private?
PrivateMessageNotice.create! from: post.user,
user: post.discussion.user_talking_to(post.user),
message: post.body_html
end
end
#
......
......@@ -110,10 +110,6 @@ class Post < ActiveRecord::Base
return post
end
def body_html
read_attribute(:body_html).try :html_safe
end
# used for default context, if present, to set for any embedded links
def owner_name
discussion.page.owner_name if discussion.page
......
......@@ -102,6 +102,10 @@ class Notice < ActiveRecord::Base
I18n.t(data[attr], count: 1)
end
end
rescue
Rails.logger.error "Invalid attribute #{attr} in Notice ##{id}."
Rails.logger.debug "value: " + data[attr].inspect
raise
end
end
class PageUpdateNotice < PageNotice
def display_title
I18n.t("page_updated", data).html_safe
end
def display_body_as_quote?
false
end
def display_body
""
end
def display_label
I18n.t "page_update"
end
end