diff --git a/.github/workflows/gitlab.yml b/.github/workflows/gitlab.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aac739274eb8946eee49f8568f7b290a0564a5db
--- /dev/null
+++ b/.github/workflows/gitlab.yml
@@ -0,0 +1,19 @@
+name: GitlabSync
+
+on:
+  - push
+  - delete
+
+jobs:
+  sync:
+    runs-on: ubuntu-latest
+    name: Git Repo Sync
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        fetch-depth: 0
+    - uses: wangchucheng/git-repo-sync@v0.1.0
+      with:
+        target-url: ${{ secrets.GITLAB_TARGET_URL }}
+        target-username: ${{ secrets.GITLAB_TARGET_USERNAME }}
+        target-token: ${{ secrets.GITLAB_TARGET_TOKEN }}
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 8358008487f8378a7c17b05628d12b9ffb69a25f..a1eae116bce4500a9144a9e65503c69cb76cdff1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -27,7 +27,6 @@ gem "enum_help"
 gem "faceted_search"
 gem "font-awesome-sass"
 gem "front_matter_parser"
-gem "gdpr", "~> 1.2.5"
 gem "geocoder", "~> 1.8"
 gem "geo_point"
 gem "gitlab"
diff --git a/Gemfile.lock b/Gemfile.lock
index 093d7a859f6986a54e4aff65d117d93370bc2a47..a93bf9338577c783ee930fcacd32ed6240440ff5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -93,7 +93,7 @@ GEM
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
       tzinfo (~> 2.0)
-    addressable (2.8.2)
+    addressable (2.8.3)
       public_suffix (>= 2.0.2, < 6.0)
     angularjs-rails (1.8.0)
     annotate (3.2.0)
@@ -102,7 +102,7 @@ GEM
     autoprefixer-rails (10.4.13.0)
       execjs (~> 2)
     aws-eventstream (1.2.0)
-    aws-partitions (1.740.0)
+    aws-partitions (1.742.0)
     aws-sdk-core (3.171.0)
       aws-eventstream (~> 1, >= 1.0.2)
       aws-partitions (~> 1, >= 1.651.0)
@@ -111,7 +111,7 @@ GEM
     aws-sdk-kms (1.63.0)
       aws-sdk-core (~> 3, >= 3.165.0)
       aws-sigv4 (~> 1.1)
-    aws-sdk-s3 (1.120.0)
+    aws-sdk-s3 (1.120.1)
       aws-sdk-core (~> 3, >= 3.165.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.4)
@@ -224,10 +224,6 @@ GEM
     font-awesome-sass (6.4.0)
       sassc (~> 2.0)
     front_matter_parser (1.0.1)
-    gdpr (1.2.5)
-      js_cookie_rails
-      rails
-      sassc-rails
     geo_calc (0.7.8)
       activesupport (>= 3.0.1)
       geo_units (~> 0.3.2)
@@ -279,8 +275,6 @@ GEM
       thor (>= 0.14, < 2.0)
     jquery-ui-rails (6.0.1)
       railties (>= 3.2.16)
-    js_cookie_rails (2.2.0)
-      railties (>= 3.1)
     json (2.6.3)
     jwt (2.7.0)
     kamifusen (1.11.2)
@@ -588,7 +582,6 @@ DEPENDENCIES
   figaro
   font-awesome-sass
   front_matter_parser
-  gdpr (~> 1.2.5)
   geo_point
   geocoder (~> 1.8)
   gitlab
diff --git a/app/assets/images/communication/blocks/templates/features.jpg b/app/assets/images/communication/blocks/templates/features.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6c32f7e0dac707c1b58cf3c5f02ed8339cdd4096
Binary files /dev/null and b/app/assets/images/communication/blocks/templates/features.jpg differ
diff --git a/app/assets/images/communication/blocks/templates/posts/alternate.png b/app/assets/images/communication/blocks/templates/posts/alternate.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e3517906860abe0f2bf69cde13750917ac839c9
Binary files /dev/null and b/app/assets/images/communication/blocks/templates/posts/alternate.png differ
diff --git a/app/assets/javascripts/admin/appstack.js b/app/assets/javascripts/admin/appstack.js
index eea9af01b78b0bd2d79035316b5dc8b2c4bd094f..cd460cea74caa81e28e1a514ca9cbb7473cccdb9 100644
--- a/app/assets/javascripts/admin/appstack.js
+++ b/app/assets/javascripts/admin/appstack.js
@@ -8,7 +8,6 @@
 //= require cropperjs/dist/cropper
 //= require jquery-cropper/dist/jquery-cropper
 //= require appstack/app
-//= require gdpr/cookie_consent
 //= require sortablejs/Sortable
 //= require summernote/summernote-bs5
 //= require slug/slug
diff --git a/app/assets/javascripts/admin/pure.js b/app/assets/javascripts/admin/pure.js
index 00c97d27fd4b2f11e65cae02ff8802e0d6ef27fd..4cc380290368a53cb9caebe40eb3a841c4c31d36 100644
--- a/app/assets/javascripts/admin/pure.js
+++ b/app/assets/javascripts/admin/pure.js
@@ -9,7 +9,6 @@
 //= require jquery-cropper/dist/jquery-cropper
 // TODO remove appstack js
 //= require appstack/app
-//= require gdpr/cookie_consent
 //= require sortablejs/Sortable
 //= require summernote/summernote-bs5
 //= require slug/slug
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 19545a0bccd3fa9908e5b35280ed224190b74b3e..e8a0f2e802bc126febf0934d16a09fd55818ac04 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -8,7 +8,6 @@
 //= require simple_form_password_with_hints
 //= require simple_form_bs5_file_input
 //= require summernote/summernote-bs5
-//= require gdpr/cookie_consent
 //= require autocomplete-rails
 //= require_tree ./application/plugins
 //= require_self
diff --git a/app/assets/javascripts/devise.js b/app/assets/javascripts/devise.js
index 26efc2d665f4a7e902f56e9075c6baec61b83df2..87f4ad18ee3ead168bb2e72b76cfb736557c7246 100644
--- a/app/assets/javascripts/devise.js
+++ b/app/assets/javascripts/devise.js
@@ -8,7 +8,6 @@
 //= require simple_form_bs5_file_input
 //= require cropperjs/dist/cropper
 //= require jquery-cropper/dist/jquery-cropper
-//= require gdpr/cookie_consent
 //= require_self
 //= require_tree ./admin/plugins
 
diff --git a/app/assets/javascripts/extranet.js b/app/assets/javascripts/extranet.js
index acfd1811ba7440b3a839c8868adee361bf550d1c..f82a3bacb14d7873d0bef0c38bd5f1f2832cafc4 100644
--- a/app/assets/javascripts/extranet.js
+++ b/app/assets/javascripts/extranet.js
@@ -10,7 +10,6 @@
 //= require simple_form_password_with_hints
 //= require simple_form_bs5_file_input
 //= require summernote/summernote-bs5
-//= require gdpr/cookie_consent
 //= require autocomplete-rails
 //= require_tree ./application/plugins
 //= require_tree ./extranet
diff --git a/app/assets/stylesheets/admin/appstack.sass b/app/assets/stylesheets/admin/appstack.sass
index 3413526ba440914d25ccfa8aea3f2e44d45ddf3e..d2a849435d0051d3cd1b00934fd699d71e0f826d 100644
--- a/app/assets/stylesheets/admin/appstack.sass
+++ b/app/assets/stylesheets/admin/appstack.sass
@@ -6,7 +6,6 @@
 @import 'simple_form_bs5_file_input'
 @import 'summernote-bs5'
 @import 'cropperjs/dist/cropper'
-@import 'gdpr/cookie_consent'
 @import 'codemirror/lib/codemirror'
 @import '../commons/*'
 @import 'commons/*'
diff --git a/app/assets/stylesheets/admin/pure.sass b/app/assets/stylesheets/admin/pure.sass
index 0b29efb7e3f0b6037ceba282053169f3b6cdaf67..b3638de514982db5fb02d87543400830c493721b 100644
--- a/app/assets/stylesheets/admin/pure.sass
+++ b/app/assets/stylesheets/admin/pure.sass
@@ -10,7 +10,6 @@
 @import 'simple_form_bs5_file_input'
 @import 'summernote-bs5'
 @import 'cropperjs/dist/cropper'
-@import 'gdpr/cookie_consent'
 @import 'codemirror/lib/codemirror'
 @import '../commons/*'
 @import 'commons/*'
diff --git a/app/assets/stylesheets/admin/pure/grid.sass b/app/assets/stylesheets/admin/pure/grid.sass
index ff028e0fa6d911a7dcd4b07f1e27b0b81f0e0ad6..ffdb47b85b9d4a40d07889a70e80e9d6f0ad4053 100644
--- a/app/assets/stylesheets/admin/pure/grid.sass
+++ b/app/assets/stylesheets/admin/pure/grid.sass
@@ -1,4 +1,4 @@
 *
     --bs-gutter-x: 64px !important
     @media (max-width: 768px)
-        --bs-gutter-x: 20px !important
+        --bs-gutter-x: 32px !important
diff --git a/app/assets/stylesheets/application.sass b/app/assets/stylesheets/application.sass
index cbdad2dd5161fd3e130203aabb7e325e41f39451..407658cd3780ab5ca9bb1ff60e3db4f77441fd70 100644
--- a/app/assets/stylesheets/application.sass
+++ b/app/assets/stylesheets/application.sass
@@ -3,7 +3,6 @@
 @import 'simple_form_password_with_hints'
 @import 'simple_form_bs5_file_input'
 @import 'cropperjs/dist/cropper'
-@import 'gdpr/cookie_consent'
 @import 'faceted_search'
 @import 'commons/*'
 @import 'application/*'
diff --git a/app/assets/stylesheets/extranet.sass b/app/assets/stylesheets/extranet.sass
index 7beab43bb867d71afd0fe6afdbf4e304fc0976d3..de4182540a5c480f4ce792507521b2e02d6ebb41 100644
--- a/app/assets/stylesheets/extranet.sass
+++ b/app/assets/stylesheets/extranet.sass
@@ -6,7 +6,6 @@
 @import 'simple_form_password_with_hints'
 @import 'simple_form_bs5_file_input'
 @import 'summernote-bs5'
-@import 'gdpr/cookie_consent'
 @import 'cropperjs/dist/cropper'
 @import 'commons/summernote'
 @import 'commons/bootstrap-icons'
diff --git a/app/assets/stylesheets/extranet/pages/_posts.sass b/app/assets/stylesheets/extranet/pages/_posts.sass
index 1d785f2606e0a2728bddb1c9a6375ea115529736..6eea8a17340e00de999c521dbfbdfe9291891dab 100644
--- a/app/assets/stylesheets/extranet/pages/_posts.sass
+++ b/app/assets/stylesheets/extranet/pages/_posts.sass
@@ -1,10 +1,6 @@
 .posts-show header
     figure
         position: relative
-        @include media-breakpoint-up(md)
-            display: inline-block
-            padding-left: 2.813rem
-            width: 25%
         picture img
             width: 100%
         &.with-credit::after
diff --git a/app/models/communication/block.rb b/app/models/communication/block.rb
index 7bfc5a3bf480fb675e78a095548ef6710f333900..eefa859355215106632fcb614c7c71c49ff2452a 100644
--- a/app/models/communication/block.rb
+++ b/app/models/communication/block.rb
@@ -12,23 +12,20 @@
 #  created_at    :datetime         not null
 #  updated_at    :datetime         not null
 #  about_id      :uuid             indexed => [about_type]
-#  heading_id    :uuid             indexed
 #  university_id :uuid             not null, indexed
 #
 # Indexes
 #
-#  index_communication_blocks_on_heading_id     (heading_id)
 #  index_communication_blocks_on_university_id  (university_id)
 #  index_communication_website_blocks_on_about  (about_type,about_id)
 #
 # Foreign Keys
 #
 #  fk_rails_18291ef65f  (university_id => universities.id)
-#  fk_rails_90ac986fab  (heading_id => communication_block_headings.id)
 #
 class Communication::Block < ApplicationRecord
   include Accessible
-  include WithConnections
+  include AsIndirectObject
   include WithPosition
   include WithUniversity
   include Sanitizable
@@ -42,12 +39,19 @@ class Communication::Block < ApplicationRecord
   # template_blobs would be a better name, because there are files
   has_many_attached :template_images
 
