dynamic_image_generator-1.0.x-dev/image_creating_engine/src/Service/WkhtmlImageGenerator.php

image_creating_engine/src/Service/WkhtmlImageGenerator.php
<?php

namespace Drupal\image_creating_engine\Service;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;

/**
 * Service for generating images using wkhtmltoimage.
 */
class WkhtmlImageGenerator {

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The logger service.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * The private tempstore.
   *
   * @var \Drupal\Core\TempStore\PrivateTempStore
   */
  protected $tempStore;

  /**
   * The file URL generator service.
   *
   * @var \Drupal\Core\File\FileUrlGeneratorInterface
   */
  protected $fileUrlGenerator;

  /**
   * Constructs a WkhtmlImageGenerator object.
   */
  public function __construct(
    FileSystemInterface $file_system,
    EntityTypeManagerInterface $entity_type_manager,
    LoggerChannelFactoryInterface $logger_factory,
    ConfigFactoryInterface $config_factory,
    RendererInterface $renderer,
    PrivateTempStoreFactory $temp_store_factory,
    FileUrlGeneratorInterface $file_url_generator
  ) {
    $this->fileSystem = $file_system;
    $this->entityTypeManager = $entity_type_manager;
    $this->logger = $logger_factory->get('wkhtml_image_generator');
    $this->configFactory = $config_factory;
    $this->renderer = $renderer;
    $this->tempStore = $temp_store_factory->get('wkhtml_image_generator');
    $this->fileUrlGenerator = $file_url_generator;
  }

  /**
   * Generate an image from HTML and CSS.
   *
   * @param string $html
   *   The HTML content.
   * @param string $css
   *   The CSS content.
   * @param array $options
   *   An array of options for image generation.
   *
   * @return array|null
   *   An array containing the file URI and File entity, or NULL on failure.
   */
  public function generateImage($html, $css, array $options = []) {
    try {
      // Check dependencies first
      if (!$this->checkDependencies()) {
        return NULL;
      }

      // Set up options for image generation
      $width = $options['width'] ?? 1200;
      $height = $options['height'] ?? 630;
      $format = strtolower($options['format'] ?? 'png');
      $quality = $options['quality'] ?? 94;
      
      // Make sure format is valid
      if (!in_array($format, ['png', 'jpg', 'jpeg'])) {
        $format = 'png';
      }
      
      // Set up the output file path
      $output_dir = 'public://dynamic_image_generator/generated';
      $this->fileSystem->prepareDirectory($output_dir, FileSystemInterface::CREATE_DIRECTORY);
      $output_file = $output_dir . '/image_' . uniqid() . '.' . $format;
      $output_path = $this->fileSystem->realpath($output_file);
      
      // Generate the image using wkhtmltoimage
      $result = $this->generateImageWithWkhtml($html, $css, $output_path, $width, $height, $format, $quality);
      
      if ($result && file_exists($output_path) && filesize($output_path) > 0) {
        // Create a file entity for the generated image
        $file = File::create([
          'uri' => $output_file,
          'uid' => \Drupal::currentUser()->id(),
          'status' => FileInterface::STATUS_PERMANENT,
        ]);
        $file->save();
        
        $this->logger->info('Successfully generated image at: @uri with dimensions @widthx@height, size: @size bytes', [
          '@uri' => $output_file,
          '@width' => $width,
          '@height' => $height,
          '@size' => filesize($output_path),
        ]);
        
        // Return both the file URI and the File entity
        return [
          'uri' => $output_file,
          'file' => $file,
          'url' => $this->fileUrlGenerator->generateAbsoluteString($output_file),
          'width' => $width,
          'height' => $height,
          'format' => $format,
          'generator' => 'wkhtmltoimage',
        ];
      } else {
        $this->logger->error('Failed to generate image with wkhtmltoimage');
      }
      
      return NULL;
    }
    catch (\Exception $e) {
      $this->logger->error('Error generating image: @error', ['@error' => $e->getMessage()]);
      return NULL;
    }
  }

  /**
   * Check if wkhtmltoimage is available.
   *
   * @return bool
   *   TRUE if wkhtmltoimage is available, FALSE otherwise.
   */
  protected function checkDependencies() {
    $wkhtml_path = $this->findWkhtmlPath();
    if (!$wkhtml_path) {
      $this->logger->error('wkhtmltoimage is not installed or not in PATH. Install with: sudo apt-get install wkhtmltopdf');
      return FALSE;
    }

    // Test if wkhtmltoimage can run
    $test_command = escapeshellarg($wkhtml_path) . ' --version 2>&1';
    $output = shell_exec($test_command);
    
    if (!$output || strpos($output, 'wkhtmltoimage') === FALSE) {
      $this->logger->error('wkhtmltoimage test failed. Output: @output', ['@output' => $output]);
      return FALSE;
    }

    $this->logger->info('wkhtmltoimage available at: @path, version: @version', [
      '@path' => $wkhtml_path,
      '@version' => trim($output),
    ]);
    
    return TRUE;
  }

  /**
   * Find wkhtmltoimage executable path.
   *
   * @return string|null
   *   Path to wkhtmltoimage executable or NULL if not found.
   */
  public function findWkhtmlPath() {
    $possible_paths = [
      '/usr/bin/wkhtmltoimage',
      '/usr/local/bin/wkhtmltoimage',
      '/opt/bin/wkhtmltoimage',
      '/bin/wkhtmltoimage',
    ];
    
    // Check direct paths first
    foreach ($possible_paths as $path) {
      if (file_exists($path) && is_executable($path)) {
        return $path;
      }
    }
    
    // Try which command
    $which_result = trim(shell_exec('which wkhtmltoimage 2>/dev/null') ?: '');
    if ($which_result && file_exists($which_result) && is_executable($which_result)) {
      return $which_result;
    }
    
    return NULL;
  }

