07.26 php saved
The_Cake_Eater
Tags add more
 
Note
Version 1.4 of upload behaviour
  1. <?php
  2. /**
  3. *  Improved Upload Behaviour
  4. *
  5. *  This behaviour is based on Chris Partridge's upload behaviour (http://bin.cakephp.org/saved/17539)
  6. *  @author Chris Partridge http://blog.chris-partridge.info/2007/03/16/uploadbehavior-is-here/
  7. *  @author Tane Piper (digitalspaghetti@gmail.com)
  8. *  @author Tijs Teulings
  9. *  @author The_Cake_Eater
  10. *
  11. *  @link http://www.digitalspaghetti.me.uk
  12. *
  13. *  @todo fields dir, mimetype, filesize will break if more than one fields are used for upload
  14. *  @todo option to only overwrite files that were referenced by the same field before
  15. *  @todo add naming mode 'modelid' where the file name is model->id + extension ==> is tricky since the id is not available in beforeSave for new records
  16. *
  17. *  @version 1.4
  18. *
  19. *  How to use
  20. *  ==========
  21. *  1) Download this behaviour and place it in your models/behaviours/upload.php
  22. *  2) Insert the following SQL into your database.  This is a basic model you can expand on:
  23. *        CREATE TABLE `images` (
  24. *          `id` int(8) unsigned NOT NULL auto_increment,
  25. *          `filename` varchar(255) default NULL,
  26. *          `dir` varchar(255) default NULL,
  27. *          `mimetype` varchar(255) NULL,
  28. *          `filesize` int(11) unsigned default NULL,
  29. *          `created` datetime default NULL,
  30. *          `modified` datetime default NULL,
  31. *          PRIMARY KEY  (`id`) ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
  32. *  3) In your model that you want to have the upload behavior work, place the below code.  This example is for an Image model:
  33. *
  34. *     var $actsAs = array('Upload' => array(
  35. *        'filename' => array(
  36. *           'dir' => 'files/images',
  37. *           'overwrite_existing' => false,
  38. *           'delete_obsolete_files' => true,
  39. *           'naming' => 'filename',
  40. *           'naming_if_exists' => 'increment',
  41. *           'create_directory' => false,
  42. *           'allowed_mime' => array('image/jpeg', 'image/pjpeg', 'image/gif', 'image/png'),
  43. *           'allowed_ext'  => array('.jpg', '.jpeg', '.png', '.gif'),
  44. *           'thumbsizes' => array(
  45. *              'small'   =>    array('width'=>100, 'height'=>100),
  46. *              'medium'  =>    array('width'=>220, 'height'=>220),
  47. *              'large'   =>   array('width'=>800, 'height'=>600)
  48. *           )
  49. *        )
  50. *    ));
  51. *
  52. *  The above code will save the uploaded file's name in the 'filename' field in database,
  53. *  it will not overwrite existing files, instead it will create a random filename if
  54. *  it already exists.
  55. *  Allowed Mimetypes and extentions should be pretty explanitory
  56. *  For thumbnails, when the file is uploaded, it will create 3 thumbnail sizes and prepend the name
  57. *  to the thumbfiles (i.e. image_001.jpg will produced thumb.small.image_001.jpg, thumb.medium.image_001.jpg, etc)
  58. *
  59. *  4) Create your upload view, make sure it's a multipart/form-data form, and the filename field is of type $form->file
  60. *  5) Make sure your directory is at least CHMOD 775, also check your php.ini MAX_UPLOAD_SIZE is enough to support the filesizes you are uploading
  61. *
  62. *  Version History
  63. *  ===============
  64. *  1.4 25/7/2007 The_Cake_Eater
  65. *    - fixed the merging of defauilt options with given options
  66. *    + introduced 'naming' option specifying how the name is generatd (filename, random)
  67. *    + introduced 'naming_if_exists' option specifying how alternate names are generated (random, increment)
  68. *    + introduced 'delete_obsolete_files' option specifying if files that are no longer referenced by a field (replaced with a new file)
  69. *                  or files referenced by a deleted record are deleted
  70. *    * refactoring of parts of the code
  71. *    - some minor fixes
  72. *    - some performance improvements
  73. *
  74. *  1.3 12/7/2007 Tijs Teulings (tijs@automatique.nl) http://bin.cakephp.org/saved/21813
  75. *    - default quality to 'high'
  76. *    - fixed proportional resizing
  77. *    - fixed permissions for both thumbs and originals
  78. *
  79. *  1.2 22/6/2007 Tijs Teulings http://bin.cakephp.org/saved/21697
  80. *    * made filename checks case insensitive
  81. *    * removed thumb component dependency
  82. *    - fixes permissions for original file now
  83. *    - fixed handling of file extensions (supports filenames with dots in them now)
  84. *    * rounding new sizes for more reliable results
  85. *
  86. *  1.1
  87. *    * Improved Image scaling code
  88. *    - Fixed check to see if file exists and rename file to unique name
  89. *    * Improved model actsAs to allow more thumbnail sizes
  90. *
  91. *  1.0
  92. *    + Initial release with thumbnail code.
  93. */
  94.  
  95. uses('folder');
  96. uses('file');
  97.  
  98. class UploadBehavior extends ModelBehavior
  99. {
  100.    static $default_options = array('dir' => '',
  101.    'allowed_mime'          => array(),
  102.    'allowed_ext'           => array(),
  103.    'delete_obsolete_files' => true,
  104.    'overwrite_existing'    => false,
  105.    'naming'                => 'filename', // random, filename
  106.    'naming_if_exists'      => 'random',   // random, increment
  107.    'create_directory'      => true,
  108.    'thumbsizes'            => array()
  109.    );
  110.  
  111.    /*
  112.     * temporary registry of obsolete file names, required since obsolete files are identified in beforeSave and beforeDelete
  113.     * but are removed in afterSave and afterDelete
  114.     */
  115.    var $__obsoleteFiles = array();
  116.  
  117.    /*
  118.     * parses the configuration array and sets up the $this->settings property
  119.     */
  120.    function setup(&$model, $config=array())
  121.    {
  122.       $folder = &new Folder;
  123.  
  124.       // iterate over all upload enabled fields
  125.       foreach($config as $field => $options)
  126.       {
  127.          // Check if given field exists
  128.          if(!$model->hasField($field)) {
  129.             unset($config[$field]);
  130.             unset($model->data[$model->name][$field]);
  131.             continue;
  132.          }
  133.  
  134.          // Merge given options with defaults
  135.          $options = array_merge(UploadBehavior::$default_options, $options);
  136.          $config[$field] = $options;
  137.  
  138.          // FIXME this looks odd
  139.          // Generate temporary directory if none provided
  140.          if(empty($options['dir'])) {
  141.             $options['dir'] = 'img' . DS . 'uploads' . DS . $model->name;
  142.          }
  143.  
  144.          // Check if directory exists and create it recursively if required
  145.          if(!is_dir($options['dir'])) {
  146.             if($options['create_directory'] && !$folder->create($options['dir'])) {
  147.                unset($config[$field]);
  148.                unset($model->data[$model->name][$field]);
  149.                continue;
  150.             }
  151.          }
  152.  
  153.          // Check if directory is writable
  154.          if(!is_writable($options['dir'])) {
  155.             unset($config[$field]);
  156.             unset($model->data[$model->name][$field]);
  157.             continue;
  158.          }
  159.  
  160.          // Check that the given directory does not have a DS on the end
  161.          if($options['dir'][strlen($options['dir'])-1] == DS) {
  162.             $options['dir'] = substr($options['dir'],0,strlen($options['dir'])-2);
  163.          }
  164.       }
  165.       $this->settings[$model->name] = $config;
  166.    }
  167.  
  168.    function beforeSave(&$model)
  169.    {
  170.       // return if no fields are configured for file upload
  171.       if(count($this->settings[$model->name]) == 0) return;
  172.  
  173.       foreach($this->settings[$model->name] as $field=>$options)
  174.       {
  175.          if (isset($model->data[$model->name][$field]))
  176.          {
  177.             // Check for upload
  178.             if(isset($model->data) && !is_array($model->data[$model->name][$field]))
  179.             {
  180.                unset($model->data[$model->name][$field]);
  181.                continue;
  182.             }
  183.  
  184.             // Check for error
  185.             if($model->data[$model->name][$field]['error'] > 0)
  186.             {
  187.                unset($model->data[$model->name][$field]);
  188.                continue;
  189.             }
  190.  
  191.             // Fix the file name
  192.             $model->data[$model->name][$field]['name'] = $this->__getFilteredFileName($model->data[$model->name][$field]['name']);
  193.  
  194.             // Check mime type
  195.             if(count($options['allowed_mime']) > 0 && !in_array($model->data[$model->name][$field]['type'], $options['allowed_mime']))
  196.             {
  197.                unset($model->data[$model->name][$field]);
  198.                continue;
  199.             }
  200.  
  201.             // Check if file extension is allowed
  202.             if(count($options['allowed_ext']) > 0)
  203.             {
  204.                $extension = false;
  205.                foreach($options['allowed_ext'] as $allowed_ext)
  206.                {
  207.                   if(substr(strtolower($model->data[$model->name][$field]['name']),-strlen($allowed_ext)) == $allowed_ext)
  208.                   {
  209.                      $extension = $allowed_ext;
  210.                      break;
  211.                   }
  212.                }
  213.  
  214.                if(!$extension)
  215.                {
  216.                   unset($model->data[$model->name][$field]);
  217.                   continue;
  218.                }
  219.             }
  220.  
  221.             $model->data[$model->name][$field]['name'] = $this->__getTargetFileName($options, $model, $field, $extension);
  222.             $saveAs = $options['dir'] . DS . $model->data[$model->name][$field]['name'];
  223.  
  224.             // Attempt to move uploaded file
  225.             if(!move_uploaded_file($model->data[$model->name][$field]['tmp_name'], $saveAs))
  226.             {
  227.                // if moving fails clear the variables and move on to the next field
  228.                unset($model->data[$model->name][$field]);
  229.                unset($saveAs);
  230.                continue;
  231.             }
  232.  
  233.             // Create thumbnail of uploaded image
  234.             $this->__createThumbs($options, $model, $field, $saveAs);
  235.      
  236.             // Attempt to set correct file permissions
  237.             if(!chmod($saveAs, 0755))
  238.             {
  239.                // if chmod fails clear the variable and move on to the next field
  240.                unset($saveAs);
  241.                continue;
  242.             }
  243.  
  244.             // Update model data
  245.             // FIXME dir, mimetype, filesize, dir are the same for multiple upload field
  246.             $model->data[$model->name]['dir'] = $options['dir'];
  247.             $model->data[$model->name]['mimetype'] = $model->data[$model->name][$field]['type'];
  248.             $model->data[$model->name]['filesize'] = $model->data[$model->name][$field]['size'];
  249.             $model->data[$model->name][$field] = $model->data[$model->name][$field]['name'];
  250.  
  251.             if($options['delete_obsolete_files'] && $model->id)
  252.             {
  253.                // retrieve the previous file associated with the current field
  254.                $oldFile = $this->__getFieldValueFromDB($model, $field);
  255.                if(empty($oldFile)) continue;
  256.                
  257.                if($oldFile != $model->data[$model->name][$field]['name'])
  258.                {
  259.                   // register the file for garbage collection after successful saving of the model
  260.                   $key = $model->name.'#'.$model->id;
  261.                   if(!isset($this->__obsoleteFiles[$key])) $this->__obsoleteFiles[$key] = array();
  262.                   $this->__obsoleteFiles[$key][$field] = $this->__getFieldValueFromDB($model, $field);
  263.                }
  264.             }
  265.          }
  266.       }
  267.    }
  268.  
  269.    function onError(&$model, $error)
  270.    {
  271.       // if an error occured during save operations precautionary unregister files marked as obsolete related to the current model
  272.       if($model->id)
  273.       {
  274.          $key = $model->name.'#'.$model->id;
  275.          unset($this->__obsoleteFiles[$key]);
  276.       }
  277.    }
  278.  
  279.    function afterSave(&$model, $created)
  280.    {
  281.       // check if any upload fields are defined
  282.       if(count($this->settings[$model->name]) == 0) return;
  283.  
  284.       if(!$created) $this->__deleteObsoleteFiles($model)
  285.    }
  286.    
  287.    function afterDelete(&$model)
  288.    {
  289.       // return if no fields are configured for file upload
  290.       if(count($this->settings[$model->name]) == 0) return;
  291.  
  292.       $this->__deleteObsoleteFiles($model);
  293.    }
  294.    
  295.    function beforeDelete(&$model)
  296.    {
  297.       // return if no fields are configured for file upload
  298.       if(count($this->settings[$model->name]) == 0) return;
  299.      
  300.       $fieldNames = array_keys($this->settings[$model->name]);
  301.       $fieldValues = $this->__getFieldValuesFromDB($model, $fieldNames);
  302.  
  303.       foreach($this->settings[$model->name] as $fieldName=>$options)
  304.       {
  305.          if($options['delete_obsolete_files'])
  306.          {
  307.             $dir = $options['dir'];
  308.  
  309.             $fileName = $fieldValues[$fieldName];
  310.            
  311.             // register the file for garbage collection after successful saving of the model
  312.             $key = $model->name.'#'.$model->id;
  313.             if(!isset($this->__obsoleteFiles[$key])) $this->__obsoleteFiles[$key] = array();
  314.             $this->__obsoleteFiles[$key][$fieldName] = $fileName;
  315.          }
  316.       }
  317.       return true;
  318.    }
  319.  
  320.    /**
  321.     * creates a thumbnail image
  322.     */
  323.    function __createThumb($name, $filename, $new_w, $new_h)
  324.    {
  325.       $path_info = pathinfo($name);
  326.       $ext = $path_info['extension'];
  327.  
  328.       if (preg_match('/jpg|jpeg/i', $ext))
  329.       {
  330.          $src_img = imagecreatefromjpeg($name);
  331.       }
  332.  
  333.       if (preg_match('/png/i', $ext))
  334.       {
  335.          $src_img = imagecreatefrompng($name);
  336.       }
  337.  
  338.       $old_x = imagesx($src_img);
  339.       $old_y = imagesy($src_img);
  340.  
  341.       $newSizes = $this->__proportionalResize($old_x, $old_y, $new_w, $new_h);
  342.  
  343.       $thumb_w = $newSizes[0];
  344.       $thumb_h = $newSizes[1];
  345.        
  346.       $dst_img = imagecreatetruecolor($thumb_w, $thumb_h);
  347.       imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $thumb_w, $thumb_h, $old_x, $old_y);
  348.  
  349.       if (preg_match("/png/i", $ext))
  350.       {
  351.          imagepng($dst_img, $filename, 0); // no compression
  352.       } else {
  353.          imagejpeg($dst_img, $filename, 100); // 100% quality
  354.       }
  355.  
  356.       imagedestroy($dst_img);
  357.       imagedestroy($src_img);
  358.    }
  359.  
  360.    function __createThumbs(&$options, &$model, $fieldName, $saveAs)
  361.    {
  362.       // This is hard-coded to only support JPEG + PNG at this time
  363.       // Code unable to handle other formats
  364.       if (count($options['allowed_ext']) > 0 && in_array($model->data[$model->name][$fieldName]['type'], array('image/jpeg', 'image/pjpeg', 'image/png')))
  365.       {
  366.          foreach ($options['thumbsizes'] as $key => $value)
  367.          {
  368.             $this->__createThumb($saveAs, $options['dir'] . DS . 'thumb.' . $key . '.' . $model->data[$model->name][$fieldName]['name'], $value['width'], $value['height']);
  369.          }
  370.       }
  371.    }
  372.  
  373.    function __deleteObsoleteFiles(&$model)
  374.    {
  375.       // check if obsolete files for the current model exist
  376.       $key = $model->name.'#'.$model->id;
  377.       if(!isset($this->__obsoleteFiles[$key])) return;
  378.  
  379.       // get a handle to the obsolete files
  380.       $obsoleteFilesByFieldName = $this->__obsoleteFiles[$key];
  381.       unset($this->__obsoleteFiles[$key]);
  382.  
  383.       foreach($this->settings[$model->name] as $fieldName=>$options)
  384.       {
  385.          if(!isset($obsoleteFilesByFieldName[$fieldName])) continue;
  386.  
  387.          $obsoleteFileForField = $obsoleteFilesByFieldName[$fieldName];
  388.          $dir = $options['dir'];
  389.          unlink($dir.DS.$obsoleteFileForField);
  390.          foreach ($options['thumbsizes'] as $key => $value)
  391.          {
  392.             unlink($dir.DS.'thumb.' . $key . '.' . $obsoleteFileForField);
  393.          }
  394.       }
  395.    }
  396.    
  397.    function __getFilteredFileName($filename)
  398.    {
  399.       $filtered_filename  = '';
  400.  
  401.       $patterns = array(
  402.       '/\s/', // Whitespace
  403.       '/\&/', // Ampersand
  404.       '/\+/'  // Plus
  405.       );
  406.       $replacements = array(
  407.       '_',    // Whitespace
  408.       'and'// Ampersand
  409.       'plus'  // Plus
  410.       );
  411.        
  412.       $filename = preg_replace($patterns, $replacements, $filename);
  413.  
  414.       for ($i=0;$i<strlen($filename);$i++)
  415.       {
  416.          $current_char = substr($filename,$i,1);
  417.          if (ctype_alnum($current_char) == TRUE || $current_char == "_" || $current_char == ".")
  418.          {
  419.             $filtered_filename .= $current_char;
  420.          }
  421.       }
  422.  
  423.       return $filtered_filename;
  424.    }
  425.  
  426.    function __getFieldValueFromDB(&$model, $fieldName)
  427.    {
  428.       $rs = $model->find(array($model->escapeField() => $model->id), $fieldName);
  429.       return $rs[$model->name][$fieldName];
  430.    }
  431.    
  432.    function __getFieldValuesFromDB(&$model, $fieldNames)
  433.    {
  434.       $rs = $model->find(array($model->escapeField() => $model->id), $fieldNames);
  435.       return $rs[$model->name];
  436.    }
  437.    
  438.    function __getTargetFileName(&$options, &$model, $field, $extension)
  439.    {
  440.       // Calculate a new name if a naming mode other than 'filename' has been selected
  441.       switch($options['naming'])
  442.       {
  443.          case 'random'$fileName = uniqid() . $extension; break;
  444.          default:        $fileName = substr($model->data[$model->name][$field]['name'], 0, -strlen($extension)) . $extension;
  445.       }
  446.  
  447.       // Calculate final save path
  448.       $saveAs = $options['dir'] . DS . $fileName;
  449.  
  450.       // Check if file with the desired name exists already
  451.       if(file_exists($saveAs))
  452.       {
  453.          // if overwrite is enabled and removing the existing file succeeded, then return the file name directly
  454.          if($options['overwrite_existing'] && unlink($saveAs)) return $fileName;
  455.          
  456.          // find a unique filename by appending a counter to the base name
  457.          if($options['naming_if_exists']=='increment')
  458.          {
  459.             $basename = substr($fileName, 0, -strlen($extension));
  460.             $inc=1;
  461.             do
  462.             {
  463.                $fileName = $basename . '_' . $inc . $extension;
  464.                $saveAs = $options['dir'] . DS . $candidate;
  465.                $inc++;
  466.             }
  467.             while(file_exists($saveAs));
  468.  
  469.             return $fileName;
  470.          }
  471.              
  472.          // find a unique file name by using a UID as the base name
  473.          else
  474.          {
  475.             do
  476.             {
  477.                $fileName = uniqid('') . $extension;
  478.                $saveAs = $options['dir'] . DS . $fileName;
  479.             }
  480.             while(file_exists($saveAs));
  481.          }
  482.       }
  483.       return $fileName;
  484.    }
  485.    
  486.    function __proportionalResize($old_width, $old_height, $new_width = false, $new_height = false)
  487.    {
  488.       if (($new_width === false) && ($new_height === false)) return false;
  489.        
  490.       $old_aspect_ratio = $old_width / $old_height;
  491.        
  492.       if ($new_width === false) {
  493.          $new_width = $new_height * $old_aspect_ratio;
  494.       } elseif ($new_height === false) {
  495.          $new_height = $new_width / $old_aspect_ratio;
  496.       }
  497.       $new_aspect_ratio = $new_width / $new_height;
  498.       if ($new_aspect_ratio == $old_aspect_ratio) {
  499.          // great, done
  500.       } elseif ($new_aspect_ratio < $old_aspect_ratio) {
  501.          // limited by width
  502.          $new_height = $new_width / $old_aspect_ratio;
  503.       } elseif ($new_aspect_ratio > $old_aspect_ratio) {
  504.          // limited by height
  505.          $new_width = $new_height * $old_aspect_ratio;
  506.       }
  507.       return array(round($new_width), round($new_height));
  508.    }
  509. }
  510. ?>
Parsed in 0.715 seconds, using GeSHi 1.0.7.14

Modify this Paste