+  # Les numƩros sont un peu en vrac
+  # Dans l'idƩe, pour le futur
+  # 1000 basic
+  # 2000 storytelling
+  # 3000 references
+  # 4000 utilities
   enum template_kind: {
     chapter: 50,
     image: 51,
     gallery: 300,
     video: 52,
     key_figures: 56,
+    features: 2010,
     datatable: 54,
     files: 55,
     embed: 53,
@@ -65,14 +69,13 @@ class Communication::Block < ApplicationRecord
 
   CATEGORIES = {
     basic: [:chapter, :image, :video, :datatable],
-    storytelling: [:key_figures, :gallery, :call_to_action, :testimonials, :timeline],
+    storytelling: [:key_figures, :features, :gallery, :call_to_action, :testimonials, :timeline],
     references: [:pages, :posts, :organization_chart, :partners, :programs],
     utilities: [:files, :definitions, :embed, :contact]
   }
 
   scope :published, -> { where(published: true) }
 
-  after_save :sync_if_about_is_direct
   before_save :attach_template_blobs
   before_validation :set_university_from_about, on: :create
 
@@ -146,10 +149,6 @@ class Communication::Block < ApplicationRecord
     "Communication::Block::Template::#{template_kind.classify}".constantize
   end
 
-  def sync_if_about_is_direct
-    about.save_and_sync if about.respond_to? :save_and_sync
-  end
-
   # FIXME @sebou
   # Could not find or build blob: expected attachable, got #<ActiveStorage::Blob id: "f4c78657-5062-416b-806f-0b80fb66f9cd", key: "gri33wtop0igur8w3a646llel3sd", filename: "logo.svg", content_type: "image/svg+xml", metadata: {"identified"=>true, "width"=>709, "height"=>137, "analyzed"=>true}, service_name: "scaleway", byte_size: 4137, checksum: "aZqqTYabP5+72ZeddcZ/2Q==", created_at: "2022-05-05 12:17:33.941505000 +0200", university_id: "ebf2d273-ffc9-4d9f-a4ee-a2146913d617">
   def attach_template_blobs
diff --git a/app/models/communication/block/template/feature.rb b/app/models/communication/block/template/feature.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bd888d365cc1fecb680ab6301ca49411336f203b
--- /dev/null
+++ b/app/models/communication/block/template/feature.rb
@@ -0,0 +1,6 @@
+class Communication::Block::Template::Feature < Communication::Block::Template::Base
+
+  has_elements
+  has_component :description, :rich_text
+
+end
diff --git a/app/models/communication/block/template/feature/element.rb b/app/models/communication/block/template/feature/element.rb
new file mode 100644
index 0000000000000000000000000000000000000000..25cd20876617e71004bcb3143b0ecb5568192a4b
--- /dev/null
+++ b/app/models/communication/block/template/feature/element.rb
@@ -0,0 +1,9 @@
+class Communication::Block::Template::Feature::Element < Communication::Block::Template::Base
+
+  has_component :title, :string
+  has_component :description, :text
+  has_component :image, :image
+  has_component :alt, :string
+  has_component :credit, :rich_text
+
+end
diff --git a/app/models/communication/block/template/post.rb b/app/models/communication/block/template/post.rb
index 617ff89ae25c1df41ee019a780ca2d128baf7ce1..de2ad77e93ac2f575e7ce5fc41f42697dfc418dc 100644
--- a/app/models/communication/block/template/post.rb
+++ b/app/models/communication/block/template/post.rb
@@ -1,7 +1,7 @@
 class Communication::Block::Template::Post < Communication::Block::Template::Base
 
   has_elements
-  has_layouts [:grid, :list, :highlight]
+  has_layouts [:grid, :list, :highlight, :alternate]
   has_component :mode, :option, options: [:all, :category, :selection]
   has_component :posts_quantity, :number, options: 3
   has_component :category_id, :category
diff --git a/app/models/communication/extranet.rb b/app/models/communication/extranet.rb
index 0dc31636314ab864924471003317609996665e7c..1a4615b3d6c6865f180dd318b43f9c1268235907 100644
--- a/app/models/communication/extranet.rb
+++ b/app/models/communication/extranet.rb
@@ -2,34 +2,35 @@
 #
 # Table name: communication_extranets
 #
-#  id                         :uuid             not null, primary key
-#  about_type                 :string           indexed => [about_id]
-#  color                      :string
-#  cookies_policy             :text
-#  css                        :text
-#  feature_alumni             :boolean          default(FALSE)
-#  feature_contacts           :boolean          default(FALSE)
-#  feature_jobs               :boolean          default(FALSE)
-#  feature_library            :boolean          default(FALSE)
-#  feature_posts              :boolean          default(FALSE)
-#  has_sso                    :boolean          default(FALSE)
-#  home_sentence              :text
-#  host                       :string
-#  name                       :string
-#  privacy_policy             :text
-#  registration_contact       :string
-#  sass                       :text
-#  sso_button_label           :string
-#  sso_cert                   :text
-#  sso_mapping                :jsonb
-#  sso_name_identifier_format :string
-#  sso_provider               :integer          default("saml")
-#  sso_target_url             :string
-#  terms                      :text
-#  created_at                 :datetime         not null
-#  updated_at                 :datetime         not null
-#  about_id                   :uuid             indexed => [about_type]
-#  university_id              :uuid             not null, indexed
+#  id                             :uuid             not null, primary key
+#  about_type                     :string           indexed => [about_id]
+#  allow_experiences_modification :boolean          default(TRUE)
+#  color                          :string
+#  cookies_policy                 :text
+#  css                            :text
+#  feature_alumni                 :boolean          default(FALSE)
+#  feature_contacts               :boolean          default(FALSE)
+#  feature_jobs                   :boolean          default(FALSE)
+#  feature_library                :boolean          default(FALSE)
+#  feature_posts                  :boolean          default(FALSE)
+#  has_sso                        :boolean          default(FALSE)
+#  home_sentence                  :text
+#  host                           :string
+#  name                           :string
+#  privacy_policy                 :text
+#  registration_contact           :string
+#  sass                           :text
+#  sso_button_label               :string
+#  sso_cert                       :text
+#  sso_mapping                    :jsonb
+#  sso_name_identifier_format     :string
+#  sso_provider                   :integer          default("saml")
+#  sso_target_url                 :string
+#  terms                          :text
+#  created_at                     :datetime         not null
+#  updated_at                     :datetime         not null
+#  about_id                       :uuid             indexed => [about_type]
+#  university_id                  :uuid             not null, indexed
 #
 # Indexes
 #
diff --git a/app/models/communication/website.rb b/app/models/communication/website.rb
index a2fc9d3ccdc70669f6a7fc5b9b4d5d67aae8bf36..6e9502353145d815d8a2290548b2308f0b3e4792 100644
--- a/app/models/communication/website.rb
+++ b/app/models/communication/website.rb
@@ -36,7 +36,6 @@
 class Communication::Website < ApplicationRecord
   self.filter_attributes += [:access_token]
 
-  include WithUniversity
   include WithAbouts
   include WithConfigs
   include WithConnectedObjects
@@ -46,10 +45,12 @@ class Communication::Website < ApplicationRecord
   include WithImport
   include WithOldDependencies
   include WithProgramCategories
+  include WithReferences
   include WithSpecialPages
   include WithMenus # Menus must be created after special pages, so we can fill legal menu
   include WithStyle
   include WithTheme
+  include WithUniversity
 
   enum git_provider: {
     github: 0,
@@ -78,10 +79,6 @@ class Communication::Website < ApplicationRecord
     ", term: "%#{sanitize_sql_like(term)}%")
   }
 
-  def self.save_and_sync_websites!
-    find_each &:save_and_sync
-  end
-
   def to_s
     "#{name}"
   end
diff --git a/app/models/communication/website/category.rb b/app/models/communication/website/category.rb
index 612276685f122391352a1a04d20c57c8209b29fe..a05f65c5f4c9c8b5688fc56b73efc68fc32396e0 100644
--- a/app/models/communication/website/category.rb
+++ b/app/models/communication/website/category.rb
@@ -41,11 +41,11 @@
 #  fk_rails_e58348b119  (program_id => education_programs.id)
 #
 class Communication::Website::Category < ApplicationRecord
+  include AsDirectObject
   include Sanitizable
   include WithBlobs
   include WithBlocks
   include WithFeaturedImage
-  include WithGit
   include WithMenuItemTarget
   include WithPermalink
   include WithPosition
@@ -58,8 +58,6 @@ class Communication::Website::Category < ApplicationRecord
                           class_name: 'Communication::Website::Imported::Category',
                           dependent: :destroy
   belongs_to              :university
-  belongs_to              :website,
-                          foreign_key: :communication_website_id
   belongs_to              :parent,
                           class_name: 'Communication::Website::Category',
                           optional: true
diff --git a/app/models/communication/website/menu.rb b/app/models/communication/website/menu.rb
index 87041f6d6966715259467fd5eca2969b75de5c24..a92deff4e91e8b6420a5c7434e31034571bf0e98 100644
--- a/app/models/communication/website/menu.rb
+++ b/app/models/communication/website/menu.rb
@@ -28,12 +28,11 @@
 #  fk_rails_dcc7198fc5  (communication_website_id => communication_websites.id)
 #
 class Communication::Website::Menu < ApplicationRecord
+  include AsDirectObject
   include Sanitizable
-  include WithGit
   include WithTranslations
   include WithUniversity
 
-  belongs_to :website, foreign_key: :communication_website_id
   has_many :items, class_name: 'Communication::Website::Menu::Item', dependent: :destroy
 
   validates :title, :identifier, presence: true
diff --git a/app/models/communication/website/page.rb b/app/models/communication/website/page.rb
index 2c3478afb10608c214df60cb1c15a113e2328288..09d5284361704fc7cabb834d5c396f29df538b9d 100644
--- a/app/models/communication/website/page.rb
+++ b/app/models/communication/website/page.rb
@@ -47,12 +47,12 @@ class Communication::Website::Page < ApplicationRecord
   self.ignored_columns = %w(path)
 
   include Accessible
+  include AsDirectObject
   include Sanitizable
   include WithBlobs
   include WithBlocks
   include WithDuplication
   include WithFeaturedImage
-  include WithGit
   include WithMenuItemTarget
   include WithPosition
   include WithTree
@@ -65,8 +65,6 @@ class Communication::Website::Page < ApplicationRecord
 
   has_summernote :text # TODO: Remove text attribute
 
-  belongs_to :website,
-             foreign_key: :communication_website_id
   belongs_to :parent,
              class_name: 'Communication::Website::Page',
              optional: true
diff --git a/app/models/communication/website/post.rb b/app/models/communication/website/post.rb
index 1e19a45a1804d8fc4595028f4a7635b44f1d2fa5..1e61cbe30c244cffa5f117fcd0b299a181d38d7f 100644
--- a/app/models/communication/website/post.rb
+++ b/app/models/communication/website/post.rb
@@ -38,12 +38,12 @@
 #  fk_rails_e0eec447b0  (author_id => university_people.id)
 #
 class Communication::Website::Post < ApplicationRecord
+  include AsDirectObject
   include Sanitizable
   include WithBlobs
   include WithBlocks
   include WithDuplication
   include WithFeaturedImage
-  include WithGit
   include WithMenuItemTarget
   include WithPermalink
   include WithSlug # We override slug_unavailable? method
@@ -54,10 +54,7 @@ class Communication::Website::Post < ApplicationRecord
 
   has_one :imported_post,
           class_name: 'Communication::Website::Imported::Post',
-          dependent: :destroy
-  belongs_to :website,
-             class_name: 'Communication::Website',
-             foreign_key: :communication_website_id
+          dependent: :destroy  
   belongs_to :author,
              class_name: 'University::Person',
              optional: true
diff --git a/app/models/communication/website/with_connected_objects.rb b/app/models/communication/website/with_connected_objects.rb
index 8ec4a970ec1fda026714cdd6eec7ec10908059be..356752344403224d5a4ccab53b2c046c7da32624 100644
--- a/app/models/communication/website/with_connected_objects.rb
+++ b/app/models/communication/website/with_connected_objects.rb
@@ -2,15 +2,28 @@ module Communication::Website::WithConnectedObjects
   extend ActiveSupport::Concern
 
   included do
-    has_many  :connections
+    has_many :connections
 
-    # before_save :clean_connections!
+    after_save :connect_about, if: :saved_change_to_about_id?
   end
 
-  def clean_connections!
-    start = Time.now
-    connect self, self
-    connections.reload.where('updated_at < ?', start).delete_all
+  # AppelƩ
+  # - par un objet avec des connexions lorsqu'il est destroyed
+  # - par le website lui-mĆŖme au changement du about
+  def destroy_obsolete_connections
+    up_to_date_dependencies = recursive_dependencies
+    deletable_connection_ids = []
+    connections.find_each do |connection|
+      has_living_connection = up_to_date_dependencies.detect { |dependency|
+        dependency.class.name == connection.indirect_object_type &&
+        dependency.id == connection.indirect_object_id
+      }
+      deletable_connection_ids << connection.id unless has_living_connection
+    end
+    # On utilise delete_all pour supprimer les connexions obsolĆØtes en une unique requĆŖte DELETE FROM
+    # Cependant, on peut le faire car les connexions n'ont pas de callback.
+    # Dans le cas oĆ¹ on en rajoute au destroy, il faut repasser sur un appel de destroy sur chaque
+    connections.where(id: deletable_connection_ids).delete_all
   end
 
   def has_connected_object?(indirect_object)
@@ -55,19 +68,23 @@ module Communication::Website::WithConnectedObjects
     University::Organization.where(id: ids)
   end
 
+  def is_direct_object?
+    true
+  end
+
+  def is_indirect_object?
+    false
+  end
+
   protected
 
+  def connect_about
+    self.connect(about, self) if about.present? && about.try(:is_indirect_object?)
+    destroy_obsolete_connections
+  end
+
   def connect_object(indirect_object, direct_source, direct_source_type: nil)
-    # byebug if indirect_object.is_a?(Communication::Block) && indirect_object.template_kind == 'organization_chart'
-    return unless persisted?
-    # On ne connecte pas les objets inexistants
-    return if indirect_object.nil?
-    # On ne connecte pas les objets sans source
-    return if direct_source.nil?
-    # On ne connecte pas le site Ć  lui-mĆŖme
-    return if indirect_object.is_a?(Communication::Website)
-    # On ne connecte pas les objets directs
-    return if indirect_object.respond_to?(:website)
+    return unless should_connect?(indirect_object, direct_source)
     # puts "connect #{object} (#{object.class})"
     direct_source_type ||= direct_source.class.base_class.to_s
     connection = connections.where( university: university,
@@ -77,4 +94,18 @@ module Communication::Website::WithConnectedObjects
                             .first_or_create
     connection.touch if connection.persisted?
   end
+
+  def should_connect?(indirect_object, direct_source)
+    # Ce cas se produit quand on save un new website et qu'on ne passe pas un validateur
+    return false unless persisted?
+    # On ne connecte pas les objets inexistants
+    return false if indirect_object.nil?
+    # On ne connecte pas les objets sans source
+    return false if direct_source.nil?
+    # On ne connecte pas le site Ć  lui-mĆŖme
+    return false if indirect_object.is_a?(Communication::Website)
+    # On ne connecte pas les objets directs (en principe Ƨa n'arrive pas)
+    return false if indirect_object.try(:is_direct_object?)
+    true
+  end
 end
\ No newline at end of file
diff --git a/app/models/communication/website/with_dependencies.rb b/app/models/communication/website/with_dependencies.rb
deleted file mode 100644
index 2bfdc73ae855964a08be5e83ecabe8ff56ab9034..0000000000000000000000000000000000000000
--- a/app/models/communication/website/with_dependencies.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module Communication::Website::WithDependencies
-  extend ActiveSupport::Concern
-
-  def sync_obsolete_dependencies
-    all_dependencies = recursive_dependencies
-    syncable_dependencies = recursive_dependencies(syncable_only: true)
-    obsolete_dependencies = all_dependencies - syncable_dependencies
-    return unless obsolete_dependencies.any?
-    obsolete_dependencies.each do |dependency|
-      Communication::Website::GitFile.sync self, dependency, destroy: true
-    end
-    self.git_repository.sync!
-  end
-  handle_asynchronously :sync_obsolete_dependencies, queue: :default
-
-end
diff --git a/app/models/communication/website/with_git_repository.rb b/app/models/communication/website/with_git_repository.rb
index 436b1f64ef663f438eff86b1efabc1e93b935e83..fd6bb72da7e04eff10bb34809e1ea6641653680c 100644
--- a/app/models/communication/website/with_git_repository.rb
+++ b/app/models/communication/website/with_git_repository.rb
@@ -10,4 +10,18 @@ module Communication::Website::WithGitRepository
   def git_repository
     @git_repository ||= Git::Repository.new self
   end
+
+  # Supprimer tous les git_files qui ne sont pas dans les recursive_dependencie_syncable
+  def destroy_obsolete_git_files
+    website_git_files.find_each do |git_file|
+      dependency = git_file.about
+      is_obsolete = !dependency.in?(recursive_dependencie_syncable)
+      if is_obsolete
+        # TODO git_file.destroy serait plus ActiveRecord
+        Communication::Website::GitFile.sync(self, dependency, destroy: true)
+      end
+    end
+    self.git_repository.sync!
+  end
+  handle_asynchronously :destroy_obsolete_git_files, queue: :default
 end
diff --git a/app/models/communication/website/with_old_dependencies.rb b/app/models/communication/website/with_old_dependencies.rb
index 527d1aa980f3cd3718a99d0ef08aa0a9fabf840b..39e51edead9c61358aaca5b360d192c03db2c50c 100644
--- a/app/models/communication/website/with_old_dependencies.rb
+++ b/app/models/communication/website/with_old_dependencies.rb
@@ -41,15 +41,15 @@ module Communication::Website::WithOldDependencies
   end
 
   def administrators
-    about&.administrators
+    has_administrators? ? about.administrators : University::Person.none
   end
 
   def researchers
-    about&.researchers
+    has_researchers? ? about.researchers : University::Person.none
   end
 
   def teachers
-    about&.teachers
+    has_teachers? ? about.teachers : University::Person.none
   end
 
   def people_in_blocks
diff --git a/app/models/concerns/as_direct_object.rb b/app/models/concerns/as_direct_object.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cfbaec991499b42af9995809793584b67ba5f761
--- /dev/null
+++ b/app/models/concerns/as_direct_object.rb
@@ -0,0 +1,34 @@
+# Ce concern ajoute les ƩlƩments nƩcessaires pour les objets directs :
+# - DĆ©pendances (avec et via synchro)
+# - Git
+# - GitFiles
+# - RƩfƩrences
+# - Connexions (en tant que source)
+module AsDirectObject
+  extend ActiveSupport::Concern
+
+  included do
+    include WithDependencies
+    include WithGit
+    include WithGitFiles
+    include WithReferences
+
+    belongs_to :website,
+               class_name: 'Communication::Website',
+               foreign_key: :communication_website_id
+
+    has_many  :connections, 
+              as: :direct_source,
+              class_name: 'Communication::Website::Connection',
+              dependent: :destroy # When the direct object disappears all connections with the object as a source must disappear
+
+  end
+
+  def is_direct_object?
+    true
+  end
+
+  def is_indirect_object?
+    false
+  end
+end
\ No newline at end of file
diff --git a/app/models/concerns/as_indirect_object.rb b/app/models/concerns/as_indirect_object.rb
new file mode 100644
index 0000000000000000000000000000000000000000..118bbe5ce4f0ba34bfc55ac67ddb0dc8fd72fa0e
--- /dev/null
+++ b/app/models/concerns/as_indirect_object.rb
@@ -0,0 +1,89 @@
+# Ce concern ajoute les ƩlƩments nƩcessaires pour les objets indirects :
+# - connexions
+# - dƩpendances (avec et via synchro)
+# - rƩfƩrences nƩcessaires
+module AsIndirectObject
+  extend ActiveSupport::Concern
+
+  included do
+    # Les blocs sont des objets indirects, mais n'ont pas de GitFiles, on n'inclut donc pas WithGitFiles ici
+    include WithDependencies
+    include WithReferences
+
+    has_many  :connections,
+              as: :indirect_object,
+              class_name: 'Communication::Website::Connection'
+              # Pas dependent_destroy parce que le processus est plus sophistiquƩ, et est fait dans la mƩthode destroy
+    has_many  :websites,
+              through: :connections
+    # Ce serait super de faire la ligne ci-dessous, mais Rails ne sait pas faire Ƨa avec un objet polymorphe (direct_source)
+    # has_many :direct_sources, through: :connections
+
+    after_save  :sync_connections
+    after_touch :sync_connections
+  end
+
+  def is_direct_object?
+    false
+  end
+
+  def is_indirect_object?
+    true
+  end
+
+  def for_website?(website)
+    website.has_connected_object?(self)
+  end
+
+  def direct_sources
+    @direct_sources ||= begin
+      # On initialise les direct_sources avec les connexions existantes
+      direct_sources = direct_sources_from_existing_connections
+      # On boucle sur les rƩfƩrences pour rƩcupƩrer les direct sources manquantes
+      references.each do |reference|
+        direct_sources += direct_sources_from_reference(reference)
+      end
+      direct_sources.uniq
+    end
+  end
+
+  def destroy
+    # On est obligĆ©s d'overwrite la mĆ©thode destroy pour Ć©viter un problĆØme d'Å“uf et de poule.
+    # On a besoin que les websites puissent recalculer leurs recursive_dependencies
+    # et on a besoin que ces recursive_dependencies n'incluent pas l'objet courant, puisqu'il est "en cours de destruction" (ni ses propres recursive_dependencies).
+    # Mais si on dƩtruit juste l'objet et qu'on fait un `after_destroy :clean_website_connections`
+    # on ne peut plus accƩder aux websites (puisque l'objet est dƩjƠ dƩtruit et ses connexions en cascades).
+    # Donc :
+    # 1. on stocke les websites
+    # 2. PUIS on dƩtruit les connexions
+    # 3. PUIS on dƩtruit l'objet (la mƩthode destroy normale)
+    # 4. PUIS on demande aux websites stockƩs de nettoyer leurs connexions
+    self.transaction do
+      website_ids = websites.pluck(:id)
+      connections.destroy_all
+      super
+      Communication::Website.where(id: website_ids).each do |website|
+        website.destroy_obsolete_connections
+        website.save_and_sync
+      end
+    end
+  end
+
+  protected
+
+  def direct_sources_from_existing_connections
+    connections.collect &:direct_source
+  end
+
+  def direct_sources_from_reference(reference)
+    reference.is_direct_object? ? [reference] # RƩcupƩration de la connexion directe
+                                : reference.direct_sources # RƩcursivitƩ sur les rƩfƩrences
+  end
+
+  def sync_connections
+    direct_sources.each do |direct_source|
+      direct_source.website.connect self, direct_source
+      direct_source.sync_with_git
+    end
+  end
+end
\ No newline at end of file
diff --git a/app/models/concerns/with_abouts.rb b/app/models/concerns/with_abouts.rb
index c2cbb413bccc9d353fd07b1a34b56549a97cf7ec..4be1b380992c26208b575eab97037a611fd5fb15 100644
--- a/app/models/concerns/with_abouts.rb
+++ b/app/models/concerns/with_abouts.rb
@@ -6,6 +6,8 @@ module WithAbouts
                 polymorphic: true,
                 optional: true
 
+    before_validation :nullify_about_id_if_about_type_changed_to_blank
+
     scope :for_about_type, -> (type) { where(about_type: type) }
 
     def self.about_types
@@ -17,6 +19,11 @@ module WithAbouts
         Research::Journal.name,
       ]
     end
+  end
+
+  protected
 
+  def nullify_about_id_if_about_type_changed_to_blank
+    self.about_id = nil if about_type_changed? && about_type.blank?
   end
 end
diff --git a/app/models/concerns/with_connections.rb b/app/models/concerns/with_connections.rb
deleted file mode 100644
index 16f4b84d26c5c5c27450c7375221553a06246411..0000000000000000000000000000000000000000
--- a/app/models/concerns/with_connections.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# Concern exclusivement utilisƩ pour les objets indirects
-module WithConnections
-  extend ActiveSupport::Concern
-
-  included do
-    include WithDependencies
-    include WithReferences
-
-    has_many  :connections, 
-              as: :indirect_object,
-              class_name: 'Communication::Website::Connection',
-              dependent: :destroy # When the indirect object disappears, the connections must disappear
-    has_many  :websites, 
-              through: :connections
-    # Ce serait super de faire la ligne ci-dessous, mais Rails ne sait pas faire Ƨa avec un objet polymorphe (direct_source)
-    # has_many :direct_sources, through: :connections
-
-    after_save    :sync_connections
-    after_touch   :sync_connections
-    after_save    :sync_obsolete_dependencies
-    after_destroy :destroy_obsolete_connections
-  end
-
-  def for_website?(website)
-    website.has_connected_object?(self)
-  end
-
-  def direct_sources
-    @direct_sources ||= begin
-      # On initialise les direct_sources avec les connexions existantes
-      direct_sources = direct_sources_from_existing_connections
-      # On boucle sur les rƩfƩrences pour rƩcupƩrer les direct sources manquantes
-      references.each do |reference|
-        direct_sources += direct_sources_from_reference(reference)
-      end
-      direct_sources.uniq
-    end
-  end
-
-  protected
-
-  def direct_sources_from_existing_connections
-    connections.collect &:direct_source
-  end
-
-  def direct_sources_from_reference(reference)
-    reference.respond_to?(:website) ? [reference] # RƩcupƩration de la connexion directe
-                                    : reference.direct_sources # RƩcursivitƩ sur les rƩfƩrences
-  end
-
-  def sync_connections
-    direct_sources.each do |direct_source|
-      direct_source.website.connect self, direct_source
-      direct_source.save_and_sync
-    end
-  end
-
-  # La suppression d'un objet indirect dƩclenche le recalcul des connexions de tous les objets directs
-  def destroy_obsolete_connections
-    direct_sources.each do |direct_source|
-      # TODO
-    end
-  end
-
-  def sync_obsolete_dependencies
-     # TODO: pas ouf de passer par le site, ce serait plus lƩger en calcul de faire une analyse plus Ʃtroite
-    websites.each do |website|
-      website.sync_obsolete_dependencies
-    end
-  end
-end
\ No newline at end of file
diff --git a/app/models/concerns/with_dependencies.rb b/app/models/concerns/with_dependencies.rb
index dbc5ca0d8bf0a940192d0ca09aca538614bcd845..5b8526ead1a1afcb9c2920b6bb7add7cdce2320d 100644
--- a/app/models/concerns/with_dependencies.rb
+++ b/app/models/concerns/with_dependencies.rb
@@ -5,6 +5,17 @@
 module WithDependencies
   extend ActiveSupport::Concern
 
+  included do
+    attr_accessor :previous_dependencies
+
+    if self < ActiveRecord::Base
+      before_save :snapshot_dependencies
+      after_save :clean_websites_if_necessary
+      after_destroy :clean_websites
+    end
+  end
+
+
   # Cette mĆ©thode doit ĆŖtre dĆ©finie dans chaque objet,
   # et renvoyer un tableau de ses rƩfƩrences directes.
   # Jamais de rƩfƩrence indirecte !
@@ -24,14 +35,71 @@ module WithDependencies
     end
   end
 
+  # On ne liste pas les objets en cours de suppression
+  # return array if respond_to?(:mark_for_destruction?) && mark_for_destruction
+  # On renvoie l'array tel quel, non modifiƩ, si on demande les contenus syncable_only et que le contenu ne l'est pas
   def recursive_dependencies(array: [], syncable_only: false)
-    return array if syncable_only && !syncable?
+    return array unless dependency_should_be_synced?(self, syncable_only)
     dependencies.each do |dependency|
-      next if dependency.in?(array)
+      # Si l'objet ne doit pas ĆŖtre ajoutĆ© on n'ajoute pas non plus ses dĆ©pendances rĆ©cursives
+      # C'est le fait de couper ici qui Ć©vite la boucle infinie
+      next unless dependency_should_be_added?(array, dependency, syncable_only)
       array << dependency
       next unless dependency.respond_to?(:recursive_dependencies)
       array = dependency.recursive_dependencies(array: array, syncable_only: syncable_only)
     end
     array.compact
   end
+
+  def recursive_dependencies_syncable
+    @recursive_dependencies_syncable ||= recursive_dependencies(syncable_only: true)
+  end
+
+  protected
+  
+  # Si l'objet est dƩjƠ lƠ, on ne doit pas l'ajouter
+  # Si l'objet n'est pas syncable, on ne doit pas l'ajouter non plus
+  def dependency_should_be_added?(array, dependency, syncable_only)
+    !dependency.in?(array) && dependency_should_be_synced?(dependency, syncable_only)
+  end
+  
+  # Si on n'est pas en syncable only on liste tout, sinon, il faut analyser
+  def dependency_should_be_synced?(dependency, syncable_only)
+    !syncable_only || (dependency.respond_to?(:syncable?) && dependency.syncable?)
+  end
+
+  # Stockage en RAM des dƩpendances avant enregistrement
+  def snapshot_dependencies
+    @previous_dependencies = persisted? ? reloaded_recursive_dependencies_syncable_filtered : []
+  end
+
+  def clean_websites_if_necessary
+    # Debug :)
+    # puts self
+    # puts "  previous_dependencies           #{ @previous_dependencies }"
+    # puts "  recursive_dependencies_syncable #{ reloaded_recursive_dependencies_syncable_filtered }"
+    # puts "  missing_dependencies_after_save #{ missing_dependencies_after_save }"
+    # puts
+    clean_websites if missing_dependencies_after_save.any?
+  end
+
+  def clean_websites
+    return unless respond_to?(:is_direct_object?)
+    
+    if is_direct_object?
+      website.destroy_obsolete_git_files
+    elsif is_indirect_object?
+      websites.each(&:destroy_obsolete_git_files)
+    end
+  end
+
+  def missing_dependencies_after_save
+    @previous_dependencies - reloaded_recursive_dependencies_syncable_filtered
+  end
+
+  def reloaded_recursive_dependencies_syncable_filtered
+    reloaded_object = self.class.unscoped.find(id)
+    reloaded_dependencies = reloaded_object.recursive_dependencies_syncable
+    DependenciesFilter.filtered(reloaded_dependencies)
+  end
 end
\ No newline at end of file
diff --git a/app/models/concerns/with_git.rb b/app/models/concerns/with_git.rb
index af457c0e184ca094de6085b9e375b106f26f59ad..b06c6f5bfc98be17ce46842cd962cc845786b4aa 100644
--- a/app/models/concerns/with_git.rb
+++ b/app/models/concerns/with_git.rb
@@ -1,13 +1,6 @@
 module WithGit
   extend ActiveSupport::Concern
 
-  included do
-    # WithGit a besoin de ces 3 concerns
-    include WithDependencies
-    include WithGitFiles
-    include WithReferences
-  end
-
   def save_and_sync
     if save
       sync_with_git
@@ -49,11 +42,6 @@ module WithGit
   def destroy_from_git
     return unless website.git_repository.valid?
     Communication::Website::GitFile.sync website, self, destroy: true
-    # # FIXME
-    # dependencies = git_destroy_dependencies(website).to_a.flatten.uniq.compact
-    # dependencies.each do |object|
-    #   Communication::Website::GitFile.sync website, object, destroy: true
-    # end
     website.git_repository.sync!
   end
 
diff --git a/app/models/concerns/with_translations.rb b/app/models/concerns/with_translations.rb
index 3d1771af782eeccbbe4ecb55004e9a3f356eef04..50d90c41243c6b321629814a5021c3c45a935fdb 100644
--- a/app/models/concerns/with_translations.rb
+++ b/app/models/concerns/with_translations.rb
@@ -16,7 +16,7 @@ module WithTranslations
 
   def available_languages
     @available_languages ||= begin
-      languages = respond_to?(:website) ? website.languages : Language.all
+      languages = is_direct_object? ? website.languages : Language.all
       languages.ordered
     end
   end
diff --git a/app/models/education/diploma.rb b/app/models/education/diploma.rb
index ef19ef4a6bf45f4a18bcd66b70884540d0c7bb10..0d31049aadc1a51f4da44ce1d3739b908a7f23e1 100644
--- a/app/models/education/diploma.rb
+++ b/app/models/education/diploma.rb
@@ -23,9 +23,9 @@
 #  fk_rails_6cb2e9fa90  (university_id => universities.id)
 #
 class Education::Diploma < ApplicationRecord
+  include AsIndirectObject
   include Sanitizable
   include WithBlocks
-  include WithConnections
   include WithGitFiles
   include WithPermalink
   include WithSlug
diff --git a/app/models/education/program.rb b/app/models/education/program.rb
index 510b58acb70f29d7fe1db5c375ad7fe3acd4fd31..76f86dface99c1a7414f5c73f38a0c325eb85ae2 100644
--- a/app/models/education/program.rb
+++ b/app/models/education/program.rb
@@ -51,11 +51,11 @@
 #
 class Education::Program < ApplicationRecord
   include Aboutable
+  include AsIndirectObject
   include Sanitizable
   include WithAlumni
   include WithBlobs
   include WithBlocks
-  include WithConnections
   include WithDiploma
   include WithFeaturedImage
   include WithGitFiles
diff --git a/app/models/education/school.rb b/app/models/education/school.rb
index efd65404f37fb25bc48a6b5687e107afb86ad4ba..b2160c3e240b44420c52ac7df63c1bf8b564ca4f 100644
--- a/app/models/education/school.rb
+++ b/app/models/education/school.rb
@@ -26,10 +26,10 @@
 #
 class Education::School < ApplicationRecord
   include Aboutable
+  include AsIndirectObject
   include Sanitizable
   include WithAlumni
   include WithBlobs
-  include WithConnections
   include WithCountry
   include WithGitFiles
   include WithPrograms # must come before WithAlumni and WithTeam
diff --git a/app/models/research/hal/publication.rb b/app/models/research/hal/publication.rb
index 45103be805f600af7d896b8fdbcc7ae53ac9a286..9682b57fb4da7b323272410c52ad7705777e7a09 100644
--- a/app/models/research/hal/publication.rb
+++ b/app/models/research/hal/publication.rb
@@ -20,8 +20,8 @@
 #  index_research_hal_publications_on_docid  (docid)
 #
 class Research::Hal::Publication < ApplicationRecord
+  include AsIndirectObject
   include Sanitizable
-  include WithConnections
   include WithGitFiles
   include WithSlug
 
diff --git a/app/models/research/journal.rb b/app/models/research/journal.rb
index afbc32ba9c48f572031649d8b7213cc84a3e7039..a97d40b27bf47b01f7f620b0574973f620f2d3b6 100644
--- a/app/models/research/journal.rb
+++ b/app/models/research/journal.rb
@@ -20,9 +20,9 @@
 #  fk_rails_96097d5f10  (university_id => universities.id)
 #
 class Research::Journal < ApplicationRecord
-  include Sanitizable
+  include AsIndirectObject
   include Aboutable
-  include WithConnections
+  include Sanitizable
   include WithGitFiles
   include WithUniversity
 
diff --git a/app/models/research/journal/paper.rb b/app/models/research/journal/paper.rb
index dcf9299c8b272916f1762b0181542e29b92ec876..3ed767691c00946415f4d432602acc34f79551a4 100644
--- a/app/models/research/journal/paper.rb
+++ b/app/models/research/journal/paper.rb
@@ -43,10 +43,10 @@
 #  fk_rails_db4e38788c  (kind_id => research_journal_paper_kinds.id)
 #
 class Research::Journal::Paper < ApplicationRecord
+  include AsIndirectObject
   include Sanitizable
   include WithBlobs
   include WithBlocks
-  include WithConnections
   include WithGitFiles
   include WithPermalink
   include WithPosition
diff --git a/app/models/research/journal/paper/kind.rb b/app/models/research/journal/paper/kind.rb
index 85ab5934fc8b2ec2b94d39d79f6c00cfd7cc35f4..8b376f5154b7333c32faed94c8fc870d597036c3 100644
--- a/app/models/research/journal/paper/kind.rb
+++ b/app/models/research/journal/paper/kind.rb
@@ -21,8 +21,8 @@
 #  fk_rails_8e6f992b9d  (university_id => universities.id)
 #
 class Research::Journal::Paper::Kind < ApplicationRecord
+  include AsIndirectObject
   include Sanitizable
-  include WithConnections
   include WithGitFiles
   include WithSlug
   include WithUniversity
diff --git a/app/models/research/journal/volume.rb b/app/models/research/journal/volume.rb
index f7f72b6850da4314ab10de778744ff0f927aeb8b..513f44046ef6d22fe10c4631d66471c3794f80d7 100644
--- a/app/models/research/journal/volume.rb
+++ b/app/models/research/journal/volume.rb
@@ -30,9 +30,9 @@
 #  fk_rails_c83d5e9068  (university_id => universities.id)
 #
 class Research::Journal::Volume < ApplicationRecord
+  include AsIndirectObject
   include Sanitizable
   include WithBlobs
-  include WithConnections
   include WithFeaturedImage
   include WithGitFiles
   include WithPermalink
diff --git a/app/models/research/laboratory.rb b/app/models/research/laboratory.rb
index 866a143237e48b4ef025a140a7202479e5d9ce9d..eb0f676f3480a88e58e20ef30b13eebdbc1be410 100644
--- a/app/models/research/laboratory.rb
+++ b/app/models/research/laboratory.rb
@@ -22,8 +22,8 @@
 #
 class Research::Laboratory < ApplicationRecord
   include Aboutable
+  include AsIndirectObject
   include Sanitizable
-  include WithConnections
   include WithCountry
   include WithGitFiles
 
diff --git a/app/models/university/organization.rb b/app/models/university/organization.rb
index fd9f77990ea7138fb6fc104790fb9f2d9735d787..c5302c089af886b379cbd402c50a68fd940ea459 100644
--- a/app/models/university/organization.rb
+++ b/app/models/university/organization.rb
@@ -40,10 +40,10 @@
 #  fk_rails_35fcd198e0  (university_id => universities.id)
 #
 class University::Organization < ApplicationRecord
+  include AsIndirectObject
   include Sanitizable
   include WithBlobs
   include WithBlocks
-  include WithConnections
   include WithCountry
   include WithGeolocation
   include WithGitFiles
diff --git a/app/models/university/person.rb b/app/models/university/person.rb
index 1f60a92fbc577e9af35c6a737f0344f209b4adaf..fa91f6d149b2a87287e555d0c4dbb3b193ba5daa 100644
--- a/app/models/university/person.rb
+++ b/app/models/university/person.rb
@@ -53,10 +53,10 @@
 #  fk_rails_da35e70d61  (university_id => universities.id)
 #
 class University::Person < ApplicationRecord
+  include AsIndirectObject
   include Sanitizable
   include WithBlobs
   include WithBlocks
-  include WithConnections
   include WithCountry
   include WithEducation
   include WithExperiences
diff --git a/app/services/dependencies_filter.rb b/app/services/dependencies_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4523403c22c3860252e2cae806079d5bd0646fc0
--- /dev/null
+++ b/app/services/dependencies_filter.rb
@@ -0,0 +1,12 @@
+class DependenciesFilter
+
+  # [
+  #   "gid://osuny/Communication::Block/4ac9f6fe-80cd-46f6-becc-fc14b3fecea0",
+  #   "gid://osuny/University::Person/36501bf0-99b9-58f9-b269-bf161b451c43"
+  # ]
+  def self.filtered(dependencies)
+    dependencies.select { |dependency| dependency.is_a?(ActiveRecord::Base) }
+                .map    { |dependency| dependency.to_global_id.to_s }
+                .uniq
+  end
+end
\ No newline at end of file
diff --git a/app/services/osuny/simple_navigation_renderer.rb b/app/services/osuny/simple_navigation_renderer.rb
index b6857b68e21279677696b68b500714ad00d18d76..b1dcd9275ac3f0fdfcef8fad3ed1c1a3092b0e34 100644
--- a/app/services/osuny/simple_navigation_renderer.rb
+++ b/app/services/osuny/simple_navigation_renderer.rb
@@ -1,5 +1,5 @@
 class Osuny::SimpleNavigationRenderer < SimpleNavigation::Renderer::Base
-  OPEN = "<div class=\"col-md-4 col-lg-3 mb-5\">"
+  OPEN = "<div class=\"col-sm-6 col-md-4 col-lg-3 mb-5\">"
   CLOSE = "</div>"
 
   attr_accessor :content, :index, :item
diff --git a/app/views/admin/application/i18n/_widget.html.erb b/app/views/admin/application/i18n/_widget.html.erb
index a761a658dfbcbf13dcda6d2d9ec3da966564edb4..dd235bf33dbc7a7869ba3d916d1b049b0641f4a4 100644
--- a/app/views/admin/application/i18n/_widget.html.erb
+++ b/app/views/admin/application/i18n/_widget.html.erb
@@ -1,7 +1,7 @@
 <% if about.available_languages.many? %>
   <%
-    route_args = about.respond_to?(:website) ? [:admin, about.becomes(about.class.base_class)]
-                                              : [:show_in_language, :admin, about.becomes(about.class.base_class)]
+    route_args = about.is_direct_object?  ? [:admin, about.becomes(about.class.base_class)]
+                                          : [:show_in_language, :admin, about.becomes(about.class.base_class)]
   %>
   <%= osuny_panel t('internationalization.label') do %>
     <ol class="list-unstyled">
diff --git a/app/views/admin/communication/blocks/_preview.html.erb b/app/views/admin/communication/blocks/_preview.html.erb
index bd26dfe7acf4a9579b6cae270b2b5c53423d96b8..1fc6d39b60eb598b4f0d980d138013af92860f17 100644
--- a/app/views/admin/communication/blocks/_preview.html.erb
+++ b/app/views/admin/communication/blocks/_preview.html.erb
@@ -1,18 +1,4 @@
-<% is_leaflet_needed = true; %>
-
 <% about.blocks.published.ordered.each do |block| %>
   <% @block = block %>
   <%= render "admin/communication/blocks/templates/#{@block.template_kind}/preview" %>
-  <% if block.template_kind == "partners" && block.data[:layout] == "map" %>
-    <% is_leaflet_needed = true; %>
-  <% end %>
-<% end %>
-
-<% if is_leaflet_needed %>
-  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
-        integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI="
-        crossorigin=""/>
-  <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"
-          integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM="
-          crossorigin=""></script>
 <% end %>
diff --git a/app/views/admin/communication/blocks/templates/features/_edit.html.erb b/app/views/admin/communication/blocks/templates/features/_edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e9a60f1c569c753464c16a159eeab64dd613273f
--- /dev/null
+++ b/app/views/admin/communication/blocks/templates/features/_edit.html.erb
@@ -0,0 +1,35 @@
+<div class="row pure__row--small">
+  <div class="col-xl-6">
+    <%= block_component_edit :description %>
+  </div>
+</div>
+
+<%= block_component_add_element t('.add_element') %>
+<draggable :list="data.elements" handle=".dragHandle" class="mb-3 <%= if_appstack 'list-group' %>">
+  <div v-for="(element, index) in data.elements" class="draggable-item <%= if_appstack 'list-group-item' %>">
+    <div>
+      <a class="btn ps-0 pt-0 dragHandle"><i class="<%= Icon::DRAG %> handle"></i></a>{{element.title}}
+      <a  class="btn btn-sm text-danger float-end pe-0"
+          v-on:click="data.elements.splice(data.elements.indexOf(element), 1)"
+          title="<%= t '.remove_element' %>">
+          <i class="<%= Icon::DELETE %>"></i>
+      </a>
+    </div>
+    <div class="row pure__row--small">
+      <div class="col-lg-4">
+        <%= block_component_edit :title, template: @element %>
+        <%= block_component_edit :description, template: @element, rows: 5 %>
+      </div>
+      <div class="col-lg-4">
+        <%= block_component_edit :image, template: @element %>
+      </div>
+      <div class="col-lg-4" v-show="element.image.id != ''">
+        <%= block_component_edit :alt, template: @element %>
+        <%= block_component_edit :credit, template: @element %>
+      </div>
+    </div>
+  </div>
+</draggable>
+<div v-show="data.elements.length > 2">
+  <%= block_component_add_element t('.add_element') %>
+</div>
\ No newline at end of file
diff --git a/app/views/admin/communication/blocks/templates/features/_preview.html.erb b/app/views/admin/communication/blocks/templates/features/_preview.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/views/admin/communication/blocks/templates/features/_static.html.erb b/app/views/admin/communication/blocks/templates/features/_static.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..2dd180eae5b5d1367c84827e7f24a8b8f89d7ea0
--- /dev/null
+++ b/app/views/admin/communication/blocks/templates/features/_static.html.erb
@@ -0,0 +1,9 @@
+<%= block_component_static :description %>
+      elements:
+<% block.template.elements.each do |element| %>
+<%= block_component_static :title, template: element, list: true, depth: 4 %>
+<%= block_component_static :description, template: element, depth: 5 %>
+<%= block_component_static :image, template: element, depth: 5 %>
+<%= block_component_static :alt, template: element, depth: 5 %>
+<%= block_component_static :credit, template: element, depth: 5 %>
+<% end %>
diff --git a/app/views/admin/communication/blocks/templates/pages/_preview.html.erb b/app/views/admin/communication/blocks/templates/pages/_preview.html.erb
index 42f0873509ba6bec96c72a782d8ebf2352379e5e..36bf97d8769a3bc319ac57580dc8cb27b15e23fc 100644
--- a/app/views/admin/communication/blocks/templates/pages/_preview.html.erb
+++ b/app/views/admin/communication/blocks/templates/pages/_preview.html.erb
@@ -5,49 +5,90 @@ unless @block.title.blank?
 end
 class_name += " block-pages--" + @block.template.layout
 %>
-<section class="<%= class_name %>" style="display: none;">
+<section class="<%= class_name %>">
   <div class="container">
     <div class="block-content">
-      <% if @block.data %>
-        <% if @block.template.page %>
-          <%#= @block.template.page.slug %>
-        <% end %>
+      <% unless @block.title.blank? %>
+        <div class="top">
+          <% unless @block.title.blank? %>
 
-        <% if @block.template.show_main_description %>
-          <p></p>
+            <% if @block.template.layout === "cards" || @block.template.layout === "list" %>
+              <h2><%= link_to @block.template.page, @block.template.page.url %></h2>
+            
+            <% elsif @block.template.layout === "grid" && @block.template.page && @block.template.show_main_description %>
+              <h2><%= link_to @block.title, @block.template.page.url %></h2>
+              <p class="description"><%= @block.template.page.summary %></p>
+
+            <% else %>
+              <h2><%= @block.title %></h2>
+            <% end %>
+
+          <% end %>
+          <% if @block.template.layout === "cards" && @block.template.show_main_description && @block.data %>
+            <div class="description">
+              <p><%= @block.template.page.summary %></p>
+            </div>
+          <% end %>
+        </div>
+        <% if @block.template.layout === "list" && @block.template.show_main_description && @block.data %> 
+          <p class="description"><%= @block.template.page.summary %></p>
         <% end %>
+      <% end %>
+      <% if @block.data %>
 
         <% if @block.template.layout ===  "list" %>
-          
           <ul>
-            <% @block.template.elements.each do |element| %>
-              <li>
-                <%#= element %>
-              </li>
+            <% @block.template.selected_pages.each do |page|
+              next if page.nil?
+            %>
+            <li><%= link_to page, page.url %></li>
             <% end %>
           </ul>
 
         <% elsif @block.template.layout ===  "cards" %>
-
           <div class="cards">
-            <% @block.template.elements.each do |element| %>
+            <% @block.template.selected_pages.each do |page|
+              next if page.nil?
+            %>
               <article class="card">
-                <%# <%= element %>
+                <h3>
+                  <%= link_to page, page.url %>
+                </h3>
                 <% if @block.template.show_description %>
-                  <p><%#= element.summary %></p>
+                  <p><%= page.summary %></p>
+                <% end %>
+
+                <p class="more meta" aria-hidden="true"><%= t 'admin.communication.blocks.templates.pages.layouts.cards.more' %></p>
+
+                <% if @block.template.show_image %>
+                  <div class="media" itemprop="image">
+                    <% if page.featured_image.attached? %>
+                      <%= kamifusen_tag page.featured_image %>
+                    <% end %>
+                  </div>
                 <% end %>
               </article>
             <% end %>
           </div>
 
         <% else %>
-
           <div class="grid">
-            <% @block.template.elements.each do |element| %>
+            <% @block.template.selected_pages.each do |page|
+              next if page.nil?
+            %>
               <article>
-                <%# <%= element %>
+                <h3>
+                  <%= link_to page, page.url %>  
+                </h3>
                 <% if @block.template.show_description %>
-                  <p><%#= element.summary %></p>
+                  <p><%= page.summary %></p>
+                <% end %>
+                <% if @block.template.show_image %>
+                  <div class="media" itemprop="image">
+                    <% if page.featured_image.attached? %>
+                      <%= kamifusen_tag page.featured_image %>
+                    <% end %>
+                  </div>
                 <% end %>
               </article>
             <% end %>
diff --git a/app/views/admin/communication/blocks/templates/partners/_preview.html.erb b/app/views/admin/communication/blocks/templates/partners/_preview.html.erb
index 3d14a837cd01c07bf62f7a736c28b8a6926d2b7a..40246eb957ea8eb7e8cf8f241bc7a295e63eeefd 100644
--- a/app/views/admin/communication/blocks/templates/partners/_preview.html.erb
+++ b/app/views/admin/communication/blocks/templates/partners/_preview.html.erb
@@ -24,44 +24,37 @@
 
       <% if @block.template.layout == "grid" %>
         <div class="organizations grid">
-          <% @block.template.elements.each do |element| %>
-            <article class="organization">
-              <h3>
-                <% if element.best_url %>
-                  <a href="<%= element.best_url %>" target="_blank">
-                <% end %>
-                <%= element.best_name %>
-                <% if element.best_url%>
-                  </a>
-                <% end %>
-              </h3>
-              <div class="media">
-                <%= kamifusen_tag element.best_logo, width: 600%>
-              </div>
-            </article>
-          <% end %>
-        </div>
-      <% else %>
+      <% else # Map %>
         <div class="map" data-marker-icon="<%= image_path 'map-marker.svg' %>">
-          <% @block.template.elements.each do |element| %>
-            <article class="organization" data-latitude="<%= element.organization.latitude %>" data-longitude="<%= element.organization.longitude %>">
-              <h3>
-                <% if element.best_url %>
-                  <a href="<%= element.best_url %>" target="_blank">
-                <% end %>
-                <%= element.best_name %>
-                <% if element.best_url%>
-                  </a>
-                <% end %>
-              </h3>
+          <link   rel="stylesheet" 
+                  href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
+                  integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI="
+                  crossorigin=""/>
+          <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"
+                  integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM="
+                  crossorigin=""></script>
+      <% end %>
+        <% @block.template.elements.each do |element| %>
+          <article  class="organization"
+                    data-latitude="<%= element.organization&.latitude %>"
+                    data-longitude="<%= element.organization&.longitude %>">
+            <h3>
+              <% if element.best_url %>
+                <a href="<%= element.best_url %>" target="_blank">
+              <% end %>
+              <%= element.best_name %>
+              <% if element.best_url%>
+                </a>
+              <% end %>
+            </h3>
+            <% if element.best_logo %>
               <div class="media">
                 <%= kamifusen_tag element.best_logo, width: 600%>
               </div>
-            </article>
-          <% end %>
-        </div>
-      <% end %>
+            <% end %>
+          </article>
+        <% end %>
+      </div>
     </div>
   </div>
 </section>
-
diff --git a/app/views/admin/communication/blocks/templates/posts/_preview.html.erb b/app/views/admin/communication/blocks/templates/posts/_preview.html.erb
index 0d73386078540ecaa076a29d2db3ed8cbbeb7d8f..3e1fa9f7d19e174fb56c77821c69e5b914e47204 100644
--- a/app/views/admin/communication/blocks/templates/posts/_preview.html.erb
+++ b/app/views/admin/communication/blocks/templates/posts/_preview.html.erb
@@ -1,11 +1,13 @@
 <%
-$class = "block block-posts"
+class_name = "block block-posts"
 unless @block.title.blank?
-  $class += " block-with-title"
+  class_name += " block-with-title"
 end
-$class += " block-posts--" + @block.template.layout
+class_name += " block-posts--" + @block.template.layout
+
+date_format = "%e %B %Y"
 %>
-<section class="<%= $class %>">
+<section class="<%= class_name %>">
   <div class="container">
     <div class="block-content">
       <% unless @block.title.blank? %>
@@ -13,26 +15,148 @@ $class += " block-posts--" + @block.template.layout
           <h2><%= @block.title %></h2>
         </div>
       <% end %>
-      <div class="list">
-        <% if @block.data %>
-          <% @block.template.selected_posts.each do |post| %>
-            <article class="post" itemprop="blogPosts" itemscope itemtype="http://schema.org/BlogPosting">
-              <div class="post-content">
-                <a href="#"><%= post %></a>
-                <p itemprop="articleBody"><%= post.summary %></p>
-              </div>
-              <div class="post-meta">
-                <time itemprop="datePublished" datetime="<%= post.published_at %>"><%= post.published_at.to_date %></time>
-              </div>
-              <% if post.best_featured_image.attached? %>
+
+      <% if @block.template.layout ===  "list" %>
+        <div class="list">
+          <% if @block.data %>
+            <% @block.template.selected_posts.each do |post| %>
+              <article class="post" itemprop="blogPosts" itemscope itemtype="http://schema.org/BlogPosting">
+                <div class="post-content">
+                  <h3 itemprop="headline">
+                    <%= link_to post, post.url %>
+                  </h3>
+                  <% if !post.categories.empty? %>
+                    <ul class="post-categories">
+                      <% post.categories.each do |category| %>
+                        <%= link_to category, category.path %>
+                      <% end %>
+                    </ul>
+                  <% end %>
+                  <p itemprop="articleBody"><%= post.summary %></p>
+                  <div class="post-meta">
+                    <time itemprop="datePublished" datetime="<%= post.published_at %>"><%= l(post.published_at, format: date_format) %></time>
+                    <% if post.author.present? %>
+                      <div class="post-author" itemscope itemtype="https://schema.org/Person" itemprop="author">
+                        <p itemprop="name"><%= post.author %></p>
+                      </div>
+                    <% end %>
+                  </div>
+                </div>
                 <div class="media">
-                  <%= kamifusen_tag post.best_featured_image %>
+                  <% if post.best_featured_image.attached? %>
+                    <%= kamifusen_tag post.best_featured_image %>
+                  <% end %>
                 </div>
-              <% end %>
-            </article>
+              </article>
+            <% end %>
           <% end %>
+        </div>
+
+      <% elsif @block.template.layout ===  "highlight"%>
+        <% if @block.data
+          $highlight_post = @block.template.selected_posts.first 
+        %>
+          <div class="highlight">
+            <div class="highlight-post">
+              <article class="post" itemprop="blogPosts" itemscope itemtype="http://schema.org/BlogPosting">
+                <div class="post-content">
+                  <h3 itemprop="headline">
+                    <%= link_to $highlight_post, $highlight_post.url %>
+                  </h3>
+                  <% if !$highlight_post.categories.empty? %>
+                    <ul class="post-categories">
+                      <% $highlight_post.categories.each do |category| %>
+                        <%= link_to category, category.path %>
+                      <% end %>
+                    </ul>
+                  <% end %>
+                  <p itemprop="articleBody"><%= $highlight_post.summary %></p>
+                  <div class="post-meta">
+                    <time itemprop="datePublished" datetime="<%= $highlight_post.published_at %>"><%= l($highlight_post.published_at, format: date_format) %></time>
+                    <% if $highlight_post.author.present? %>
+                      <div class="post-author" itemscope itemtype="https://schema.org/Person" itemprop="author">
+                        <p itemprop="name"><%= $highlight_post.author %></p>
+                      </div>
+                    <% end %>
+                  </div>
+                </div>
+                <div class="media">
+                  <% if $highlight_post.best_featured_image.attached? %>
+                    <%= kamifusen_tag $highlight_post.best_featured_image %>
+                  <% end %>
+                </div>
+              </article>
+            </div>
+              
+            <div class="list">
+              <% @block.template.selected_posts.each do |post|
+                next if post.nil?
+              %>
+                <article class="post" itemprop="blogPosts" itemscope itemtype="http://schema.org/BlogPosting">
+                  <div class="post-content">
+                    <h3 itemprop="headline">
+                      <%= link_to post, post.url %>
+                    </h3>
+                    <% if !post.categories.empty? %>
+                      <ul class="post-categories">
+                        <% post.categories.each do |category| %>
+                          <%= link_to category, category.path %>
+                        <% end %>
+                      </ul>
+                    <% end %>
+                    <p itemprop="articleBody"><%= post.summary %></p>
+                    <div class="post-meta">
+                      <time itemprop="datePublished" datetime="<%= post.published_at %>"><%= l(post.published_at, format: date_format) %></time>
+                      <% if post.author.present? %>
+                        <div class="post-author" itemscope itemtype="https://schema.org/Person" itemprop="author">
+                          <p itemprop="name"><%= post.author %></p>
+                        </div>
+                      <% end %>
+                    </div>
+                  </div>
+                </article>
+              <% end %>
+            </div>
+          </div>
         <% end %>
-      </div>
+      <% else %>
+        <div class="grid">
+          <% if @block.data %>
+            <% @block.template.selected_posts.each do |post|
+              next if post.nil?
+            %>
+              <article class="post" itemprop="blogPosts" itemscope itemtype="http://schema.org/BlogPosting">
+                <div class="post-content">
+                  <h3 itemprop="headline">
+                    <%= link_to post, post.url %>
+                  </h3>
+                  <% if !post.categories.empty? %>
+                    <ul class="post-categories">
+                      <% post.categories.each do |category| %>
+                        <%= link_to category, category.path %>
+                      <% end %>
+                    </ul>
+                  <% end %>
+                  <p itemprop="articleBody"><%= post.summary %></p>
+                  <div class="post-meta">
+                    <time itemprop="datePublished" datetime="<%= post.published_at %>"><%= l(post.published_at, format: date_format) %></time>
+                    <% if post.author.present? %>
+                      <div class="post-author" itemscope itemtype="https://schema.org/Person" itemprop="author">
+                        <p itemprop="name"><%= post.author %></p>
+                      </div>
+                    <% end %>
+                  </div>
+                </div>
+                <div class="media">
+                  <% if post.best_featured_image.attached? %>
+                    <%= kamifusen_tag post.best_featured_image %>
+                  <% end %>
+                </div>
+              </article>
+            <% end %>
+          <% end %>
+        </div>
+      <% end %>
     </div>
   </div>
 </section>
\ No newline at end of file
diff --git a/app/views/admin/communication/blocks/templates/posts/_static.html.erb b/app/views/admin/communication/blocks/templates/posts/_static.html.erb
index c88c34d54acdc9ebd95ba3f82b357d9abb596cc4..167e7e361c87955622b10570652ec5db3a6da0fa 100644
--- a/app/views/admin/communication/blocks/templates/posts/_static.html.erb
+++ b/app/views/admin/communication/blocks/templates/posts/_static.html.erb
@@ -1,4 +1,6 @@
-<% if block.template.category %>
+<% if block.template.mode == 'all' %>
+      all: true
+<% elsif block.template.mode == 'category' && block.template.category %>
       category: "<%= block.template.category.path %>"
 <% end %>
       layout: <%= block.template.layout %>
diff --git a/app/views/admin/communication/blocks/templates/programs/_preview.html.erb b/app/views/admin/communication/blocks/templates/programs/_preview.html.erb
index adda2a10c96ddae8c77b783cac0f71414345269d..6abdb0c738143387ec6cb657c7c6b9aa22c9145f 100644
--- a/app/views/admin/communication/blocks/templates/programs/_preview.html.erb
+++ b/app/views/admin/communication/blocks/templates/programs/_preview.html.erb
@@ -1,16 +1,27 @@
 <%
-$class = "block block-programs"
+class_name = "block block-programs"
 unless @block.title.blank?
-  $class += " block-with-title"
+  class_name += " block-with-title"
 end
 %>
-<section class="<%= $class %>" style="display: none;">
+
+<section class="<%= class_name %>">
   <div class="container">
     <div class="block-content">
+      <% unless @block.title.blank? %>
+        <div class="top">
+          <% unless @block.title.blank? %>
+            <h2><%= @block.title %></h2>
+          <% end %>
+        </div>
+      <% end %>
       <ol class="programs">
-        <% @block.template.selected_programs.each do |program| %>
+        <% @block.template.elements.each do |element|
+          program = element.program
+          next if program.nil?
+        %>
           <li>
-            <%= program %></p>
+            <%= link_to program, [:admin, program] %>  
           </li>
         <% end %>
       </ol>
diff --git a/app/views/admin/communication/websites/categories/_form.html.erb b/app/views/admin/communication/websites/categories/_form.html.erb
index 0769ed9a26c14933cbdd34e5b2854e2cfb62e2e1..44bb161df365955ebd7e93b56af6c48629675c52 100644
--- a/app/views/admin/communication/websites/categories/_form.html.erb
+++ b/app/views/admin/communication/websites/categories/_form.html.erb
@@ -4,37 +4,25 @@
 
   <div class="row">
     <div class="col-md-8">
-      <div class="card flex-fill w-100">
-        <div class="card-header">
-          <h5 class="card-title mb-0"><%= t('content') %></h5>
-        </div>
-        <div class="card-body">
-          <%= f.input :name %>
-          <%= render 'admin/application/summary/form', f: f, about: category %>
-        </div>
-      </div>
+      <%= osuny_panel t('content') do %>
+        <%= f.input :name %>
+        <%= render 'admin/application/summary/form', f: f, about: category %>
+      <% end %>
       <%= render 'admin/application/meta_description/form', f: f, about: category %>
     </div>
     <div class="col-md-4">
-      <div class="card flex-fill w-100">
-        <div class="card-header">
-          <h5 class="card-title mb-0"><%= t('metadata') %></h5>
-        </div>
-        <div class="card-body">
-          <%= f.input :slug,
-                      as: :string,
-                      input_html: category.persisted? ? {} : {
-                        class: 'js-slug-input',
-                        data: { source: '#communication_website_category_name' }
-                      } %>
-          <%= f.association :parent,
-                            collection: collection_tree(@website.categories.for_language(current_website_language), category),
-                            label_method: ->(p) { sanitize p[:label] },
-                            value_method: ->(p) { p[:id] } %>
-          <ul>
-          </ul>
-        </div>
-      </div>
+      <%= osuny_panel t('metadata') do %>
+        <%= f.input :slug,
+                    as: :string,
+                    input_html: category.persisted? ? {} : {
+                      class: 'js-slug-input',
+                      data: { source: '#communication_website_category_name' }
+                    } %>
+        <%= f.association :parent,
+                          collection: collection_tree(@website.categories.for_language(current_website_language), category),
+                          label_method: ->(p) { sanitize p[:label] },
+                          value_method: ->(p) { p[:id] } %>
+      <% end %>
       <%= render 'admin/application/featured_image/edit', about: category, f: f %>
     </div>
   </div>
diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb
index 0fefbb6fb51ef980e4dc8affc00113c1c3edafd6..e067d7978729903f04eda3bb49b8835ec5664efc 100644
--- a/app/views/admin/dashboard/index.html.erb
+++ b/app/views/admin/dashboard/index.html.erb
@@ -14,14 +14,14 @@
     <%= osuny_panel t('hello', name: current_user.first_name) do %>
       <% if current_admin_theme == 'appstack' %>
         <p>
-          Osuny a un nouveau thĆØme pour l'administration, "Pure". <br>
-          Voulez-vous l'essayer ?
+          Osuny a un nouveau thĆØme pour l'administration, "Pure".
+          Voulez-vous l'essayerĀ ?
         </p>
         <%= link_to 'Changer de thĆØme', admin_set_theme_path(theme: 'pure'), method: :put %>
       <% else %>
         <p>
-          Vous utilisez le thĆØme "Pure" pour l'administration. <br>
-          Voulez-vous revenir au thĆØme "Appstack" ?
+          Vous utilisez le thĆØme "Pure" pour l'administration.
+          Voulez-vous revenir au thĆØme "Appstack"Ā ?
         </p>
         <%= link_to 'Changer de thĆØme', admin_set_theme_path(theme: 'appstack'), method: :put %>
       <% end %>
@@ -143,9 +143,6 @@
           <%= link_to t("#{term}"), t("#{term}_url"), target: '_blank', rel: 'noreferrer' %>
         </li>
       <% end %>
-      <li class="list-inline-item">
-        <%= link_to t('cookies_consent_choice'), '', class: 'js-gdpr__cookie_consent__display_again' %>
-      </li>
     </ul>
   </div>
 <% end %>
diff --git a/app/views/admin/layouts/themes/_appstack.html.erb b/app/views/admin/layouts/themes/_appstack.html.erb
index 331f7b38364abd8c6d9a9b7a46c14fb087a0b5b0..96264a0a66990ef247ed2d5b1fc2a8604521e200 100644
--- a/app/views/admin/layouts/themes/_appstack.html.erb
+++ b/app/views/admin/layouts/themes/_appstack.html.erb
@@ -27,7 +27,6 @@
     </div>
   </div>
   <%= javascript_include_tag 'admin/appstack' %>
-  <%= render 'gdpr/cookie_consent' %>
   <%= render 'bugsnag' %>
   <%= render 'summernote_localization' %>
 </body>
diff --git a/app/views/admin/layouts/themes/_pure.html.erb b/app/views/admin/layouts/themes/_pure.html.erb
index 965029888465752746d75e9336121af843a8d2e2..b6e54cbba664e91fd9a116ee58858d6cf35de1f0 100644
--- a/app/views/admin/layouts/themes/_pure.html.erb
+++ b/app/views/admin/layouts/themes/_pure.html.erb
@@ -20,7 +20,6 @@
   <%= render "admin/layouts/themes/pure/commands" %>
   <%= render "admin/layouts/themes/pure/footer" %>
   <%= javascript_include_tag 'admin/pure' %>
-  <%= render 'gdpr/cookie_consent' %>
   <%= render 'bugsnag' %>
   <%= render 'summernote_localization' %>
 </body>
diff --git a/app/views/admin/layouts/themes/pure/_nav.html.erb b/app/views/admin/layouts/themes/pure/_nav.html.erb
index 4e18def30d29207c87a930a3a8bddd84ffb88eac..5c34176bea861792e4764b0e404761586af15e82 100644
--- a/app/views/admin/layouts/themes/pure/_nav.html.erb
+++ b/app/views/admin/layouts/themes/pure/_nav.html.erb
@@ -42,7 +42,7 @@ context ||= :admin
     <div class="container-fluid">
       <div class="row">
         <%= render_navigation context: context, renderer: Osuny::SimpleNavigationRenderer %>
-        <div class="col-md-4 col-lg-3">
+        <div class="col-sm-6 col-md-4 col-lg-3">
           <% 
           if current_user.picture.attached? && current_user.picture.variable? 
             image = current_user.picture.variant(resize: '200x200')
diff --git a/app/views/admin/research/journals/papers/_form.html.erb b/app/views/admin/research/journals/papers/_form.html.erb
index 03c61a270834052d812d2743a72f9ded8a4000ba..b3cd705d9edf20280240a5f00234c8fbfef3cd9c 100644
--- a/app/views/admin/research/journals/papers/_form.html.erb
+++ b/app/views/admin/research/journals/papers/_form.html.erb
@@ -6,6 +6,7 @@
     <div class="col-md-8">
       <%= osuny_panel t('content') do %>
         <%= f.input :title, as: :text, input_html: { rows: 3 } %>
+        <%= f.input :summary %>
         <%= f.input :abstract, as: :text, input_html: { rows: 8 } %>
         <%= f.input :pdf %>
         <%= f.input :doi %>
diff --git a/app/views/admin/university/organizations/show.html.erb b/app/views/admin/university/organizations/show.html.erb
index ce72a0a5eba056cb9631625eddb7b0a3cd7be6b8..dccb51446de3a8d610129d8c7ca84e5fe53b5be7 100644
--- a/app/views/admin/university/organizations/show.html.erb
+++ b/app/views/admin/university/organizations/show.html.erb
@@ -104,12 +104,16 @@
 
     <%= osuny_panel t('university.organization.logo') do %>
       <% if @organization.logo.attached? %>
-        <%= osuny_label University::Organization.human_attribute_name('logo') %><br>
-        <%= kamifusen_tag @organization.logo, class: 'img-fluid img-fill bg-light img-thumbnail p-5 mb-3' %>
+        <div>
+          <%= osuny_label University::Organization.human_attribute_name('logo') %><br>
+          <%= kamifusen_tag @organization.logo, class: 'img-fluid img-fill bg-light img-thumbnail p-5 mb-3' %>
+        </div>
       <% end %>
       <% if @organization.logo_on_dark_background.attached? %>
-        <%= osuny_label University::Organization.human_attribute_name('logo_on_dark_background') %><br>
-        <%= kamifusen_tag @organization.logo_on_dark_background, class: 'img-fluid img-fill bg-dark img-thumbnail p-5' %>
+        <div>
+          <%= osuny_label University::Organization.human_attribute_name('logo_on_dark_background') %><br>
+          <%= kamifusen_tag @organization.logo_on_dark_background, class: 'img-fluid img-fill bg-dark img-thumbnail p-5' %>
+        </div>
       <% end %>
     <% end if @organization.logo.attached? || @organization.logo_on_dark_background.attached? %>
 
diff --git a/app/views/api/layouts/application.html.erb b/app/views/api/layouts/application.html.erb
index 29a6ebea36ce213ade8cdce3702a2260918f7191..1053513aec3a9f60eb18b13556c008ad70ed8bce 100644
--- a/app/views/api/layouts/application.html.erb
+++ b/app/views/api/layouts/application.html.erb
@@ -23,7 +23,6 @@
       <%= yield %>
     </main>
     <%= render 'footer' %>
-    <%= render 'gdpr/cookie_consent' %>
     <%= render 'bugsnag' %>
   </body>
 </html>
diff --git a/app/views/application/_footer.html.erb b/app/views/application/_footer.html.erb
index 530f5a966f9b8b3f19be1e545ee2823448ba94cd..abd05f1f855f16846bd4eefb57306e8110e027fc 100644
--- a/app/views/application/_footer.html.erb
+++ b/app/views/application/_footer.html.erb
@@ -1,27 +1,24 @@
-<footer class="pt-5">
-  <div class="container text-center">
-    <div class="mb-5">
+<footer class="mt-5 pt-5">
+  <div class="container">
+    <div class="mb-5 text-lg-center">
       <%= image_tag 'osuny-black.svg', width: 80 %>
     </div>
-    <nav class="nav justify-content-center">
+    <nav class="nav small d-block d-lg-flex justify-content-lg-center">
       <%= link_to t('terms_of_service'),
                   t('terms_of_service_url'),
-                  class: 'nav-link',
+                  class: 'nav-link ps-0',
                   target: '_blank',
                   rel: 'noreferrer' %>
       <%= link_to t('privacy_policy'),
                   t('privacy_policy_url'),
-                  class: 'nav-link',
+                  class: 'nav-link ps-0',
                   target: '_blank',
                   rel: 'noreferrer' %>
       <%= link_to t('cookies_policy'),
                   t('cookies_policy_url'),
-                  class: 'nav-link',
+                  class: 'nav-link ps-0',
                   target: '_blank',
                   rel: 'noreferrer' %>
-      <%= link_to t('cookies_consent_choice'),
-                  '',
-                  class: 'nav-link js-gdpr__cookie_consent__display_again' %>
     </nav>
   </div>
 </footer>
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb
index 08194d186dc16e87a888231f500e9a43dee4a33e..5bb6d917bb75447f5ad32f404aceade0130ceabe 100644
--- a/app/views/devise/registrations/edit.html.erb
+++ b/app/views/devise/registrations/edit.html.erb
@@ -4,7 +4,7 @@
   <%= f.error_notification %>
 
   <div class="row">
-    <div class="col-md-4">
+    <div class="col-lg-6">
       <%= f.input :email, required: true %>
       <%= f.input :first_name,
                   required: true,
@@ -13,15 +13,12 @@
       <%= f.input :last_name,
                   required: true,
                   input_html: { autocomplete: "last_name" } %>
-    </div>
-    <div class="col-md-4">
       <%= f.association :language,
                         include_blank: false,
                         label_method: lambda { |l| t("languages.#{l.iso_code.to_s}") } %>
-      <%= f.input :mobile_phone %>
       <%= f.input :admin_theme, include_blank: false %>
     </div>
-    <div class="col-md-4">
+    <div class="col-lg-6">
       <%= f.input :password,
                   as: :password_with_hints,
                   allow_password_uncloaking: true,
@@ -35,6 +32,7 @@
                   hint: t(".leave_blank_if_you_don_t_want_to_change_it"),
                   required: false,
                   input_html: { autocomplete: "new-password" } %>
+      <%= f.input :mobile_phone %>
       <%= f.input :picture,
                   as: :single_deletable_file,
                   input_html: { accept: '.jpg,.jpeg,.png,.svg' },
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb
index 34db524df5ac26e9ec94e93f7afb440ab5d695eb..cc85287f0ac19054fb0b051522e7b1684be16a12 100644
--- a/app/views/devise/registrations/new.html.erb
+++ b/app/views/devise/registrations/new.html.erb
@@ -6,7 +6,7 @@
   <%= f.error_notification %>
 
   <div class="row">
-    <div class="col-md-6">
+    <div class="col-lg-6">
       <%= f.input :email,
                   required: true,
                   input_html: { autocomplete: "email" } %>
@@ -22,7 +22,7 @@
                         label_method: lambda { |l| t("languages.#{l.iso_code.to_s}") },
                         include_blank: :translate %>
     </div>
-    <div class="col-md-6">
+    <div class="col-lg-6">
       <%= f.input :password,
                   as: :password_with_hints,
                   required: true,
diff --git a/app/views/devise/two_factor_authentication/show.html.erb b/app/views/devise/two_factor_authentication/show.html.erb
index 964870ab3d7a2c78b0fa0198938d3c72bffb8b79..e200bb69b183fa9beed2eb58dd97691d74c7f1c1 100644
--- a/app/views/devise/two_factor_authentication/show.html.erb
+++ b/app/views/devise/two_factor_authentication/show.html.erb
@@ -1,6 +1,8 @@
 <%= content_for :title, t('.title') %>
 
-<h4>
+<h4 class="mb-5"><%= t('.title') %></h4>
+
+<p>
   <% if resource.direct_otp %>
     <% if resource.direct_otp_delivery_method == 'mobile_phone' %>
       <%= t('devise.two_factor_authentication.enter_code_direct_otp_mobile_phone', phone: masked_phone(resource.mobile_phone)) %>
@@ -10,42 +12,37 @@
   <% else %>
     <%= t('devise.two_factor_authentication.enter_code_totp') %>
   <% end %>
-</h4>
+</p>
 
 <%= simple_form_for(resource, url: user_two_factor_authentication_path, html: { method: :put, class: 'my-3' }) do |f| %>
-  <div class="row">
-    <div class="col-md-6">
-      <div class="form-inputs">
-        <div class="form-group required mt-0">
-          <%= text_field_tag :code,
-                              '',
-                              type: 'tel',
-                              pattern: '\d*',
-                              required: true,
-                              autofocus: true,
-                              autocomplete: 'off',
-                              class: 'form-control string required'%>
-          <p class="mt-2 mb-0">
-            <% if resource.direct_otp %>
-              <%= link_to t('devise.two_factor_authentication.resend_code'), [:resend_code, resource_name, :two_factor_authentication] %>
-            <% else %>
-              <%= link_to t('devise.two_factor_authentication.send_code_instead'), [:resend_code, resource_name, :two_factor_authentication] %>
-            <% end %>
-            <% unless resource.mobile_phone.blank? # when phone is blank default code method is already :email so we don't need another link %>
-              ā€¢ <%= link_to t('devise.two_factor_authentication.send_email_code'), [:resend_code, resource_name, :two_factor_authentication, delivery_method: :email] %>
-            <% end %>
-          </p>
-        </div>
-      </div>
-    </div>
-    <div class="col-md-6">
-      <%= link_to t('devise.shared.links.sign_out'),
-                  destroy_user_session_path,
-                  method: :delete,
-                  class: "btn btn-danger float-end" %>
+  <div class="form-inputs">
+    <div class="input-group required mt-0" style="max-width: 400px">
+      <%= text_field_tag :code,
+                          '',
+                          type: 'tel',
+                          pattern: '\d*',
+                          required: true,
+                          autofocus: true,
+                          autocomplete: 'off',
+                          class: 'form-control string required'%>
       <%= f.button  :submit,
                     t('devise.two_factor_authentication.validate'),
                     class: "btn btn-primary" %>
     </div>
+    <p class="mt-4 mb-5">
+      <% if resource.direct_otp %>
+        <%= link_to t('devise.two_factor_authentication.resend_code'), [:resend_code, resource_name, :two_factor_authentication] %>
+      <% else %>
+        <%= link_to t('devise.two_factor_authentication.send_code_instead'), [:resend_code, resource_name, :two_factor_authentication] %>
+      <% end %>
+      <% unless resource.mobile_phone.blank? # when phone is blank default code method is already :email so we don't need another link %>
+        ā€¢ <%= link_to t('devise.two_factor_authentication.send_email_code'), [:resend_code, resource_name, :two_factor_authentication, delivery_method: :email] %>
+      <% end %>
+    </p>
   </div>
 <% end %>
+
+<%= link_to t('devise.shared.links.sign_out'),
+            destroy_user_session_path,
+            method: :delete,
+            class: "btn btn-outline-danger" %>
diff --git a/app/views/extranet/application/_footer.html.erb b/app/views/extranet/application/_footer.html.erb
index 90c6d199b9fba77c8d58b1e512e88671c735f37f..6f774e2449b0e57f4d82608656fcf81e1aa8dd3c 100644
--- a/app/views/extranet/application/_footer.html.erb
+++ b/app/views/extranet/application/_footer.html.erb
@@ -45,10 +45,6 @@ about = current_extranet.about
                     target: '_blank',
                     rel: 'noreferrer' if current_extranet.has_cookies_policy? %>
         <%= link_to t('extranet.data'), data_path %>
-        <%= link_to t('cookies_consent_choice'),
-                    '',
-                    class: 'js-gdpr__cookie_consent__display_again' %>
-                  
         <%= t 'extranet.osuny_html' %>
       </nav>
     </div>
diff --git a/app/views/extranet/gdpr/_cookie_consent.html.erb b/app/views/extranet/gdpr/_cookie_consent.html.erb
deleted file mode 100644
index 3a1cfe18e18ad1aff4e2fffef35dbf87a7a0dfd3..0000000000000000000000000000000000000000
--- a/app/views/extranet/gdpr/_cookie_consent.html.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-<div class="gdpr__cookie_consent js-gdpr__cookie_consent">
-  <div class="gdpr__cookie_consent__text">
-    <%= t('gdpr.cookie_consent.text') %>
-    <%= t('gdpr.cookie_consent.learn_more_html', link: cookies_policy_path) %>
-  </div>
-  <div class="gdpr__cookie_consent__buttons">
-    <button class="gdpr__cookie_consent__buttons__ok js-gdpr__cookie_consent__buttons__ok btn btn-primary btn-sm btn-xs"> <%= t('gdpr.cookie_consent.button_ok') %></button>
-    <button class="gdpr__cookie_consent__buttons__ko js-gdpr__cookie_consent__buttons__ko btn btn-primary btn-sm btn-xs"> <%= t('gdpr.cookie_consent.button_ko') %></button>
-  </div>
-</div>
\ No newline at end of file
diff --git a/app/views/extranet/layouts/application.html.erb b/app/views/extranet/layouts/application.html.erb
index 8b20a64470b47db0a214116ba3c18c4e12a065a1..ccad435576b5eec0aea0abed45ea8745def16a78 100644
--- a/app/views/extranet/layouts/application.html.erb
+++ b/app/views/extranet/layouts/application.html.erb
@@ -11,7 +11,6 @@
       <%= yield %>
     </main>
     <%= render 'extranet/application/footer' %>
-    <%= render 'extranet/gdpr/cookie_consent' %>
     <%= render 'bugsnag' %>
     <%= javascript_include_tag 'extranet' %>
     <script src="https://example.osuny.org/js/extranet.js"></script>
diff --git a/app/views/extranet/layouts/devise.html.erb b/app/views/extranet/layouts/devise.html.erb
index f94aae6fee43e9285b0b021aa7c4ae9b779a4464..69ff30bdc1c2f7817e2ff0782323af130aab3621 100644
--- a/app/views/extranet/layouts/devise.html.erb
+++ b/app/views/extranet/layouts/devise.html.erb
@@ -24,12 +24,10 @@
               <%= link_to t('terms_of_service'), terms_path, rel: 'noreferrer' if current_extranet.has_terms? %>
               <%= link_to t('privacy_policy'), privacy_policy_path, rel: 'noreferrer' if current_extranet.has_privacy_policy? %>
               <%= link_to t('cookies_policy'), cookies_policy_path, rel: 'noreferrer' if current_extranet.has_cookies_policy? %>
-              <%= link_to t('cookies_consent_choice'), '', class: 'js-gdpr__cookie_consent__display_again' %>
           </footer>
         </div>
       </div>
     </main>
-    <%= render 'extranet/gdpr/cookie_consent' %>
     <%= render 'bugsnag' %>
     <%= javascript_include_tag 'extranet' %>
   </body>
diff --git a/app/views/extranet/posts/posts/show.html.erb b/app/views/extranet/posts/posts/show.html.erb
index df92a2c85ad8cf28f31d9afac3f6801ebb902505..f661b233a85ae883efbb47eb52f1b0eae4c09edf 100644
--- a/app/views/extranet/posts/posts/show.html.erb
+++ b/app/views/extranet/posts/posts/show.html.erb
@@ -1,20 +1,32 @@
-<% content_for :title, @post %>
-
-<% content_for :header_right do %>
-  <% if @post.featured_image.attached? %>
-    <figure <% if @post.featured_image_credit.present? %>class="with-credit"<% end %>>
-      <%= kamifusen_tag @post.featured_image, class: 'img-fluid', width: 300 %>
-      <% if @post.featured_image_credit.present? %>
-        <figcaption tabindex="0">
-            <%= sanitize @post.featured_image_credit %>
-        </figcaption>
+<% content_for :header do %>
+  <div class="row">
+    <div class="header__info col-md-8">
+      <h1><%= @post %></h1>
+      <p class="small">
+        <% if @post.published_at %>
+          PubliƩ le <%= l @post.published_at.to_date, format: :long %>
+        <% end %>
+        <% if @post.category %>
+          <br>
+          Dans : <%= link_to @post.category, posts_category_path(slug: @post.category.slug), class: "link" %>
+        <% end %>
+      </p>
+    </div>
+    <div class="col-md-4">
+      <% if @post.featured_image.attached? %>
+        <figure class="<% if @post.featured_image_credit.present? %>with-credit<% end %>">
+          <%= kamifusen_tag @post.featured_image, class: 'img-fluid', width: 300 %>
+          <% if @post.featured_image_credit.present? %>
+            <figcaption tabindex="0">
+                <%= sanitize @post.featured_image_credit %>
+            </figcaption>
+          <% end %>
+        </figure>
       <% end %>
-    </figure>
-  <% end %>
+    </div>
+  </div>
 <% end %>
 
-<%= link_to @post.category, posts_category_path(slug: @post.category.slug) if @post.category %>
-
 </main>
 <%= render 'admin/communication/blocks/preview', about: @post %>
 <main>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index d7a2782bd31e89f40eb05cc1090e87caeb0b5ed7..fa8ec3ad26ada8cec7b7640605039a938125412a 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -17,7 +17,6 @@
       <%= yield %>
     </main>
     <%= render 'footer' %>
-    <%= render 'gdpr/cookie_consent' %>
     <%= render 'bugsnag' %>
     <%= render 'summernote_localization' %>
   </body>
diff --git a/app/views/layouts/devise.html.erb b/app/views/layouts/devise.html.erb
index 6c3eb195df94f507849be3b7c6073a7cf27339bc..09a94714b1c8e7954b3b9da2e3093fe40d5a9bf9 100644
--- a/app/views/layouts/devise.html.erb
+++ b/app/views/layouts/devise.html.erb
@@ -12,29 +12,20 @@
   </head>
   <body class="<%= body_classes %>">
     <div class="container">
-      <div class="row">
-        <div class="col-sm-10 mx-auto">
-          <h1 class="my-5 py-5 text-center">
-            <%= link_to root_path do %>
-              <%= render 'logo' %>
-            <% end %>
-          </h1>
-          <div class="card">
-            <div class="card-body text-start">
-              <% unless notice.blank? %>
-                <div class="alert alert-success mt-2" role="alert"><%= notice.html_safe %></div>
-              <% end %>
-              <% unless alert.blank? %>
-                <div class="alert alert-danger mt-2" role="alert"><%= alert.html_safe %></div>
-              <% end %>
-              <%= yield %>
-            </div>
-          </div>
-        </div>
-      </div>
+      <h1 class="my-5 py-5 text-center">
+        <%= link_to root_path do %>
+          <%= render 'logo' %>
+        <% end %>
+      </h1>
+      <% unless notice.blank? %>
+        <div class="alert alert-success mt-2" role="alert"><%= notice.html_safe %></div>
+      <% end %>
+      <% unless alert.blank? %>
+        <div class="alert alert-danger mt-2" role="alert"><%= alert.html_safe %></div>
+      <% end %>
+      <%= yield %>
     </div>
     <%= render 'footer' %>
-    <%= render 'gdpr/cookie_consent' %>
     <%= render 'bugsnag' %>
     <%= render 'summernote_localization' %>
   </body>
diff --git a/config/locales/communication/en.yml b/config/locales/communication/en.yml
index 0267ffb5ead36d617b465409ab5aa2d35c5ed0f9..b44050096f649c85683cbc2db8abe6fd118731ff 100644
--- a/config/locales/communication/en.yml
+++ b/config/locales/communication/en.yml
@@ -373,6 +373,27 @@ en:
                 label: Iframe title (mandatory for accessibility)
                 placeholder: Enter the title
               warning: Beware, the code below is used as is, without any security filtering. Be extremely careful and never integrate possibly unreliable code.
+          features:
+            description: A list of features with images (often icons) and a description.
+            edit:
+              add_element: Add feature
+              remove_element: Remove feature
+              element:
+                image:
+                  label: Image (.png, .jpg, .svg)
+                  remove: Remove image
+                alt:
+                  label: Alternative text
+                  placeholder: Enter text description
+                credit:
+                  label: Credit
+                  placeholder: Enter image's credit here
+                title:
+                  label: Title
+                  placeholder: Enter title here
+                description:
+                  label: Text
+                  placeholder: Enter text here
           files:
             description: A list of downloadable files, mentioning their file size.
             edit:
@@ -458,6 +479,7 @@ en:
               cards:
                 label: Cards
                 description: Side by side cards, same height, strong color on rollover. Perfect for a small amount of pages.
+                more: Show more
               grid:
                 label: Grid
                 description: As a grid, left to right and top to bottom.
@@ -532,6 +554,9 @@ en:
               list:
                 label: List
                 description: A list of posts with small images the same width.
+              alternate:
+                label: Alternate
+                description: Posts either on the left or on the right, composing a graphically light page.
             edit:
               add_post: Add post
               mode:
@@ -718,6 +743,7 @@ en:
           datatable: Table
           definitions: Definitions
           embed: HTML embed
+          features: Features
           files: Files
           gallery: Gallery
           image: Image
diff --git a/config/locales/communication/fr.yml b/config/locales/communication/fr.yml
index 8794d71d438a5a77160f4ae71a1627a0e362ad1a..4d98d28791c099cf4fbcf224f3c6a77b63621cec 100644
--- a/config/locales/communication/fr.yml
+++ b/config/locales/communication/fr.yml
@@ -373,6 +373,27 @@ fr:
                 label: Titre de l'iframe (nƩcessaire pour l'accessibilitƩ)
                 placeholder: Entrer le titre
               warning: Attention, le code ci-dessous est intƩgrƩ tel quel, sans filtrage de sƩcuritƩ. N'intƩgrez jamais de code externe dont la fiabilitƩ n'est pas certaine.
+          features:
+            description: Une liste de fonctionnalitƩs avec des images, par exemple des icƴnes, et une description.
+            edit:
+              add_element: Ajouter une fonctionnalitƩ
+              remove_element: Supprimer la fonctionnalitƩ
+              element:
+                image:
+                  label: Image (.png, .jpg, .svg)
+                  remove: Enlever l'image
+                alt:
+                  label: Texte alternatif
+                  placeholder: Entrer la description textuelle
+                credit:
+                  label: CrƩdit
+                  placeholder: Entrer le crƩdit de l'image ici
+                title:
+                  label: Titre
+                  placeholder: Entrer le titre de la fonctionnalitƩ
+                description:
+                  label: Texte
+                  placeholder: Entrer la description de la fonctionnalitƩ
           files:
             description: Une liste de fichiers tƩlƩchargeables, prƩsentƩs avec leur poids.
             edit:
@@ -458,6 +479,7 @@ fr:
               cards:
                 label: Cartes
                 description: Des cartes cĆ´te Ć  cĆ´te, de mĆŖme hauteur, avec une couleur forte au survol. Ce format est idĆ©al pour un petit nombre de pages.
+                more: En savoir plus
               grid:
                 label: Grille
                 description: Les pages sont prƩsentƩes en grille, de gauche Ơ droite puis de haut en bas.
