Last active
February 19, 2016 13:06
-
-
Save romaninsh/099dc008a5898dc0bbd3 to your computer and use it in GitHub Desktop.
FileStore for ATK (version 2.0) draft - You will need https://github.com/blueimp/jQuery-File-Upload and tpyo/amazon-s3-php-class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Model_File extends LF_Model { | |
public $table = 'file'; | |
public $storage = null; | |
public $local_copy = null; | |
function init(){ | |
parent::init(); | |
$this->addField('location'); // e.g. s3://bucket/file or http://host/file, or file://path/to/file | |
$this->addField('url'); // public access URL if file is available | |
$this->addField('storage'); // storage controller used | |
$this->addField('status')->enum(['draft','uploaded','verified']); | |
$this->containsOne(['file_metadata', 'json'=>true], [$this, 'initFileMetadata']); | |
$this->addHook('beforeDelete,afterUnload,afterLoad', $this); | |
} | |
function initFileMetadata($m){ | |
$m->addField('extension'); | |
$m->addField('size'); | |
$m->addField('md5'); | |
$m->addField('original_filename'); | |
$m->addField('is_image')->type('boolean'); | |
$m->addField('mime'); | |
$m->addField('width'); | |
$m->addField('height'); | |
} | |
/** | |
* Call to associate with a file storage service (such as Amazon S3) | |
*/ | |
function setStorage($controller){ | |
$controller = $this->api->normalizeClassName($controller, 'Storage'); | |
$this->storage = $this->setController($controller); | |
return $this; | |
} | |
function import($source, $original_filename=null){ | |
if(!$this->storage)throw $this->exception('Set Storage Controller first'); | |
$this->unload(); | |
$this['status']='draft'; | |
$this->save(); | |
$m = $this->ref('file_metadata'); | |
$m['original_filename'] = basename($original_filename?:$source); | |
$m['size'] = filesize($source); | |
$m['extension'] = pathinfo($original_filename?:$source, PATHINFO_EXTENSION); | |
// TODO: that's only for images | |
$is = getimagesize($source); | |
if($m['is_image'] = (boolean)$is){ | |
$m['mime'] = $is['mime']; | |
$m['width'] = $is[0]; | |
$m['height'] = $is[1]; | |
$m['md5'] = md5_file($source); | |
//$m['extension'] = $is['mime']; | |
} | |
$m->save(); | |
$this->storage->put($this, $source); | |
$this['status']='uploaded'; | |
//$this->local_copy = $source; | |
$this->save(); | |
} | |
/** | |
* Verify that the file is uploaded correctly | |
* @param [type] $source [description] | |
* @return [type] [description] | |
*/ | |
function verify($source = null){ | |
// make sure, that public URL is accessible | |
if(md5_file($this['url']) == $this->ref('file_metadata')['md5']){ | |
$this['status'] = 'verified'; | |
$this->save(); | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Retrieve file and store in local file | |
*/ | |
function getFile(){ | |
if($this->local_copy)return $this->local_copy; | |
return $this->local_copy = $this->storage->get($this); | |
} | |
function beforeDelete(){ | |
try { | |
if($this->loaded() and $this->storage){ | |
$this->storage->delete($this); | |
} | |
}catch(Exception $e){ | |
// ignore | |
} | |
} | |
function __destruct(){ | |
if($this->local_copy)@unlink($this->local_copy); | |
} | |
function afterLoad(){ | |
if($this['storage'] == 's3' && !$this->storage)$this->setStorage('S3'); | |
} | |
function afterUnload(){ | |
if($this->local_copy)@unlink($this->local_copy); | |
$this->local_copy=null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Represents an uploaded file which is still being processed | |
*/ | |
class Model_Image extends SQL_Model | |
{ | |
public $table="image"; | |
public $default_storage='S3'; | |
public $title_field='thumb_url'; | |
public $thumb_width = 600; | |
public $thumb_height = 400; | |
function init() | |
{ | |
parent::init(); | |
$this->addField('status') | |
->enum(['draft','uploaded','thumbok','normalok','ready','linked']); | |
$this->hasOne('File','original_file_id'); | |
$this->addField('original_url'); | |
$this->hasOne('File','thumb_file_id'); | |
$this->addField('thumb_url'); | |
$this->hasOne('File','normal_file_id'); | |
$this->addField('url'); | |
$this->addField('type')->enum(['testing','loan','gallery']); | |
$this->addHook('beforeDelete', $this); | |
} | |
function setStorage($st){ | |
$this->default_storage = $st; | |
return $this; | |
} | |
function autoRotateImage($i){ | |
$orientation = $i->getImageOrientation(); | |
switch($orientation) { | |
case imagick::ORIENTATION_BOTTOMRIGHT: | |
$i->rotateimage("#000", 180); // rotate 180 degrees | |
break; | |
case imagick::ORIENTATION_RIGHTTOP: | |
$i->rotateimage("#000", 90); // rotate 90 degrees CW | |
break; | |
case imagick::ORIENTATION_LEFTBOTTOM: | |
$i->rotateimage("#000", -90); // rotate 90 degrees CCW | |
break; | |
} | |
// Now that it's auto-rotated, make sure the EXIF data is correct in case the EXIF gets saved with the image! | |
$i->setImageOrientation(imagick::ORIENTATION_TOPLEFT); | |
} | |
function import($original,$original_filename = null) { | |
$this->unload(); | |
$f = $this->ref('original_file_id','model'); | |
$f->setStorage( | |
$this->default_storage, | |
$this->app->getConfig('amazon/s3/image-bucket').'/original' | |
); | |
if($this->debug)$f->debug(); | |
$f->import($original,$original_filename); | |
$f->verify(); | |
$this['original_file_id'] = $f->id; | |
$this['original_url'] = $f['url']; | |
$orig = $f->getFile(); | |
$i = new \Imagick($orig); | |
$this->autoRotateImage($i); | |
$i->cropThumbnailImage($this->thumb_width, $this->thumb_height); | |
$thumb = tempnam('/tmp',$f->ref('file_metadata')['original_filename'].'.thumb.'); | |
$i->writeImage($thumb); | |
unset($i); | |
$f = $this->ref('thumb_file_id','model'); | |
if($this->debug)$f->debug(); | |
$f->setStorage( | |
$this->default_storage, | |
$this->app->getConfig('amazon/s3/image-bucket').'/thumb' | |
); | |
$f->import($thumb, $original_filename); | |
$f->verify(); | |
$this['thumb_file_id'] = $f->id; | |
$this['thumb_url'] = $f['url']; | |
$i = new \Imagick($orig); | |
$this->autoRotateImage($i); | |
$i->cropThumbnailImage($this->thumb_width*2, $this->thumb_height*2); | |
$norm = tempnam('/tmp',$f->ref('file_metadata')['original_filename'].'.norm.'); | |
$i->writeImage($norm); | |
$f = $this->ref('normal_file_id','model'); | |
if($this->debug)$f->debug(); | |
$f->setStorage( | |
$this->default_storage, | |
$this->app->getConfig('amazon/s3/image-bucket').'/normal' | |
); | |
$f->import($norm, $original_filename); | |
$f->verify(); | |
$this['normal_file_id'] = $f->id; | |
$this['url'] = $f['url']; | |
$this->save(); | |
} | |
function ref($x,$t = null){ | |
$m = parent::ref($x,$t); | |
if($m instanceof Model_File){ | |
$m->setStorage('S3'); | |
} | |
return $m; | |
} | |
function beforeDelete(){ | |
try { | |
if($this['original_file_id'])$this->ref('original_file_id')->delete(); | |
if($this['thumb_file_id'])$this->ref('thumb_file_id')->delete(); | |
if($this['normal_file_id'])$this->ref('normal_file_id')->delete(); | |
}catch(Exception $e){ | |
// | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
abstract class Controller_Storage extends AbstractController { | |
public $default_exception = 'Exception_Storage'; | |
/** | |
* Some file storage systems support public URLs to the | |
* file (CDN) | |
*/ | |
public $supportPublicURL = false; | |
function init(){ | |
parent::init(); | |
if(!$this->owner instanceof Model_File)throw $this->exception('Add only into File model'); | |
} | |
/** | |
* Places currently loaded file into the store. Second argument | |
* must contain the file which is being imported and it must be | |
* readable. Model will be modified with some additional information | |
* which can then further be used to retrieve the file. | |
*/ | |
abstract function put($model, $source_file); | |
/** | |
* Retrieve file from the store, store in a local file and | |
* return name of that file. Can also return URL to public | |
* file. | |
*/ | |
abstract function get($model); | |
/** | |
* Deletes associtaed file from the store. | |
*/ | |
abstract function delete($model); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Controller_Storage_S3 extends Controller_Storage { | |
public $useSSL = true; | |
public $endpoint = 's3-eu-west-1.amazonaws.com'; | |
function connect(){ | |
return new S3( | |
$this->app->getConfig('amazon/s3/Access_Key_ID'), | |
$this->app->getConfig('amazon/s3/Secret_Access_Key'), | |
$this->useSSL, | |
$this->endpoint | |
); | |
} | |
function put($model, $source_file){ | |
// genetare unique filename | |
$uniq = uniqid().'.'.$model->ref('file_metadata')['extension']; | |
$s3 = $this->connect(); | |
$res = $s3 -> putObject( | |
S3::inputFile($source_file, false), | |
$bucket = $this->app->getConfig('amazon/s3/image-bucket').'/original', | |
$uniq, | |
S3::ACL_PUBLIC_READ, | |
[], | |
['Content-Type' => $model->ref('file_metadata')['mime']] | |
); | |
$model['storage']='s3'; | |
$model['location']='s3://'.$this->endpoint.'/'.$bucket.'/'.$uniq; | |
list($b1, $b2) = explode('/', $bucket, 2); | |
$model['url']='https://'.$b1.'.s3.amazonaws.com/'.$b2.'/'.$uniq; | |
return $res; | |
} | |
/** | |
* Break location such as s3://s3-eu-west-1.amazonaws.com/lf-image/original/55dc62824072f.png | |
* into bucket and filename. | |
* | |
* @return array(bucket, uri); | |
*/ | |
function splitLocation($location){ | |
// s3://s3-eu-west-1.amazonaws.com/lf-image/original/55dc62824072f.png | |
list(,$location) = explode('//', $location, 2); | |
// s3-eu-west-1.amazonaws.com/lf-image/original/55dc62824072f.png | |
list(,$location) = explode('/', $location, 2); | |
// lf-image/original/55dc62824072f.png | |
return [dirname($location), basename($location)]; | |
// lf-image/original | 55dc62824072f.png | |
} | |
/** | |
* Retrieve file from the store, store in a local file and | |
* return name of that file. Can also return URL to public | |
* file. | |
*/ | |
function get($model){ | |
$s3 = $this->connect(); | |
list($bucket,$file) = $this->splitLocation($model['location']); | |
$s = tempnam('/tmp','atk-file-'); | |
$s3->getObject($bucket, $file, $s); | |
return $s; | |
} | |
/** | |
* Deletes associtaed file from the store. | |
*/ | |
function delete($model){ | |
$s3 = $this->connect(); | |
list($bucket,$file) = $this->splitLocation($model['location']); | |
$s = tempnam('/tmp','atk-file-'); | |
return $s3->deleteObject($bucket, $file, $s); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Form_Field_Upload2 extends Form_Field { | |
public $vp; | |
function init(){ | |
parent::init(); | |
$this->app->jquery->addStaticInclude('jquery.iframe-transport'); | |
$this->app->jquery->addStaticInclude('jquery.fileupload'); | |
} | |
function setModel($m){ | |
parent::setModel($m); | |
$this->vp = $this->app->page_object->add('VirtualPage'); | |
$this->vp->set(function($f){ | |
try { | |
error_reporting(E_ALL | E_STRICT); | |
if(!$_FILES[$this->short_name]['tmp_name']){ | |
throw $this->exception('File data empty. Possibly it is too large') | |
->addMoreInfo('content-length',$_SERVER['CONTENT_LENGTH']) | |
->addMoreInfo('post_max_size',ini_get('post_max_size')) | |
; | |
} | |
$this->model->import($_FILES[$this->short_name]['tmp_name'], $_FILES[$this->short_name]['name']); | |
$res = $this->model->get(); | |
echo json_encode($res); | |
exit; | |
}catch(Exception $e){ | |
$res = ['exception' => $e->getHTML()]; | |
echo json_encode($res); | |
exit; | |
} | |
}); | |
return $this->model; | |
} | |
function getInput(){ | |
if($_GET['val']){ | |
if(!$this->value)$this->js(true)->trigger('uploaded'); // we just changed our value. | |
$this->value=$_GET['val']; | |
} | |
if($_GET['del']){ | |
$f = $this->model->tryLoad($_GET['del']); | |
$this->js(true)->trigger('removed'); // we just changed our value. | |
if($f->loaded())$f->delete() ; | |
$this->value=null; | |
} | |
if($this->value){ | |
$f = $this->model->tryLoad($this->value); | |
if($f->loaded()){ | |
if($f instanceof Model_Image)$f=$f->ref('thumb_file_id'); | |
$this->on('click', '.do-link')->univ()->dialogOK('Image','<img src="'.$f['url'].'"/>',null,['width'=>'1000']); | |
$this->on('click', '.do-remove-but', $this->form->js()->atk4_form('reloadField',$this->short_name,null,null,null,['del'=>$this->value])); | |
return '<div class="atk-form-field" data-shortname="'.$this->short_name.'" id="'.$this->getJSID().'">'. | |
parent::getInput(array('type'=>'hidden')). | |
'<div class="atk-cells atk-cells-gutter-small"><div class="atk-cell"><button class=" atk-button do-remove-but"><span class=" icon-trash" style=""></span> Remove</button></div><div class="atk-cell atk-expand"><div class="atk-effect-info atk-box-small atk-shape-rounded atk-text-nowrap">'. | |
'File: <a href="javascript: void(0)" class="do-link"><span class=" icon-file-image" | |
style=""></span> '.$f->ref('file_metadata')['original_filename'].'</a></div> | |
</div></div></div>'; | |
} | |
} | |
$this->js(true) | |
->_load('jquery.iframe-transport') | |
->_load('jquery.fileupload') | |
->_selector('#'.$this->getJSID().' .do-upload')->fileupload([ | |
'url'=>$this->vp->getURL(), | |
'dataType'=>'json', | |
'progressall'=>$this->js(null,'function (e, data) { | |
var progress = parseInt(data.loaded / data.total * 100, 10); | |
if(progress!=100){ | |
$("#'.$this->getJSID().'").find(".do-pill").css("width",progress+"%").find("span:first").text(progress+"%"); | |
}else{ | |
$("#'.$this->getJSID().'").find(".do-pill").css("width","100%").find("span:first").text("Processing file.."); | |
$("#'.$this->getJSID().'").find(".do-spinner").show(); | |
} | |
}'), | |
'start'=>$this->js(null,'function (e, data) { | |
$("#'.$this->getJSID().' .do-upload-but").attr("disabled",true).hide(); | |
$("#'.$this->getJSID().'").find(".do-info").remove(); | |
$("#'.$this->getJSID().'").find(".do-pill").removeClass("atk-effect-danger").addClass("atk-effect-info").show().css("width","0%"); | |
}'), | |
'done'=>$this->js(null,'function (e, data) { if(data.result.exception){ | |
var but = $(document.createElement("button")).insertAfter("#'.$this->getJSID().' .do-upload-but").addClass("atk-button").text("OK"); | |
but.click(function(e){ e.preventDefault(); but.closest(".atk4_form_widget").atk4_form("reloadField","'.$this->short_name.'"); }); | |
$("#'.$this->getJSID().'").find(".do-pill").text("Upload file problem").removeClass("atk-effect-info").addClass("atk-effect-danger"); | |
$.univ().dialogOK("Upload Problem",data.result.exception,null,{width:1200}); | |
return; | |
}; console.log(data); | |
$("#'.$this->getJSID().'").find(".do-pill").closest(".atk4_form_widget").atk4_form("reloadField","'.$this->short_name.'",null,null,null,{val:data.result.id}); | |
}'), | |
]); | |
; | |
//$this->js('click', $this->js()->_selector('#upload')->click())->_selector('#upload-but'); | |
$this->on('click','.do-upload-but',$this->js()->find('.do-upload')->click()); | |
//$this->js('click', $this->js()->_selector('#pill')->show()->css('width','100%'))->_selector('#info')->remove(); | |
//->css('width','100%'); | |
// $this->js('click')->_selector('#upload-but')->univ()->alert(1); | |
return | |
'<div class="atk-form-field" data-shortname="'.$this->short_name.'" id="'.$this->getJSID().'"><input class=" atk-button do-upload" type="file" name="'.$this->short_name.'" multiple style="display: none">'. | |
'<div class="atk-cells atk-cells-gutter-small"><div class="atk-cell"><button class=" atk-button do-upload-but"><span class=" icon-upload" | |
style=""></span> Upload</button></div><div class="atk-cell atk-expand"><div | |
style="display: none; -webkit-transition: width 0.3s; width: 0%; left: 0; padding-left: 10px; position: relative; overflow: none" | |
class="atk-effect-info atk-box-small atk-shape-rounded atk-text-nowrap do-pill" style="overflow: hidden">Progress: <span>0%</span><span class="do-spinner animate-spin atk-move-right icon-spin4" | |
style="display: none"></span></div><div class="do-info">Select file then wait until upload finishes before submitting form.</div> | |
</div></div></div>'; | |
} | |
} | |
class MyUploadHandler extends UploadHandler { | |
protected function handle_file_upload($uploaded_file, $name, $size, $type, $error, | |
$index = null, $content_range = null) { | |
$file = new \stdClass(); | |
$file->name = $this->get_file_name($uploaded_file, $name, $size, $type, $error, | |
$index, $content_range); | |
$file->size = $this->fix_integer_overflow((int)$size); | |
$file->type = $type; | |
if ($this->validate($uploaded_file, $file, $error, $index)) { | |
$this->handle_form_data($file, $index); | |
$file_path = $this->get_upload_path($file->name); | |
var_dump($file_path, $uploaded_file, $name, $size, $type, $error, $index, $content_range); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment