elfinder - copy hardlinks - nginx

We move alot of big files and this work flawlessly because this is done in a instance without copying the file. (just moving the pointer)
But sometimes we need acces to the same file from multiple locations in the filesystem, today we copy the file but this takes extremely long time and is storage consuming, this is expected since the file is copied to another place on disk. So we can't complain on this :)
But in our workflow we actually don't have to have multiple files, multiple pointers to the same file is enough. Therefore the hardlink model is perfect choice for us. This would be extremely usefull if it was possible to change the behavior on the copy feature in elfinder, Does anyone know if it is possible. ? :)

It is made possible by extending the elFinderVolumeLocalFileSystem class.
class elFinderVolumeMyLocalFileSystem extends elFinderVolumeLocalFileSystem
{
protected function _copy($source, $targetDir, $name) {
$target = $this->_joinPath($targetDir, $name);
if (! $ret = link($source, $target)) {
return parent::_copy($source, $targetDir, $name);
}
return $ret;
}
}
$opts = array(
'locale' => '',
'roots' => array(
array(
'driver' => 'MyLocalFileSystem',
'path' => '/path/to/files/',
'URL' => 'http://localhost/to/files/'
)
)
);
// run elFinder
$connector = new elFinderConnector(new elFinder($opts));
$connector->run();

Related

Populate Image & Image List Fields with Bolt CMS Programatically

I am attempting to create entries programaticaly in Bolt 4. I have managed to create basic text entries fine which covers the majority of fields I need to fill in however unsure how to go about image and image list types.
$content->setFieldValue('name', 'Test Name');
Works fine for most fields as stated but images field types looks like below in database and am unsure what the "Bolt / Symfony / Doctrine" way of generating below is:
{"media":11,"filename":"entity\/year\/month\/image.jpg","alt":"","0":""}
Which looks like some JSON formatted to contain a media ID, file path and an alt attribute. I'm guessing image lists are similar but with multiple of above but hoping there is a function I can use to generate this output as unsure how I would grab media ID etc.
Am assuming I may need to upload a file temporarily from an external URL and provide this to some function however cannot find any examples. Any help would be much appreciated.
Not quite sure on this but will answer anyway as it works and may help someone else, but would be good to clear some of the bits up.
Example of an Image:
//not sure what goes in media here but blank seemed to not work here but a 7 did as looked at example in database however worried this is wrong and should be an Id
$image = array('media' => '7', 'filename' => $filename, 'alt' => $image['Alt'], '0' => '');
$imageJSON = json_encode($image);
$content->setFieldValue('image', $image);
Example of an Image List:
$images = array();
foreach ($images as $image) {
$ImageId = $image['id'];
//images may be already on system but I had to download them here
if ($fileName = $this->downloadImage($propertyFile->{'url'}->__toString(), $ImageId)) {
//not sure what goes in media here but seemed to work blank for imagelist type and unsure what the 0 is on the end either
$image[] = array('media' => '', 'filename' => $filename, 'alt' => $image['Alt'], '0' => ''); }
}
$imageJSON = json_encode($image);
$content->setFieldValue('gallery', $image);

Silverstripe 4 Upgrade - Unversioned DataObjects in ModelAdmin lose their image objects