@@ -532,6 +554,9 @@ fr:
               list:
                 label: Liste
                 description: Une liste d'articles avec de petites images Ć  la mĆŖme largeur.
+              alternate:
+                label: Alternance
+                description: Une alternance trĆØs lĆ©gĆØre graphiquement d'articles entre la gauche et la droite de la page.
             edit:
               add_post: Ajouter un article
               mode:
@@ -718,6 +743,7 @@ fr:
           datatable: Tableau
           definitions: DĆ©finitions
           embed: IntƩgration HTML
+          features: FonctionnalitƩs
           files: Fichiers
           gallery: Galerie
           image: Image
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 91169d4178a01d62dd536c08878967686b2177a3..551e5fb3b8104a64070910aec3a0232cf3e89bd9 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -201,8 +201,6 @@ en:
     empty: Folder is empty
     open: Open folder
     close: Close folder
-  gdpr:
-    privacy_policy: https://osuny.org/politique-de-confidentialite
   hello: "Hello %{name}!"
   home: Home
   imports:
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index d978aafc97311eaa4f3f2004d0d1bf71dbb4ed48..1fc37088e5c8ff952d9534c617e994326808d2da 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -201,8 +201,6 @@ fr:
     empty: Le dossier est vide
     open: Ouvrir le dossier
     close: Fermer le dossier
