Skip to content
Snippets Groups Projects
page.rb 6.16 KiB
Newer Older
Arnaud Levy's avatar
Arnaud Levy committed
# == Schema Information
#
# Table name: communication_website_pages
#
#  id                       :uuid             not null, primary key
pabois's avatar
pabois committed
#  bodyclass                :string
Arnaud Levy's avatar
Arnaud Levy committed
#  full_width               :boolean          default(FALSE)
Arnaud Levy's avatar
Arnaud Levy committed
#  migration_identifier     :string
Arnaud Levy's avatar
Arnaud Levy committed
#  position                 :integer          default(0), not null
Arnaud Levy's avatar
Arnaud Levy committed
#  created_at               :datetime         not null
#  updated_at               :datetime         not null
pabois's avatar
pabois committed
#  communication_website_id :uuid             not null, indexed
#  parent_id                :uuid             indexed
#  university_id            :uuid             not null, indexed
Arnaud Levy's avatar
Arnaud Levy committed
#
# Indexes
#
#  index_communication_website_pages_on_communication_website_id  (communication_website_id)
#  index_communication_website_pages_on_parent_id                 (parent_id)
#  index_communication_website_pages_on_university_id             (university_id)
#
# Foreign Keys
#
pabois's avatar
pabois committed
#  fk_rails_1a42003f06  (parent_id => communication_website_pages.id)
#  fk_rails_280107c62b  (communication_website_id => communication_websites.id)
#  fk_rails_d208d15a73  (university_id => universities.id)
Arnaud Levy's avatar
Arnaud Levy committed
class Communication::Website::Page < ApplicationRecord
  # FIXME: Remove legacy column from db
  # kind was replaced by type in January 2023
  self.ignored_columns = %w(path kind)
Arnaud Levy's avatar
Arnaud Levy committed
  include AsDirectObject
Sébastien Gaya's avatar
Sébastien Gaya committed
  include Duplicable
  include Filterable
  include Localizable
  include Orderable
  include Sanitizable
Arnaud Levy's avatar
Arnaud Levy committed
  include WithAutomaticMenus
pabois's avatar
pabois committed
  include WithMenuItemTarget
Sébastien Gaya's avatar
Sébastien Gaya committed
  include WithOpenApi
  include WithSpecialPage # WithSpecialPage can set default publication status, so must be included before WithPublication
Arnaud Levy's avatar
Arnaud Levy committed
  include WithTree
Arnaud Levy's avatar
Arnaud Levy committed
  include WithUniversity
pabois's avatar
pabois committed

Arnaud Levy's avatar
Arnaud Levy committed
  belongs_to :parent,
             class_name: 'Communication::Website::Page',
             optional: true
Arnaud Levy's avatar
Arnaud Levy committed
  has_many   :children,
             class_name: 'Communication::Website::Page',
pabois's avatar
pabois committed
             foreign_key: :parent_id,
pabois's avatar
pabois committed
             dependent: :destroy
