File Move Google Drive API v3 PHP - php

Using Google API v3 I try to move a file from one folder to another. I am using a wrapper class in Laravel, the file and parent IDs are correct. Developing from the documentation, I have tried code as:
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ['fields' => '*']);
$fromParentID = $fromFile->getParents();
$blankFile = new Google_Service_Drive_DriveFile();
$this->service->files->update($fileID, $blankFile, [
"removeParents" => $fromParentID,
"addParents" => $toParentID
]);
}
However, this seems to move the file but strips out all the meta data.
I have also tried
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ['fields' => '*']);
$fromParentID = $fromFile->getParents();
$fromFile->setParents($fromParentID);
$this->service->files->update($fileID, $fromFile);
}
However, this gives the error:
Google\Service\Exception
{ "error": { "errors": [ { "domain": "global", "reason":
"fieldNotWritable", "message": "The resource body includes fields
which are not directly writable." } ], "code": 403, "message": "The
resource body includes fields which are not directly writable." } }
I wish to simply move the file and retain all its metadata. From the documentation, it seems either a new empty file is required in update (really weird) or I must somehow strip out the fields of the object used in the second argument ($fromFile) it does not like to be written to (even though I am simply updating the files parents - which is writable).
See also https://issuetracker.google.com/issues/199921300
Problems with Answers so far:
but grateful for responses
$fromFile = $this->service->files->get($fileID, ['fields' => 'parents, id']);
returns all ~75 attributes a lot of which are not writeable.
Instead of the expected 2 as per PHPStorm debug (note the break is at the statement immediately following the GET request so irrelevant at this point
using
unset($fromFile->shared);
still leaves other writable attributes
and indeed the file is not actually shared
UPDATE TO MY CODING
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ["fields" => "id,parents"]);
$fromFile = $this->getParsedWritableFile($fromFile);
$fromFile->setParents($toParentID);
$this->service->files->update($fileID, $fromFile, ['addParents' => $toParentID]);
}
getParsedWritableFile is trying to just set writable attributes on a new Google Drive file object:
public function getParsedWritableFile($gdrivefile) {
$gdrivefile = new \Google_Service_Drive_DriveFile();//comment or delete, just here to check auto complete function names
$parsedFile = new \Google_Service_Drive_DriveFile();
//$parsedFile=$gdrivefile;
// currently only allow these according to https://developers.google.com/drive/api/v3/reference/files#resource-representations
$parsedFile->setName($gdrivefile->getName());//name
$parsedFile->setMimeType($gdrivefile->getMimeType());//mimeType
$parsedFile->setDescription($gdrivefile->getDescription());//description
$parsedFile->setStarred($gdrivefile->getStarred());//starred
$parsedFile->setTrashed($gdrivefile->getTrashed());//trashed
$parsedFile->setParents($gdrivefile->getParents());//parents
$parsedFile->setProperties($gdrivefile->getProperties());//properties [object]
$parsedFile->setAppProperties($gdrivefile->getAppProperties());//appProperties [object]
$parsedFile->setCreatedTime($gdrivefile->getCreatedTime());//createdTime
$parsedFile->setModifiedTime($gdrivefile->getModifiedTime());//modifiedTime
$parsedFile->setWritersCanShare($gdrivefile->getWritersCanShare());//writersCanShare
$parsedFile->setViewedByMeTime($gdrivefile->getViewedByMeTime());//viewedByMeTime
$parsedFile->setFolderColorRgb($gdrivefile->getFolderColorRgb());//folderColorRgb
$parsedFile->setOriginalFilename($gdrivefile->getOriginalFilename());//originalFilename
$parsedFile->setCopyRequiresWriterPermission($gdrivefile->getCopyRequiresWriterPermission());//copyRequiresWriterPermission
/*complex permissions*/
/*
contentHints.thumbnail.image
contentHints.thumbnail.mimeType
contentHints.indexableText
*/
$contenthints=$gdrivefile->getContentHints();//could be null meaning further functions eg getThumbnail cause exception
if($contenthints){
$parsedFile->setContentHints($contenthints->getThumbnail()->getImage());
$parsedFile->setContentHints($contenthints->getThumbnail()->getMimeType());
$parsedFile->setContentHints($contenthints->getIndexableText());
}
/*no function to get indiviual attributes*/
/*
contentRestrictions[].readOnly
ccontentRestrictions[].reason
*/
$parsedFile->setContentRestrictions($gdrivefile->getContentRestrictions());
//</ end>
return $parsedFile;
}
This is proving a bit successful but this is original meta
the above code does move it, with seemingly proper meta data, created time and EXIF data is now intact

The problem is that you are using file.update which uses HTTP PATCH methodology. By using a PATCH its going to try to update all of the properties in the file object that you send.
You did a file.get and you included fileds *
$fromFile = $this->service->files->get($fileID, ['fields' => '*']);
By including 'fields' => '*' you told the API to return to you all of the properties that the file has. A file resource has a lot of properties and they are not all writeable.
By sending the file.update method all of the fields you are telling it you want to update everything including some of the properties that you are not allowed to update. To which the api responds with fieldNotWritable
The soultion would be to do the following
$fromFile = $this->service->files->get($fileID, ['fields' => 'parents, id']);
Which will cause the method to only return the two parameters that you actual need. The id and the parents parameter. (TBH you may just need the parents part, I would need to test that.)
{
"id": "1x8-vD-XiA5Spf3qp8x2wltablGF22Lpwup8VtxNY",
"parents": ["0B1bbSFgVLpoXcVDRFRF8tTkU"
]
}
You should then be allowed to update the parent and move your file.

I don't know how to do in laravel but the problem might be
I have also faced the same problem some time and after searching internet for over months found nothing and one day Referring to documentation, seen that shared
isn't a writable field.
That's it, if you are sharing the File and trying to move the file it wouldn't move. Try to un-share the File and then try to move the File it would be done.

I ended up creating a custom function to only include writeable attributes as specified in the Google Documentation:
public function getParsedWritableFile($gdrivefile) {
$gdrivefile = new \Google_Service_Drive_DriveFile();//comment or delete, just here to check auto complete function names
$parsedFile = new \Google_Service_Drive_DriveFile();
// currently only allow these according to https://developers.google.com/drive/api/v3/reference/files#resource-representations
$parsedFile->setName($gdrivefile->getName());//name
$parsedFile->setMimeType($gdrivefile->getMimeType());//mimeType
$parsedFile->setDescription($gdrivefile->getDescription());//description
$parsedFile->setStarred($gdrivefile->getStarred());//starred
$parsedFile->setTrashed($gdrivefile->getTrashed());//trashed
$parsedFile->setParents($gdrivefile->getParents());//parents
$parsedFile->setProperties($gdrivefile->getProperties());//properties [object]
$parsedFile->setAppProperties($gdrivefile->getAppProperties());//appProperties [object]
$parsedFile->setCreatedTime($gdrivefile->getCreatedTime());//createdTime
$parsedFile->setModifiedTime($gdrivefile->getModifiedTime());//modifiedTime
$parsedFile->setWritersCanShare($gdrivefile->getWritersCanShare());//writersCanShare
$parsedFile->setViewedByMeTime($gdrivefile->getViewedByMeTime());//viewedByMeTime
$parsedFile->setFolderColorRgb($gdrivefile->getFolderColorRgb());//folderColorRgb
$parsedFile->setOriginalFilename($gdrivefile->getOriginalFilename());//originalFilename
$parsedFile->setCopyRequiresWriterPermission($gdrivefile->getCopyRequiresWriterPermission());//copyRequiresWriterPermission
/*complex permissions*/
/*
contentHints.thumbnail.image
contentHints.thumbnail.mimeType
contentHints.indexableText
*/
$contenthints=$gdrivefile->getContentHints();//could be null meaning further functions eg getThumbnail cause exception
if($contenthints){
$parsedFile->setContentHints($contenthints->getThumbnail()->getImage());
$parsedFile->setContentHints($contenthints->getThumbnail()->getMimeType());
$parsedFile->setContentHints($contenthints->getIndexableText());
}
/*no function to get indiviual attributes*/
/*
contentRestrictions[].readOnly
ccontentRestrictions[].reason
*/
$parsedFile->setContentRestrictions($gdrivefile->getContentRestrictions());
return $parsedFile;
}
and called with
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ["fields" => "id,parents"]);
$fromFile = $this->getParsedWritableFile($fromFile);
$fromFile->setParents($toParentID);
$this->service->files->update($fileID, $fromFile, ['addParents' => $toParentID]);
}

Related

Queue::fake not work with Laravel5 codeception module

I write test for Laravel app with codeception and modules Laravel5, REST.
One of api test:
public function testEmailRegistration(ApiTester $I) {
...
// Not correct data
$I->sendPOST($route, [
'first_name' => (string)$this->faker->randomNumber(),
'password' => $this->faker->password(1, 7),
'email' => 'not_valid_email',
]);
$I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY);
// Correct data
\Illuminate\Support\Facades\Queue::fake();
$I->sendPOST($route, [
'first_name' => $firstName,
'password' => $password,
'email' => $email,
]);
\Illuminate\Support\Facades\Queue::assertPushed(\App\Jobs\SendEmail::class);
...
}
I send requests on incorrect and correct data and make some assertions. In addition I check, that job is present in queue.
After execute test I give error:
[Error] Call to undefined method Illuminate\Queue\SyncQueue::assertPushed()
After Queue:fake facade \Illuminate\Support\Facades\Queue must resolves to QueueFake, but in fact is still QueueManager, thus assertPushed function is undefined.
Execution of $I->sendPOST() reset call Queue::fake. It happened in laravel 5 module \Codeception\Lib\Connector\Laravel5, method doRequest.
protected function doRequest($request)
{
if (!$this->firstRequest) {
$this->initialize($request);
}
$this->firstRequest = false;
$this->applyBindings();
$this->applyContextualBindings();
$this->applyInstances();
$this->applyApplicationHandlers();
$request = Request::createFromBase($request);
$response = $this->kernel->handle($request);
$this->app->make('Illuminate\Contracts\Http\Kernel')->terminate($request, $response);
return $response;
}
Each call of doRequest except the first init app again and some configurations as Queue::fake are cleared.
One of decision is one request per test. Is there another variant to work Queue::fake when in test make more then one request?
I am not sure why the Laravel module does this but I found a work-around that allows you to use fakes:
public function someTest(ApiTester $I): void
{
// what the SomeFacade::fake method call does is basically create
// a fake object and swaps it for the original implementation in
// the app container, so the we're recreating that behavior here
// only this will be persisted even after the request is issued:
$notification_fake = new NotificationFake();
// `haveInstance` is a method from Laravel Codeception Module
// which sets an object in the app container for you:
$I->haveInstance(ChannelManager::class, $notification_fake);
// making the request
$I->sendPUT('some url', $some_payload);
// assertions
$I->canSeeResponseCodeIs(Response::HTTP_OK);
$notification_fake->assertSentToTimes($expected_user, MyNotification::class, 1);
}
note that this test method is only for illustrative purposes and it misses so of the details, hence the undefined variables and such.
Also note that I user the notification fake which get registered at Illuminate\Notifications\ChannelManager, unlike most fakes that you can register under their aliased name e.g. queue. So you have to check what is being instantiated and how to swap it on your own. You can either find this in respective service providers for each service. Most of the time it's lowercase name of the facade.

