dynamic_image_generator-1.0.x-dev/dynamic_image_generator.module

dynamic_image_generator.module
<?php

/**
 * @file
 * Contains dynamic_image_generator.module.
 */

use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Entity\FieldableEntityInterface;

/**
 * Get available content types for the content_type field options.
 *
 * @return array
 *   An array of content type labels keyed by machine name.
 */
function dynamic_image_generator_get_content_types() {
  $options = [];
  $content_types = \Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple();
  
  foreach ($content_types as $content_type) {
    $options[$content_type->id()] = $content_type->label();
  }
  
  return $options;
}

/**
 * Get available image and media fields for the selected content type.
 *
 * @param string $content_type
 *   The content type machine name.
 *
 * @return array
 *   An array of field labels keyed by field name.
 */
function dynamic_image_generator_get_target_fields($content_type) {
  $options = [];
  
  if (empty($content_type)) {
    return $options;
  }
  
  // Get all field definitions for the content type
  $field_definitions = \Drupal::service('entity_field.manager')
    ->getFieldDefinitions('node', $content_type);
  
  // Filter for image and media fields
  foreach ($field_definitions as $field_name => $field_definition) {
    if ($field_definition->getFieldStorageDefinition()->isBaseField()) {
      continue; // Skip base fields
    }
    
    $field_type = $field_definition->getType();
    $field_label = $field_definition->getLabel();
    
    // Include image fields and media reference fields
    if ($field_type === 'image') {
      $options[$field_name] = t('@label (Image field)', ['@label' => $field_label]);
    }
    elseif ($field_type === 'entity_reference' && 
            $field_definition->getSetting('handler') === 'default:media') {
      $options[$field_name] = t('@label (Media field)', ['@label' => $field_label]);
    }
  }
  
  return $options;
}

/**
 * Implements hook_form_BASE_FORM_ID_alter() for node forms.
 * Shows auto-generation options for image templates.
 */
