root/fORMFile.php

Revision 270, 33.8 kB (checked in by wbond, 2 years ago)

Added fCore::call() and switched all applicable callbacks to class::method style

LineHide Line Numbers
1 <?php
2 /**
3  * Provides file manipulation functionality for {@link fActiveRecord} classes
4  *
5  * @copyright  Copyright (c) 2008 William Bond
6  * @author     William Bond [wb] <will@flourishlib.com>
7  * @license    http://flourishlib.com/license
8  *
9  * @package    Flourish
10  * @link       http://flourishlib.com/fORMFile
11  *
12  * @version    1.0.0b
13  * @changes    1.0.0b  The initial implementation [wb, 2008-05-28]
14  */
15 class fORMFile
16 {
17     const addFImageMethodCall        = 'fORMFile::addFImageMethodCall';
18     const addFUploadMethodCall       = 'fORMFile::addFUploadMethodCall';
19     const begin                      = 'fORMFile::begin';
20     const commit                     = 'fORMFile::commit';
21     const configureColumnInheritance = 'fORMFile::configureColumnInheritance';
22     const configureFileUploadColumn  = 'fORMFile::configureFileUploadColumn';
23     const configureImageUploadColumn = 'fORMFile::configureImageUploadColumn';
24     const delete                     = 'fORMFile::delete';
25     const deleteOld                  = 'fORMFile::deleteOld';
26     const encode                     = 'fORMFile::encode';
27     const inspect                    = 'fORMFile::inspect';
28     const moveFromTemp               = 'fORMFile::moveFromTemp';
29     const objectify                  = 'fORMFile::objectify';
30     const populate                   = 'fORMFile::populate';
31     const prepare                    = 'fORMFile::prepare';
32     const processImage               = 'fORMFile::processImage';
33     const reflect                    = 'fORMFile::reflect';
34     const rollback                   = 'fORMFile::rollback';
35     const set                        = 'fORMFile::set';
36     const upload                     = 'fORMFile::upload';
37     const validate                   = 'fORMFile::validate';
38    
39    
40     /**
41     * The temporary directory to use for various tasks
42     *
43     * @internal
44      *
45     * @var string
46     */
47     const TEMP_DIRECTORY = '__flourish_temp/';
48    
49    
50     /**
51     * Defines how columns can inherit uploaded files
52     *
53     * @var array
54     */
55     static private $column_inheritence = array();
56    
57     /**
58     * Methods to be called on fUpload before the file is uploaded
59     *
60     * @var array
61     */
62     static private $fupload_method_calls = array();
63    
64     /**
65     * Columns that can be filled by file uploads
66     *
67     * @var array
68     */
69     static private $file_upload_columns = array();
70    
71     /**
72     * Methods to be called on the fImage instance
73     *
74     * @var array
75     */
76     static private $fimage_method_calls = array();
77    
78     /**
79     * Columns that can be filled by image uploads
80     *
81     * @var array
82     */
83     static private $image_upload_columns = array();
84    
85     /**
86     * Keeps track of the nesting level of the filesystem transaction so we know when to start, commit, rollback, etc
87     *
88     * @var integer
89     */
90     static private $transaction_level = 0;
91    
92    
93     /**
94     * Adds an {@link fImage} method call to the image manipulation for a column if an image file is uploaded
95     *
96     * @param  mixed  $class       The class name or instance of the class
97     * @param  string $column      The column to call the method for
98     * @param  string $method      The fImage method to call
99     * @param  array  $parameters  The parameters to pass to the method
100     * @return void
101     */
102     static public function addFImageMethodCall($class, $column, $method, $parameters=array())
103     {
104         $class = fORM::getClass($class);
105        
106         if (!array_key_exists($column, self::$image_upload_columns[$class])) {
107             fCore::toss(
108                 'fProgrammerException',
109                 fGrammar::compose(
110                     'The column specified, %s, has not been configured as an image upload column.',
111                     fCore::dump($column)
112                 )
113             );
114         }
115        
116         if (empty(self::$fimage_method_calls[$class])) {
117             self::$fimage_method_calls[$class] = array();
118         }
119         if (empty(self::$fimage_method_calls[$class][$column])) {
120             self::$fimage_method_calls[$class][$column] = array();
121         }
122        
123         self::$fimage_method_calls[$class][$column][] = array(
124             'method'     => $method,
125             'parameters' => $parameters
126         );
127     }
128    
129    
130     /**
131     * Adds an {@link fUpload} method call to the {@link fUpload} initialization for a column
132     *
133     * @param  mixed  $class       The class name or instance of the class
134     * @param  string $column      The column to call the method for
135     * @param  string $method      The fUpload method to call
136     * @param  array  $parameters  The parameters to pass to the method
137     * @return void
138     */
139     static public function addFUploadMethodCall($class, $column, $method, $parameters=array())
140     {
141         $class = fORM::getClass($class);
142        
143         if (empty(self::$file_upload_columns[$class][$column])) {
144             fCore::toss(
145                 'fProgrammerException',
146                 fGrammar::compose(
147                     'The column specified, %s, has not been configured as a file or image upload column.',
148                     fCore::dump($column)
149                 )
150             );
151         }
152        
153         if (empty(self::$fupload_method_calls[$class])) {
154             self::$fupload_method_calls[$class] = array();
155         }
156         if (empty(self::$fupload_method_calls[$class][$column])) {
157             self::$fupload_method_calls[$class][$column] = array();
158         }
159        
160         self::$fupload_method_calls[$class][$column][] = array(
161             'callback'   => array('fUpload', $method),
162             'parameters' => $parameters
163         );
164     }
165    
166    
167     /**
168     * Begins a transaction, or increases the level
169     *
170     * @internal
171      *
172     * @return void
173     */
174     static public function begin()
175     {
176         // If the transaction was started by something else, don't even track it
177         if (self::$transaction_level == 0 && fFilesystem::isInsideTransaction()) {
178             return;
179         }
180        
181         self::$transaction_level++;
182        
183         if (!fFilesystem::isInsideTransaction()) {
184             fFilesystem::begin();
185         }
186     }
187    
188    
189     /**
190     * Commits a transaction, or decreases the level
191     *
192     * @internal
193      *
194     * @return void
195     */
196     static public function commit()
197     {
198         // If the transaction was started by something else, don't even track it
199         if (self::$transaction_level == 0) {
200             return;
201         }
202        
203         self::$transaction_level--;
204        
205         if (!self::$transaction_level) {
206             fFilesystem::commit();
207         }
208     }
209    
210    
211     /**
212     * Sets a column to be a file upload column
213     *
214     * @param  mixed             $class      The class name or instance of the class
215     * @param  string            $column     The column to set as a file upload column
216     * @param  fDirectory|string $directory  The directory to upload to
217     * @return void
218     */
219     static public function configureFileUploadColumn($class, $column, $directory)
220     {
221         $class     = fORM::getClass($class);
222         $table     = fORM::tablize($class);
223         $data_type = fORMSchema::getInstance()->getColumnInfo($table, $column, 'type');
224        
225         $valid_data_types = array('varchar', 'char', 'text');
226         if (!in_array($data_type, $valid_data_types)) {
227             fCore::toss(
228                 'fProgrammerException',
229                 fGrammar::compose(
230                     'The column specified, %1$s, is a %2$s column. Must be one of %3$s to be set as a file upload column.',
231                     fCore::dump($column),
232                     $data_type,
233                     join(', ', $valid_data_types)
234                 )
235             );
236         }
237        
238         if (!is_object($directory)) {
239             $directory = new fDirectory($directory);
240         }
241        
242         if (!$directory->isWritable()) {
243             fCore::toss(
244                 'fEnvironmentException',
245                 fGrammar::compose(
246                     'The file upload directory, %s, is not writable',
247                     $directory->getPath()
248                 )
249             );
250         }
251        
252         $camelized_column = fGrammar::camelize($column, TRUE);
253        
254         fORM::registerHookCallback(
255             $class,
256             'replace::inspect' . $camelized_column . '()',
257             self::inspect
258         );
259        
260         fORM::registerHookCallback(
261             $class,
262             'replace::upload' . $camelized_column . '()',
263             self::upload
264         );
265        
266         fORM::registerHookCallback(
267             $class,
268             'replace::set' . $camelized_column . '()',
269             self::set
270         );
271        
272         fORM::registerHookCallback(
273             $class,
274             'replace::encode' . $camelized_column . '()',
275             self::encode
276         );
277        
278         fORM::registerHookCallback(
279             $class,
280             'replace::prepare' . $camelized_column . '()',
281             self::prepare
282         );
283        
284         fORM::registerReflectCallback(
285             $class,
286             self::reflect
287         );
288        
289         fORM::registerObjectifyCallback(
290             $class,
291             $column,
292             self::objectify
293         );
294        
295         $only_once_hooks = array(
296             'post-begin::delete()'    => self::begin,
297             'pre-commit::delete()'    => self::delete,
298             'post-commit::delete()'   => self::commit,
299             'post-rollback::delete()' => self::rollback,
300             'post::populate()'        => self::populate,
301             'post-begin::store()'     => self::begin,
302             'post-validate::store()'  => self::moveFromTemp,
303             'pre-commit::store()'     => self::deleteOld,
304             'post-commit::store()'    => self::commit,
305             'post-rollback::store()'  => self::rollback,
306             'post::validate()'        => self::validate
307         );
308        
309         foreach ($only_once_hooks as $hook => $callback) {
310             if (!fORM::checkHookCallback($class, $hook, $callback)) {
311                 fORM::registerHookCallback($class, $hook, $callback);
312             }
313         }
314        
315         if (empty(self::$file_upload_columns[$class])) {
316             self::$file_upload_columns[$class] = array();
317         }
318        
319         self::$file_upload_columns[$class][$column] = $directory;
320     }
321    
322    
323     /**
324     * Takes one file or image upload columns and sets it to inherit any uploaded files from another column
325     *
326     * @param  mixed  $class                The class name or instance of the class
327     * @param  string $column               The column that will inherit the uploaded file
328     * @param  string $inherit_from_column  The column to inherit the uploaded file from
329     * @return void
330     */
331     static public function configureColumnInheritance($class, $column, $inherit_from_column)
332     {
333         $class = fORM::getClass($class);
334        
335         if (empty(self::$column_inheritence[$class])) {
336             self::$column_inheritence[$class] = array();
337         }
338        
339         if (empty(self::$column_inheritence[$class][$inherit_from_column])) {
340             self::$column_inheritence[$class][$inherit_from_column] = array();
341         }
342        
343         self::$column_inheritence[$class][$inherit_from_column][] = $column;
344     }
345    
346    
347     /**
348     * Sets a column to be a date created column
349     *
350     * @param  mixed             $class       The class name or instance of the class
351     * @param  string            $column      The column to set as a file upload column
352     * @param  fDirectory|string $directory   The directory to upload to
353     * @param  string            $image_type  The image type to save the image as. Valid: {null}, 'gif', 'jpg', 'png'
354     * @return void
355     */
356     static public function configureImageUploadColumn($class, $column, $directory, $image_type=NULL)
357     {
358         $valid_image_types = array(NULL, 'gif', 'jpg', 'png');
359         if (!in_array($image_type, $valid_image_types)) {
360             $valid_image_types[0] = '{null}';
361             fCore::toss(
362                 'fProgrammerException',
363                 fGrammar::compose(
364                     'The image type specified, %1$s, is not valid. Must be one of: %2$s.',
365                     fCore::dump($image_type),
366                     join(', ', $valid_image_types)
367                 )
368             );
369         }
370        
371         self::configureFileUploadColumn($class, $column, $directory);
372        
373         $class = fORM::getClass($class);
374        
375         if (empty(self::$image_upload_columns[$class])) {
376             self::$image_upload_columns[$class] = array();
377         }
378        
379         self::$image_upload_columns[$class][$column] = $image_type;
380        
381         self::addFUploadMethodCall($class, $column, 'setType', array('image'));
382     }
383    
384    
385     /**
386     * Deletes the files for this record
387     *
388     * @internal
389      *
390     * @param  fActiveRecord $object            The fActiveRecord instance
391     * @param  array         &$values           The current values
392     * @param  array         &$old_values       The old values
393     * @param  array         &$related_records  Any records related to this record
394     * @return void
395     */
396     static public function delete($object, &$values, &$old_values, &$related_records)
397     {
398         $class = get_class($object);
399        
400         foreach (self::$file_upload_columns[$class] as $column => $directory) {
401            
402             // Remove the current file for the column
403             if ($values[$column] instanceof fFile) {
404                 $values[$column]->delete();
405             }
406            
407             // Remove the old files for the column
408             if (isset($old_values[$column])) {
409                 foreach ($old_values[$column] as $file) {
410                     if ($file instanceof fFile) {
411                         $file->delete();
412                     }
413                 }
414             }
415            
416         }
417     }
418    
419    
420     /**
421     * Deletes old files for this record that have been replaced
422     *
423     * @internal
424      *
425     * @param  fActiveRecord $object            The fActiveRecord instance
426     * @param  array         &$values           The current values
427     * @param  array         &$old_values       The old values
428     * @param  array         &$related_records  Any records related to this record
429     * @return void
430     */
431     static public function deleteOld($object, &$values, &$old_values, &$related_records)
432     {
433         $class = get_class($object);
434        
435         foreach (self::$file_upload_columns[$class] as $column => $directory) {
436            
437             // Remove the old files for the column
438             if (isset($old_values[$column])) {
439                 foreach ($old_values[$column] as $file) {
440                     if ($file instanceof fFile) {
441                         $file->delete();
442                     }
443                 }
444             }
445            
446         }
447     }
448    
449    
450     /**
451     * Encodes a file for output into an HTML input tag
452     *
453     * @internal
454      *
455     * @param  fActiveRecord $object            The fActiveRecord instance
456     * @param  array         &$values           The current values
457     * @param  array         &$old_values       The old values
458     * @param  array         &$related_records  Any records related to this record
459     * @param  string        &$method_name      The method that was called
460     * @param  array         &$parameters       The parameters passed to the method
461     * @return void
462     */
463     static public function encode($object, &$values, &$old_values, &$related_records, &$method_name, &$parameters)
464     {
465         list ($action, $column) = fORM::parseMethod($method_name);
466        
467         $filename = ($values[$column] instanceof fFile) ? $values[$column]->getFilename() : NULL;
468         if ($filename && strpos($values[$column]->getPath(), self::TEMP_DIRECTORY . $filename) !== FALSE) {
469             $filename = self::TEMP_DIRECTORY . $filename;
470         }
471        
472         return fHTML::encode($filename);
473     }
474    
475    
476     /**
477     * Returns the metadata about a column including features added by this class
478     *
479     * @internal
480      *
481     * @param  fActiveRecord $object            The fActiveRecord instance
482     * @param  array         &$values           The current values
483     * @param  array         &$old_values       The old values
484     * @param  array         &$related_records  Any records related to this record
485     * @param  string        &$method_name      The method that was called
486     * @param  array         &$parameters       The parameters passed to the method
487     * @return mixed  The metadata array or element specified
488     */
489     static public function inspect($object, &$values, &$old_values, &$related_records, &$method_name, &$parameters)
490     {
491         list ($action, $column) = fORM::parseMethod($method_name);
492        
493         $class   = get_class($object);
494         $info    = fORMSchema::getInstance()->getColumnInfo(fORM::tablize($class), $column);
495         $element = (isset($parameters[0])) ? $parameters[0] : NULL;
496        
497         if (!empty(self::$image_upload_columns[$class][$column])) {
498             $info['feature'] = 'image';
499            
500         } elseif (!empty(self::$file_upload_columns[$class][$column])) {
501             $info['feature'] = 'file';
502         }
503        
504         $info['directory'] = self::$file_upload_columns[$class][$column]->getPath();
505        
506         if ($element) {
507             return (isset($info[$element])) ? $info[$element] : NULL;
508         }
509        
510         return $info;
511     }
512    
513    
514     /**
515     * Moves uploaded file from the temporary directory to the permanent directory
516     *
517     * @internal
518      *
519     * @param  fActiveRecord $object            The fActiveRecord instance
520     * @param  array         &$values           The current values
521     * @param  array         &$old_values       The old values
522     * @param  array         &$related_records  Any records related to this record
523     * @return void
524     */
525     static public function moveFromTemp($object, &$values, &$old_values, &$related_records)
526     {
527         foreach ($values as $column => $value) {
528             if (!$value instanceof fFile) {
529                 continue;
530             }
531            
532             // If the file is in a temp dir, move it out
533             if (stripos($value->getDirectory()->getPath(), self::TEMP_DIRECTORY) !== FALSE) {
534                 $new_filename = str_replace(self::TEMP_DIRECTORY, '', $value->getPath());
535                 $new_filename = fFilesystem::makeUniqueName($new_filename);
536                 $value->rename($new_filename, FALSE);
537             }
538         }
539     }
540    
541    
542     /**
543     * Turns a filename into an {@link fFile} or {@link fImage} object
544     *
545     * @internal
546      *
547     * @param  string $class   The class this value is for
548     * @param  string $column  The column the value is in
549     * @param  mixed  $value   The value
550     * @return mixed  The {@link fFile}, {@link fImage} or raw value
551     */
552     static public function objectify($class, $column, $value)
553     {
554         if (!fCore::stringlike($value)) {
555             return $value;
556         }
557        
558         $path = self::$file_upload_columns[$class][$column]->getPath() . $value;
559        
560         try {
561            
562             if (fImage::isImageCompatible($path)) {
563                 return new fImage($path);
564             }
565            
566             return new fFile($path);
567              
568         // If there was some error creating the file, just return the raw value
569         } catch (fExpectedException $e) {
570             return $value;
571         }
572     }
573    
574    
575     /**
576     * Performs the upload action for file uploads during {@link fActiveRecord::populate()}
577     *
578     * @internal
579      *
580     * @param  fActiveRecord $object            The fActiveRecord instance
581     * @param  array         &$values           The current values
582     * @param  array         &$old_values       The old values
583     * @param  array         &$related_records  Any records related to this record
584     * @return void
585     */
586     static public function populate($object, &$values, &$old_values, &$related_records)
587     {
588         $class = get_class($object);
589        
590         foreach (self::$file_upload_columns[$class] as $column => $directory) {
591             if (fUpload::check($column)) {
592                 $method = 'upload' . fGrammar::camelize($column, TRUE);
593                 $object->$method();
594             }
595         }
596     }
597    
598    
599     /**
600     * Prepares a file for output into HTML by returning the web server path to the file
601     *
602     * @internal
603      *
604     * @param  fActiveRecord $object            The fActiveRecord instance
605     * @param  array         &$values           The current values
606     * @param  array         &$old_values       The old values
607     * @param  array         &$related_records  Any records related to this record
608     * @param  string        &$method_name      The method that was called
609     * @param  array         &$parameters       The parameters passed to the method
610     * @return void
611     */
612     static public function prepare($object, &$values, &$old_values, &$related_records, &$method_name, &$parameters)
613     {
614         list ($action, $column) = fORM::parseMethod($method_name);
615        
616         if (sizeof($parameters) > 1) {
617             fCore::toss(
618                 'fProgrammerException',
619                 fGrammar::compose(
620                     'The column specified, %s, does not accept more than one parameter',
621                     fCore::dump($column)
622                 )
623             );
624         }
625        
626         $translate_to_web_path = (empty($parameters[0])) ? FALSE : TRUE;
627         $value                 = $values[$column];
628        
629         if ($value instanceof fFile) {
630             $path = ($translate_to_web_path) ? $value->getPath(TRUE) : $value->getFilename();
631         } else {
632             $path = NULL;
633         }
634        
635         return fHTML::prepare($path);
636     }
637    
638    
639     /**
640     * Performs image manipulation on an uploaded image
641     *
642     * @internal
643      *
644     * @param  string $class   The name of the class we are manipulating the image for
645     * @param  string $column  The column the image is assigned to
646     * @param  fFile  $image   The image object to manipulate
647     * @return void
648     */
649     static public function processImage($class, $column, $image)
650     {
651         // If we don't have an image or we haven't set it up to manipulate images, just exit
652         if (!$image instanceof fImage || empty(self::$fimage_method_calls[$class][$column])) {
653             return;
654         }
655        
656         // Manipulate the image
657         if (!empty(self::$fimage_method_calls[$class][$column])) {
658             foreach (self::$fimage_method_calls[$class][$column] as $method_call) {
659                 $callback   = array($image, $method_call['method']);
660                 $parameters = $method_call['parameters'];
661                 if (!is_callable($callback)) {
662                     fCore::toss(
663                         'fProgrammerException',
664                         fGrammar::compose(
665                             'The fImage method specified, %s, is not a valid method.',
666                             fCore::dump($method_call['method']) . '()'
667                         )
668                     );
669                 }
670                 fCore::call($callback, $parameters);
671             }
672         }
673        
674         // Save the changes
675         $callback   = array($image, 'saveChanges');
676         $parameters = array(self::$image_upload_columns[$class][$column]);
677         fCore::call($callback, $parameters);
678     }
679    
680    
681     /**
682     * Adjusts the {@link fActiveRecord::reflect()} signatures of columns that have been configured in this class
683     *
684     * @internal
685      *
686     * @param  string  $class                 The class to reflect
687     * @param  array   &$signatures           The associative array of {method name} => {signature}
688     * @param  boolean $include_doc_comments  If doc comments should be included with the signature
689     * @return void
690     */
691     static public function reflect($class, &$signatures, $include_doc_comments)
692     {
693         $image_columns = (isset(self::$image_upload_columns[$class])) ? array_keys(self::$image_upload_columns[$class]) : array();
694         $file_columns  = (isset(self::$file_upload_columns[$class]))  ? array_keys(self::$file_upload_columns[$class])  : array();
695        
696         foreach(self::$link_columns[$class] as $column => $enabled) {
697             $signature = '';
698             if ($include_doc_comments) {
699                 $signature .= "/**\n";
700                 $signature .= " * Prepares the value of " . $column . " for output into HTML\n";
701                 $signature .= " * \n";
702                 $signature .= " * This method will ensure all links that start with a domain name are preceeded by http://\n";
703                 $signature .= " * \n";
704                 $signature .= " * @return string  The HTML-ready value\n";
705                 $signature .= " */\n";
706             }
707             $prepare_method = 'prepare' . fGrammar::camelize($column, TRUE);
708             $signature .= 'public function prepare' . $prepare_method . '()';
709            
710             $signatures[$prepare_method] = $signature;
711         }
712        
713         foreach($file_columns as $column) {
714             $camelized_column = fGrammar::camelize($column, TRUE);
715            
716             $noun = 'file';
717             if (in_array($column, $image_columns)) {
718                 $noun = 'image';
719             }
720            
721             $signature = '';
722             if ($include_doc_comments) {
723                 $signature .= "/**\n";
724                 $signature .= " * Encodes the filename of " . $column . " for output into an HTML form\n";
725                 $signature .= " * \n";
726                 $signature .= " * Only the filename will be returned, any directory will be stripped.\n";
727                 $signature .= " * \n";
728                 $signature .= " * @return string  The HTML form-ready value\n";
729                 $signature .= " */\n";
730             }
731             $encode_method = 'encode' . $camelized_column;
732             $signature .= 'public function ' . $encode_method . '()';
733            
734             $signatures[$encode_method] = $signature;
735            
736             $signature = '';
737             if ($include_doc_comments) {
738                 $signature .= "/**\n";
739                 $signature .= " * Prepares the filename of " . $column . " for output into HTML\n";
740                 $signature .= " * \n";
741                 $signature .= " * By default only the filename will be returned and any directory will be stripped.\n";
742                 $signature .= " * The \$include_web_path parameter changes this behaviour.\n";
743                 $signature .= " * \n";
744                 $signature .= " * @param  boolean \$include_web_path  If the full web path to the " . $noun . " should be included\n";
745                 $signature .= " * @return string  The HTML-ready value\n";
746                 $signature .= " */\n";
747             }
748             $prepare_method = 'prepare' . $camelized_column;
749             $signature .= 'public function ' . $prepare_method . '($include_web_path=FALSE)';
750            
751             $signatures[$prepare_method] = $signature;
752            
753             $signature = '';
754             if ($include_doc_comments) {
755                 $signature .= "/**\n";
756                 $signature .= " * Takes a file uploaded through an HTML form for " . $column . " and moves it into the specified directory\n";
757                 $signature .= " * \n";
758                 $signature .= " * Any columns that were designated as inheriting from this column will get a copy\n";
759                 $signature .= " * of the uploaded file.\n";
760                 $signature .= " * \n";
761                 if ($noun == 'image') {
762                     $signature .= " * Any fImage calls that were added to the column will be processed on the uploaded image.\n";
763                     $signature .= " * \n";
764                 }
765                 $signature .= " * @return void\n";
766                 $signature .= " */\n";
767             }
768             $upload_method = 'upload' . $camelized_column;
769             $signature .= 'public function ' . $upload_method . '()';
770            
771             $signatures[$upload_method] = $signature;
772            
773             $signature = '';
774             if ($include_doc_comments) {
775                 $signature .= "/**\n";
776                 $signature .= " * Takes a file that exists on the filesystem and copies it into the specified directory for " . $column . "\n";
777                 $signature .= " * \n";
778                 if ($noun == 'image') {
779                     $signature .= " * Any fImage calls that were added to the column will be processed on the copied image.\n";
780                     $signature .= " * \n";
781                 }
782                 $signature .= " * @return void\n";
783                 $signature .= " */\n";
784             }
785             $set_method = 'set' . $camelized_column;
786             $signature .= 'public function ' . $set_method . '()';
787            
788             $signatures[$set_method] = $signature;
789            
790             $signature = '';
791             if ($include_doc_comments) {
792                 $signature .= "/**\n";
793                 $signature .= " * Returns metadata about " . $column . "\n";
794                 $signature .= " * \n";
795                 $signature .= " * @param  string \$element  The element to return. Must be one of: 'type', 'not_null', 'default', 'valid_values', 'max_length', 'feature', 'directory'.\n";
796                 $signature .= " * @return mixed  The metadata array or a single element\n";
797                 $signature .= " */\n";
798             }
799             $inspect_method = 'inspect' . $camelized_column;
800             $signature .= 'public function ' . $inspect_method . '($element=NULL)';
801            
802             $signatures[$inspect_method] = $signature;
803         }
804     }
805    
806    
807     /**
808     * Rolls back a transaction, or decreases the level
809     *
810     * @internal
811      *
812     * @return void
813     */
814     static public function rollback()
815     {
816         // If the transaction was started by something else, don't even track it
817         if (self::$transaction_level == 0) {
818             return;
819         }
820        
821         self::$transaction_level--;
822        
823         if (!self::$transaction_level) {
824             fFilesystem::rollback();
825         }
826     }
827    
828    
829     /**
830     * Copies a file from the filesystem to the file upload directory and sets it as the file for the specified column
831     *
832     * This method will perform the fImage calls defined for the column.
833     *
834     * @internal
835      *
836     * @param  fActiveRecord $object            The fActiveRecord instance
837     * @param  array         &$values           The current values
838     * @param  array         &$old_values       The old values
839     * @param  array         &$related_records  Any records related to this record
840     * @param  string        &$method_name      The method that was called
841     * @param  array         &$parameters       The parameters passed to the method
842     * @return void
843     */
844     static public function set($object, &$values, &$old_values, &$related_records, &$method_name, &$parameters)
845     {
846         $class = get_class($object);
847        
848         list ($action, $column) = fORM::parseMethod($method_name);
849        
850         $doc_root = realpath($_SERVER['DOCUMENT_ROOT']);
851        
852         if (!array_key_exists(0, $parameters)) {
853             fCore::toss(
854                 'fProgrammerException',
855                 fGrammar::compose(
856                     'The method %s requires exactly one parameter',
857                     fCore::dump($method_name) . '()'
858                 )
859             );
860         }
861        
862         $file_path    = $parameters[0];
863         $invalid_file = !fCore::stringlike($file_path);
864        
865         if (!$file_path || (!file_exists($file_path) && !file_exists($doc_root . $file_path))) {
866             fCore::toss(
867                 'fEnvironmentException',
868                 fGrammar::compose(
869                     'The file specified, %s, does not exist. This may indicate a missing enctype="multipart/form-data" attribute in form tag.',
870                     fCore::dump($file_path)
871                 )
872             );
873         }
874        
875         if (!file_exists($file_path) && file_exists($doc_root . $file_path)) {
876             $file_path = $doc_root . $file_path;
877         }
878        
879         $file     = new fFile($file_path);
880         $new_file = $file->duplicate(self::$file_upload_columns[$class][$column]);
881        
882         fActiveRecord::assign($values, $old_values, $column, $new_file);
883        
884         self::processImage($class, $column, $new_file);
885     }
886    
887    
888     /**
889     * Sets up the {@link fUpload} class for a specific column
890     *
891     * @param  string $class   The class to set up for
892     * @param  string $column  The column to set up for
893     * @return void
894     */
895     static private function setUpFUpload($class, $column)
896     {
897         fUpload::reset();
898        
899         // Set up the fUpload class
900         if (!empty(self::$fupload_method_calls[$class][$column])) {
901             foreach (self::$fupload_method_calls[$class][$column] as $method_call) {
902                 if (!is_callable($method_call['callback'])) {
903                     fCore::toss(
904                         'fProgrammerException',
905                         fGrammar::compose(
906                             'The fUpload method specified, %s, is not a valid method.',
907                             fCore::dump($method_call['method']) . '()'
908                         )
909                     );
910                 }
911                 fCore::call($method_call['callback'], $method_call['parameters']);
912             }
913         }
914     }
915    
916    
917     /**
918     * Uploads a file
919     *
920     * @internal
921      *
922     * @param  fActiveRecord $object            The fActiveRecord instance
923     * @param  array         &$values           The current values
924     * @param  array         &$old_values       The old values
925     * @param  array         &$related_records  Any records related to this record
926     * @param  string        &$method_name      The method that was called
927     * @param  array         &$parameters       The parameters passed to the method
928     * @return void
929     */
930     static public function upload($object, &$values, &$old_values, &$related_records, &$method_name, &$parameters)
931     {
932         $class = get_class($object);
933        
934         list ($action, $column) = fORM::parseMethod($method_name);
935        
936         self::setUpFUpload($class, $column);
937        
938         $upload_dir = self::$file_upload_columns[$class][$column];
939        
940         // Let's clean out the upload temp dir
941         try {
942             $temp_dir = new fDirectory($upload_dir->getPath() . self::TEMP_DIRECTORY);
943         } catch (fValidationException $e) {
944             $temp_dir = fDirectory::create($upload_dir->getPath() . self::TEMP_DIRECTORY);
945         }
946        
947         $temp_files = $temp_dir->scan();
948         foreach ($temp_files as $temp_file) {
949             if (filemtime($temp_file->getPath()) < strtotime('-6 hours')) {
950                 unlink($temp_file->getPath());
951             }
952         }
953        
954         // Try to upload the file putting it in the temp dir incase there is a validation problem with the record
955         try {
956             $file = fUpload::upload($temp_dir, $column);
957             fUpload::reset();
958        
959         // If there was an eror, check to see if we have an existing file
960         } catch (fExpectedException $e) {
961             fUpload::reset();
962            
963             // If there is an existing file and none was uploaded, substitute the existing file
964             $existing_file = fRequest::get('__flourish_existing_' . $column);
965             $delete_file   = fRequest::get('__flourish_delete_' . $column, 'boolean');
966             $no_upload     = $e->getMessage() == fGrammar::compose('Please upload a file');
967            
968             if ($existing_file && $delete_file && $no_upload) {
969                 $file = NULL;
970                
971             } elseif ($existing_file) {
972                
973                 $file = new fFile($upload_dir->getPath() . $existing_file);
974                
975                 $current_file = $values[$column];
976                 if (!$current_file || ($current_file && $file->getPath() != $current_file->getPath())) {
977                     fActiveRecord::assign($values, $old_values, $column, $file);
978                 }
979                 return;
980                
981             } else {
982                 return;
983             }
984         }
985        
986         // Assign the file
987         fActiveRecord::assign($values, $old_values, $column, $file);
988        
989         // Perform the file upload inheritance
990         if (!empty(self::$column_inheritence[$class][$column])) {
991             foreach (self::$column_inheritence[$class][$column] as $other_column) {
992                
993                 if ($file) {
994                     // Let's clean out the upload temp dir
995                     try {
996                         $other_upload_dir = self::$file_upload_columns[$class][$other_column];
997                         $other_temp_dir   = new fDirectory($other_upload_dir->getPath() . self::TEMP_DIRECTORY);
998                     } catch (fValidationException $e) {
999                         $other_temp_dir   = fDirectory::create($other_upload_dir->getPath() . self::TEMP_DIRECTORY);
1000                     }
1001                    
1002                     $temp_files = $other_temp_dir->scan();
1003                     foreach ($temp_files as $temp_file) {
1004                         if (filemtime($temp_file->getPath()) < strtotime('-6 hours')) {
1005                             unlink($temp_file->getPath());
1006                         }
1007                     }
1008                    
1009                     $other_file = $file->duplicate($other_temp_dir, FALSE);
1010                 } else {
1011                     $other_file = $file;
1012                 }
1013                
1014                 fActiveRecord::assign($values, $old_values, $other_column, $other_file);
1015                
1016                 if ($other_file) {
1017                     self::processImage($class, $other_column, $other_file);
1018                 }
1019             }
1020         }
1021        
1022         // Process the file
1023         if ($file) {
1024             self::processImage($class, $column, $file);
1025         }
1026     }
1027    
1028    
1029     /**
1030     * Moves uploaded file from the temporary directory to the permanent directory
1031     *
1032     * @internal
1033      *
1034     * @param  fActiveRecord $object                The fActiveRecord instance
1035     * @param  array         &$values               The current values
1036     * @param  array         &$old_values           The old values
1037     * @param  array         &$related_records      Any records related to this record
1038     * @param  array         &$validation_messages  The existing validation messages
1039     * @return void
1040     */
1041     static public function validate($object, &$values, &$old_values, &$related_records, &$validation_messages)
1042     {
1043         $class = get_class($object);
1044        
1045         foreach (self::$file_upload_columns[$class] as $column => $directory) {
1046             $column_name = fORM::getColumnName($class, $column);
1047            
1048             $search_message  = fGrammar::compose('%s: Please enter a value', $column_name);
1049             $replace_message = fGrammar::compose('%s: Please upload a file', $column_name);;
1050             $validation_messages = str_replace($search_message, $replace_message, $validation_messages);
1051            
1052             self::setUpFUpload($class, $column);
1053            
1054             // Grab the error that occured
1055             try {
1056                 if (fUpload::check($column)) {
1057                     fUpload::validate($column);
1058                 }
1059             } catch (fValidationException $e) {
1060                 if ($e->getMessage() != fGrammar::compose('Please upload a file')) {
1061                     $validation_messages[] = $column_name . ': ' . $e->getMessage();
1062                 }
1063             }
1064         }
1065     }
1066    
1067    
1068     /**
1069     * Forces use as a static class
1070     *
1071     * @return fORMFile
1072     */
1073     private function __construct() { }
1074 }
1075  
1076  
1077  
1078 /**
1079  * Copyright (c) 2008 William Bond <will@flourishlib.com>
1080  *
1081  * Permission is hereby granted, free of charge, to any person obtaining a copy
1082  * of this software and associated documentation files (the "Software"), to deal
1083  * in the Software without restriction, including without limitation the rights
1084  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1085  * copies of the Software, and to permit persons to whom the Software is
1086  * furnished to do so, subject to the following conditions:
1087  *
1088  * The above copyright notice and this permission notice shall be included in
1089  * all copies or substantial portions of the Software.
1090  *
1091  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1092  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1093  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1094  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1095  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1096  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1097  * THE SOFTWARE.
1098  */