-  gdpr:
-    privacy_policy: https://osuny.org/politique-de-confidentialite
   hello: "Bonjour %{name}Ā !"
   home: Accueil
   imports:
diff --git a/db/schema.rb b/db/schema.rb
index 024cee19b2ef8cefd8186932d3216cfcb3fae815..320e151e6fb186a723a9866d48b3d63c7a50ff5c 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -16,7 +16,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
   enable_extension "plpgsql"
   enable_extension "unaccent"
 
-  create_table "action_text_rich_texts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "action_text_rich_texts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name", null: false
     t.text "body"
     t.string "record_type", null: false
@@ -26,7 +26,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
   end
 
-  create_table "active_storage_attachments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "active_storage_attachments", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name", null: false
     t.string "record_type", null: false
     t.uuid "record_id", null: false
@@ -36,7 +36,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
   end
 
-  create_table "active_storage_blobs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "active_storage_blobs", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "key", null: false
     t.string "filename", null: false
     t.string "content_type"
@@ -50,13 +50,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_active_storage_blobs_on_university_id"
   end
 
-  create_table "active_storage_variant_records", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "active_storage_variant_records", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "blob_id", null: false
     t.string "variation_digest", null: false
     t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
   end
 
-  create_table "administration_qualiopi_criterions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "administration_qualiopi_criterions", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.integer "number"
     t.text "name"
     t.text "description"