function dynamic_image_generator_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Get the node from the form
  $node = $form_state->getFormObject()->getEntity();
  
  // Skip if this is not a node form
  if (!$node || $node->getEntityTypeId() !== 'node') {
    return;
  }
  
  // Get the node's content type
  $content_type = $node->bundle();
  
  // Find all active poster templates for this content type
  $poster_storage = \Drupal::entityTypeManager()->getStorage('poster_entity');
  $query = $poster_storage->getQuery()
    ->condition('content_type', $content_type)
    ->condition('status', 1)
    ->accessCheck(FALSE);
  
  $poster_ids = $query->execute();
  
  if (empty($poster_ids)) {
    return;
  }
  
  // Load poster entities and filter for those with target fields that exist on this node
  $poster_entities = $poster_storage->loadMultiple($poster_ids);
  $qualifying_posters = [];
  
  foreach ($poster_entities as $poster) {
    $target_field = $poster->getTargetField();
    
    // Only include posters that have a target field AND the field exists on this node
    if (!empty($target_field) && $node->hasField($target_field)) {
      $qualifying_posters[$poster->id()] = $poster;
    }
  }
  
  if (empty($qualifying_posters)) {
    return;
  }
  
  // Add form elements
  if (!isset($form['advanced'])) {
    $form['advanced'] = [
      '#type' => 'vertical_tabs',
      '#weight' => 99,
    ];
  }
  
  // Add dynamic image settings tab
  $form['dynamic_image_settings'] = [
    '#type' => 'details',
    '#title' => t('Dynamic Image Generation'),
    '#group' => 'advanced',
    '#weight' => 100,
    '#open' => FALSE,
    '#description' => t('Configure automatic dynamic image generation for this content.'),
    '#attached' => [
      'library' => [
        'core/drupal.dialog.ajax',
        'core/drupal.ajax',
      ],
    ],
  ];
  
  // Add checkbox and preview button for each qualifying poster
  foreach ($qualifying_posters as $poster_id => $poster) {
    $checkbox_key = 'poster_auto_generate_' . $poster_id;
    $target_field = $poster->getTargetField();
    
    // Get field label for better UX
    $field_definition = $node->getFieldDefinition($target_field);
    $field_label = $field_definition ? $field_definition->getLabel() : $target_field;
    
    // Create a container for each template
    $form['dynamic_image_settings']['template_' . $poster_id] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => ['dynamic-image-template-container'],
        'style' => 'margin-bottom: 1.5rem; border-bottom: 1px solid #ddd;',
      ],
    ];
    
    // Template title
    $form['dynamic_image_settings']['template_' . $poster_id]['title'] = [
      '#markup' => '<div class="form-item__label">' . $poster->label() . '</div>',
      '#weight' => -10,
    ];
    
    // Auto-generate checkbox
    $form['dynamic_image_settings']['template_' . $poster_id][$checkbox_key] = [
      '#type' => 'checkbox',
      '#title' => t('Auto-generate Image', [
        '@field' => $field_label,
      ]),
      '#description' => t('Generate this image automatically when content is saved. The generated image will be saved to the <strong>@field</strong> field.', [
        '@field' => $field_label,
      ]),
      '#default_value' => FALSE,
      '#weight' => 0,
    ];
    
    // Preview button with real-time data
    $preview_url = Url::fromRoute('dynamic_image_generator.preview_with_data', [
      'poster_entity' => $poster_id,
      'node' => $node->id() ?: 'new',
    ]);
    
    /*$form['dynamic_image_settings']['template_' . $poster_id]['preview_button'] = [
      '#type' => 'button',
      '#value' => t('Preview with This Content'),
      '#attributes' => [
        'class' => ['button', 'button--small', 'node-content-preview-trigger'],
        'style' => 'margin-left: 10px; background: #28a745; color: white; text-decoration: none; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;',
        'data-node-id' => $node->id() ?: 'new',
        'data-poster-id' => $poster_id,
        'type' => 'button',
      ],
      '#weight' => 5,
    ];*/
  }
  
  // Add form data extraction JavaScript for preview with node data
  $form['#attached']['library'][] = 'core/drupal.ajax';
  $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
  $form['#attached']['html_head'][] = [
    [
      '#type' => 'html_tag',
      '#tag' => 'script',
      '#value' => '
(function($, Drupal) {
  "use strict";
  
  Drupal.behaviors.nodeContentPreview = {
    attach: function(context, settings) {
      $(".node-content-preview-trigger", context).once("node-content-preview").on("click", function(e) {
        e.preventDefault();
        e.stopPropagation();
        
        var $button = $(this);
        var nodeId = $button.attr("data-node-id");
        var posterId = $button.attr("data-poster-id");
        
        // Prevent double-clicking
        if ($button.data("processing")) {
          return false;
        }
        
        $button.data("processing", true);
        $button.prop("disabled", true).text("Generating...");
        
        // Show loader
        var $loader = $("<div class=\"node-preview-loader\" style=\"position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 9999; display: flex; align-items: center; justify-content: center;\">" +
          "<div style=\"background: white; padding: 30px; border-radius: 8px; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.3);\">" +
            "<div style=\"width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #28a745; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 15px;\"></div>" +
            "<p style=\"margin: 0; font-weight: bold; color: #333;\">Generating Preview...</p>" +
            "<p style=\"margin: 5px 0 0; font-size: 0.9em; color: #666;\">Using this content data</p>" +
          "</div>" +
        "</div>");
        
        // Add spinner CSS if not exists
        if (!$("#node-preview-spinner-css").length) {
          $("<style id=\"node-preview-spinner-css\">@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }</style>").appendTo("head");
        }
        
        $("body").append($loader);
        
        // Build form data with current node form values
        var formData = {};
        
        // Get current form field values
        $("#node-form input, #node-form textarea, #node-form select").each(function() {
          var $field = $(this);
          var name = $field.attr("name");
          var value = $field.val();
          
          if (name && value) {
            if ($field.attr("type") === "checkbox" || $field.attr("type") === "radio") {
              if ($field.is(":checked")) {
                formData[name] = value;
              }
            } else {
              formData[name] = value;
            }
          }
        });
        
        // Add node ID and preview mode
        formData.current_node_id = nodeId;
        formData.preview_mode = "node_form";
        formData.trigger_type = "node_form_preview";
        
        // Function to reset button state
        function resetButton() {
          $button.removeData("processing").prop("disabled", false).text("Preview with This Content");
        }
        
        // Make AJAX request to preview endpoint
        var previewUrl = "/admin/dynamic-image-generator/" + posterId + "/preview-with-data/" + nodeId;
        
        $.ajax({
          url: previewUrl,
          type: "POST",
          data: {
            form_data: JSON.stringify(formData)
          },
          timeout: 30000,
          success: function(response) {
            $loader.remove();
            resetButton();
            
            if (response && response.length > 0) {
              for (var i = 0; i < response.length; i++) {
                var command = response[i];
                
                if (command.command === "openModalDialog" || command.command === "openDialog") {
                  var title = "Preview: " + ($button.closest(".dynamic-image-template-container").find("h4").text() || "Template");
                  
                  if (!command.data || command.data.trim() === "") {
                    alert("Preview generated but no content received.");
                    return;
                  }
                  
                  // Create simple modal
                  var modalId = "node-preview-modal-" + Date.now();
                  var $modal = $("<div id=\"" + modalId + "\" style=\"position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 10000; display: flex; align-items: center; justify-content: center;\">" +
                    "<div style=\"background: white; max-width: 90%; max-height: 90%; overflow: auto; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); position: relative;\">" +
                      "<div style=\"padding: 20px; border-bottom: 1px solid #ddd; display: flex; justify-content: space-between; align-items: center; background: #f8f9fa;\">" +
                        "<h3 style=\"margin: 0; color: #2c3e50;\">" + title + "</h3>" +
                        "<button id=\"close-" + modalId + "\" style=\"background: #dc3545; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 16px;\">×</button>" +
                      "</div>" +
                      "<div style=\"padding: 20px; min-width: 800px; min-height: 500px;\">" + command.data + "</div>" +
                    "</div>" +
                  "</div>");
                  
                  $("body").append($modal);
                  
                  // Close functionality
                  $("#close-" + modalId + ", #" + modalId).on("click", function(e) {
                    if (e.target === this) {
                      $modal.remove();
                      $("body").css("overflow", "auto");
                    }
                  });
                  
                  $modal.find("> div").on("click", function(e) {
                    e.stopPropagation();
                  });
                  
                  break;
                }
              }
            } else {
              alert("No preview data received. Please check the template configuration.");
            }
          },
          error: function(xhr, status, error) {
            $loader.remove();
            resetButton();
            console.error("Preview request failed:", error, xhr.responseText);
            alert("Preview request failed: " + error);
          }
        });
        
        return false;
      });
    }
  };
})(jQuery, Drupal);',
    ],
    'node_content_preview_script',
  ];
  
  // Add submit handler
  $form['actions']['submit']['#submit'][] = 'dynamic_image_generator_node_form_submit';
  
  // Store qualifying poster info for the submit handler
  $form_state->set('qualifying_posters', array_keys($qualifying_posters));
}

