 edit.module            |  25 ++++++-
 includes/panelizer.inc | 194 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 218 insertions(+), 1 deletion(-)

diff --git a/edit.module b/edit.module
index 38b4cc3..9ad60ff 100644
--- a/edit.module
+++ b/edit.module
@@ -17,6 +17,9 @@ module_load_include('inc', 'edit', 'includes/filter');
 // Load node module "extra fields" support.
 module_load_include('inc', 'edit', 'includes/node');
 
+// Load Panelizer support.
+module_load_include('inc', 'edit', 'includes/panelizer');
+
 /**
  * Implements hook_menu().
  */
@@ -94,6 +97,17 @@ function edit_page_build(&$page) {
     return;
   }
 
+  // Incredibly ugly hack to support Panelizer overrides using Page Manager.
+  // @see includes/panelizer.inc/edit_ctools_render_alter()
+  global $edit_workaround_for_fundamentally_broken_page_manager;
+  if ($edit_workaround_for_fundamentally_broken_page_manager) {
+    // Change from
+    //   <div class="contextual-links-wrapper">
+    // to
+    //   <div class="contextual-links-wrapper" data-edit-is-contextual-region-for-entity>
+    $page['content']['system_main']['#attributes']['data-edit-is-contextual-region-for-entity'] = '';
+  }
+
   // Abuse the 'page_top' region for attaching our libraries.
   $page['page_top']['#attached']['library'][] = array('edit', 'edit');
 
@@ -362,7 +376,10 @@ function edit_preprocess_field(&$variables) {
   // mode).
   // @see field_view_field()
   // @see https://drupal.org/node/2120335
-  if ($element['#view_mode'] === '_custom_display') {
+  // However, we do support Panelizer, and Panelizer always uses a custom view
+  // mode when rendering field panes, in all of its own "view modes".
+  // @see includes/panelizer.inc
+  if ($element['#view_mode'] === '_custom_display' && !isset($element['#object']->panelizer)) {
     return;
   }
 
@@ -407,6 +424,12 @@ function edit_preprocess_field(&$variables) {
     }
   }
 
+  // Panelizer support; see explanation at the top of this function.
+  // @see includes/panelizer.inc
+  if ($element['#view_mode'] === '_custom_display' && isset($element['#object']->panelizer)) {
+    $view_mode = _panelizer_generate_edit_viewmode($element['#object']);
+  }
+
   // Provide metadata through data- attributes.
   $variables['attributes_array']['data-edit-field-id'] = "$entity_type/$id/$field_name/$language/$view_mode";
 }
