SilverStripe Image DataObject not available after creation - silverstripe

I have this code that creates a new Image based on a new file added to the filesystem but not yet in the DB:
$image = Image::create();
$image->Filename = 'assets/Art/e3434cc7-d348-491a-9dc8-325af3d9086d.jpg';
$image->write();
$images = Image::get();
$image = $images->last();
$vd = new ViewableData();
$ad = new ArrayData(array(
'Image' => $image
));
$strHTML = $vd->customise($ad)->renderWith('Art');
Art.ss contains only $Image.SetWidth(100)
Ignoring the fact the query doesn't look up by ID or whatever... why is the image only rendered into $strHTML if I retrieve the image from the DB after creating? If I delete the code below, $strHTML is empty:
$images = Image::get();
$image = $images->last();

There is a setter available so using that alone may help, but you also may need to assign the ID of the parent folder to the object:
$pFolder = Folder::find_or_make('Art');
if ($pFolder) {
$image->setParentID($pFolder->ID);
$image->setFilename('assets/Art/e3434cc7-d348-491a-9dc8-325af3d9086d.jpg');
}

Related

How to create an ad using Facebook Marketing API for PHP?

I followed https://developers.facebook.com/docs/marketing-api/buying-api#ad-creative but it didn't work. I stuck at creating AdImage.
Api::init($facebook_appid, $facebook_appsecret, $facebook_accesstoken);
$image = new AdImage(null, $facebook_accountid);
$image->{AdImageFields::FILENAME} = '/path/to/file.jpg';
$image->create();
echo 'Image Hash: ', $image->{AdImageFields::HASH}, PHP_EOL;
I got:
$parent_id as a parameter of constructor is being deprecated, please try not to use this in new code.
But assigning account id to a field doesnt work either:
Api::init($facebook_appid, $facebook_appsecret, $facebook_accesstoken);
$image = new AdImage();
$image->{AdImageFields::ACCOUNT_ID} = $facebook_accountid;
$image->{AdImageFields::FILENAME} = public_path('demo/banners/coworking-300x250.png');
$image->create();
echo 'Image Hash: ', $image->{AdImageFields::HASH}, PHP_EOL;
I got:
A parent ID is required.
-- UPDATE --
The following should work for images:
Api::init($facebook_appid, $facebook_appsecret, $facebook_accesstoken);
$account = new AdAccount($facebook_accountid);
$image = $account->createAdImage([], [
AdImageFields::FILENAME => public_path('demo/banners/coworking-300x250.png'),
]);
The current problem is - how to create account for testing...
i have same problem with you. My problem solved by using setParentId() function from AbstractCrudObject.php file.
Do change your code like this, hope it helps
$image = new AdImage();
$image->setParentId("act_somenumber");

Create file dynamically as File object and then publish

It's evidently a little more complicated to create a file dynamically in SS4
$folder = Folder::find_or_make('Cards');
$filename = 'myimage.jpg';
$contents = file_get_contents('http://example.com/image.jpg');
$pathToFile = Controller::join_links(Director::baseFolder(), ASSETS_DIR, $folder->Title, $filename);
file_put_contents($pathToFile, $contents);
$image = Image::create();
$image->ParentID = $folder->ID;
$image->Title = "My title";
$image->Name = $filename;
$image->FileFilename = 'Cards/' . $filename;
$image->write();
Member::actAs(Member::get()->first(), function() use ($image, $folder) {
if (!$image->isPublished()) {
$image->publishFile();
$image->publishSingle();
}
if (!$folder->isPublished()) {
$folder->publishSingle();
}
});
The above, creates the file as expected in /assets/Cards/myimage.jpg and publishes it fine
However all previews are blank, so it's obviously not finding the file:
Any idea what I missed in creating the Image object?
This should work:
$folder = Folder::find_or_make('Cards');
$contents = file_get_contents('http://example.com/image.jpg');
$img = Image::create();
$img->setFromString($contents, 'image.jpg');
$img->ParentID = $parent->ID;
$img->write();
// This is needed to build the thumbnails
\SilverStripe\AssetAdmin\Controller\AssetAdmin::create()->generateThumbnails($img);
$img->publishSingle();
FYI: $img->Filename no longer exists. An Image or File object have a File property, which is a composite field of type DBFile. This composite fields contains the filename, hash and a variant…
So you should use the composite field to address these fields.

Forms: transform multiple uploaded files (multiple => true) to multiple entities

I have an entity called Image. It contains a file attribute. I have also a form to create new Image entities with a FileType field which admits multiple uploads (multiple => true).
In case the user uploads multiple files, I would like to create the corresponding Image entities. What/where is the smartest way/place to do that?
We can do this inside controller,
$request = Request::createFromGlobals();
$em = $this->getDoctrine()->getManager();
if($request->isMethod('POST')){
foreach($request->files as $uploadedFile) {
$name = 'yourname.jpg';
$file = $uploadedFile->move($directory, $name);
$image->setFile('yourname.jpg');
$image = new Image();
$em->persist($image);
$em->flush();
}
}
* Remember images will be saved in the table according to the order you put them in view :)