@@ -64,7 +64,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.datetime "updated_at", null: false
   end
 
-  create_table "administration_qualiopi_indicators", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "administration_qualiopi_indicators", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "criterion_id", null: false
     t.integer "number"
     t.text "name"
@@ -78,7 +78,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["criterion_id"], name: "index_administration_qualiopi_indicators_on_criterion_id"
   end
 
-  create_table "communication_blocks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_blocks", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "about_type"
     t.uuid "about_id"
@@ -175,7 +175,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_communication_extranet_posts_on_university_id"
   end
 
-  create_table "communication_extranets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_extranets", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.uuid "university_id", null: false
     t.string "host"
@@ -203,11 +203,12 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.text "home_sentence"
     t.text "sass"
     t.text "css"
+    t.boolean "allow_experiences_modification", default: true
     t.index ["about_type", "about_id"], name: "index_communication_extranets_on_about"
     t.index ["university_id"], name: "index_communication_extranets_on_university_id"
   end
 
-  create_table "communication_website_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "name"
@@ -256,7 +257,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "index_communication_website_connections_on_website_id"
   end
 
-  create_table "communication_website_git_files", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_git_files", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "previous_path"
     t.string "about_type", null: false
     t.uuid "about_id", null: false
@@ -268,7 +269,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "index_communication_website_git_files_on_website_id"
   end
 
