Dropbox account as Wordpress uploads folder - php

As I'm developing a site with the potential of holding a huge number of images I'm wondering if I can set the standard WordPress uploads folder to a Dropbox account?
And if so, how do I do that?
It would be great when I can implement it in a maner that even WordPress don't know it is a 'remote' folder. Media upload should work the same way as in a native WordPress setup.
I've read about the possibility to use another folder instead of wp-content/uploads but I could not find any info about using Dropbox for this.

Yes, you can do it. As long as you keep the same structure on Dropbox and save Dropbox shareable links as meta data for both the original file and generated sizes, a simple yet fully working setup for it would be something like the following, by using thephpleague/flysystem with their Dropbox Adapter:
Step 1
Add a file called composer.json to the root of your theme with this content:
{
"require": {
"league/flysystem": "^1",
"league/flysystem-dropbox": "^1"
}
}
Step 2
Install Composer by following these instructions
Step 3
Using the command line on your terminal/console, go to your theme directory and run:
composer install -o
Step 4
Create Dropbox App here.
I suggest that you select "App folder" as the type of access.
A directory matching the name of you app will be created on the "Apps" directory at the root of your Dropbox account; This will be your "uploads" directory on Dropbox.
Step 5
Go to your app admin page and generate a new access token.
Save the access token somewhere and also copy the "App secret"
Step 6
Add the following to your functions.php:
use League\Flysystem\AdapterInterface;
use League\Flysystem\Adapter\Local as LocalAdapter;
use League\Flysystem\Dropbox\DropboxAdapter;
use League\Flysystem\Filesystem;
use League\Flysystem\MountManager;
use Dropbox\Client as DropboxClient;
// Autoload vendors
require_once __DIR__ .'/vendor/autoload.php';
/**
* Class that will handle uploading to Dropbox
*
*/
class SO40950172Filesystem {
/**
* Contains several mounted filesystems
*
* #var League\Flysystem\MountManager object
*/
protected $filesystem;
/**
* Contains Dropbox client
*
* We need this accessible to create shareable links
*
* #var Dropbox\Client object
*/
protected $dropbox_client;
/**
* Instantiates this class
*/
public function __construct() {
// Get WordPress uploads directory info
$uploads_info = wp_upload_dir();
// Create Local filesystem
$local_adapter = new LocalAdapter($uploads_info['basedir']);
$local_fs = new Filesystem($local_adapter, [
'visibility' => AdapterInterface::VISIBILITY_PUBLIC
]);
// Create Dropbox filesystem
$this->dropbox_client = new DropboxClient($app_access_token, $app_secret);
$dropbox_adapter = new DropboxAdapter($this->dropbox_client, '/');
$dropbox_fs = new Filesystem($dropbox_adapter, [
'visibility' => AdapterInterface::VISIBILITY_PUBLIC
]);
// Set filesystem manager
$this->filesystem = new MountManager([
'local' => $local_fs,
'dropbox' => $dropbox_fs
]);
}
/**
* Uploads file to Dropbox
*
* #param string $path Path to file
* #return object Current object
*/
public function uploadToDropbox($path)
{
// Normalize path
$path = static::getNormalizedPath($path);
// Get content from the local file
$content = $this->read("local://$path");
// Push file to Dropbox
$this->put("dropbox://$path", $content);
return $this;
}
/**
* Deletes file from Dropbox
*
* #param string $path Path to file
* #return object Current object
*/
public function deleteFromDropbox($path)
{
// Normalize path
$path = static::getNormalizedPath($path);
// Delete file from Dropbox
$this->delete("dropbox://$path");
return $this;
}
/**
* Returns the unique identifier path section of a Dropbox URL
*
* #param string $path Path to file
* #return string Dropbox URL unique identifier
*/
public function getDropboxUrlID($path)
{
// Normalize path
$path = static::getNormalizedPath($path);
// Get unique link
$url = $this->dropbox_client->createShareableLink("/$path");
// Parse URL to retrive its path
$url_info = parse_url($url);
$url_path = $url_info['path'];
// Remove "s/" section and file name from the URL path
$id = str_replace(['s/', basename($path)], '', $url_path);
// Return Dropbox unique identifier for this file URL
return trim($id, '/');
}
/**
* Returns clean & relative paths
*
* #param string $path Raw path
* #return string Parsed path
*/
public static function getNormalizedPath($path)
{
// Get WordPress uploads directory info
$uploads_info = wp_upload_dir();
// Remove uploads base path so that we end up
// with the "/YYYY/MM/filename.extension" format
$path = str_replace($uploads_info['basedir'], '', $path);
// Remove uploads base url so that we end up
// with the "/YYYY/MM/filename.extension" format
$path = str_replace($uploads_info['baseurl'], '', $path);
// Remove forward slashes on both ends
$path = trim($path, '/');
// Return path
return $path;
}
/**
* Making sure all calls go to $this->filesystem
*
* #param string $name Method name
* #param array $args Method arguments
* #return mixed
*/
public function __call($name, array $args)
{
if (method_exists($this->filesystem, $name))
throw new \Exception("\League\Flysystem\MountManager doesn't have \"$name\" method");
return call_user_func_array([$this->filesystem, $name], $args);
}
}
// Manipulate media URLs sitewide
add_filter('wp_get_attachment_url', 'so_40950172_get_dropbox_url', 9, 2);
function so_40950172_get_dropbox_url($absolute_url, $post_id) {
// Get normalized path
$path = SO40950172Filesystem::getNormalizedPath($absolute_url);
// Get only the filename
$path = basename($path);
// Get Dropbox URL unique ID
$id = get_post_meta($post_id, 'dropbox_id_'. $path, true);
// Return absolute URL
return $id ? "https://dl.dropboxusercontent.com/s/$id/$path/?dl=0" : $path;
}
// Upload new and updated files to Dropbox
add_filter('wp_update_attachment_metadata', 'so_40950172_upload_to_dropbox', 9, 2);
function so_40950172_upload_to_dropbox($data, $post_id) {
// Get filesystem
$fs = new SO40950172Filesystem();
// Upload original file to Dropbox
$fs->uploadToDropbox($data['file']);
// Add Dropbox URL unique ID to meta data
add_post_meta($post_id, 'dropbox_id_'. basename($data['file']), $fs->getDropboxUrlID($data['file']));
// Upload intermediate image sizes
if (isset($data['sizes']) && $data['sizes']) {
// Get year and month prefix (e.g /YYYY/MM) from original file
$base_path = dirname($data['file']);
// Loop through all sizes
foreach ($data['sizes'] as $size_name => $size_data) {
// Set path for current size
$size_path = $base_path .'/'. $size_data['file'];
// Upload size to Dropbox
$fs->uploadToDropbox($size_path);
// Add Dropbox URL unique ID to meta data
add_post_meta($post_id, 'dropbox_id_'. basename($size_path), $fs->getDropboxUrlID($size_path));
}
}
return $data;
}
// Delete Dropbox file
add_filter('wp_delete_file', 'so_40950172_delete_dropbox_file');
function so_40950172_delete_dropbox_file($absolute_path) {
// Get filesystem
$fs = new SO40950172Filesystem();
// Delete file from Dropbox
$fs->deleteFromDropbox($absolute_path);
}
Step 7
On the code your just pasted into functions.php:
Replace $app_access_token with the Dropbox app access token you generated
Replace $app_secret with the Dropbox app secret
NOTES
The original file and generated sizes will also be saved locally, but you don't need to worry about them. You can even delete the local file after confirmation of a successful upload, if you want and/or care about disk space.
I also tested the built-in image editor and it worked without any issues.
If you ever need to move things on the Dropbox side and since there is no info saved on the database (this is just fine), all you need to do is to update the functions above.
Apparently you can mirror the WordPress structure on Dropbox, but you can't simply link to them using a base URL and the WordPress uploads structure to get the URLs, you really need to get the shareable link for each original file and generated sizes and store something about them as metadata. On the code above I chose to only store the unique part of the URL as metadata, which really is the only unique thing about them.
I know this is off-topic, but I would recommend either AWS S3 or Google Cloud Storage because you can access your files with the exact same file structure you have on Dropbox. No need to save anything as meta data.