Magento: White page on order view page

Posted this on http://magento.stackexchange.com but I haven't gotten an answer.
I am getting this error for a while and I am clueless to what is wrong with it. Whenever I click the view order page, I get a blank page. Now I installed some code on my index.php that displays the errors.
This is the error that I get:
Array ( [type] => 1 [message] => Class
'Mage_Sales_Block_Order_Info' not found [file] =>
/app/code/core/Mage/Core/Model/Layout.php [line] => 491 )
This is line 491 (and context)
/**
* Create block object instance based on block type
*
* #param string $block
* #param array $attributes
* #return Mage_Core_Block_Abstract
*/
protected function _getBlockInstance($block, array $attributes=array())
{
if (is_string($block)) {
if (strpos($block, '/')!==false) {
if (!$block = Mage::getConfig()->getBlockClassName($block)) {
Mage::throwException(Mage::helper('core')->__('Invalid block type: %s', $block));
}
}
if (class_exists($block, false) || mageFindClassFile($block)) {
$block = new $block($attributes);
}
}
if (!$block instanceof Mage_Core_Block_Abstract) {
Mage::throwException(Mage::helper('core')->__('Invalid block type: %s', $block));
}
return $block;
}
Seems nothing is wrong there (and why should it, its magento default code)
Now the next part is somewhat strange. I expected the class to live in app/code/core/Mage/Sales/Block/Order/Info I looked it up. The folders Order/Info didnt exist in my folder. Nor in the fresh install zip.
What is going on and more importantly, how can I fix this?
I hope you are using latest version of magento never mind even if it is old looks like you are missing some order block files try copying all the files properly. some times you may miss some files while you download the files from magento website check your magento zip file size that you downloaded.

