Source for file pdfimporter.php

Documentation is available at pdfimporter.php

  1. <?php
  2. // This file is part of the Sloodle project (www.sloodle.org) and is released under the GNU GPL v3.
  3.  
  4. /**
  5. * Defines the SLOODLE Presenter importer plugin for converting a PDF file to a list of images (1 image per page).
  6. * Many thanks to Jordan Guinaud for his original code which made this plugin possible.
  7. * Note: this plugin requires PHP >= 4.1.0 and the presence of the IMagick extension.
  8. *
  9. @package sloodle
  10. @copyright Copyright (c) 2009 Sloodle (various contributors)
  11. @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
  12. @since Sloodle 0.4.1
  13. *
  14. @contributor Jordan Guinaud
  15. @contributor Peter R. Bloomfield
  16. *
  17. */
  18.  
  19.  
  20. // Path to the ImageMagick "convert" file. Leave it blank to DISABLE this feature for security.
  21. // Likely to be something like this:  '/usr/bin/convert' or '/usr/local/bin/convert'
  22. global $IMAGICK_CONVERT_PATH;
  23. $IMAGICK_CONVERT_PATH '/usr/local/bin/convert';
  24.  
  25.  
  26. /**
  27. * Presenter plugin for importing a PDF file as a series of image slides.
  28. *
  29. @package sloodle
  30. */
  31. {
  32.     /**
  33.     * Render this importer on a web page.
  34.     * All importing functionality should be handled by this function as well.
  35.     * @param string $url A URL to this Presenter, without a "mode" parameter.
  36.     * @param SloodleModulePresenter $presenter An object representing this Presenter.
  37.     */
  38.     function render($url$presenter)
  39.     {
  40.         // Get translation strings
  41.         $struploadfile get_string('upload:file''sloodle');
  42.         $strselectuploadfile get_string('upload:selectfile''sloodle');
  43.         $strimportposition get_string('presenter:importposition''sloodle');
  44.         $strimportfromcomputer get_string('presenter:importfrommycomputer''sloodle');
  45.         $strimportname get_string('presenter:importname''sloodle');
  46.         $strimportnamecaption get_string('presenter:importnamecaption''sloodle');
  47.  
  48.         // Get expected form data
  49.         $selectfile optional_param('selectfile'''PARAM_CLEAN);
  50.         $uploadfile optional_param('uploadfile'''PARAM_CLEAN);
  51.         $importname optional_param('importname'''PARAM_CLEAN);
  52.         $position = (int)optional_param('sloodleentryposition'-1PARAM_INT);
  53.         
  54.         // Has a file been selected?
  55.         if (!empty($selectfile)) {
  56.             // Has a local file name been specified to import from?
  57.             $localfile optional_param('sloodleimportfile'''PARAM_CLEAN);
  58.             if (!empty($localfile)) return $this->import_file($presenter$localfile$importname$position);
  59.         }
  60.  
  61.         // Has a file been uploaded?
  62.         if (!empty($uploadfile)) {
  63.             // Has an upload been made which we can import from?
  64.             $localfile ''$name '';
  65.             $res $this->process_upload($localfile$name);
  66.             if ($res === true{
  67.                 sloodle_debug("Upload successful<br/>\n");
  68.                 return $this->import_file($presenter$localfile$importname$position);
  69.             }
  70.             if (is_string($res)) error($res$url.'&amp;mode=edit');
  71.         }
  72.  
  73.         // No file specified - display forms to let the user select or upload the file.
  74.  
  75.         // Determine our maximum upload size
  76.         $maxsize sloodle_get_max_post_upload();
  77.         $maxsizedesc sloodle_get_size_description($maxsize);
  78.  
  79.         // Open the form
  80.         echo '<form action="" method="post" enctype="multipart/form-data"><fieldset style="border-style:none;">';
  81.         echo '<input type="hidden" name="id" id="id" value="'.$presenter->cm->id.'" />';
  82.         echo '<input type="hidden" name="mode" id="mode" value="importslides" />';
  83.         echo '<input type="hidden" name="sloodleplugintype" id="sloodleplugintype" value="'.$this->get_id().'" />';
  84.         echo '</fieldset><br/>';
  85.  
  86.         // Let the user specify a name for the imported files
  87.         echo '<label for="importname" title="'.$strimportnamecaption.'">'.$strimportname.': </label>';
  88.         echo '<input type="text" name="importname" id="importname" value="" size="30" maxlength="100" title="'.$strimportnamecaption.'" />';
  89.         echo "<br/><br/>\n";
  90.         // Let the user select the position to upload to in the Presentation
  91.         $this->print_slide_position_menu($presenter$position);
  92.         echo "<br/><br/>\n";
  93.  
  94.         // Display our upload form
  95.         echo '<fieldset style="width:50%; margin-left:auto; margin-right:auto;">';
  96.         echo '<h3>'.$strimportfromcomputer."</h3>\n";
  97.         echo '<input type="hidden" name="MAX_FILE_SIZE" value="'.$maxsize.'" />';
  98.         echo '<label for="userfile">'.$strselectuploadfile.': </label>';
  99.         echo '<input type="file" name="userfile" id="userfile" size="50" />';
  100.         echo '<p style="font-style:italic; font-size:90%;">['.get_string('upload:maxsize''sloodle'$maxsizedesc)."]</p><br/>\n";
  101.         echo '<input type="submit" name="uploadfile" id="uploadfile" value="'.$struploadfile.'" /><br/>'."\n";
  102.         echo '</fieldset><br/>';
  103.  
  104.         // TODO: add a separate section allowing the import of a file elsewhere on the web
  105.  
  106.         // TODO: add a separate section allowing the import of a file already in the course/site files
  107.  
  108.         // Close the form
  109.         echo "</fieldset></form><br/>\n";
  110.  
  111.     }
  112.  
  113.     /**
  114.     * Display a drop-down menu of slides in the current presentation.
  115.     * @param SloodleModulePresenter $presenter An object representing the Preseter to work from.
  116.     * @param integer $position Specifies the intially selected position, if known. Defaults to end.
  117.     */
  118.     function print_slide_position_menu($presenter$position = -1)
  119.     {
  120.         // Get translation strings
  121.         $strimportposition get_string('presenter:importposition''sloodle');
  122.         $strimportpositioncaption get_string('presenter:importpositioncaption''sloodle');
  123.  
  124.         // Get a list of slides
  125.         $slides $presenter->get_slides();
  126.         // Display the form element
  127.         echo '<label for="sloodleentryposition" title="'.$strimportpositioncaption.'">'.$strimportposition.': </label>';
  128.         echo '<select name="sloodleentryposition" id="sloodleentryposition" size="1" title="'.$strimportpositioncaption.'">'."\n";
  129.         $selected false;
  130.         foreach ($slides as $curslide{
  131.             // Add this slide to the menu
  132.             echo "<option value=\"{$curslide->slideposition}\"";
  133.             if ($curslide->slideposition == $position{
  134.                 echo ' selected="selected"';
  135.                 $selected true;
  136.             }
  137.             echo ">{$curslide->slideposition}: {$curslide->name}</option>\n";
  138.         }
  139.         // Add an 'end' option
  140.         $endentrynum $curslide->slideposition 1;
  141.         echo "<option value=\"{$endentrynum}\"";
  142.         if (!$selectedecho " selected=\"selected\"";
  143.         echo ">--".get_string('end''sloodle')."--</option>\n";
  144.         echo "</select>\n";
  145.     }
  146.  
  147.  
  148.     /**
  149.     * Attempt to process a file upload.
  150.     * @param string $path [out] This reference parameter will contain the path of the uploaded file if an upload has been performed. It will be empty if no upload has occurred.
  151.     * @param string $name [out] This reference parameter will contain the original name of the uploaded file if an upload has been performed.
  152.     * @return bool|stringReturns true if an upload occurred succesfully. Returns false if no upload has happened. Returns a string containing an error message if an error occurred.
  153.     */
  154.     function process_upload(&$path&$name)
  155.     {
  156.         // If no file has been uploaded, then there is nothing to do
  157.         if (empty($_FILES['userfile']['name'])) return false;
  158.  
  159.         // Is the file empty?
  160.         if ((int)$_FILES['userfile']['size'== 0return get_string('upload:emptyfile''sloodle');
  161.  
  162.         // Was an error code specified?
  163.         if (isset($_FILES['userfile']['error'])) {
  164.             switch ($_FILES['userfile']['error']{
  165.                 case UPLOAD_ERR_INI_SIZEcase UPLOAD_ERR_FORM_SIZEreturn get_string('upload:toobig''sloodle');
  166.                 case UPLOAD_ERR_PARTIALreturn get_string('upload:partial''sloodle');
  167.             }
  168.             if ($_FILES['userfile']['error'!= UPLOAD_ERR_OKreturn get_string('upload:error''sloodle');
  169.         }
  170.         
  171.         // Store the file path and name
  172.         $name $_FILES['userfile']['name'];
  173.         $path $_FILES['userfile']['tmp_name'];
  174.         return true;
  175.     }
  176.  
  177.  
  178.     /**
  179.     * Import a file to this Presenter.
  180.     * @param SloodleModulePresenter $presenter An object representing the Presenter we are importing into.
  181.     * @param string $path The path of the file to import (must be local... i.e. on disk!)
  182.     * @param string $name Optional -- a name for the import. If omitted, it will be taken from the path.
  183.     * @param integer $position The position at which the slides should be imported. Optional. Defaults to import at the end.
  184.     * @return bool True if successful or false if not.
  185.     */
  186.     function import_file($presenter$path$name ''$position = -1)
  187.     {
  188.         global $CFG;
  189.  
  190.         // PHP 4 doesn't support recursive creation of folders, so we need to do this the manual way
  191.         $dir_sitefiles $CFG->dataroot.'/'.SITEID;
  192.         $dir_presenter $dir_sitefiles.'/presenter';
  193.         $dir_import $dir_presenter.'/'.$presenter->cm->id;
  194.         if (!file_exists($dir_sitefiles)) mkdir($dir_sitefiles);
  195.         if (!file_exists($dir_presenter)) mkdir($dir_presenter);
  196.         if (!file_exists($dir_import)) mkdir($dir_import);
  197.         // Now check on last time that the import folder exists
  198.         if (!file_exists($dir_import)) {
  199.             error("Failed to create directory for imported images. Please check the file permissions for your MoodleData folder.<br/><br/>Attempted to create: {$dir_import}");
  200.         }
  201.  
  202.         // Construct the URL of the folder for viewing the files
  203.         $dir_view $CFG->wwwroot.'/file.php/'.SITEID.'/presenter/'.$presenter->cm->id;
  204.  
  205.         // Use the file name from the path if necessary
  206.         if (empty($name)) $name basename($path);
  207.         // Construct a basic identifier for the files which will be imported.
  208.         // It will consist of a timestamp and the import name
  209.         $filebase gmdate('U').'_'.str_replace(" ""_"$name);
  210.  
  211.         // We'll use JPG files as standard just now. We could make this customizable in future?
  212.         $ext 'jpg';
  213.  
  214.         // Attempt each importing method in turn
  215.         $result $this->_import_MagickWand($presenter$path$dir_import$dir_view$filebase$ext$name$position);
  216.         if ($result === false$result $this->_import_ImageMagick($presenter$path$dir_import$dir_view$filebase$ext$name$position);
  217.  
  218.         // Prepare a "Continue" link which takes us to edit mode
  219.         $continueURL $CFG->wwwroot."/mod/sloodle/view.php?id={$presenter->cm->id}&amp;mode=edit";
  220.  
  221.         // Display the results (in future, it might be good to show a list of slides, and let the user rename or delete them before addition to the presentation)
  222.         if ($result === false{
  223.             echo "<h3>",get_string('presenter:importfailed''sloodle'),"</h3>\n";
  224.             echo "<h4>",get_string('presenter:importneedimagick''sloodle'),"</h4>\n";
  225.             redirect($continueURL''5);
  226.             return false;
  227.         }
  228.         echo "<h3>",get_string('presenter:importsuccessful''sloodle'$result),"</h3>\n";
  229.         redirect($continueURL''5);
  230.         return true;
  231.     }
  232.     
  233.  
  234.     /**
  235.     * Imports the given file using the MagickWand extension if possible. (Internal only)
  236.     * @param SloodleModulePresenter $presenter An object representing the Presenter we are importing into.
  237.     * @param string $srcfile Full path of the PDF file we are importing
  238.     * @param string $destpath Folder path to which the imported files will be added.
  239.     * @param string $viewurl URL of the folder in which the imported files will be viewed
  240.     * @param string $destfile Name for the output files (excluding extension, such as .jpg). The page numbers will be appended automatically, before the extension
  241.     * @param string $destfileext Extension for destination files, not including the dot. (e.g. "jpg" or "png").
  242.     * @param string $destname Basic name to use for each imported slide. The page numbers will be appended automatically.
  243.     * @param integer $position The position within the Presentation to add the new slides. Optional. Default is to put them at the end.
  244.     * @return integer|boolIf successful, an integer indicating the number of slides loaded is displayed. If the import does not (or cannot) work, then boolean false is returned.
  245.     * @access private
  246.     */
  247.     function _import_MagickWand($presenter$srcfile$destpath$viewurl$destfile$destfileext$destname$position = -1)
  248.     {
  249.         global $CFG;
  250.         // Check to see if the MagickWand extension is already loaded.
  251.         // Attempt to load it if not -- the name will vary depending on OS
  252.         sloodle_debug('<br/><strong>Checking for presence of extension "magickwand"...</strong> ');
  253.         if (!extension_loaded('magickwand')) {
  254.             sloodle_debug('not loaded.<br/>Checking OS... ');
  255.             if (strtoupper(substr(PHP_OS03)) === 'WIN'{
  256.                 sloodle_debug('Windows.<br/>Attempting to load "php_magickwand.dll"... ');
  257.                 @dl('php_magickwand.dll');
  258.             else {
  259.                 sloodle_debug('Non-Windows.<br>Attempting to load "magickwand.so"... ');
  260.                 @dl('magickwand.so');
  261.             }
  262.             // If it's still not loaded, then we cannot use this function
  263.             if (!@extension_loaded('magickwand')) {
  264.                 sloodle_debug('extension unavailable.<br/>');
  265.                 return false;
  266.             }
  267.         }
  268.         sloodle_debug('extension loaded.<br/>');
  269.         
  270.         // Load the PDF file
  271.         sloodle_debug('Loading PDF file... ');
  272.         $mwand NewMagickWand();
  273.         if (!MagickReadImage($mwand$srcfile)) {
  274.             sloodle_debug('failed.<br/>');
  275.             return false;
  276.         }
  277.         sloodle_debug('OK.<br/>');
  278.         
  279.         // Quick validation - position should start at 1. (-ve numbers mean "at the end")
  280.         if ($position == 0$position 1;
  281.  
  282.         // Go through each page
  283.         sloodle_debug('Preparing to iterate through pages of document...<br/>');
  284.         MagickSetFirstIterator($mwand);
  285.         $pagenum 0$page_position = -1;
  286.         do {
  287.             // Determine this page's position in the Presentation
  288.             if ($position 0$page_position $position $pagenum;
  289.             $pagenum++;
  290.  
  291.             // Construct the file and slide names for this page
  292.             $page_filename "{$destpath}/{$destfile}-{$pagenum}.{$destfileext}"// Where it gets uploaded to
  293.             $page_slidesource "{$viewurl}/{$destfile}-{$pagenum}.{$destfileext}"// The URL to access it publicly
  294.             $page_slidename "{$destname} ({$pagenum})";
  295.  
  296.             // Output the file
  297.             sloodle_debug(" Writing page {$pagenum} to file...");
  298.             if (!MagickWriteImage($mwand$page_filename)) {
  299.                 sloodle_debug('failed.<br/>');
  300.             else {
  301.                 sloodle_debug('OK.<br/>');
  302.             }
  303.  
  304.             // Add the entry to the Presenter
  305.             sloodle_debug("  Adding slide \"{$page_slidename}\" to presentation at position {$page_position}... ");
  306.             if (!$presenter->add_entry($page_slidesource'PresenterSlideImage'$page_slidename$page_position)) {
  307.                 sloodle_debug('failed.<br/>');
  308.             else {
  309.                 sloodle_debug('OK.<br/>');
  310.             }
  311.             
  312.         while (MagickNextImage($mwand));
  313.         sloodle_debug('Finished.<br/>');
  314.         DestroyMagickWand($mwand);
  315.         return $pagenum;
  316.     }
  317.  
  318.  
  319.     /**
  320.     * Imports the given file using the ImageMagick command line programs if possible. (Internal only)
  321.     * @param SloodleModulePresenter $presenter An object representing the Presenter we are importing into.
  322.     * @param string $srcfile Full path of the PDF file we are importing
  323.     * @param string $destpath Folder path to which the imported files will be added.
  324.     * @param string $viewurl URl of the folder in which the files can be viewed
  325.     * @param string $destfile Name for the output files (excluding extension, such as .jpg). The page numbers will be appended automatically, before the extension
  326.     * @param string $destfileext Extension for destination files, not including the dot. (e.g. "jpg" or "png").
  327.     * @param string $destname Basic name to use for each imported slide. The page numbers will be appended automatically.
  328.     * @param integer $position The position within the Presentation to add the new slides. Optional. Default is to put them at the end.
  329.     * @return integer|boolIf successful, an integer indicating the number of slides loaded is displayed. If the import does not (or cannot) work, then boolean false is returned.
  330.     * @access private
  331.     */
  332.     function _import_ImageMagick($presenter$srcfile$destpath$viewurl$destfile$destfileext$destname$position = -1)
  333.     {
  334.         global $IMAGICK_CONVERT_PATH;
  335.  
  336.         // Do a security check -- has command-line execution of IMagick been disabled by the admin?
  337.         sloodle_debug("<br/><strong>Attempting to use ImageMagick by command-line.</strong><br/>");
  338.         if (empty($IMAGICK_CONVERT_PATH)) {
  339.             sloodle_debug(" ERROR: path to ImageMagick \"convert\" program is blank.");
  340.             return false;
  341.         }
  342.         // Now make sure there are no quotation marks in the source/destination file and path names
  343.         //  (these could be used to execute malicious commands on the server)
  344.         if (strpos($srcfile"\""!== false || strpos($destpath"\""!== false || strpos($destfile"\""!== false || strpos($destfileext"\""!= falseerror("Invalid file name -- please remove quotation marks from file names.");
  345.  
  346.         // Execute the conversion command
  347.         $cmd escapeshellcmd("{$IMAGICK_CONVERT_PATH} -verbose \"{$srcfile}\" \"{$destpath}/{$destfile}.{$destfileext}\"");
  348.         $output '';
  349.         sloodle_debug(" Executing shell command<br/>");
  350.         $result exec($cmd);
  351.         // If all the output is empty, then execution failed
  352.         if (empty($result&& empty($output)) {
  353.             sloodle_debug(" ERROR: execution of the shell command failed.<br/>");
  354.             return false;
  355.         }
  356.         
  357.         // Quick validation - position should start at 1. (-ve numbers mean "at the end")
  358.         if ($position == 0$position 1;
  359.  
  360.         // Go through each page which was created.
  361.         // Stop when we encounter a file which wasn't created -- that will be the end of the document.
  362.         $pagenum 0$page_position = -1;
  363.         $stop false;
  364.         while ($stop == false && $pagenum 10000{
  365.             // Determine this page's position in the Presentation
  366.             if ($position 0$page_position $position $pagenum;
  367.  
  368.             // Construct the file and slide names for this page
  369.             $page_filename "{$destpath}/{$destfile}-{$pagenum}.{$destfileext}"// Where it gets uploaded to
  370.             $page_slidesource "{$viewurl}/{$destfile}-{$pagenum}.{$destfileext}"// The URL to access it publicly
  371.             $page_slidename "{$destname} (".($pagenum 1).")";
  372.             // Was this file created?
  373.             if (file_exists($page_filename)) {
  374.                 // Add it to the Presenter
  375.                 $presenter->add_entry($page_slidesource'PresenterSlideImage'$page_slidename$page_position);
  376.                 $pagenum++;
  377.             else {
  378.                 $stop true;
  379.             }
  380.         }
  381.  
  382.         return $pagenum;
  383.     }
  384.     
  385.  
  386.  
  387.     /**
  388.     * Gets the human-readable name of this plugin.
  389.     * @param string $lang Optional -- can specify the language we want the plugin name in, as an identifier like "en_utf8". If unspecified, then the current Moodle language should be used.
  390.     * @access public
  391.     * @return string The human-readable name of this plugin
  392.     */
  393.     function get_plugin_name($lang null)
  394.     {
  395.         return 'PDF Importer';
  396.     }
  397.  
  398.     /**
  399.     * Gets the human-readable description of this plugin.
  400.     */
  401.     function get_plugin_description($lang null)
  402.     {
  403.         return 'Imports an Adbobe Acrobat (PDF) file into your Presentation. Each page becomes a single image slide.';
  404.     }
  405.  
  406.     /**
  407.     * Gets the internal version number of this plugin.
  408.     * This should be a number like the internal version number for Moodle modules, containing the date and release number.
  409.     * Format is: YYYYMMDD##.
  410.     * For example, "2009012302" would be the 3rd release on the 23rd January 2009.
  411.     * @return int The version number of this module.
  412.     */
  413.     function get_version()
  414.     {
  415.         return 2009063000;
  416.     }
  417. }
  418.  
  419.  
  420. ?>

Documentation generated on Fri, 17 Jul 2009 11:02:21 +0100 by phpDocumentor 1.4.0