Been pulling my hair out over this for a day and exhausted my google foo. I have inherited a Silverstripe 3.4 site that we have upgraded to 4.4. But something odd has been going on with certain images after running MigrateFilesTask.
I think this is something to do with a file being attached to an unversioned objects that are accessed via ModelAdmin. But I have not been able to find a definitive solution.
Code for this object below. Problems experienced are under it.
<?php
use SilverStripe\Assets\Image;
use gorriecoe\Link\Models\Link;
use SilverStripe\Security\Member;
use SilverStripe\Control\Controller;
use SilverStripe\View\Parsers\URLSegmentFilter;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\FieldGroup;
use gorriecoe\LinkField\LinkField;
use SilverStripe\TagField\TagField;
use SilverStripe\ORM\DataObject;
use SilverStripe\SelectUpload\SelectUploadField;
class Person extends DataObject
{
private static $db = array(
'FirstName' => 'Varchar(128)',
'LastName' => 'Varchar(128)',
'Role' => 'Varchar(128)',
'DirectDialNumber' => 'Varchar(128)',
'Email' => 'Varchar(128)',
'CellphoneNumber' => 'Varchar(30)',
'DirectDial' => 'Varchar(30)',
'UrlSegment' => 'Varchar(255)',
'Blurb' => 'HTMLText',
'SortOrder' => 'Int'
);
private static $has_one = array(
'Image' => Image::class,
'Office' => 'Office',
'LinkedIn' => Link::class,
'Member' => Member::class
);
private static $many_many = array(
'Interests' => 'Section'
);
private static $belongs_many_many = array(
'ElementCollection' => 'ElementCollection'
);
static $sort_fields = array(
'FirstName' => 'First name',
'LastName' => 'Last name',
'Role' => 'Role'
);
private static $summary_fields = array(
'Name' => 'Name',
'Role' => 'Role',
'Office.Name' => 'Office'
);
private static $searchable_fields = array(
'FirstName',
'LastName',
'Role'
);
// For use with the ElementCollection
public static $templates = array(
'ElementPeople' => 'Default',
'ElementPeopleAlternative' => 'Alternative'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName( ['SortOrder', 'ElementCollection', 'FirstName', 'LastName', 'Interests'] );
$firstname = TextField::create('FirstName', 'First name');
$lastname = TextField::create('LastName', 'Last name');
$fields->addFieldsToTab('Root.Main', FieldGroup::create($firstname, $lastname)->setTitle('Name')->setName('Name'), 'Role');
$image = UploadField::create('Image', 'Photo');
$image->setFolderName('Uploads/People');
$image->setCanSelectFolder(false);
$fields->addFieldToTab('Root.Main', $image);
$linkedin = LinkField::create('LinkedIn', 'LinkedIn', $this);
$fields->addFieldToTab('Root.Main', $linkedin);
$interests = TagField::create(
'Interests',
'Interests Tags',
Section::get(),
$this->Interests()
)->setShouldLazyLoad(true)
->setCanCreate(false);
$fields->addFieldToTab('Root.Main', $interests);
return $fields;
}
public function onBeforeWrite()
{
$count = 1;
$this->UrlSegment = $this->generateURLSegment();
while (!$this->validURLSegment()) {
$this->UrlSegment = preg_replace('/-[0-9]+$/', null, $this->UrlSegment) . '-' . $count;
$count++;
}
parent::onBeforeWrite();
}
}
Problem #1 is after running MigrateFileTask, ALL existing images attached to instances of this class get moved from /assets/Uploads/People to /assets/.protected/Uploads/People. The confusing part here is that there is one other class called Company that is structurally near identical, yet images for that remain in /assets/Uploads/Companies as expected.
Problem #2 is if I create a new Person object and attach an image, that image is in Draft, sitting in /assets/.protected/Uploads/People with no method of actually publishing it. Meanwhile, if I do the same with a Company object, the image is still in Draft, but I can see it in the CMS.
Can someone offer some guidance on the above? At this point I'd be happy to just be able for images to be published when the DO is and I'll manually go through every single Person record and hit save myself just to get this upgrade over the line.
You should be able to fix this issue by adding the image to your DataObejct's owns property. Basically add this:
private static $owns = [
'Image'
];
Basically owns tells a DataObject which objects to publish when it is saved:
More info in the docs: https://docs.silverstripe.org/en/4/developer_guides/model/versioning/#defining-ownership-between-related-versioned-dataobjects
The cause of issue #1 was found. Leaving this here in case it helps someone in future:
The database table File has a row for every File and Folder in the system. This table has a column called "CanViewType". It exists in both Silverstripe 3 and 4.
For the particular Folder that was causing trouble during the Migration process, I found it was the only one with that column set to "OnlyTheseUsers". The rest were set to "Inherit". This was the state of the table before the upgrade.
I'm unsure how or by what mechanism that row is ever changed, but the solution to problem #1 was to manually change that field to "Inherit" before running FileMigrationTask.
Issue #2 persists, but it looks like there are two very different issues here.
OK. So sorted problem #2 finally (see other answer for solution to #1), but it's put a massive dent in our confidence in Silverstripe and sparked a meeting here.
Code for future readers:
In your unversioned DataObject, add this. In this case, my file object is called "Image". If you had more than one file to publish on save, you would have to add one of these IF blocks for each.
public function onAfterWrite()
{
if ($this->Image()->exists() && !$this->Image()->isPublished()) {
$this->Image()->doPublish();
}
parent::onAfterWrite();
}
SIDENOTE:
This Object/File relationship really is a strange design choice. Are there actually any situations where you would want to attach a file or image to a data object and NOT publish that file at the same time as you save/publish the object/page? Developers even need to explicitly define that on Versioned objects using $owns - which I'm happy to bet that most developers have to add more times than NOT. Which should really tell a us something is around wrong way.
Adding an image to a CMS system shouldn't be hard. It should take reading basic docs at the most. Not Googling, deep API doc dives (which don't answer much) or posting on StackOverlfow (where no one really knows the answer) over three days. It's an image. A core function of the product.
I've been working with SS since v2.4 and seen all the hard lessons learned to get to v4. But this appears to be a textbook case of the simple being over-engineered.

Get Uploaded File's Original Name