/**
 * Submit handler for auto-generation when saving content.
 */
function dynamic_image_generator_node_form_submit($form, FormStateInterface $form_state) {
  $node = $form_state->getFormObject()->getEntity();
  $node_id = $node->id();
  
  if (!$node_id) {
    return;
  }
  
  // Get the qualifying posters from form state
  $qualifying_poster_ids = $form_state->get('qualifying_posters') ?: [];
  
  if (empty($qualifying_poster_ids)) {
    return;
  }
  
  $poster_storage = \Drupal::entityTypeManager()->getStorage('poster_entity');
  $generated_count = 0;
  $failed_count = 0;
  
  foreach ($qualifying_poster_ids as $poster_id) {
    $checkbox_key = 'poster_auto_generate_' . $poster_id;
    $auto_generate = $form_state->getValue($checkbox_key);
    
    if (!$auto_generate) {
      continue; // User didn't check this poster's checkbox
    }
    
    $poster = $poster_storage->load($poster_id);
    if (!$poster) {
      continue;
    }
    
    try {
      // Check if service exists before using it
      if (!\Drupal::hasService('dynamic_image_generator.dynamic_image_generator_service')) {
        \Drupal::logger('dynamic_image_generator')->error('Dynamic Image Generator service not available');
        $failed_count++;
        continue;
      }
      
      // Check if we have the required media types before proceeding
      $media_type_storage = \Drupal::entityTypeManager()->getStorage('media_type');
      $available_types = $media_type_storage->loadMultiple();
      
      if (empty($available_types)) {
        \Drupal::logger('dynamic_image_generator')->error('No media types available for image generation');
        $failed_count++;
        continue;
      }
      
      $image_generator = \Drupal::service('dynamic_image_generator.dynamic_image_generator_service');
      $result = $image_generator->generatePosterImage($poster_id, [], $node_id);
      
      if ($result) {
        $generated_count++;
        \Drupal::logger('dynamic_image_generator')->notice('Auto-generated image for template @poster_id "@title" for node @node_id', [
          '@poster_id' => $poster_id,
          '@title' => $poster->label(),
          '@node_id' => $node_id,
        ]);
      } else {
        $failed_count++;
        \Drupal::logger('dynamic_image_generator')->error('Failed to auto-generate image for template @poster_id for node @node_id', [
          '@poster_id' => $poster_id,
          '@node_id' => $node_id,
        ]);
      }
    } catch (\Exception $e) {
      $failed_count++;
      \Drupal::logger('dynamic_image_generator')->error('Exception during auto-generation of template @poster_id for node @node_id: @error', [
        '@poster_id' => $poster_id,
        '@node_id' => $node_id,
        '@error' => $e->getMessage(),
      ]);
    }
  }
  
  // Provide user feedback
  if ($generated_count > 0) {
    if ($generated_count === 1) {
      \Drupal::messenger()->addMessage(t('Image was automatically generated and saved successfully!'), 'status');
    } else {
      \Drupal::messenger()->addMessage(t('@count images were automatically generated and saved successfully!', [
        '@count' => $generated_count,
      ]), 'status');
    }
  }
  
  if ($failed_count > 0) {
    \Drupal::messenger()->addError(t('@count image(s) failed to generate. Please check the logs for details.', [
      '@count' => $failed_count,
    ]));
  }
}

