From c645dfb16e0a0b054132b70f15ecc13e8e84a63d Mon Sep 17 00:00:00 2001
From: docwilmot <docwilmot@users.noreply.github.com>
Date: Tue, 28 Apr 2020 19:34:30 -0500
Subject: [PATCH 01/28] Issue #4354: Allow API saving of new inactive (forward)
 revisions.

Great enhancement that should allow for workflow improvements like scheduled revisions or review before publishing new revisions!

By @stpaultim, @docwilmot, @olafgrabienski, @jenlampton, @Graham-72, and @quicksketch.
---
 core/modules/entity/entity.class.inc          | 56 +++++++++++++++++++
 core/modules/entity/entity.controller.inc     |  8 ++-
 core/modules/entity/tests/entity_query.test   | 14 ++---
 .../field_sql_storage.module                  | 20 +++++--
 .../tests/field_test/field_test.entity.inc    | 14 +++--
 core/modules/node/node.entity.inc             | 44 +++++++++++++--
 core/modules/node/node.module                 | 20 +++----
 core/modules/node/node.pages.inc              |  3 +
 core/modules/node/tests/node.test             | 38 +++++++++++++
 core/modules/taxonomy/taxonomy.module         |  4 +-
 10 files changed, 183 insertions(+), 38 deletions(-)

diff --git a/core/modules/entity/entity.class.inc b/core/modules/entity/entity.class.inc
index 179551e985..b1e77b02e8 100644
--- a/core/modules/entity/entity.class.inc
+++ b/core/modules/entity/entity.class.inc
@@ -177,6 +177,34 @@ interface EntityInterface {
    * @since 1.13.0 Method added.
    */
   public function getFieldValues($field_name, $value_key = 'value', $langcode = NULL);
+
+  /**
+   * Returns the revision identifier of the entity.
+   *
+   * @return
+   *   The revision identifier of the entity, or NULL if the entity does not
+   *   have a revision identifier.
+   *
+   * @since 1.16.0 Method added.
+   */
+  public function getRevisionId();
+
+  /**
+   * Checks if this entity is the active revision.
+   *
+   * @return bool
+   *   TRUE if the entity is the active revision, FALSE otherwise.
+   *
+   * @since 1.16.0 Method added.
+   */
+  public function isActiveRevision();
+
+  /**
+   * Sets this revision as the active revision.
+   *
+   * @since 1.16.0 Method added.
+   */
+  public function setIsActiveRevision();
 }
 
 /**
@@ -184,6 +212,13 @@ interface EntityInterface {
  */
 abstract class Entity extends stdClass implements EntityInterface {
 
+  /**
+   * Indicates whether this is the active revision.
+   *
+   * @var bool
+   */
+  public $is_active_revision = TRUE;
+
   /**
    * Constructs a new entity object.
    */
@@ -278,6 +313,27 @@ abstract class Entity extends stdClass implements EntityInterface {
     }
     return $values;
   }
+
+  /**
+   * Implements Backdrop\entity\EntityInterface::getRevisionId().
+   */
+  public function getRevisionId() {
+    return NULL;
+  }
+
+  /**
+   * Implements Backdrop\entity\EntityInterface::isActiveRevision().
+   */
+  public function isActiveRevision() {
+    return $this->is_active_revision;
+  }
+
+  /**
+   * Implements Backdrop\entity\EntityInterface::setActiveRevision().
+   */
+  public function setIsActiveRevision() {
+    $this->is_active_revision = TRUE;
+  }
 }
 
 /**
diff --git a/core/modules/entity/entity.controller.inc b/core/modules/entity/entity.controller.inc
index d41bd518bb..0d3ba28c45 100644
--- a/core/modules/entity/entity.controller.inc
+++ b/core/modules/entity/entity.controller.inc
@@ -408,6 +408,10 @@ class DefaultEntityController implements EntityControllerInterface {
         }
       }
       $query->fields('revision', $entity_revision_fields);
+
+      // Compare revision id of the base and revision table, if equal then this
+      // is the active revision.
+      $query->addExpression('base.' . $this->revisionKey . ' = revision.' . $this->revisionKey, 'is_active_revision');
     }
 
     $query->fields('base', $entity_fields);
@@ -437,8 +441,8 @@ class DefaultEntityController implements EntityControllerInterface {
    * @param $queried_entities
    *   Associative array of query results, keyed on the entity ID.
    * @param $revision_id
-   *   ID of the revision that was loaded, or FALSE if the most current revision
-   *   was loaded.
+   *   ID of the revision that was loaded, or FALSE if no revision was
+   *   specified..
    */
   protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
     // Attach fields.