Wordpress get attachment of post doesn't work

I'm trying to retrieve all the attachment of a specific post, but it doesn't work for the moment. Here is the code :
$args = array(
'post_type' => 'attachment',
'posts_per_page' => -1,
'post_parent' => $id
);
$attachments = get_posts($args);
Where Id is the id of my post. I've tried also with new WP_Query() but it didn't worked neither. Both ways return an empty result.
Does anyone have an idea what I'm doing wrong here ?
Thanks
EDIT
Ok, I think I know what's going wront.
When using this arguments for the get_posts function, it will return the images uploaded through the "Add a media" button in the specific post.
So basically, let's say that on my first post, I've uploaded all the images I would need for all my future post. If I apply the request on the first post, I will retrieve all the images, even the one that I don't use in this post.
If I apply this function to another post, because I didn't uploaded any file in this post, the function will retrieve an empty array.
Does anyone have an idea how I can retrieve all the images used in a specific post ? So not only uploaded, but really integrated into the post or added to a custom field ?
Thanks
When using get_posts to fetch attachments, you need to set post_status to inherit. Also, check out the get_children function:
$args = array(
'post_type' => 'attachment',
'post_mime_type' => 'image',
'numberposts' => -1,
'post_status' => 'inherit',
'post_parent' => $id
);
$attachments = get_children( $args );
Case: using ACF, I created an repeater field 'resort_pics' for my gallery, which has 2 fields 'picture_title' as text and 'picture' as an picture type.
Then happily uploaded 100 photos, some of them were same for several posts so I only clicked those from uploaded gallery (I will use term of "referenced images" for those).
Problem : then one happy day I noticed all "referenced images" are missing in our api.
Cause :
As noted at documentation comment from 'Uriahs Victor' (https://developer.wordpress.org/reference/functions/get_attached_media/)
Important to note that this function only returns the attachments that
were first uploaded/added to the post. Uploading an image to Post A(ID
1) and then adding that image later to Post B(ID 2) would give an
empty array if the following code was used:
$media = get_attached_media( 'image', 2 );
var_dump( $media );
Real cause :
Real source of all this problem is The thing that information about "reference images" are not stored in 'wp_posts' table but are actually stored in 'wp_postmeta', so by just querying 'wp_posts' table or using get_attached_media() which only looks there also you will not get all attachements for post.
Solution :
Lets take an example of post with ID - $postId which has defined
repeater - 'resort_pics', field in repeater with image 'picture'. (created with ACF plugin)
First get all attachements for our post (resort) (including images/pdfs and so on) with classic way (can be also used 'get_attached_media'):
$images = $wpdb->get_results("select ID,guid from wp_posts where post_parent = " . $postId . " and `post_type`= 'attachment'");
where guid is 'url' to an attachement, lets index those in array where key will be post id of an attachement
$mapImages = [];
foreach ($images as $image) {
$mapImages[$image->ID] = $image->guid;
}
Now we have all atachements but are missing all referenced images/files.
Lets continue by selecting all meta for our post (resort).
$metas = $wpdb->get_results("select meta_key,meta_value from wp_postmeta where post_id=" . $postId);
And index them by an meta key
$mapMetas = [];
foreach ($metas as $meta) {
$mapMetas[$meta->meta_key] = $meta->meta_value;
}
Lets say our post ($postId) has an 9 entries in 'resort_pics' with an image uploaded to its 'picture' field, then $mapMetas['resort_pics'] will have an value of 8.
Now the way how repeater fields keys are stored in $mapMetas array, is actually an :
'resort_pics_0_picture' -> itsPostId (5640 for example)
'resort_pics_1_picture' -> itsPostId (5641 for example)
'resort_pics_2_picture' -> itsPostId (5642 for example)
...
'resort_pics_8_picture' -> itsPostId (5648 for example)
Knowing this we can get simply all image urls for "resort_pics"
for ($i = 0; $i < $mapMetas['resort_pics']; $i++) {
$picture = [];
$picture['name'] = $mapMetas['resort_pics_' . $i . '_picture_title'];
$picture['url'] = $mapImages[$mapMetas['resort_pics_' . $i . '_picture']];
$pictures[] = $picture;
}
You may already get to this point, simply from $mapMetas get image ID and using it get an image url from $mapImages.
Lets say 'resort_pics_1_picture' is 'referenced' one (not directly uploaded image), we have its id '5641' but since its not connected to our $postID but to some other post id when it was actually uploaded. Its missing in our $mapImages array, so lets edit this code a bit.
for ($i = 0; $i < $mapMetas['resort_pics']; $i++) {
$picture = [];
$picture['name'] = $mapMetas['resort_pics_' . $i . '_picture_title'];
$picture['url'] = getAttachmentOrItsReferenceUrl('resort_pics_' . $i . '_picture', $mapImages, $mapMetas);
$pictures[] = $picture;
}
We have added an getAttachementOrItsReferenceUrl() method, which will simply first check if we already have this image (all uploaded to this post) and if not will fetch image data by its post id from 'wp_posts' table..
function getAttachmentOrItsReferenceUrl($code, $allAttachmentById, $allPostMetaByKey) {
global $wpdb;
$attachmentUrl = $allAttachmentById[$allPostMetaByKey[$code]];
if($attachmentUrl === null && isset($allPostMetaByKey[$code]) && $allPostMetaByKey[$code] !== '') {
$attachments = $wpdb->get_results("select ID,guid from wp_posts where ID = " . $allPostMetaByKey[$code]);
foreach ($attachments as $image) {
return $image->guid;
break;
}
} else {
return $attachmentUrl;
}
return "";
}
Finnal thoughts :
If you know your fields structure you can build up its key pretty straightforward
For an example
'rooms' repeater which has inside 'room_gallery' repeater which has inside 'image' image field.
'rooms_0_room_gallery_0_image'
--
$mapMetas['rooms'] - number of rooms
$mapMetas['rooms_' . $i . '_room_gallery'] - number of gallery images for room
--
'rooms_' . $i . '_room_gallery_' . $j . '_image'
How heavy is it ? Not really, if you are like me, having only small amount of referenced images you wont even feel it. All we added to load is an one meta query for an post, if no images are referenced that's all, then for every referenced image there is one more query, but its query on ID should be fast. There is a lot of way how to make load less, for example not do query for every single missing image, but do single query at the end with 'where in (id1,id2..)' to have single query to get all, or after fetching new image data, store it in some global array by its id, and check this array before fetching image for next posts (so if one image is stored in 200 posts, and you are fetching all it would make only 1 query), you got the point I leave it just at the ideas since this is bloody long post even without that :)
Hope this will help anybody in future.

Setting location data programmatically

I'm stuck on a problem that I've researched for several days with no luck and the answers here are usually spot on.
I have custom module code that adds a node from form supplied data:
$edit = array();
$edit['uid'] = $user->id;
$edit['name'] = $user->name;
$edit['status'] = 1;
$edit['taxonomy'] = array($term_id);
$edit['title'] = $Title;
$edit['body'] = $body;
etc...
and then saved with:
node_invoke_nodeapi($edit, $nType);
node_validate($edit);
if ($errors = form_get_errors()) {
print_r($errors);
}
$node = node_submit($edit);
node_save($node);
This all works perfectly. But I'm trying to add location data to each node based on a supplied (sanitized) zip field.
I have gmap and location modules installed and working. When I add the zip directly using the drupal content editor it all works. Even the views gmap. So i know versions and mods are all correct.
I've used this:
$location = array(
'country' => 'US',
'postal_code' => $zip,
);
$locationID = location_save($location);
and this:
$location['country'] = "US";
$location['postal_code'] = $zip;
$locationID = location_save($location);
with and without the country element.
And then in the node data init section (above) this:
$edit->locations[0]['lid'] = $locationID;
or
if($locationID) $edit['field_location'][0]['lid'] = $locationID;
or
if($locationID) $edit['location'][0]['lid'] = $locationID;
But none of this works. The submit will go through ok actually but no location data is saved. And no errors thrown.
Any help with this would be greatly appreciated.
I did get this to work, (in case anyone is having the same issue and stumbles upon this), by creating the node first and then adding the location data to the node with:
$locations = array();
$locations[0]['postal_code'] = $zip;
$criteria = array();
$criteria['nid'] = $node->nid;
$criteria['vid'] = $node->vid;
$criteria['genid'] = 'NAME OF NODE TYPE HERE';
location_save_locations( $locations, $criteria );
I guess location_save_locations is the correct way of doing it, not location_save.
Following your approach, as exposed by the wider location_save_locations() at line 4, you can update a location with location_save($locations[$key], TRUE, $criteria).
A few notes:
location_save return the lid of the saved location, or FALSE if the location is considered "empty."
Clean your cache or query the database ( mysql> SELECT * FROM location ORDER BY lid DESC limit 6 ) for a fresh view of the new entity location (ie: node_load data may be cached).
Alternatively, if you are handling an entity with a location field, you may try something cleaner like:
// Updated the location.
$node->field_location['und'][0] = $location;
// Save the node.
node_save ($node);

Resources