-  create_table "communication_website_imported_authors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_authors", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "author_id"
@@ -284,7 +285,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "idx_communication_website_imported_auth_on_website"
   end
 
-  create_table "communication_website_imported_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "category_id"
@@ -302,7 +303,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "idx_communication_website_imported_cat_on_website"
   end
 
-  create_table "communication_website_imported_media", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_media", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "identifier"
     t.jsonb "data"
     t.text "file_url"
@@ -317,7 +318,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "index_communication_website_imported_media_on_website_id"
   end
 
-  create_table "communication_website_imported_pages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_pages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "page_id"
@@ -341,7 +342,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "index_communication_website_imported_pages_on_website_id"
   end
 
-  create_table "communication_website_imported_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_posts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "post_id"
@@ -366,7 +367,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "index_communication_website_imported_posts_on_website_id"
   end
 
-  create_table "communication_website_imported_websites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_websites", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.integer "status", default: 0
@@ -376,7 +377,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "index_communication_website_imported_websites_on_website_id"
   end
 
-  create_table "communication_website_menu_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_menu_items", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "menu_id", null: false
@@ -396,7 +397,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "index_communication_website_menu_items_on_website_id"
   end
 
-  create_table "communication_website_menus", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_menus", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "title"
@@ -412,7 +413,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_communication_website_menus_on_university_id"
   end
 
-  create_table "communication_website_pages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_pages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "title"
@@ -458,7 +459,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["website_id"], name: "index_communication_website_permalinks_on_website_id"
   end
 
-  create_table "communication_website_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_posts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "title"
@@ -484,7 +485,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_communication_website_posts_on_university_id"
   end
 
-  create_table "communication_websites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_websites", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.string "url"
@@ -536,7 +537,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["priority", "run_at"], name: "delayed_jobs_priority"
   end
 
-  create_table "education_academic_years", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_academic_years", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.integer "year"
     t.datetime "created_at", null: false
@@ -551,7 +552,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_person_id", "education_academic_year_id"], name: "index_person_academic_year"
   end
 
-  create_table "education_cohorts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_cohorts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "program_id", null: false
     t.uuid "academic_year_id", null: false
@@ -572,7 +573,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_person_id", "education_cohort_id"], name: "index_person_cohort"
   end
 
-  create_table "education_diplomas", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_diplomas", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.string "short_name"
     t.integer "level", default: 0
@@ -586,7 +587,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_education_diplomas_on_university_id"
   end
 
-  create_table "education_programs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_programs", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.integer "capacity"
@@ -646,7 +647,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["education_program_id", "user_id"], name: "index_education_programs_users_on_program_id_and_user_id"
   end
 
-  create_table "education_schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_schools", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.string "address"
@@ -662,7 +663,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_education_schools_on_university_id"
   end
 
-  create_table "imports", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "imports", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.integer "number_of_lines"
     t.jsonb "processing_errors"
     t.integer "kind"
@@ -675,7 +676,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["user_id"], name: "index_imports_on_user_id"
   end
 
-  create_table "languages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "languages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.string "iso_code"
     t.datetime "created_at", null: false
@@ -742,7 +743,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_research_journal_paper_kinds_on_university_id"
   end
 
-  create_table "research_journal_papers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_journal_papers", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "title"
     t.datetime "published_at", precision: nil
     t.uuid "university_id", null: false
@@ -779,7 +780,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["researcher_id"], name: "index_research_journal_papers_researchers_on_researcher_id"
   end
 
-  create_table "research_journal_volumes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_journal_volumes", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "research_journal_id", null: false
     t.string "title"
@@ -799,7 +800,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_research_journal_volumes_on_university_id"
   end
 
-  create_table "research_journals", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_journals", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "title"
     t.text "meta_description"
@@ -810,7 +811,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_research_journals_on_university_id"
   end
 
-  create_table "research_laboratories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_laboratories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.string "address"
@@ -822,7 +823,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_research_laboratories_on_university_id"
   end
 
-  create_table "research_laboratory_axes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_laboratory_axes", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "research_laboratory_id", null: false
     t.string "name"
@@ -836,7 +837,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_research_laboratory_axes_on_university_id"
   end
 
-  create_table "research_theses", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_theses", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "research_laboratory_id", null: false
     t.uuid "author_id", null: false
@@ -854,7 +855,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_research_theses_on_university_id"
   end
 
-  create_table "universities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "universities", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.string "identifier"
     t.string "address"
@@ -893,7 +894,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_university_organization_categories_on_university_id"
   end
 
-  create_table "university_organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_organizations", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.string "long_name"
@@ -931,7 +932,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["organization_id"], name: "index_university_organizations_categories_on_organization_id"
   end
 
-  create_table "university_people", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_people", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "user_id"
     t.string "last_name"
@@ -987,7 +988,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_university_person_categories_on_university_id"
   end
 
-  create_table "university_person_experiences", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_person_experiences", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "person_id", null: false
     t.uuid "organization_id", null: false
@@ -1001,7 +1002,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_university_person_experiences_on_university_id"
   end
 
-  create_table "university_person_involvements", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_person_involvements", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "person_id", null: false
     t.integer "kind"
@@ -1016,7 +1017,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_university_person_involvements_on_university_id"
   end
 
-  create_table "university_roles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_roles", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "target_type"
     t.uuid "target_id"
@@ -1028,7 +1029,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_142031) do
     t.index ["university_id"], name: "index_university_roles_on_university_id"
   end
 
-  create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "users", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "first_name"
     t.string "last_name"
diff --git a/lib/tasks/auto.rake b/lib/tasks/auto.rake
index 2af83edd413a6606b88da8be7ed8347334287cc4..cc68e377221307d291395a01a8fbee6c05e0dc91 100644
--- a/lib/tasks/auto.rake
+++ b/lib/tasks/auto.rake
@@ -1,11 +1,8 @@
 namespace :auto do
+
   desc 'Update publications from HAL for all researchers'
   task update_hal: :environment do
     Research::Hal.update_from_api!
   end
 
-  desc 'Save all websites to update connections and clean Git repositories'
-  task save_and_sync_websites: :environment do
-    Communication::Website.save_and_sync_websites!
-  end
 end
\ No newline at end of file
diff --git a/test/fixtures/communication/blocks.yml b/test/fixtures/communication/blocks.yml
index 0a0c4a5f730950692e674446a6d394b837c298d5..a60600f67229ea5c6b01309153463389d40205eb 100644
--- a/test/fixtures/communication/blocks.yml
+++ b/test/fixtures/communication/blocks.yml
@@ -12,19 +12,16 @@
 #  created_at    :datetime         not null
 #  updated_at    :datetime         not null
 #  about_id      :uuid             indexed => [about_type]
-#  heading_id    :uuid             indexed
 #  university_id :uuid             not null, indexed
 #
 # Indexes
 #
-#  index_communication_blocks_on_heading_id     (heading_id)
 #  index_communication_blocks_on_university_id  (university_id)
 #  index_communication_website_blocks_on_about  (about_type,about_id)
 #
 # Foreign Keys
 #
 #  fk_rails_18291ef65f  (university_id => universities.id)
-#  fk_rails_90ac986fab  (heading_id => communication_block_headings.id)
 #
 
 olivia_in_noesya:
diff --git a/test/fixtures/communication/extranets.yml b/test/fixtures/communication/extranets.yml
index c4a72564511a4905256b44eff1b90d9ad95d76db..5b00f559b78ac19b9a3871e2736923e92e325c4b 100644
--- a/test/fixtures/communication/extranets.yml
+++ b/test/fixtures/communication/extranets.yml
@@ -2,34 +2,35 @@
 #
 # Table name: communication_extranets
 #
-#  id                         :uuid             not null, primary key
-#  about_type                 :string           indexed => [about_id]
-#  color                      :string
-#  cookies_policy             :text
-#  css                        :text
-#  feature_alumni             :boolean          default(FALSE)
-#  feature_contacts           :boolean          default(FALSE)
-#  feature_jobs               :boolean          default(FALSE)
-#  feature_library            :boolean          default(FALSE)
-#  feature_posts              :boolean          default(FALSE)
-#  has_sso                    :boolean          default(FALSE)
-#  home_sentence              :text
-#  host                       :string
-#  name                       :string
-#  privacy_policy             :text
-#  registration_contact       :string
-#  sass                       :text
-#  sso_button_label           :string
-#  sso_cert                   :text
-#  sso_mapping                :jsonb
-#  sso_name_identifier_format :string
-#  sso_provider               :integer          default("saml")
-#  sso_target_url             :string
-#  terms                      :text
-#  created_at                 :datetime         not null
-#  updated_at                 :datetime         not null
-#  about_id                   :uuid             indexed => [about_type]
-#  university_id              :uuid             not null, indexed
+#  id                             :uuid             not null, primary key
+#  about_type                     :string           indexed => [about_id]
+#  allow_experiences_modification :boolean          default(TRUE)
+#  color                          :string
+#  cookies_policy                 :text
+#  css                            :text
+#  feature_alumni                 :boolean          default(FALSE)
+#  feature_contacts               :boolean          default(FALSE)
+#  feature_jobs                   :boolean          default(FALSE)
+#  feature_library                :boolean          default(FALSE)
+#  feature_posts                  :boolean          default(FALSE)
+#  has_sso                        :boolean          default(FALSE)
+#  home_sentence                  :text
+#  host                           :string
+#  name                           :string
+#  privacy_policy                 :text
+#  registration_contact           :string
+#  sass                           :text
+#  sso_button_label               :string
+#  sso_cert                       :text
+#  sso_mapping                    :jsonb
+#  sso_name_identifier_format     :string
+#  sso_provider                   :integer          default("saml")
+#  sso_target_url                 :string
+#  terms                          :text
+#  created_at                     :datetime         not null
+#  updated_at                     :datetime         not null
+#  about_id                       :uuid             indexed => [about_type]
+#  university_id                  :uuid             not null, indexed
 #
 # Indexes
 #
