Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
Loading items

Target

Select target project
  • liberate/crabgrass
  • netsec/crabgrass
  • tern/crabgrass
  • leixet/crabgrass
  • sleazysquid/crabgrass
  • djspacecat/crabgrass
  • tobasti/crabgrass
  • anoixti_antispisistiki/crabgrass
  • b4x/crabgrass
9 results
Select Git revision
Loading items
Show changes

Commits on Source 28

Showing
with 505 additions and 9 deletions
...@@ -152,10 +152,6 @@ gem 'daemons' ...@@ -152,10 +152,6 @@ gem 'daemons'
# unpack file uploads # unpack file uploads
gem 'rubyzip', '~> 1.2.2', require: false gem 'rubyzip', '~> 1.2.2', require: false
# load new rubyzip, but with the old API.
# TODO: use the new zip api and remove gem zip-zip
gem 'zip-zip', require: 'zip'
# gnupg for email encryption # gnupg for email encryption
# #
gem 'mail-gpg', '~> 0.3.3' gem 'mail-gpg', '~> 0.3.3'
......
...@@ -279,8 +279,6 @@ GEM ...@@ -279,8 +279,6 @@ GEM
will_paginate (3.1.7) will_paginate (3.1.7)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zip-zip (0.3)
rubyzip (>= 1.0.0)
PLATFORMS PLATFORMS
ruby ruby
...@@ -336,7 +334,6 @@ DEPENDENCIES ...@@ -336,7 +334,6 @@ DEPENDENCIES
web-console web-console
whenever whenever
will_paginate (~> 3.1) will_paginate (~> 3.1)
zip-zip
BUNDLED WITH BUNDLED WITH
1.17.3 1.17.3
body {
font-family: Verdana, "Liberation Sans", Arial, sans-serif;
font-size: 12px;
line-height: 1.428571429;
color: #000;
max-width: 1200px;
padding-right: 40px;
padding-left: 40px;
}
h1 {
font-size: 30px;
color: #666666;
border-bottom: 2px dotted #999999;
margin: 0.67em 0;
}
h2 {
font-size: 18px;
border-bottom: 1px dashed #cccccc;
margin: 0.83em 0;
}
a {
color: #2a5183;
text-decoration: none;
}
a.anchor, a.shy {display:none;}
a:hover {
color: #172d49;
text-decoration: underline;
}
a.back {
float: right;
margin-right: 10px;
}
ul, ol {
padding-left: 2.5em;
margin-left: 0.5em;
margin-top: 0;
margin-bottom: 9px;
}
code {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
display: block;
border: solid 1px #ffe8a9;
background-color: #fff6dd;
}
.title_box {
margin-bottom: 15px;
margin-top: 25px;
background-color: #eee;
border: 1px solid #ddd;
border-radius: 4px;
}
.backlink {
margin-right: 10px;
float: 'right';
}
require 'zip'
require 'fileutils'
class Group::ArchiveController < Group::BaseController
def show
authorize @group, :admin?
@request = RequestToCreateGroupArchive.to_group(@group).pending.last
@archive = @group.archive
respond_to do |format|
format.html
format.zip do
send_file @archive.zipfile, type: 'application/zip', charset: 'utf-8'
end
end
end
def create
authorize @group, :create_archive?
Delayed::Job.enqueue GroupArchiveJob.new(@group, current_user, I18n.locale)
redirect_to group_archive_url(@group)
end
def destroy
authorize @group, :admin?
@group.archive.destroy
redirect_to group_archive_url(@group)
end
end
...@@ -38,7 +38,8 @@ class Group::RequestsController < Group::BaseController ...@@ -38,7 +38,8 @@ class Group::RequestsController < Group::BaseController
REQUEST_TYPES = { REQUEST_TYPES = {
destroy_group: 'RequestToDestroyOurGroup', destroy_group: 'RequestToDestroyOurGroup',
create_council: 'RequestToCreateCouncil' create_council: 'RequestToCreateCouncil',
create_group_archive: 'RequestToCreateGroupArchive'
}.with_indifferent_access }.with_indifferent_access
def requested_class def requested_class
......
...@@ -126,4 +126,45 @@ module Group::LinksHelper ...@@ -126,4 +126,45 @@ module Group::LinksHelper
RequestToRemoveUser RequestToRemoveUser
klass.for_membership(membership).first_or_initialize klass.for_membership(membership).first_or_initialize
end end
def create_group_archive_link
if logged_in?
if policy(@group).create_archive?
link_to :create_a_new_thing.t(thing: :archive.t),
group_archive_path(@group),
method: :post,
class: 'btn btn-primary btn-space'
elsif may_create?(request_to_create_group_archive)
link_to :create_a_new_thing.t(thing: :archive.t),
group_requests_path(@group, type: 'create_group_archive'),
method: 'post',
class: 'btn btn-primary btn-space'
end
end
end
def reload_archive_page_link
link_to :group_archive_settings_reload.t, group_archive_path(@group)
end
def request_to_create_group_archive
RequestToCreateGroupArchive.new(recipient: @group, requestable: @group)
end
def destroy_group_archive_link
if logged_in? && policy(@group).update?
button_to :destroy_thing.t(thing: :archive.t),
group_archive_path(@group),
method: :delete,
class: 'btn btn-danger'
end
end
def download_group_archive_link
if logged_in? && policy(@group).admin?
link_to @group.archive.zipname,
group_archive_path(@group, format: :zip),
class: 'btn btn-default'
end
end
end end
GroupArchiveJob = Struct.new(:group, :user, :language) do
def enqueue(job)
Group::Archive.create!(group: group, created_by_id: user.id)
I18n.locale = language
end
def perform
# although we should only have one archive per group,
# we make sure to get the newest
archive = Group::Archive.order(created_at: :desc).
find_by(group: group, created_by_id: user.id)
archive.process if archive
end
end
...@@ -154,6 +154,17 @@ class Group < ApplicationRecord ...@@ -154,6 +154,17 @@ class Group < ApplicationRecord
format('%s (%s)', display_name, name) format('%s (%s)', display_name, name)
end end
def group_names
names = []
if self.parent_id
names << self.parent.name
else
names << self.name
end
names += self.children.pluck(:name) if self.children.any?
return names
end
# visual identity # visual identity
def banner_style def banner_style
@style ||= Style.new(color: '#eef', background_color: '#1B5790') @style ||= Style.new(color: '#eef', background_color: '#1B5790')
...@@ -205,6 +216,8 @@ class Group < ApplicationRecord ...@@ -205,6 +216,8 @@ class Group < ApplicationRecord
has_many :wikis, through: :profiles has_many :wikis, through: :profiles
has_one :archive, dependent: :destroy
def public_wiki def public_wiki
public_profile.try.wiki public_profile.try.wiki
end end
......
require 'zip'
require 'zipfilegenerator'
#
# HTML-Archive of group content for download
#
# creates two zip files:
#
# - singlepage: one HTML file per group or committee
# - pages: one HTML file per page
#
# Big files are not archived. For those files we show
# download links.
#
# TODO: currently we store the ids of the big files during
# archive generation and show links to the files which are
# still available. New big files will not be displayed.
#
class Group::Archive < ActiveRecord::Base
belongs_to :group
belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_id'
validates_presence_of :group, :created_by_id
before_destroy :delete_archive_dir
attr_reader :excluded_assets
ARCHIVED_TYPES = %w[WikiPage DiscussionPage AssetPage Gallery].freeze
EXPIRY_PERIOD = 1.month.freeze
enum state: { pending: 0, success: 1 }
def process
return false unless valid?
remove_old_archive
generator = Group::Archive::Generator.new(archive: self, types: ARCHIVED_TYPES)
generator.generate
self.excluded_asset_ids = generator.excluded_assets.join(',')
self.filename = zipname
self.state = 'success'
save!
end
def zipfile
File.join(archive_dir, zipname)
end
def zipname
"#{group.name}.zip"
end
def excluded_assets
unless excluded_asset_ids.empty?
ids = excluded_asset_ids.split(',')
assets = Asset.where(id: ids)
else
[]
end
end
# remove self where it is not needed!
def expires_at
self.created_at + EXPIRY_PERIOD
end
def expired?
Time.zone.now > expires_at
end
def archived_by
created_by.login
end
def zipname_suffix
"#{group.name}.zip"
end
private
def archive_dir
File.join(ARCHIVE_STORAGE, group.id.to_s)
end
def remove_old_archive
delete_archive_dir
Group::Archive.where(group_id: group.id).order('created_at DESC').offset(1).destroy_all
create_archive_dir
end
def create_archive_dir
FileUtils.mkdir_p(archive_dir)
end
def delete_archive_dir
FileUtils.rm_r(archive_dir) if File.exist? archive_dir
end
end
#
# All paths used for archive creation
#
module Group::Archive::Path
def css_file(group)
if group.committee? # FIXME: will not work for councils (if exported as children)
'../archive.css'
else
'archive.css'
end
end
def avatar_url_for(group)
format("#{APP_ROOT}/public/avatars/%s/large.jpg", group.avatar_id || 0)
end
# used in pages_zip only
def group_path(group)
if !group.parent_id
File.join(tmp_dir, group.name)
else
File.join(tmp_dir, group.parent.name, group.name)
end
end
def index_path(group)
File.join(group_path(group), 'index.html')
end
def file_path(page, group)
file_name = "#{page.name_url}.html"
File.join(group_path(group), file_name)
end
def asset_dir(group = @group)
File.join(group_path(group), 'assets')
end
def asset_group_path(asset_id, group)
File.join(group_path(group), 'assets', asset_id)
end
# used in singlepage only
def asset_path(asset_id)
File.join(tmp_dir, 'assets', asset_id)
end
end
...@@ -44,6 +44,10 @@ module Group::Pages ...@@ -44,6 +44,10 @@ module Group::Pages
pages.where(name: name).first pages.where(name: name).first
end end
def pages_of_type(types)
pages_owned.where(type: types).order('type desc').order('updated_at DESC')
end
# #
# build or modify a group_participation between a group and a page # build or modify a group_participation between a group and a page
# return the group_participation object, which must be saved for # return the group_participation object, which must be saved for
......
class Notice::GroupArchivedNotice < Notice
alias_attr :group, :noticable
def button_text
end
def display_label
:archive.t
end
def display_body
display_attr(:body).html_safe
end
def redirect_object
group.try.name || data[:group]
end
protected
before_create :encode_data
def encode_data
self.data = {title: :notification, body: [:group_archive_created, {group: ('<group>%s</group>' % group.name), user: user}]}
end
end
...@@ -340,6 +340,13 @@ class Page < ApplicationRecord ...@@ -340,6 +340,13 @@ class Page < ApplicationRecord
true true
end end
# override this in subclasses…
# _type says if it's a singlepage or multi page archive
def archive_html(_type)
''
end
protected protected
def save_timestamps def save_timestamps
......
#
# A request to create archive of group content.
#
# recipient: the group to be exported
# requestable: the same group
# created_by: person in group who want their group to be destroyed
#
class RequestToCreateGroupArchive < Request
validates_format_of :recipient_type, with: /\AGroup\z/
validates_format_of :requestable_type, with: /\AGroup\z/
alias_attr :group, :recipient
# once the group has been deleted we do not require it anymore.
def recipient_required?
!approved?
end
alias requestable_required? recipient_required?
def self.already_exists?(options)
pending.from_group(options[:group]).exists?
end
def may_create?(user)
user.may?(:admin, group)
end
def may_approve?(user)
user.may?(:admin, group) and user.id != created_by_id
end
def no_duplicate; end
alias may_view? may_create?
alias may_destroy? may_create?
def after_approval
Delayed::Job.enqueue GroupArchiveJob.new(group, created_by, I18n.locale)
end
def event
:create_group_archive
end
def event_attrs
{ groupname: group.name, recipient: created_by, archived_by: approved_by }
end
def description
[:request_to_create_group_archive_description, description_args]
end
def short_description
[:request_to_create_group_archive_short, description_args]
end
def description_args
{ group: group_span,
group_type: group.group_type.downcase,
user: user_span(created_by) }
end
def icon_entity
group
end
end
...@@ -13,7 +13,7 @@ module Tracking::Action ...@@ -13,7 +13,7 @@ module Tracking::Action
update_user_access: ['Page::History::GrantUserAccess'], update_user_access: ['Page::History::GrantUserAccess'],
update_title: ['Page::History::ChangeTitle'], update_title: ['Page::History::ChangeTitle'],
update_wiki: ['Page::History::UpdatedContent'], update_wiki: ['Page::History::UpdatedContent'],
create_star: ['Notice::PostStarredNotice'] create_star: ['Notice::PostStarredNotice'] # FIXME: does this really track anything? tracking seems to be page related.
}.freeze }.freeze
def self.track(event, options = {}) def self.track(event, options = {})
......
...@@ -118,6 +118,10 @@ class Wiki < ApplicationRecord ...@@ -118,6 +118,10 @@ class Wiki < ApplicationRecord
read_attribute(:body_html).try.html_safe read_attribute(:body_html).try.html_safe
end end
def has_content?
!body.blank?
end
# will calculate structure if not up to date # will calculate structure if not up to date
# calculating structure will also update body_html # calculating structure will also update body_html
def raw_structure def raw_structure
......
...@@ -93,6 +93,12 @@ class GroupPolicy < ApplicationPolicy ...@@ -93,6 +93,12 @@ class GroupPolicy < ApplicationPolicy
may_create_council? or may_create_committee? may_create_council? or may_create_committee?
end end
### Archive permissions
#
def create_archive?
destroy?
end
# #
# A group member can create a council for a group during the group's first week, # A group member can create a council for a group during the group's first week,
# but after that they can only create a request to create a council, which must be approved. # but after that they can only create a request to create a council, which must be approved.
......
...@@ -108,6 +108,9 @@ textarea { ...@@ -108,6 +108,9 @@ textarea {
margin-top: 0 !important; margin-top: 0 !important;
} }
.btn-space {
margin-right: 5px;
}
// //
// the order of submit tags matters. although there is no requirement in html // the order of submit tags matters. although there is no requirement in html
......
%div.title_box
- if type == :singlepage
%a{ href: '#toc', title: :back.t, class: 'back'} #{:back.t}
- else
%a{ href: 'index.html', title: :back.t, class: 'back'} #{:back.t}
%h1{ id: page.name_url }#{page.title}
%small.summary=h page.summary
= page.archive_html(type)
- if page.posts.present?
%div.comments
%h2=h(:comments.t)
- page.posts.each do |post|
%h4= "#{post.user.login} on #{post.updated_at}"
.negativespace
= post.body_html
- if page.assets.present?
%div.attachments
%h2=h(:attachments.t)
%ul
- page.assets.each do |asset|
- image_url = File.join('assets', asset.id.to_s, asset.filename.gsub(' ', '+'))
%li
%a{ href: image_url, title: asset.filename } #{asset.filename}
- if page.data.is_a? Asset
%div.attachments
%h2=h(:file.t)
- image_url = File.join('assets', page.data.id.to_s, page.data.filename.gsub(' ', '+'))
%img{ src: image_url }
%ul
%li
%a{ href: image_url, title: page.data.filename } #{page.data.filename}
%html
%head
%meta{ charset: 'utf8' }
%link{ rel: :stylesheet, type: :"text/css", href: @css_file }
%body
%img{ src: "assets/#{@group.name}.jpg", alt: 'avatar', height: '64', width: '64' }
%h1= "Archive of #{@group.display_name}"
-# Todo: hide if wikis are empty
-if @group.private_wiki&.has_content?
%h3= :private_wiki.t
%p= @group.private_wiki.body_html
- if @group.public_wiki&.has_content?
%h3= :public_wiki.t
%p= @group.public_wiki.body_html
%h2= :pages.t
%table
%tr
%th= :title.t
%th= :updated
- @pages.each do |page|
%tr
%td
%a{ href: "#{page.name_url}.html" } #{page.title}
%td
#{page.updated_by.display_name}
%td
#{page.updated_at}
- if @group.real_committees.any?
%h2= :committees.t
- @group.real_committees.each do |committee|
-# unless group_pages(committee).empty?
%a{ href: "#{committee.name}/index.html" } #{committee.display_name}
- if @group.parent and not @group.council?
%h2= h(:back_to_group.t)
%a{ href: "../index.html"} #{@group.parent.display_name}