$request = $event->getRequest();
print_r($request->files);die;
gives me
Symfony\Component\HttpFoundation\FileBag Object
(
[parameters:protected] => Array
(
[files] => Array
(
[0] => Symfony\Component\HttpFoundation\File\UploadedFile Object
(
[test:Symfony\Component\HttpFoundation\File\UploadedFile:private] =>
[originalName:Symfony\Component\HttpFoundation\File\UploadedFile:private] => Chrysanthemum.jpg
[mimeType:Symfony\Component\HttpFoundation\File\UploadedFile:private] => image/jpeg
[size:Symfony\Component\HttpFoundation\File\UploadedFile:private] => 879394
[error:Symfony\Component\HttpFoundation\File\UploadedFile:private] => 0
[pathName:SplFileInfo:private] => /tmp/phpmUl54W
[fileName:SplFileInfo:private] => phpmUl54W
)
)
)
)
I'm trying to get at the value for 'originalname' i.e "Chrysanthemum.jpg" without resorting to a loop, but I can't seem to find the right syntax
Using the 1UP file uploader, but I dont think that's important
When you upload files you get UploadFile (API link) objects (basically the wrappers of array).
$this->getRequest()->files[0]->getClientOriginalName();
Can't try this now but you might need to do this instead:
$this->getRequest()->files['name_of_file_field_in_post']->getClientOriginalName();
where you would replace name_of_file_field_in_post with your form field's name.
2015 Update:
$request->files->get('your-file-name')->getClientOriginalName();
your-file-name for me was just file.
This will probably help too:
https://github.com/1up-lab/OneupUploaderBundle/issues/21
This wound up working for me, I guess the OneUp Class handles it a bit differently
use Oneup\UploaderBundle\Event\PostPersistEvent;
class UploadListener
{
public function onUpload(PostPersistEvent $event)
{
$request = $event->getRequest();
$original_filename = $request->files->get('blueimp')->getClientOriginalName();
}
}
Relevent Frontend
<input id="fileupload" type="file" name="blueimp" data-url="{{ oneup_uploader_endpoint('images') }}" multiple />
I'm using symfony 4,
I wanted to get the files original name. What worked for me is:
$form = $this->createForm(SomeType::class, $object);
if($form->isSubmitted() && $form->isValid()) {
$fileName = $object->getImage()->getClientOriginalName();
}
In my case getImage() is the getter for the name of the file upload field. So that would change to your relevant getter function for the file upload field.

Programmatically insert a file in Drupal's file system?

I am grabbing a file using CURL, and want to save it locally, but so it plugs into Drupal's file system. Would I have to do a manual insert or is there a better way? The closest I could find was:
function image_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items);
But I don't know how to use this. Any better suggestions?
There are a couple of ways but the easiest would be to use file_save_upload():
$source = '/path/to/file.ext';
$dest = 'public://uploads/'; // Or whatever
$file = file_save_upload($source, array(), $dest, FILE_EXISTS_RENAME);
if ($file) {
// file_save_upload marks the file as temporary, we need to mark as permanent or it will be cleared from the database in a few hours
$file->status = FILE_STATUS_PERMANENT;
file_save($file);
}
This just about drove me out of my mind and in trying to figure out a simple way to circumvent the form API expectations I came across this question. Clive's answer was the beginning of how I figured it out because it looks like you can provide a source as the first arg for file_save_upload() but as it turns out, you cannot. At least not in the current drupal 7.30.
I checked the link he provided and deconstructed the function. Basically, even if you pass a full source path it still looks for the first arg in the $_FILES array way down the path. Who knows why, but what a dumb idea. So I did this:
//after verifying all post fields are set...
$node = new stdClass();
$node->type = 'user_files';
node_object_prepare($node);
$node->title = $_POST['title'];
$node->author = 2; //hardcoded because only admin uses this form
$node->uid = 2;
$node->language = LANGUAGE_NONE;
$node->body[$node->language][0]['value'] = $_POST['body'];
$node->body[$node->language][0]['summary'] = text_summary($_POST['body']);
$node->body[$node->language][0]['format'] = 'filtered_html';
$node->field_first_name[$node->language][0]['value'] = $_POST['first_name'];
$node->field_last_name[$node->language][0]['value'] = $_POST['last_name'];
node_save($node);
if(isset($_FILES['file']['tmp_name']) && $_FILES['file']['name'] != '')
{
//upload file
$file = new stdClass();
$file->uid = 2;
$file->status = 0;
$file->filename = trim(drupal_basename($_FILES['file']['name']), '.');
$file->uri = $_FILES['file']['name'];
$file->filemime = file_get_mimetype($file->filename);
$file->filesize = $_FILES['file']['size'];
$file->filename = file_munge_filename($file->filename, 'jpg jpeg gif png doc docx pdf');
$file->destination = file_destination('private://' . $file->filename, FILE_EXISTS_RENAME);
$file->uri = $file->destination;
if (!drupal_move_uploaded_file($_FILES['file']['tmp_name'], $file->uri)) {
return false; //set errors or do whatever you want on error
}
else
{
drupal_chmod($file->uri);
$existing_files = file_load_multiple(array(), array('uri' => $file->uri));
if (count($existing_files)) {
$existing = reset($existing_files);
$file->fid = $existing->fid;
}
if ($file = file_save($file)) {
// Add file to the cache.
$node->field_file[$node->language][0]['uri'] = 'private://'.$file->filename;
$node->field_file[$node->language][0]['fid'] = $file->fid;
$node->field_file[$node->language][0]['display'] = 1;
$node->field_file[$node->language][0]['description'] = 'User uploaded file';
node_save($node);
//do we notify someone?
}
}
}
What this does is creates a node of a specified type. In this case, user_files, then if a file is uploaded, add the file to the media table, or file table or whatever it's called. Then adds the association to the newly created node.
Is it ugly? Yeah. Why didn't I use the internal drupal form API? Shut up, that's why. We don't always have a choice, so when a client asks for a basic form that just emails them, it is quick and easy to make a simple field that sends through a mail chimp API or something. Then later they add that they want files. Then later they want it to add to the drupal back end and suddenly you have a snowball of horror that would have been a lot easier if it was done with the form API to begin with. I don't know who started it, but I had to end it and I did it with this ghastly hack. Hope it helps you or whoever else is currently spinning in the drupal vortex of horror.
I was able to do the following to add a file from the public:// Drupal folder (typically sites/default/files or sites/DOMAIN/files) using file_save.
$uri = 'public://filename.pdf';
$file = file_save((object) array(
'filename' => basename($uri),
'uri' => $uri,
'status' => FILE_STATUS_PERMANENT,
'filemime' => file_get_mimetype($uri),
));
This adds the appropriate entry into the Drupal core file_managed table. If you have the File Entity module installed, an entry is created there as well.
The returned file object will include the database ID as $file->fid.