Yii restful API, how to modify return results from model

I followed this tutorial to set up my back end server based on Yii framework and then this tutorial to set up my API and everything is working as it should.
But I am not sure how to accomplish the next step:
My return array right now is pulling records that look like this:
{
"id": 1,
"user_id": 1,
"description": "This is a summary of article 1",
"status": 2,
"type": 1,
"created_at": 1426210780,
"updated_at": 1426365319
}
The value for 'type' is '1' in the db, but I want to store all the possible values of 'type' in another table like this:
1 : Red
2 : Blue
3 : Green
And then I want the JSON returned via my API to contain "type":'red' instead of "type":1. I assume I need to override something in my Model, but I can't figure out what to override or with what.
I'm happy to read through tutorials or documentation but I'm such a beginner at this that I'm not sure what terms to search for. Thanks for your help!
Have a look at models and their relationships to other models, this will allow you to get the information you need.
http://www.yiiframework.com/doc/guide/1.1/en/database.arr
Once the relationship is working correctly you should be able to get the colour from the original model.
Although this is from a earlier version of Yii it may help you understand how the models will interact as well
http://www.yiiframework.com/wiki/285/accessing-data-in-a-join-table-with-the-related-models/
#Burrito's response provided documentation but I want to give the full solution for other searchers:
First, I needed to set up a model for 'Type'.
Second, I needed to declare the relationship between 'Report' (my main model) and 'Type' like this (in my Report model):
public function getType()
{
return $this->hasOne(Type::className(), ['id' => 'type']);
}
(I'm not sure if that step is necessary, but the documentation makes it seem necessary.)
Third, I created getTypeName (in Report model), to get the name of the type based on the ID:
public function getTypeName($type = null)
{
return Type::findOne($type)->name;
}
Lastly, in my apiController, I modified the function that I am using to get all records to include a loop for each record that called getTypeName:
protected function findAllReports()
{
// get all reports
$reports = Report::find()
->asArray()
->all();
if( $reports ){
$i = 0;
// loop through each report
foreach($reports as $report){
$model = new Report();
// add a new key/value pair to each report array, populate with getTypeName and pass the type ID to it as a parameter
$reports[$i]['typeName'] = $model->getTypeName($reports[$i]['type']);
$i++;
}
return $reports;
} else {
// error or no results
}
}
For reference, the other routine needed here is the action that the API hits, which calls findAllReports():
public function actionList()
{
$reports=$this->findAllReports();
$this->setHeader(200);
echo json_encode(array('status'=>1,'data'=>$reports),JSON_PRETTY_PRINT);
}
Finally, now if I called [url]/api/list, I get an array of reports, including the typeName.

joomla 3.x tags in custom component fails

Running Joomla 3.3.0-dev
I'm following the info posted here about adding tag support to a third-party component.
I've added the content type to the #__content_types table and modified my table file like this:
class MycomponentTableElement extends JTable
{
public $tagsHelper = null; // failed when protected and public
public function __construct(&$_db)
{
parent::__construct('#__mycomponent', 'id', $_db);
// Add Joomla tags
JObserverMapper::addObserverClassToClass('JTableObserverTags', 'MycomponentTableElement', array('typeAlias' => 'com_mycomponent.element'));
//$this->_observers = new JObserverUpdater($this); JObserverMapper::attachAllObservers($this); // failed with or without this line
}
I added the tag field in the edit template, and it worked fine-- but when I save an object I get the following error:
Save failed with the following error: Unknown column 'tagsHelper' in 'field list'
What am I missing? There's no other steps (besides front-end steps!) that are mentioned. It seems like I need to modify the model but that info is not applicable.
Thanks
"This Page Needs Copy Editing" and it's really true!
I also follow initial steps as described in page
Register a 'Content type' for the extension view(s)
Add 'Observer methods' to the extension table class(es)
Add 'Tag fields' to the extension edit forms
But to make field tag works on custom extensions I need to explicit set form field value in view file of backend:
$tagsHelper = new JHelperTags;
$this->form= $this->get('Form');
$this->form->setValue('tags', null, $tagsHelper->getTagIds( $this->item->id, 'com_custom.viewname') );
in this way on edit page all seems to work correctly.. surely exist better and more clean method, but until doc page will not be updated, this can help someone!
1- Add tag field to your xml form file or edit template file
2- Modify #__content_types table file:
function __construct(&$db)
{
parent::__construct('#__ir_products', 'id', $db);
JTableObserverTags::createObserver($this, array('typeAlias' => 'com_itemreview.product'));
}
3- Modify model file getItem function:
public function getItem($pk = null)
{
$item = parent::getItem($pk);
if (!empty($item->id))
{
$item->tags = new JHelperTags;
$item->tags->getTagIds($item->id, 'com_yourcomponent.yourmodel');
}
return $item;
}

set upload folder in dataobject to the title of the related page - Silverstripe 3.1

hi i've got a gallery page. this gallery page has a gallery image object with an has_many relation.
private static $has_many = array(
'GalleryImages' => 'GalleryObject'
);
my gallery object has an image upload field. I want to set the upload folder to the title of the gallery page
i tried this with no result
$visual->setFolderName('Galerie/'.$this->Gallery()->Title);
and this (what i would prefer)
public function getGalleryTitle() {
$galleryTitle = $this->Gallery()->Title->First();
$uploadFolder = str_replace(' ', '-', $this->$galleryTitle);
return $uploadFolder;
}
$visual->setFolderName('Galerie/'.$this->$uploadFolder);
the second returns and error (undefined variable uploadFolder ?!) and my upload folder is now set to "Galerie/DataList"
can someone tell me how to convert the output of $uploadFolder so that i get back the title?
EDIT:
GalleryHolder: http://www.sspaste.com/paste/show/5267dea3579a6
GalleryPage: http://www.sspaste.com/paste/show/5267dee4c9752
GalleryObject: http://www.sspaste.com/paste/show/5267df0af1a65
you where almost there..
Here is your edited getGalleryTitle() function.
It is basically checking if the GalleryObject has a parent Gallery via $this->GalleryID. Since it is a has_one relation the column will be named GalleryID.
Then we get the Gallery object with $this->Gallery() and get it's title with $gallery->Title.
I've also replaced your str_replace with SilverStripe's URLSegmentFilter class. Which will removed spaces and other special characters non welcome in URL, a better solution.
public function getGalleryTitle()
{
if ( $this->GalleryID )
{
$gallery = $this->Gallery();
$filter = new URLSegmentFilter();
return $filter->filter( $gallery->Title );
}
else{
return 'default';
}
}
Then in the getCMSFields() function, when creating your UploadField we just call the getGalleryTitle() function that returns the string for the folder name.
$visual = new UploadField('Visual', _t('Dict.IMAGE', 'Image'));
$visual->setFolderName('Galerie/'.$this->getGalleryTitle());
A few notes..
$this references the current Object instance, so you can't use $this->$galleryTitle to access a variable you just created in your function, $galleryTitle by itself is enough.
You were calling $this->$uploadFolder in setFolderName, this doesn't work for the same reason, and also, using $uploadFolder by itself wouldn't work since this variable was created in the scope of another function. So we just call the function we defined on our Object with $this->getGalleryTitle() since it returns the value we want.
This should work fine, but keep in mind that if the Title of the Gallery changes at some point, the folder name will change too. So you might end up with images uploaded in many different folders for the same gallery... I personally wouldn't advise it, unless you implement some kind of "Title locking system" or some way to keep the "correct" or first "valid/acceptable" Gallery title in a separate object property that can't be edited and use this in the folder name.
I usually only use the ID in those case ($gallery->ID), as this will not change.
edit
Another version of getGalleryTitle() that should work even if the GalleryObject isn't saved yet.
public function getGalleryTitle()
{
$parentID = Session::get('CMSMain')['currentPage'];
if ( $parentID )
{
$gallery = Page::get()->byID( $parentID );
$filter = new URLSegmentFilter();
return $filter->filter( $gallery->Title );
}
else{
return 'default';
}
}
First, I check to see whether we're on the CMSSettingsPage or in a ModelAdmin page (Should you be using them). You want to get all the information about which class the controller is managing as it's data record. (If you have firebug, FB($this) in getCMSFields() on the related DataObject (DO) will show you the page managed under DataRecord)
Controller::curr()->currentPage() will get you the current page the DO is being managed on, and ->URLSegment will get the page url name, though you could use Title or MenuTitle also.
Here is an example which will set up a folder underneath assets/Headers to save images in. Running this on the HomePage (ie URL Segment 'home') will create and save objects into the folder /assets/Headers/home.
if (Controller::curr()->class == 'CMSSettingsController' || Controller::curr() instanceof Modeladmin) {
$uploadField->setFolderName('Headers');
}
else
{
$uploadField->setFolderName('Headers/' . Controller::curr()->currentPage()->URLSegment);
}

Categories