diff --git a/core/modules/entity/tests/entity_query.test b/core/modules/entity/tests/entity_query.test
index fd5a9e2b06..a0527ba418 100644
--- a/core/modules/entity/tests/entity_query.test
+++ b/core/modules/entity/tests/entity_query.test
@@ -91,7 +91,7 @@ class EntityFieldQueryTestCase extends BackdropWebTestCase {
 
     // Create entities which have a 'bundle key' defined.
     for ($i = 1; $i < 7; $i++) {
-      $entity = new stdClass();
+      $entity = entity_create('test_entity', array());
       $entity->ftid = $i;
       $entity->fttype = ($i < 5) ? 'bundle1' : 'bundle2';
 
@@ -100,7 +100,7 @@ class EntityFieldQueryTestCase extends BackdropWebTestCase {
       field_attach_insert('test_entity_bundle_key', $entity);
     }
 
-    $entity = new stdClass();
+    $entity = entity_create('test_entity', array());
     $entity->ftid = 5;
     $entity->fttype = 'test_entity_bundle';
     $entity->{$this->field_names[1]}[LANGUAGE_NONE][0]['shape'] = 'square';
@@ -118,7 +118,7 @@ class EntityFieldQueryTestCase extends BackdropWebTestCase {
 
     // Create entities with support for revisions.
     for ($i = 1; $i < 5; $i++) {
-      $entity = new stdClass();
+      $entity = entity_create('test_entity', array());
       $entity->ftid = $i;
       $entity->ftvid = $i;
       $entity->fttype = 'test_bundle';
@@ -131,7 +131,7 @@ class EntityFieldQueryTestCase extends BackdropWebTestCase {
 
     // Add two revisions to an entity.
     for ($i = 100; $i < 102; $i++) {
-      $entity = new stdClass();
+      $entity = entity_create('test_entity', array());
       $entity->ftid = 4;
       $entity->ftvid = $i;
       $entity->fttype = 'test_bundle';
@@ -969,7 +969,7 @@ class EntityFieldQueryTestCase extends BackdropWebTestCase {
     ), 'Test offset on a field.', TRUE);
 
     for ($i = 6; $i < 10; $i++) {
-      $entity = new stdClass();
+      $entity = entity_create('test_entity', array());
       $entity->ftid = $i;
       $entity->fttype = 'test_entity_bundle';
       $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i - 5;
@@ -1051,7 +1051,7 @@ class EntityFieldQueryTestCase extends BackdropWebTestCase {
     field_test_entity_info_translatable('test_entity', TRUE);
 
     // Create more items with different languages.
-    $entity = new stdClass();
+    $entity = entity_create('test_entity', array());
     $entity->ftid = 1;
     $entity->ftvid = 1;
     $entity->fttype = 'test_bundle';
@@ -1088,7 +1088,7 @@ class EntityFieldQueryTestCase extends BackdropWebTestCase {
     field_test_entity_info_translatable('test_entity', TRUE);
 
     // Create more items with different languages.
-    $entity = new stdClass();
+    $entity = entity_create('test_entity', array());
     $entity->ftid = 1;
     $entity->ftvid = 1;
     $entity->fttype = 'test_bundle';
diff --git a/core/modules/field/modules/field_sql_storage/field_sql_storage.module b/core/modules/field/modules/field_sql_storage/field_sql_storage.module
index 64f42e8527..ccb4b23b4d 100644
--- a/core/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/core/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -388,11 +388,15 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel
       // Delete all languages if $entity->$field_name is empty.
       $languages = !empty($entity->$field_name) ? $field_languages : $all_languages;
       if ($languages) {
-        db_delete($table_name)
-          ->condition('entity_type', $entity_type)
-          ->condition('entity_id', $id)
-          ->condition('language', $languages, 'IN')
-          ->execute();
+        // Only overwrite the field's base table if saving the active revision
+        // of an entity.
+        if ($entity->isActiveRevision()) {
+          db_delete($table_name)
+            ->condition('entity_type', $entity_type)
+            ->condition('entity_id', $id)
+            ->condition('language', $languages, 'IN')
+            ->execute();
+        }
         db_delete($revision_name)
           ->condition('entity_type', $entity_type)
           ->condition('entity_id', $id)
@@ -441,7 +445,11 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel
 
     // Execute the query if we have values to insert.
     if ($do_insert) {
-      $query->execute();
+      // Only overwrite the field's base table if saving the active revision
+      // of an entity.
+      if ($entity->isActiveRevision()) {
+        $query->execute();
+      }
       $revision_query->execute();
     }
   }
diff --git a/core/modules/field/tests/field_test/field_test.entity.inc b/core/modules/field/tests/field_test/field_test.entity.inc
index 56228dc4fe..8590360778 100644
--- a/core/modules/field/tests/field_test/field_test.entity.inc
+++ b/core/modules/field/tests/field_test/field_test.entity.inc
@@ -254,6 +254,10 @@ function field_test_entity_test_load($ftid, $ftvid = NULL) {
   }
 
   $entities = $query->execute()->fetchAllAssoc('ftid');
+  foreach ($entities as $key => $entity) {
+    $entities[$key] = entity_create('test_entity', (array) $entity);
+  }
+    
 
   // Attach fields.
   if ($ftvid) {
@@ -321,7 +325,7 @@ function field_test_entity_save(&$entity) {
  */
 function field_test_entity_add($fttype) {
   $fttype = str_replace('-', '_', $fttype);
-  $entity = (object)array('fttype' => $fttype);
+  $entity = entity_create('test_entity', array('fttype' => $fttype));
   backdrop_set_title(t('Create test_entity @bundle', array('@bundle' => $fttype)), PASS_THROUGH);
   return backdrop_get_form('field_test_entity_form', $entity, TRUE);
 }
@@ -455,10 +459,10 @@ function field_test_entity_nested_form($form, &$form_state, $entity_1, $entity_2
  * Validate handler for field_test_entity_nested_form().
  */
 function field_test_entity_nested_form_validate($form, &$form_state) {
-  $entity_1 = (object) $form_state['values'];
+  $entity_1 = entity_create('test_entity', $form_state['values']);
   field_attach_form_validate('test_entity', $entity_1, $form, $form_state);
 
-  $entity_2 = (object) $form_state['values']['entity_2'];
+  $entity_2 = entity_create('test_entity', $form_state['values']['entity_2']);
   field_attach_form_validate('test_entity', $entity_2, $form['entity_2'], $form_state);
 }
 
@@ -466,11 +470,11 @@ function field_test_entity_nested_form_validate($form, &$form_state) {
  * Submit handler for field_test_entity_nested_form().
  */
 function field_test_entity_nested_form_submit($form, &$form_state) {
-  $entity_1 = (object) $form_state['values'];
+  $entity_1 = entity_create('test_entity', $form_state['values']);
   field_attach_submit('test_entity', $entity_1, $form, $form_state);
   field_test_entity_save($entity_1);
 
-  $entity_2 = (object) $form_state['values']['entity_2'];
+  $entity_2 = entity_create('test_entity', $form_state['values']['entity_2']);
   field_attach_submit('test_entity', $entity_2, $form['entity_2'], $form_state);
   field_test_entity_save($entity_2);
 
diff --git a/core/modules/node/node.entity.inc b/core/modules/node/node.entity.inc
index 690e1ef959..fcb8a40bb3 100644
--- a/core/modules/node/node.entity.inc
+++ b/core/modules/node/node.entity.inc
@@ -23,6 +23,16 @@ class Node extends Entity {
    */
   public $vid;
 
+  /**
+   * Indicates whether this is the active node revision.
+   *
+   * The active revision of a node is the one loaded when no specific revision
+   * has been specified. Only active revisions are saved to the node table.
+   *
+   * @var boolean
+   */
+  public $is_active_revision = TRUE;
+
   /**
    * The node content type (bundle).
    *
@@ -382,6 +392,12 @@ class Node extends Entity {
     return $duplicate;
   }
 
+  /**
+   * Overrides Backdrop\entity\Entity::getRevisionId().
+   */
+  public function getRevisionId() {
+    return $this->vid;
+  }
 }
 
 
@@ -462,7 +478,14 @@ class NodeStorageController extends EntityDatabaseStorageController {
       }
       else {
         $op = 'update';
-        $return = backdrop_write_record($this->entityInfo['base table'], $entity, $this->idKey);
+        // Update the base node table, but only if this revision is marked as
+        // the active revision.
+        if ($entity->isActiveRevision()) {
+          $return = backdrop_write_record($this->entityInfo['base table'], $entity, $this->idKey);
+        }
+        else {
+          $return = SAVED_UPDATED;
+        }
       }
 
       if ($this->revisionKey) {
@@ -501,10 +524,14 @@ class NodeStorageController extends EntityDatabaseStorageController {
 
     if (empty($entity->{$this->revisionKey}) || !empty($entity->revision)) {
       backdrop_write_record($this->revisionTable, $record);
-      db_update($this->entityInfo['base table'])
-        ->fields(array($this->revisionKey => $record->{$this->revisionKey}))
-        ->condition($this->idKey, $entity->{$this->idKey})
-        ->execute();
+      // Only update the base node table if this revision is the active
+      // revision.
+      if ($entity->isActiveRevision()) {
+        db_update($this->entityInfo['base table'])
+          ->fields(array($this->revisionKey => $record->{$this->revisionKey}))
+          ->condition($this->idKey, $entity->{$this->idKey})
+          ->execute();
+      }
     }
     else {
       backdrop_write_record($this->revisionTable, $record, $this->revisionKey);
@@ -636,7 +663,12 @@ class NodeStorageController extends EntityDatabaseStorageController {
    *   The node object that has just been saved.
    */
   function postSave(EntityInterface $node, $update) {
-    node_access_acquire_grants($node, $update);
+    // Update the node access table for this node, but only if it is the
+    // active revision. There's no need to delete existing records if the node
+    // is new.
+    if ($node->isActiveRevision()) {
+      node_access_acquire_grants($node, $update);
+    }
   }
 
   /**
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 88d29c8ea2..8d633c5a71 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1078,9 +1078,9 @@ function node_delete_multiple($nids) {
  */
 function node_revision_delete($revision_id) {
   if ($revision = node_load(NULL, $revision_id)) {
-    // Prevent deleting the current revision.
+    // Prevent deleting the active revision.
     $node = node_load($revision->nid);
-    if ($revision_id == $node->vid) {
+    if ($revision->isActiveRevision()) {
       return FALSE;
     }
 
@@ -1527,7 +1527,7 @@ function node_ranking() {
 function node_user_cancel($edit, $account, $method) {
   switch ($method) {
     case 'user_cancel_block_unpublish':
-      // Unpublish nodes (current revisions).
+      // Unpublish nodes (active revisions).
       module_load_include('inc', 'node', 'node.admin');
       $nodes = db_select('node', 'n')
         ->fields('n', array('nid'))
@@ -1538,7 +1538,7 @@ function node_user_cancel($edit, $account, $method) {
       break;
 
     case 'user_cancel_reassign':
-      // Anonymize nodes (current revisions).
+      // Anonymize nodes (active revisions).
       module_load_include('inc', 'node', 'node.admin');
       $nodes = db_select('node', 'n')
         ->fields('n', array('nid'))
@@ -1563,7 +1563,7 @@ function node_user_cancel($edit, $account, $method) {
  * Implements hook_user_predelete().
  */
 function node_user_predelete($account) {
-  // Delete nodes (current revisions).
+  // Delete nodes (active revisions).
   // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
   $nodes = db_select('node', 'n')
     ->fields('n', array('nid'))
@@ -1627,12 +1627,12 @@ function _node_revision_access(Node $node, $op = 'view', $account = NULL) {
     }
 
     $node_current_revision = node_load($node->nid);
-    $is_current_revision = $node_current_revision->vid == $node->vid;
+    $is_current_revision = $node->isActiveRevision();
 
     // There should be at least two revisions. If the vid of the given node
-    // and the vid of the current revision differ, then we already have two
+    // and the vid of the active revision differ, then we already have two
     // different revisions so there is no need for a separate database check.
-    // Also, if you try to revert to or delete the current revision, that's
+    // Also, if you try to revert to or delete the active revision, that's
     // not good.
     if ($is_current_revision && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) {
       $access[$cid] = FALSE;
@@ -1641,8 +1641,8 @@ function _node_revision_access(Node $node, $op = 'view', $account = NULL) {
       $access[$cid] = TRUE;
     }
     else {
-      // First check the access to the current revision and finally, if the
-      // node passed in is not the current revision then access to that, too.
+      // First check the access to the active revision and finally, if the
+      // node passed in is not the active revision then access to that, too.
       $access[$cid] = node_access($op, $node_current_revision, $account) && ($is_current_revision || node_access($op, $node, $account));
     }
   }
diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc
index 2d1b771fe3..23f9c2ffcc 100644
--- a/core/modules/node/node.pages.inc
+++ b/core/modules/node/node.pages.inc
@@ -953,6 +953,9 @@ function node_revision_revert_confirm($form, $form_state, $node_revision) {
 function node_revision_revert_confirm_submit($form, &$form_state) {
   $node_revision = $form['#node_revision'];
   $node_revision->revision = 1;
+  // Make this the new active revision for the node.
+  $node_revision->setIsActiveRevision();
+
   // The revision timestamp will be updated when the revision is saved. Keep the
   // original one for the confirmation message.
   $original_revision_timestamp = $node_revision->revision_timestamp;
diff --git a/core/modules/node/tests/node.test b/core/modules/node/tests/node.test
index acaab4f432..da22a50f29 100644
--- a/core/modules/node/tests/node.test
+++ b/core/modules/node/tests/node.test
@@ -176,6 +176,7 @@ class NodeRevisionsTestCase extends BackdropWebTestCase {
       'edit any page content',
       'delete revisions',
       'delete any page content',
+      'administer nodes',
     ));
     $this->backdropLogin($web_user);
 
@@ -183,6 +184,7 @@ class NodeRevisionsTestCase extends BackdropWebTestCase {
     $node = $this->backdropCreateNode();
     $settings = get_object_vars($node);
     $settings['revision'] = 1;
+    $settings['is_active_revision'] = TRUE;
 
     $nodes = array();
     $logs = array();
@@ -200,6 +202,7 @@ class NodeRevisionsTestCase extends BackdropWebTestCase {
       // Make sure we get revision information.
       $node = node_load($node->nid);
       $settings = get_object_vars($node);
+      $settings['is_active_revision'] = TRUE;
 
       $nodes[] = $node;
     }
@@ -227,6 +230,9 @@ class NodeRevisionsTestCase extends BackdropWebTestCase {
     foreach ($logs as $log) {
       $this->assertText($log, t('Log message found.'));
     }
+ 
+    // Confirm that this is the active revision.
+    $this->assertTrue($node->isActiveRevision(), 'Third node revision is the current one.');
 
     // Confirm that revisions revert properly.
     $this->backdropPost("node/$node->nid/revisions/{$nodes[1]->vid}/revert", array(), t('Revert'));
@@ -237,6 +243,10 @@ class NodeRevisionsTestCase extends BackdropWebTestCase {
     )), 'Revision reverted.');
     $reverted_node = node_load($node->nid);
     $this->assertTrue(($nodes[1]->body[LANGUAGE_NONE][0]['value'] == $reverted_node->body[LANGUAGE_NONE][0]['value']), 'Node reverted correctly.');
+ 
+    // Confirm that this is not the current version.
+    $node = node_load($node->nid, $node->vid);
+    $this->assertFalse($node->isActiveRevision(), 'Third node revision is not the current one.');
 
     // Confirm revisions delete properly.
     $this->backdropPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete'));
@@ -262,6 +272,33 @@ class NodeRevisionsTestCase extends BackdropWebTestCase {
       '%title' => $nodes[2]->title,
       '%revision-date' => format_date($old_revision_date),
     )));
+
+    // Make a new revision and set it to not be active.
+    // This will create a new revision that is not "front facing".
+    $new_node_revision = clone $node;
+    $new_body = $this->randomName();
+    $new_node_revision->body[LANGUAGE_NONE][0]['value'] = $new_body;
+    // Save this as a non-active revision.
+    $new_node_revision->revision = TRUE;
+    $new_node_revision->is_active_revision = FALSE;
+    node_save($new_node_revision);
+
+    $this->backdropGet("node/$node->nid");
+    $this->assertNoText($new_body, t('Revision body text is not present on active version of node.'));
+
+    // Verify that the new body text is present on the revision.
+    $this->backdropGet("node/$node->nid/revisions/" . $new_node_revision->vid . "/view");
+    $this->assertText($new_body, t('Revision body text is present when loading specific revision.'));
+
+    // Verify that the non-active revision vid is greater than the active
+    // revision vid.
+    $active_revision = db_select('node', 'n')
+      ->fields('n', array('vid'))
+      ->condition('nid', $node->nid)
+      ->execute()
+      ->fetchCol();
+    $active_revision_vid = $active_revision[0];
+    $this->assertTrue($new_node_revision->vid > $active_revision_vid, 'Revision vid is greater than active revision vid.');
   }
 
   /**
@@ -2983,6 +3020,7 @@ class NodeRevisionPermissionsTestCase extends BackdropWebTestCase {
       $revision = clone $node;
       $revision->revision = 1;
       $revision->log = $this->randomName(32);
+      $revision->is_active_revision = FALSE;
       node_save($revision);
       $this->node_revisions[] = $revision;
     }
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index d249e7b53a..d439617709 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -326,7 +326,7 @@ function taxonomy_menu() {
     'title' => 'Taxonomy term',
     'title callback' => 'taxonomy_term_title',
     'title arguments' => array(2),
-    // The page callback also invokes drupal_set_title() in case the menu
+    // The page callback also invokes backdrop_set_title() in case the menu
     // router's title is overridden by a menu link.
     'page callback' => 'taxonomy_term_page',
     'page arguments' => array(2),
@@ -1979,7 +1979,7 @@ function taxonomy_build_node_index(Node $node) {
     }
   }
   // We only maintain the taxonomy index for published nodes.
-  if ($status) {
+  if ($status && $node->isActiveRevision()) {
     // Collect a unique list of all the term IDs from all node fields.
     $tid_all = array();
     foreach (field_info_instances('node', $node->type) as $instance) {

From 3bf46eb639d7770ad838b92bda63be39208faabd Mon Sep 17 00:00:00 2001
From: Tim Erickson <tim@politalk.org>
Date: Fri, 1 May 2020 21:01:07 -0500
Subject: [PATCH 02/28] Issue #2379: Allow content-type-specific customization
 of author & date information.

By @BWPanda, @stpaultim, @herbdool, @olafgrabienski, @klonos, @jenlampton, and @quicksketch.
---
 core/modules/node/node.theme.inc |  2 +-
 core/modules/node/node.types.inc | 29 +++++++++++++++++++++++------
 2 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/core/modules/node/node.theme.inc b/core/modules/node/node.theme.inc
index b0adc69457..e8227963a7 100644
--- a/core/modules/node/node.theme.inc
+++ b/core/modules/node/node.theme.inc
@@ -233,7 +233,7 @@ function template_preprocess_node(&$variables) {
   if ($node_type->settings['node_submitted']) {
     if (!isset($variables['elements']['#display_submitted']) || $variables['elements']['#display_submitted'] == TRUE) {
       $variables['display_submitted'] = TRUE;
-      $variables['submitted'] = t('!datetime by !username', array('!datetime' => $variables['date'], '!username' => $variables['name']));
+      $variables['submitted'] = token_replace($node_type->settings['node_submitted_format'], array('node' => $node));
       $variables['user_picture'] = '';
       if ($node_type->settings['node_user_picture']) {
         $variables['user_picture'] = theme('user_picture', array('account' => $node));
diff --git a/core/modules/node/node.types.inc b/core/modules/node/node.types.inc
index 8d0858d754..ec0f8a80ba 100644
--- a/core/modules/node/node.types.inc
+++ b/core/modules/node/node.types.inc
@@ -313,18 +313,34 @@ function node_type_form($form, &$form_state, $type = NULL) {
     '#default_value' => $type->settings['node_submitted'],
     '#description' => t('Author username and publish date will be displayed.'),
   );
+  $node_submitted_state = array(
+    'visible' => array(
+      'input[name="node_submitted"]' => array('checked' => TRUE),
+    ),
+  );
+  $form['display']['node_submitted_format'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Author/date format'),
+    '#default_value' => $type->settings['node_submitted_format'],
+    '#description' => t('The format to use for the author username and publish date. Example: "Submitted by [node:author] on [node:created:medium]"'),
+    '#states' => $node_submitted_state,
+  );
+  $form['display']['node_submitted_tokens'] = array(
+    '#type' => 'item', // Needed for states to work.
+    '#theme' => 'token_tree_link',
+    '#token_types' => array('node'),
+    '#global_types' => TRUE,
+    '#click_insert' => TRUE,
+    '#states' => $node_submitted_state,
+  );
   if (config_get('system.core', 'user_pictures')) {
     $form['display']['node_user_picture'] = array(
       '#type' => 'checkbox',
       '#title' => t('Display the author picture'),
       '#default_value' => $type->settings['node_user_picture'],
       '#description' => t('Author picture will be included along with username and publish date, if provided.'),
-      '#states' => array(
-        // Hide the picture settings when submitted info is disabled.
-        'visible' => array(
-          'input[name="node_submitted"]' => array('checked' => TRUE),
-        ),
-      ),
+      // Hide the picture settings when submitted info is disabled.
+      '#states' => $node_submitted_state,
     );
   }
   $form['display']['hidden_path'] = array(
@@ -592,6 +608,7 @@ function node_node_type_load(&$types) {
 
       // Submitted information.
       'node_submitted' => TRUE,
+      'node_submitted_format' => t('[node:created:medium] by [node:author]'),
       'node_user_picture' => TRUE,
 
       // Setting to prevent page from being accessed directly.

From 82f573ec5c1a16417e9ab920af031fcdf8a523c2 Mon Sep 17 00:00:00 2001
From: quicksketch <nate@quicksketch.org>
Date: Fri, 1 May 2020 21:28:41 -0700
Subject: [PATCH 03/28] Issue #4197: Allow per content type search indexing.

By @alanmels, @quicksketch, @jenlampton, @herbdool, and @docwilmot.
---
 core/modules/node/node.module         | 118 +++++++++++++++++++-------
 core/modules/node/node.theme.inc      |  22 ++---
 core/modules/node/node.types.inc      |  11 +++
 core/modules/search/search.admin.inc  |   6 +-
 core/modules/search/search.module     |  11 +--
 core/modules/search/tests/search.test |  74 +++++++++++++++-
 6 files changed, 191 insertions(+), 51 deletions(-)

diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 8d633c5a71..74f68613e7 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -88,8 +88,8 @@ function node_theme() {
       'render element' => 'elements',
       'template' => 'templates/node',
     ) + $base,
-    'node_search_admin' => array(
-      'render element' => 'form',
+    'node_search_factors' => array(
+      'render element' => 'element',
     ) + $base,
     'node_preview_banner_form' => array(
       'render element' => 'form',
@@ -1389,8 +1389,9 @@ function node_search_reset() {
  * Implements hook_search_status().
  */
 function node_search_status() {
+  $types = _node_search_get_types();
   $total = db_query('SELECT COUNT(*) FROM {node}')->fetchField();
-  $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0")->fetchField();
+  $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE (d.sid IS NULL OR d.reindex <> 0) AND n.type IN (:types)", array(':types' => $types))->fetchField();
   return array('remaining' => $remaining, 'total' => $total);
 }
 
@@ -1401,19 +1402,35 @@ function node_search_admin() {
   $config = config('search.settings');
 
   // Output form for defining rank factor weights.
-  $form['content_ranking'] = array(
+  $form['node_settings'] = array(
     '#type' => 'fieldset',
-    '#title' => t('Content ranking'),
+    '#title' => t('Content settings'),
+    '#states' => array(
+      'visible' => array(
+        '[name*="search_active_modules[node]"]' => array('checked' => TRUE)
+      ),
+    ),
+    '#element_validate' => array('node_search_admin_validate'),
   );
-  $form['content_ranking']['#theme'] = 'node_search_admin';
-  $form['content_ranking']['info'] = array(
-    '#value' => '<em>' . t('Influence is a numeric multiplier used in ordering search results. A higher number means the corresponding factor has more influence on search results; zero means the factor is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
+  $form['node_settings']['node_types'] = array(
+    '#title' => t('Enabled content types'),
+    '#type' => 'checkboxes',
+    '#default_value' => _node_search_get_types(),
+    '#options' => node_type_get_names(),
+    '#description' => t('Only enabled content types will be available in search results. Disabled content types will not be indexed in the future.'),
+  );
+
+  $form['node_settings']['factors'] = array(
+    '#type' => 'item',
+    '#title' => t('Content rankings'),
+    '#theme' => 'node_search_factors',
+    '#help' => '<em>' . t('Influence is a numeric multiplier used in ordering search results. A higher number means the corresponding factor has more influence on search results; zero means the factor is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>',
   );
 
   // Note: reversed to reflect that higher number = higher ranking.
   $options = backdrop_map_assoc(range(0, 10));
   foreach (module_invoke_all('ranking') as $var => $values) {
-    $form['content_ranking']['factors']['node_rank_' . $var] = array(
+    $form['node_settings']['factors']['node_rank_' . $var] = array(
       '#title' => $values['title'],
       '#type' => 'select',
       '#options' => $options,
@@ -1422,10 +1439,27 @@ function node_search_admin() {
   }
 
   // Sort factors by rank, highest first.
-  backdrop_sort($form['content_ranking']['factors'], array('#default_value' => SORT_NUMERIC), SORT_DESC);
+  backdrop_sort($form['node_settings']['factors'], array('#default_value' => SORT_NUMERIC), SORT_DESC);
   return $form;
 }
 
+/**
+ * Element validation callback for the search admin form.
+ */
+function node_search_admin_validate($element, &$form_state) {
+  // Turn the saved value into an unindexed array.
+  $form_state['values']['node_types'] = array_keys(array_filter($form_state['values']['node_types']));
+
+  // Ensure at least one value was checked.
+  if (empty($form_state['values']['node_types'])) {
+    form_set_error('node_types', t('Please select at least one content type to search, or completely disable searching content.'));
+  }
+  // If all types are checked, save an empty array indicating all types are enabled.
+  elseif (count($form_state['values']['node_types']) === count(node_type_get_names())) {
+    $form_state['values']['node_types'] = array();
+  }
+}
+
 /**
  * Implements hook_search_execute().
  */
@@ -2384,9 +2418,11 @@ function node_page_view(Node $node) {
  * Implements hook_update_index().
  */
 function node_update_index() {
-  $limit = (int)config_get('search.settings', 'search_cron_limit');
+  $limit = (int) config_get('search.settings', 'search_cron_limit');
 
-  $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit, array(), array('target' => 'slave'));
+  // Index selected content types.
+  $enabled_types = _node_search_get_types();
+  $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE (d.sid IS NULL OR d.reindex <> 0) AND n.type IN (:enabled_types) ORDER BY d.reindex ASC, n.nid ASC", 0, $limit, array(':enabled_types' => $enabled_types), array('target' => 'slave'));
   $nids = $result->fetchCol();
   if (!$nids) {
     return;
@@ -2426,6 +2462,21 @@ function _node_index_node(Node $node) {
   search_index($node->nid, 'node', $text);
 }
 
+/**
+ * Get the list of node types that are enabled for indexing.
+ *
+ * @return array
+ *   An unindexed array of node type machine names.
+ */
+function _node_search_get_types() {
+  $node_types = config_get('search.settings', 'node_types');
+  // If no types are in the config file, it means all types are enabled.
+  if (empty($node_types)) {
+    $node_types = array_keys(node_type_get_names());
+  }
+  return $node_types;
+}
+
 /**
  * Implements hook_form_FORM_ID_alter().
  *
@@ -2466,9 +2517,12 @@ function node_form_search_form_alter(&$form, $form_state) {
 
     // Node types:
     $types = array();
+    $search_content_types = _node_search_get_types();
     foreach (node_type_get_types() as $type => $detail) {
-      if (!$detail->settings['hidden_path'] || user_access('view hidden paths') ) {
-        $types[$type] = $detail->name;
+      if (in_array($type, $search_content_types)) {
+        if (!$detail->settings['hidden_path'] || user_access('view hidden paths') ) {
+          $types[$type] = $detail->name;
+        }
       }
     }
     $form['advanced']['type'] = array(
@@ -3085,38 +3139,41 @@ function _node_query_node_access_alter($query, $type) {
 }
 
 /**
- * Exclude nodes from the search query where the node has a hidden path
- * and the current user doesn't have permission to bypass the restriction.
+ * Exclude nodes from the search query in the special situations.
+ *
+ * - The node type is disabled in the search settings.
+ * - The node type has hidden paths and the current user doesn't have permission to bypass the restriction.
  */
 function _node_search_query_alter(QueryAlterableInterface $query) {
-  global $user;
-
-  if (in_array('view hidden paths', user_role_permissions($user->roles))) {
-    return;
-  }
-
-  $search = FALSE;
-  $node = FALSE;
+  $search_table = FALSE;
+  $node_type = FALSE;
 
   foreach ($query->getTables() as $alias => $table) {
     if ($table['table'] == 'search_index') {
-      $search = $alias;
+      $search_table = $alias;
     }
     elseif ($table['table'] == 'node') {
-      $node = $alias;
+      $node_type = $alias;
     }
   }
 
-  if ($node && $search) {
+  if ($node_type && $search_table) {
+    $enabled_types = _node_search_get_types();
+    $all_types = node_type_get_types();
+    $access_hidden_paths = user_access('view hidden paths');
+
     $excluded_content_types = array();
-    foreach (node_type_get_types() as $type => $detail) {
-      if ($detail->settings['hidden_path']) {
+    foreach ($all_types as $type => $detail) {
+      if (!$access_hidden_paths && $detail->settings['hidden_path']) {
+        $excluded_content_types[] = $type;
+      }
+      elseif (!in_array($type, $enabled_types)) {
         $excluded_content_types[] = $type;
       }
     }
 
     if (!empty($excluded_content_types)) {
-      $query->condition($node . '.type', array($excluded_content_types), 'NOT IN');
+      $query->condition($node_type . '.type', array($excluded_content_types), 'NOT IN');
     }
   }
 }
@@ -3778,4 +3835,3 @@ function node_build_tempstore_id() {
   }
   return $node_tempstore_id;
 }
-
diff --git a/core/modules/node/node.theme.inc b/core/modules/node/node.theme.inc
index e8227963a7..4896dc6923 100644
--- a/core/modules/node/node.theme.inc
+++ b/core/modules/node/node.theme.inc
@@ -37,27 +37,27 @@ function theme_node_preview_banner_form($variables) {
  *
  * @param $variables
  *   An associative array containing:
- *   - form: A render element representing the form.
+ *   - element: A render element representing the content rankings portion of the form.
  *
  * @see node_search_admin()
  * @ingroup themeable
  */
-function theme_node_search_admin($variables) {
-  $form = $variables['form'];
-
-  $output = backdrop_render($form['info']);
+function theme_node_search_factors($variables) {
+  $element = $variables['element'];
 
+  $rows = array();
   $header = array(t('Factor'), t('Influence'));
-  foreach (element_children($form['factors']) as $key) {
+  foreach (element_children($element) as $key) {
     $row = array();
-    $row[] = $form['factors'][$key]['#title'];
-    $form['factors'][$key]['#title_display'] = 'invisible';
-    $row[] = backdrop_render($form['factors'][$key]);
+    $row[] = $element[$key]['#title'];
+    $element[$key]['#title_display'] = 'invisible';
+    $row[] = backdrop_render($element[$key]);
     $rows[] = $row;
   }
+  $output = '';
+  $output .= theme('help', array('markup' => $element['#help']));
   $output .= theme('table', array('header' => $header, 'rows' => $rows));
-
-  $output .= backdrop_render_children($form);
+  $output .= backdrop_render_children($element);
   return $output;
 }
 
diff --git a/core/modules/node/node.types.inc b/core/modules/node/node.types.inc
index ec0f8a80ba..f931f01f75 100644
--- a/core/modules/node/node.types.inc
+++ b/core/modules/node/node.types.inc
@@ -487,6 +487,17 @@ function node_type_form_submit($form, &$form_state) {
     watchdog('node', 'Added content type %name.', array('%name' => $type->name), WATCHDOG_NOTICE, l(t('view'), 'admin/structure/types'));
   }
 
+  // Make the new content type indexable by default if Search module is enabled.
+  if (module_exists('search')) {
+    $search_content_types = config_get('search.settings', 'content_types');
+    // If no types are specified, then all types are enabled and there's no need to make any changes.
+    if ($search_content_types) {
+      $search_content_types[] = $type->type;
+      natsort($search_content_types);
+      config_set('search.settings', 'content_types', $search_content_types);
+    }
+  }
+
   $form_state['redirect'] = 'admin/structure/types';
   return;
 }
diff --git a/core/modules/search/search.admin.inc b/core/modules/search/search.admin.inc
index 5f36712913..413229b7b4 100644
--- a/core/modules/search/search.admin.inc
+++ b/core/modules/search/search.admin.inc
@@ -55,7 +55,6 @@ function _search_get_module_names() {
 function search_admin_settings($form, &$form_state) {
   $config = config('search.settings');
 
-
   $form['active'] = array(
     '#type' => 'fieldset',
     '#title' => t('Search items')
@@ -77,8 +76,9 @@ function search_admin_settings($form, &$form_state) {
     '#type' => 'radios',
     '#default_value' => $config->get('search_default_module'),
     '#options' => $module_options,
-    '#description' => t('Only one type of item will be searched by default. This selection will determine what appears at http://www.example.com/search.')
+    '#description' => t('Only one type of item will be searched by default. This selection will determine what appears at <a href="@search">@search</a>.', array('@search' => url('search', array('absolute' => TRUE)))),
   );
+
   $form['logging'] = array(
     '#type' => 'fieldset',
     '#title' => t('Logging')
@@ -112,7 +112,6 @@ function search_admin_settings($form, &$form_state) {
     '#description' => t('<p><em>The search index is not cleared immediately when invalidated, instead it will be systematically updated during cron runs. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed.</em></p>'),
   );
 
-
   // Collect some stats
   $remaining = 0;
   $total = 0;
@@ -226,6 +225,7 @@ function search_admin_settings_submit($form, &$form_state) {
   }
 
   $config->save();
+  backdrop_set_message(t('The configuration options have been saved.'));
 }
 
 /**
diff --git a/core/modules/search/search.module b/core/modules/search/search.module
index a05faaa513..17ba7b094b 100644
--- a/core/modules/search/search.module
+++ b/core/modules/search/search.module
@@ -412,13 +412,14 @@ function search_dirty($word = NULL) {
  * @see search_dirty()
  */
 function search_cron() {
-  // We register a shutdown function to ensure that search_total is always up
-  // to date.
+  // We register a shutdown function to ensure that search_total is always up-to-date.
   backdrop_register_shutdown_function('search_update_totals');
 
-  foreach (config_get('search.settings', 'search_active_modules') as $module) {
-    // Update word index
-    module_invoke($module, 'update_index');
+  $search_active_modules = config_get('search.settings', 'search_active_modules');
+  if (isset($search_active_modules)) {
+    foreach ($search_active_modules as $module) {
+      module_invoke($module, 'update_index');
+    }
   }
 }
 
diff --git a/core/modules/search/tests/search.test b/core/modules/search/tests/search.test
index 35dc1fbd15..241f17978a 100644
--- a/core/modules/search/tests/search.test
+++ b/core/modules/search/tests/search.test
@@ -1348,7 +1348,7 @@ class SearchConfigSettingsForm extends BackdropWebTestCase {
     parent::setUp('search', 'search_extra_type');
 
     // Login as a user that can create and search content.
-    $this->search_user = $this->backdropCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks', 'access site reports'));
+    $this->search_user = $this->backdropCreateUser(array('search content', 'use advanced search', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks', 'access site reports'));
     $this->backdropLogin($this->search_user);
 
     // Delete the two default nodes.
@@ -1505,6 +1505,78 @@ class SearchConfigSettingsForm extends BackdropWebTestCase {
       }
     }
   }
+
+  /**
+   * Verify enabling/disabling of certain node types.
+   */
+  function testSearchNodeTypes() {
+    $new_type1 = $this->backdropCreateContentType();
+    $new_type2 = $this->backdropCreateContentType();
+    $new_type3 = $this->backdropCreateContentType();
+
+    // Create 2 nodes that should both be indexed.
+    $node1 = $this->backdropCreateNode(array(
+      'title' => 'Rabbits',
+      'body' => array(LANGUAGE_NONE => array(array('value' => "The bunny's ears were fuzzy."))),
+      'type' => $new_type1->type,
+    ));
+    $node2 = $this->backdropCreateNode(array(
+      'title' => 'Llamas',
+      'body' => array(LANGUAGE_NONE => array(array('value' => "Llamas are fuzzy all over."))),
+      'type' => $new_type2->type,
+    ));
+
+    // Update the search index.
+    module_invoke_all('update_index');
+    search_update_totals();
+
+    // Disable types 2 and 3.
+    $edit['node_types[page]'] = TRUE;
+    $edit['node_types[post]'] = TRUE;
+    $edit['node_types[' . $new_type1->type . ']'] = TRUE;
+    $edit['node_types[' . $new_type2->type . ']'] = FALSE;
+    $edit['node_types[' . $new_type3->type . ']'] = FALSE;
+    $this->backdropPost('admin/config/search/settings', $edit, t('Save configuration'));
+
+    // Create a node of the third type that should not be indexed at all.
+    $node3 = $this->backdropCreateNode(array(
+      'title' => 'Fish',
+      'body' => array(LANGUAGE_NONE => array(array('value' => "These creatures are scaly but beautiful. Not fuzzy."))),
+      'type' => $new_type3->type,
+    ));
+
+    // Update the search index.
+    module_invoke_all('update_index');
+    search_update_totals();
+
+    // Check that the names of the node types are not found on the search page,
+    // as they should not be visible in the advanced search.
+    $this->backdropGet('search/node/fuzzy');
+    $this->assertText($node1->type, 'Enabled first node type found on advanced search form.');
+    $this->assertNoText($node2->type, 'Disabled second node type not found on advanced search form.');
+    $this->assertNoText($node3->type, 'Disabled third node type not found on advanced search form.');
+
+    // Test searching all 3.
+    $this->backdropGet('search/node/fuzzy');
+    $this->assertText($node1->title, 'Fuzzy bunny ears found in search. Node type is enabled.');
+    $this->assertNoText($node2->title, 'Fuzzy llamas not found in search. Node type is disabled.');
+    $this->assertNoText($node3->title, 'Scaly fish not found in search. Node type is disabled.');
+
+    // Check that node 2 is still in the index even after it was disabled.
+    $result = db_select('search_index', 'i')
+      ->extend('SearchQuery')
+      ->searchexpression($node2->title, 'node')
+      ->execute();
+    $this->assertEqual($result->rowCount(), 1, 'Node correctly still indexed after disabling its type.');
+
+    // Check that node 3 is not in the index, as it was disabled before create.
+    $result = db_select('search_index', 'i')
+      ->extend('SearchQuery')
+      ->searchexpression($node3->title, 'node')
+      ->execute();
+    $this->assertEqual($result->rowCount(), 0, 'New nodes of disabled type are not added to search index.');
+  }
+
 }
 
 /**

From 3489233cbc95869d1748c27f875bbca5e14d10f0 Mon Sep 17 00:00:00 2001
From: quicksketch <nate@quicksketch.org>
Date: Fri, 1 May 2020 21:42:29 -0700
Subject: [PATCH 04/28] Issue #1032: New Text Format Configuration UI with
 Filter Dialogs.

By @docwilmot, @quicksketch, @jenlampton, @BWPanda, and @klonos.
---
 core/modules/ckeditor/tests/ckeditor.test     |   2 +-
 core/modules/filter/css/filter.admin.css      |  20 +
 core/modules/filter/css/filter.css            |  14 +-
 core/modules/filter/filter.admin.inc          | 345 +++++++++++++++---
 core/modules/filter/filter.module             |  92 ++++-
 core/modules/filter/filter.theme.inc          |   9 +-
 core/modules/filter/js/filter.admin.js        |  55 ++-
 .../filter/js/filter.filtered_html.admin.js   |  20 +-
 core/modules/filter/tests/filter.test         |  18 +-
 9 files changed, 445 insertions(+), 130 deletions(-)
 create mode 100644 core/modules/filter/css/filter.admin.css

diff --git a/core/modules/ckeditor/tests/ckeditor.test b/core/modules/ckeditor/tests/ckeditor.test
index 553d2b6c29..c0b9fb4f43 100644
--- a/core/modules/ckeditor/tests/ckeditor.test
+++ b/core/modules/ckeditor/tests/ckeditor.test
@@ -100,7 +100,7 @@ class CKEditorTestCase extends BackdropWebTestCase {
     );
     $this->backdropPost('admin/config/content/formats/ckeditor', array(
       'editor_settings[toolbar]' => json_encode($toolbar),
-      'filters[filter_html][settings][allowed_html]' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <h3> <h4> <h5> <p> <img> <figure> <figcaption> <table> <thead> <tbody> <tr> <td> <th>',
+      'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <h3> <h4> <h5> <p> <img> <figure> <figcaption> <table> <thead> <tbody> <tr> <td> <th>',
     ), t('Save configuration'));
     $this->backdropGet('node/add/article');
     $settings = $this->backdropGetSettings();
diff --git a/core/modules/filter/css/filter.admin.css b/core/modules/filter/css/filter.admin.css
new file mode 100644
index 0000000000..0820f2f68a
--- /dev/null
+++ b/core/modules/filter/css/filter.admin.css
@@ -0,0 +1,20 @@
+/**
+ * Styles used when configuring a text format.
+ */
+#filter-order .filter-info {
+  padding: 0 0 0 3em;
+  white-space: normal;
+}
+[dir="rtl"] #filter-order .filter-info {
+  padding: 0 3em 0 0;
+}
+#filter-order .disabled-row,
+#filter-order .disabled-row .description {
+  color: #ccc;
+}
+#filter-order .form-submit {
+  margin: 0;
+}
+input[name="allowed_html"] {
+  width: 100%;
+}
diff --git a/core/modules/filter/css/filter.css b/core/modules/filter/css/filter.css
index 585694ea37..d4c3a425e5 100644
--- a/core/modules/filter/css/filter.css
+++ b/core/modules/filter/css/filter.css
@@ -1,3 +1,6 @@
+/**
+ * Styles used when displaying a formatted_html field or rich text editor.
+ */
 .text-format-wrapper .form-item {
   margin-bottom: 0;
 }
@@ -16,17 +19,6 @@
   margin-top: 0.5em;
 }
 
-#filter-order tr .form-item {
-  padding: 0.5em 0 0 3em;
-  white-space: normal;
-}
-#filter-order tr .form-type-checkbox .description {
-  padding: 0 0 0 2.5em;
-}
-input#edit-filters-filter-html-settings-allowed-html {
-  width: 100%;
-}
-
 .tips {
   margin-top: 0;
   margin-bottom: 0;
diff --git a/core/modules/filter/filter.admin.inc b/core/modules/filter/filter.admin.inc
index ea71a10bfd..dc23a73e7e 100644
--- a/core/modules/filter/filter.admin.inc
+++ b/core/modules/filter/filter.admin.inc
@@ -181,6 +181,16 @@ function filter_admin_format_page($format = NULL) {
  * @ingroup forms
  */
 function filter_admin_format_form($form, &$form_state, $format) {
+  // Use a format stored in tempstore if available.
+  if ($stored_format = filter_get_format_tempstore($format->format)) {
+    filter_admin_set_message($stored_format);
+    $format = $stored_format;
+  }
+  // If editing an already-existing format.
+  elseif (isset($format->format)) {
+    filter_set_format_tempstore($format);
+  }
+
   $is_fallback = ($format->format == filter_fallback_format());
   $editors = filter_get_editors();
   if (isset($form_state['editor_info'])) {
@@ -190,12 +200,24 @@ function filter_admin_format_form($form, &$form_state, $format) {
     $editor_info = ($format->editor && isset($editors[$format->editor])) ? $editors[$format->editor] : NULL;
   }
 
+  $form['messages'] = array(
+    '#theme' => 'status_messages',
+    '#messages' => '',
+    '#weight' => -100,
+    // Prefix/suffix used to identify in AJAX requests.
+    '#prefix' => '<div id="filter-messages">',
+    '#suffix' => '</div>',
+  );
+
   $form_state['format'] = $format;
   $form_state['editor_info'] = $editor_info;
+
   $form['#validate'] = array();
   $form['#submit'] = array();
   $form['#tree'] = TRUE;
   $form['#attached']['library'][] = array('filter', 'filter.admin');
+  $form['#attached']['library'][] = array('filter', 'filter.filtered_html.admin');
+  $form['#attached']['library'][] = array('system', 'backdrop.dialog');
 
   $form['name'] = array(
     '#type' => 'textfield',
@@ -292,7 +314,7 @@ function filter_admin_format_form($form, &$form_state, $format) {
   // Add user role access selection.
   $form['roles'] = array(
     '#type' => 'checkboxes',
-    '#title' => t('Roles allowed to use this text format'),
+    '#title' => t('Roles'),
     '#options' => array_map('check_plain', user_roles()),
     '#disabled' => $is_fallback,
   );
@@ -313,13 +335,11 @@ function filter_admin_format_form($form, &$form_state, $format) {
   // Retrieve available filters and load all configured filters for existing
   // text formats.
   $all_filter_info = filter_get_filters();
-  $filters = $format->filters;
 
-  // Prepare filters for form sections.
+  // Create an empty filter object for new/unconfigured filters.
   foreach ($all_filter_info as $name => $filter_info) {
-    // Create an empty filter object for new/unconfigured filters.
-    if (!isset($filters[$name])) {
-      $filters[$name] = (object) array(
+    if (!isset($format->filters[$name])) {
+      $format->filters[$name] = (object) array(
         'format' => $format->format,
         'module' => $filter_info['module'],
         'name' => $name,
@@ -329,73 +349,65 @@ function filter_admin_format_form($form, &$form_state, $format) {
       );
     }
   }
-  $form['#filters'] = $filters;
 
-  // Filter status.
-  $form['filters']['status'] = array(
+  // Filter order (tabledrag).
+  $form['filters'] = array(
     '#type' => 'item',
-    '#title' => t('Enabled filters'),
-    '#prefix' => '<div id="filters-status-wrapper">',
-    '#suffix' => '</div>',
+    '#title' => t('Filter settings'),
+    '#theme' => 'filter_admin_format_filter_order',
   );
   foreach ($all_filter_info as $name => $filter_info) {
-    $form['filters']['status'][$name] = array(
+    $filter_config = $format->filters[$name];
+    $form['filters'][$name]['status'] = array(
       '#type' => 'checkbox',
+      '#default_value' => $filter_config->status,
+      '#attributes' => array('class' => array('filter-status')),
+    );
+    if (isset($filter_info['settings callback'])) {
+      $form['filters'][$name]['configure_button'] = array(
+        '#type' => 'submit',
+        '#name' => $name,
+        '#validate' => array('filter_admin_format_filter_settings_form_redirect_validate'),
+        '#submit' => array('filter_admin_format_filter_settings_form_redirect'),
+        '#ajax' => array(
+          'callback' => 'filter_admin_format_filter_settings_form_ajax',
+          'effect' => 'fade',
+        ),
+        '#value' => t('Configure'),
+      );
+    }
+    $form['filters'][$name]['filter'] = array(
+      '#type' => 'item',
       '#title' => $filter_info['title'],
-      '#default_value' => $filters[$name]->status,
-      '#parents' => array('filters', $name, 'status'),
-      '#description' => $filter_info['description'],
-      '#weight' => $filter_info['weight'],
+      '#description' => _filter_admin_format_get_tips($format, $name),
+      '#wrapper_attributes' => array(
+        'class' => array('filter-info'),
+        'data-filter-name' => $name,
+      ),
     );
-  }
 
-  // Filter order (tabledrag).
-  $form['filters']['order'] = array(
-    '#type' => 'item',
-    '#title' => t('Filter processing order'),
-    '#theme' => 'filter_admin_format_filter_order',
-  );
-  foreach ($all_filter_info as $name => $filter_info) {
-    $form['filters']['order'][$name]['filter'] = array(
-      '#markup' => $filter_info['title'],
-    );
-    $form['filters']['order'][$name]['weight'] = array(
+    $form['filters'][$name]['weight'] = array(
       '#type' => 'weight',
       '#title' => t('Weight for @title', array('@title' => $filter_info['title'])),
       '#title_display' => 'invisible',
       '#delta' => 50,
-      '#default_value' => $filters[$name]->weight,
+      '#default_value' => $filter_config->weight,
       '#parents' => array('filters', $name, 'weight'),
     );
-    $form['filters']['order'][$name]['#weight'] = $filters[$name]->weight;
+    $form['filters'][$name]['#weight'] = $filter_config->weight;
   }
 
-  // Filter settings.
-  $form['filter_settings_title'] = array(
-    '#type' => 'item',
-    '#title' => t('Filter settings'),
-  );
-  $form['filter_settings'] = array(
-    '#type' => 'vertical_tabs',
+  // This hidden field is used to store allowed HTML tags for the filter_html
+  // filter. This is necessary to allow JS to respond to changes in editor
+  // buttons, changing allowed tags in response.
+  $filter_html = $format->filters['filter_html'];
+  $form['allowed_html'] = array(
+    '#type' => 'hidden',
+    '#maxlength' => 1024,
+    '#attributes' => array('id' => array('allowed-html')),
+    '#default_value' => $filter_html->settings['allowed_html'],
   );
 
-  foreach ($all_filter_info as $name => $filter_info) {
-    if (isset($filter_info['settings callback'])) {
-      $function = $filter_info['settings callback'];
-      $settings_form = $function($form, $form_state, $filters[$name], $format);
-      if (!empty($settings_form)) {
-        $form['filters']['settings'][$name] = array(
-          '#type' => 'fieldset',
-          '#title' => $filter_info['title'],
-          '#parents' => array('filters', $name, 'settings'),
-          '#weight' => $filter_info['weight'],
-          '#group' => 'filter_settings',
-        );
-        $form['filters']['settings'][$name] += $settings_form;
-      }
-    }
-  }
-
   $form['#validate'][] = 'filter_admin_format_form_validate';
   $form['#submit'][] = 'filter_admin_format_form_submit';
 
@@ -404,10 +416,191 @@ function filter_admin_format_form($form, &$form_state, $format) {
     '#type' => 'submit',
     '#value' => t('Save configuration'),
   );
+  $form['actions']['cancel'] = array(
+    '#type' => 'submit',
+    '#value' => t('Cancel'),
+    '#validate' => array(),
+    '#submit' => array('filter_admin_format_form_cancel'),
+  );
 
   return $form;
 }
 
+/**
+ * Submit handler for filter_admin_format_form() that cancels in-progress work.
+ */
+function filter_admin_format_form_cancel($form, &$form_state) {
+  $format = $form_state['format'];
+
+  if (isset($format->format)) {
+    filter_clear_format_tempstore($format->format);
+  }
+  $form_state['redirect'] = 'admin/config/content/formats';
+}
+
+/**
+ * Validation for filter "Configure" buttons in filter_admin_format_form().
+ */
+function filter_admin_format_filter_settings_form_redirect_validate($form, &$form_state) {
+  // Throw a form error if there is no machine name yet, as we need this to set
+  // up the URLs for settings forms.
+  if (empty($form_state['values']['format'])) {
+    form_set_error('format', t('Please enter a format name before configuring filter settings.'));
+  }
+}
+
+/**
+ * Submit handler for filter "Configure" buttons in filter_admin_format_form().
+ */
+function filter_admin_format_filter_settings_form_redirect($form, &$form_state) {
+  $trigger = $form_state['triggering_element'];
+  $name = $trigger['#name'];
+
+  // Save any changes to the allowed HTML values made by the front-end.
+  if (isset($form_state['format']->filters['filter_html'])) {
+    $form_state['format']->filters['filter_html']->settings['allowed_html'] = $form_state['values']['allowed_html'];
+  }
+
+  // Populate a format name if creating a new format.
+  if (empty($form_state['format']->format)) {
+    $form_state['format']->format = $form_state['values']['format'];
+  }
+
+  filter_set_format_tempstore($form_state['format']);
+
+  $form_state['redirect'] = 'admin/config/content/formats/' . $form_state['format']->format . '/filter-settings/' . $name;
+}
+
+/**
+ * Ajax handler: Opens the dialog to configure a text format filter.
+ */
+function filter_admin_format_filter_settings_form_ajax($form, &$form_state) {
+  // If there are form errors, show them and scroll to the top of the page.
+  if (form_get_errors()) {
+    $commands[] = ajax_command_html('#filter-messages', theme('status_messages'));
+    $commands[] = ajax_command_redirect('#filter-messages');
+  }
+  // If no form errors, open the configure dialog.
+  else {
+    $trigger = $form_state['triggering_element'];
+    $name = $trigger['#name'];
+    $format = $form_state['format'];
+
+    $configure_form = backdrop_get_form('filter_admin_format_filter_settings_form', $format, $name);
+    $title = t('Configure filter');
+    $options = array(
+      'dialogClass' => 'filter-dialog',
+      'width' => '90%',
+    );
+
+    $html = '';
+    $html .= theme('status_messages');
+    $html .= '<div data-filter-name="' . $name . '" class="filter-dialog-settings">';
+    $html .= backdrop_render($configure_form);
+    $html .= '</div>';
+
+    $commands = array();
+    $commands[] = ajax_command_html('#filter-messages', '');
+    $commands[] = ajax_command_open_modal_dialog($title, $html, $options);
+  }
+
+  return array('#type' => 'ajax', '#commands' => $commands);
+}
+
+/**
+ * Returns the configuration form for an individual configurable filter.
+ */
+function filter_admin_format_filter_settings_form($form, &$form_state, $format, $filter_name) {
+  form_load_include($form_state, 'inc', 'filter', 'filter.admin');
+  $form_state['format'] = $format;
+  $form_state['filter_name'] = $filter_name;
+
+  $all_filter_info = filter_get_filters();
+  $filter_info = $all_filter_info[$filter_name];
+  $filter = $format->filters[$filter_name];
+
+  if (isset($filter_info['settings callback'])) {
+    $function = $filter_info['settings callback'];
+    $settings_form = $function($form, $form_state, $filter, $format);
+  }
+
+  $settings_form['filter'] = array(
+    '#type' => 'value',
+    '#value' => $filter->name,
+  );
+  $settings_form['actions']['#type'] = 'actions';
+  $settings_form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Update'),
+    '#validate' => array(),
+    '#submit' => array('filter_admin_format_filter_settings_update'),
+    '#ajax' => array(
+      'callback' => 'filter_admin_format_filter_settings_ajax',
+      'progress' => array(
+        'type' => 'throbber',
+        'message' => NULL,
+      ),
+    ),
+  );
+
+  $settings_form['actions']['cancel'] = array(
+    '#type' => 'link',
+    '#title' => t('Cancel'),
+    '#href' => '',
+    '#attributes' => array(
+      // Adding button classes puts the link in the button area of the dialog,
+      // while the special "dialog-cancel" class closes the dialog.
+      'class' => array('button', 'button-secondary', 'form-submit', 'dialog-cancel'),
+    ),
+  );
+
+  return $settings_form;
+}
+
+/**
+ * Form submit handler for filter_admin_format_filter_settings_form().
+ */
+function filter_admin_format_filter_settings_update($form, &$form_state) {
+  form_state_values_clean($form_state);
+  unset($form_state['values']['filter']);
+  $filter_name = $form_state['filter_name'];
+  $format = $form_state['format'];
+
+  $format->filters[$filter_name]->settings = $form_state['values'];
+  $format->updated = TRUE;
+  filter_set_format_tempstore($format);
+  filter_admin_set_message($format);
+
+  $form_state['redirect'] = 'admin/config/content/formats/' . $format->format;
+}
+
+/**
+ * Ajax handler for buttons in filter_admin_format_filter_settings_form().
+ */
+function filter_admin_format_filter_settings_ajax($form, &$form_state) {
+  $filter_name = $form_state['filter_name'];
+  $format = $form_state['format'];
+
+  $commands = array();
+
+  // Update the filter tips for the configured filter.
+  $cloned_format = clone($format);
+  $cloned_format->editor = FALSE;
+  $tips = _filter_admin_format_get_tips($format, $filter_name);
+  if (isset($tips)) {
+    $commands[] = ajax_command_html('.filter-info[data-filter-name="' . $filter_name . '"] .description', $tips);
+  }
+
+  if (isset($form_state['values']['allowed_html'])) {
+    // Update the tag list stored in the main form hidden element.
+    $commands[] = ajax_command_invoke('#allowed-html', 'val', array($form_state['values']['allowed_html']));
+    $commands[] = ajax_command_invoke('#allowed-html', 'trigger', array('change.filter-update'));
+  }
+  $commands[] = ajax_command_html('#filter-messages', theme('status_messages'));
+  $commands[] = ajax_command_close_modal_dialog();
+  return array('#type' => 'ajax', '#commands' => $commands);
+}
+
 /**
  * Form validation handler for filter_admin_format_form().
  *
@@ -469,6 +662,10 @@ function filter_admin_format_form_submit($form, &$form_state) {
   unset($form_state['values']['filter_settings']);
   unset($form_state['values']['actions']);
 
+  // Save allowed HTML tags from the hidden field in the event JS modified them.
+  $form_state['values']['filters']['filter_html']['settings']['allowed_html'] = $form_state['values']['allowed_html'];
+  unset($form_state['values']['allowed_html']);
+
   // Add the submitted form values to the text format, and save it.
   $format = $form_state['format'];
   foreach ($form_state['values'] as $key => $value) {
@@ -499,6 +696,7 @@ function filter_admin_format_form_submit($form, &$form_state) {
       break;
   }
 
+  filter_clear_format_tempstore($format->format);
   $form_state['redirect'] = 'admin/config/content/formats';
 }
 
@@ -787,3 +985,40 @@ function filter_editor_file_upload_settings_form($format) {
 
   return $form;
 }
+
+/**
+ * Sets a page message for changed filter tempstore values.
+ */
+function filter_admin_set_message($format) {
+  if (!empty($format->updated)) {
+    $message = t('This form has unsaved changes. Click "Save configuration" to make changes permanent or "Cancel" to discard changes.');
+    backdrop_set_message($message, 'warning', FALSE);
+  }
+}
+
+/**
+ * Get filter tips for an individual filter within a text format.
+ *
+ * This will return tips as though any editor were disabled.
+ *
+ * @param stdClass $format
+ *   The format from which to pull filter tips.
+ * @param string $filter_name
+ *   The machine name of the filter from which to get tips.
+ */
+function _filter_admin_format_get_tips($format, $filter_name) {
+  // Temporarily disable editor setting.
+  $editor_settings = $format->editor;
+  $format->editor = FALSE;
+
+  $filter_info = filter_get_filters();
+
+  if (isset($filter_info[$filter_name]['tips callback'])) {
+    $tips = $filter_info[$filter_name]['tips callback']($format->filters[$filter_name], $format, FALSE);
+  }
+
+  // Restore editor settings.
+  $format->editor = $editor_settings;
+
+  return isset($tips) ? $tips : NULL;
+}
diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module
index 8492c5dab8..90cd2e3795 100644
--- a/core/modules/filter/filter.module
+++ b/core/modules/filter/filter.module
@@ -146,6 +146,14 @@ function filter_menu() {
     'access arguments' => array(4),
     'file' => 'filter.admin.inc',
   );
+  $items['admin/config/content/formats/%filter_format/filter-settings/%'] = array(
+    'title callback' => 'filter_admin_format_title',
+    'title arguments' => array(4),
+    'page callback' => 'backdrop_get_form',
+    'page arguments' => array('filter_admin_format_filter_settings_form', 4, 6),
+    'access arguments' => array('administer filters'),
+    'file' => 'filter.admin.inc',
+  );
   return $items;
 }
 
@@ -254,7 +262,7 @@ function filter_format_load($format_id, $load_if_disabled = FALSE) {
 }
 
 /**
- * Saves a text format object to the database.
+ * Builds a text format object from initial values.
  *
  * @param $format
  *   A format object having the properties:
@@ -278,10 +286,10 @@ function filter_format_load($format_id, $load_if_disabled = FALSE) {
  *     - settings: (optional) An array of configured settings for the filter.
  *       See hook_filter_info() for details.
  *
- * @return
- *   SAVED_NEW or SAVED_UPDATED.
+ * @return $format
+ *   A text format object suitable for saving to configuration.
  */
-function filter_format_save($format) {
+function filter_format_build_format($format) {
   $format->name = trim($format->name);
   $format->cache = _filter_format_is_cacheable($format);
   if (!isset($format->status)) {
@@ -316,6 +324,40 @@ function filter_format_save($format) {
     $format->filters[$name] = $filter;
   }
 
+  return $format;
+}
+
+/**
+ * Saves a text format object to configuration.
+ *
+ * @param $format
+ *   A format object having the properties:
+ *   - format: A machine-readable name representing the ID of the text format
+ *     to save. If this corresponds to an existing text format, that format
+ *     will be updated; otherwise, a new format will be created.
+ *   - name: The title of the text format.
+ *   - status: (optional) An integer indicating whether the text format is
+ *     enabled (1) or not (0). Defaults to 1.
+ *   - weight: (optional) The weight of the text format, which controls its
+ *     placement in text format lists. If omitted, the weight is set to 0.
+ *   - filters: (optional) An associative, multi-dimensional array of filters
+ *     assigned to the text format, keyed by the name of each filter and using
+ *     the properties:
+ *     - weight: (optional) The weight of the filter in the text format. If
+ *       omitted, either the currently stored weight is retained (if there is
+ *       one), or the filter is assigned a weight of 10, which will usually
+ *       put it at the bottom of the list.
+ *     - status: (optional) A boolean indicating whether the filter is
+ *       enabled in the text format. If omitted, the filter will be disabled.
+ *     - settings: (optional) An array of configured settings for the filter.
+ *       See hook_filter_info() for details.
+ *
+ * @return
+ *   SAVED_NEW or SAVED_UPDATED.
+ */
+function filter_format_save($format) {
+  $format = filter_format_build_format($format);
+
   // Save the filter format.
   $config = config('filter.format.' . $format->format);
   $is_new = $config->isNew();
@@ -656,7 +698,7 @@ function filter_library_info() {
       $module_path . '/js/filter.admin.js' => array('group' => JS_THEME, 'aggregate' => FALSE,),
     ),
     'css' => array(
-      $module_path . '/css/filter.css' => array(),
+      $module_path . '/css/filter.admin.css' => array('group' => CSS_THEME, 'aggregate' => FALSE),
     ),
   );
   $libraries['filter.filtered_html.admin'] = array(
@@ -1813,6 +1855,45 @@ function filter_parse_file_fids($text) {
   return $fids;
 }
 
+
+/**
+ * Get a format currently being edited from the tempstore.
+ *
+ * @param string $name
+ *   The machine name of the format item.
+ */
+function filter_get_format_tempstore($name) {
+  $caches = &backdrop_static(__FUNCTION__, array());
+  if (!isset($caches[$name])) {
+    // Try loading from tempstore first.
+    $item = tempstore_get('filter_store', $name);
+    $caches[$name] = $item;
+  }
+
+  return $caches[$name];
+}
+
+/**
+ * Store changes to a format in the temporary store.
+ *
+ * @param $item
+ *   The format item to save into tempstore.
+ */
+function filter_set_format_tempstore($item) {
+  if (empty($item->format)) {
+    return;
+  }
+
+  tempstore_set('filter_store', $item->format, $item, 604800);
+}
+
+/**
+ * Remove an item from the object cache.
+ */
+function filter_clear_format_tempstore($name) {
+  tempstore_clear('filter_store', $name);
+}
+
 /**
  * @defgroup standard_filters Standard filters
  * @{
@@ -1881,7 +1962,6 @@ function filter_filter_info() {
  * Filter settings callback for the HTML content filter.
  */
 function _filter_html_settings($form, &$form_state, $filter, $format) {
-  $settings['#attached']['library'][] = array('filter', 'filter.filtered_html.admin');
   $settings['allowed_html'] = array(
     '#type' => 'textfield',
     '#title' => t('Allowed HTML tags'),
diff --git a/core/modules/filter/filter.theme.inc b/core/modules/filter/filter.theme.inc
index c1bbd311d7..97c27a8531 100644
--- a/core/modules/filter/filter.theme.inc
+++ b/core/modules/filter/filter.theme.inc
@@ -139,16 +139,21 @@ function theme_filter_admin_format_filter_order($variables) {
   $rows = array();
   foreach (element_children($element, TRUE) as $name) {
     $element[$name]['weight']['#attributes']['class'][] = 'filter-order-weight';
+    $row_class =  $element[$name]['status']['#default_value'] ? 'draggable' : 'draggable disabled-row';
+
     $rows[] = array(
       'data' => array(
         backdrop_render($element[$name]['filter']),
+        backdrop_render($element[$name]['status']),
+        backdrop_render($element[$name]['configure_button']),
         backdrop_render($element[$name]['weight']),
       ),
-      'class' => array('draggable'),
+      'class' => array($row_class),
     );
   }
   $output = backdrop_render_children($element);
-  $output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'filter-order')));
+  $header = array(t('Filter'), t('Enabled'), t('Configure'), '');
+  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'filter-order')));
   backdrop_add_tabledrag('filter-order', 'order', 'sibling', 'filter-order-weight', NULL, NULL, TRUE);
 
   return $output;
diff --git a/core/modules/filter/js/filter.admin.js b/core/modules/filter/js/filter.admin.js
index 845e1ce609..bc49d8e832 100644
--- a/core/modules/filter/js/filter.admin.js
+++ b/core/modules/filter/js/filter.admin.js
@@ -7,41 +7,28 @@
 
 Backdrop.behaviors.filterStatus = {
   attach: function (context, settings) {
-    $('#filters-status-wrapper input.form-checkbox', context).once('filter-status', function () {
-      var $checkbox = $(this);
-      // Retrieve the tabledrag row belonging to this filter.
-      var $row = $('#' + $checkbox.attr('id').replace(/-status$/, '-weight'), context).closest('tr');
-      // Retrieve the vertical tab belonging to this filter.
-      var tab = $('#' + $checkbox.attr('id').replace(/-status$/, '-settings'), context).data('verticalTab');
-
-      // Bind click handler to this checkbox to conditionally show and hide the
-      // filter's tableDrag row and vertical tab pane.
-      $checkbox.bind('click.filterUpdate', function () {
-        if ($checkbox.is(':checked')) {
-          $row.show();
-          if (tab) {
-            tab.tabShow().updateSummary();
-          }
-        }
-        else {
-          $row.hide();
-          if (tab) {
-            tab.tabHide().updateSummary();
-          }
-        }
-        // Restripe table after toggling visibility of table row.
-        Backdrop.tableDrag['filter-order'].restripeTable();
-      });
+    // TableDrag is required and we should be on the filters admin page.
+    if (typeof Backdrop.tableDrag == 'undefined' || typeof Backdrop.tableDrag['filter-order'] == 'undefined') {
+      return;
+    }
 
-      // Attach summary for configurable filters (only for screen-readers).
-      if (tab) {
-        tab.fieldset.backdropSetSummary(function (tabContext) {
-          return $checkbox.is(':checked') ? Backdrop.t('Enabled') : Backdrop.t('Disabled');
-        });
-      }
+    $('.filter-container', context).each(function() {
+      $(this).addClass('filter-container-initial');
+    });
+
+    // Suppress tabledrag messages.
+    Backdrop.theme.tableDragChangedWarning = function () {
+      return '';
+    };
+    Backdrop.theme.tableDragChangedMarker = function () {
+      return '';
+    }
 
-      // Trigger our bound click handler to update elements to initial state.
-      $checkbox.triggerHandler('click.filterUpdate');
+    // Toggle class on row if checkbox changed.
+    $('#filter-order input.filter-status:checkbox', context).change(function (event) {
+      var isChecked = !$(this).prop('checked');
+      $(this).closest('tr').toggleClass('disabled-row', isChecked);
+      Backdrop.filterConfiguration.update();
     });
   }
 };
@@ -513,7 +500,7 @@ Backdrop.behaviors.initializeFilterConfiguration = {
   attach: function (context, settings) {
     var $context = $(context);
 
-    $context.find('#filters-status-wrapper input.form-checkbox').once('filter-editor-status').each(function () {
+    $context.find('#filter-order input.form-checkbox').once('filter-editor-status').each(function () {
       var $checkbox = $(this);
       var nameAttribute = $checkbox.attr('name');
 
diff --git a/core/modules/filter/js/filter.filtered_html.admin.js b/core/modules/filter/js/filter.filtered_html.admin.js
index a538c33455..a16a4fc564 100644
--- a/core/modules/filter/js/filter.filtered_html.admin.js
+++ b/core/modules/filter/js/filter.filtered_html.admin.js
@@ -14,7 +14,7 @@
 if (Backdrop.filterConfiguration) {
   Backdrop.filterConfiguration.liveSettingParsers.filter_html = {
     getRules: function () {
-      var currentValue = $('#edit-filters-filter-html-settings-allowed-html').val();
+      var currentValue = $('#allowed-html').val();
       var rules = [];
       var rule;
 
@@ -56,9 +56,9 @@ Backdrop.behaviors.filterFilterHtmlUpdating = {
 
   attach: function (context, settings) {
     var that = this;
-    $(context).find('[name="filters[filter_html][settings][allowed_html]"]').once('filter-filter_html-updating').each(function () {
+    $(context).find('#allowed-html').once('filter-filter_html-updating').each(function () {
       that.$allowedHTMLFormItem = $(this);
-      that.$allowedHTMLDescription = that.$allowedHTMLFormItem.closest('.form-item').find('.description');
+      that.$allowedHTMLDescription = $('[data-filter-name="filter_html"]').find('.description');
       that.userTags = that._parseSetting(this.value);
 
       // Update the new allowed tags based on added text editor features.
@@ -80,17 +80,11 @@ Backdrop.behaviors.filterFilterHtmlUpdating = {
             }
           });
 
-      // When the allowed tags list is manually changed, update userTags.
-      that.$allowedHTMLFormItem.on('change.updateUserTags', function () {
-        var tagList = that._parseSetting(this.value);
-        var userTags = [];
-        for (var n in tagList) {
-          if ($.inArray(tagList[n], that.autoTags) === -1) {
-            userTags.push(tagList[n]);
-          }
-        }
-        that.userTags = userTags;
+      // Update the user tags when the allowed-html field is manually changed.
+      that.$allowedHTMLFormItem.on('change.filter-update', function() {
+        that.userTags = that._parseSetting(this.value);
       });
+
     });
   },
 
diff --git a/core/modules/filter/tests/filter.test b/core/modules/filter/tests/filter.test
index c522d54ff7..cf0a8fdb68 100644
--- a/core/modules/filter/tests/filter.test
+++ b/core/modules/filter/tests/filter.test
@@ -307,10 +307,11 @@ class FilterAdminTestCase extends BackdropWebTestCase {
 
     // Add an additional tag.
     $edit = array();
-    $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>';
-    $this->backdropPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
+    $edit['allowed_html'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>';
+    $this->backdropPost('admin/config/content/formats/' . $filtered . '/filter-settings/filter_html', $edit, t('Update'));
+    $this->backdropPost(NULL, array(), t('Save configuration'));
     $this->backdropGet('admin/config/content/formats/' . $filtered);
-    $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], 'Allowed HTML tag added.');
+    $this->assertFieldByName('allowed_html', $edit['allowed_html'], 'Allowed HTML tag added.');
 
     $result = db_query('SELECT * FROM {cache_filter}')->fetchObject();
     $this->assertFalse($result, 'Cache cleared.');
@@ -393,10 +394,11 @@ class FilterAdminTestCase extends BackdropWebTestCase {
     // Clean up.
     // Allowed tags.
     $edit = array();
-    $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>';
-    $this->backdropPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
+    $edit['allowed_html'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>';
+    $this->backdropPost('admin/config/content/formats/' . $filtered . '/filter-settings/filter_html', $edit, t('Update'));
+    $this->backdropPost(NULL, array(), t('Save configuration'));
     $this->backdropGet('admin/config/content/formats/' . $filtered);
-    $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], 'Changes reverted.');
+    $this->assertFieldByName('allowed_html', $edit['allowed_html'], 'Allowed HTML tag added.');
 
     // Full HTML.
     $edit = array();
@@ -459,9 +461,9 @@ class FilterAdminTestCase extends BackdropWebTestCase {
   function testUrlFilterAdmin() {
     // The form does not save with an invalid filter URL length.
     $edit = array(
-      'filters[filter_url][settings][filter_url_length]' => $this->randomName(4),
+      'filter_url_length' => $this->randomName(4),
     );
-    $this->backdropPost('admin/config/content/formats/filtered_html', $edit, t('Save configuration'));
+    $this->backdropPost('admin/config/content/formats/filtered_html/filter-settings/filter_url', $edit, t('Update'));
     $this->assertNoRaw(t('The text format %format has been updated.', array('%format' => 'Filtered HTML')));
   }
 }

From 7845e460cb0b4d85d0a86b334cfc09bc2439d074 Mon Sep 17 00:00:00 2001
From: quicksketch <nate@quicksketch.org>
Date: Wed, 6 May 2020 12:53:17 -0700
Subject: [PATCH 05/28] Backdrop 1.16.0 Preview.

---
 core/includes/bootstrap.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 35e4f09d23..7353df8395 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -7,7 +7,7 @@
 /**
  * The current system version.
  */
-define('BACKDROP_VERSION', '1.16.x-dev');
+define('BACKDROP_VERSION', '1.16.0-preview');
 
 /**
  * Core API compatibility.

From b01cbd1b2aba60ecce8a012fd53fdb87d61f788b Mon Sep 17 00:00:00 2001
From: quicksketch <nate@quicksketch.org>
Date: Wed, 6 May 2020 13:03:50 -0700
Subject: [PATCH 06/28] Reverting version string.

---
 core/includes/bootstrap.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 7353df8395..35e4f09d23 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -7,7 +7,7 @@
 /**
  * The current system version.
  */
-define('BACKDROP_VERSION', '1.16.0-preview');
+define('BACKDROP_VERSION', '1.16.x-dev');
 
 /**
  * Core API compatibility.

From d7286476835f002e1ac796d4ca55a70162a9c757 Mon Sep 17 00:00:00 2001
From: docwilmot <docwilmot@users.noreply.github.com>
Date: Sun, 10 May 2020 20:35:24 -0500
Subject: [PATCH 07/28] Issue #4371: Fix file path placeholder for Layouts.

By @docwilmot, @indigoxela, and @jenlampton.
---
 core/modules/layout/includes/layout.layout.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/modules/layout/includes/layout.layout.inc b/core/modules/layout/includes/layout.layout.inc
index cde6229d10..d16158bc3f 100644
--- a/core/modules/layout/includes/layout.layout.inc
+++ b/core/modules/layout/includes/layout.layout.inc
@@ -56,7 +56,7 @@ function layout_layout_context_info() {
       'file/%file',
       'file/%file/view',
     ),
-    'path placeholder' => 'file',
+    'path placeholder' => '%file',
     'load callback' => 'file_load',
   );
   $info['overrides_path'] = array(

From 8fa074f61bdd7aca1ce5a28d23921b4a678d7e20 Mon Sep 17 00:00:00 2001
From: indigoxela <xela@indigofloat.at>
Date: Mon, 11 May 2020 03:41:20 +0200
Subject: [PATCH 08/28] Issue #4357: Prevent "theme hook not found" on date
 field creation.

By @indigoxela, @olafgrabienski, @docwilmot, and @quicksketch.
---
 core/modules/date/date.admin.inc | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/modules/date/date.admin.inc b/core/modules/date/date.admin.inc
index 2d68cffdde..bcce67b908 100644
--- a/core/modules/date/date.admin.inc
+++ b/core/modules/date/date.admin.inc
@@ -416,9 +416,9 @@ function _date_field_widget_settings_form($field, $instance) {
     '#title' => t('Position of date part labels'),
     '#description' => $description,
   );
-  $form['advanced']['text_parts'] = array(
-    '#theme' => $widget['type'] == 'date_select' ? 'date_text_parts' : '',
-  );
+  if ($widget['type'] == 'date_select') {
+    $form['advanced']['text_parts']['#theme'] = 'date_text_parts';
+  }
   $text_parts = (array) $settings['text_parts'];
   foreach (date_granularity_names() as $key => $value) {
     if ($widget['type'] == 'date_select') {

From e322ad78da620654f9fca54d0be43fdab14f7bfb Mon Sep 17 00:00:00 2001
From: docwilmot <docwilmot@users.noreply.github.com>
Date: Sun, 10 May 2020 20:57:36 -0500
Subject: [PATCH 09/28] Issue #4295: Rebuild Color module CSS on cache flush.

By @docwilmot, @hosef, @klonos, @stpaultim, and @quicksketch.
---
 core/modules/admin_bar/admin_bar.inc |  4 +++
 core/modules/color/color.module      | 39 ++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)

diff --git a/core/modules/admin_bar/admin_bar.inc b/core/modules/admin_bar/admin_bar.inc
index 759ddacba8..c21f7a178d 100644
--- a/core/modules/admin_bar/admin_bar.inc
+++ b/core/modules/admin_bar/admin_bar.inc
@@ -823,6 +823,10 @@ function _admin_bar_flush_cache($name = NULL) {
       break;
 
     case 'assets':
+      // Rebuild all color palettes.
+      if (module_exists('color')) {
+        color_rebuild_settings();
+      }
       // Change query-strings on css/js files to enforce reload for all users.
       _backdrop_flush_css_js();
 
diff --git a/core/modules/color/color.module b/core/modules/color/color.module
index 2eec32615e..59377c259f 100644
--- a/core/modules/color/color.module
+++ b/core/modules/color/color.module
@@ -524,6 +524,45 @@ function color_scheme_form_submit($form, &$form_state) {
   $form_state['values']['color'] = color_save_configuration($theme, $info, $palette);
 }
 
+/**
+ * Implements hook_flush_caches().
+ */
+function color_flush_caches() {
+  // Rebuild all color palettes.
+  color_rebuild_settings();
+}
+
+/**
+ * Rebuild color scheme info.
+ */
+function color_rebuild_settings($theme_name = NULL) {
+  $themes = array();
+  if ($theme_name) {
+    $themes = array($theme_name);
+  }
+  else {
+    foreach (list_themes() as $key => $data) {
+      if ($data->status) {
+        $themes[] = $key;
+      }
+    }
+  }
+
+  foreach ($themes as $theme) {
+    $theme_config_name = $theme . '.settings';
+    $theme_config = config($theme_config_name);
+    if ($theme_config && $new_color_config = $theme_config->get('color')) {
+      $color_info = color_get_info($theme);
+      if ($color_info) {
+        $palette = isset($new_color_config['palette']) ? $new_color_config['palette'] : NULL;
+        $new_color_info = color_save_configuration($theme, $color_info, $palette);
+        $theme_config->set('color', $new_color_info);
+        $theme_config->save();
+      }
+    }
+  }
+}
+
 /**
  * Generates the necessary CSS and images based on a color palette.
  *

From 45f8bc1d1f15d418cbe4ca2fbc67dbcc2055f963 Mon Sep 17 00:00:00 2001
From: indigoxela <xela@indigofloat.at>
Date: Mon, 11 May 2020 04:08:24 +0200
Subject: [PATCH 10/28] Issue #4266: Better UX for setting passwords on account
 creation form.

By @indigoxela, @herbdool, @olafgrabienski, @klonos, and @jenlampton.
---
 core/modules/dblog/tests/dblog.test |  1 +
 core/modules/user/tests/user.test   |  1 +
 core/modules/user/user.module       | 72 +++++++++++++++++++++++------
 3 files changed, 59 insertions(+), 15 deletions(-)

diff --git a/core/modules/dblog/tests/dblog.test b/core/modules/dblog/tests/dblog.test
index 906c318aa5..14ae90a75d 100644
--- a/core/modules/dblog/tests/dblog.test
+++ b/core/modules/dblog/tests/dblog.test
@@ -225,6 +225,7 @@ class DBLogTestCase extends BackdropWebTestCase {
     $edit['mail'] = $name . '@example.com';
     $edit['pass'] = $pass;
     $edit['status'] = 1;
+    $edit['notify'] = FALSE;
     $this->backdropPost('admin/people/create', $edit, t('Create new account'));
     $new_user_time = time();
     $this->assertResponse(200);
diff --git a/core/modules/user/tests/user.test b/core/modules/user/tests/user.test
index 0980b8be5a..e190ae7d47 100644
--- a/core/modules/user/tests/user.test
+++ b/core/modules/user/tests/user.test
@@ -2337,6 +2337,7 @@ class UserRolesAssignmentTestCase extends BackdropWebTestCase {
       'name' => $this->randomName(),
       'mail' => $this->randomName() . '@example.com',
       'pass' => $pass = $this->randomString(),
+      'notify' => FALSE,
       "roles[$role_name]" => $role_name,
     );
     $this->backdropPost('admin/people/create', $edit, t('Create new account'));
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 48dbac0393..92ba418c3a 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -327,6 +327,25 @@ function user_save($account) {
   return $account->save();
 }
 
+/**
+ * Element validate handler for the password field.
+ *
+ * Set a default value if one-time login link goes out,
+ * set a form error if not and password value is empty.
+ */
+function user_pass_required_validate($element, &$form_state, $form) {
+  $values = $form_state['values'];
+  if ($values['notify'] == 1) {
+    $form_state['values']['pass'] = user_password(22);
+  }
+  else {
+    if (empty($values['pass']) && $values['pass'] !== '0') {
+      $message = t('!name field is required.', array('!name' => $element['#title']));
+      form_set_error('pass', $message);
+    }
+  }
+}
+
 /**
  * Verify the syntax of the given name.
  */
@@ -802,15 +821,44 @@ function user_account_form(&$form, &$form_state) {
       $form['#validate'][] = 'user_validate_current_pass';
     }
   }
-  elseif (!config_get('system.core', 'user_email_verification') || $admin_users) {
-    $form['account']['pass'] = array(
-      '#type' => 'password',
-      '#title' => t('Password'),
-      '#description' => t('Provide a password for the new account.'),
-      '#password_toggle' => TRUE,
-      '#password_strength' => TRUE,
-      '#required' => TRUE,
-    );
+  else {
+    if (!config_get('system.core', 'user_email_verification') && !$admin_users) {
+      // Someone registers a new account and may set a password directly.
+      $form['account']['pass'] = array(
+        '#type' => 'password',
+        '#title' => t('Password'),
+        '#description' => t('Provide a password for the new account.'),
+        '#password_toggle' => TRUE,
+        '#password_strength' => TRUE,
+        '#required' => TRUE,
+      );
+    }
+    if ($admin_users) {
+      // An admin creates an account.
+      $form['account']['notify'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Notify user of new account'),
+        '#default_value' => 1,
+        '#description' => t('The user will receive an email with a one-time login link which leads to a page where they can set their password.'),
+      );
+      $form['account']['pass'] = array(
+        '#type' => 'password',
+        '#title' => t('Password'),
+        '#description' => t('Provide a password for the new account.'),
+        '#password_toggle' => TRUE,
+        '#password_strength' => TRUE,
+        '#required' => FALSE,
+        '#element_validate' => array('user_pass_required_validate'),
+        '#states' => array(
+          'visible' => array(
+            ':input[name="notify"]' => array('checked' => FALSE),
+          ),
+          'required' => array(
+            ':input[name="notify"]' => array('checked' => FALSE),
+          ),
+        ),
+      );
+    }
   }
 
   $form['account_settings'] = array(
@@ -849,12 +897,6 @@ function user_account_form(&$form, &$form_state) {
     '#value' => 'authenticated',
   );
 
-  $form['account_settings']['notify'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Notify user of new account'),
-    '#access' => $register && $admin_users,
-  );
-
   // Signature.
   $form['signature_settings'] = array(
     '#type' => 'fieldset',

From 482a79116b5deaffc9a04e92df8f125efc791d97 Mon Sep 17 00:00:00 2001
From: docwilmot <docwilmot@users.noreply.github.com>
Date: Sun, 10 May 2020 21:16:24 -0500
Subject: [PATCH 11/28] Issue #4227: Do not allow image field min dimensions to
 be larger than max dimensions.

By @docwilmot, @jenlampton, @klonos, @indigoxela, and @quicksketch.
---
 core/modules/image/image.field.inc  | 27 +++++++++++++++++++++++++--
 core/modules/image/tests/image.test | 26 ++++++++++++++++++++++++++
 2 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc
index a1ad2e7949..a3b003a7fb 100644
--- a/core/modules/image/image.field.inc
+++ b/core/modules/image/image.field.inc
@@ -85,6 +85,9 @@ function image_field_instance_settings_form($field, $instance) {
   // Use the file field instance settings form as a basis.
   $form = file_field_instance_settings_form($field, $instance);
 
+  $form['#element_validate'] = isset($form['#element_validate']) ? $form['#element_validate'] : array();
+  $form['#element_validate'][] = 'image_field_instance_settings_form_validate';
+
   // Add maximum and minimum resolution settings.
   $max_dimensions = explode('x', $settings['max_dimensions']) + array('', '');
   $max_dimensions[0] = empty($max_dimensions[0]) ? NULL : $max_dimensions[0];
@@ -96,7 +99,7 @@ function image_field_instance_settings_form($field, $instance) {
     '#weight' => 4.1,
     '#field_prefix' => '<div class="container-inline">',
     '#field_suffix' => '</div>',
-    '#description' => t('The maximum allowed image dimensions expressed as WIDTHxHEIGHT. Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of <a href="http://en.wikipedia.org/wiki/Exchangeable_image_file_format">EXIF data</a> in the image.'),
+    '#description' => t('The maximum allowed image dimensions expressed as <code>WIDTH</code> x <code>HEIGHT</code>. Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of <a href="http://en.wikipedia.org/wiki/Exchangeable_image_file_format">EXIF data</a> in the image.'),
   );
   $form['max_dimensions']['x'] = array(
     '#type' => 'number',
@@ -127,7 +130,7 @@ function image_field_instance_settings_form($field, $instance) {
     '#weight' => 4.2,
     '#field_prefix' => '<div class="container-inline">',
     '#field_suffix' => '</div>',
-    '#description' => t('The minimum allowed image dimensions expressed as WIDTHxHEIGHT. Leave blank for no restriction. If a smaller image is uploaded, it will be rejected.'),
+    '#description' => t('The minimum allowed image dimensions expressed as <code>WIDTH</code> x <code>HEIGHT</code>. Leave blank for no restriction. If a smaller image is uploaded, it will be rejected.'),
   );
   $form['min_dimensions']['x'] = array(
     '#type' => 'number',
@@ -200,6 +203,26 @@ function image_field_instance_settings_form($field, $instance) {
   return $form;
 }
 
+/**
+ * Element validate handler for image_field_instance_settings_form().
+ */
+function image_field_instance_settings_form_validate($element, &$form_state, $complete_form) {
+  $min_dimensions = explode('x', $form_state['values']['instance']['settings']['min_dimensions']) + array('', '');
+  $max_dimensions = explode('x', $form_state['values']['instance']['settings']['max_dimensions']) + array('', '');
+
+  $min_dimensions_x = (int) $min_dimensions[0];
+  $min_dimensions_y = (int) $min_dimensions[1];
+  $max_dimensions_x = (int) $max_dimensions[0];
+  $max_dimensions_y = (int) $max_dimensions[1];
+
+  // Ensure that min dimensions are not set to be bigger than max dimensions,
+  // but only if max dimensions have been specified. This allows to set min
+  // dimensions, without having to restrict max dimensions.
+  if ((!empty($max_dimensions_x) && $min_dimensions_x > $max_dimensions_x) || (!empty($max_dimensions_y) && $min_dimensions_y > $max_dimensions_y)) {
+    form_set_error('instance][settings][min_dimensions', t('The minimum image dimensions cannot be bigger than its maximum dimensions.'));
+  }
+}
+
 /**
  * Element validate function for resolution fields.
  */
diff --git a/core/modules/image/tests/image.test b/core/modules/image/tests/image.test
index 83f6919b33..7edeedb0ee 100644
--- a/core/modules/image/tests/image.test
+++ b/core/modules/image/tests/image.test
@@ -872,6 +872,32 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase {
       '%max' => $schema['fields'][$field_name .'_title']['length'],
       '%length' => $test_size,
     )));
+
+    // Check that image minimum size is not larger than maximum size.
+    $edit = array(
+      'instance[settings][max_dimensions][x]' => 100,
+      'instance[settings][min_dimensions][x]' => 200,
+    );
+    $this->backdropPost('admin/structure/types/manage/post/fields/' . $field_name, $edit, t('Save settings'));
+    $this->assertText(t('The minimum image dimensions cannot be bigger than its maximum dimensions.'));
+    // Try again with unacceptable image heights instead.
+    $edit = array(
+      'instance[settings][max_dimensions][x]' => '',
+      'instance[settings][min_dimensions][x]' => '',
+      'instance[settings][max_dimensions][y]' => 100,
+      'instance[settings][min_dimensions][y]' => 200,
+    );
+    $this->backdropPost('admin/structure/types/manage/post/fields/' . $field_name, $edit, t('Save settings'));
+    $this->assertText(t('The minimum image dimensions cannot be bigger than its maximum dimensions.'));
+    // Try again with acceptable values.
+    $edit = array(
+      'instance[settings][max_dimensions][x]' => '',
+      'instance[settings][min_dimensions][x]' => '',
+      'instance[settings][max_dimensions][y]' => 300,
+      'instance[settings][min_dimensions][y]' => 200,
+    );
+    $this->backdropPost('admin/structure/types/manage/post/fields/' . $field_name, $edit, t('Save settings'));
+    $this->assertRaw(t('Saved %field_name configuration.', array('%field_name' => $field_name)));
   }
 
   /**

From 3c9998bfd798aadc1ac4f525633a9edb1b166f18 Mon Sep 17 00:00:00 2001
From: Gregory Netsas <gregory@twinz.gr>
Date: Mon, 11 May 2020 06:04:38 +0300
Subject: [PATCH 12/28] Issue #4017: Fix "file may not be referenced" in image
 dialog for manually uploaded files.

By @klonos, @Graham-72, @jenlampton, @indigoxela, and @docwilmot.
---
 core/modules/file/file.pages.inc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/core/modules/file/file.pages.inc b/core/modules/file/file.pages.inc
index d6e796379a..d8c737da7e 100644
--- a/core/modules/file/file.pages.inc
+++ b/core/modules/file/file.pages.inc
@@ -357,6 +357,11 @@ function file_add_form_submit($form, &$form_state) {
       entity_form_submit_build_entity('file', $file, $form, $form_state);
 
       file_save($file);
+      // Record that the module (in this case, the File module) is using the
+      // file. Without the call to file_usage_add, file_managed_file_validate()
+      // produces an error upon saving the form, saying that the uploaded file
+      // may not be referenced.
+      file_usage_add($file, 'file', 'file', $file->fid);
       $form_state['file'] = $file;
       backdrop_set_message(t('@type %name was uploaded.', array('@type' => file_type_get_name($file), '%name' => $file->filename)));
     }

From 5f390821da844825a20e5ef8423d2e34078be241 Mon Sep 17 00:00:00 2001
From: docwilmot <docwilmot@users.noreply.github.com>
Date: Sun, 10 May 2020 22:08:22 -0500
Subject: [PATCH 13/28] Issue #1425: Add "Token" display format to allow
 customization of fields as tokens.

By @docwilmot, @mikemccaffrey, @herbdool, @olafgrabienski, @quinnanya, @klonos, @quicksketch, and @jenlampton.
---
 core/modules/comment/comment.module       |   4 +
 core/modules/entity/entity.module         |  11 ++
 core/modules/field/tests/field.test       | 141 +++++++++++++++++++++-
 core/modules/field/tests/field.tests.info |   6 +
 core/modules/node/node.module             |   4 +
 core/modules/taxonomy/taxonomy.module     |   4 +
 core/modules/user/user.module             |   4 +
 7 files changed, 173 insertions(+), 1 deletion(-)

diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 2d6726d7f0..bb8b936eb2 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -89,6 +89,10 @@ function comment_entity_info() {
           'label' => t('Full comment'),
           'custom settings' => FALSE,
         ),
+        'token' => array(
+          'label' => t('Tokens'),
+          'custom settings' => FALSE,
+        ),
       ),
       'static cache' => FALSE,
     ),
diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module
index c9624720fb..871ca11100 100644
--- a/core/modules/entity/entity.module
+++ b/core/modules/entity/entity.module
@@ -282,6 +282,17 @@ function entity_get_info($entity_type = NULL) {
           }
         }
 
+        // Add a token view mode if it does not already exist. Only work with
+        // fieldable entities.
+        if (!empty($entity_info[$name]['fieldable'])) {
+          if (!isset($entity_info[$name]['view modes']['token'])) {
+            $entity_info[$name]['view modes']['token'] = array(
+              'label' => t('Tokens'),
+              'custom settings' => FALSE,
+            );
+          }
+        }
+
         foreach ($entity_info[$name]['view modes'] as $view_mode => $view_mode_info) {
           $entity_info[$name]['view modes'][$view_mode] += array(
             'custom settings' => FALSE,
diff --git a/core/modules/field/tests/field.test b/core/modules/field/tests/field.test
index 8ace378f69..86bb2e1780 100644
--- a/core/modules/field/tests/field.test
+++ b/core/modules/field/tests/field.test
@@ -3667,4 +3667,143 @@ class FieldGetValueTestCase extends FieldTestCase {
     $this->assertEqual(array(), $invalid_field, 'Empty array is returned with an invalid field name.');
 
   }
-}
\ No newline at end of file
+}
+
+/**
+ * Test fields with token display mode.
+ */
+class FieldTokenTestCase extends FieldTestCase {
+  protected $profile = 'testing';
+  protected $test_field_name;
+  protected $test_date_field_name;
+  protected $admin_user;
+  protected $node;
+
+  function setUp() {
+    parent::setUp(array('node', 'field', 'field_ui', 'field_test', 'date', 'path'));
+
+    // Create test user.
+    $this->admin_user = $this->backdropCreateUser(array('access content', 'bypass node access', 'administer content types', 'administer nodes', 'administer fields', 'administer view modes'));
+    $this->backdropLogin($this->admin_user);
+
+    $this->content_type = $this->backdropCreateContentType(array(
+      'type' => 'post',
+      'name' => 'Post',
+    ));
+
+    $this->backdropGet('admin/structure/types/manage/post/display');
+    list($enable_link) = $this->xpath('//tr[contains(@class, "view-mode--token")]//a');
+    $enable_href_parts = backdrop_parse_url($enable_link['href']);
+    $this->backdropGet($enable_href_parts['path'], $enable_href_parts);
+
+    // Create a text field.
+    $this->test_field_name = backdrop_strtolower($this->randomName() . '_field_name');
+    $field = array(
+      'field_name' => $this->test_field_name,
+      'type' => 'text',
+      'cardinality' => 1
+    );
+    $instance = array(
+      'label' => $this->randomName(),
+      'field_name' => $this->test_field_name,
+      'entity_type' => 'node',
+      'bundle' => 'post',
+    );
+    $instance['display'] = array(
+      'default' => array(
+        'label' => 'above',
+        'type' => 'text_default',
+        'settings' => array(
+        ),
+        'module' => 'text',
+        'weight' => 0 ,
+      ),
+      'token' => array(
+        'label' => 'hidden',
+        'type' => 'text_default',
+        'weight' => 0,
+        'settings' => array(
+        ),
+        'module' => 'text',
+      ),
+    );
+
+    field_create_field($field);
+    field_create_instance($instance);
+
+    // Create a 'date' field.
+    $this->test_date_field_name = backdrop_strtolower($this->randomName() . '_field_name');
+    $field = array(
+      'field_name' => $this->test_date_field_name,
+      'type' => 'date',
+      'cardinality' => 1,
+      'settings' => array(
+      ),
+    );
+    $instance = array(
+      'entity_type' => 'node',
+      'field_name' => $this->test_date_field_name,
+      'label' => 'Test',
+      'bundle' => 'post',
+      'weight' => -5,
+      'widget' => array(
+        'type' => 'date_text',
+        'settings' => array(
+        ),
+        'weight' => -5,
+      ),
+      'settings' => array(
+        'default_value' => 'now',
+      ),
+    );
+
+    $instance['display'] = array(
+      'default' => array(
+        'label' => 'above',
+        'type' => 'date_default',
+        'settings' => array(
+          'format_type' => 'long',
+        ),
+        'module' => 'date',
+        'weight' => 0 ,
+      ),
+      'token' => array(
+        'label' => 'hidden',
+        'type' => 'date_default',
+        'weight' => 0,
+        'settings' => array(
+          'format_type' => 'html_year',
+        ),
+        'module' => 'date',
+      ),
+    );
+
+    field_create_field($field);
+    field_create_instance($instance);
+    config('path.settings')
+      ->set('node_post_pattern', 'posts/[node:' . $this->test_date_field_name . ']/[node:' . $this->test_field_name . ']/[node:title]')
+      ->save();
+  }
+
+  /**
+   * Test fields in token display mode.
+   */
+  function testFieldToken() {
+    $edit = array();
+    $edit['title'] = $this->randomName(8);
+    $edit[$this->test_field_name . '[und][0][value]'] = 'foo';
+    $edit[$this->test_date_field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30am';
+
+    // Create a dummy node.
+    $this->backdropPost('node/add/post', $edit, t('Save'));
+    $this->node = $this->backdropGetNodeByTitle($edit['title']);
+
+    $this->backdropGet('node/' . $this->node->nid);
+
+    $uri = entity_uri('node', $this->node);
+    backdrop_clear_path_cache($uri['path']);
+    $alias = backdrop_get_path_alias($uri['path'], LANGUAGE_NONE);
+    $expected_alias = 'posts/2010/' . $edit[$this->test_field_name . '[und][0][value]'] . '/' . backdrop_strtolower($this->node->title);
+    $this->assertIdentical($alias, $expected_alias, format_string("Alias for %source was %actual, expected %expected.", array('%source' => $uri['path'], '%actual' => $alias, '%expected' => $expected_alias)));
+  }
+}
diff --git a/core/modules/field/tests/field.tests.info b/core/modules/field/tests/field.tests.info
index a1a97b6f05..a94842f0c4 100644
--- a/core/modules/field/tests/field.tests.info
+++ b/core/modules/field/tests/field.tests.info
@@ -64,6 +64,12 @@ description = Tests field_get_value().
 group = Field API
 file = field.test
 
+[FieldTokenTestCase]
+name = Token display for Field API
+description = Tests the field in token display mode.
+group = Field API
+file = field.test
+
 [FieldViewsDataTest]
 name = Views integration for Field API
 description = Tests the Field API Views data.
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 74f68613e7..e6014c60cf 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -173,6 +173,10 @@ function node_entity_info() {
           'label' => t('RSS'),
           'custom settings' => FALSE,
         ),
+        'token' => array(
+          'label' => t('Tokens'),
+          'custom settings' => FALSE,
+        ),
       ),
     ),
   );
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index d439617709..3ecf69164d 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -104,6 +104,10 @@ function taxonomy_entity_info() {
           'label' => t('Taxonomy term page'),
           'custom settings' => FALSE,
         ),
+        'token' => array(
+          'label' => t('Tokens'),
+          'custom settings' => FALSE,
+        ),
       ),
     ),
   );
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 92ba418c3a..12e86523c9 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -131,6 +131,10 @@ function user_entity_info() {
           'label' => t('User account'),
           'custom settings' => FALSE,
         ),
+        'token' => array(
+          'label' => t('Tokens'),
+          'custom settings' => FALSE,
+        ),
       ),
     ),
   );

From 35cbbd6502168d25f0c96e77a55c98a04fea5910 Mon Sep 17 00:00:00 2001
From: docwilmot <docwilmot@users.noreply.github.com>
Date: Sun, 10 May 2020 22:12:40 -0500
Subject: [PATCH 14/28] Issue #4161: Add separate permission for administering
 flexible templates.

By @docwilmot, @jenlampton, and @stpaultim.
---
 core/modules/layout/layout.admin.inc  | 28 +++++++++++++++------------
 core/modules/layout/layout.module     | 13 +++++++++++++
 core/modules/layout/tests/layout.test | 24 +++++++++++++++++++++++
 3 files changed, 53 insertions(+), 12 deletions(-)

diff --git a/core/modules/layout/layout.admin.inc b/core/modules/layout/layout.admin.inc
index 56e4b4c7fb..400cb5be87 100644
--- a/core/modules/layout/layout.admin.inc
+++ b/core/modules/layout/layout.admin.inc
@@ -2814,14 +2814,16 @@ function _layout_settings_get_group_operations($template_info, $is_default) {
   $links = array();
 
   if (!empty($template_info['flexible'])) {
-    $links['configure'] = array(
-      'title' => t('Configure regions'),
-      'href' => 'admin/structure/layouts/settings/flexible-template/' . $template_info['name'] . '/configure',
-    );
-    $links['edit'] = array(
-      'title' => t('Configure name'),
-      'href' => 'admin/structure/layouts/settings/flexible-template/' . $template_info['name'] . '/edit',
-    );
+    if (user_access('administer flexible templates')) {
+      $links['configure'] = array(
+        'title' => t('Configure regions'),
+        'href' => 'admin/structure/layouts/settings/flexible-template/' . $template_info['name'] . '/configure',
+      );
+      $links['edit'] = array(
+        'title' => t('Configure name'),
+        'href' => 'admin/structure/layouts/settings/flexible-template/' . $template_info['name'] . '/edit',
+      );
+    }
     if (user_access('synchronize configuration')) {
       $links['export'] = array(
         'title' => t('Export'),
@@ -2832,10 +2834,12 @@ function _layout_settings_get_group_operations($template_info, $is_default) {
         ),
       );
     }
-    $links['delete'] = array(
-      'title' => t('Delete'),
-      'href' => 'admin/structure/layouts/settings/flexible-template/' . $template_info['name'] . '/delete',
-    );
+    if (user_access('administer flexible templates')) {
+      $links['delete'] = array(
+        'title' => t('Delete'),
+        'href' => 'admin/structure/layouts/settings/flexible-template/' . $template_info['name'] . '/delete',
+      );
+    }
   }
   if ($is_default) {
     $links['disable'] = array(
diff --git a/core/modules/layout/layout.module b/core/modules/layout/layout.module
index 76bcc052b2..cf406e2607 100644
--- a/core/modules/layout/layout.module
+++ b/core/modules/layout/layout.module
@@ -95,6 +95,7 @@ function layout_menu() {
   $items['admin/structure/layouts/settings/flexible-template/add'] = array(
     'title' => 'Add flexible layout template',
     'page callback' => 'layout_flexible_template_settings_add_form',
+    'access arguments' => array('administer flexible templates'),
     'type' => MENU_LOCAL_ACTION,
     'weight' => -10,
     'file' => 'layout.flexible.inc',
@@ -104,6 +105,7 @@ function layout_menu() {
     'title' => 'Configure flexible layout template',
     'page callback' => 'backdrop_get_form',
     'page arguments' => array('layout_flexible_template_settings_edit_form', 5),
+    'access arguments' => array('administer flexible templates'),
     'type' => MENU_NORMAL_ITEM,
     'weight' => -10,
     'file' => 'layout.flexible.inc',
@@ -113,6 +115,7 @@ function layout_menu() {
     'title' => 'Configure flexible layout template',
     'page callback' => 'backdrop_get_form',
     'page arguments' => array('layout_flexible_template_configure_form', 5),
+    'access arguments' => array('administer flexible templates'),
     'type' => MENU_NORMAL_ITEM,
     'weight' => -10,
     'file' => 'layout.flexible.inc',
@@ -122,6 +125,7 @@ function layout_menu() {
     'title' => 'Delete flexible layout template',
     'page callback' => 'backdrop_get_form',
     'page arguments' => array('layout_flexible_template_delete_form', 5),
+    'access arguments' => array('administer flexible templates'),
     'type' => MENU_NORMAL_ITEM,
     'weight' => -10,
     'file' => 'layout.flexible.inc',
@@ -131,6 +135,7 @@ function layout_menu() {
     'title' => 'Delete row',
     'page callback' => 'layout_flexible_template_delete_row',
     'page arguments' => array(5,7),
+    'access arguments' => array('administer flexible templates'),
     'type' => MENU_CALLBACK,
     'weight' => -10,
     'file' => 'layout.flexible.inc',
@@ -140,6 +145,7 @@ function layout_menu() {
     'title' => 'Choose region widths',
     'page callback' => 'backdrop_get_form',
     'page arguments' => array('layout_flexible_template_region_style_select', 5, 7),
+    'access arguments' => array('administer flexible templates'),
     'type' => MENU_CALLBACK,
     'weight' => -10,
     'file' => 'layout.flexible.inc',
@@ -149,6 +155,7 @@ function layout_menu() {
     'title' => 'Add row',
     'page callback' => 'backdrop_get_form',
     'page arguments' => array('layout_flexible_template_edit_row_form', 5, 7),
+    'access arguments' => array('administer flexible templates'),
     'type' => MENU_CALLBACK,
     'weight' => -10,
     'file' => 'layout.flexible.inc',
@@ -158,6 +165,7 @@ function layout_menu() {
     'title' => 'Add row',
     'page callback' => 'backdrop_get_form',
     'page arguments' => array('layout_flexible_template_edit_row_form', 5, 7, 9),
+    'access arguments' => array('administer flexible templates'),
     'type' => MENU_CALLBACK,
     'weight' => -10,
     'file' => 'layout.flexible.inc',
@@ -693,6 +701,11 @@ function layout_permission() {
       'description' => t('Add and modify layouts on pages.'),
       'restrict access' => TRUE,
     ),
+    'administer flexible templates' => array(
+      'title' => t('Administer Flexible Templates'),
+      'description' => t('Create, configure and delete flexible layout templates.'),
+      'restrict access' => TRUE,
+    ),
   );
 }
 
diff --git a/core/modules/layout/tests/layout.test b/core/modules/layout/tests/layout.test
index 5339c4c2e4..fd71305ec6 100644
--- a/core/modules/layout/tests/layout.test
+++ b/core/modules/layout/tests/layout.test
@@ -2540,6 +2540,7 @@ class LayoutFlexibleTemplateTest extends BackdropWebTestCase {
       'access administration pages',
       'administer site configuration',
       'administer layouts',
+      'administer flexible templates',
     ));
     $this->backdropLogin($this->admin_user);
   }
@@ -2879,6 +2880,29 @@ class LayoutFlexibleTemplateTest extends BackdropWebTestCase {
     ));
     $last_row_attr = (string) $last_row_attr[0][0];
     $this->assertEqual($last_row_attr, $data_row_id, 'The last row is the new row.');
+
+    // Test that a user without permissions cannot create flexible templates
+    // But can still use them.
+    $this->no_flex_user = $this->backdropCreateUser(array(
+      'access administration pages',
+      'administer site configuration',
+      'administer layouts',
+    ));
+    $this->backdropLogin($this->no_flex_user);
+
+    $this->backdropGet('admin/structure/layouts/settings');
+    $this->assertNoLink(t('Add flexible layout template'));
+    $this->assertNoLink(t('Configure regions'));
+
+    // Attempt to go to the template editor of the flexible template, and
+    // confirm no access.
+    $flexible_layout_configure_link = 'admin/structure/layouts/settings/flexible-template/' . $template_machine_name1 . '/configure';
+    $this->backdropGet($flexible_layout_configure_link);
+    $this->assertResponse(403, 'Access not allowed to flexible layout configuration pages without permission.');
+
+    // Go to the layout created with this template and confirm still has access.
+    $this->backdropGet('admin/structure/layouts/manage/' . $layout_name);
+    $this->backdropPost(NULL, array(), t('Save layout'));
   }
 
 }

From da8cbcc9991d8366f6dd9d7e3bd24cb3d5757a27 Mon Sep 17 00:00:00 2001
From: quicksketch <nate@quicksketch.org>
Date: Mon, 11 May 2020 14:32:39 -0700
Subject: [PATCH 15/28] Revert "Issue #1140: Fix fields with inline labels next
 to floated elements."

This reverts commit c053df1379855592ef17f6cbdb6ce4aca5f9ba4e.
---
 core/modules/field/css/field.css         |  8 --------
 core/modules/field/field.theme.inc       |  5 +++++
 core/modules/system/css/system.theme.css | 12 ++++++------
 3 files changed, 11 insertions(+), 14 deletions(-)

diff --git a/core/modules/field/css/field.css b/core/modules/field/css/field.css
index 1b52c4a1fc..8f7da034c7 100644
--- a/core/modules/field/css/field.css
+++ b/core/modules/field/css/field.css
@@ -3,14 +3,6 @@
 .field .field-label {
   font-weight: bold;
 }
-/**
- * Clear fields with inline labels, as the label and field items are floated.
- *
- * @see https://meiert.com/en/blog/no-clearfix/
- */
-.field-label-inline {
-  overflow: auto;
-}
 .field-label-inline .field-label,
 .field-label-inline .field-items {
   float:left; /*LTR*/
diff --git a/core/modules/field/field.theme.inc b/core/modules/field/field.theme.inc
index 6aaa6ec532..065d9243a0 100644
--- a/core/modules/field/field.theme.inc
+++ b/core/modules/field/field.theme.inc
@@ -206,6 +206,11 @@ function template_preprocess_field(&$variables, $hook) {
     'field-type-' . $variables['field_type_css'],
     'field-label-' . $element['#label_display'],
   );
+  // Add a "clearfix" class to the wrapper since we float the label and the
+  // field items in field.css if the label is inline.
+  if ($element['#label_display'] == 'inline') {
+    $variables['classes'][] = 'clearfix';
+  }
 
   // Add specific suggestions that can override the default implementation.
   $variables['theme_hook_suggestions'] = array(
diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css
index fa6adfb9c6..4e05ab9bd0 100644
--- a/core/modules/system/css/system.theme.css
+++ b/core/modules/system/css/system.theme.css
@@ -543,11 +543,11 @@ img.align-right {
 }
 
 /**
- * Clear text areas in case of floated contents.
- *
- * @see https://meiert.com/en/blog/no-clearfix/
+ * Clearfix text areas in case of floated contents
  */
-.field-type-text-long,
-.field-type-text-with-summary {
-  overflow: auto;
+.field-type-text-long:after,
+.field-type-text-with-summary:after {
+  content: '';
+  display: table;
+  clear: both;
 }

From 5447e13a78c449c0330b9daf9a5c7cd22717a5be Mon Sep 17 00:00:00 2001
From: hosef <joseph@flattandsons.com>
Date: Fri, 15 May 2020 12:12:12 -0700
Subject: [PATCH 16/28] Issue #4308: Convert deprecated code for PHP 7.4
 compatibility.

By @hosef, @indigoxela, and @quicksketch.
---
 core/includes/batch.inc                       |  2 +-
 core/includes/bootstrap.inc                   |  9 ++++---
 core/includes/common.inc                      | 21 ++++++++++-----
 core/includes/filetransfer/filetransfer.inc   |  2 +-
 core/includes/install.inc                     |  2 +-
 core/includes/pager.inc                       |  2 +-
 core/includes/path.inc                        |  4 ++-
 core/includes/token.inc                       |  2 +-
 core/modules/date/tests/date_themes.test      | 15 +++++++++++
 core/modules/field/field.crud.inc             | 12 ++++++---
 .../field/tests/field_test/field_test.module  |  6 ++++-
 .../tests/field_test/field_test.storage.inc   |  2 +-
 core/modules/field/views/field.views.inc      | 10 +++++++
 core/modules/field_ui/field_ui.admin.inc      |  7 +++--
 core/modules/file/file.module                 |  1 -
 core/modules/file/tests/file.test             | 10 +++++++
 core/modules/image/image.module               |  2 +-
 core/modules/image/tests/image.test           |  6 +++--
 core/modules/path/path.module                 | 27 ++++++++++---------
 core/modules/search/search.module             |  2 +-
 core/modules/system/system.admin.inc          | 13 ++++-----
 core/modules/system/system.module             |  2 +-
 core/modules/system/system.tokens.inc         |  4 +--
 core/modules/taxonomy/taxonomy.admin.inc      |  5 +++-
 core/modules/views_ui/views_ui.admin.inc      |  2 +-
 25 files changed, 119 insertions(+), 51 deletions(-)

diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 9613f77d69..fd492a94ee 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -453,7 +453,7 @@ function _batch_finished() {
 
   // Clean-up the session. Not needed for CLI updates.
   if (isset($_SESSION)) {
-    unset($_SESSION['batches'][$batch['id']]);
+    unset($_SESSION['batches'][$_batch['id']]);
     if (empty($_SESSION['batches'])) {
       unset($_SESSION['batches']);
     }
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 35e4f09d23..9456816441 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2014,7 +2014,7 @@ function t($string, array $args = array(), array $options = array()) {
 function format_string($string, array $args = array()) {
   // Transform arguments before inserting them.
   foreach ($args as $key => $value) {
-    switch ($key[0]) {
+    switch (substr($key, 0, 1)) {
       case '@':
         // Escaped only.
         $args[$key] = check_plain($value);
@@ -3258,7 +3258,10 @@ function _backdrop_bootstrap_sanitize_request() {
     }
 
     // If there is a query string, check its query parameters.
-    $destination_parts = backdrop_parse_url($_GET['destination']);
+    if (isset($_GET['destination'])) {
+      $destination_parts = backdrop_parse_url($_GET['destination']);
+    }
+
     if (!empty($destination_parts['query'])) {
       $sanitized_keys = _backdrop_bootstrap_sanitize_input($destination_parts['query'], $whitelist);
     }
@@ -3289,7 +3292,7 @@ function _backdrop_bootstrap_sanitize_input(&$input, $whitelist = array()) {
 
   if (is_array($input)) {
     foreach ($input as $key => $value) {
-      if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) {
+      if ($key !== '' && substr($key, 0, 1) === '#' && !in_array($key, $whitelist, TRUE)) {
         unset($input[$key]);
         $sanitized_keys[] = $key;
       }
diff --git a/core/includes/common.inc b/core/includes/common.inc
index e8d54ac786..2cd6874200 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -471,7 +471,7 @@ function backdrop_add_feed($url = NULL, $title = '') {
  */
 function backdrop_get_feeds($delimiter = "\n") {
   $feeds = backdrop_add_feed();
-  return implode($feeds, $delimiter);
+  return implode($delimiter, $feeds);
 }
 
 /**
@@ -2398,7 +2398,9 @@ function format_date($timestamp, $date_format_name = 'medium', $pattern = '', $t
   // If we have a non-custom date format use the provided date format pattern.
   if ($date_format_name != 'custom') {
     $date_format = system_date_format_load($date_format_name);
-    $pattern = isset($date_format['locales'][$langcode]) ? $date_format['locales'][$langcode] : $date_format['pattern'];
+    if (!empty($date_format)) {
+      $pattern = isset($date_format['locales'][$langcode]) ? $date_format['locales'][$langcode] : $date_format['pattern'];
+    }
   }
 
   // Fall back to medium if a format was not found.
@@ -2554,7 +2556,7 @@ function url($path = NULL, array $options = array()) {
   // path is always treated as internal by default (to prevent external link
   // injection vulnerabilities).
   if (!isset($options['external'])) {
-    $options['external'] = ($path === $_GET['q']) ? FALSE : url_is_external($path);
+    $options['external'] = (isset($_GET['q']) && $path === $_GET['q']) ? FALSE : url_is_external($path);
   }
 
   // Preserve the original path before altering or aliasing.
@@ -3993,7 +3995,12 @@ function _backdrop_build_css_path($matches, $base = NULL) {
   }
 
   // Prefix with base and remove '../' segments where possible.
-  $path = $_base . $matches[1];
+  if (is_array($matches)) {
+    $path = $_base . $matches[1];
+  }
+  else {
+    $path = $_base;
+  }
   $last = '';
   while ($path != $last) {
     $last = $path;
@@ -7184,7 +7191,7 @@ function backdrop_sort(array &$array, array $keys = array('weight'), $dir = SORT
  * Checks if the key is a property.
  */
 function element_property($key) {
-  return $key[0] == '#';
+  return substr($key, 0, 1) == '#';
 }
 
 /**
@@ -7198,7 +7205,7 @@ function element_properties($element) {
  * Checks if the key is a child.
  */
 function element_child($key) {
-  return !isset($key[0]) || $key[0] != '#';
+  return empty($key) || substr($key, 0, 1) != '#';
 }
 
 /**
@@ -7223,7 +7230,7 @@ function element_children(&$elements, $sort = FALSE) {
   $children = array();
   $sortable = FALSE;
   foreach ($elements as $key => $value) {
-    if ($key === '' || $key[0] !== '#') {
+    if ($key === '' || substr($key, 0, 1) !== '#') {
       if (is_array($value)) {
         $children[$key] = $value;
         if (isset($value['#weight'])) {
diff --git a/core/includes/filetransfer/filetransfer.inc b/core/includes/filetransfer/filetransfer.inc
index 829a67fd2b..15425cca75 100644
--- a/core/includes/filetransfer/filetransfer.inc
+++ b/core/includes/filetransfer/filetransfer.inc
@@ -313,7 +313,7 @@ abstract class FileTransfer {
     $parts = explode('/', $path);
     $chroot = '';
     while (count($parts)) {
-      $check = implode($parts, '/');
+      $check = implode('/', $parts);
       if ($this->isFile($check . '/' . backdrop_basename(__FILE__))) {
         // Remove the trailing slash.
         return substr($chroot, 0, -1);
diff --git a/core/includes/install.inc b/core/includes/install.inc
index c8909c2ba1..c208d624e3 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -1298,7 +1298,7 @@ function st($string, array $args = array(), array $options = array()) {
 
   // Transform arguments before inserting them
   foreach ($args as $key => $value) {
-    switch ($key[0]) {
+    switch (substr($key, 0, 1)) {
       // Escaped only
       case '@':
         $args[$key] = check_plain($value);
diff --git a/core/includes/pager.inc b/core/includes/pager.inc
index 32868420f5..dfb958e622 100644
--- a/core/includes/pager.inc
+++ b/core/includes/pager.inc
@@ -163,7 +163,7 @@ function theme_pager($variables) {
   global $pager_page_array, $pager_total;
 
   // Return if there is no pager to be rendered.
-  if (!isset($pager_page_array[$element])) {
+  if (!isset($pager_page_array[$element]) || empty($pager_total)) {
     return '';
   }
 
diff --git a/core/includes/path.inc b/core/includes/path.inc
index 3ec70d39d2..5d1eda8cd6 100644
--- a/core/includes/path.inc
+++ b/core/includes/path.inc
@@ -337,7 +337,9 @@ function backdrop_match_path($path, $patterns) {
  * @see request_path()
  */
 function current_path() {
-  return $_GET['q'];
+  if (isset($_GET['q'])) {
+    return $_GET['q'];
+  }
 }
 
 /**
diff --git a/core/includes/token.inc b/core/includes/token.inc
index a62034aca5..7604971776 100644
--- a/core/includes/token.inc
+++ b/core/includes/token.inc
@@ -831,7 +831,7 @@ function token_get_invalid_tokens($type, array $tokens) {
 function token_render_array(array $array, array $options = array()) {
   $rendered = array();
   foreach ($array as $key => $value) {
-    if ($key[0] === '#') {
+    if (substr($key, 0, 1) === '#') {
       continue;
     }
     $rendered[] = is_array($value) ? render($value) : (string) $value;
diff --git a/core/modules/date/tests/date_themes.test b/core/modules/date/tests/date_themes.test
index 43bf6ac507..f107317805 100644
--- a/core/modules/date/tests/date_themes.test
+++ b/core/modules/date/tests/date_themes.test
@@ -77,6 +77,21 @@ class DateThemeTestCase extends BackdropWebTestCase {
           'granularity' => array('year', 'month', 'day', 'hour', 'minute'),
         ),
       ),
+      'display' => array(
+          'label' => 'above',
+          'type' => 'date_default',
+          'settings' => array(
+            'format_type' => 'long',
+            'show_repeat_rule' => 'show',
+            'multiple_number' => '',
+            'multiple_from' => '',
+            'multiple_to' => '',
+            'fromto' => 'both',
+          ),
+          'module' => 'date',
+          'weight' => 0 ,
+      ),
+
       'show_remaining_days' => '',
     );
     $expected = '<span class="date-display-single">2019-03-20 <span class="date-display-range">';
diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc
index 42e571c803..ff4f88389a 100644
--- a/core/modules/field/field.crud.inc
+++ b/core/modules/field/field.crud.inc
@@ -212,7 +212,9 @@ function field_update_field($field) {
   // Tell the storage engine to update the field. Do this before
   // saving the new definition since it still might fail.
   $storage_type = field_info_storage_types($field['storage']['type']);
-  module_invoke($storage_type['module'], 'field_storage_update_field', $field, $prior_field, $has_data);
+  if (!empty($storage_type)) {
+    module_invoke($storage_type['module'], 'field_storage_update_field', $field, $prior_field, $has_data);
+  }
 
   // Clear caches
   field_cache_clear();
@@ -508,6 +510,10 @@ function field_create_instance($instance) {
 
   // Populate the instance default settings.
   $field = field_read_field($instance['field_name']);
+  if (empty($field)) {
+    $message = t('Attempted to create an instance of field @field_name, but that field does not exist.', array('@field_name' => $instance['field_name']));
+    throw new FieldException($message);
+  }
   $instance['settings'] += field_info_instance_settings($field['type']);
 
   // Validate the fully populated instance.
@@ -644,7 +650,7 @@ function _field_write_instance($instance, $update = FALSE) {
   }
   // Check widget module.
   $widget_type = field_info_widget_types($instance['widget']['type']);
-  $instance['widget']['module'] = $widget_type['module'];
+  $instance['widget']['module'] = isset($widget_type['module']) ? $widget_type['module'] : '';
   $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']);
 
   // Make sure there are at least display settings for the 'default' display
@@ -661,7 +667,7 @@ function _field_write_instance($instance, $update = FALSE) {
     );
     if ($display['type'] != 'hidden') {
       $formatter_type = field_info_formatter_types($display['type']);
-      $display['module'] = $formatter_type['module'];
+      $display['module'] = isset($formatter_type['module']) ? $formatter_type['module'] : '';
       $display['settings'] += field_info_formatter_settings($display['type']);
     }
     // If no weight specified, make sure the field sinks at the bottom.
diff --git a/core/modules/field/tests/field_test/field_test.module b/core/modules/field/tests/field_test/field_test.module
index 8241e04150..e7927fa914 100644
--- a/core/modules/field/tests/field_test/field_test.module
+++ b/core/modules/field/tests/field_test/field_test.module
@@ -284,7 +284,11 @@ function field_test_query_store_global_test_query_alter($query) {
  * Implements hook_field_formatter_settings_form_alter().
  */
 function field_test_field_formatter_settings_form_alter(&$element, &$form_state, $context) {
-  $settings = $context['instance']['display'][$context['view_mode']]['settings'];
+  $settings = array();
+  if (!empty($context)) {
+    $settings = $context['instance']['display'][$context['view_mode']]['settings'];
+  }
+
   $element['field_test_formatter_settings_form_alter'] = array(
     '#type' => 'textfield',
     '#title' => t('Formatter settings form alter'),
diff --git a/core/modules/field/tests/field_test/field_test.storage.inc b/core/modules/field/tests/field_test/field_test.storage.inc
index b136d59910..2a843b637d 100644
--- a/core/modules/field/tests/field_test/field_test.storage.inc
+++ b/core/modules/field/tests/field_test/field_test.storage.inc
@@ -456,7 +456,7 @@ function field_test_field_attach_delete_bundle($entity_type, $bundle, $instances
 
   foreach ($instances as $field_name => $instance) {
     $field = field_info_field($field_name);
-    if ($field['storage']['type'] == 'field_test_storage') {
+    if (isset($field['storage']['type']) && $field['storage']['type'] == 'field_test_storage') {
       $field_data = &$data[$field['field_name']];
       foreach (array('current', 'revisions') as $sub_table) {
         foreach ($field_data[$sub_table] as &$row) {
diff --git a/core/modules/field/views/field.views.inc b/core/modules/field/views/field.views.inc
index fd208d5db5..412eeb2ac6 100644
--- a/core/modules/field/views/field.views.inc
+++ b/core/modules/field/views/field.views.inc
@@ -113,6 +113,10 @@ function field_views_field_default_views_data($field) {
   // Build the relationships between the field table and the entity tables.
   foreach ($field['bundles'] as $entity => $bundles) {
     $entity_info = entity_get_info($entity);
+    if (empty($entity_info)) {
+      continue;
+    }
+
     $groups[$entity] = $entity_info['label'];
 
     // Override Node to Content.
@@ -200,6 +204,9 @@ function field_views_field_default_views_data($field) {
     $aliases = array();
     $also_known = array();
     foreach ($all_labels as $entity_name => $labels) {
+      if (empty($groups[$entity_name])) {
+        continue;
+      }
       foreach ($labels as $label_name => $true) {
         if ($type == FIELD_LOAD_CURRENT) {
           if ($group_name != $groups[$entity_name] || $label != $label_name) {
@@ -309,6 +316,9 @@ function field_views_field_default_views_data($field) {
       $aliases = array();
       $also_known = array();
       foreach ($all_labels as $entity_name => $labels) {
+        if (empty($groups[$entity_name])) {
+          continue;
+        }
         foreach ($labels as $label_name => $true) {
           if ($group_name != $groups[$entity_name] || $label != $label_name) {
             if (count($field['columns']) == 1 || $column == 'value') {
diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc
index 86ebbb7dca..8ec320eb2c 100644
--- a/core/modules/field_ui/field_ui.admin.inc
+++ b/core/modules/field_ui/field_ui.admin.inc
@@ -1087,7 +1087,7 @@ function field_ui_display_form($form, &$form_state, $entity_type, $bundle, $view
 
     $instance['display'][$view_mode]['type'] = $formatter_type;
     $formatter = field_info_formatter_types($formatter_type);
-    $instance['display'][$view_mode]['module'] = $formatter['module'];
+    $instance['display'][$view_mode]['module'] = !empty($formatter) ? $formatter['module'] : '';
     $instance['display'][$view_mode]['settings'] = $settings;
 
     // Base button element for the various formatter settings actions.
@@ -1158,7 +1158,10 @@ function field_ui_display_form($form, &$form_state, $entity_type, $bundle, $view
     }
     else {
       // Display a summary of the current formatter settings.
-      $summary = module_invoke($formatter['module'], 'field_formatter_settings_summary', $field, $instance, $view_mode);
+      $summary = '';
+      if (!empty($formatter)) {
+        $summary = module_invoke($formatter['module'], 'field_formatter_settings_summary', $field, $instance, $view_mode);
+      }
 
       // Allow other modules to alter the formatter summary.
       $context = array(
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 5dbdf10ac0..b456854619 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -3390,7 +3390,6 @@ function file_view_file($file, $display = 'full', $langcode = NULL) {
   }
 
   $formatter = $display['formatter'];
-  $settings = $display['settings'][$formatter];
 
   // Attempt to display the file with the specified display.
   // See file_display() for details.
diff --git a/core/modules/file/tests/file.test b/core/modules/file/tests/file.test
index 3e8677d7c8..da993adaad 100644
--- a/core/modules/file/tests/file.test
+++ b/core/modules/file/tests/file.test
@@ -121,6 +121,16 @@ class FileTestHelper extends BackdropWebTestCase {
     }
 
     file_type_save($type);
+
+    file_display_save($type->type, 'default', array(
+      'formatter' => 'file_field_file_download_link',
+      'settings' => array(
+        'file_field_file_download_link' => array(
+          'text' => 'Download [file:name]',
+        ),
+      ),
+    ));
+
     return $type;
   }
 
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 2d27234825..3ddbda81ed 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -1245,7 +1245,7 @@ function image_effect_save($style_name, $effect) {
   }
 
   $config->save();
-  $style = image_style_load(NULL, $style_name);
+  $style = image_style_load($style_name);
   image_style_flush($style);
   return $effect;
 }
diff --git a/core/modules/image/tests/image.test b/core/modules/image/tests/image.test
index 7edeedb0ee..f80ca1fe06 100644
--- a/core/modules/image/tests/image.test
+++ b/core/modules/image/tests/image.test
@@ -1881,8 +1881,10 @@ class ImageStyleFloodProtection extends BackdropWebTestCase {
 
     // Close the handles
     for ($n = 0; $n < $count; $n++) {
-      curl_close(${'handle' . $n});
-      curl_multi_remove_handle($multi_handle, ${'handle' . $n});
+      if (gettype(${'handle' . $n}) === 'resource') {
+        curl_multi_remove_handle($multi_handle, ${'handle' . $n});
+        curl_close(${'handle' . $n});
+      }
     }
     curl_multi_close($multi_handle);
   }
diff --git a/core/modules/path/path.module b/core/modules/path/path.module
index d840704d51..9e9bcc5d5a 100644
--- a/core/modules/path/path.module
+++ b/core/modules/path/path.module
@@ -688,18 +688,20 @@ function path_load_multiple_by_entities(array $entities) {
   // Each entity may have a different language, so group them by langcode.
   foreach ($entities as $entity) {
     $uri = $entity->uri();
-    $langcode = isset($entity->langcode) ? $entity->langcode : LANGUAGE_NONE;
-    $sources[$langcode][] = $uri['path'];
-    $map[$langcode][$uri['path']] = $entity->id();
-
-    // Provide a default path in the event no path is found.
-    $entity->path = array(
-      'pid' => NULL,
-      'source' => $uri['path'],
-      'alias' => NULL,
-      'langcode' => $langcode,
-      'auto' => FALSE,
-    );
+    if (!empty($uri)) {
+      $langcode = isset($entity->langcode) ? $entity->langcode : LANGUAGE_NONE;
+      $sources[$langcode][] = $uri['path'];
+      $map[$langcode][$uri['path']] = $entity->id();
+
+      // Provide a default path in the event no path is found.
+      $entity->path = array(
+        'pid' => NULL,
+        'source' => $uri['path'],
+        'alias' => NULL,
+        'langcode' => $langcode,
+        'auto' => FALSE,
+      );
+    }
   }
 
   // Load each set of paths by langcode, and update each entity's path property.
@@ -818,6 +820,7 @@ function path_form_element(Entity $entity) {
   $pattern = path_get_pattern_by_entity_type($entity_type, $bundle, $langcode);
   if ($pattern) {
     if (!isset($entity->path['auto'])) {
+      $entity->path['auto'] = FALSE;
       if (!$entity->isNew()) {
         module_load_include('inc', 'path');
         $uri = $entity->uri();
diff --git a/core/modules/search/search.module b/core/modules/search/search.module
index 17ba7b094b..dfbda4192c 100644
--- a/core/modules/search/search.module
+++ b/core/modules/search/search.module
@@ -1194,7 +1194,7 @@ function search_excerpt($keys, $text) {
       }
       else {
         $info = search_simplify_excerpt_match($key, $text, $included[$key], $boundary);
-        if ($info['where']) {
+        if (!empty($info) && $info['where']) {
           $p = $info['where'];
           if ($info['keyword']) {
             $foundkeys[] = $info['keyword'];
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 8bc389a3a2..44b96adae7 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -662,7 +662,7 @@ function system_modules($form, $form_state = array()) {
     // Generate link for module's configuration page, if provided.
     if ($module->status && isset($module->info['configure'])) {
       $configure_link = menu_get_item($module->info['configure']);
-      if ($configure_link['access']) {
+      if (is_array($configure_link) && $configure_link['access']) {
         $extra['links']['configure'] = array(
           'title' => t('Configure'),
           'href' => $configure_link['href'],
@@ -1499,11 +1499,12 @@ function system_site_information_settings_submit($form, &$form_state) {
   }
 
   if ($form_state['values']['site_logo_path']) {
+    $form_state['values']['site_logo_attributes'] = array();
     $dimensions = @getimagesize($form_state['values']['site_logo_path']);
-    $form_state['values']['site_logo_attributes'] = array(
-      'width' => $dimensions[0],
-      'height' => $dimensions[1],
-    );
+    if (!empty($dimensions)) {
+      $form_state['values']['site_logo_attributes']['width'] = $dimensions[0];
+      $form_state['values']['site_logo_attributes']['height'] = $dimensions[1];
+    }
   }
   else {
     $form_state['values']['site_logo_attributes'] = array(
@@ -2425,7 +2426,7 @@ function system_configure_date_formats_form($form, &$form_state, $format = NULL)
     '#title' => t('Machine-readable name'),
     '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
     '#disabled' => !empty($format),
-    '#default_value' => $format['name'],
+    '#default_value' => !empty($format) ? $format['name'] : '',
     '#machine_name' => array(
       'exists' => 'system_date_format_load',
       'source' => array('label'),
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 7eff245254..364dd300f3 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -3929,7 +3929,7 @@ function system_archiver_info() {
     'class' => 'ArchiverTar',
     'extensions' => array('tar', 'tgz', 'tar.gz', 'tar.bz2'),
   );
-  if (function_exists('zip_open')) {
+  if (class_exists('ZipArchive')) {
     $archivers['zip'] = array(
       'class' => 'ArchiverZip',
       'extensions' => array('zip'),
diff --git a/core/modules/system/system.tokens.inc b/core/modules/system/system.tokens.inc
index b7a93499c4..387fd2a49b 100644
--- a/core/modules/system/system.tokens.inc
+++ b/core/modules/system/system.tokens.inc
@@ -441,7 +441,7 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
     };
     $keys = array();
     foreach ($array as $key => $value) {
-      if ($key[0] !== '#') {
+      if (substr($key, 0, 1) !== '#') {
         $keys[] = $key;
       }
     }
@@ -477,7 +477,7 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
     // [array:value:*] dynamic tokens.
     if ($value_tokens = token_find_with_prefix($tokens, 'value')) {
       foreach ($value_tokens as $key => $original) {
-        if ($key[0] !== '#' && isset($array[$key])) {
+        if (substr($key, 0, 1) !== '#' && isset($array[$key])) {
           $replacements[$original] = token_render_array_value($array[$key], $options);
         }
       }
diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc
index 551ec449f7..77fff65328 100644
--- a/core/modules/taxonomy/taxonomy.admin.inc
+++ b/core/modules/taxonomy/taxonomy.admin.inc
@@ -310,7 +310,10 @@ function taxonomy_form_vocabulary_submit($form, &$form_state) {
   }
 
   $vocabulary = $form_state['vocabulary'];
-  entity_form_submit_build_entity('taxonomy_vocabulary', $vocabulary, $form, $form_state);
+  form_state_values_clean($form_state);
+  foreach ($form_state['values'] as $key => $value) {
+    $vocabulary->$key = $value;
+  }
 
   // Prevent leading and trailing spaces in vocabulary names.
   $vocabulary->name = trim($vocabulary->name);
diff --git a/core/modules/views_ui/views_ui.admin.inc b/core/modules/views_ui/views_ui.admin.inc
index 97e31c7dd7..a1e762bb90 100644
--- a/core/modules/views_ui/views_ui.admin.inc
+++ b/core/modules/views_ui/views_ui.admin.inc
@@ -2978,7 +2978,7 @@ function views_ui_reorder_displays_form($form, &$form_state) {
       'weight' => array(
         '#type' => 'weight',
         '#value' => $display->position,
-        '#delta' => count($last_display->position),
+        '#delta' => count($view->display),
         '#title' => t('Weight for @display', array('@display' => $display->display_title)),
         '#title_display' => 'invisible',
       ),

From ba6e7e51507806564d76057d27877179f3945161 Mon Sep 17 00:00:00 2001
From: indigoxela <xela@indigofloat.at>
Date: Fri, 15 May 2020 21:57:22 +0200
Subject: [PATCH 17/28] Issue #4389: PHP notice on List layouts page when
 permission is missing.

By @indigoxela, @findlabnet, and @quicksketch.
---
 .../layout/plugins/access/user_permission_layout_access.inc  | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/core/modules/layout/plugins/access/user_permission_layout_access.inc b/core/modules/layout/plugins/access/user_permission_layout_access.inc
index 25f07f0bd7..19de9ee0e2 100644
--- a/core/modules/layout/plugins/access/user_permission_layout_access.inc
+++ b/core/modules/layout/plugins/access/user_permission_layout_access.inc
@@ -23,6 +23,11 @@ class UserPermissionLayoutAccess extends LayoutAccess {
     }
 
     $permissions = module_invoke_all('permission');
+    if (!array_key_exists($this->settings['permission'], $permissions)) {
+      return t('User has missing "@permission" permission (not provided by any module).', array(
+        '@permission' => $this->settings['permission'],
+      ));
+    }
     return t('User has "@permission" permission.', array('@permission' => $permissions[$this->settings['permission']]['title']));
   }
 

From 966195ee8da3147ff5074fe3be849d508a4e0d5d Mon Sep 17 00:00:00 2001
From: Gregory Netsas <gregory@twinz.gr>
Date: Sat, 16 May 2020 00:49:55 +0300
Subject: [PATCH 18/28] Issue #4161: Add flexible layouts permission for
 existing sites.

By @klonos.
---
 core/modules/system/system.install | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 8db13fd906..c709c54616 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -3101,6 +3101,21 @@ function system_update_1070() {
   }
 }
 
+/**
+ * Grant the "administer flexible templates" permission to all user roles that
+ * already have the "administer layouts" permission.
+ *
+ * This update hook would normally go into the layout.install file, but adding
+ * update hooks to new modules that did not exist in D7 is problematic.
+ * See: https://github.com/backdrop/backdrop-issues/issues/1759
+ */
+function system_update_1071() {
+  $roles = user_roles(FALSE, 'administer layouts');
+  foreach ($roles as $role_name => $role) {
+    user_role_grant_permissions($role_name, array('administer flexible templates'));
+  }
+}
+
 /**
  * @} End of "defgroup updates-7.x-to-1.x"
  * The next series of updates should start at 2000.

From dba44fa20af5510fd0c9492c6e7db0245d147251 Mon Sep 17 00:00:00 2001
From: quicksketch <nate@quicksketch.org>
Date: Fri, 15 May 2020 15:27:25 -0700
Subject: [PATCH 19/28] Backdrop 1.16.0

---
 core/includes/bootstrap.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 9456816441..279baefe67 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -7,7 +7,7 @@
 /**
  * The current system version.
  */
-define('BACKDROP_VERSION', '1.16.x-dev');
+define('BACKDROP_VERSION', '1.16.0');
 
 /**
  * Core API compatibility.

From 46dcfa179de1be83440bdd7f0ac3cb745f0e6935 Mon Sep 17 00:00:00 2001
From: quicksketch <nate@quicksketch.org>
Date: Fri, 15 May 2020 15:28:58 -0700
Subject: [PATCH 20/28] Incrementing version string to 1.17.x-dev.

---
 core/includes/bootstrap.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 279baefe67..841dcc6c32 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -7,7 +7,7 @@
 /**
  * The current system version.
  */
-define('BACKDROP_VERSION', '1.16.0');
+define('BACKDROP_VERSION', '1.17.x-dev');
 
 /**
  * Core API compatibility.

From 51549f24721c32cc627f62cc65592d99135aac20 Mon Sep 17 00:00:00 2001
From: Gregory Netsas <gregory@twinz.gr>
Date: Tue, 19 May 2020 08:42:38 +0300
Subject: [PATCH 21/28] Issue #4318: Align color inputs with labels in theme
 settings.

By @klonos, @hosef & @BWPanda.
---
 core/modules/color/css/color.admin.css | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/core/modules/color/css/color.admin.css b/core/modules/color/css/color.admin.css
index 5fd9041164..8fd3e46816 100644
--- a/core/modules/color/css/color.admin.css
+++ b/core/modules/color/css/color.admin.css
@@ -28,6 +28,19 @@ input[type=color] {
   margin-top: .5em;
   margin-bottom: .5em;
   clear: both;
+}
+
+/**
+ * Prevent color input labels from being crumbed up together, when there are
+ * some that wrap into multiple lines.
+ */
+#system-theme-settings .form-type-color {
+  display: table;
+}
+
+/* Properly align color labels with the color input, when they are long. */
+#system-theme-settings .form-type-color label {
+  display: table-cell;
   vertical-align: middle;
 }
 
@@ -38,6 +51,8 @@ input[type=color] {
   width: 35px;
   padding: 0px;
   cursor: pointer;
+  display: table-cell;
+  vertical-align: middle;
 }
 
 /* Styles to control the color inputs in Firefox. */

From 7cf6dcb736532491f4796136acd98431008eb760 Mon Sep 17 00:00:00 2001
From: hosef <joseph@flattandsons.com>
Date: Tue, 19 May 2020 19:48:59 -0700
Subject: [PATCH 22/28] Issue #4399: Added a check so that the color module
 does not try to modify the settings form if there are no fields defined.

By @hosef, @findlabnet, and @klonos.
---
 core/modules/color/color.module | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/core/modules/color/color.module b/core/modules/color/color.module
index 59377c259f..a6f10dfb33 100644
--- a/core/modules/color/color.module
+++ b/core/modules/color/color.module
@@ -22,7 +22,8 @@ function color_theme() {
 function color_form_system_theme_settings_alter(&$form, &$form_state) {
   if (isset($form_state['build_info']['args'][0])
       && ($theme_name = $form_state['build_info']['args'][0])
-      && ($theme_info = color_get_info($theme_name))) {
+      && ($theme_info = color_get_info($theme_name))
+      && isset($theme_info['fields'])) {
     $form['color'] = array(
       '#type' => 'fieldset',
       '#title' => t('Color scheme'),

From e939767877e55df4f62eaf46529691d9b192898b Mon Sep 17 00:00:00 2001
From: indigoxela <xela@indigofloat.at>
Date: Wed, 20 May 2020 20:48:40 +0200
Subject: [PATCH 23/28] Issue #4400: Text filter configuration is not saved
 when editing a filter multiple times.

By @indigoxela, @metatop, @herbdool, and @quicksketch.
---
 core/modules/filter/filter.admin.inc  | 36 ++++++++++++++++++---------
 core/modules/filter/tests/filter.test | 27 ++++++++++++++++++++
 2 files changed, 51 insertions(+), 12 deletions(-)

diff --git a/core/modules/filter/filter.admin.inc b/core/modules/filter/filter.admin.inc
index dc23a73e7e..022c87df87 100644
--- a/core/modules/filter/filter.admin.inc
+++ b/core/modules/filter/filter.admin.inc
@@ -186,11 +186,6 @@ function filter_admin_format_form($form, &$form_state, $format) {
     filter_admin_set_message($stored_format);
     $format = $stored_format;
   }
-  // If editing an already-existing format.
-  elseif (isset($format->format)) {
-    filter_set_format_tempstore($format);
-  }
-
   $is_fallback = ($format->format == filter_fallback_format());
   $editors = filter_get_editors();
   if (isset($form_state['editor_info'])) {
@@ -465,9 +460,6 @@ function filter_admin_format_filter_settings_form_redirect($form, &$form_state)
   if (empty($form_state['format']->format)) {
     $form_state['format']->format = $form_state['values']['format'];
   }
-
-  filter_set_format_tempstore($form_state['format']);
-
   $form_state['redirect'] = 'admin/config/content/formats/' . $form_state['format']->format . '/filter-settings/' . $name;
 }
 
@@ -484,7 +476,13 @@ function filter_admin_format_filter_settings_form_ajax($form, &$form_state) {
   else {
     $trigger = $form_state['triggering_element'];
     $name = $trigger['#name'];
-    $format = $form_state['format'];
+    // Load previously added settings from tempstore, if any.
+    if ($stored_format = filter_get_format_tempstore($form_state['format']->format)) {
+      $format = $stored_format;
+    }
+    else {
+      $format = $form_state['format'];
+    }
 
     $configure_form = backdrop_get_form('filter_admin_format_filter_settings_form', $format, $name);
     $title = t('Configure filter');
@@ -515,6 +513,12 @@ function filter_admin_format_filter_settings_form($form, &$form_state, $format,
   $form_state['format'] = $format;
   $form_state['filter_name'] = $filter_name;
 
+  // Use a format stored in tempstore if available.
+  // Has to get loaded again here in case we did not come here via ajax.
+  if ($stored_format = filter_get_format_tempstore($format->format)) {
+    $form_state['format'] = $stored_format;
+    $format = $stored_format;
+  }
   $all_filter_info = filter_get_filters();
   $filter_info = $all_filter_info[$filter_name];
   $filter = $format->filters[$filter_name];
@@ -662,15 +666,23 @@ function filter_admin_format_form_submit($form, &$form_state) {
   unset($form_state['values']['filter_settings']);
   unset($form_state['values']['actions']);
 
+  // Add the submitted form values to the text format, and save it.
+  $format = $form_state['format'];
   // Save allowed HTML tags from the hidden field in the event JS modified them.
-  $form_state['values']['filters']['filter_html']['settings']['allowed_html'] = $form_state['values']['allowed_html'];
+  $allowed_html = $form_state['values']['allowed_html'];
   unset($form_state['values']['allowed_html']);
 
-  // Add the submitted form values to the text format, and save it.
-  $format = $form_state['format'];
   foreach ($form_state['values'] as $key => $value) {
     $format->$key = $value;
   }
+  // Get values from filter settings forms back from tempstore.
+  if ($stored_format = filter_get_format_tempstore($format->format)) {
+    foreach ($stored_format->filters as $name => $filter) {
+      $format->filters[$name]['settings'] = $filter->settings;
+    }
+  }
+  // Set allowed html, parked in a variable previously.
+  $format->filters['filter_html']['settings']['allowed_html'] = $allowed_html;
 
   // If not saving an editor, do not save any settings.
   if (!$format->editor) {
diff --git a/core/modules/filter/tests/filter.test b/core/modules/filter/tests/filter.test
index cf0a8fdb68..0abada3661 100644
--- a/core/modules/filter/tests/filter.test
+++ b/core/modules/filter/tests/filter.test
@@ -465,6 +465,33 @@ class FilterAdminTestCase extends BackdropWebTestCase {
     );
     $this->backdropPost('admin/config/content/formats/filtered_html/filter-settings/filter_url', $edit, t('Update'));
     $this->assertNoRaw(t('The text format %format has been updated.', array('%format' => 'Filtered HTML')));
+
+    // The form does save permitted value and it appears in the config json.
+    $length = 88;
+    $edit_setting = array(
+      'filter_url_length' => $length,
+    );
+    $this->backdropPost('admin/config/content/formats/filtered_html/filter-settings/filter_url', $edit_setting, t('Update'));
+    $this->backdropPost('admin/config/content/formats/filtered_html', array(), t('Save configuration'));
+    $saved_length = config_get('filter.format.filtered_html', 'filters.filter_url.settings.filter_url_length');
+    $this->assertEqual($saved_length, $length, 'Filter url length saved to config file.');
+
+    // Make sure consecutive settings form submits do not discard
+    // previously added settings.
+    $length = 102;
+    $edit_url = array(
+      'filter_url_length' => $length,
+    );
+    $edit_html = array(
+      'filter_html_nofollow' => TRUE,
+    );
+    $this->backdropPost('admin/config/content/formats/filtered_html/filter-settings/filter_url', $edit_url, t('Update'));
+    $this->backdropPost('admin/config/content/formats/filtered_html/filter-settings/filter_html', $edit_html, t('Update'));
+    $this->backdropPost('admin/config/content/formats/filtered_html', array(), t('Save configuration'));
+    $url_length = config_get('filter.format.filtered_html', 'filters.filter_url.settings.filter_url_length');
+    $this->assertEqual($url_length, $length, 'Filter url not overridden by consecutive form post.');
+    $nofollow = config_get('filter.format.filtered_html', 'filters.filter_html.settings.filter_html_nofollow');
+    $this->assertTrue($nofollow, 'Consecutive settings form post saved correctly');
   }
 }
 

From e1eaf8ffd2416ec3494c8824aa70033bfc401e72 Mon Sep 17 00:00:00 2001
From: Jen Lampton <jen@jeneration.com>
Date: Tue, 19 May 2020 17:15:54 -0700
Subject: [PATCH 24/28] Double check the path derived by  is not external.

---
 core/includes/common.inc | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/core/includes/common.inc b/core/includes/common.inc
index 2cd6874200..8d60af648c 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -834,7 +834,10 @@ function backdrop_goto($path = '', array $options = array(), $http_response_code
   // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
   if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
     $destination = backdrop_parse_url($_GET['destination']);
-    $path = $destination['path'];
+    // Double check the path derived by backdrop_parse_url() is not external.
+    if (!url_is_external($destination['path'])) {
+      $path = $destination['path'];
+    }
     $options['query'] = $destination['query'];
     $options['fragment'] = $destination['fragment'];
   }

From 0a553368ed92bb6d6fb3ccd882e5f9d5d28cad44 Mon Sep 17 00:00:00 2001
From: quicksketch <nate@quicksketch.org>
Date: Sun, 17 May 2020 17:32:35 -0700
Subject: [PATCH 25/28] Backport jQuery 3.5.0 htmlPrefilter() hardening.

---
 core/misc/jquery-html-prefilter-3.5.0.js | 122 +++++++++++++++++++++++
 core/modules/system/system.install       |   9 +-
 core/modules/system/system.module        |   3 +-
 3 files changed, 132 insertions(+), 2 deletions(-)
 create mode 100644 core/misc/jquery-html-prefilter-3.5.0.js

diff --git a/core/misc/jquery-html-prefilter-3.5.0.js b/core/misc/jquery-html-prefilter-3.5.0.js
new file mode 100644
index 0000000000..4949ec59b3
--- /dev/null
+++ b/core/misc/jquery-html-prefilter-3.5.0.js
@@ -0,0 +1,122 @@
+/**
+ * For jQuery versions less than 3.5.0, this replaces the jQuery.htmlPrefilter()
+ * function with one that fixes these security vulnerabilities while also
+ * retaining the pre-3.5.0 behavior where it's safe to do so.
+ * - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11022
+ * - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11023
+ *
+ * Backdrop includes jQuery 1.12.4, the last stable version in the 1.x branch.
+ * This incorporates only the fix needed for that version, and so is simpler
+ * than the respective port for Drupal 7.
+ */
+
+(function (jQuery) {
+
+  // Parts of this backport differ by jQuery version.
+  var versionParts = jQuery.fn.jquery.split('.');
+  var majorVersion = parseInt(versionParts[0]);
+  var minorVersion = parseInt(versionParts[1]);
+
+  // No backport is needed if we're already on jQuery 3.5 or higher.
+  if ( (majorVersion > 3) || (majorVersion === 3 && minorVersion >= 5) ) {
+    return;
+  }
+
+  // Prior to jQuery 3.5, jQuery converted XHTML-style self-closing tags to
+  // their XML equivalent: e.g., "<div />" to "<div></div>". This is
+  // problematic for several reasons, including that it's vulnerable to XSS
+  // attacks. However, since this was jQuery's behavior for many years, many
+  // Drupal modules and jQuery plugins may be relying on it. Therefore, we
+  // preserve that behavior, but for a limited set of tags only, that we believe
+  // to not be vulnerable. This is the set of HTML tags that satisfy all of the
+  // following conditions:
+  // - In DOMPurify's list of HTML tags. If an HTML tag isn't safe enough to
+  //   appear in that list, then we don't want to mess with it here either.
+  //   @see https://github.com/cure53/DOMPurify/blob/2.0.11/dist/purify.js#L128
+  // - A normal element (not a void, template, text, or foreign element).
+  //   @see https://html.spec.whatwg.org/multipage/syntax.html#elements-2
+  // - An element that is still defined by the current HTML specification
+  //   (not a deprecated element), because we do not want to rely on how
+  //   browsers parse deprecated elements.
+  //   @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element
+  // - Not 'html', 'head', or 'body', because this pseudo-XHTML expansion is
+  //   designed for fragments, not entire documents.
+  // - Not 'colgroup', because due to an idiosyncrasy of jQuery's original
+  //   regular expression, it didn't match on colgroup, and we don't want to
+  //   introduce a behavior change for that.
+  var selfClosingTagsToReplace = [
+    'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo',
+    'blockquote', 'button', 'canvas', 'caption', 'cite', 'code', 'data',
+    'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em',
+    'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3',
+    'h4', 'h5', 'h6', 'header', 'hgroup', 'i', 'ins', 'kbd', 'label', 'legend',
+    'li', 'main', 'map', 'mark', 'menu', 'meter', 'nav', 'ol', 'optgroup',
+    'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt',
+    'ruby', 's', 'samp', 'section', 'select', 'small', 'source', 'span',
+    'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th',
+    'thead', 'time', 'tr', 'u', 'ul', 'var', 'video'
+  ];
+
+  // Define regular expressions for <TAG/> and <TAG ATTRIBUTES/>. Doing this as
+  // two expressions makes it easier to target <a/> without also targeting
+  // every tag that starts with "a".
+  var xhtmlRegExpGroup = '(' + selfClosingTagsToReplace.join('|') + ')';
+  var whitespace = '[\\x20\\t\\r\\n\\f]';
+  var rxhtmlTagWithoutSpaceOrAttributes = new RegExp('<' + xhtmlRegExpGroup + '\\/>', 'gi');
+  var rxhtmlTagWithSpaceAndMaybeAttributes = new RegExp('<' + xhtmlRegExpGroup + '(' + whitespace + '[^>]*)\\/>', 'gi');
+
+  // jQuery 3.5 also fixed a vulnerability for when </select> appears within
+  // an <option> or <optgroup>, but it did that in local code that we can't
+  // backport directly. Instead, we filter such cases out. To do so, we need to
+  // determine when jQuery would otherwise invoke the vulnerable code, which it
+  // uses this regular expression to determine. The regular expression changed
+  // for version 3.0.0 and changed again for 3.4.0.
+  // @see https://github.com/jquery/jquery/blob/1.12.4/dist/jquery.js#L4432
+  // @see https://github.com/jquery/jquery/blob/3.0.0/dist/jquery.js#L4584
+  var rtagName;
+  if (majorVersion < 3) {
+    rtagName = /<([\w:]+)/;
+  }
+  else if (minorVersion < 4) {
+    rtagName = /<([a-z][^\/\0>\x20\t\r\n\f]+)/i;
+  }
+  else {
+    rtagName = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i;
+  }
+
+  // The regular expression that jQuery uses to determine which self-closing
+  // tags to expand to open and close tags. This is vulnerable, because it
+  // matches all tag names except the few excluded ones. We only use this
+  // expression for determining vulnerability. The expression changed for
+  // version 3, but we only need to check for vulnerability in versions 1 and 2,
+  // so we use the expression from those versions.
+  // @see https://github.com/jquery/jquery/blob/1.12.4/dist/jquery.js#L5874
+  var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
+
+  jQuery.extend({
+    htmlPrefilter: function (html) {
+      // This is how jQuery determines the first tag in the HTML.
+      // @see https://github.com/jquery/jquery/blob/1.12.4/dist/jquery.js#L6353
+      var tag = ( rtagName.exec( html ) || [ "", "" ] )[ 1 ].toLowerCase();
+
+      // It is not valid HTML for <option> or <optgroup> to have <select> as
+      // either a descendant or sibling, and attempts to inject one can cause
+      // XSS on jQuery versions before 3.5. Since this is invalid HTML and a
+      // possible XSS attack, reject the entire string.
+      // @see https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11023
+      if ((tag === 'option' || tag === 'optgroup') && html.match(/<\/?select/i)) {
+        html = '';
+      }
+
+      // Retain jQuery's prior to 3.5 conversion of pseudo-XHTML, but for only
+      // the tags in the `selfClosingTagsToReplace` list defined above.
+      // @see https://github.com/jquery/jquery/blob/1.12.4/dist/jquery.js#L6130
+      // @see https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11022
+      html = html.replace(rxhtmlTagWithoutSpaceOrAttributes, "<$1></$1>");
+      html = html.replace(rxhtmlTagWithSpaceAndMaybeAttributes, "<$1$2></$1>");
+
+      return html;
+    }
+  });
+
+})(jQuery);
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index c709c54616..d815982606 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -724,7 +724,7 @@ function system_install() {
 
   // Set the public files path to the directory with the settings.php file.
   config_set('system.core', 'file_public_path', str_replace('./', '', conf_path() . '/files'));
-  
+
   // Sets the path of system-appropriate temporary directory.
   config_set('system.core', 'file_temporary_path', file_directory_temp());
 
@@ -3116,6 +3116,13 @@ function system_update_1071() {
   }
 }
 
+/**
+ * Add 'jquery-html-prefilter-3.5.0.js' to the 'jquery' library.
+ */
+function system_update_1072() {
+  // Empty update to force a rebuild of hook_library() and JS aggregates.
+}
+
 /**
  * @} End of "defgroup updates-7.x-to-1.x"
  * The next series of updates should start at 2000.
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 364dd300f3..e238bb3136 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1292,9 +1292,10 @@ function system_library_info() {
     'version' => '1.12.4',
     'js' => array(
       'core/misc/jquery.js' => array('group' => JS_LIBRARY, 'weight' => -20),
-      // This includes a security fix, so assign a weight that makes this load
+      // These include security fixes, so assign a weight that makes them load
       // as soon after jquery.js is loaded as possible.
       'core/misc/jquery-extend-3.4.0.js' => array('group' => JS_LIBRARY, 'weight' => -19),
+      'core/misc/jquery-html-prefilter-3.5.0.js' => array('group' => JS_LIBRARY, 'weight' => -19),
     ),
   );
 

From 557824453eeec969800c2ff47db7bde4da036467 Mon Sep 17 00:00:00 2001
From: Peter Anderson <BWPanda@users.noreply.github.com>
Date: Sun, 24 May 2020 08:15:10 +0300
Subject: [PATCH 26/28] Issue #4417: Fixed formatting of core text files.

By @BWPanda & @klonos.
---
 README.md         | 13 ++++++++-----
 core/LICENSE.txt  | 14 +++++++-------
 layouts/README.md |  6 ++++--
 modules/README.md | 13 ++++++++-----
 themes/README.md  | 14 ++++++++------
 5 files changed, 35 insertions(+), 25 deletions(-)

diff --git a/README.md b/README.md
index 856a3c11bf..fa9e9d8ca0 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,9 @@ Backdrop aims to provide:
 
 Requirements
 ------------
-- PHP 5.3.2 or higher. Even if Backdrop can run on older versions of PHP, we strongly recommend that you use a [supported version of PHP](https://secure.php.net/supported-versions.php).
+- PHP 5.3.2 or higher. Even if Backdrop can run on older versions of PHP, we
+  strongly recommend that you use a
+  [supported version of PHP](https://secure.php.net/supported-versions.php).
 - MySQL 5.0.15 or higher with PDO enabled
 - Apache (recommended) or Nginx web server
 - 50 MB of disk space (recommended), 15 MB (minimum)
@@ -38,9 +40,10 @@ Security Issues
 ---------------
 If you have discovered a security issue with Backdrop CMS or any of its
 [contributed modules](https://github.com/backdrop-contrib/), please contact the
-Backdrop Security Team directly at [security@backdropcms.org](mailto:security@backdropcms.org).
-We manage security issues separately in a private repository until the issue
-has been resolved. Even if you're not sure if it's a security problem, please
+Backdrop Security Team directly at
+[security@backdropcms.org](mailto:security@backdropcms.org).
+We manage security issues separately in a private repository until the issue has
+been resolved. Even if you're not sure if it's a security problem, please
 contact the security team before filing an issue.
 
 Developers
@@ -84,5 +87,5 @@ All Backdrop code is Copyright 2001 - 2016 by the original authors.
 
 Backdrop also includes works under different copyright notices that are
 distributed according to the terms of the GNU General Public License or a
-compatible license. These individual works may have specific copyright 
+compatible license. These individual works may have specific copyright
 information noted within their source code files or directories.
diff --git a/core/LICENSE.txt b/core/LICENSE.txt
index 94fb84639c..d159169d10 100644
--- a/core/LICENSE.txt
+++ b/core/LICENSE.txt
@@ -1,12 +1,12 @@
-        GNU GENERAL PUBLIC LICENSE
-           Version 2, June 1991
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
 
  Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
-          Preamble
+                            Preamble
 
   The licenses for most software are designed to take away your
 freedom to share and change it.  By contrast, the GNU General Public
@@ -56,7 +56,7 @@ patent must be licensed for everyone's free use or not licensed at all.
   The precise terms and conditions for copying, distribution and
 modification follow.
 
-        GNU GENERAL PUBLIC LICENSE
+                    GNU GENERAL PUBLIC LICENSE
    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
   0. This License applies to any program or other work which contains
@@ -255,7 +255,7 @@ make exceptions for this.  Our decision will be guided by the two goals
 of preserving the free status of all derivatives of our free software and
 of promoting the sharing and reuse of software generally.
 
-          NO WARRANTY
+                            NO WARRANTY
 
   11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGES.
 
-         END OF TERMS AND CONDITIONS
+                     END OF TERMS AND CONDITIONS
 
-      How to Apply These Terms to Your New Programs
+            How to Apply These Terms to Your New Programs
 
   If you develop a new program, and you want it to be of the greatest
 possible use to the public, the best way to achieve this is to make it
diff --git a/layouts/README.md b/layouts/README.md
index f3b5242d91..4b63c74aea 100644
--- a/layouts/README.md
+++ b/layouts/README.md
@@ -1,10 +1,12 @@
-Layouts divide pages on your site into different regions where content can be placed. You can use layouts contributed by others or create your own.
+Layouts divide pages on your site into different regions where content can be
+placed. You can use layouts contributed by others or create your own.
 
 What to place in this directory?
 --------------------------------
 
 Placing downloaded and custom layouts in this directory separates them from
-Backdrop core's layouts. This allows Backdrop core to be updated without overwriting these files.
+Backdrop core's layouts. This allows Backdrop core to be updated without
+overwriting these files.
 
 Download additional layouts
 ---------------------------
diff --git a/modules/README.md b/modules/README.md
index 3053191317..97d8f9112f 100644
--- a/modules/README.md
+++ b/modules/README.md
@@ -12,21 +12,24 @@ overwriting these files.
 Download additional modules
 ---------------------------
 
-Contributed modules from the Backdrop community may be downloaded at http://backdropcms.org/modules.
+Contributed modules from the Backdrop community may be downloaded at
+http://backdropcms.org/modules.
 
 Organizing modules in this directory
 ------------------------------------
 
-You may create subdirectories within this directory to organize your added modules. Some common subdirectories include "contrib" for contributed modules,
+You may create subdirectories within this directory to organize your added
+modules. Some common subdirectories include "contrib" for contributed modules,
 and "custom" for custom modules. Note that if you change the location of a
 module after it has been enabled, you may need to clear the Backdrop cache so it
-can be found. (Alternatively, you can disable the module before moving it and then re-enable it after the move.)
+can be found. (Alternatively, you can disable the module before moving it and
+then re-enable it after the move.)
 
 Multisite configuration
 -----------------------
 
-In multisite configuration, modules found in this directory are available to
-all sites. To restrict modules to a specific site instance, place modules in a
+In multisite configuration, modules found in this directory are available to all
+sites. To restrict modules to a specific site instance, place modules in a
 directory following the pattern sites/your_site_name/modules.
 
 More information
diff --git a/themes/README.md b/themes/README.md
index 8a1567f408..e0ab57db8c 100644
--- a/themes/README.md
+++ b/themes/README.md
@@ -1,4 +1,5 @@
-Themes are skins for your site that allow you to change the look, feel, and general appearance. You can use themes contributed by others or create your own.
+Themes are skins for your site that allow you to change the look, feel, and
+general appearance. You can use themes contributed by others or create your own.
 
 What to place in this directory?
 --------------------------------
@@ -16,15 +17,16 @@ https://www.backdropcms.org/themes
 Organizing themes in this directory
 ------------------------------------
 
-It is safe to organize themes into subdirectories to ensure easy maintenance
-and upgrades. Is highly recommended to use Backdrop's sub-theme functionality to keep the code for related themes separated.
+It is safe to organize themes into subdirectories to ensure easy maintenance and
+upgrades. Is highly recommended to use Backdrop's sub-theme functionality to
+keep the code for related themes separated.
 
 Multisite configuration
 -----------------------
 
-In multisite configuration, themes found in this directory are available to
-all sites. Alternatively, the sites/your_site_name/themes directory pattern may
-be used to restrict themes to a specific site instance.
+In multisite configuration, themes found in this directory are available to all
+sites. Alternatively, the sites/your_site_name/themes directory pattern may be
+used to restrict themes to a specific site instance.
 
 MORE INFORMATION
 -----------------

From e491b9c15399c0aec862528d8ba54d8ac78808e7 Mon Sep 17 00:00:00 2001
From: indigoxela <user@olga.indigofloat.at>
Date: Sun, 24 May 2020 13:55:40 +0200
Subject: [PATCH 27/28] Issue #4419: prevent new empty formats if disabled ones
 exist

---
 core/modules/filter/filter.admin.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/modules/filter/filter.admin.inc b/core/modules/filter/filter.admin.inc
index 022c87df87..4442b82556 100644
--- a/core/modules/filter/filter.admin.inc
+++ b/core/modules/filter/filter.admin.inc
@@ -109,7 +109,7 @@ function filter_admin_overview($form) {
  * Form submission handler for filter_admin_overview().
  */
 function filter_admin_overview_submit($form, &$form_state) {
-  $formats = filter_formats();
+  $formats = filter_formats(NULL, TRUE);
   foreach ($form_state['values']['formats'] as $id => $data) {
     if (is_array($data) && isset($data['weight']) && $data['weight'] != $formats[$id]->weight) {
       // Update the weight of this filter.

From 497c43b0b7a99b63ca1db81683b9ff59aa99ffac Mon Sep 17 00:00:00 2001
From: indigoxela <xela@indigofloat.at>
Date: Thu, 28 May 2020 11:28:07 +0200
Subject: [PATCH 28/28] Issue #4423: Fix incorrect t() parameter usage in
 update_mail().

---
 core/modules/update/update.module | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index 1ae55b54ef..c32d2dc53a 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -445,7 +445,8 @@ function _update_get_cached_available_releases() {
 function update_mail($key, &$message, $params) {
   $language = $message['language'];
   $langcode = $language->langcode;
-  $message['subject'] .= t('New release(s) available for !site_name', array('!site_name' => config_get_translated('system.core', 'site_name'), array(), array('langcode' => $langcode)), array('langcode' => $langcode));
+  $site_name_localized = config_get_translated('system.core', 'site_name', array(), array('langcode' => $langcode));
+  $message['subject'] .= t('New release(s) available for !site_name', array('!site_name' => $site_name_localized), array('langcode' => $langcode));
   foreach ($params as $msg_type => $msg_reason) {
     $message['body'][] = _update_message_text($msg_type, $msg_reason, FALSE, $language);
   }