that's an interesting idea, but WordPress uses DB relations in order to manage the uploaded images - so i think you must handle the images through the media uploader. TL:DR; - you can't.

Related

Prestashop 1.6 - How I can get the admin directory name dynamically?

I want to put my module in Prestashop market place, and make it standard everyone can use it. This plugin needs to know the admin directory name dynamically to do its service.
I have searched on the Internet a lot of times, but I didn't find a solution to this issue.
You can use _PS_ADMIN_DIR_ witch is set in [your_admin_dir]/index.php:
if (!defined('_PS_ADMIN_DIR_')) {
define('_PS_ADMIN_DIR_', getcwd());
}
This constant is only set when you're on an admin context. Your FrontOffice doesn't have knowledge of this directory and should not for obvious security reason.
There's also a getAdminLink method in class Link:
/**
* Use controller name to create a link
*
* #param string $controller
* #param bool $with_token include or not the token in the url
* #return string url
*/
public function getAdminLink($controller, $with_token = true)
{
$id_lang = Context::getContext()->language->id;
$params = $with_token ? array('token' => Tools::getAdminTokenLite($controller)) : array();
return Dispatcher::getInstance()->createUrl($controller, $id_lang, $params, false);
}
Example:
// Here we create a link to the dashboard without token
$this->context->link->getAdminLink(Tab::getClassNameById(1), false)