/**
 * Implements hook_theme().
 */
function dynamic_image_generator_theme() {
  return [
    'poster_entity' => [
      'render element' => 'elements',
    ],
  ];
}

/**
 * Prepares variables for poster entity templates.
 *
 * Default template: poster-entity.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the poster entity information and any
 *     fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_poster_entity(array &$variables) {
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for image template forms.
 */
function dynamic_image_generator_form_poster_entity_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Get the entity from the form
  $entity = $form_state->getFormObject()->getEntity();
  
  // First, let's improve how we get the selected content type
  $selected_content_type = NULL;
  
  // Check form_state first (for AJAX updates)
  if ($form_state->hasValue('content_type')) {
    $content_type_values = $form_state->getValue('content_type');
    if (is_array($content_type_values) && isset($content_type_values[0]['value'])) {
      $selected_content_type = $content_type_values[0]['value'];
    }
  } 
  // If not in form_state, try to get from entity
  elseif (method_exists($entity, 'getContentType') && $entity->getContentType()) {
    $selected_content_type = $entity->getContentType();
  }
  
  // Add AJAX callback to content_type field
  $form['content_type']['widget']['#ajax'] = [
    'callback' => 'dynamic_image_generator_content_type_ajax_callback',
    'wrapper' => 'image-template-form',
    'event' => 'change',
    'progress' => [
      'type' => 'throbber',
      'message' => t('Loading...'),
    ],
  ];
  
  // Add an ID to the form so it can be targeted by AJAX
  $form['#prefix'] = '<div id="image-template-form">';
  $form['#suffix'] = '</div>';

  // Add a new select element for user interface
  $form['target_field_select'] = [
    '#type' => 'select',
    '#title' => t('Target Field'),
    '#description' => t('The field in the content type where auto-generation options will appear.'),
    '#options' => ['' => t('- Select a field -')],
    '#weight' => -3.5,
    '#attributes' => ['id' => 'edit-target-field-select'],
  ];

  // Update options based on selected content type
  if ($selected_content_type) {
    $target_field_options = dynamic_image_generator_get_target_fields($selected_content_type);
    $form['target_field_select']['#options'] = ['' => t('- None -')] + $target_field_options;
    
    // Update description
    $form['target_field_select']['#description'] = t('Select which field in @type content will show auto-generation options to users.', [
      '@type' => $selected_content_type,
    ]);
    
    // Get the current target field value
    $current_target_field = '';
    if (method_exists($entity, 'getTargetField') && $entity->getTargetField()) {
      $current_target_field = $entity->getTargetField();
    }
    
    // Set default value for the select if there's a value in the entity
    if (!empty($current_target_field)) {
      $form['target_field_select']['#default_value'] = $current_target_field;
    }
  }

  // Hide the original target_field and update it with JavaScript
  if (isset($form['target_field'])) {
    $form['target_field']['#access'] = FALSE;
    
    // Add JavaScript to sync the fields
    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
    $form['#attached']['library'][] = 'core/jquery.form';
    
    // Add inline JavaScript to sync the selection
    $form['#attached']['html_head'][] = [
      [
        '#type' => 'html_tag',
        '#tag' => 'script',
        '#value' => 'document.addEventListener("DOMContentLoaded", function() {
          (function ($) {
            $("#edit-target-field-select").on("change", function() {
              $("input[name=\'target_field[0][value]\']").val($(this).val());
            });
            // Initialize on page load
            $("input[name=\'target_field[0][value]\']").val($("#edit-target-field-select").val());
          })(jQuery);
        });',
      ],
      'sync_target_field_script',
    ];
    
    $form['#submit'][] = 'dynamic_image_generator_target_field_submit';
  }
  
  // Add token browser container
  $form['token_browser'] = [
    '#type' => 'details',
    '#title' => t('Available Tokens'),
    '#open' => TRUE,
    '#weight' => 5,
  ];
  
  // If we have a selected content type, show token information
  if ($selected_content_type) {
    // Make sure token module is enabled
    if (\Drupal::moduleHandler()->moduleExists('token')) {
      // Get all available fields for the selected content type
      $entity_fields = [];
      $field_definitions = \Drupal::service('entity_field.manager')
        ->getFieldDefinitions('node', $selected_content_type);
      
      foreach ($field_definitions as $field_name => $field_definition) {
        $field_type = $field_definition->getType();
        $entity_fields[] = [
          'name' => $field_name,
          'type' => $field_type,
          'label' => $field_definition->getLabel(),
        ];
      }
      
      // Add token browser with comprehensive token information
      $form['token_browser']['token_help'] = [
        '#type' => 'markup',
        '#markup' => '<h4>' . t('Available Tokens for @type', ['@type' => $selected_content_type]) . '</h4><p>' . 
          t('Use tokens like <code>[node:title]</code>, <code>[node:field_image]</code>, etc. in both HTML and CSS templates.') . '</p>' .
          '<p>' . t('You can also use Twig syntax for conditionals, loops, etc. in both templates.') . '</p>',
      ];
      
      // Group fields by type for better organization
      $grouped_fields = [];
      foreach ($entity_fields as $field) {
        $type = $field['type'];
        if (!isset($grouped_fields[$type])) {
          $grouped_fields[$type] = [];
        }
        $grouped_fields[$type][] = $field;
      }
      
      // Display fields grouped by type
      $markup = '';
      foreach ($grouped_fields as $type => $fields) {
        $markup .= '<h5>' . t('@type Fields', ['@type' => ucfirst($type)]) . '</h5><ul class="token-field-list">';
        foreach ($fields as $field) {
          $markup .= '<li><code>[node:' . $field['name'] . ']</code> - ' . $field['label'] . '</li>';
        }
        $markup .= '</ul>';
      }
      
      $form['token_browser']['field_list'] = [
        '#type' => 'markup',
        '#markup' => $markup,
      ];
      
      // Use token module's token tree browser
      $form['token_browser']['browser'] = [
        '#type' => 'token_tree_link',
        '#token_types' => ['node'],
        '#global_types' => TRUE,
        '#click_insert' => TRUE,
        '#dialog' => TRUE,
        '#show_restricted' => FALSE,
        '#recursion_limit' => 3,
        '#weight' => 10,
      ];
      
      // Add common token usage examples
      $form['token_browser']['examples'] = [
        '#type' => 'markup',
        '#markup' => '<h5>' . t('Common Token Examples') . '</h5>' .
          '<ul>' .
          '<li><code>[node:title]</code> - ' . t('The title of the content') . '</li>' .
          '<li><code>[node:body]</code> - ' . t('The body content') . '</li>' .
          '<li><code>[node:field_image]</code> - ' . t('An image field (if available)') . '</li>' .
          '<li><code>[node:created:medium]</code> - ' . t('The creation date in medium format') . '</li>' .
          '<li><code>[node:author:name]</code> - ' . t("The content author's name") . '</li>' .
          '<li><code>[site:name]</code> - ' . t('The site name') . '</li>' .
          '</ul>',
        '#weight' => 30,
      ];
    }
    else {
      $form['token_browser']['message'] = [
        '#type' => 'markup',
        '#markup' => '<p>' . t('Install the Token module to browse available tokens.') . '</p>',
      ];
    }
  }
  else {
    $form['token_browser']['message'] = [
      '#type' => 'markup',
      '#markup' => '<p>' . t('Select a content type to see available tokens.') . '</p>',
    ];
  }
  
  // Update descriptions for HTML and CSS fields to mention tokens and Twig
  if (isset($form['html']['widget'][0]['value'])) {
    $form['html']['widget'][0]['value']['#description'] = t('HTML content for the image. Use tokens like [node:title], [node:field_image], etc. for content. Use [image_1], [image_2], etc. to reference uploaded images. Supports Twig syntax for conditionals, loops, etc.');
  }
  if (isset($form['css']['widget'][0]['value'])) {
    $form['css']['widget'][0]['value']['#description'] = t('CSS styles for the image. You can use tokens like [node:title] and [image_1], [image_2], etc. tokens in your CSS. Supports Twig syntax for conditionals, loops, etc.');
  }
  
  // Add validation handler
  $form['#validate'][] = 'dynamic_image_generator_image_template_validate';
  
  // Add required libraries
  $form['#attached']['library'][] = 'core/drupal.dialog.ajax';

  // Add preview functionality to the template form
  $form['preview_section'] = [
    '#type' => 'details',
    '#title' => t('Template Preview & Testing'),
    '#open' => FALSE,
    '#weight' => 10,
  ];

  $form['preview_section']['preview_info'] = [
    '#markup' => '<p>' . t('Test your template with different data sources to see how it will look.') . '</p>' .
      '<div style="background: #e7f3ff; padding: 10px; border-radius: 4px; margin-bottom: 15px; border-left: 4px solid #0066cc;">' .
      '<strong>Live Preview:</strong> Uses your current HTML/CSS from the form fields above (not saved data). ' .
      'Make changes to the template and preview them instantly without saving.' .
      '</div>',
  ];

  // Add preview button for testing the template
  if (!$entity->isNew()) {
    // Sample data preview button
    $preview_url = \Drupal\Core\Url::fromRoute('dynamic_image_generator.generate_preview', [
      'poster_entity' => $entity->id(),
    ]);

    // Add node selection for real content preview
    if ($selected_content_type) {
      $form['preview_section']['node_selection'] = [
        '#type' => 'fieldset',
        '#title' => t('Test with Real Content'),
        '#attributes' => [
          'style' => 'margin-top: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 4px; background: #f9f9f9;',
        ],
      ];

      // Node selection autocomplete
      $form['preview_section']['node_selection']['selected_node'] = [
        '#type' => 'entity_autocomplete',
        '#title' => t('Select @type Content', ['@type' => $selected_content_type]),
        '#target_type' => 'node',
        '#selection_settings' => [
          'target_bundles' => [$selected_content_type],
          'sort' => [
            'field' => 'created',
            'direction' => 'DESC',
          ],
        ],
        '#description' => t('Start typing to search for @type content to use for preview.', ['@type' => $selected_content_type]),
        '#attributes' => [
          'id' => 'edit-selected-node',
        ],
        '#ajax' => [
          'callback' => 'dynamic_image_generator_node_selection_ajax_callback',
          'wrapper' => 'node-preview-buttons',
          'event' => 'autocompleteclose',
        ],
      ];

      // Container for preview buttons
      $form['preview_section']['node_selection']['preview_buttons'] = [
        '#type' => 'container',
        '#attributes' => ['id' => 'node-preview-buttons'],
      ];

      // Get selected node ID from form state or default
      $selected_node_id = NULL;
      if ($form_state->hasValue(['preview_section', 'node_selection', 'selected_node'])) {
        $selected_node_id = $form_state->getValue(['preview_section', 'node_selection', 'selected_node']);
      }

      if ($selected_node_id) {
        // Remove the old preview button - only keep live preview
        $form['preview_section']['node_selection']['preview_buttons']['instruction'] = [
          '#markup' => '<p style="margin-top: 10px; color: #28a745; font-weight: bold;">' . 
            t('✓ Content selected. Use the Live Preview button below to test.') . '</p>',
        ];
      } else {
        $form['preview_section']['node_selection']['preview_buttons']['instruction'] = [
          '#markup' => '<p style="margin-top: 10px; color: #666; font-style: italic;">' . 
            t('Select content above to enable live preview.') . '</p>',
        ];
      }

      // Live preview button with current form data (initially hidden)
      $form['preview_section']['live_preview_container'] = [
        '#type' => 'container',
        '#attributes' => [
          'id' => 'live-preview-container',
          'style' => 'margin-top: 20px; padding: 15px; border: 1px solid #17a2b8; border-radius: 4px; background: #e7f6fd; display: none;',
        ],
      ];

      $form['preview_section']['live_preview_container']['info'] = [
        '#markup' => '<p style="margin: 0 0 10px 0; color: #0c5460;"><strong>Live Template Testing:</strong> Preview your current HTML/CSS changes with the selected content. <em>Uses form field values, not saved template data.</em></p>',
      ];

      $form['preview_section']['live_preview_container']['live_preview_button'] = [
        '#type' => 'link',
        '#title' => t('Live Preview with Selected Content'),
        '#url' => \Drupal\Core\Url::fromRoute('dynamic_image_generator.preview_with_data', [
          'poster_entity' => $entity->id(),
          'node' => 'new',
        ]),
        '#attributes' => [
          'class' => ['button', 'live-preview-trigger', 'use-ajax'],
          'data-dialog-type' => 'modal',
          'data-dialog-options' => json_encode([
            'width' => 900,
            'height' => 700,
          ]),
          'style' => 'background: #17a2b8; color: white; border-color: #17a2b8;',
        ],
      ];

        // Add JavaScript for quick node selection and live preview visibility - FIXED VERSION
        $form['#attached']['library'][] = 'dynamic_image_generator/node_selection';
        
        // Add the JavaScript as an external file to avoid encoding issues
        $form['#attached']['drupalSettings']['dynamicImageGenerator'] = [
          'nodeSelectionEnabled' => TRUE,
          'containerId' => 'live-preview-container',
          'fieldId' => 'edit-selected-node',
        ];
    }
    else {
      $form['preview_section']['content_type_required'] = [
        '#markup' => '<p style="margin-top: 15px; color: #e74c3c; font-style: italic;">' . 
          t('Select a content type above to enable preview with real content.') . '</p>',
      ];
    }
  } else {
    $form['preview_section']['save_first'] = [
      '#markup' => '<p><em>' . t('Save the template first to enable preview functionality.') . '</em></p>',
    ];
  }
}