diff --git a/includes/panelizer.inc b/includes/panelizer.inc
new file mode 100644
index 0000000..c7da951
--- /dev/null
+++ b/includes/panelizer.inc
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * @file
+ * Implements Edit module hooks on behalf of panelizer.module.
+ */
+
+/**
+ * Implements hook_ctools_render_alter().
+ */
+function panelizer_ctools_render_alter(&$info, &$page, &$context) {
+  // Detect when an entity is being rendered by page_manager, and then set a
+  // global (GASP!) variable. This is necessary, because page manager has an
+  // incorrectly applied "contextual-links-region" class: it's set on the wrong
+  // element (by ctools_context_handler_render_handler(), hence breaking Edit
+  // module's JS in the process. The worst part: it is *impossible* to override
+  // it in a sane way.
+  // As always, there is a work-around in Drupal, but it's not pretty: detect
+  // here whether an entity is being rendered by page manager, set a global
+  // variable, and then let edit_page_build() perform string manipulation on the
+  // final $page renderable array.
+  if ($context['task']['task type'] == 'page') {
+    foreach ($context['contexts'] as $page_context) {
+      if ($page_context->type[1] === 'entity') {
+        global $edit_workaround_for_fundamentally_broken_page_manager;
+        $edit_workaround_for_fundamentally_broken_page_manager = TRUE;
+
+        // To make matters even more funny, hook_preprocess_node() is invoked
+        // when using the "IPE" renderer, but not when using the "Standard"
+        // renderer. (It's called "renderer" in the UI, but it's called
+        // "pipeline" internally.)
+        // Hence Edit is unable to set the data-edit-entity-id attribute
+        // depending on the renderer/pipeline, and we must apply yet more
+        // monkey-patching.
+        // To keep things as consistent as possible, we therefore just wrap the
+        // "Standard" renderer/pipeline's output in a div that contains the
+        // data-edit-entity-id attribute that would've been set otherwise.
+        // Finally, to top it off: we have no way of determining the pipeline/
+        // renderer without also loading the associated entity, so unfortunately
+        // we're condemned to doing that.
+        $entity_id = $context['args'][0];
+        $entity_type = $page_context->type[2];
+        $entity = entity_load_single($entity_type, $entity_id);
+        // @see panelizer_edit_render_field()
+        $entity_handler = panelizer_entity_plugin_get_handler($entity_type);
+        $panelizer = $entity->panelizer['page_manager'];
+        // Add the data-edit-entity attribute when the "standard" pipeline/ren-
+        // derer is used.
+        if ($panelizer->pipeline === 'standard') {
+          $prefix = '<div data-edit-entity-id="' . $entity_type . '/' . $entity_id . '">';
+          $suffix = '</div>';
+          $info['content'] = $prefix . $info['content'] . $suffix;
+        }
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_panelizer_pre_render_alter().
+ *
+ * Panelizer fails to communicate to the theme layer what view mode an entity's
+ * fields are being rendered in, so we unfortunately have to do that ourselves.
+ */
+function panelizer_panelizer_pre_render_alter(&$panelizer, &$display, &$entity) {
+  foreach ($entity->panelizer as $view_mode_key => $reference_panelizer) {
+    if ($reference_panelizer === $panelizer) {
+      $entity->panelizer_view_mode_key = $view_mode_key;
+      break;
+    }
+  }
+}
+
+/**
+ * Implements hook_preprocess_panelizer_view_mode().
+ *
+ * Sets data-edit-entity-id attribute in case of a panelized, non-full-page
+ * entity view.
+ *
+ * @see edit_preprocess_node()
+ */
+//
+function edit_preprocess_panelizer_view_mode(&$variables) {
+  $entity_type = $variables['element']['#entity_type'];
+  $entity = $variables['element']['#' . $entity_type];
+  $entity_id = entity_id($entity_type, $entity);
+
+  // Set data-edit-entity-id attribute.
+  $variables['attributes_array']['data-edit-entity-id'] = $entity_type . '/' . $entity_id;
+
+  // If the entity has a title, set data-edit-field-id attribute.
+  if (!empty($entity->title)) {
+    $language = !empty($entity->language) ? $entity->language : LANGUAGE_NONE;
+    $view_mode = _panelizer_generate_edit_viewmode($entity);
+    $variables['title_attributes_array']['data-edit-field-id'] = "$entity_type/$entity_id/title/$language/$view_mode";
+  }
+}
+
+/**
+ * Helper function to transform the "panelizer view mode" into an "Edit view
+ * mode ID", to make Edit use Panelizer's render pipeline to re-render fields
+ * after they've been edited.
+ *
+ * @see panelizer_edit_render_field()
+ */
+function _panelizer_generate_edit_viewmode($entity) {
+  // @see includes/panelizer.inc/panelizer_panelizer_pre_render_alter()
+  $key = $entity->panelizer_view_mode_key;
+  $panelizer_entity = $entity->panelizer[$key];
+  return implode('-', array('panelizer', $panelizer_entity->view_mode));
+}
+
+/**
+ * Implements hook_edit_render_field().
+ *
+ * @see _panelizer_generate_edit_viewmode()
+ */
+function panelizer_edit_render_field($entity_type, $entity, $field_name, $view_mode_id, $langcode) {
+  ctools_include('plugins', 'panels');
+  ctools_include('content', 'ctools');
+
+  list($module, $panelizer_view_mode) = explode('-', $view_mode_id);
+
+  // Now render the given field (which resides in the given pane ID) through
+  // Panels' render pipeline.
+  $args = array(entity_id($entity_type, $entity));
+  // @see panelizer_panelizer_task_render()
+  $entity_handler = panelizer_entity_plugin_get_handler($entity_type);
+  // @see PanelizerEntityDefault.class.php::render_entity().
+  $panelizer = $entity->panelizer[$panelizer_view_mode];
+
+  // Special case: the title.
+  // Note: it is technically impossible to determine what should be rendered
+  // exactly, because it is determined by a subset of a template. Hence we do a
+  // best-effort approximation.
+  // @see panelizer-view-mode.tpl.php
+  if ($field_name === 'title') {
+    $entity_id = entity_id($entity_type, $entity);
+    // @see PanelizerEntityDefault::preprocess_panelizer_view_mode()
+    $title_element = 'h2';
+    if (!empty($panelizer->title_element)) {
+      $title_element = $panelizer->title_element;
+    }
+    $entity_url = NULL;
+    if (!empty($panelizer->link_to_entity)) {
+      $bits = explode('/', $entity_handler->plugin['entity path']);
+      foreach ($bits as $count => $bit) {
+        if (strpos($bit, '%') === 0) {
+          $bits[$count] = $entity_id;
+        }
+      }
+      $entity_url = url(implode('/', $bits));
+    }
+    // Immediately return the rendered title pseudo-field.
+    $edit_id = "$entity_type/$entity_id/title/$langcode/$view_mode_id";
+    $prefix = '<' . $title_element . ' data-edit-field-id="' . $edit_id .'">';
+    $suffix . '</' . $title_element . '>';
+    if (isset($entity_url)) {
+      $markup = $prefix . '<a href="' . $entity_url . '">' . $entity->title . '</a>' . $suffix;
+    }
+    else {
+      $markup =  $prefix . $entity->title . $suffix;
+    }
+    return array('#markup' => $markup);
+  }
+
+  // @see PanelizerEntityDefault.class.php::render_entity().
+  $display = $panelizer->display;
+  $display->context = $entity_handler->get_contexts($panelizer, $entity);
+  $display->args = $args;
+  $display->css_id = $panelizer->css_id;
+  $renderer = panels_get_renderer_handler($panelizer->pipeline, $display);
+  // Find the ID of the pane where the given field resides; it's impossible to
+  // know which pane ID the field is being rendered in when the
+  // data-edit-field-id attribute gets set in edit_preprocess_field(), so sadly
+  // we have to resort to this work-around. This *will* break down if the same
+  // field is rendered multiple times.
+  $pane_id = NULL;
+  foreach ($display->content as $id => $pane) {
+    if ($pane->type === 'entity_field' && $pane->subtype === $entity_type . ':'. $field_name) {
+      $pane_id = $id;
+      break;
+    }
+  }
+  // @see panels_renderer_standard::render_panes()
+  $pane = $display->content[$pane_id];
+  // @see panels_renderer_standard::render_pane()
+  module_invoke_all('panels_pane_prerender', $pane);
+  $pane_content = $renderer->render_pane_content($pane);
+
+  // Finally, return the renderable array containing the field.
+  return $pane_content->content;
+}
