Class: Alchemy::Page

Inherits:
BaseRecord
  • Object
show all
Includes:
Hints, Logger, PageElements, PageLayouts, PageNaming, PageNatures, PageScopes, Taggable
Defined in:
app/models/alchemy/page/page_scopes.rb,
app/models/alchemy/page.rb,
app/models/alchemy/page/url_path.rb,
app/models/alchemy/page/publisher.rb,
app/models/alchemy/page/page_naming.rb,
app/models/alchemy/page/page_layouts.rb,
app/models/alchemy/page/page_natures.rb,
app/models/alchemy/page/page_elements.rb,
app/models/alchemy/page/fixed_attributes.rb

Overview

ActiveRecord scopes for Alchemy::Page

Defined Under Namespace

Modules: PageElements, PageLayouts, PageNaming, PageNatures, PageScopes Classes: FixedAttributes, Publisher, UrlPath

Constant Summary collapse

DEFAULT_ATTRIBUTES_FOR_COPY =
{
  autogenerate_elements: false,
  public_on: nil,
  public_until: nil,
  locked_at: nil,
  locked_by: nil
}
SKIPPED_ATTRIBUTES_ON_COPY =
%w[
  id
  updated_at
  created_at
  creator_id
  updater_id
  lft
  rgt
  depth
  urlname
  cached_tag_list
]
PERMITTED_ATTRIBUTES =
[
  :meta_description,
  :meta_keywords,
  :name,
  :page_layout,
  :public_on,
  :public_until,
  :restricted,
  :robot_index,
  :robot_follow,
  :searchable,
  :sitemap,
  :tag_list,
  :title,
  :urlname,
  :layoutpage,
  :menu_id
]

Constants included from PageNaming

PageNaming::RESERVED_URLNAMES

Constants included from SearchableResource

SearchableResource::SEARCHABLE_COLUMN_TYPES

Class Method Summary collapse

Instance Method Summary collapse

Methods included from PageElements

#available_element_definitions, #available_element_names, #available_elements_within_current_scope, #descendent_element_definitions, #element_definition_names, #element_definitions, #element_definitions_by_name, #richtext_ingredients_ids

Methods included from PageNaming

#renamed?, #slug, #update_urlname!

Methods included from NameConversions

#convert_to_humanized_name, #convert_to_urlname

Methods included from PageNatures

#cache_page?, #cache_version, #definition, #editor_roles, #expiration_time, #folded?, #has_limited_editors?, #last_modified_at, #layout_display_name, #layout_partial_name, #locked?, #public?, #rootpage?, #status, #status_message, #status_title

Methods included from Taggable

included, #tag_list=

Methods included from Logger

#log_warning, warn

Methods included from Hints

#has_hint?, #hint

Methods included from SearchableResource

#ransackable_associations, #ransackable_attributes, #ransortable_attributes

Class Method Details

.alchemy_resource_filtersObject



186
187
188
189
190
191
192
193
194
195
196
197
# File 'app/models/alchemy/page.rb', line 186

def alchemy_resource_filters
  [
    {
      name: :by_page_layout,
      values: PageLayout.all.map { |p| [Alchemy.t(p["name"], scope: "page_layout_names"), p["name"]] }
    },
    {
      name: :status,
      values: %w[published not_public restricted]
    }
  ]
end

.all_from_clipboard(clipboard) ⇒ Object



243
244
245
246
247
# File 'app/models/alchemy/page.rb', line 243

def all_from_clipboard(clipboard)
  return [] if clipboard.blank?

  where(id: clipboard.collect { |p| p["id"] })
end

.all_from_clipboard_for_select(clipboard, language_id, layoutpages: false) ⇒ Object



249
250
251
252
253
254
255
256
# File 'app/models/alchemy/page.rb', line 249

def all_from_clipboard_for_select(clipboard, language_id, layoutpages: false)
  return [] if clipboard.blank?

  clipboard_pages = all_from_clipboard(clipboard)
  allowed_page_layouts = Alchemy::Page.selectable_layouts(language_id, layoutpages: layoutpages)
  allowed_page_layout_names = allowed_page_layouts.collect { |p| p["name"] }
  clipboard_pages.select { |cp| allowed_page_layout_names.include?(cp.page_layout) }
end

.copy(source, differences = {}) ⇒ Alchemy::Page

Creates a copy of given source.

Also copies all elements included in source.

Note:

It prevents the element auto generator from running.

Parameters:

  • source (Alchemy::Page)

    The source page the copy is taken from

  • differences (Hash) (defaults to: {})

    A optional hash with attributes that take precedence over the source attributes

Returns:



225
226
227
# File 'app/models/alchemy/page.rb', line 225

def copy(source, differences = {})
  Alchemy::CopyPage.new(page: source).call(changed_attributes: differences)
end

.copy_and_paste(source, new_parent, new_name) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/alchemy/page.rb', line 229

def copy_and_paste(source, new_parent, new_name)
  page = Alchemy::CopyPage.new(page: source)
    .call(changed_attributes: {
      parent: new_parent,
      language: new_parent&.language,
      name: new_name,
      title: new_name
    })
  if source.children.any?
    source.copy_children_to(page)
  end
  page
end

.language_root_for(language_id) ⇒ Object

Returns the language root page for given language id.

Parameters:

  • language_id (Fixnum)

Returns:

  • the language root page for given language id.



206
207
208
# File 'app/models/alchemy/page.rb', line 206

def language_root_for(language_id)
  language_roots.find_by_language_id(language_id)
end


258
259
260
261
262
263
264
265
266
267
268
# File 'app/models/alchemy/page.rb', line 258

def link_target_options
  options = [[Alchemy.t(:default, scope: "link_target_options"), ""]]
  link_target_options = Config.get(:link_target_options)
  link_target_options.each do |option|
    # add an underscore to the options to provide the default syntax
    # @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target
    options << [Alchemy.t(option, scope: "link_target_options",
      default: option.to_s.humanize), "_#{option}"]
  end
  options
end

.searchable_alchemy_resource_attributesObject



199
200
201
# File 'app/models/alchemy/page.rb', line 199

def searchable_alchemy_resource_attributes
  %w[name urlname title]
end

.url_path_classObject

The url_path class

See Also:



173
174
175
# File 'app/models/alchemy/page.rb', line 173

def url_path_class
  @_url_path_class ||= Alchemy::Page::UrlPath
end

.url_path_class=(klass) ⇒ Object

Set a custom url path class

# config/initializers/alchemy.rb
Alchemy::Page.url_path_class = MyPageUrlPathClass


182
183
184
# File 'app/models/alchemy/page.rb', line 182

def url_path_class=(klass)
  @_url_path_class = klass
end

Instance Method Details

#attribute_fixed?(name) ⇒ Boolean

True if given attribute name is defined as fixed

Returns:

  • (Boolean)


443
444
445
# File 'app/models/alchemy/page.rb', line 443

def attribute_fixed?(name)
  fixed_attributes.fixed?(name)
end

#copy_children_to(new_parent) ⇒ Object



398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'app/models/alchemy/page.rb', line 398

def copy_children_to(new_parent)
  children.each do |child|
    next if child == new_parent

    new_child = Alchemy::CopyPage.new(page: child).call(changed_attributes: {
      parent_id: new_parent.id,
      language_id: new_parent.language_id,
      language_code: new_parent.language_code
    })
    new_child.move_to_child_of(new_parent)
    child.copy_children_to(new_child) unless child.children.blank?
  end
end

#creator_nameObject

Returns the name of the creator of this page.

If no creator could be found or associated user model does not respond to #name it returns ‘unknown’



479
480
481
# File 'app/models/alchemy/page.rb', line 479

def creator_name
  creator.try(:alchemy_display_name) || Alchemy.t("unknown")
end

#editable_by?(user) ⇒ Boolean

Checks the current page’s list of editors, if defined.

This allows us to pass in a user and see if any of their roles are enable them to make edits

Returns:

  • (Boolean)


452
453
454
455
456
# File 'app/models/alchemy/page.rb', line 452

def editable_by?(user)
  return true unless has_limited_editors?

  (editor_roles & user.alchemy_roles).any?
end

#find_elements(options = {}) ⇒ ActiveRecord::Relation

Returns elements from pages public version.

You can pass another page_version to load elements from in the options.

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :only (Array<String>|String)

    Returns only elements with given names

  • :except (Array<String>|String)

    Returns all elements except the ones with given names

  • :count (Integer)

    Limit the count of returned elements

  • :offset (Integer)

    Starts with an offset while returning elements

  • :include_hidden (Boolean) — default: false

    Return hidden elements as well

  • :random (Boolean) — default: false

    Return elements randomly shuffled

  • :reverse (Boolean) — default: false

    Reverse the load order

  • :finder (Class) — default: Alchemy::ElementsFinder

    A class that will return elements from page. Use this for your custom element loading logic.

  • :page_version (Alchemy::PageVersion)

    A page version to load elements from. Uses the pages public_version by default.

Returns:

  • (ActiveRecord::Relation)


300
301
302
303
# File 'app/models/alchemy/page.rb', line 300

def find_elements(options = {})
  finder = options[:finder] || Alchemy::ElementsFinder.new(options)
  finder.elements(page_version: options[:page_version] || public_version)
end

#first_public_childObject

Returns the first published child



389
390
391
# File 'app/models/alchemy/page.rb', line 389

def first_public_child
  children.published.first
end

#fixed_attributesObject

Holds an instance of FixedAttributes



438
439
440
# File 'app/models/alchemy/page.rb', line 438

def fixed_attributes
  @_fixed_attributes ||= FixedAttributes.new(self)
end

#fold!(user_id, status) ⇒ Object



372
373
374
375
376
# File 'app/models/alchemy/page.rb', line 372

def fold!(user_id, status)
  folded_page = folded_pages.find_or_create_by(user_id: user_id)
  folded_page.folded = status
  folded_page.save!
end

#get_language_rootObject