/**
 * AJAX callback for content_type field.
 */
function dynamic_image_generator_content_type_ajax_callback(array &$form, FormStateInterface $form_state) {
  // Simply return the entire form
  return $form;
}

/**
 * AJAX callback for node selection.
 */
function dynamic_image_generator_node_selection_ajax_callback(array &$form, FormStateInterface $form_state) {
  return $form['preview_section']['node_selection']['preview_buttons'];
}

/**
 * Validation handler for image template forms.
 */
function dynamic_image_generator_image_template_validate($form, FormStateInterface $form_state) {
  // Get the content type
  $content_type_values = $form_state->getValue('content_type');
  $content_type = NULL;
  if (is_array($content_type_values) && isset($content_type_values[0]['value'])) {
    $content_type = $content_type_values[0]['value'];
  }
  
  // Get the target field - now take it from target_field_select
  $target_field = $form_state->getValue('target_field_select');
  
  // Get the target field value from the hidden field if select is empty
  if (empty($target_field)) {
    $target_field_values = $form_state->getValue('target_field');
    if (is_array($target_field_values) && isset($target_field_values[0]['value'])) {
      $target_field = $target_field_values[0]['value'];
    }
    
    // If we got a value from the hidden field, update the select field value
    if (!empty($target_field)) {
      $form_state->setValue('target_field_select', $target_field);
    }
  } else {
    // Ensure the target_field value matches the select
    $form_state->setValue(['target_field', 0, 'value'], $target_field);
  }
  
  // Only validate target field if it's not empty
  if (!empty($target_field) && !empty($content_type)) {
    // Validate that the target field is a valid option for the content type
    $valid_fields = dynamic_image_generator_get_target_fields($content_type);
    if (!isset($valid_fields[$target_field])) {
      $form_state->setErrorByName('target_field_select', t('The selected target field "@field" is not valid for the content type "@type".', [
        '@field' => $target_field,
        '@type' => $content_type,
      ]));
    }
  }
}

/**
 * Submit handler to ensure target field value is saved correctly.
 */
function dynamic_image_generator_target_field_submit($form, FormStateInterface $form_state) {
  // Get the selected target field from the select element
  $target_field = $form_state->getValue('target_field_select');
  
  // Update the actual target_field value
  if (!empty($target_field)) {
    $form_state->setValue(['target_field', 0, 'value'], $target_field);
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc