-
Notifications
You must be signed in to change notification settings - Fork 0
/
asset_saver.module
222 lines (181 loc) · 5.38 KB
/
asset_saver.module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
<?php
namespace {
use Drupal\asset_saver;
/**
* Implements @see hook_flush_caches()
*
* Well, kind of. This is supposed to return an array of cache tables to be
* cleared but there aren't any relevant cache tables to be cleared so we just
* do the clearing ourselves.
*
* @return array
* An empty array.
* @author Donovan Mueller <zotobi@gmail.com>
**/
function asset_saver_flush_caches()
{
asset_saver\_clear_all_files();
return array();
}
/**
* Implements @see hook_admin_menu_cache_info()
*/
function asset_saver_admin_menu_cache_info()
{
$caches['asset_saver'] = array(
'title' => t('Asset Saver saved files')
,'callback' => 'Drupal\\asset_saver\\_clear_all_files'
);
return $caches;
}
} // END namespace global
namespace Drupal\asset_saver {
const URI_PREFIX = 'public://asset_saver'
, MAX_AGE = 86500 # 1 day
, CLEAR_FILES_LOCK = 'asset_saver:flush_cache';
/**
* Saves a remote asset to a file and returns the path.
*
* @param string $url
* The URL to the remote asset file.
* @return The path to the local file or `FALSE` on failure.
* @author Donovan Mueller <zotobi@gmail.com>
**/
function url_to_file($url)
{
if (strpos($url, '//') === 0) {
$protocol = !empty($_SERVER['HTTPS']) ? 'https' : 'http';
$url = "$protocol:$url";
}
$uri = _url_to_file_uri($url);
$filename = drupal_realpath($uri);
lock_wait(CLEAR_FILES_LOCK);
# Don't want to have the file disappear out from under the user or the
# creation of a new file when the system is trying to remove all of them.
# Does the file already exist?
if ($filename and file_exists($filename)
and (time() - filemtime($filename)) < MAX_AGE)
return $uri;
## Save the file
$lock_name = 'asset_saver:save_file:'.base64_encode($uri);
if (!lock_acquire($lock_name))
# Hopefully, the other request that is trying to save this file will be
# finished by the time the client requests the file.
return $uri;
if (file_exists($filename)) {
# Looks like a different request was able to create this file just as this
# request tried to acquire the lock.
lock_release($lock_name);
return $uri;
}
$contents = file_get_contents($url);
if ($contents === false) {
lock_release($lock_name);
watchdog('asset_saver', 'Failed downloading asset "%url"'
,array('%url' => $url), WATCHDOG_ERROR);
return file_exists($filename) ? $uri : false;
# The old file is better than no file.
}
# Uncompress if needed.
$headers = get_headers($url, true);
if (isset($headers['Content-Encoding'])
and $headers['Content-Encoding'] === 'gzip')
$contents = _gzdecode($contents);
# Prepare the directory.
$dir_is_ready = file_prepare_directory(
drupal_dirname($uri), FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS
);
if (!$dir_is_ready) return false;
# Write the file.
$path = file_unmanaged_save_data($contents, $uri, FILE_EXISTS_REPLACE);
lock_release($lock_name);
return $path;
}
/**
* Takes a remote URL and converts it to a local file URI.
*
* @param string $url
* The URL to the remote asset file.
* @return The local file URI on success, `FALSE` otherwise.
* @author Donovan Mueller <zotobi@gmail.com>
**/
function _url_to_file_uri($url)
{
# Break URL into manageable pieces.
if (version_compare(PHP_VERSION, '5.4.7', '<') and strpos($url, '//') === 0)
# Fix for PHP < 5.4.7 `parse_url()` not handling URLs with their scheme
# ommitted.
$url = "https:$url";
$parts = parse_url($url);
if ($parts === false or !isset($parts['host'])) return false;
if (empty($parts['scheme']))
$parts['scheme'] = !empty($_SERVER['HTTPS']) ? 'https' : 'http';
$uri = "{$parts['scheme']}/{$parts['host']}"
. (isset($parts['path']) ? $parts['path'] : '/index');
if (isset($parts['query'])) {
if ($uri[strlen($uri)-1] !== '/') $uri .= '/';
$uri .= $parts['query'];
}
$uri = rtrim($uri, '/');
if (!preg_match('#\.js$#', $uri))
# Hint to the webserver to serve these with the correct mime type.
$uri .= '.js';
return URI_PREFIX . "/$uri";
}
/**
* A workaround for PHP versions without `gzdecode()`.
*
* Taken from PartySoft's comment on this answer:
* http://stackoverflow.com/a/3002691/134014
*
* @param string $data
* The GZIP compressed string.
* @return The uncompressed string.
*/
function _gzdecode($data)
{
if (function_exists('gzdecode'))
return gzdecode($data);
$file = tempnam('/tmp', 'asset_saver_');
file_put_contents($file, $data);
ob_start();
readgzfile($file);
$data = ob_get_clean();
unlink($file);
return $data;
}
/**
* Clears all saved asset files.
*
* @return void
* @author Donovan Mueller <zotobi@gmail.com>
**/
function _clear_all_files()
{
if (!lock_acquire(CLEAR_FILES_LOCK)) return;
_rmdir(URI_PREFIX);
lock_release(CLEAR_FILES_LOCK);
}
/**
* An alternative to Drupal's `drupal_rmdir()` that can handle non-empty
* directories.
*
* Adapted from http://stackoverflow.com/a/1334425/134014
*
* @param string $path
* A file path or Drupal URI.
*/
function _rmdir($path)
{
$path = drupal_realpath($path);
if (is_dir($path)) {
$files = array_diff(scandir($path), array('.', '..'));
foreach ($files as $file)
_rmdir(realpath($path) . '/' . $file);
return rmdir($path);
}
if (is_file($path))
return unlink($path);
return false;
}
} // END namespace Drupal\asset_saver