Laravel 5 & Mamp - stores weird path when uploading a file

/**
* Store a newly created resource in storage.
*
* #param Request $request
* #return Response
*/
public function store(AdminProgramRequest $request)
{
$request = $this->move_image_file($request, 'thumbnail');
$request = $this->move_image_file($request, 'korean_brochure');
$request = $this->move_image_file($request, 'english_brochure');
$request = $this->move_image_file($request, 'chinese_brochure');
Program::create($request->all());
return redirect('dashboard/program_category/'.$request->program_category_id.'/edit');
}
private function move_image_file($request, $input_name)
{
if($request->hasFile($input_name)){
$filename = $request->file($input_name)->getClientOriginalName();
$request->file($input_name)->move(
base_path() . '/public/pdf/program_category/' . $request->program_category_id . '/' , $filename
);
$request->merge([
// I think this is where
// /private/var/folders/_l/9jdb6p2s3ms4vh8jv0mp_43w0000gn/T/phphi95xw
// stuff gets stored in $request even if
// $filename has the uploaded filename for sure.
$input_name => $filename
// This hardcode also works in the same way and stores weird path in db... WHY!?
// $input_name => 'whats_going_on.jpg'
]);
}
return $request;
}
This is driving me nuts. I'm pretty sure that $filename has the filename of the uploaded file.
I've double checked if $filename has a correct value. Yes.
I've hardcoded and given a randome filename to $filname. Still doesn't work but stores the weird path thing.
I've tested if files are being uploaded correctly. and Yes.
I've checked view files. Since #3 is working, of course Yes.
Now, I'm suspecting MAMP.
Has anyone experienced the same issue with Laravel5 and Mamp on OSX?

Sonata Media Bundle - How to extend FormatThumbnail.php

The Sonata Media Bundle you have the thumbnail property on a provider in the config where you can specify either
sonata.media.thumbnail.format
sonata.media.thumbnail.liip_imagine
This all fine and the sonata.media.thumbnail.format one works fine for everything I want to achieve. My problem comes in with what happens within these files.
In the FormatThumbnail.php there is a function called generatePublicUrl where they generate the url of the media file and also the name of the formatted file. They use the media id within the name or url. If you have private files not everyone must be able to see this causes a problem with it is easy to manipulate the id to another id.
I know the public files that will be served will always stay public so if the url can be guessed the user will access the file. For this specific reason I want to try and replace that id with the unique reference that the bundle uses before they create the actual formatted files as this will not be as easy to just change.
I am aware that there are still risks of leaking out data.
I want to change this
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getId(), $format, $this->getExtension($media));
}
return $path;
}
to this
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getProviderReference(), $format, $this->getExtension($media));
}
return $path;
}
How do I override the file that the bundle just picks up the change?
I have followed the steps on Sonata's site on how to install and set up the bundle using the easy extends bundle. I have my own Application\Sonata\MediaBundle folder that is extending the original Sonata\MediaBundle.
For installation related information have a look through the documentation(https://sonata-project.org/bundles/media/master/doc/reference/installation.html)
However I tried to create my own Thumbnail folder and creating a new FormatThumbnail.php as follows
<?php
namespace Application\Sonata\MediaBundle\Thumbnail;
use Sonata\MediaBundle\Model\MediaInterface;
use Sonata\MediaBundle\Provider\MediaProviderInterface;
use Sonata\MediaBundle\Thumbnail\FormatThumbnail as BaseFormatThumbnail;
class FormatThumbnail extends BaseFormatThumbnail
{
/**
* Overriding this to replace the id with the reference
*
* {#inheritdoc}
*/
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getProviderReference(), $format, $this->getExtension($media));
}
return $path;
}
}
But the bundle still generates everything using the id instead of the reference. Is there a more specific way to achieve extending this file and overriding the function?
After looking at a few different bundles and after looking in code I found that they physically have a parameter which is set to use Sonata\MediaBundle\Thumbnail\FormatThumbnail.
The solution is to override the parameter in the config aswell.
#As top level classification in app/config/config.yml
parameters:
sonata.media.thumbnail.format: Application\Sonata\MediaBundle\Thumbnail\FormatThumbnail
This way the custom FormatThumbnail class is injected everywhere it will be used within the bundle.