diff --git a/test/fixtures/communication/website/menus.yml b/test/fixtures/communication/website/menus.yml
new file mode 100644
index 0000000000000000000000000000000000000000..51d442523ed21780b78d5634352551aa4485d8cc
--- /dev/null
+++ b/test/fixtures/communication/website/menus.yml
@@ -0,0 +1,34 @@
+# == Schema Information
+#
+# Table name: communication_website_menus
+#
+#  id                       :uuid             not null, primary key
+#  github_path              :text
+#  identifier               :string
+#  title                    :string
+#  created_at               :datetime         not null
+#  updated_at               :datetime         not null
+#  communication_website_id :uuid             not null, indexed
+#  language_id              :uuid             not null, indexed
+#  original_id              :uuid             indexed
+#  university_id            :uuid             not null, indexed
+#
+# Indexes
+#
+#  idx_comm_website_menus_on_communication_website_id  (communication_website_id)
+#  index_communication_website_menus_on_language_id    (language_id)
+#  index_communication_website_menus_on_original_id    (original_id)
+#  index_communication_website_menus_on_university_id  (university_id)
+#
+# Foreign Keys
+#
+#  fk_rails_2901ebb799  (original_id => communication_website_menus.id)
+#  fk_rails_4d43d36541  (language_id => languages.id)
+#  fk_rails_8d6227916e  (university_id => universities.id)
+#  fk_rails_dcc7198fc5  (communication_website_id => communication_websites.id)
+#
+primary_menu:
+  university: default_university
+  website: website_with_github
+  title: 'Menu principal'
+  language: fr
diff --git a/test/models/communication/website/connection_test.rb b/test/models/communication/website/connection_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..57d808e5b411ed08f82c91918c5cd0b3686bb39a
--- /dev/null
+++ b/test/models/communication/website/connection_test.rb
@@ -0,0 +1,193 @@
+# == Schema Information
+#
+# Table name: communication_website_connections
+#
+#  id                   :uuid             not null, primary key
+#  direct_source_type   :string           indexed => [direct_source_id]
+#  indirect_object_type :string           not null, indexed => [indirect_object_id]
+#  created_at           :datetime         not null
+#  updated_at           :datetime         not null
+#  direct_source_id     :uuid             indexed => [direct_source_type]
+#  indirect_object_id   :uuid             not null, indexed => [indirect_object_type]
+#  university_id        :uuid             not null, indexed
+#  website_id           :uuid             not null, indexed
+#
+# Indexes
+#
+#  index_communication_website_connections_on_object         (indirect_object_type,indirect_object_id)
+#  index_communication_website_connections_on_source         (direct_source_type,direct_source_id)
+#  index_communication_website_connections_on_university_id  (university_id)
+#  index_communication_website_connections_on_website_id     (website_id)
+#
+# Foreign Keys
+#
+#  fk_rails_728034883b  (website_id => communication_websites.id)
+#  fk_rails_bd1ac8383b  (university_id => universities.id)
+#
+require "test_helper"
+
+# rails test test/models/communication/website/connection_test.rb
+class Communication::Website::ConnectionTest < ActiveSupport::TestCase
+  def test_unpublish_indirect_does_nothing
+    page = communication_website_pages(:page_with_no_dependency)
+    setup_page_connections(page)
+
+    # On dƩpublie un bloc : +0
+    assert_no_difference("Communication::Website::Connection.count") do
+      page.blocks.second.update(published: false)
+    end
+  end
+
+  def test_unpublish_direct_does_nothing
+    page = communication_website_pages(:page_with_no_dependency)
+    setup_page_connections(page)
+
+    # On dƩpublie la page ayant un bloc chapitre : +0
+    assert_no_difference("Communication::Website::Connection.count") do
+      page.update(published: false)
+    end
+  end
+
+  def test_deleting_direct_removes_all_its_connections
+    page = communication_website_pages(:page_with_no_dependency)
+    setup_page_connections(page)
+
+    # On supprime la page ayant un bloc chapitre, et ainsi toutes ses connexions : -10
+    assert_difference -> { Communication::Website::Connection.count } => -10 do
+      page.destroy
+    end
+  end
+
+  def test_deleting_indirect_removes_all_its_connections
+    page = communication_website_pages(:page_with_no_dependency)
+    setup_page_connections(page)
+
+    # On supprime le bloc qui contient PA : -2 (parce que PA doit ĆŖtre supprimĆ© aussi)
+    assert_difference -> { Communication::Website::Connection.count } => -2 do
+      page.blocks.find_by(position: 2).destroy
+    end
+  end
+
+  def test_deleting_indirect_with_a_dependency_having_two_sources
+    page = communication_website_pages(:page_with_no_dependency)
+    setup_page_connections(page)
+
+    # On ajoute noesya Ć  PA via un block "Organisations"
+    assert_difference -> { Communication::Website::Connection.count } => 1 do
+      block = pa.blocks.create(position: 1, published: true, template_kind: :partners)
+      block.data = "{ \"elements\": [ { \"id\": \"#{noesya.id}\" } ] }"
+      block.save
+    end
+
+    # Suppression d'un objet indirect qui a en dƩpendance un autre objet utilisƩ ailleurs (dans le cas prƩcƩdent si PA Ʃtait utilisƩ par une autre source)
+    # On supprime le bloc qui contient PA : -3 (parce que PA doit ĆŖtre supprimĆ© aussi ainsi que son bloc Organisations mais pas Noesya, toujours connectĆ©e via le block 3)
+    assert_difference -> { Communication::Website::Connection.count } => -3 do
+      page.blocks.find_by(position: 2).destroy
+    end
+  end
+
+  def test_unpublish_indirect_does_nothing
+    page = communication_website_pages(:page_with_no_dependency)
+    setup_page_connections(page)
+
+    # On dƩpublie la page ayant un bloc chapitre : +0
+    assert_no_difference("Communication::Website::Connection.count") do
+      page.blocks.ordered.last.update(published: false)
+    end
+  end
+
+  def test_deleting_direct_with_indirect_dependency_having_two_sources
+    # https://developers.osuny.org/docs/admin/communication/sites-web/dependencies/iteration-4/#olivia-et-le-saumon-de-schr%C3%B6dinger
+    page = communication_website_pages(:page_with_no_dependency)
+    setup_page_connections(page)
+
+    second_page = communication_website_pages(:page_test)
+    block = second_page.blocks.create(position: 1, published: true, template_kind: :partners)
+    block.data = "{ \"elements\": [ { \"id\": \"#{noesya.id}\" } ] }"
+    block.save
+
+    # Noesya est connectƩe via les 2 pages, donc 2 connexions
+    assert_equal 2, page.website.connections.where(indirect_object: noesya).count
+
+    # On supprime la 2e page, donc ses 7 connexions Ć  savoir :
+    # - Le block Organisations et Noesya (2)
+    # - Les 5 connexions via Noesya, Ć  savoir :
+    #   - Le block Personnes avec Olivia et son block Organisations (3)
+    #     note : Pas Noesya car la connexion existe dƩjƠ plus haut
+    #   - Le block Personnes avec Arnaud (2)
+    assert_difference -> { Communication::Website::Connection.count } => -7 do
+      second_page.destroy
+    end
+
+    # Noesya est toujours connectƩe via la 1re page
+    assert_equal 1, page.website.connections.where(indirect_object: noesya).count
+  end
+
+  def test_connecting_and_disconnecting_indirect_to_website_directly
+    # En connectant l'Ʃcole au site, on crƩe une connexion pour ses 2 objets ainsi que les dƩpendances de l'Ʃcole :
+    # Ses formations (default_program) et ses diplĆ´mes (default_diploma) : donc 3 connexions au total
+    assert_difference -> { Communication::Website::Connection.count } => 3 do
+      website_with_github.update(about: default_school)
+    end
+
+    # En dƩconnectant l'Ʃcole du site, on supprime les connexions crƩƩes prƩcƩdemment
+    assert_difference -> { Communication::Website::Connection.count } => -3 do
+      website_with_github.update(about: nil)
+    end
+  end
+
+  private
+
+  def setup_page_connections(page)
+    assert_no_difference("Communication::Website::Connection.count") do
+      page.save
+    end
+
+    # On ajoute un block "Chapitre" : +1
+    assert_difference -> { Communication::Website::Connection.count } => 1 do
+      page.blocks.create(position: 1, published: true, template_kind: :chapter)
+    end
+
+    # On connecte PA via un block "Personnes" : +2
+    assert_difference -> { Communication::Website::Connection.count } => 2 do
+      block = page.blocks.create(position: 2, published: true, template_kind: :organization_chart)
+      block.data = "{ \"elements\": [ { \"id\": \"#{pa.id}\" } ] }"
+      block.save
+    end
+
+    # On ajoute noesya via un block "Organisations" : +4 parce que noesya a un block "Personnes" avec Olivia
+    assert_difference -> { Communication::Website::Connection.count } => 4 do
+      block = page.blocks.create(position: 3, published: true, template_kind: :partners)
+      block.data = "{ \"elements\": [ { \"id\": \"#{noesya.id}\" } ] }"
+      block.save
+    end
+
+    # On ajoute Arnaud Ć  noesya via un block "Personnes" : +2
+    assert_difference -> { Communication::Website::Connection.count } => 2 do
+      block = noesya.blocks.create(position: 2, published: true, template_kind: :organization_chart)
+      block.data = "{ \"elements\": [ { \"id\": \"#{arnaud.id}\" } ] }"
+      block.save
+    end
+
+    # On tente la boucle infine en ajoutant noesya Ơ Olivia : +1 (le block ajoutƩ Ơ Olivia)
+    assert_difference -> { Communication::Website::Connection.count } => 1 do
+      block = olivia.blocks.create(position: 1, published: true, template_kind: :partners)
+      block.data = "{ \"elements\": [ { \"id\": \"#{noesya.id}\" } ] }"
+      block.save
+    end
+
+    # La page est donc comme ceci
+    # Page
+    # - Block Chapitre
+    # - Block Personnes
+    #   - PA
+    # - Block Organisations
+    #   - Noesya
+    #     - Block Personnes
+    #       - Olivia
+    #         - Block Organisations
+    #           - Noesya
+    #     - Block Personnes
+    #       - Arnaud
+  end
+end
diff --git a/test/models/communication/website/dependencies_test.rb b/test/models/communication/website/dependencies_test.rb
deleted file mode 100644
index a1245d7624cd903df9cea788ce56284971bb5bec..0000000000000000000000000000000000000000
--- a/test/models/communication/website/dependencies_test.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require "test_helper"
-
-# rails test test/models/communication/website/dependencies_test.rb
-class Communication::Website::DependenciesTest < ActiveSupport::TestCase
-  test "page" do
-    # Rien : 0 dƩpendances
-    page = communication_website_pages(:page_with_no_dependency)
-    assert_equal 0, page.recursive_dependencies.count
-    
-    #  On ajoute un block "Chapitre" : 7 dƩpendances (les 6 composants du chapitre + le chapitre)
-    page = communication_website_pages(:page_with_no_dependency)
-    page.blocks.create(position: 1, published: true, template_kind: :chapter)
-    assert_equal 7, page.recursive_dependencies.count
-  end
-end
diff --git a/test/models/communication/website/dependency_test.rb b/test/models/communication/website/dependency_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ce703c6f429a6b8c2d6a7e5e181efd36fbe7ebb0
--- /dev/null
+++ b/test/models/communication/website/dependency_test.rb
@@ -0,0 +1,95 @@
+require "test_helper"
+
+# rails test test/models/communication/website/dependency_test.rb
+class Communication::Website::DependencyTest < ActiveSupport::TestCase
+  def test_page_dependencies
+    # Rien : 0 dƩpendances
+    page = communication_website_pages(:page_with_no_dependency)
+    assert_equal 0, page.recursive_dependencies.count
+
+    #  On ajoute un block "Chapitre" : 7 dĆ©pendances (les 6 composants du chapitre + le block lui mĆŖme)
+    page.blocks.create(position: 1, published: true, template_kind: :chapter)
+    assert_equal 7, page.recursive_dependencies.count
+  end
+
+  def test_change_block_dependencies
+    page = communication_website_pages(:page_with_no_dependency)
+
+    # On ajoute un block Personnes liƩ Ơ Arnaud : 9 dƩpendances
+    # - le block Personnes (1)
+    # - 4 composants du template du block + 1 ƩlƩment (5)
+    # - 2 composants de l'ƩlƩment du template (2)
+    # - la personne en dƩpendance du composant Person (1)
+    block = page.blocks.create(position: 1, published: true, template_kind: :organization_chart)
+    block.data = "{ \"elements\": [ { \"id\": \"#{arnaud.id}\" } ] }"
+    block.save
+    
+    page = page.reload
+    
+    assert_equal 9, page.recursive_dependencies.count
+    
+    # On modifie le target du block
+    Delayed::Job.destroy_all
+    block.data = "{ \"elements\": [ { \"id\": \"#{olivia.id}\" } ] }"
+    block.save
+    # On vƩrifie qu'on appelle bien la mƩthode destroy_obsolete_git_files sur le site de la page
+    assert(destroy_obsolete_git_files_job)
+    
+    assert_equal 9, page.recursive_dependencies.count
+    
+    # VĆ©rifie qu'on a bien une tĆ¢che de nettoyage si le block est supprimĆ©
+    Delayed::Job.destroy_all
+    block.destroy
+    assert(destroy_obsolete_git_files_job)
+
+  end
+  
+  def test_change_website_dependencies
+    dependencies_before_count = website_with_github.recursive_dependencies.count
+    
+    # On modifie l'about du website en ajoutant une Ć©cole
+    website_with_github.update(about: default_school)
+    refute(destroy_obsolete_git_files_job)
+    delta = website_with_github.recursive_dependencies.count - dependencies_before_count
+    assert_equal 12, delta
+    
+    Delayed::Job.destroy_all
+    
+    # On enlĆØve l'about du website
+    website_with_github.update(about: nil)
+    
+    # On vƩrifie qu'on appelle bien la mƩthode destroy_obsolete_git_files sur le site
+    assert(destroy_obsolete_git_files_job)
+       
+  end
+
+  def test_change_menu_item_dependencies
+    menu = communication_website_menus(:primary_menu)
+
+    item = menu.items.create(university: default_university, website: website_with_github, kind: :blank, title: 'Test')
+    assert_equal 2, item.recursive_dependencies.count
+    
+    item.kind = :page
+    item.about = communication_website_pages(:page_with_no_dependency)
+    item.save
+    assert_equal 2, item.recursive_dependencies.count
+    
+    # Comme les menu items ne rĆ©pondent pas Ć  is_direct_object? du coup aucune tĆ¢che de nettoyage n'est ajoutĆ©e
+    item.destroy
+    refute(destroy_obsolete_git_files_job)
+  end
+
+  protected
+
+  def destroy_obsolete_git_files_job(website_id = website_with_github.id)
+    find_performable_method_job(:destroy_obsolete_git_files_without_delay, website_id)
+  end
+
+  # On ne peut pas utiliser assert_enqueued_jobs sur les mƩthodes asynchrones gƩrƩes avec handle_asynchronously
+  def find_performable_method_job(method, id)
+    Delayed::Job.all.detect { |job|
+      job.payload_object.method_name == method &&
+        job.payload_object.object.id == id
+    }
+  end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 6aba439556b595ffef2e854cf46438a608933078..34de059cec8a1965cbb4581fbcb3e05fd5e64531 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -26,6 +26,14 @@ class ActiveSupport::TestCase
     @default_extranet ||= communication_extranets(:default_extranet)
   end
 
+  def website_with_github
+    @website_with_github ||= communication_websites(:website_with_github)
+  end
+
+  def default_school
+    @default_school ||= education_schools(:default_school)
+  end
+
   def alumnus
     @alumnus ||= users(:alumnus)
   end