Added array_merge_recursive_distinct() function to utilities.inc.
[project/advagg.git] / includes / utilities.inc
1 <?php
2
3 /**
4 * @file
5 * Utility and wrapper functions for the advagg module.
6 */
7
8 /**
9 * Given path output uri to that file
10 */
11 function advagg_build_uri($path) {
12 static $hook_file_url_alter = array();
13
14 // If the current path is an absolute path, return immediately.
15 $fragments = parse_url($path);
16 if (isset($fragments['host'])) {
17 return $path;
18 }
19
20 $original_path = $path;
21 // CDN Support.
22 if (module_exists('cdn')) {
23 $status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
24 if ($status == CDN_ENABLED || ($status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING))) {
25 // Alter URL when the file_create_url() patch is not there.
26 if (variable_get(CDN_THEME_LAYER_FALLBACK_VARIABLE, FALSE)) {
27 cdn_file_url_alter($path);
28 }
29 // Use the patched version of file_create_url().
30 else {
31 $path = file_create_url($path);
32 }
33 // Return here if the path was changed above.
34 if (strcmp($original_path, $path) != 0) {
35 return $path;
36 }
37 }
38 }
39
40 // Other modules besides CDN might want to use hook_file_url_alter.
41 if (empty($hook_file_url_alter)) {
42 $hook_file_url_alter = module_implements('file_url_alter');
43 }
44 if (!empty($hook_file_url_alter)) {
45 $path = file_create_url($path);
46 // Return here if the path was changed above.
47 if (strcmp($original_path, $path) != 0) {
48 return $path;
49 }
50 }
51
52 // If nothing was altered then use the drupal default.
53 return base_path() . $path;
54 }
55
56 /**
57 * Helper function to build an URL for asynchronous requests.
58 *
59 * @param $filepath
60 * Path to a URI excluding everything to the left and including the base path.
61 */
62 function _advagg_build_url($filepath = '') {
63 global $base_path;
64
65 // Server auth.
66 $auth = '';
67 if (isset($_SERVER['AUTH_TYPE']) && $_SERVER['AUTH_TYPE'] == 'Basic') {
68 $auth = $_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW'] . '@';
69 }
70
71 // Host.
72 $ip = variable_get('advagg_server_addr', FALSE);
73 if ($ip == -1) {
74 $ip = $_SERVER['HTTP_HOST'];
75 }
76 elseif (empty($ip)) {
77 $ip = empty($_SERVER['SERVER_ADDR']) ? '127.0.0.1' : $_SERVER['SERVER_ADDR'];
78 }
79
80 // Port.
81 $port = '';
82 // if ( isset($_SERVER['SERVER_PORT'])
83 // && is_numeric($_SERVER['SERVER_PORT'])
84 // && ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443)
85 // ) {
86 // $port = ':' . $_SERVER['SERVER_PORT'];
87 // }
88 return 'http://' . $auth . $ip . $port . $base_path . $filepath;
89 }
90
91 /**
92 * Wrapper around clearstatcache so it can use php 5.3's new features.
93 *
94 * @param $clear_realpath_cache
95 * Bool.
96 * @param $filename
97 * String.
98 * @return
99 * value from clearstatcache().
100 */
101 function advagg_clearstatcache($clear_realpath_cache = FALSE, $filename = NULL) {
102 static $php530;
103 if (!isset($php530)) {
104 $php530 = version_compare(PHP_VERSION, '5.3.0', '>=');
105 }
106
107 if ($php530) {
108 return clearstatcache($clear_realpath_cache, $filename);
109 }
110 else {
111 return clearstatcache();
112 }
113 }
114
115 /**
116 * Helper function to determine if the cookie to bypass aggregation is active.
117 *
118 * @return bool
119 * TRUE if enabled, FALSE otherwise.
120 */
121 function advagg_cookie_bypass() {
122 return !empty($_COOKIE[ADVAGG_COOKIE_NAME]) && $_COOKIE[ADVAGG_COOKIE_NAME] === md5(drupal_get_private_key()) ? TRUE : FALSE;
123 }
124
125 /**
126 * Select records in the database matching where IN(...).
127 *
128 * NOTE Be aware of the servers max_packet_size variable.
129 *
130 * @param $table
131 * The name of the table.
132 * @param $field
133 * field name to be compared to
134 * @param $placeholder
135 * db_query placeholders; like %d or '%s'
136 * @param $data
137 * array of values you wish to compare to
138 * @param $returns
139 * array of db fields you return
140 * @return
141 * returns db_query() result.
142 */
143 function advagg_db_multi_select_in($table, $field, $placeholder, $data, $returns = array(), $groupby = '') {
144 // Set returns if empty
145 if (empty($returns)) {
146 $returns[] = '*';
147 }
148 // Get the number of rows that will be inserted
149 $rows = count($data);
150 // Create what goes in the IN ()
151 $in = $placeholder;
152 // Add the rest of the place holders
153 for ($i = 1; $i < $rows; $i++) {
154 $in .= ', ' . $placeholder;
155 }
156 // Build the query
157 $query = "SELECT " . implode(', ', $returns) . " FROM {" . $table . "} WHERE $field IN ($in) $groupby";
158 // Run the query
159 // TODO Please convert this statement to the D7 database API syntax.
160 return db_query($query, $data);
161 }
162
163 /**
164 * Get the CSS & JS path for advagg.
165 *
166 * @return
167 * Example below:
168 * array(
169 * array(
170 * public://advagg_css,
171 * sites/default/files/advagg_css,
172 * ),
173 * array(
174 * public://advagg_js,
175 * sites/default/files/advagg_js,
176 * ),
177 * )
178 */
179 function advagg_get_root_files_dir() {
180 static $css_path;
181 static $js_path;
182 // Make sure directories are available and writable.
183 if (empty($css_path) || empty($js_path)) {
184 $css_path = 'public://advagg_css';
185 $js_path = 'public://advagg_js';
186 file_prepare_directory($css_path, FILE_CREATE_DIRECTORY);
187 file_prepare_directory($js_path, FILE_CREATE_DIRECTORY);
188 $css_path = parse_url(file_create_url($css_path));
189 $js_path = parse_url(file_create_url($js_path));
190 }
191 return array(ltrim($css_path['path'], '/'), ltrim($js_path['path'], '/'));
192 }
193
194 /**
195 * Return the server schema (http or https).
196 *
197 * @return string
198 * http OR https.
199 * @TODO: We probably should use relative references instead of trying to
200 * determine it based on the $_SERVER array.
201 * @see: http://tools.ietf.org/html/rfc3986#section-4.2
202 * @see: http://paulirish.com/2010/the-protocol-relative-url
203 * @see: http://www.stevesouders.com/blog/2010/02/10/5a-missing-schema-double-download/
204 */
205 function advagg_get_server_schema() {
206 return ( (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
207 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
208 || (isset($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] == 'on')
209 ) ? 'https' : 'http';
210 }
211
212 /**
213 * Always return TRUE, used for array_map in advagg_css_js_file_builder().
214 */
215 function advagg_return_true() {
216 return TRUE;
217 }
218
219 /**
220 * See if a string ends with a substring.
221 *
222 * @param $haystack
223 * The main string being compared.
224 * @param $needle
225 * The secondary string being compared.
226 * @return
227 * bool
228 */
229 function advagg_string_ends_with($haystack, $needle) {
230 // Define substr_compare if it doesn't exist (PHP 4 fix).
231 if (!function_exists('substr_compare')) {
232 /**
233 * Binary safe comparison of two strings from an offset, up to length
234 * characters.
235 *
236 * Compares main_str from position offset with str up to length characters.
237 * @see http://php.net/substr-compare#53084
238 *
239 * @param $main_str
240 * The main string being compared.
241 * @param $str
242 * The secondary string being compared.
243 * @param $offset
244 * The start position for the comparison. If negative, it starts counting
245 * from the end of the string.
246 * @param $length
247 * The length of the comparison. The default value is the largest of the
248 * length of the str compared to the length of main_str less the offset.
249 * @param $case_insensitivity
250 * If TRUE, comparison is case insensitive.
251 * @return
252 * Returns < 0 if main_str from position offset is less than str, > 0 if
253 * it is greater than str, and 0 if they are equal. If offset is equal to
254 * or greater than the length of main_str or length is set and is less than
255 * 1, substr_compare() prints a warning and returns FALSE.
256 */
257 function substr_compare($main_str, $str, $offset, $length = NULL, $case_insensitivity = FALSE) {
258 $offset = (int) $offset;
259
260 // Throw a warning because the offset is invalid
261 if ($offset >= strlen($main_str)) {
262 trigger_error('The start position cannot exceed initial string length.', E_USER_WARNING);
263 return FALSE;
264 }
265
266 // We are comparing the first n-characters of each string, so let's use the PHP function to do it
267 if ($offset == 0 && is_int($length) && $case_insensitivity === TRUE) {
268 return strncasecmp($main_str, $str, $length);
269 }
270
271 // Get the substring that we are comparing
272 if (is_int($length)) {
273 $main_substr = substr($main_str, $offset, $length);
274 $str_substr = substr($str, 0, $length);
275 }
276 else {
277 $main_substr = substr($main_str, $offset);
278 $str_substr = $str;
279 }
280
281 // Return a case-insensitive comparison of the two strings
282 if ($case_insensitivity === TRUE) {
283 return strcasecmp($main_substr, $str_substr);
284 }
285
286 // Return a case-sensitive comparison of the two strings
287 return strcmp($main_substr, $str_substr);
288 }
289 }
290
291 $haystack_len = strlen($haystack);
292 $needle_len = strlen($needle);
293 if ($needle_len > $haystack_len) {
294 return FALSE;
295 }
296 return substr_compare($haystack, $needle, $haystack_len -$needle_len, $needle_len, TRUE) === 0;
297 }
298
299 if (!function_exists('array_merge_recursive_distinct')) {
300 /**
301 * Merges any number of arrays / parameters recursively, replacing
302 * entries with string keys with values from latter arrays.
303 * If the entry or the next value to be assigned is an array, then it
304 * automagically treats both arguments as an array.
305 * Numeric entries are appended, not replaced, but only if they are
306 * unique
307 *
308 * calling: result = array_merge_recursive_distinct(a1, a2, ... aN)
309 **/
310
311 function array_merge_recursive_distinct () {
312 $arrays = func_get_args();
313 $base = array_shift($arrays);
314 if(!is_array($base)) $base = empty($base) ? array() : array($base);
315 foreach($arrays as $append) {
316 if(!is_array($append)) $append = array($append);
317 foreach($append as $key => $value) {
318 if(!array_key_exists($key, $base) and !is_numeric($key)) {
319 $base[$key] = $append[$key];
320 continue;
321 }
322 if(is_array($value) or is_array($base[$key])) {
323 $base[$key] = array_merge_recursive_distinct($base[$key], $append[$key]);
324 } else if(is_numeric($key)) {
325 if(!in_array($value, $base)) $base[] = $value;
326 } else {
327 $base[$key] = $value;
328 }
329 }
330 }
331 return $base;
332 }
333 }