Symfony remove cached images using liipImagine

I'm trying to remove cached images (which are created with LiipImagineBundle) when the source image is deleted or updated. I have already found out that it could be done using CacheManager ( https://github.com/liip/LiipImagineBundle/issues/132 )
The problem is that I can't figure it out how to use it exactly. What else do I need to add (like libraries) to my code despite these three lines:
$cacheManager = $this->get('liip_imagine.cache.manager');
$cacheManager->resolve($this->getRequest(),$pngPath,$filter);
$cacheManager->remove($pngPath, $filter);
I believe there should be something like
$cacheManager = new CacheManager();
I would really appreciate if anyone could explain me how to do that in more detail.
So, for example in your controller:
/**
* Remove an image in the cache based on its relative path and the filter applied to it
*
* #param string $path
* #param string $filter
*
* #return void
*/
protected function removeCachedImageAction($path, $filter)
{
$cacheManager = $this->container->get('liip_imagine.cache.manager');
// Remove the cached image corresponding to that path & filter, if it is stored
if ($cacheManager->isStored($path, $filter)) {
$cacheManager->remove($path, $filter);
}
}
/**
* An action that doesn't do much except testing the function above
*
* #param Request $request
*
* #return void
*/
protected function whateverAction(Request $request)
{
$path = //... probably from the request
$filter = //... probably from the request
// Remove the cached image
$this->removeCachedImage($path, $filter);
// ...
}
As you can see in the CacheManager, the function that you'd like to use is:
public function remove($paths = null, $filters = null){ ... }
If $paths is null, the function assumes that you want to remove the cached images for ALL PATHS that have been resolved with the $filters provided.
If $filters is null, the function assumes that you want to remove the cached images corresponding for the $paths provided and that have previously been resolved with ALL FILTERS.
If $paths and $filters are null, the function assumes that you want to remove the cached images corresponding to ALL PATHS and for ALL FILTERS. Basically ALL CACHED IMAGES.

How to use Minify PHP with YUI compressor?

I would like to use YUI compressor with minify PHP rather than the default JSmin. Does anyone have experience setting this up?
Right now I am using the groupsConfig.php to combine the JS.
return array(
'jsAll' => array('//contenido/themes/bam/assets/js/jquery.js', '//contenido/themes/bam/assets/js/modernizr.js','//contenido/themes/bam/assets/js/imgpreload.js', '//contenido/themes/bam/assets/js/imgpreload.js', '//contenido/themes/bam/assets/js/history.js','//contenido/themes/bam/assets/js/ajaxify.js', '//contenido/themes/bam/assets/js/isotope.js'),
'jsHome' => array('//contenido/themes/bam/assets/js/easing.js','//contenido/themes/bam/assets/js/scrollable.js', '//contenido/themes/bam/assets/js/home.js'),
'cssAll' => array('//contenido/themes/bam/bam.css'),
);
As it says on the homepage:
Uses an enhanced port of Douglas Crockford's JSMin library and custom classes to minify CSS and HTML
I have the following code in config.php, but I get a 500 error when trying to view the combined js file:
function yuiJs($js) {
require_once '/lib/Minify/YUICompressor.php';
Minify_YUICompressor::$jarFile = '/lib/yuicompressor-2.4.2.jar';
Minify_YUICompressor::$tempDir = '/temp';
return Minify_YUICompressor::minifyJs($js);
}
$min_serveOptions['minifiers']['application/x-javascript'] = 'yuiJs';
It also appears that there are several lines in lib/Minify/YUICompressor.php that need to be configured, and I'm not sure if I'm doing it right:
class Minify_YUICompressor {
/**
* Filepath of the YUI Compressor jar file. This must be set before
* calling minifyJs() or minifyCss().
*
* #var string
*/
public static $jarFile = '../yuicompressor-2.4.2.jar';
/**
* Writable temp directory. This must be set before calling minifyJs()
* or minifyCss().
*
* #var string
*/
public static $tempDir = '../../temp/';
/**
* Filepath of "java" executable (may be needed if not in shell's PATH)
*
* #var string
*/
public static $javaExecutable = 'java';
I had the same problem on windows. It seems jar file needs to be executable in order to run yui compressor. So, i have to remove excutable check from YUICompressor.php
#132
private static function _prepare()
{
if (! is_file(self::$jarFile)) {
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.');
}
// if (! is_executable(self::$jarFile)) {
// throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not executable.');
// }
if (! is_dir(self::$tempDir)) {
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.');
}
if (! is_writable(self::$tempDir)) {
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.');
}
}
and that works fine.

Categories