  /**
   * Generate image using wkhtmltoimage.
   *
   * @param string $html
   *   The HTML content.
   * @param string $css
   *   The CSS content.
   * @param string $output_path
   *   Path to save the image.
   * @param int $width
   *   Image width.
   * @param int $height
   *   Image height.
   * @param string $format
   *   Image format.
   * @param int $quality
   *   Image quality (for JPEG).
   *
   * @return bool
   *   TRUE if successful, FALSE otherwise.
   */
  protected function generateImageWithWkhtml($html, $css, $output_path, $width, $height, $format, $quality) {
    try {
      $wkhtml_path = $this->findWkhtmlPath();
      if (!$wkhtml_path) {
        throw new \Exception('wkhtmltoimage not found');
      }

      // Create temporary HTML file
      $temp_html = tempnam(sys_get_temp_dir(), 'wkhtml_') . '.html';
      $full_html = $this->buildCompleteHtml($html, $css);
      file_put_contents($temp_html, $full_html);

      // Build wkhtmltoimage command
      $command_parts = [
        escapeshellarg($wkhtml_path),
        '--width', (string)$width,
        '--height', (string)$height,
        '--format', ($format === 'jpg' ? 'jpg' : 'png'),
      ];

      // Add quality for JPEG
      if ($format === 'jpg' || $format === 'jpeg') {
        $command_parts[] = '--quality';
        $command_parts[] = (string)$quality;
      }

      // Add other useful options
      $command_parts = array_merge($command_parts, [
        '--enable-local-file-access',
        '--disable-smart-width',
        '--no-stop-slow-scripts',
        '--no-pdf-compression',
        '--disable-javascript', // Disable JS for security and speed
        escapeshellarg($temp_html),
        escapeshellarg($output_path),
      ]);

      $command = implode(' ', $command_parts) . ' 2>&1';
      
      $this->logger->info('Executing wkhtmltoimage command: @command', ['@command' => $command]);
      
      // Execute command
      $output = shell_exec($command);
      $success = file_exists($output_path) && filesize($output_path) > 0;

      if ($success) {
        $this->logger->info('wkhtmltoimage generated image successfully. Size: @size bytes', [
          '@size' => filesize($output_path),
        ]);
      } else {
        $this->logger->error('wkhtmltoimage failed. Output: @output', ['@output' => $output]);
      }

      // Clean up temporary file
      if (file_exists($temp_html)) {
        unlink($temp_html);
      }

      return $success;
      
    } catch (\Exception $e) {
      $this->logger->error('Error in wkhtmltoimage generation: @error', ['@error' => $e->getMessage()]);
      
      // Clean up temporary file
      if (isset($temp_html) && file_exists($temp_html)) {
        unlink($temp_html);
      }
      
      return FALSE;
    }
  }

  /**
   * Build complete HTML document with CSS.
   *
   * @param string $html
   *   The HTML content.
   * @param string $css
   *   The CSS content.
   *
   * @return string
   *   Complete HTML document.
   */
  protected function buildCompleteHtml($html, $css) {
    $full_html = '<!DOCTYPE html>';
    $full_html .= '<html lang="en">';
    $full_html .= '<head>';
    $full_html .= '<meta charset="UTF-8">';
    $full_html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
    $full_html .= '<style>';
    $full_html .= 'body { margin: 0; padding: 20px; font-family: Arial, sans-serif; }';
    $full_html .= $css;
    $full_html .= '</style>';
    $full_html .= '</head>';
    $full_html .= '<body>';
    $full_html .= $html;
    $full_html .= '</body>';
    $full_html .= '</html>';
    
    return $full_html;
  }

  /**
   * Test wkhtmltoimage installation.
   *
   * @return array
   *   Test results.
   */
  public function testInstallation() {
    $results = [
      'wkhtml_found' => FALSE,
      'wkhtml_path' => NULL,
      'version' => '',
      'can_generate' => FALSE,
      'error_messages' => [],
    ];

    try {
      // Find wkhtmltoimage
      $wkhtml_path = $this->findWkhtmlPath();
      $results['wkhtml_path'] = $wkhtml_path;
      $results['wkhtml_found'] = !empty($wkhtml_path);

      if (!$wkhtml_path) {
        $results['error_messages'][] = 'wkhtmltoimage executable not found';
        return $results;
      }

      // Get version
      $version_cmd = escapeshellarg($wkhtml_path) . ' --version 2>&1';
      $version_output = shell_exec($version_cmd);
      $results['version'] = trim($version_output);

      // Test image generation
      $test_html = '<div style="width:200px;height:100px;background:green;color:white;text-align:center;line-height:100px;font-weight:bold;">WKHTML TEST</div>';
      $test_css = 'body { margin: 0; padding: 10px; }';
      $temp_output = tempnam(sys_get_temp_dir(), 'wkhtml_test_') . '.png';

      $test_result = $this->generateImageWithWkhtml($test_html, $test_css, $temp_output, 220, 120, 'png', 94);
      
      if ($test_result && file_exists($temp_output) && filesize($temp_output) > 0) {
        $results['can_generate'] = TRUE;
        $results['test_file_size'] = filesize($temp_output);
        unlink($temp_output);
      } else {
        $results['error_messages'][] = 'Test image generation failed';
      }

    } catch (\Exception $e) {
      $results['error_messages'][] = 'Test failed: ' . $e->getMessage();
    }

    return $results;
  }

}

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

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