Gets the language_root page for page



394
395
396
# File 'app/models/alchemy/page.rb', line 394

def get_language_root
  self_and_ancestors.find_by(language_root: true)
end

#hint_translation_attributeObject

Key hint translations by page layout, rather than the default name.



503
504
505
# File 'app/models/alchemy/page.rb', line 503

def hint_translation_attribute
  page_layout
end

#inherit_restricted_statusObject



384
385
386
# File 'app/models/alchemy/page.rb', line 384

def inherit_restricted_status
  self.restricted = parent.restricted?
end

#lock_to!(user) ⇒ Object

Locks the page to given user



360
361
362
# File 'app/models/alchemy/page.rb', line 360

def lock_to!(user)
  update_columns(locked_at: Time.current, locked_by: user.id)
end

#locker_nameObject

Returns the name of the user currently editing this page.

If no locker could be found or associated user model does not respond to #name it returns ‘unknown’



497
498
499
# File 'app/models/alchemy/page.rb', line 497

def locker_name
  locker.try(:alchemy_display_name) || Alchemy.t("unknown")
end

Menus (aka. root nodes) this page is attached to



509
510
511
# File 'app/models/alchemy/page.rb', line 509

def menus
  @_menus ||= nodes.map(&:root)
end

#next(options = {}) ⇒ Object Also known as: next_page

Returns the next page on the same level or nil.

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :restricted (Boolean) — default: false

    only restricted pages (true), skip restricted pages (false)

  • :public (Boolean) — default: true

    only public pages (true), skip public pages (false)



351
352
353
354
# File 'app/models/alchemy/page.rb', line 351

def next(options = {})
  pages = self_and_siblings.where("lft > ?", lft)
  select_page(pages, options.merge(order: :asc))
end

#previous(options = {}) ⇒ Object Also known as: previous_page

Returns the previous page on the same level or nil.

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :restricted (Boolean) — default: false

    only restricted pages (true), skip restricted pages (false)

  • :public (Boolean) — default: true

    only public pages (true), skip public pages (false)



337
338
339
340
# File 'app/models/alchemy/page.rb', line 337

def previous(options = {})
  pages = self_and_siblings.where("lft < ?", lft)
  select_page(pages, options.merge(order: :desc))
end

#public_onObject

Returns the value of public_on attribute from public version

If it’s a fixed attribute then the fixed value is returned instead



462
463
464
# File 'app/models/alchemy/page.rb', line 462

def public_on
  attribute_fixed?(:public_on) ? fixed_attributes[:public_on] : public_version&.public_on
end

#public_on=(time) ⇒ Object

Sets the public_on date on the published version

Builds a new version if none exists yet. Destroys public version if empty time is set



423
424
425
426
427
428
429
430
431
432
433
# File 'app/models/alchemy/page.rb', line 423

def public_on=(time)
  if public_version && time.blank?
    public_version.destroy!
    # Need to reset the public version on the instance so we do not need to reload
    self.public_version = nil
  elsif public_version
    public_version.public_on = time
  elsif time.present?
    versions.build(public_on: time)
  end
end

#public_untilObject

Returns the value of public_until attribute

If it’s a fixed attribute then the fixed value is returned instead



470
471
472
# File 'app/models/alchemy/page.rb', line 470

def public_until
  attribute_fixed?(:public_until) ? fixed_attributes[:public_until] : public_version&.public_until
end

#publish!(current_time = Time.current) ⇒ Object

Creates a public version of the page in the background.



414
415
416
# File 'app/models/alchemy/page.rb', line 414

def publish!(current_time = Time.current)
  PublishPageJob.perform_later(id, public_on: current_time)
end

#set_restrictions_to_child_pagesObject



378
379
380
381
382
# File 'app/models/alchemy/page.rb', line 378

def set_restrictions_to_child_pages
  descendants.each do |child|
    child.update(restricted: restricted?)
  end
end

#to_partial_pathObject

The page’s view partial is dependent from its page layout

Define page layouts

Page layouts are defined in the config/alchemy/page_layouts.yml file

- name: contact
  elements: [contactform]
  ...

Override the view

Page layout partials live in app/views/alchemy/page_layouts



326
327
328
# File 'app/models/alchemy/page.rb', line 326

def to_partial_path
  "alchemy/page_layouts/#{layout_partial_name}"
end

#unlock!Object

Unlocks the page without updating the timestamps



366
367
368
369
370
# File 'app/models/alchemy/page.rb', line 366

def unlock!
  if update_columns(locked_at: nil, locked_by: nil)
    Current.preview_page = nil
  end
end

#updater_nameObject

Returns the name of the last updater of this page.

If no updater could be found or associated user model does not respond to #name it returns ‘unknown’



488
489
490
# File 'app/models/alchemy/page.rb', line 488

def updater_name
  updater.try(:alchemy_display_name) || Alchemy.t("unknown")
end

#url_pathObject

The url_path for this page



308
309
310
# File 'app/models/alchemy/page.rb', line 308

def url_path
  self.class.url_path_class.new(self).call
end