Multiple concurrent database connections in drupal 7

I'm writing a wrapper class for my drupal 7 site which lets me connect to and query my phpbb database.
When connecting to an external data source (as per drupal documentation) you have set the active db, run the query, then set the active db back to the default.
e.g.
db_set_active('phpbb');
$result = db_query($sql,$args,$opts);
db_set_active();//back to default
But is there any way to use drupal's database wrapper to create a brand new connection which can be permanently set to the new database without having to do this switching back-and-forth nonsense? surely we can handle connections to multiple databases concurrently.
I have done some googling but haven't found anybody trying to do this as yet.
Typical. 5 minutes after posting i figure it out... so, for future googlers:
Basically, you don't use db_query, instead you run the query on your connection without setting the active link.
you can figure this out by looking at how db_query works:
http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_query/7
So it looks like this:
$target='default';
$key = 'phpbb';
$phpbb = Database::getConnection($target,$key);
$result = $phpbb->query($sql,$args,$opts);
This assumes you have a database configured in your settings.php like the following:
$databases['phpbb']['default'] = array(
'driver' => 'mysql',
'database' => 'forum',
'username' => 'username',
'password' => 'password',
'host' => 'mysql.host.com',
'prefix' => 'phpbb3_'
);
Database::addConnectionInfo() perhaps?
This method allows the addition of new connection credentials at
runtime. Under normal circumstances the preferred way to specify
database credentials is via settings.php. However, this method allows
them to be added at arbitrary times, such as during unit tests, when
connecting to admin-defined third party databases, etc.
If the given key/target pair already exists, this method will be
ignored.
The definition for getConnection cites a different order for arguments than used above.
function getConnection($target = 'default', $key = NULL)
This is sadly different from Database::addConnectionInfo() which is
public static function addConnectionInfo($key, $target, $info)
Also, on DB_select, the $key is not a parameter, though it is in the options array:
function db_select($table, $alias = NULL, array $options = array()) {
if (empty($options['target'])) {
$options['target'] = 'default';
}
return Database::getConnection($options['target'])->select($table, $alias, $options);
}
while
final public static function getConnection($target = 'default', $key = NULL) {
so this implies that the 'master' or 'slave' or 'default' is always used as set, but not the key to the alternative database/schema, requiring the db_set_active('...'); and db_set_active(); around the db_select.
Since calls to other dbs can easily be required within the processing of the db_select (such as devel calls or calls in alters), this is inflexible design. Changing this call:
return Database::getConnection($options['target'])->select($table, $alias, $options);
to add the Key parameter (it is already spec'd as an argument!!) is needed but insufficient so far as I can now see.

Resources