Unverified Commit 8933b86c authored by azul's avatar azul
Browse files

move the WikiExtensions into Wiki namespace

parent efed01c7
......@@ -22,7 +22,7 @@ class Wiki::LocksController < Wiki::BaseController
def destroy
@wiki.release_my_lock!(@section, current_user)
head :accepted
rescue WikiExtension::Sections::SectionNotFoundError
rescue Wiki::Sections::SectionNotFoundError
head :not_found
end
......
......@@ -106,7 +106,7 @@ module Wikis::BaseHelper
if @wiki && !@wiki.section_open_for?(:document, current_user)
other_user = @wiki.locker_of(:document)
section_they_have_locked = @wiki.section_edited_by(other_user)
msg = WikiExtension::Locking::SectionLockedError.new(section_they_have_locked, other_user).to_s
msg = Wiki::Locking::SectionLockedError.new(section_they_have_locked, other_user).to_s
content_tag(:div, msg, class: "alert alert-info")
end
end
......
......@@ -23,9 +23,9 @@
# 3. wiki should never get saved with body/body products mismatch
# 4. loaded wiki should see only the latest body products, if body was updated from outside
class Wiki < ActiveRecord::Base
include WikiExtension::Locking
include WikiExtension::Sections
include WikiExtension::Versioning
include Wiki::Locking
include Wiki::Sections
include Wiki::Versioning
# a wiki can be used in multiple places: pages or profiles
has_one :page, as: :data
......@@ -141,7 +141,7 @@ class Wiki < ActiveRecord::Base
end
def structure
@structure ||= WikiExtension::WikiStructure.new(raw_structure, body.to_s)
@structure ||= Wiki::Structure.new(raw_structure, body.to_s)
end
def edit_sections?
......
#
# This is a wrapper for the lower level WikiLock class
# it adds lock permissions, breaking and section hierarchy
# see WikiLock for more info
#
# The rules for locks are this:
#
# (1) every wiki has a hierarchical structure of heading sections
# (2) When one section is locked, all the sections above and below it are also treated
# as locked.
# (3) This means two locks can only co-exist if they are in sibling trees
#
#
module Wiki::Locking
#
# EXCEPTIONS
#
class LockedError < CrabgrassException
end
class SectionLockedError < LockedError
def initialize(section, user, options = {})
if section == :document
super([
:wiki_is_locked.t(user: bold(user.name))
], options)
else
super([
:cant_edit_section.t(section: bold(section)),
:user_locked_section.t(section: bold(section), user: bold(user.name))
], options)
end
end
end
class OtherSectionLockedError < LockedError
def initialize(section, options = {})
super(
:other_section_locked_error.t(section: bold(section)).html_safe,
options
)
end
end
class SectionLockedOnSaveError < LockedError
def initialize(section, user, options = {})
if section == :document
super([
:wiki_is_locked.t(user: bold(user.name)),
:can_still_save.t,
:changes_might_be_overwritten.t
], options)
else
super([
:user_locked_section.t(section: bold(section), user: bold(user.name)),
:can_still_save.t,
:changes_might_be_overwritten.t
], options)
end
end
end
#
# LOCK/UNLOCK
#
#
# create a new exclusive lock for user
#
def lock!(section, user)
return unless section_exists?(section)
if section_edited_by?(user) and section_edited_by(user) != section
#
# NOTE: for now, we only allow the user a single lock. This is for UI
# reasons more than anything else.
#
raise OtherSectionLockedError.new(section_edited_by(user))
elsif may_modify_lock?(section, user)
section_locks.lock!(section, user)
else
other_user = locker_of(section)
section_they_have_locked = section_edited_by(other_user)
raise SectionLockedError.new(section_they_have_locked, other_user)
end
end
#
# Forcibly unlock a section.
#
# The actual lock may be on a parent or child section, so we unlock the genealogy
#
def break_lock!(section)
return unless section_exists?(section)
section_locks.unlock!(structure.genealogy_for_section(section))
end
#
# Release the section held by user.
#
def release_my_lock!(section, user)
if may_modify_lock?(section, user)
section_locks.unlock!(section)
end
end
#
# HELPERS
#
#
# get a list of sections that the +user+ may not edit
#
# some sections are not locked, but should appear locked to this user
# for example, a locked section might have a subsection, or a parent section
# no one else should be able to edit either the subsection or the parent
#
def sections_locked_for(user)
locked_sections = section_locks.sections_locked_for(user)
appearant_locked_sections = []
locked_sections.each do |section|
appearant_locked_sections |= structure.genealogy_for_section(section)
end
appearant_locked_sections
end
# get a list of sections that the +user+ may edit
def sections_open_for(user)
all_sections - sections_locked_for(user)
end
def section_open_for?(section, user)
sections_open_for(user).include?(section)
end
def section_locked_for?(section, user)
sections_locked_for(user).include?(section)
end
# returns which user is responsible for locking a section
def locker_of(section)
section_locks.locks.each do |section_name, lock|
# we found the user, if their locked section has in its genealogy
# the section we're looking for
return User.find_by_id(lock[:by]) if structure.genealogy_for_section(section_name).include?(section)
end
nil
end
# a section that +user+ is currently editing or _nil_
def section_edited_by(user)
section_locks.section_locked_by(user)
end
alias section_edited_by? section_edited_by
protected
def may_modify_lock?(section, user)
user.present? && user.real? && !sections_locked_for(user).include?(section)
end
def section_exists?(section)
all_sections.include?(section)
end
end
module Wiki::Sections
class SectionNotFoundError < CrabgrassException
def initialize(section = 'document', options = {})
message = :cant_find_wiki_section.t(section: section)
super(message)
end
end
def all_sections
structure.all_sections
end
def save_section!(section, text)
section = structure.find_section(section) unless section.is_a? GreenTree
updated_body = section.sub_markup(text)
return if self.body == updated_body
self.body = updated_body
self.save!
end
def get_body_for_section(section)
structure.get_body(section)
end
def level_for_section(section)
structure.get_level(section)
end
def successor_for_section(section)
structure.get_successor(section)
end
def get_body_html_for_section(section)
GreenCloth.new(get_body_for_section(section), link_context, [:outline]).to_html
end
end
class Wiki::Structure
# this is used for aggregation, not for inclusion in Wiki model
attr_reader :green_tree
def initialize(raw_structure, body)
@green_tree = GreenTree.from_hash(raw_structure, body)
end
# all parent and child elements for section
def genealogy_for_section(section)
names = find(section).genealogy.collect &:name
names.compact.unshift :document
end
def all_sections
[:document] + green_tree.section_names
end
def sections
green_tree.section_names
end
def update_body(section, section_body)
find(section).sub_markup(section_body)
end
def get_body(section)
find(section).markup
end
def get_level(section)
find(section).heading_level
end
def get_successor(section)
find(section).successor
end
def find_section(name)
find(name)
end
protected
def find(section)
node = green_tree if section == :document
node ||= green_tree.find(section)
return node || (raise Wiki::Sections::SectionNotFoundError.new(section))
end
end
# (Wiki::Version is declared in app/models/wiki.rb)
#
# create_table "wiki_versions", :force => true do |t|
# t.integer "wiki_id", :limit => 11
# t.integer "version", :limit => 11
# t.text "body"
# t.text "body_html"
# t.text "raw_structure"
# t.datetime "updated_at"
# t.integer "user_id", :limit => 11
# end
#
# add_index "wiki_versions", ["wiki_id"], :name => "index_wiki_versions"
# add_index "wiki_versions", ["wiki_id", "updated_at"], :name => "index_wiki_versions_with_updated_at"
module Wiki::Versioning
class VersionNotFoundError < CrabgrassException
def initialize(version_or_message = '', options = {})
message = version_or_message.is_a?(Integer) ?
:version_doesnt_exist.t(version: version_or_message.to_s) :
version_or_message.to_s
super(message, options)
end
end
class VersionExistsError < CrabgrassException
def initialize(version_or_message = '', options = {})
message = version_or_message.respond_to?(:user) ?
:version_exists_error.t(user: version_or_message.user.display_name) :
version_or_message.to_s
super(message, options)
end
end
def create_new_version?
# always create a new version if we have no versions at all
return true if versions.empty?
# overwrite blank versions (we don't want to store blank versions)
return false if current.body.blank?
body_changed? && ( user_id_changed? || long_time_no_updates? )
end
def long_time_no_updates?
updated_at && (updated_at < 30.minutes.ago)
end
def current
versions.order(:version).last
end
def former
last_version_before(last_seen_at) if last_seen_at.present?
end
# returns first version since +time+
def last_version_before(time)
return nil unless time
versions.first conditions: ["updated_at <= :time", {time: time}],
order: "updated_at DESC"
end
def find_version(number)
self.versions.find_by_version(number) or
raise VersionNotFoundError.new(number.to_i)
end
# reverts and keeps all the old versions
def revert_to_version(version, user)
self.body = version.body
self.user = user
save!
end
# reverts and deletes all versions after the reverted version.
def revert_to_version!(version_number, user=nil)
revert_to(version_number)
destroy_versions_after(version_number)
end
# update the latest Wiki::Version object with the newest attributes
# when wiki changes, but a new version is not being created
def update_latest_version_record
# only need to update the latest version when not creating a new one
return if create_new_version?
versions.last.update_attributes(
body: body,
# read_attributes for body_html and raw_structure
# because we don't want to trigger another rendering
# by calling our own body_html method
body_html: read_attribute(:body_html),
raw_structure: read_attribute(:raw_structure),
user: user,
updated_at: Time.now)
end
def page_for_version(version)
return 1 unless version
page_index = versions_since(version) / Wiki.per_page #Version.per_page
page_index + 1
end
protected
def versions_since(version)
self.versions.where("version > #{version.version}").count
end
def destroy_versions_after(version_number)
versions.where("version > ?", version_number).each do |version|
version.destroy
end
end
end
#
# This is a wrapper for the lower level WikiLock class
# it adds lock permissions, breaking and section hierarchy
# see WikiLock for more info
#
# The rules for locks are this:
#
# (1) every wiki has a hierarchical structure of heading sections
# (2) When one section is locked, all the sections above and below it are also treated
# as locked.
# (3) This means two locks can only co-exist if they are in sibling trees
#
#
module WikiExtension
module Locking
#
# EXCEPTIONS
#
class LockedError < CrabgrassException
end
class SectionLockedError < LockedError
def initialize(section, user, options = {})
if section == :document
super([
:wiki_is_locked.t(user: bold(user.name))
], options)
else
super([
:cant_edit_section.t(section: bold(section)),
:user_locked_section.t(section: bold(section), user: bold(user.name))
], options)
end
end
end
class OtherSectionLockedError < LockedError
def initialize(section, options = {})
super(
:other_section_locked_error.t(section: bold(section)).html_safe,
options
)
end
end
class SectionLockedOnSaveError < LockedError
def initialize(section, user, options = {})
if section == :document
super([
:wiki_is_locked.t(user: bold(user.name)),
:can_still_save.t,
:changes_might_be_overwritten.t
], options)
else
super([
:user_locked_section.t(section: bold(section), user: bold(user.name)),
:can_still_save.t,
:changes_might_be_overwritten.t
], options)
end
end
end
#
# LOCK/UNLOCK
#
#
# create a new exclusive lock for user
#
def lock!(section, user)
return unless section_exists?(section)
if section_edited_by?(user) and section_edited_by(user) != section
#
# NOTE: for now, we only allow the user a single lock. This is for UI
# reasons more than anything else.
#
raise OtherSectionLockedError.new(section_edited_by(user))
elsif may_modify_lock?(section, user)
section_locks.lock!(section, user)
else
other_user = locker_of(section)
section_they_have_locked = section_edited_by(other_user)
raise SectionLockedError.new(section_they_have_locked, other_user)
end
end
#
# Forcibly unlock a section.
#
# The actual lock may be on a parent or child section, so we unlock the genealogy
#
def break_lock!(section)
return unless section_exists?(section)
section_locks.unlock!(structure.genealogy_for_section(section))
end
#
# Release the section held by user.
#
def release_my_lock!(section, user)
if may_modify_lock?(section, user)
section_locks.unlock!(section)
end
end
#
# HELPERS
#
#
# get a list of sections that the +user+ may not edit
#
# some sections are not locked, but should appear locked to this user
# for example, a locked section might have a subsection, or a parent section
# no one else should be able to edit either the subsection or the parent
#
def sections_locked_for(user)
locked_sections = section_locks.sections_locked_for(user)
appearant_locked_sections = []
locked_sections.each do |section|
appearant_locked_sections |= structure.genealogy_for_section(section)
end
appearant_locked_sections
end
# get a list of sections that the +user+ may edit
def sections_open_for(user)
all_sections - sections_locked_for(user)
end
def section_open_for?(section, user)
sections_open_for(user).include?(section)
end
def section_locked_for?(section, user)
sections_locked_for(user).include?(section)
end
# returns which user is responsible for locking a section
def locker_of(section)
section_locks.locks.each do |section_name, lock|
# we found the user, if their locked section has in its genealogy
# the section we're looking for
return User.find_by_id(lock[:by]) if structure.genealogy_for_section(section_name).include?(section)
end
nil
end
# a section that +user+ is currently editing or _nil_
def section_edited_by(user)
section_locks.section_locked_by(user)
end
alias section_edited_by? section_edited_by
protected
def may_modify_lock?(section, user)
user.present? && user.real? && !sections_locked_for(user).include?(section)
end
def section_exists?(section)
all_sections.include?(section)
end
end
end
module WikiExtension
module Sections
class SectionNotFoundError < CrabgrassException
def initialize(section = 'document', options = {})
message = :cant_find_wiki_section.t(section: section)
super(message)
end
end
def all_sections
structure.all_sections
end
def save_section!(section, text)
section = structure.find_section(section) unless section.is_a? GreenTree
updated_body = section.sub_markup(text)
return if self.body == updated_body
self.body = updated_body
self.save!
end
def get_body_for_section(section)
structure.get_body(section)
end
def level_for_section(section)
structure.get_level(section)
end
def successor_for_section(section)
structure.get_successor(section)
end