├── README.md └── third_party └── antenna ├── addon.setup.php └── pi.antenna.php /README.md: -------------------------------------------------------------------------------- 1 | Antenna for ExpressionEngine 2, 3, 4, and 5 2 | ======== 3 | 4 | **Antenna** is a plugin that will generate the exact, most up-to-date YouTube, Vimeo, Wistia, or Viddler embed code available. With an [embed.ly](http://embed.ly/) API key, you also have access to [hundreds more video providers](http://embed.ly/providers). In addition to the video's embed code, **Antenna** also provides you with the video's title, its author, the author's YouTube/Vimeo URL, and a thumbnail. All you have to do is pass it a single URL. 5 | 6 | You can also output various pieces of metadata about the video. 7 | 8 | Support for ExpressionEngine 1 ended with v1.23. That version can still be [downloaded here](https://github.com/vector/Antenna/releases/tag/v1.23). 9 | 10 | Support for ExpressionEngine 3, 4, and 5 started with version 2. 11 | 12 | Installation 13 | ------- 14 | 15 | - ExpressionEngine 2: 16 | - Place the directory `/third_party/antenna` in `/system/expressionengine/third_party/`. 17 | - ExpressionEngine 3, 4, 5: 18 | - Place the directory `/third_party/antenna` in `/system/user/addons/`. 19 | - Go go the addon manager and click "Install" next to Antenna. 20 | 21 | Usage 22 | ------- 23 | 24 | {exp:antenna url='{the_youtube_or_vimeo_url}' max_width="232" max_height="323" wmode="transparent|opaque|window"} 25 | {embed_code} 26 | {video_title} 27 | {video_author} 28 | {video_author_url} 29 | {video_thumbnail} 30 | {video_mediumres} 31 | {video_highres} 32 | {video_provider} 33 | {video_ratio} 34 | {video_width} 35 | {video_height} 36 | {video_provider_id} 37 | 38 | {!-- For Vimeo Only --} 39 | {video_description} 40 | 41 | {if embed_code} 42 | It worked! {embed_code} 43 | {if:else} 44 | No video to display here. 45 | {/if} 46 | {/exp:antenna} 47 | 48 | 49 | Set the `max_width` and/or `max_height` for whatever size your website requires. The video will be resized to be within those dimensions, and will stay at the correct proportions. 50 | 51 | The optional `wmode` parameter can be used if you're experiencing issues positioning HTML content in front of the embedded media. It accepts values of transparent, opaque and window. 52 | 53 | If used as a single tag, it returns the HTML embed/object code for the video. If used as a pair, you get access to the variables above and can use them in conditionals. 54 | 55 | There are three image sizes available for videos: `{video_thumbnail}`, `{video_mediumres}`, and `{video_highres}`. They are not consistent across services but they should fall into rough size brackets. `{video_thumbnail}` is going to be between 100-200px wide; `{video_mediumres}` will be around 400-500px wide; and `{video_highres}` will be at least the full size of your uploaded video and could be as wide as 1280px. 56 | 57 | Antenna will automatically enforce HTTPS if the provided video URL has a protocol of `https://` and is supported by the video service. Alternatively, you can also attempt to force the particular service to return the HTTPS resource by adding the parameter: 58 | 59 | force_https='true' 60 | 61 | If you're using YouTube, you get access to several more parameters, such as: 62 | 63 | - `youtube_rel='0/1'` -- Show related videos at end of video. Defaults to 1. 64 | - `youtube_showinfo ='0/1'` -- Show the video title and uploader. Defaults to 1. 65 | 66 | See [this guide from Google](https://developers.google.com/youtube/player_parameters#Parameters) for the full list of HTML5 parameters available for YouTube videos. Prefix each parameter with `youtube_` when adding these parameters to your Antenna template tag. 67 | 68 | If you're using Vimeo, you get access to more parameters and one more variable: 69 | 70 | - `vimeo_byline='true/false'` -- Shows the byline on the video. Defaults to true. 71 | - `vimeo_title='true/false'` -- Shows the title on the video. Defaults to true. 72 | - `vimeo_portrait='true/false'` -- Shows the user's avatar on the video. Defaults to true. 73 | - `vimeo_autoplay='true/false'` -- Automatically start playback of the video. Defaults to false. 74 | - `vimeo_api='true/false'` -- Adds 'api=1' to the vimeo embed url to allow JavaScript API usage. Defaults to false. *NOTE*: only use this if you're using the legacy Vimeo "Froogaloop" API. Keep it off if you're using the new, Player.js API. 75 | - `vimeo_color='EFEFEF'` -- changes the color of the player controls, and the color of the video title (if enabled). The parameter here expects a hex color code. 76 | - `vimeo_loop='true/false'` -- loops the Vimeo video automatically 77 | - `{video_description}` -- The description of the video, as set in Vimeo 78 | 79 | If you're using Viddler, you get access to two more parameters: 80 | 81 | - `viddler_type='simple/player'` -- Specifies the player type. Defaults to player. 82 | - `viddler_ratio='widescreen/fullscreen'` -- Aspect ratio will be automatically determined if not set. 83 | 84 | The plugin automatically caches returned data for one week. You can control this setting with the `cache_minutes` parameter, or set `cache_minutes` to "`0`" to disable the cache. 85 | 86 | **NOTE** For this to work with all urls please ensure that in Weblog/Channel -> Preferences, you have 'Automatically turn URLs and email addresses into links?' set to 'No'. 87 | 88 | Embed.ly Support 89 | ------- 90 | 91 | By including an [embed.ly](http://embed.ly/) API key, you have access to [hundreds more video providers](http://embed.ly/providers). If your URL is not for YouTube, Vimeo, Wistia, or Viddler, **Antenna** will check embed.ly to pull the correct relevant video embed and information. 92 | 93 | Simply include your embed.ly API key as a parameter on your `{exp:antenna}` tag like: 94 | 95 | ``` 96 | {exp:antenna url='{example_vine_url}' embedly_key="xxxxxxxxxx" max_width="232" max_height="323" wmode="transparent|opaque|window"} 97 | ... 98 | {/exp:antenna} 99 | ``` 100 | 101 | or set your embed.ly API key globally by adding it to ExpressionEngine's `config.php` file: 102 | 103 | ``` 104 | $config['antenna_embedly_key'] = 'xxxxxxxxxx'; 105 | ``` 106 | 107 | Compatibility 108 | ------- 109 | 110 | **Antenna** is compatible with ExpressionEngine 2-5. The version for ExpressionEngine 2 requires 2.6+. The version for ExpressionEngine 3-5 has been tested on 3.0+. 111 | 112 | You must be using PHP 5.2+ and you must have either the `cURL` library installed or `allow_url_fopen` enabled. 113 | 114 | *Note for Wistia users*: Wistia doesn't return as much data as the other providers, so not all of the above variables will work. Also, you may need to contact Wistia and ask them to enable "oEmbed support" on your account. 115 | 116 | Warranty/License 117 | ------- 118 | 119 | There's no warranty of any kind. If you find a bug, please tell me and I may try to fix it. It's provided completely as-is; if something breaks, you lose data, or something else bad happens, the author(s) and owner(s) of this plugin are in no way responsible. 120 | 121 | This plugin is owned by Matt Weinberg. You can modify it and use it for your own personal or commercial projects, but you can't redistribute it. EE2 and Vimeo functionality added by Adam Wiggall (@turnandface). Wmode compatibility added by Tom Davies (@metadaptive). 122 | -------------------------------------------------------------------------------- /third_party/antenna/addon.setup.php: -------------------------------------------------------------------------------- 1 | 'Matt Weinberg', 5 | 'author_url' => 'https://www.happycog.com', 6 | 'name' => 'Antenna', 7 | 'description' => 'Returns the embed code and various pieces of metadata for YouTube, Vimeo, Wistia, and Viddler Videos', 8 | 'version' => '2.3.2', 9 | 'namespace' => 'Vector\Antenna', 10 | 'docs_url' => 'https://github.com/happycog/antenna' 11 | ); 12 | -------------------------------------------------------------------------------- /third_party/antenna/pi.antenna.php: -------------------------------------------------------------------------------- 1 | 'Antenna', 13 | 'pi_version' => '2.3.2', 14 | 'pi_author' => 'Matt Weinberg', 15 | 'pi_author_url' => 'https://www.happycog.com.com', 16 | 'pi_description' => 'Returns the embed code and various pieces of metadata for YouTube, Vimeo, Wistia, and Viddler Videos', 17 | 'pi_usage' => 'https://github.com/happycog/antenna' 18 | ); 19 | 20 | /** 21 | * The Antenna plugin will generate the YouTube or Vimeo embed 22 | * code for a single YouTube or Vimeo clip. It will also give 23 | * you back the Author, their URL, the video title, 24 | * and a thumbnail. 25 | * 26 | * @package Antenna 27 | */ 28 | 29 | class Antenna 30 | { 31 | public $return_data = ''; 32 | public $cache_name = 'antenna_urls'; 33 | public $cache_path = ''; 34 | public $refresh_cache = 10080; // in mintues (default is 1 week) 35 | public $cache_expired = FALSE; 36 | 37 | public function __construct() 38 | { 39 | $this->_set_cache_path(); 40 | 41 | $tagdata = ee()->TMPL->tagdata; 42 | 43 | // Check to see if it's a one-off tag or a pair 44 | $mode = ($tagdata) ? "pair" : "single"; 45 | 46 | $plugin_vars = array( 47 | "title" => "video_title", 48 | "html" => "embed_code", 49 | "author_name" => "video_author", 50 | "author_url" => "video_author_url", 51 | "thumbnail_url" => "video_thumbnail", 52 | "medres_url" => "video_mediumres", 53 | "highres_url" => "video_highres", 54 | "description" => "video_description", 55 | "provider" => "video_provider", 56 | "width" => "video_width", 57 | "height" => "video_height", 58 | "video_id" => "video_provider_id" 59 | ); 60 | 61 | $video_data = array(); 62 | 63 | foreach ($plugin_vars as $var) { 64 | $video_data[$var] = false; 65 | } 66 | 67 | // Deal with the parameters 68 | $video_url = (ee()->TMPL->fetch_param('url')) ? html_entity_decode(ee()->TMPL->fetch_param('url')) : false; 69 | $max_width = (ee()->TMPL->fetch_param('max_width')) ? "&maxwidth=" . ee()->TMPL->fetch_param('max_width') : ''; 70 | $max_height = (ee()->TMPL->fetch_param('max_height')) ? "&maxheight=" . ee()->TMPL->fetch_param('max_height') : ''; 71 | $wmode = ee()->TMPL->fetch_param('wmode', ''); 72 | $wmode_param = ! empty($wmode) ? "&wmode=" . $wmode : ''; 73 | 74 | // Check for embed.ly support 75 | $embedly_key = ee()->TMPL->fetch_param('embedly_key', false); 76 | if ( ! $embedly_key && ee()->config->item('antenna_embedly_key')) { 77 | $embedly_key = ee()->config->item('antenna_embedly_key'); 78 | } 79 | 80 | // Cache can be disabled by setting 0 as the cache_minutes param 81 | if (ee()->TMPL->fetch_param('cache_minutes') !== FALSE && is_numeric(ee()->TMPL->fetch_param('cache_minutes'))) { 82 | $this->refresh_cache = ee()->TMPL->fetch_param('cache_minutes'); 83 | } 84 | 85 | // Some optional YouTube parameters 86 | $youtube_options = array( 87 | 'autohide', 88 | 'autoplay', 89 | 'cc_load_policy', 90 | 'color', 91 | 'controls', 92 | 'disablekb', 93 | 'enablejsapi', 94 | 'end', 95 | 'fs', 96 | 'hl', 97 | 'iv_load_policy', 98 | 'list', 99 | 'listType', 100 | 'loop', 101 | 'modestbranding', 102 | 'origin', 103 | 'playlist', 104 | 'playsinline', 105 | 'rel', 106 | 'showinfo', 107 | 'start', 108 | 'theme' 109 | ); 110 | $youtube_params = array(); 111 | 112 | foreach($youtube_options as $option) 113 | { 114 | $param = ee()->TMPL->fetch_param('youtube_'.$option, null); 115 | if(!is_null($param)) 116 | { 117 | $youtube_params[$option] = $param; 118 | } 119 | } 120 | 121 | // Some optional Vimeo parameters 122 | $vimeo_byline = (ee()->TMPL->fetch_param('vimeo_byline') == "false") ? "&byline=false" : ''; 123 | $vimeo_title = (ee()->TMPL->fetch_param('vimeo_title') == "false") ? "&title=false" : ''; 124 | $vimeo_autoplay = (ee()->TMPL->fetch_param('vimeo_autoplay') == "true") ? "&autoplay=true" : ''; 125 | $vimeo_portrait = (ee()->TMPL->fetch_param('vimeo_portrait') == "false") ? "&portrait=0" : ''; 126 | $vimeo_api = (ee()->TMPL->fetch_param('vimeo_api') == "true") ? "&api=1" : ''; 127 | $vimeo_loop = (ee()->TMPL->fetch_param('vimeo_loop') == "true") ? "&loop=true" : ''; 128 | $vimeo_color = (ee()->TMPL->fetch_param('vimeo_color') !== false) ? "&color=".str_replace('#', '', ee()->TMPL->fetch_param('vimeo_color')) : ''; 129 | 130 | // Some optional Viddler parameters 131 | $viddler_type = (ee()->TMPL->fetch_param('viddler_type')) ? "&type=" . ee()->TMPL->fetch_param('viddler_type') : ''; 132 | $viddler_ratio = (ee()->TMPL->fetch_param('viddler_ratio')) ? "&ratio=" . ee()->TMPL->fetch_param('viddler_ratio') : ''; 133 | 134 | // Automatically handle scheme if https 135 | $is_https = false; 136 | if (ee()->TMPL->fetch_param('force_https') == "true" || parse_url($video_url, PHP_URL_SCHEME) == 'https') { 137 | $is_https = true; 138 | } 139 | 140 | // If it's not YouTube, Vimeo, Wistia, or Viddler bail 141 | if (strpos($video_url, "youtube.com/") !== FALSE OR strpos($video_url, "youtu.be/") !== FALSE) { 142 | $url = 'https://www.youtube.com/oembed?format=xml&iframe=1' . ($is_https ? '&scheme=https' : '') . '&url='; 143 | } elseif (strpos($video_url, "vimeo.com/") !== FALSE) { 144 | $url = 'https://vimeo.com/api/oembed.xml?url='; 145 | } elseif (strpos($video_url, "wistia.com/") !== FALSE) { 146 | $url = 'http://app.wistia.com/embed/oembed.xml?url='; 147 | } elseif (strpos($video_url, "viddler.com/") !== FALSE) { 148 | $url = 'http://www.viddler.com/oembed/?format=xml&url='; 149 | } elseif ($embedly_key) { 150 | $url = 'https://api.embedly.com/1/oembed?format=xml&key=' . urlencode($embedly_key) . '&url='; 151 | } else { 152 | $tagdata = ee()->functions->var_swap($tagdata, $video_data); 153 | $this->return_data = $tagdata; 154 | return; 155 | } 156 | 157 | $url .= urlencode($video_url) . $max_width . $max_height . $wmode_param . $vimeo_byline . $vimeo_title . $vimeo_autoplay . $vimeo_portrait . $vimeo_api . $vimeo_color . $viddler_type . $viddler_ratio . $vimeo_loop; 158 | 159 | // checking if url has been cached 160 | $cached_url = $this->_check_cache($url); 161 | 162 | if (! $this->refresh_cache OR $this->cache_expired OR ! $cached_url) 163 | { 164 | //Create the info and header variables 165 | list($video_info, $video_header) = $this->curl($url); 166 | 167 | if (!$video_info || $video_header != "200") 168 | { 169 | $tagdata = ee()->functions->var_swap($tagdata, $video_data); 170 | $this->return_data = $tagdata; 171 | return; 172 | } 173 | 174 | // write the data to cache if caching hasn't been disabled 175 | if ($this->refresh_cache) { 176 | $this->_write_cache($video_info, $url); 177 | } 178 | } 179 | else 180 | { 181 | $video_info = $cached_url; 182 | } 183 | 184 | // Decode the cURL data 185 | $video_info = simplexml_load_string($video_info); 186 | 187 | // Inject wmode transparent if required 188 | if ($wmode === 'transparent' || $wmode === 'opaque' || $wmode === 'window' ) { 189 | $param_str = ''; 190 | $embed_str = ' wmode="' . $wmode .'" '; 191 | 192 | // Determine whether we are dealing with iframe or embed and handle accordingly 193 | if (strpos($video_info->html, "html, "html = substr($video_info->html, 0, $param_pos) . $param_str . substr($video_info->html, $param_pos); 196 | $param_pos = strpos( $video_info->html, "html = substr($video_info->html, 0, $param_pos) . $embed_str . substr($video_info->html, $param_pos); 198 | } 199 | else 200 | { 201 | // Determine whether to add question mark to query string 202 | preg_match('//i', $video_info->html, $matches); 203 | $append_query_marker = (strpos($matches[1], '?') !== false ? '' : '?'); 204 | 205 | $video_info->html = preg_replace('//i', '', $video_info->html); 206 | } 207 | } 208 | 209 | // Inject YouTube parameters if required 210 | if( !empty($youtube_params) && (strpos($video_url, "youtube.com/") !== FALSE OR strpos($video_url, "youtu.be/") !== FALSE)) 211 | { 212 | $youtube_args = ''; 213 | foreach($youtube_params as $param => $value) 214 | { 215 | $youtube_args .= '&'.$param.'=' . $value; 216 | } 217 | preg_match('/.*?src="(.*?)".*?/', $video_info->html, $matches); 218 | if (!empty($matches[1])) $video_info->html = str_replace($matches[1], $matches[1] . $youtube_args, $video_info->html); 219 | } 220 | 221 | 222 | // actually setting thumbnails at a reasonably consistent size, as well as getting higher-res images 223 | if(strpos($video_url, "youtube.com/") !== FALSE OR strpos($video_url, "youtu.be/") !== FALSE) { 224 | $video_info->highres_url = str_replace('hqdefault','maxresdefault',$video_info->thumbnail_url); 225 | $video_info->medres_url = $video_info->thumbnail_url; 226 | $video_info->thumbnail_url = str_replace('hqdefault','mqdefault',$video_info->thumbnail_url); 227 | $video_info->provider = "youtube"; 228 | 229 | if(strpos($video_url, "youtube.com/") !== FALSE) { 230 | $qs = parse_url($video_url, PHP_URL_QUERY); 231 | parse_str($qs, $query_params); 232 | $video_info->video_id = $query_params['v']; 233 | } else { 234 | $video_info->video_id = ltrim(parse_url($video_url, PHP_URL_PATH), '/'); 235 | } 236 | 237 | } 238 | else if (strpos($video_url, "vimeo.com/") !== FALSE) { 239 | $video_info->highres_url = preg_replace('/_(.*?)\./','_1280.',$video_info->thumbnail_url); 240 | $video_info->medres_url = preg_replace('/_(.*?)\./','_640.',$video_info->thumbnail_url); 241 | $video_info->thumbnail_url = preg_replace('/_(.*?)\./','_295.',$video_info->thumbnail_url); 242 | $video_info->provider = "vimeo"; 243 | } 244 | else if (strpos($video_url, "wistia.com/") !== FALSE) 245 | { 246 | // For now, turn the image_crop_resized query string into an unrecognized value 247 | $video_info->highres_url = str_replace('image_crop_resized=','icrtemp=',$video_info->thumbnail_url); 248 | $video_info->medres_url = str_replace('?image_crop_resized=100x60','?image_crop_resized=640x400',$video_info->thumbnail_url); 249 | $video_info->thumbnail_url = str_replace('?image_crop_resized=100x60','?image_crop_resized=240x135',$video_info->thumbnail_url); 250 | $video_info->provider = "wistia"; 251 | } 252 | else if (strpos($video_url, "viddler.com/") !== FALSE) 253 | { 254 | $video_info->highres_url = $video_info->thumbnail_url; 255 | $video_info->medres_url = $video_info->thumbnail_url; 256 | $video_info->thumbnail_url = str_replace('thumbnail_2','thumbnail_1',$video_info->thumbnail_url); 257 | $video_info->provider = "viddler"; 258 | } 259 | 260 | 261 | // Handle a single tag 262 | if ($mode == "single") 263 | { 264 | $this->return_data = $video_info->html; 265 | return; 266 | } 267 | 268 | // Handle multiple tag with tagdata 269 | foreach ($plugin_vars as $key => $var) 270 | { 271 | if (isset($video_info->$key)) 272 | { 273 | $video_data[$var] = $video_info->$key; 274 | } 275 | } 276 | 277 | if(!empty($video_info->width) && !empty($video_info->height)) 278 | { 279 | $video_data['video_ratio'] = floatval($video_info->width / $video_info->height); 280 | } 281 | 282 | $tagdata = ee()->functions->prep_conditionals($tagdata, $video_data); 283 | 284 | foreach ($video_data as $key => $value) 285 | { 286 | $tagdata = ee()->TMPL->swap_var_single($key, $value, $tagdata); 287 | } 288 | 289 | $this->return_data = $tagdata; 290 | 291 | return; 292 | } 293 | 294 | private function _set_cache_path() { 295 | $this->cache_path = version_compare(APP_VER, '3.0.0', '>=') ? SYSPATH . 'user/' : APPPATH; 296 | $this->cache_path .= 'cache/' . $this->cache_name . '/'; 297 | return ''; 298 | } 299 | 300 | /** 301 | * Generates a CURL request to the YouTube URL 302 | * to make sure that 303 | * 304 | * @param string $vid_url The YouTube URL 305 | * @return void 306 | */ 307 | public function curl($vid_url) { 308 | // Do we have curl? 309 | if (function_exists('curl_init')) 310 | { 311 | $curl = curl_init(); 312 | 313 | // Our cURL options 314 | $options = array( 315 | CURLOPT_URL => $vid_url, 316 | CURLOPT_RETURNTRANSFER => 1, 317 | CURLOPT_CONNECTTIMEOUT => 10, 318 | ); 319 | 320 | curl_setopt_array($curl, $options); 321 | 322 | $video_info = curl_exec($curl); 323 | $video_header = curl_getinfo($curl, CURLINFO_HTTP_CODE); 324 | 325 | //Close the request 326 | curl_close($curl); 327 | 328 | } 329 | // Do we have fopen? 330 | elseif (ini_get('allow_url_fopen') === TRUE) 331 | { 332 | $video_header = ($video_info = file_get_contents($vid_url)) ? '200' : TRUE; 333 | } 334 | else 335 | { 336 | $video_header = $video_info = FALSE; 337 | } 338 | 339 | return array($video_info, $video_header); 340 | } 341 | 342 | /** 343 | * Check Cache 344 | * 345 | * Check for cached data 346 | * 347 | * @access public 348 | * @param string 349 | * @param bool Allow pulling of stale cache file 350 | * @return mixed - string if pulling from cache, FALSE if not 351 | */ 352 | function _check_cache($url) 353 | { 354 | // Check for cache directory 355 | 356 | $dir = $this->cache_path; 357 | 358 | if ( ! @is_dir($dir)) 359 | { 360 | return FALSE; 361 | } 362 | 363 | // Check for cache file 364 | 365 | $file = $dir.md5($url); 366 | 367 | if ( ! file_exists($file) OR ! ($fp = @fopen($file, 'rb'))) 368 | { 369 | return FALSE; 370 | } 371 | 372 | flock($fp, LOCK_SH); 373 | 374 | $cache = @fread($fp, filesize($file)); 375 | 376 | flock($fp, LOCK_UN); 377 | 378 | fclose($fp); 379 | 380 | // Grab the timestamp from the first line 381 | 382 | $eol = strpos($cache, "\n"); 383 | 384 | $timestamp = substr($cache, 0, $eol); 385 | $cache = trim((substr($cache, $eol))); 386 | 387 | if ( time() > ($timestamp + ($this->refresh_cache * 60)) ) 388 | { 389 | $this->cache_expired = TRUE; 390 | } 391 | 392 | return $cache; 393 | } 394 | 395 | /** 396 | * Write Cache 397 | * 398 | * Write the cached data 399 | * 400 | * @access public 401 | * @param string 402 | * @return void 403 | */ 404 | function _write_cache($data, $url) 405 | { 406 | // Check for cache directory 407 | 408 | $dir = $this->cache_path; 409 | 410 | if ( ! @is_dir($dir)) 411 | { 412 | if ( ! @mkdir($dir, DIR_WRITE_MODE)) 413 | { 414 | return FALSE; 415 | } 416 | 417 | @chmod($dir, DIR_WRITE_MODE); 418 | } 419 | 420 | // add a timestamp to the top of the file 421 | $data = time()."\n".$data; 422 | 423 | 424 | // Write the cached data 425 | 426 | $file = $dir.md5($url); 427 | 428 | if ( ! $fp = @fopen($file, 'wb')) 429 | { 430 | return FALSE; 431 | } 432 | 433 | flock($fp, LOCK_EX); 434 | fwrite($fp, $data); 435 | flock($fp, LOCK_UN); 436 | fclose($fp); 437 | 438 | @chmod($file, DIR_WRITE_MODE); 439 | } 440 | 441 | } 442 | --------------------------------------------------------------------------------