pabois's avatar
pabois committed

  after_save :touch_elements_if_special_page_in_hierarchy

  scope :latest_in, -> (language) { published_now_in(language).order("communication_website_page_localizations.updated_at DESC").limit(5) }

  scope :ordered_by_title, -> (language) {
    localization_title_select = <<-SQL
      COALESCE(
        MAX(CASE WHEN localizations.language_id = '#{language.id}' THEN TRIM(LOWER(UNACCENT(localizations.title))) END),
        MAX(TRIM(LOWER(UNACCENT(localizations.title)))) FILTER (WHERE localizations.rank = 1)
      ) AS localization_title
    SQL

    joins(sanitize_sql_array([<<-SQL
      LEFT JOIN (
        SELECT
          localizations.*,
          ROW_NUMBER() OVER(PARTITION BY localizations.about_id ORDER BY localizations.created_at ASC) as rank
        FROM
          communication_website_page_localizations as localizations
      ) localizations ON communication_website_pages.id = localizations.about_id
    SQL
    ]))
    .select("communication_website_pages.*", localization_title_select)
    .group("communication_website_pages.id")
    .order("localization_title ASC")
Arnaud Levy's avatar
Arnaud Levy committed
  }
  scope :for_search_term, -> (term, language) {
     joins(:localizations)
      .where(communication_website_page_localizations: { language_id: language.id })
      .where("
        unaccent(communication_website_page_localizations.meta_description) ILIKE unaccent(:term) OR
        unaccent(communication_website_page_localizations.summary) ILIKE unaccent(:term) OR
        unaccent(communication_website_page_localizations.title) ILIKE unaccent(:term)
      ", term: "%#{sanitize_sql_like(term)}%")
  }
  scope :for_published, -> (published, language) {
    joins(:localizations)
      .where(communication_website_page_localizations: { language_id: language.id , published: published == 'true'})
  }
  scope :for_full_width, -> (full_width, language = nil) { where(full_width: full_width == 'true') }
Arnaud Levy's avatar
Arnaud Levy committed

Arnaud Levy's avatar
Arnaud Levy committed
  def dependencies
    localizations.in_languages(website.active_language_ids) +
    categories
Arnaud Levy's avatar
Arnaud Levy committed
  end

  def references
Sébastien Gaya's avatar
Sébastien Gaya committed
    [parent] +
    website.menus.in_languages(website.active_language_ids) +
Arnaud Levy's avatar
Arnaud Levy committed
    abouts_with_page_block
  # La page actuelle a les bodyclass classe1 et classe2 ("classe1 classe2")
  # Les différents ancêtres ont les classes home, bodyclass et secondclass
  # -> "page-classe1 page-classe2 ancestor-home ancestor-bodyclass ancestor-secondclass"
pabois's avatar
pabois committed
  def best_bodyclass
    classes = []
    classes += add_prefix_to_classes(bodyclass.split(' '), 'page') unless bodyclass.blank?
    classes += add_prefix_to_classes(ancestor_classes, 'ancestor') unless ancestor_classes.blank?
    classes.join(' ')
Sébastien Gaya's avatar
Sébastien Gaya committed
  def siblings
Arnaud Levy's avatar
Arnaud Levy committed
    self.class.unscoped
              .where(parent: parent, university: university, website: website)
              .where.not(id: id)
Sébastien Gaya's avatar
Sébastien Gaya committed
  # Some special pages can override this method to allow explicit direct connections
  # Example: The Communication::Website::Page::Person special page allows to connect University::Person records directly.
  def self.direct_connection_permitted_about_class
Arnaud Levy's avatar
Arnaud Levy committed
  protected

  # ["class1", "class2"], "page" -> ["page-class1", "page-class2"]
  def add_prefix_to_classes(classes, prefix)
    classes.map { |single_class| "#{prefix}-#{single_class}" }
  end

  # ["class1", "class2", "class3 class4"] -> ["class1", "class2", "class3", "class4"]
  def ancestor_classes
    @ancestor_classes ||= ancestors.pluck(:bodyclass)
                                   .compact_blank
                                   .join(' ')
                                   .split(' ')
                                   .compact_blank
Sébastien Gaya's avatar
Sébastien Gaya committed
  def last_ordered_element
    website.pages.where(parent_id: parent_id).ordered.last
Arnaud Levy's avatar
Arnaud Levy committed

  def abouts_with_page_block
Arnaud Levy's avatar
Arnaud Levy committed
    website.blocks.template_pages.collect(&:about)
Arnaud Levy's avatar
Arnaud Levy committed
  end

  def touch_elements_if_special_page_in_hierarchy
    # We do not call touch as we don't want to trigger the sync on the connected objects
    descendants_and_self.each do |page|
      if page.type == 'Communication::Website::Page::Person'
        website.connected_people.update_all(updated_at: Time.zone.now)
      elsif page.type == 'Communication::Website::Page::Organization'
        website.connected_organizations.update_all(updated_at: Time.zone.now)
Arnaud Levy's avatar
Arnaud Levy committed
end