I have spent a couple hours trying to add a simple upload file option to my Zend application. I have double checked all of the necessary permissions and everything works fine. Quite simply, I have it uploading nicely to a temporary folder but once I have it in that temp folder, I can't get it to move to its permanent storage location. Below is the code that keeps failing...
To be precise, the code fails with the $uploaded die statement. I thought it might be an issue since I am sending it to the Model rather than handling it right in the Action but that didn't solve the problem either. Can anyone point me in the right direction? I just can't get the file out of the temprorary directly and into the permenant storage locatoin I want.
Thank you!
//This is the action that is called when form is submitted.
function addImageAction()
{
$imgForm = new Admin_Form_ImageUploadForm();
$imgForm->setAction('/admin/media/add-image');
$imgForm->setMethod('post');
$this->view->form = $imgForm;
if($this->getRequest()->isPost())
{
if(!$imgForm->image->receive())
{
$this->view->message = '<div class="popup-warning">Errors Receiving File.</div>';
$this->_redirect('/admin/media/add-image');
}
if($imgForm->image->isUploaded())
{
$imageModel = new Admin_Model_Image();
$imageId = $imageModel->addImage($imgForm->image->getFileName());
$this->_redirect('/admin/media/view-image/'.$imageId);
}
}
}
Block #2 - The Model
public function addImage($image)
{
// Process the New File
// Check to see if Filename is already in Database
$select = $this->select();
$select->where('filename=?', $image);
$row = $this->fetchRow($select);
if ($row)
{
die("Filename already exists in Database. Please try another file.");
}
// Move file to Storage Directory
// Check/Create Storage Directoy (YYYYMMDD)
// Temporarily set MEDIA_DIR
$mediaDir = APPLICATION_PATH . '/../public/media/uploads/';
$destinationDir = $mediaDir . date('Ymd');
if (!is_dir($destinationDir)){
$storageDir = mkdir($destinationDir);
}
// Save Image
$uploaded = is_uploaded_file($image);
if (!$uploaded) {
die("Image has not been uploaded");
}
$image_saved = move_uploaded_file($image, $destinationDir);
if(!$image_saved)
{
die("Image could not be moved");
}
// Create Alternative Sizes
// Save Data to Database Tables
$dateObject = new Zend_Date();
$row = $this->createRow();
$row->filename = $image;
$row->date_added = $dateObject->get(Zend_Date::TIMESTAMP);
$row->date_modified = $dateObject->get(Zend_Date::TIMESTAMP);
$row->save();
// Fetch the ID of the newly created row
$id = $this->_db->lastInsertId();
// Retrieve IPTC Data
// Retrieve EXIF Data
// Return Image ID
return $id;
}
receive() method moves the file using move_uploaded_file(). So the file you work with is not uploaded anymore, it's normal file. You should use standard copy() function instead.
Related
Where can I find code and instruction on how to allow users to upload files with Google Form without login?
I searched all over here and couldn't find any information.
https://developers.google.com/apps-script/reference
Thanks in advance.
The user will be uploading the files to your drive. So, google needs to verify the user. If there is no verification, someone can fill your drive in no time.
It is for your safety to know who has uploaded, so, login is must.
There's a workaround, I'm in a hurry to write the code now, but if you're interested let me know and I'll edit later.
Basically, you set up a web app with apps script, then you setup a custom HTML form, you'll have to manually collect the file, convert is to base64 then json, then when you catch it in apps script you reverse the process and save it wherever you want in your drive.
Since the user will be executing the script as you, there's no verification required
/*
These functions basically go through a file array and reads the files first as binary string (in second function), then converts the files to base64 string (func 1) before stringifying the files (after putting their base64 content into an object with other metadata attached; mime, name e.t.c);
You pass this stringified object into the body part of fetch(request,{body:"stringified object goes here"})
see next code block for how to read in apps script and save the files to google drive
N.B. The body data will be available under doPost(e){e.postData.contents}
*/
async function bundleFilesForUpload(){
let filesDataObj = [];
let copy = {fileInfo:{"ogname":"","meme":""},fileData:""};
for(let i = 0 ; i < counters.localVar.counters.filesForUploadArr.length ; i++){
let tempObj = JSON.parse(JSON.stringify(copy));
let file = counters.localVar.counters.filesForUploadArr[i];
tempObj.fileInfo.ogname = file.name;
tempObj.fileInfo.meme = file.type;
tempObj.fileData = await readFile(file).then((file)=>{
file = btoa(file);
return file;
}).then((file)=>{
return file;
})
filesDataObj.push(tempObj);
}
return filesDataObj;
}
async function readFile (file){
const toBinaryString = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsBinaryString(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
let parsedFile = null;
parsedFile = await toBinaryString(file);
return parsedFile;
}
/*From doPost downward, we read the file Array convert the base64 to blob and make a file in google drive using the blob and metadata we have, you may also see some sheet code, I'm using sheet as db for this */
//in buit function doPost in Code.gs
doPost(e){
const myDataObj = JSON.parse(e.postData.contents);
mainFileFunc(myDataObj.params[0].dataObj.images);
//the actual object structure might look different from yours, console log around
}
function mainFileFunc(fileArr) {
let myArrObj = [{"madeit":"toFileF"}];
let copy = JSON.parse(JSON.stringify(myArrObj[0]));
//sheet.getRange("A1").setValue(JSON.stringify(fileArr.length));
for(let i=0 ; i < fileArr.length ; i++){
myArrObj.push(copy);
let blob = doFileStuff(fileArr[i].data,fileArr[i].info[0].mime,fileArr[i].id);
myArrObj[i] = uploadFileOne(blob,fileArr[i].id);
myArrObj[i].mime = fileArr[i].info[0].mime;
myArrObj[i].realName = fileArr[i].name;
// sheet.getRange("A"+(i+1)).setValue(myArrObj[i].name);
// sheet.getRange("B"+(i+1)).setValue(myArrObj[i].url);
// sheet.getRange("C"+(i+1)).setValue(myArrObj[i].mime);
// sheet.getRange("D"+(i+1)).setValue(myArrObj[i].size);
}
return myArrObj;
}
function doFileStuff(filedata,filetype,filename){
var data = Utilities.base64Decode(filedata, Utilities.Charset.UTF_8);
var blob = Utilities.newBlob(data,filetype,filename);
return blob;
}
function uploadFileOne(data,filename) {
let myObj = {}
myObj["name"] = "";
myObj["realName"] = "Story_Picture";
myObj["url"] = "";
myObj["mime"] = "";
myObj["size"] = "";
myObj["thumb"] = "nonety";
var folders = DriveApp.getFoldersByName("LadhaWeb");
while (folders.hasNext()) {
var folder = folders.next();
folder.createFile(data);
}
var files = DriveApp.getFilesByName(filename);
while (files.hasNext()) {
var file = files.next();
myObj.name = file.getName();
myObj.url = file.getUrl();
myObj.mime = file.getMimeType();
myObj.size = file.getSize();
}
return myObj;
}
You can view the full frontend code for this project here and the backend here.
Hope this helps someone.
For Example I have this code
class MyDataObject extends DataObject {
private static $has_one = [
"MyImage" => Image::class,
];
public function getCMSFields(){
$fields = parent::getCMSFields();
$fields->addFieldsToTab('Root.Main', [
UploadField::create('MyImage');
]);
return $fields;
}
}
When user removes file from MyDataObject in admin
this file still remains in 'files' part of cms, in database and on filesystem, so user need to go to 'files' and remove in manually.
On practice he often forgets to remove file after detaching it from some dataobject and all these files holds a lot of place.
How can SilverStripe automatically remove file from filesystem when users clicks on cross on screenshot?
You need a onAfterDelete() on your Data Object. There you can delete the file.
(don't forget to call Parent::onAfterDelete() in your method)
** UPDATE **
OR if it's only when the user is editing not deleting your Object, then onAfterWrite() is your friend. There you can compare old and new ID of the image, and if is different, delete the Image with the old ID.
To solve the problem I've created this extension
<?php
use SilverStripe\ORM\DataExtension;
use SilverStripe\Assets\File;
class DataObjectRemoveImagesExt extends DataExtension {
private function killFile($fileId) {
$fileToRemove = File::get()->byId($fileId);
if ($fileToRemove) $fileToRemove->delete();
}
public function onAfterWrite() {
$changedFieldsArr = $this->owner->getChangedFields();
if (!$this->owner->config()->get('kill_on_detach')) return;
$detachList = $this->owner->config()->get('kill_on_detach');
foreach ($detachList as $fileFieldName) {
if (!isset($changedFieldsArr["{$fileFieldName}ID"])) continue;
$changedFieldValues = $changedFieldsArr["{$fileFieldName}ID"];
if (
(
$changedFieldValues['before'] != $changedFieldValues['after']
&&
$changedFieldValues['before'] != 0
&&
$changedFieldValues['after'] != 0
)
||
(
$changedFieldValues['after'] == 0
&&
$changedFieldValues['before'] != 0
)
){
$this->killFile($changedFieldValues['before']);
}
}
}
}
Usage:
1. Attach to DataObject
SilverStripe\ORM\DataObject:
extensions:
- DataObjectRemoveImagesExt
In your custom DataObject child use this property to set files/images to be deleted
private static $kill_on_detach = [
'Image',
'Thumb',
];
Solution is not perfect:
it kills file even if it attached to some another DataObject instance
it not kills file if user detached file from admin-panel and forgot
to save DataObject
If somebody wants to propose better decision - welcome.
I am working with Zend and I needed to check whether a row in the DB already exists (A simple solution to get rid of the duplicate key error I was getting). I tried several things but nothing seemed to work... (for example the Zend_Validate_Db_NoRecordExists method)
So I wrote the following the code and I was wondering if this is a valid way to do it, or if I should do things differently:
In the model:
$where = $condition = array(
'user_id = ' . $user_id,
'page_id = ' . $page_id
);
$check = $this->fetchRow($where);
if(count($check) > 0) {
return null;
}else{
// Here I create a new row, fill it with data, save and return it.
}
And then in my view:
if($this->result != null) { /* do stuff */ }else{ /* do other stuff */ }
It does work but it does seem to take more time (duh, because of the extra query) and I am a bit unsure whether I should stick with this..
Any recommendation is welcome :)
Assuming you have coded your function in your controller
$row = $this->fetchRow($where); //If no row is found then $row is null .
if(!$row)
{
$row = $dbTb->createNew($insert); //$insert an associative array where it keys map cols of table
$row->save();
$this->view->row_not_found = true;
}
return $row;
In your view you can do this
if($this->row_not_found)
{
}else {
}
I'm learning Zend Framework. I had Zend_Paginator working with the array adapter, but I'm having trouble using the Zend_Paginator::factory static method. The problem is the pagination control links send me to the correct URL, but the results disappear when I click next or page 2, 3, etc.
I have two tables from a database: a file table and an origination_office table. The file table has the client's names, address, etc. and the origination office stores office names (like Tampa, Sarasota, etc.). Each file is associated with an origination office.
My controller:
public function searchAction()
{
$searchForm = new Application_Form_SearchForm();
if ($this->_request->getQuery()) {
if ($searchForm->isValid($this->_request->getParams())) {
$officeName = $this->_request->getParam('officeName');
$page = $this->_request->getParam('page');
}
$fileTable = new Application_Model_DbTable_File();
$this->view->paginator = $fileTable->getFilesByOfficeName($officeName, $page);
}
$this->view->searchForm = $searchForm;
}
Here is the getFilesByOfficeName() method:
public function getFilesByOfficeName($officeName = null, $page = 1, $count = 12, $range = 15, $scrolling = 'Sliding')
{
if (is_null($officeName)) {
$query = $this->select();
$paginator = Zend_Paginator::factory($query);
} else {
$oofTable = new Application_Model_DbTable_OriginationOffice();
$query = $oofTable->select();
$query->where('oof_name like ?', $officeName.'%');
if ($oofTable->fetchRow($query)) {
$origination_office = $oofTable->fetchRow($query);
$files = $origination_office->findDependentRowset($this);
$paginator = Zend_Paginator::factory($files);
} else {
return;
}
}
Zend_Paginator::setDefaultScrollingStyle($scrolling);
Zend_View_Helper_PaginationControl::setDefaultViewPartial('_pagination_control.phtml');
$paginator->setDefaultItemCountPerPage($count);
$paginator->setDefaultPageRange($range);
$paginator->setCurrentPageNumber($page);
return $paginator;
}
Ok, I think I am understanding your problem. Your links are not maintaining the state of your initial request and it's URL query string.
You might want to edit your partial view (_pagination_control.phtml) to render the current query string in your links.
I would need to see what your doing in the partial to give an exact answer, but this should work if you add ?$_SERVER['QUERY_STRING'] to the end of your final URL. See Below Example:
<!-- Your href may look different but notice I append the query string to the end -->
Last ยป
I want to cache my results from Zend_Lucene_Search using Zend_Paginator::setCache()
I get the following error:
Warning: fseek() expects parameter 1 to be resource, integer given
Here is the portion of code:
// Load index
$index = Zend_Search_Lucene::open(APPLICATION_PATH . '/indexes');
// Paginate
$paginator = Zend_Paginator::factory($index->find($query));
$paginator->setCache($this->_cache);
$paginator->setItemCountPerPage($items);
$paginator->setCurrentPageNumber($page);
// Send to view
$this->view->hits = $paginator;
In other areas of the site where I use the same technique to cache paginated results that are not from Zend_Lucene_Search, this works fine.
I read somewhere that storing results in a session or cache destroys the lucene document and that you have to convert the QueryHit objects to stdClass objects, but how? Does this work?
Ok solved it, I was overthinking it
$hits = $index->find($query);
$this->view->totalHits = count($hits);
// Convert to stdClass to allow caching
foreach ($hits as $i => $hit) {
$resultsArray[$i] = new stdClass();
$doc = $hit->getDocument();
foreach($doc->getFieldNames() as $field){
$resultsArray[$i]->{$field} = $hit->{$field};
}
}
// Paginate
$paginator = Zend_Paginator::factory($resultsArray);
$paginator->setCache($this->_cache);
$paginator->setItemCountPerPage($items);
$paginator->setCurrentPageNumber($page);
// Send to view
$this->view->hits = $paginator;