Add composer flojs and initial structure for video upload

parent 4653922b
...@@ -5,3 +5,38 @@ ...@@ -5,3 +5,38 @@
content: "\f2c2"; content: "\f2c2";
} }
#shvtags{
float:left;
border:1px solid #ccc;
padding:5px;
font-family:Arial;
}
#shvtags > span{
cursor:pointer;
display:block;
float:left;
color:#fff;
background:#789;
padding:5px;
padding-right:25px;
margin:4px;
}
#shvtags > span:hover{
opacity:0.7;
}
#shvtags > span:after{
position:absolute;
content:"×";
border:1px solid;
padding:2px 5px;
margin-left:3px;
font-size:11px;
}
#shvtags > input{
background:#eee;
border:0;
margin:4px;
padding:7px;
width:auto;
}
...@@ -82,6 +82,16 @@ if(!class_exists('SH_Admin')) { ...@@ -82,6 +82,16 @@ if(!class_exists('SH_Admin')) {
register_setting('sexhackme-gallery-settings', 'sexhack_video_page'); register_setting('sexhackme-gallery-settings', 'sexhack_video_page');
register_setting('sexhackme-gallery-settings', 'sexhack_gallery_page'); register_setting('sexhackme-gallery-settings', 'sexhack_gallery_page');
register_setting('sexhackme-gallery-settings', 'sexhack_video404_page'); register_setting('sexhackme-gallery-settings', 'sexhack_video404_page');
register_setting('sexhackme-gallery-settings', 'sexhack_shmdown');
register_setting('sexhackme-gallery-settings', 'sexhack_shmdown_uri');
register_setting('sexhackme-gallery-settings', 'sexhack_video_tmp_path');
register_setting('sexhackme-gallery-settings', 'sexhack_video_flat_path');
register_setting('sexhackme-gallery-settings', 'sexhack_video_vr_path');
register_setting('sexhackme-gallery-settings', 'sexhack_video_hls_storage');
register_setting('sexhackme-gallery-settings', 'sexhack_video_video_storage');
register_setting('sexhackme-gallery-settings', 'sexhack_video_photo_storage');
register_setting('sexhackme-gallery-settings', 'sexhack_video_gif_storage');
register_setting('sexhackme-gallery-settings', 'sexhack_video_vr_storage');
add_action('update_option', '\wp_SexHackMe\SH_Admin::update_gallery_slug', 10, 3); add_action('update_option', '\wp_SexHackMe\SH_Admin::update_gallery_slug', 10, 3);
//register_setting('sexhackme-gallery-settings', 'sexhack_gallery_slug'); //register_setting('sexhackme-gallery-settings', 'sexhack_gallery_slug');
} }
...@@ -115,7 +125,6 @@ if(!class_exists('SH_Admin')) { ...@@ -115,7 +125,6 @@ if(!class_exists('SH_Admin')) {
update_option('need_rewrite_flush', 1); update_option('need_rewrite_flush', 1);
break; break;
default: default:
break; break;
} }
......
...@@ -117,7 +117,7 @@ if(!class_exists('SH_MetaBox')) { ...@@ -117,7 +117,7 @@ if(!class_exists('SH_MetaBox')) {
public static function save_meta_box_data($post_id) public static function save_meta_box_data($post_id)
{ {
return save_sexhack_video_meta_box_data($post_id); return save_sexhack_video_forms($post_id);
} }
} }
......
...@@ -205,10 +205,30 @@ if(!class_exists('SH_Query')) { ...@@ -205,10 +205,30 @@ if(!class_exists('SH_Query')) {
$slug = $wpdb->_real_escape($slug); $slug = $wpdb->_real_escape($slug);
$sql = "SELECT * FROM {$wpdb->prefix}".SH_PREFIX."videos WHERE slug='".$slug."'"; $sql = "SELECT * FROM {$wpdb->prefix}".SH_PREFIX."videos WHERE slug='".$slug."'";
$dbres = $wpdb->get_results( $sql ); $dbres = $wpdb->get_results( $sql );
if(is_array($dbres) && count($dbres) > 0) if(is_array($dbres) && count($dbres) > 0) return new SH_Video((array)$dbres[0]);
return false;
}
public static function get_VideosFromHLS($vpath, $level="public")
{
global $wpdb;
$vpath = $wpdb->_real_escape($vpath);
switch($level)
{ {
return new SH_Video((array)$dbres[0]); case "members":
$level="hls_members";
break;
case "premium":
$level="hls_premium";
break;
default:
$level="hls_public";
} }
$sql = "SELECT * FROM {$wpdb->prefix}".SH_PREFIX."videos WHERE ".$level."='".$vpath."'";
$dbres = $wpdb->get_results( $sql );
if(is_array($dbres) && count($dbres) > 0) return new SH_Video((array)$dbres[0]);
return false; return false;
} }
......
...@@ -24,25 +24,67 @@ namespace wp_SexHackMe; ...@@ -24,25 +24,67 @@ namespace wp_SexHackMe;
// Exit if accessed directly // Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit; if ( ! defined( 'ABSPATH' ) ) exit;
if(!class_exists('SH_VideoUpload')) { if(!class_exists('SH_VideoUpload')) {
require_once(SH_PLUGIN_DIR_PATH."vendor/autoload.php");
class SH_VideoUpload class SH_VideoUpload
{ {
public function __construct() public function __construct()
{ {
add_action('wp_ajax_file_upload', array($this, 'file_upload_callback')); add_action('wp_ajax_file_upload', array($this, 'file_upload_callback'));
add_action('wp_ajax_nopriv_file_upload', array($this, 'file_upload_callback')); add_action('wp_ajax_nopriv_file_upload', array($this, 'file_upload_callback'));
add_action('wp_ajax_sh_editvideo', array($this, 'edit_video_callback'));
add_action('wp_ajax_nopriv_sh_editvideo', array($this, 'edit_video_callback'));
} }
public function file_upload_callback() public function file_upload_callback()
{ {
check_ajax_referer('sh_video_upload', 'security'); check_ajax_referer('sh_video_upload', 'security');
$arr_img_ext = array('image/png', 'image/jpeg', 'image/jpg', 'image/gif'); //$arr_ext = array('image/png', 'image/jpeg', 'image/jpg', 'image/gif');
if (in_array($_FILES['file']['type'], $arr_img_ext)) { //$arr_ext = array('video/mp4', 'video/webm','video/mov','video/m4v','video/mpg','video/flv');
$config = new \Flow\Config();
$config->setTempDir("/tmp");
$request = new \Flow\Request();
if(isset($_POST['uniqid'])) $uniqid = $_POST['uniqid'];
else $uniqid = uniqid();
$uploadFolder = get_option('sexhack_video_tmp_path', '/tmp');
$uploadFileName = $uniqid . "_" . $request->getFileName();
$uploadPath = $uploadFolder."/".$uploadFileName;
if (\Flow\Basic::save($uploadPath, $config, $request)) {
sexhack_log("Hurray, file was saved in " . $uploadPath);
} else {
sexhack_log("UPLOADING...");
}
/*
if (in_array($_FILES['file']['type'], $arr_ext)) {
$upload = wp_upload_bits($_FILES["file"]["name"], null, file_get_contents($_FILES["file"]["tmp_name"])); $upload = wp_upload_bits($_FILES["file"]["name"], null, file_get_contents($_FILES["file"]["tmp_name"]));
//$upload['url'] will gives you uploaded file path //$upload['url'] will gives you uploaded file path
} }
wp_die(); */
//wp_die();
}
public function edit_video_callback()
{
sexhack_log("PORCODIOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO");
sexhack_log($_POST);
sexhack_log($_GET);
// XXX Sanitize $_POST['title']
if(!isset($_POST['title'])) return;
$title = $_POST['title'];
$post_id = wp_insert_post(array (
'post_type' => 'sexhack_video',
'post_title' => $title,
'post_status' => 'queue',
));
} }
} }
......
...@@ -92,8 +92,21 @@ if(!class_exists('SH_Video')) { ...@@ -92,8 +92,21 @@ if(!class_exists('SH_Video')) {
return $this->attributes['title']; return $this->attributes['title'];
} }
public function has_downloads() public function has_downloads($level=false)
{ {
switch($level) {
case 'premim':
return $this->download_premium;
break;
case 'members':
return $this->download_members;
break;
case 'public':
return $this->download_public;
break;
default:
return false;
}
if($this->download_public OR $this->download_members OR $this->download_premium) return true; if($this->download_public OR $this->download_members OR $this->download_premium) return true;
return false; return false;
} }
......
<?php
/**
* Copyright: 2022 (c)Franco (nextime) Lanza <franco@nexlab.it>
* License: GNU/GPL version 3.0
*
* This file is part of SexHackMe Wordpress Plugin.
*
* SexHackMe Wordpress Plugin is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* SexHackMe Wordpress Plugin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with SexHackMe Wordpress Plugin. If not, see <https://www.gnu.org/licenses/>.
*/
namespace wp_SexHackMe;
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit;
if(!class_exists('SH_VideoStream')) {
require_once("functions-utils.php");
class SH_VideoStream
{
private $path = "";
private $stream = "";
private $buffer = 102400;
private $start = -1;
private $end = -1;
private $size = 0;
function __construct($filePath, $download=false)
{
$this->path = $filePath;
$this->filename = basename($filePath);
$this->myme_type = sh_mime_type($filePath);
$this->download_type = $download;
}
/**
* Open stream
*/
private function open()
{
if (!($this->stream = fopen($this->path, 'rb'))) {
die('Could not open stream for reading');
}
}
/**
* Set proper header to serve the video content
*/
private function setHeader()
{
ob_get_clean();
header("Content-Type: ".$this->mime_type);
header("Cache-Control: max-age=2592000, public");
header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
header("Last-Modified: ".gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT' );
if($this->download_type) {
header('Content-Transfer-Encoding: binary');
header("Content-Disposition: attachment filename=$this->filename;");
}
$this->start = 0;
$this->size = filesize($this->path);
$this->end = $this->size - 1;
header("Accept-Ranges: 0-".$this->end);
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $this->start;
$c_end = $this->end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
if ($range == '-') {
$c_start = $this->size - substr($range, 1);
}else{
$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
}
$c_end = ($c_end > $this->end) ? $this->end : $c_end;
if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
$this->start = $c_start;
$this->end = $c_end;
$length = $this->end - $this->start + 1;
fseek($this->stream, $this->start);
header('HTTP/1.1 206 Partial Content');
header("Content-Length: ".$length);
header("Content-Range: bytes $this->start-$this->end/".$this->size);
}
else
{
header("Content-Length: ".$this->size);
}
}
/**
* close curretly opened stream
*/
private function end()
{
fclose($this->stream);
exit;
}
/**
* perform the streaming of calculated range
*/
private function stream()
{
$i = $this->start;
set_time_limit(0);
while(!feof($this->stream) && $i <= $this->end) {
$bytesToRead = $this->buffer;
if(($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = fread($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
}
/**
* Start streaming video content
*/
function start()
{
$this->open();
$this->setHeader();
$this->stream();
$this->end();
}
}
function shVideoStream($file, $download=false)
{
return new SH_VideoStream($file, $download);
}
}
?>
...@@ -32,8 +32,11 @@ if(!class_exists("SH_VideoProducts")) { ...@@ -32,8 +32,11 @@ if(!class_exists("SH_VideoProducts")) {
public function __construct() public function __construct()
{ {
//add_action('sh_save_video_after_query', array($this, 'sync_product_from_video'), 1, 10); //add_action('sh_save_video_after_query', array($this, 'sync_product_from_video'), 1, 10);
// fired when saving SH videos to sync the product concurrently.
add_filter('video_before_save', array($this, 'sync_product_from_video')); add_filter('video_before_save', array($this, 'sync_product_from_video'));
// fired when deleting a video
add_action('sh_delete_video', array($this, 'delete_video_product'), 9, 1); add_action('sh_delete_video', array($this, 'delete_video_product'), 9, 1);
} }
...@@ -70,8 +73,9 @@ if(!class_exists("SH_VideoProducts")) { ...@@ -70,8 +73,9 @@ if(!class_exists("SH_VideoProducts")) {
if(is_numeric($video->thumbnail)) if(is_numeric($video->thumbnail))
$prod->set_image_id( intval($video->thumbnail )); $prod->set_image_id( intval($video->thumbnail ));
// Product status. // Product status. Note we publish the product only
if($video->status == 'published') // if is there a downloadable video.
if(($video->status == 'published') && ($video->has_downloads()))
$prod->set_status('publish'); $prod->set_status('publish');
else else
$prod->set_status('draft'); $prod->set_status('draft');
......
...@@ -24,22 +24,28 @@ namespace wp_SexHackMe; ...@@ -24,22 +24,28 @@ namespace wp_SexHackMe;
// Exit if accessed directly // Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit; if ( ! defined( 'ABSPATH' ) ) exit;
function save_sexhack_video_meta_box_data( $post_id ) function save_sexhack_video_forms( $post_id )
{ {
// Verify that the nonce is set and valid. // Verify that the nonce is set and valid.
if (!isset( $_POST['sh_video_description_nonce'])
|| !wp_verify_nonce( $_POST['sh_video_description_nonce'], 'video_description_nonce' ) ) { if ((!isset( $_POST['sh_video_description_nonce']) || !wp_verify_nonce( $_POST['sh_video_description_nonce'], 'video_description_nonce' ))
&& (!isset( $_POST['sh_editvideo_nonce']) || !wp_verify_nonce( $_POST['sh_editvideo_nonce'], 'sh_editvideo')))
{
return; return;
} }
$admin=false;
if(isset( $_POST['sh_video_description_nonce']) && wp_verify_nonce( $_POST['sh_video_description_nonce'], 'video_description_nonce' )) $admin=true;
// We need to be executed only when post_type is set... // We need to be executed only when post_type is set...
if(!isset($_POST['post_type'])) return; if(!isset($_POST['post_type'])) return;
// ... ant it's set to sexhack_video // ... ant it's set to sexhack_video
if($_POST['post_type']!='sexhack_video') return; if($_POST['post_type']!='sexhack_video') return;
// Make sure we don't get caught in any loop // Make sure we don't get caught in any loop
unset($_POST['sh_video_description_nonce']); if($admin) unset($_POST['sh_video_description_nonce']);
if(!$admin) unset($_POST['sh_editvideo_nonce']);
// If this is an autosave, our form has not been submitted, so we don't want to do anything. // If this is an autosave, our form has not been submitted, so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
...@@ -54,7 +60,7 @@ function save_sexhack_video_meta_box_data( $post_id ) ...@@ -54,7 +60,7 @@ function save_sexhack_video_meta_box_data( $post_id )
} }
} }
else { else { // XXX Add more specific permission for our pages?
if ( ! current_user_can( 'edit_post', $post_id ) ) { if ( ! current_user_can( 'edit_post', $post_id ) ) {
return; return;
} }
...@@ -85,28 +91,38 @@ function save_sexhack_video_meta_box_data( $post_id ) ...@@ -85,28 +91,38 @@ function save_sexhack_video_meta_box_data( $post_id )
// TODO Remove debug // TODO Remove debug
//sexhack_log("SAVE post object:"); //sexhack_log("SAVE post object:");
// sexhack_log($post); // sexhack_log($post);
// sexhack_log(' - $POST:'); // sexhack_log(' - $POST:');
// sexhack_log($_POST); // sexhack_log($_POST);
// Model // Model
if(array_key_exists('video_model', $_POST) && is_numeric($_POST['video_model']) && intval($_POST['video_model']) > 0) if($admin) {
$video->user_id = intval($_POST['video_model']); if(array_key_exists('video_model', $_POST) && is_numeric($_POST['video_model']) && intval($_POST['video_model']) > 0)
$video->user_id = intval($_POST['video_model']);
} else {
$video->user_id = get_current_user_id();
}
// Video description // Video description
$video->description = sanitize_text_field( $_POST['video_description'] ); $video->description = sanitize_text_field( $_POST['video_description'] );
// Video thumbnail // Video thumbnail
if(array_key_exists('video_thumbnail', $_POST) && sanitize_text_field($_POST['video_thumbnail'])) if($admin) {
$video->thumbnail = sanitize_text_field( $_POST['video_thumbnail'] ); if(array_key_exists('video_thumbnail', $_POST) && sanitize_text_field($_POST['video_thumbnail']))
else if(array_key_exists('_thumbnail_id', $_POST) $video->thumbnail = sanitize_text_field( $_POST['video_thumbnail'] );
&& is_numeric($_POST['_thumbnail_id']) else if(array_key_exists('_thumbnail_id', $_POST)
&& intval($_POST['_thumbnail_id']) > 0) && is_numeric($_POST['_thumbnail_id'])
{ && intval($_POST['_thumbnail_id']) > 0)
$video->thumbnail = intval($_POST['_thumbnail_id']); {
$video->thumbnail = intval($_POST['_thumbnail_id']);
}
else
$video->thumbnail = false;
} else {
// Shoudn't we move it somewhere?
if(isset($_POST['filename_thumb'])) $video->thumbnail = sanitize_text_field($_POST['filename_thumb']);
else $video->thumbnail = false;
} }
else
$video->thumbnail = false;
// Video status // Video status
$validstatuses = array('creating','uploading','queue','processing','ready','published','error'); $validstatuses = array('creating','uploading','queue','processing','ready','published','error');
...@@ -134,6 +150,8 @@ function save_sexhack_video_meta_box_data( $post_id ) ...@@ -134,6 +150,8 @@ function save_sexhack_video_meta_box_data( $post_id )
if(array_key_exists('video_vr_projection', $_POST) && in_array($_POST['video_vr_projection'], array('VR180_LR','VR360_LR'))) if(array_key_exists('video_vr_projection', $_POST) && in_array($_POST['video_vr_projection'], array('VR180_LR','VR360_LR')))
$video->vr_projection = $_POST['video_vr_projection']; $video->vr_projection = $_POST['video_vr_projection'];
// XXX Arrivato qui
// Preview video // Preview video
if(array_key_exists('video_preview', $_POST) && check_url_or_path(sanitize_text_field($_POST['video_preview']))) if(array_key_exists('video_preview', $_POST) && check_url_or_path(sanitize_text_field($_POST['video_preview'])))
$video->preview = sanitize_text_field($_POST['video_preview']); $video->preview = sanitize_text_field($_POST['video_preview']);
......
...@@ -38,8 +38,7 @@ function pms_get_redirect_url($url, $location=false) ...@@ -38,8 +38,7 @@ function pms_get_redirect_url($url, $location=false)
{ {
if( !isset( $_POST['pay_gate'] ) || $_POST['pay_gate'] != 'manual' ) if( !isset( $_POST['pay_gate'] ) || $_POST['pay_gate'] != 'manual' )
return $url; return $url;
// XXX BUG apply_filter ont found?? return apply_filters('sh_get_redirect_url', $url, $location);
return apply_filter('sh_get_redirect_url', $url, $location);
} }
?> ?>
...@@ -317,4 +317,81 @@ function checkbox($res) ...@@ -317,4 +317,81 @@ function checkbox($res)
} }
function sh_mime_type($filename) {
$mime_types = array(
'txt' => 'text/plain',
'htm' => 'text/html',
'html' => 'text/html',
'php' => 'text/html',
'css' => 'text/css',
'js' => 'application/javascript',
'json' => 'application/json',
'xml' => 'application/xml',
'swf' => 'application/x-shockwave-flash',
'flv' => 'video/x-flv',
// images
'png' => 'image/png',
'jpe' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'bmp' => 'image/bmp',
'ico' => 'image/vnd.microsoft.icon',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'svg' => 'image/svg+xml',
'svgz' => 'image/svg+xml',
// archives
'zip' => 'application/zip',
'rar' => 'application/x-rar-compressed',
'exe' => 'application/x-msdownload',
'msi' => 'application/x-msdownload',
'cab' => 'application/vnd.ms-cab-compressed',
// audio/video
'mp3' => 'audio/mpeg',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
'm3u8' => 'application/vnd.apple.mpegurl',
// adobe
'pdf' => 'application/pdf',
'psd' => 'image/vnd.adobe.photoshop',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
// ms office
'doc' => 'application/msword',
'rtf' => 'application/rtf',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
// open office
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
);
$tmp = explode('.',$filename);
$ext = strtolower(end($tmp));
$tmp = explode('.',$filename);
$ext = strtolower(end($tmp));
if (array_key_exists($ext, $mime_types)) {
return $mime_types[$ext];
}else if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME);
$mimetype = finfo_file($finfo, $filename);
finfo_close($finfo);
return $mimetype;
} else {
return 'application/octet-stream';
}
}
?> ?>
...@@ -94,6 +94,11 @@ function sh_get_video_from_slug($slug) ...@@ -94,6 +94,11 @@ function sh_get_video_from_slug($slug)
return SH_Query::get_VideoFromSlug($slug); return SH_Query::get_VideoFromSlug($slug);
} }
function sh_get_video_from_hls($vpath, $level="public")
{
return SH_Query::get_VideoFromHLS($vpath, $level);
}
function sh_get_categories($id=false) function sh_get_categories($id=false)
{ {
return SH_Query::get_Categories($id); return SH_Query::get_Categories($id);
......
...@@ -72,7 +72,67 @@ if ( ! defined( 'ABSPATH' ) ) exit; ...@@ -72,7 +72,67 @@ if ( ! defined( 'ABSPATH' ) ) exit;
<p class="description">Select Video not found page</p> <p class="description">Select Video not found page</p>
</td> </td>
</tr> </tr>
<tr>
<td>
<label> Use filter script for HLS?</label>
<input type="checkbox" name="sexhack_shmdown" value='1' <?php if(get_option('sexhack_shmdown', false)) echo "checked"; ?>>
</td>
<td>
<label>HLS Filter script URI</label>
<input type='text' name='sexhack_shmdown_uri' value='<?php echo get_option('sexhack_shmdown_uri', ''); ?>'>
</td>
</tr>
<tr>
<td>
<label>Video Upload TMP path</label>
<input type='text' name='sexhack_video_tmp_path' value='<?php echo get_option('sexhack_video_tmp_path', '/tmp'); ?>'>
</td>
</tr>
<tr>
<td>
<label>Video Upload FLAT path</label>
<input type='text' name='sexhack_video_flat_path' value='<?php echo get_option('sexhack_video_flat_path', '/tmp'); ?>'>
</td>
</tr>
<tr>
<td>
<label>Video Upload VR path</label>
<input type='text' name='sexhack_video_vr_path' value='<?php echo get_option('sexhack_video_vr_path', '/tmp'); ?>'>
</td>
</tr>
<tr>
<td>
<label>Video Storage HLS</label>
<input type='text' name='sexhack_video_hls_storage' value='<?php echo get_option('sexhack_video_hls_storage', ABSPATH.'HLS'); ?>'>
</td>
</tr>
<tr>
<td>
<label>Video Storage Video</label>
<input type='text' name='sexhack_video_video_storage' value='<?php echo get_option('sexhack_video_video_storage', ABSPATH.'Videos'); ?>'>
</td>
</tr>
<tr>
<td>
<label>Video Storage Photo</label>
<input type='text' name='sexhack_video_photo_storage' value='<?php echo get_option('sexhack_video_photo_storage', ABSPATH.'Photos'); ?>'>
</td>
</tr>
<tr>
<td>
<label>Video Storage GIF</label>
<input type='text' name='sexhack_video_gif_storage' value='<?php echo get_option('sexhack_video_gif_storage', ABSPATH.'GIF'); ?>'>
</td>
</tr>
<tr>
<td>
<label>Video Storage VR</label>
<input type='text' name='sexhack_video_vr_storage' value='<?php echo get_option('sexhack_video_vr_storage', ABSPATH.'VR'); ?>'>
</td>
</tr>
</table> </table>
<?php submit_button(); ?> <?php submit_button(); ?>
</form> </form>
......
...@@ -34,6 +34,6 @@ $post = $video->get_post(); ...@@ -34,6 +34,6 @@ $post = $video->get_post();
<form class="fileUpload" enctype="multipart/form-data"> <form class="fileUpload" enctype="multipart/form-data">
<div class="form-group"> <div class="form-group">
<label>Choose File:</label> <label>Choose File:</label>
<input type="file" id="file" accept="image/*" /> <input type="file" id="file" accept="video/*" />
</div> </div>
</form> </form>
...@@ -21,34 +21,451 @@ ...@@ -21,34 +21,451 @@
// Exit if accessed directly // Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit; if ( ! defined( 'ABSPATH' ) ) exit;
$video = new \wp_SexHackMe\SH_Video();
$uniqid = uniqid();
// XXX BUG Check better the form for guest, it makes soooo much shit
?>
<script type="text/javascript">
window.guestChange = function(trig)
{
if(trig.value > 0)
{
var newsel = $('.guest_selection').clone();
var vgar = $('.guest_list p select[name="vguests[]"]').find(':selected').map(function() {return $(this).val()}).get().slice(1);
console.log(vgar);
if($.inArray(0, vgar)===-1) {
newsel.insertAfter($('.guest_list p').last());
newsel.show();
newsel.removeClass('guest_selection');
}
} else {
$('.guest_list p').last().remove();
//$('.guest_list p').last().find('select').prop('disabled', false);
}
}
</script>
<p>
<h4>Title:</h4>
<input type="text" style="width:100%" name="post_title" size="30" placeholder="Video title" value="" id="title" spellcheck="true" autocomplete="off" />
<input type="hidden" name="uniqid" value="<?php echo $uniqid ?>" />
</p>
<p>
<h4>Description:</h4>
<textarea style="width:100%" id="video_description" name="video_description" placeholder="Video description"></textarea>
</p>
<p>
<h4>Privacy:</h4>
<p>
<label> * Show video in public gallery?</label>
<input type='radio' name='video_visible' value='Y' <?php if($video->visible=='Y') echo "checked"; ?>>Yes</input>
<input type='radio' name='video_visible' value='N' <?php if($video->visible=='N') echo "checked"; ?>>No</input>
</p>
<p>
<label> * Show video in profile gallery?</label>
<input type='radio' name='video_private' value='N' <?php if($video->private=='N') echo "checked"; ?>>Yes</input>
<input type='radio' name='video_private' value='Y' <?php if($video->private=='Y') echo "checked"; ?>>No</input>
</p>
</p>
<p>
<p>
<h4>Virtual Reality</h4>
<p>
<label> * VR Video?:</label>
<input type='radio' name='video_type' value='VR' <?php if($video->video_type=='VR') echo "checked"; ?>>Yes</input>
<input type='radio' name='video_type' value='FLAT' <?php if($video->video_type=='FLAT') echo "checked"; ?>>No</input>
</p>
<p>
<label> * VR Projection</label>
<select name='video_vr_projection' id='video_vr_projection'>
<option value='VR180_LR' <?php if($video->vr_projection=='VR180_LR') echo "selected"; ?>>Equirectangular 180 LR</option>
<option value='VR360_LR' <?php if($video->vr_projection=='VR360_LR') echo "selected"; ?>>Equirectangular 360 LR</option>
</select>
<label>(ignored for non VR videos)</option>
</p>
</p>
<p>
<h4>Download Price:</h4>
<label>USD:</label>
<input type='text' name="video_price" value='<?php echo esc_attr( $video->price ); ?>' />
</p>
<!-- (A) UPLOAD BUTTON & LIST -->
<?php
foreach(array('public','members','premium') as $level) { ?>
<p>
<h4><?php echo ucfirst($level); ?> Video:</h4>
<form>
<div id="dropvideo_<?php echo $level; ?>" style="text-align:center;padding: 25px; border: 1px solid #534b4b;background: #232121;">DROP FILE HERE</div>
<div id="newvideo_<?php echo $level; ?>_list"></div>
<input type="button" id="upBrowse_<?php echo $level; ?>" value="Browse">
<input type="button" id="upToggle_<?php echo $level; ?>" value="Pause OR Continue">
<input type="button" id="delToggle_<?php echo $level; ?>" value="Cancel">
<input type="hidden" name="filename_<?php echo $level; ?>" value="">
</form>
<p>
<label> Include in Download?</label>
<input type='radio' name='video_isdownload_<?php echo $level; ?>' value='Y' <?php if($video->has_downloads($level)) echo "checked"; ?>>Yes</input>
<input type='radio' name='video_isdownload <?php echo $level; ?>' value='N' <?php if(!$video->has_downloads($level)) echo "checked"; ?>>No</input>
</p>
</p>
<?php
}
$cats = \wp_SexHackMe\sh_get_categories();
?>
<p>
<h4>Categories:</h4>
<div class="wrap">
<table class="form-table" id="catstable">
<?php
$ct=0;
foreach($cats as $cat)
{
if($ct == 0) echo "<tr align=\"top\">";
elseif($ct % 5 == 0) echo "</tr><tr align=\"top\">";
echo "<td>";
echo "<p><input type='checkbox' name='vcategory[]' value='".$cat->id."' ";
if($video->has_category($cat->id)) echo "checked />";
echo "<label>".$cat->category."</label></p>\n";
echo "</td>";
$ct+=1;
}
echo "</tr>";
?>
</table>
</div>
</p>
<p>
<h4>Models:</h4>
<div class="wrap">
<table class="form-table">
<?php
$models = get_users( array( 'role__in' => array( 'model' ) ) );
/*
<tr align="top">
<td>
<p><label>Select Model user</label></p>
<?php // XXX When this will be with thousands of model will definely not scale! ?>
<select name='video_model'>
<?php
$models = get_users( array( 'role__in' => array( 'model' ) ) );
foreach($models as $user)
{
echo "<option value='".$user->ID."' ";
if($video->user_id==$user->ID) echo "selected";
echo '>'.$user->user_login." (id:".$user->ID.")</option>";
} ?>
</select>
</td>
</tr>
*/ ?>
<tr align="top">
<td class='guest_list'>
<p style="display:none" class="guest_selection">
<select name='vguests[]' onchange='javascript:guestChange(this);'>
<option value="0">NO GUEST</option>
<?php
foreach($models as $user)
{
echo "<option value='".$user->ID."' ";
echo '>'.$user->user_login." (id:".$user->ID.")</option>";
}
?>
</select>
</p>
<p>
<label>Add guest model</label>
<?php // XXX When this will be with thousands of model will definely not scale! ?>
</p>
<p>
<select name='vguests[]' onchange='javascript:guestChange(this);'>
<option value="0">NO GUEST</option>
<?php
foreach($models as $user)
{
echo "<option value='".$user->ID."' ";
echo '>'.$user->user_login." (id:".$user->ID.")</option>";
} ?>
</select>
</p>
<?php
foreach($video->get_guests(true) as $uid => $guest)
{
?>
<p>
<select name='vguests[]' onchange='javascript:guestChange(this);'>
<option value="0">NO GUEST</option>
<?php
foreach($models as $user)
{
echo "<option value='".$user->ID."' ";
if($uid==$user->ID) echo "selected";
echo '>'.$user->user_login." (id:".$user->ID.")</option>";
} ?>
</select>
</p>
<?php
}
?>
</td>
</tr>
</table>
</div>
<p>
<h4>Tags:</h4>
<div id="shvtags">
<?php
foreach($video->get_tags() as $tag)
{
echo " <span>".$tag->tag."</span>\n";
}
?>
<input type="text" value="" placeholder="Add a tag" />
</div>
</p>
<p>
<div width="100%">
<br><br><br>
<p class="howto" id="new-tag-video_tags-desc">Insert tag, confirm with enter or comma</p>
</div>
</p>
<div id="vtagsdata"></div>
<?php
foreach($video->get_tags() as $tag)
{
echo " <input type='hidden' name='video_tags[]' data='".$tag->tag."' value='".$tag->tag."' />\n";
}
$titlear = array(
'preview' => 'Preview video',
'thumb' => 'Thumbnail image',
'gif' => 'GIF Video preview',
'gif_small' => 'Small GIF for thumbnail preview');
foreach(array('thumb','gif_small','gif','preview') as $imgt) {
?> ?>
<script language="javascript">
<p>
<h4><?php echo $titlear[$imgt]; if($imgt=='thumb') echo " (Strongly suggested)" ?>:</h4>
<form>
<div id="dropvideo_<?php echo $imgt; ?>" style="text-align:center;padding: 25px; border: 1px solid #534b4b;background: #232121;">DROP FILE HERE</div>
<div id="newvideo_<?php echo $imgt; ?>_list"></div>
<input type="button" id="upBrowse_<?php echo $imgt; ?>" value="Browse">
<input type="button" id="upToggle_<?php echo $imgt; ?>" value="Pause OR Continue">
<input type="button" id="delToggle_<?php echo $imgt; ?>" value="Cancel">
<input type="hidden" name="filename_<?php echo $imgt; ?>" value="">
</form>
</p>
<?php } ?>
<p>
<div style="align:center;text-align:center">
<input disabled type="button" id="send" value="Save Video">
</div>
</p>
<!-- (B) LOAD FLOWJS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/flow.js/2.14.1/flow.min.js"></script>
<script>
// (C) INIT FLOWJS
jQuery(function($) { jQuery(function($) {
$('body').on('change', '#file', function() {
$this = $(this); //$("#sh_admin_tabs .hidden").removeClass('hidden');
file_data = $(this).prop('files')[0]; //$("#sh_admin_tabs").tabs({'active': 0});
form_data = new FormData();
form_data.append('file', file_data);
form_data.append('action', 'file_upload'); // ::: TAGS BOX
form_data.append('security', '<?php echo wp_create_nonce( 'sh_video_upload' );?>'); $("#shvtags input").on({
focusout : function() {
$.ajax({ var txt = this.value.replace(/[^a-z0-9\+\-\.\#]/ig,''); // allowed characters
url: '<?php echo admin_url( 'admin-ajax.php' );?>', if(txt) {
$("<span/>", {text:txt.toLowerCase(), insertBefore:this});
$("<input>").attr( {type:"hidden",
name:"video_tags[]",
value:txt.toLowerCase(),
data:txt.toLowerCase(),
}).appendTo('#vtagsdata');
}
this.value = "";
},
keydown : function(ev) {
if(/(13)/.test(ev.which)) {
// Prevent enter key to send the form
ev.preventDefault();
return false;
}
},
keyup : function(ev) {
// if: comma|enter (delimit more keyCodes with | pipe)
if(/(188|13)/.test(ev.which)) {
$(this).focusout();
}
}
});
$('#shvtags').on('click', 'span', function() {
//if(confirm("Remove "+ $(this).text() +"?")) {
var txt=$(this).text();
$("input[name='video_tags[]'][data='"+txt+"']").remove();
$(this).remove();
//}
});
$('#send').on('click', function() {
console.log('uhmm');
formdata = new FormData();
formdata.append('action', 'sh_editvideo');
formdata.append('uniqid', '<?php echo $uniqid ?>');
formdata.append('sh_editvideo_nonce', '<?php echo wp_create_nonce( 'sh_editvideo' );?>');
formdata.append('title', $('#title').val());
formdata.append('video_description', $('#video_description').val());
formdata.append('video_visible', $('input[name="video_visible"]:checked').val());
formdata.append('video_private', $('input[name="video_private"]:checked').val());
formdata.append('video_type', $('input[name="video_type"]:checked').val());
formdata.append('video_vr_projection', $('#video_vr_projection').find(":selected").val());
formdata.append('video_price', $('input[name="video_price"]').val());
formdata.append('public_isdownload', $('input[name="video_isdownload_public"]').val());
formdata.append('members_isdownload', $('input[name="video_isdownload_members"]').val());
formdata.append('premium_isdownload', $('input[name="video_isdownload_premium"]').val());
formdata.append('categories', $("#catstable input:checkbox:checked").map(function(){ return $(this).val();}).get());
var guestar = $('.guest_list p select[name="vguests[]"]').find(':selected').map(function() { if($(this).val() > 0) return $(this).val()}).get();
formdata.append('guests', guestar.filter((item, index) => guestar.indexOf(item) === index));
formdata.append('tags', $('input[name="video_tags[]"]').map(function(){ return $(this).val()}).get());
formdata.append('post_type', 'sexhack_video');
<?php
foreach(array('public','members','premium','preview','thumb','gif','gif_small') as $level) { ?>
formdata.append('filename_<?php echo $level; ?>', $('input[name="filename_<?php echo $level; ?>"]').val());
<?php } ?>
$.ajax({url: '<?php echo admin_url( 'admin-ajax.php' );?>',
type: 'POST', type: 'POST',
contentType: false, contentType: false,
processData: false, processData: false,
data: form_data, data: formdata,
success: function (response) { success: function(response) {
$this.val(''); alert('saved');
alert('File uploaded successfully.'); }
} });
}); });
// (C1) NEW FLOW OBJECT
<?php
foreach(array('public','members','premium','preview','thumb','gif','gif_small') as $level) { ?>
var flow_<?php echo $level; ?> = new Flow({
target: '<?php echo admin_url( 'admin-ajax.php' ); ?>',
chunkSize: 1024*1024, // 1MB
uploadMethod:'POST',
testChunks:false,
query:{action:'file_upload', uniqid:'<?php echo $uniqid ?>', security:'<?php echo wp_create_nonce( 'sh_video_upload');?>', level:'<?php echo $level; ?>'},
singleFile: true
});
var flowuploads=0;
var needsupload=0;
function canbesaved() {
console.log(flowuploads+" - "+needsupload);
if(flowuploads < 1 && needsupload > 0 && document.getElementById('title').value.length > 2)
{
console.log("cansave");
document.getElementById('send').disabled=false;
} else {
console.log("can't save");
document.getElementById('send').disabled=true;
}
}
document.getElementById('title').addEventListener('input', canbesaved);
document.getElementById('title').addEventListener('propertychange', canbesaved);
if (flow_<?php echo $level; ?>.support) {
// (C2) ASSIGN BROWSE BUTTON
flow_<?php echo $level; ?>.assignBrowse(document.getElementById("upBrowse_<?php echo $level; ?>"));
// OR DEFINE DROP ZONE
flow_<?php echo $level; ?>.assignDrop(document.getElementById("dropvideo_<?php echo $level; ?>"));
// (C3) ON FILE ADDED
flow_<?php echo $level; ?>.on("fileAdded", (file, evt) => {
flow_<?php echo $level; ?>.cancel();
document.getElementById("newvideo_<?php echo $level; ?>_list").innerHTML="";
let fileslot = document.createElement("div");
fileslot.id = file.uniqueIdentifier;
fileslot.innerHTML = `${file.name} (${file.size}) - <strong>0%</strong>`;
document.getElementById("newvideo_<?php echo $level; ?>_list").appendChild(fileslot);
});
// (C4) ON FILE SUBMITTED (ADDED TO UPLOAD QUEUE)
flow_<?php echo $level; ?>.on("filesSubmitted", (arr, evt) => {
flowuploads++;
flow_<?php echo $level; ?>.upload();
}); });
// (C5) ON UPLOAD PROGRESS
flow_<?php echo $level; ?>.on("fileProgress", (file, chunk) => {
let progress = (chunk.offset + 1) / file.chunks.length * 100;
progress = progress.toFixed(2) + "%";
let fileslot = document.getElementById(file.uniqueIdentifier);
fileslot = fileslot.getElementsByTagName("strong")[0];
fileslot.innerHTML = progress;
});
// (C6) ON UPLOAD SUCCESS
flow_<?php echo $level; ?>.on("fileSuccess", (file, message, chunk) => {
let fileslot = document.getElementById(file.uniqueIdentifier);
fileslot = fileslot.getElementsByTagName("strong")[0];
fileslot.innerHTML = "DONE";
if(flowuploads) flowuploads--;
console.log('CI SIAMO');
ppid=fileslot.parentElement.parentElement.id;
if(ppid=='newvideo_members_list' || ppid=='newvideo_public_list' || ppid=='newvideo_premium_list') needsupload++;
$('#'+ppid).parent().find('input[type="hidden"]').val('<?php echo $uniqid."_"; ?>'+file.name);
canbesaved();
});
// (C7) ON UPLOAD ERROR
flow_<?php echo $level; ?>.on("fileError", (file, message) => {
let fileslot = document.getElementById(file.uniqueIdentifier);
fileslot = fileslot.getElementsByTagName("strong")[0];
fileslot.innerHTML = "ERROR";
if(flowuploads) flowuploads--;
canbesaved();
});
// (C8) PAUSE/CONTINUE UPLOAD
document.getElementById("upToggle_<?php echo $level; ?>").onclick = () => {
if (flow_<?php echo $level; ?>.isUploading()) { flow_<?php echo $level; ?>.pause(); }
else { flow_<?php echo $level; ?>.resume(); }
};
document.getElementById("delToggle_<?php echo $level; ?>").onclick = () => {
if (flow_<?php echo $level; ?>.isUploading()) {
flow_<?php echo $level; ?>.cancel();
if(flowuploads) flowuploads--;
canbesaved();
document.getElementById("newvideo_<?php echo $level; ?>_list").innerHTML="";
}
};
}
<?php } ?>
}); });
</script> </script>
<form class="fileUpload" enctype="multipart/form-data">
<div class="form-group">
<label>Choose File:</label>
<input type="file" id="file" accept="image/*" />
</div>
</form>
...@@ -25,6 +25,7 @@ namespace wp_SexHackMe; ...@@ -25,6 +25,7 @@ namespace wp_SexHackMe;
// Exit if accessed directly // Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit; if ( ! defined( 'ABSPATH' ) ) exit;
sexhack_log("PORCALAPUPAZZA"); sexhack_log("PORCALAPUPAZZA");
sexhack_log(get_query_var('sh_video', 'NONEEEEEEEEEEEEEE!!!')); sexhack_log(get_query_var('sh_video', 'NONEEEEEEEEEEEEEE!!!'));
...@@ -142,7 +143,8 @@ get_header(); ?> ...@@ -142,7 +143,8 @@ get_header(); ?>
</header><!-- .entry-header --> </header><!-- .entry-header -->
<div class="sexhack-video-container"> <div class="sexhack-video-container">
<?php <?php
$filterurl=false;
if(get_option('sexhack_shmdown', false)) $filterurl=get_option('sexhack_shmdown_uri', false);
if(in_array($tab, $avail)) if(in_array($tab, $avail))
{ {
switch($tab) switch($tab)
...@@ -181,8 +183,14 @@ get_header(); ?> ...@@ -181,8 +183,14 @@ get_header(); ?>
break; break;
default: // public too! default: // public too!
if($hls_public && $video->video_type=='VR') echo do_shortcode( "[sexvideo url=\"".$hls_public."\" posters=\"".$thumb."\"]" ); if($filterurl && $hls_public && $video->video_type=='VR')
else if($hls_public) echo do_shortcode( "[sexhls url=\"".$hls_public."\" posters=\"".$thumb."\"]" ); echo do_shortcode( "[sexvideo url=\"".wp_nonce_url($filterurl.$sh_video."/public/".basename($hls_public), 'shm_public_video-'.$video->id)."\" posters=\"".$thumb."\"]" );
else if($hls_public && $video->video_type=='VR')
echo do_shortcode( "[sexvideo url=\"".$hls_public."\" posters=\"".$thumb."\"]" );
else if($filterurl && $hls_public)
echo do_shortcode( "[sexhls url=\"".wp_nonce_url($filterurl.$sh_video."/public/".basename($hls_public), 'shm_public_video-'.$video->id)."\" posters=\"".$thumb."\"]" );
else if($hls_public)
echo do_shortcode( "[sexhls url=\"".$hls_public."\" posters=\"".$thumb."\"]" );
else if($video_preview) { else if($video_preview) {
//echo do_shortcode( "[sexvideo url=\"".$video_preview."\" posters=\"".$thumb."\"]" ); //echo do_shortcode( "[sexvideo url=\"".$video_preview."\" posters=\"".$thumb."\"]" );
// XXX BUG: sexvideo doesn't like google.drive.com/uc? videos for cross-site problems? // XXX BUG: sexvideo doesn't like google.drive.com/uc? videos for cross-site problems?
...@@ -229,9 +237,9 @@ get_header(); ?> ...@@ -229,9 +237,9 @@ get_header(); ?>
<?php <?php
echo $htmltags; echo $htmltags;
?> ?>
<?php if($video->has_downloads()) { ?>
<h3><a href="<?php echo get_permalink($video->product_id); ?>">Download the full lenght hi-res version of this video</a><h3> <h3><a href="<?php echo get_permalink($video->product_id); ?>">Download the full lenght hi-res version of this video</a><h3>
<?php } ?>
<hr> <hr>
<?php <?php
echo do_shortcode("[sexadv adv=".get_option('sexadv_video_bot')."]"); echo do_shortcode("[sexadv adv=".get_option('sexadv_video_bot')."]");
......
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit33072a4244de4b08826ca1e6cda560eb::getLoader();
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Flow' => array($vendorDir . '/flowjs/flow-php-server/src'),
);
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit33072a4244de4b08826ca1e6cda560eb
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit33072a4244de4b08826ca1e6cda560eb', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit33072a4244de4b08826ca1e6cda560eb', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit33072a4244de4b08826ca1e6cda560eb::getInitializer($loader));
$loader->register(true);
return $loader;
}
}
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit33072a4244de4b08826ca1e6cda560eb
{
public static $prefixesPsr0 = array (
'F' =>
array (
'Flow' =>
array (
0 => __DIR__ . '/..' . '/flowjs/flow-php-server/src',
),
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixesPsr0 = ComposerStaticInit33072a4244de4b08826ca1e6cda560eb::$prefixesPsr0;
$loader->classMap = ComposerStaticInit33072a4244de4b08826ca1e6cda560eb::$classMap;
}, null, ClassLoader::class);
}
}
{
"packages": [
{
"name": "flowjs/flow-php-server",
"version": "v1.2.0",
"version_normalized": "1.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/flowjs/flow-php-server.git",
"reference": "fe8890c25e835d0b4f58d8bd91331326ab1808ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/flowjs/flow-php-server/zipball/fe8890c25e835d0b4f58d8bd91331326ab1808ba",
"reference": "fe8890c25e835d0b4f58d8bd91331326ab1808ba",
"shasum": ""
},
"require": {
"php": ">=5.4"
},
"require-dev": {
"ext-mongodb": "*",
"fabpot/php-cs-fixer": "~2.2",
"league/phpunit-coverage-listener": "~1.1",
"mikey179/vfsstream": "v1.2.0",
"mongodb/mongodb": "^1.4.0",
"phpunit/phpunit": "4.*"
},
"suggest": {
"mongodb/mongodb": "Required to use this package with Mongo DB"
},
"time": "2022-08-26T15:50:37+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"Flow": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Aidas Klimas",
"email": "aidaskk@gmail.com"
}
],
"description": "PHP library for handling chunk uploads. Works with flow.js html5 file uploads.",
"keywords": [
"chunks",
"file upload",
"flow",
"flow.js",
"html5 file upload",
"resumable",
"resumable.js",
"upload"
],
"support": {
"issues": "https://github.com/flowjs/flow-php-server/issues",
"source": "https://github.com/flowjs/flow-php-server/tree/v1.2.0"
},
"install-path": "../flowjs/flow-php-server"
}
],
"dev": true,
"dev-package-names": []
}
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'flowjs/flow-php-server' => array(
'pretty_version' => 'v1.2.0',
'version' => '1.2.0.0',
'reference' => 'fe8890c25e835d0b4f58d8bd91331326ab1808ba',
'type' => 'library',
'install_path' => __DIR__ . '/../flowjs/flow-php-server',
'aliases' => array(),
'dev_requirement' => false,
),
),
);
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 50400)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.4.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}
# Ide
.idea
# Composer
composer.lock
/vendor
/build
\ No newline at end of file
language: php
dist: precise
php:
- 5.4
- 5.5
before_script:
- composer install --dev --no-interaction --prefer-source
script:
- mkdir -p build
- phpunit --configuration travis.phpunit.xml
# 1.0.0
## Breaking Changes
* Flow\Exception was replaced by Flow\FileOpenException and Flow\FileLockException
* php requirement was changed to >=5.4
* if chunk was not found, 204 status is returned instead of 404
\ No newline at end of file
The MIT License (MIT)
Copyright (c) 2013 Aidas Klimas
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
flow.js php server [![Build Status](https://travis-ci.org/flowjs/flow-php-server.png?branch=master)](https://travis-ci.org/flowjs/flow-php-server) [![Coverage Status](https://coveralls.io/repos/flowjs/flow-php-server/badge.png?branch=master)](https://coveralls.io/r/flowjs/flow-php-server?branch=master)
=======================
PHP library for handling chunk uploads. Library contains helper methods for:
* Testing if uploaded file chunk exists.
* Validating file chunk
* Creating separate chunks folder
* Validating uploaded chunks
* Merging all chunks to a single file
This library is compatible with HTML5 file upload library: https://github.com/flowjs/flow.js
How to get started?
--------------
Setup Composer: https://getcomposer.org/doc/00-intro.md
Run this command in your project:
```
composer require flowjs/flow-php-server
```
This will create a vendor directory for you, which contains an autoload.php file.
Create a new php file named `upload.php`:
```php
//Path to autoload.php from current location
require_once './vendor/autoload.php';
$config = new \Flow\Config();
$config->setTempDir('./chunks_temp_folder');
$request = new \Flow\Request();
$uploadFolder = './final_file_destination/'; // Folder where the file will be stored
$uploadFileName = uniqid()."_".$request->getFileName(); // The name the file will have on the server
$uploadPath = $uploadFolder.$uploadFileName;
if (\Flow\Basic::save($uploadPath, $config, $request)) {
// file saved successfully and can be accessed at $uploadPath
} else {
// This is not a final chunk or request is invalid, continue to upload.
}
```
Make sure that `./chunks_temp_folder` path exists and is writable. All chunks will be saved in this folder.
If you are stuck with this example, please read this issue: [How to use the flow-php-server](https://github.com/flowjs/flow-php-server/issues/3#issuecomment-46979467)
Advanced Usage
--------------
```php
$config = new \Flow\Config();
$config->setTempDir('./chunks_temp_folder');
$file = new \Flow\File($config);
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if ($file->checkChunk()) {
header("HTTP/1.1 200 Ok");
} else {
header("HTTP/1.1 204 No Content");
return ;
}
} else {
if ($file->validateChunk()) {
$file->saveChunk();
} else {
// error, invalid chunk upload request, retry
header("HTTP/1.1 400 Bad Request");
return ;
}
}
if ($file->validateFile() && $file->save('./final_file_name')) {
// File upload was completed
} else {
// This is not a final chunk, continue to upload
}
```
Delete unfinished files
-----------------------
For this you should setup cron, which would check each chunk upload time.
If chunk is uploaded long time ago, then chunk should be deleted.
Helper method for checking this:
```php
\Flow\Uploader::pruneChunks('./chunks_folder');
```
Cron task can be avoided by using random function execution.
```php
if (1 == mt_rand(1, 100)) {
\Flow\Uploader::pruneChunks('./chunks_folder');
}
```
Contribution
------------
Your participation in development is very welcome!
To ensure consistency throughout the source code, keep these rules in mind as you are working:
* All features or bug fixes must be tested by one or more specs.
* Your code should follow [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) coding style guide
{
"name": "flowjs/flow-php-server",
"description": "PHP library for handling chunk uploads. Works with flow.js html5 file uploads.",
"license": "MIT",
"authors": [
{
"name": "Aidas Klimas",
"email": "aidaskk@gmail.com"
}
],
"keywords": [
"flow.js",
"flow",
"resumable.js",
"resumable",
"upload",
"file upload",
"html5 file upload",
"chunks"
],
"require": {
"php": ">=5.4"
},
"require-dev": {
"mikey179/vfsstream": "v1.2.0",
"league/phpunit-coverage-listener": "~1.1",
"fabpot/php-cs-fixer": "~2.2",
"phpunit/phpunit": "4.*",
"mongodb/mongodb": "^1.4.0",
"ext-mongodb": "*"
},
"suggest": {
"mongodb/mongodb":"Required to use this package with Mongo DB"
},
"autoload": {
"psr-0": {
"Flow": "src"
}
}
}
#!/bin/bash
php ./vendor/phpunit/phpunit/phpunit "$@"
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="test/bootstrap.php"
>
<testsuites>
<testsuite name="Flow Test Suite">
<directory>./test/Unit/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/Flow/</directory>
<exclude>
<file>./src/Flow/Basic.php</file>
</exclude>
</whitelist>
</filter>
</phpunit>
<?php
namespace Flow;
class Autoloader
{
/**
* Directory path
*
* @var string
*/
private $dir;
/**
* Constructor
*
* @param string|null $dir
*/
public function __construct($dir = null)
{
if (is_null($dir)) {
$dir = __DIR__.'/..';
}
$this->dir = $dir;
}
/**
* Return directory path
*
* @return string
*/
public function getDir()
{
return $this->dir;
}
/**
* Register
*
* @codeCoverageIgnore
* @param string|null $dir
*/
public static function register($dir = null)
{
ini_set('unserialize_callback_func', 'spl_autoload_call');
spl_autoload_register(array(new self($dir), 'autoload'));
}
/**
* Handles autoloading of classes
*
* @param string $class A class name
*
* @return boolean Returns true if the class has been loaded
*/
public function autoload($class)
{
if (0 !== strpos($class, 'Flow')) {
return;
}
if (file_exists($file = $this->dir.'/'.str_replace('\\', '/', $class).'.php')) {
require $file;
}
}
}
<?php
namespace Flow;
/**
* Class Basic
*
* Example for handling basic uploads
*
* @package Flow
*/
class Basic
{
/**
* @param string $destination where to save file
* @param string|ConfigInterface $config
* @param RequestInterface $request optional
* @return bool
*/
public static function save($destination, $config, RequestInterface $request = null)
{
if (!$config instanceof ConfigInterface) {
$config = new Config(array(
'tempDir' => $config,
));
}
$file = new File($config, $request);
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if ($file->checkChunk()) {
header("HTTP/1.1 200 Ok");
} else {
// The 204 response MUST NOT include a message-body, and thus is always terminated by the first empty line after the header fields.
header("HTTP/1.1 204 No Content");
return false;
}
} else {
if ($file->validateChunk()) {
$file->saveChunk();
} else {
// error, invalid chunk upload request, retry
header("HTTP/1.1 400 Bad Request");
return false;
}
}
if ($file->validateFile() && $file->save($destination)) {
return true;
}
return false;
}
}
<?php
namespace Flow;
class Config implements ConfigInterface
{
/**
* Config
*
* @var array
*/
private $config;
/**
* Controller
*
* @param array $config
*/
public function __construct($config = array())
{
$this->config = $config;
}
/**
* Set path to temporary directory for chunks storage
*
* @param $path
*/
public function setTempDir($path)
{
$this->config['tempDir'] = $path;
}
/**
* Get path to temporary directory for chunks storage
*
* @return string
*/
public function getTempDir()
{
return isset($this->config['tempDir']) ? $this->config['tempDir'] : '';
}
/**
* Set chunk identifier
*
* @param callable $callback
*/
public function setHashNameCallback($callback)
{
$this->config['hashNameCallback'] = $callback;
}
/**
* Generate chunk identifier
*
* @return callable
*/
public function getHashNameCallback()
{
return isset($this->config['hashNameCallback']) ? $this->config['hashNameCallback'] : '\Flow\Config::hashNameCallback';
}
/**
* Callback to pre-process chunk
*
* @param callable $callback
*/
public function setPreprocessCallback($callback)
{
$this->config['preprocessCallback'] = $callback;
}
/**
* Callback to pre-process chunk
*
* @return callable|null
*/
public function getPreprocessCallback()
{
return isset($this->config['preprocessCallback']) ? $this->config['preprocessCallback'] : null;
}
/**
* Delete chunks on save
*
* @param bool $delete
*/
public function setDeleteChunksOnSave($delete)
{
$this->config['deleteChunksOnSave'] = $delete;
}
/**
* Delete chunks on save
*
* @return bool
*/
public function getDeleteChunksOnSave()
{
return isset($this->config['deleteChunksOnSave']) ? $this->config['deleteChunksOnSave'] : true;
}
/**
* Generate chunk identifier
*
* @param RequestInterface $request
*
* @return string
*/
public static function hashNameCallback(RequestInterface $request)
{
return sha1($request->getIdentifier());
}
}
<?php
namespace Flow;
interface ConfigInterface
{
/**
* Get path to temporary directory for chunks storage
*
* @return string
*/
public function getTempDir();
/**
* Generate chunk identifier
*
* @return callable
*/
public function getHashNameCallback();
/**
* Callback to pre-process chunk
*
* @param callable $callback
*/
public function setPreprocessCallback($callback);
/**
* Callback to preprocess chunk
*
* @return callable|null
*/
public function getPreprocessCallback();
/**
* Delete chunks on save
*
* @param bool $delete
*/
public function setDeleteChunksOnSave($delete);
/**
* Delete chunks on save
*
* @return bool
*/
public function getDeleteChunksOnSave();
}
<?php
namespace Flow;
class File
{
/**
* @var RequestInterface
*/
protected $request;
/**
* @var ConfigInterface
*/
private $config;
/**
* File hashed unique identifier
*
* @var string
*/
private $identifier;
/**
* Constructor
*
* @param ConfigInterface $config
* @param RequestInterface $request
*/
public function __construct(ConfigInterface $config, RequestInterface $request = null)
{
$this->config = $config;
if ($request === null) {
$request = new Request();
}
$this->request = $request;
$this->identifier = call_user_func($this->config->getHashNameCallback(), $request);
}
/**
* Get file identifier
*
* @return string
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* Return chunk path
*
* @param int $index
*
* @return string
*/
public function getChunkPath($index)
{
return $this->config->getTempDir().DIRECTORY_SEPARATOR.basename($this->identifier).'_'. (int) $index;
}
/**
* Check if chunk exist
*
* @return bool
*/
public function checkChunk()
{
return file_exists($this->getChunkPath($this->request->getCurrentChunkNumber()));
}
/**
* Validate file request
*
* @return bool
*/
public function validateChunk()
{
$file = $this->request->getFile();
if (!$file) {
return false;
}
if (!isset($file['tmp_name']) || !isset($file['size']) || !isset($file['error'])) {
return false;
}
if ($this->request->getCurrentChunkSize() != $file['size']) {
return false;
}
if ($file['error'] !== UPLOAD_ERR_OK) {
return false;
}
return true;
}
/**
* Save chunk
*
* @return bool
*/
public function saveChunk()
{
$file = $this->request->getFile();
return $this->_move_uploaded_file($file['tmp_name'], $this->getChunkPath($this->request->getCurrentChunkNumber()));
}
/**
* Check if file upload is complete
*
* @return bool
*/
public function validateFile()
{
$totalChunks = $this->request->getTotalChunks();
$totalChunksSize = 0;
for ($i = $totalChunks; $i >= 1; $i--) {
$file = $this->getChunkPath($i);
if (!file_exists($file)) {
return false;
}
$totalChunksSize += filesize($file);
}
return $this->request->getTotalSize() == $totalChunksSize;
}
/**
* Merge all chunks to single file
*
* @param string $destination final file location
*
*
* @throws FileLockException
* @throws FileOpenException
* @throws \Exception
*
* @return bool indicates if file was saved
*/
public function save($destination)
{
$fh = fopen($destination, 'wb');
if (!$fh) {
throw new FileOpenException('failed to open destination file: '.$destination);
}
if (!flock($fh, LOCK_EX | LOCK_NB, $blocked)) {
// @codeCoverageIgnoreStart
if ($blocked) {
// Concurrent request has requested a lock.
// File is being processed at the moment.
// Warning: lock is not checked in windows.
return false;
}
// @codeCoverageIgnoreEnd
throw new FileLockException('failed to lock file: '.$destination);
}
$totalChunks = $this->request->getTotalChunks();
try {
$preProcessChunk = $this->config->getPreprocessCallback();
for ($i = 1; $i <= $totalChunks; $i++) {
$file = $this->getChunkPath($i);
$chunk = fopen($file, "rb");
if (!$chunk) {
throw new FileOpenException('failed to open chunk: '.$file);
}
if ($preProcessChunk !== null) {
call_user_func($preProcessChunk, $chunk);
}
stream_copy_to_stream($chunk, $fh);
fclose($chunk);
}
} catch (\Exception $e) {
flock($fh, LOCK_UN);
fclose($fh);
throw $e;
}
if ($this->config->getDeleteChunksOnSave()) {
$this->deleteChunks();
}
flock($fh, LOCK_UN);
fclose($fh);
return true;
}
/**
* Delete chunks dir
*/
public function deleteChunks()
{
$totalChunks = $this->request->getTotalChunks();
for ($i = 1; $i <= $totalChunks; $i++) {
$path = $this->getChunkPath($i);
if (file_exists($path)) {
unlink($path);
}
}
}
/**
* This method is used only for testing
*
* @private
* @codeCoverageIgnore
*
* @param string $filePath
* @param string $destinationPath
*
* @return bool
*/
public function _move_uploaded_file($filePath, $destinationPath)
{
return move_uploaded_file($filePath, $destinationPath);
}
}
<?php
namespace Flow;
class FileLockException extends \Exception
{
}
<?php
namespace Flow;
class FileOpenException extends \Exception
{
}
<?php
namespace Flow;
/**
* Class FustyRequest
*
* Imitates single file request as a single chunk file upload
*
* @package Flow
*/
class FustyRequest extends Request
{
private $isFusty = false;
public function __construct($params = null, $file = null)
{
parent::__construct($params, $file);
$this->isFusty = $this->getTotalSize() === null && $this->getFileName() && $this->getFile();
if ($this->isFusty) {
$this->params['flowTotalSize'] = isset($this->file['size']) ? $this->file['size'] : 0;
$this->params['flowTotalChunks'] = 1;
$this->params['flowChunkNumber'] = 1;
$this->params['flowChunkSize'] = $this->params['flowTotalSize'];
$this->params['flowCurrentChunkSize'] = $this->params['flowTotalSize'];
}
}
/**
* Checks if request is formed by fusty flow
* @return bool
*/
public function isFustyFlowRequest()
{
return $this->isFusty;
}
}
<?php
namespace Flow\Mongo;
use Flow\Config;
use MongoDB\GridFS\Bucket;
/**
* @codeCoverageIgnore
*/
class MongoConfig extends Config implements MongoConfigInterface
{
private $gridFs;
/**
* @param Bucket $gridFS storage of the upload (and chunks)
*/
function __construct(Bucket $gridFS)
{
parent::__construct();
$this->gridFs = $gridFS;
}
/**
* @return Bucket
*/
public function getGridFs()
{
return $this->gridFs;
}
}
\ No newline at end of file
<?php
namespace Flow\Mongo;
use Flow\ConfigInterface;
use MongoDB\GridFS\Bucket;
/**
* @codeCoverageIgnore
*/
interface MongoConfigInterface extends ConfigInterface
{
/**
* @return Bucket
*/
public function getGridFs();
}
\ No newline at end of file
<?php
namespace Flow\Mongo;
use Exception;
use Flow\File;
use Flow\Request;
use Flow\RequestInterface;
use MongoDB\BSON\Binary;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Operation\FindOneAndReplace;
/**
* Notes:
*
* - One should ensure indices on the gridfs collection on the property 'flowIdentifier'.
* - Chunk preprocessor not supported (must not modify chunks size)!
* - Must use 'forceChunkSize=true' on client side.
*
* @codeCoverageIgnore
*/
class MongoFile extends File
{
private $uploadGridFsFile;
/**
* @var MongoConfigInterface
*/
private $config;
function __construct(MongoConfigInterface $config, RequestInterface $request = null)
{
if ($request === null) {
$request = new Request();
}
parent::__construct($config, $request);
$this->config = $config;
}
/**
* return array
*/
protected function getGridFsFile()
{
if (!$this->uploadGridFsFile) {
$gridFsFileQuery = $this->getGridFsFileQuery();
$changed = $gridFsFileQuery;
$changed['flowUpdated'] = new UTCDateTime();
$this->uploadGridFsFile = $this->config->getGridFs()->getFilesCollection()->findOneAndReplace($gridFsFileQuery, $changed,
['upsert' => true, 'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER]);
}
return $this->uploadGridFsFile;
}
/**
* @param $index int|string 1-based
* @return bool
*/
public function chunkExists($index)
{
return $this->config->getGridFs()->getChunksCollection()->findOne([
'files_id' => $this->getGridFsFile()['_id'],
'n' => (intval($index) - 1)
]) !== null;
}
public function checkChunk()
{
return $this->chunkExists($this->request->getCurrentChunkNumber());
}
/**
* Save chunk
* @param $additionalUpdateOptions array additional options for the mongo update/upsert operation.
* @return bool
* @throws Exception if upload size is invalid or some other unexpected error occurred.
*/
public function saveChunk($additionalUpdateOptions = [])
{
try {
$file = $this->request->getFile();
$chunkQuery = [
'files_id' => $this->getGridFsFile()['_id'],
'n' => intval($this->request->getCurrentChunkNumber()) - 1,
];
$chunk = $chunkQuery;
$data = file_get_contents($file['tmp_name']);
$actualChunkSize = strlen($data);
if ($actualChunkSize > $this->request->getDefaultChunkSize() ||
($actualChunkSize < $this->request->getDefaultChunkSize() &&
$this->request->getCurrentChunkNumber() != $this->request->getTotalChunks())
) {
throw new Exception("Invalid upload! (size: $actualChunkSize)");
}
$chunk['data'] = new Binary($data, Binary::TYPE_GENERIC);
$this->config->getGridFs()->getChunksCollection()->replaceOne($chunkQuery, $chunk, array_merge(['upsert' => true], $additionalUpdateOptions));
unlink($file['tmp_name']);
$this->ensureIndices();
return true;
} catch (Exception $e) {
// try to remove a possibly (partly) stored chunk:
if (isset($chunkQuery)) {
$this->config->getGridFs()->getChunksCollection()->deleteMany($chunkQuery);
}
throw $e;
}
}
/**
* @return bool
*/
public function validateFile()
{
$totalChunks = intval($this->request->getTotalChunks());
$storedChunks = $this->config->getGridFs()->getChunksCollection()
->countDocuments(['files_id' => $this->getGridFsFile()['_id']]);
return $totalChunks === $storedChunks;
}
/**
* Merge all chunks to single file
* @param $metadata array additional metadata for final file
* @return ObjectId|bool of saved file or false if file was already saved
* @throws Exception
*/
public function saveToGridFs($metadata = null)
{
$file = $this->getGridFsFile();
$file['flowStatus'] = 'finished';
$file['metadata'] = $metadata;
$result = $this->config->getGridFs()->getFilesCollection()->findOneAndReplace($this->getGridFsFileQuery(), $file);
// on second invocation no more file can be found, as the flowStatus changed:
if (is_null($result)) {
return false;
} else {
return $file['_id'];
}
}
public function save($destination)
{
throw new Exception("Must not use 'save' on MongoFile - use 'saveToGridFs'!");
}
public function deleteChunks()
{
// nothing to do, as chunks are directly part of the final file
}
public function ensureIndices()
{
$chunksCollection = $this->config->getGridFs()->getChunksCollection();
$indexKeys = ['files_id' => 1, 'n' => 1];
$indexOptions = ['unique' => true, 'background' => true];
$chunksCollection->createIndex($indexKeys, $indexOptions);
}
/**
* @return array
*/
protected function getGridFsFileQuery()
{
return [
'flowIdentifier' => $this->request->getIdentifier(),
'flowStatus' => 'uploading',
'filename' => $this->request->getFileName(),
'chunkSize' => intval($this->request->getDefaultChunkSize()),
'length' => intval($this->request->getTotalSize())
];
}
}
<?php
namespace Flow\Mongo;
use MongoDB\GridFS\Bucket;
/**
* @codeCoverageIgnore
*/
class MongoUploader
{
/**
* Delete chunks older than expiration time.
*
* @param Bucket $gridFs
* @param int $expirationTime seconds
*/
public static function pruneChunks($gridFs, $expirationTime = 172800)
{
$result = $gridFs->find([
'flowUpdated' => ['$lt' => new \MongoDB\BSON\UTCDateTime(time() - $expirationTime)],
'flowStatus' => 'uploading'
]);
foreach ($result as $file) {
$gridFs->delete($file['_id']);
}
}
}
Usage
--------------
* Must use 'forceChunkSize=true' on client side.
* Chunk preprocessor not supported.
* One should ensure indices on the gridfs files collection on the property 'flowIdentifier'.
Besides the points above, the usage is analogous to the 'normal' flow-php:
```php
$config = new \Flow\Mongo\MongoConfig($yourGridFs);
$file = new \Flow\Mongo\MongoFile($config);
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if ($file->checkChunk()) {
header("HTTP/1.1 200 Ok");
} else {
header("HTTP/1.1 204 No Content");
return ;
}
} else {
if ($file->validateChunk()) {
$file->saveChunk();
} else {
// error, invalid chunk upload request, retry
header("HTTP/1.1 400 Bad Request");
return ;
}
}
if ($file->validateFile()) {
// File upload was completed
$id = $file->saveToGridFs(['your metadata'=>'value']);
if($id) {
//do custom post processing here, $id is the MongoId of the gridfs file
}
} else {
// This is not a final chunk, continue to upload
}
```
Delete unfinished files
-----------------------
For this you should set up cron, which would check each chunk upload time.
If chunk is uploaded long time ago, then chunk should be deleted.
Helper method for checking this:
```php
\Flow\Mongo\MongoUploader::pruneChunks($yourGridFs);
```
<?php
namespace Flow;
class Request implements RequestInterface
{
/**
* Request parameters
*
* @var array
*/
protected $params;
/**
* File
*
* @var array
*/
protected $file;
/**
* Constructor
*
* @param array|null $params
* @param array|null $file
*/
public function __construct($params = null, $file = null)
{
if ($params === null) {
$params = $_REQUEST;
}
if ($file === null && isset($_FILES['file'])) {
$file = $_FILES['file'];
}
$this->params = $params;
$this->file = $file;
}
/**
* Get parameter value
*
* @param string $name
*
* @return string|int|null
*/
public function getParam($name)
{
return isset($this->params[$name]) ? $this->params[$name] : null;
}
/**
* Get uploaded file name
*
* @return string|null
*/
public function getFileName()
{
return $this->getParam('flowFilename');
}
/**
* Get total file size in bytes
*
* @return int|null
*/
public function getTotalSize()
{
return $this->getParam('flowTotalSize');
}
/**
* Get file unique identifier
*
* @return string|null
*/
public function getIdentifier()
{
return $this->getParam('flowIdentifier');
}
/**
* Get file relative path
*
* @return string|null
*/
public function getRelativePath()
{
return $this->getParam('flowRelativePath');
}
/**
* Get total chunks number
*
* @return int|null
*/
public function getTotalChunks()
{
return $this->getParam('flowTotalChunks');
}
/**
* Get default chunk size
*
* @return int|null
*/
public function getDefaultChunkSize()
{
return $this->getParam('flowChunkSize');
}
/**
* Get current uploaded chunk number, starts with 1
*
* @return int|null
*/
public function getCurrentChunkNumber()
{
return $this->getParam('flowChunkNumber');
}
/**
* Get current uploaded chunk size
*
* @return int|null
*/
public function getCurrentChunkSize()
{
return $this->getParam('flowCurrentChunkSize');
}
/**
* Return $_FILES request
*
* @return array|null
*/
public function getFile()
{
return $this->file;
}
/**
* Checks if request is formed by fusty flow
*
* @return bool
*/
public function isFustyFlowRequest()
{
return false;
}
}
<?php
namespace Flow;
interface RequestInterface
{
/**
* Get uploaded file name
*
* @return string
*/
public function getFileName();
/**
* Get total file size in bytes
*
* @return int
*/
public function getTotalSize();
/**
* Get file unique identifier
*
* @return string
*/
public function getIdentifier();
/**
* Get file relative path
*
* @return string
*/
public function getRelativePath();
/**
* Get total chunks number
*
* @return int
*/
public function getTotalChunks();
/**
* Get default chunk size
*
* @return int
*/
public function getDefaultChunkSize();
/**
* Get current uploaded chunk number, starts with 1
*
* @return int
*/
public function getCurrentChunkNumber();
/**
* Get current uploaded chunk size
*
* @return int
*/
public function getCurrentChunkSize();
/**
* Return $_FILES request
*
* @return array|null
*/
public function getFile();
/**
* Checks if request is formed by fusty flow
*
* @return bool
*/
public function isFustyFlowRequest();
}
<?php
namespace Flow;
class Uploader
{
/**
* Delete chunks older than expiration time.
*
* @param string $chunksFolder
* @param int $expirationTime seconds
*
* @throws FileOpenException
*/
public static function pruneChunks($chunksFolder, $expirationTime = 172800)
{
$handle = opendir($chunksFolder);
if (!$handle) {
throw new FileOpenException('failed to open folder: '.$chunksFolder);
}
while (false !== ($entry = readdir($handle))) {
if ($entry == "." || $entry == ".." || $entry == ".gitignore") {
continue;
}
$path = $chunksFolder.DIRECTORY_SEPARATOR.$entry;
if (is_dir($path)) {
continue;
}
if (time() - filemtime($path) > $expirationTime) {
unlink($path);
}
}
closedir($handle);
}
}
<?php
namespace Unit;
/**
* Autoload unit tests
*
* @coversDefaultClass \Flow\Autoloader
*
* @package Unit
*/
class AutoloadTest extends FlowUnitCase
{
/**
* @covers ::__construct
* @covers ::getDir
*/
public function testAutoloader_construct_default()
{
$expDir = realpath(__DIR__ . '/../../src/Flow') . '/..';
$autoloader = new \Flow\Autoloader();
$this->assertSame($expDir, $autoloader->getDir());
}
/**
* @covers ::__construct
* @covers ::getDir
*/
public function testAutoloader_construct_custom()
{
$expDir = __DIR__;
$autoloader = new \Flow\Autoloader($expDir);
$this->assertSame($expDir, $autoloader->getDir());
}
/**
* @covers ::autoload
*/
public function testClassesExist()
{
$autoloader = new \Flow\Autoloader();
$autoloader->autoload('noclass');
$this->assertFalse(class_exists('noclass', false));
$autoloader->autoload('Flow\NoClass');
$this->assertFalse(class_exists('Flow\NoClass', false));
$autoloader->autoload('Flow\File');
$this->assertTrue(class_exists('Flow\File'));
}
}
<?php
namespace Unit;
use Flow\Config;
use Flow\Request;
/**
* Config unit tests
*
* @coversDefaultClass \Flow\Config
*
* @package Unit
*/
class ConfigTest extends FlowUnitCase
{
/**
* @covers ::getTempDir
* @covers ::getDeleteChunksOnSave
* @covers ::getHashNameCallback
* @covers ::getPreprocessCallback
* @covers ::__construct
*/
public function testConfig_construct_config()
{
$exampleConfig = array(
'tempDir' => '/some/dir',
'deleteChunksOnSave' => TRUE,
'hashNameCallback' => '\SomeNs\SomeClass::someMethod',
'preprocessCallback' => '\SomeNs\SomeClass::preProcess'
);
$config = new Config($exampleConfig);
$this->assertSame($exampleConfig['tempDir'], $config->getTempDir());
$this->assertSame($exampleConfig['deleteChunksOnSave'], $config->getDeleteChunksOnSave());
$this->assertSame($exampleConfig['hashNameCallback'], $config->getHashNameCallback());
$this->assertSame($exampleConfig['preprocessCallback'], $config->getPreprocessCallback());
}
/**
* @covers ::getTempDir
* @covers ::getDeleteChunksOnSave
* @covers ::getHashNameCallback
* @covers ::getPreprocessCallback
* @covers ::__construct
*/
public function testConfig_construct_default()
{
$config = new Config();
$this->assertSame('', $config->getTempDir());
$this->assertSame(true, $config->getDeleteChunksOnSave());
$this->assertSame('\Flow\Config::hashNameCallback', $config->getHashNameCallback());
$this->assertSame(null, $config->getPreprocessCallback());
}
/**
* @covers ::setTempDir
* @covers ::getTempDir
*/
public function testConfig_setTempDir()
{
$dir = '/some/dir';
$config = new Config();
$config->setTempDir($dir);
$this->assertSame($dir, $config->getTempDir());
}
/**
* @covers ::setHashNameCallback
* @covers ::getHashNameCallback
*/
public function testConfig_setHashNameCallback()
{
$callback = '\SomeNs\SomeClass::someMethod';
$config = new Config();
$config->setHashNameCallback($callback);
$this->assertSame($callback, $config->getHashNameCallback());
}
/**
* @covers ::setPreprocessCallback
* @covers ::getPreprocessCallback
*/
public function testConfig_setPreprocessCallback()
{
$callback = '\SomeNs\SomeClass::someOtherMethod';
$config = new Config();
$config->setPreprocessCallback($callback);
$this->assertSame($callback, $config->getPreprocessCallback());
}
/**
* @covers ::setDeleteChunksOnSave
* @covers ::getDeleteChunksOnSave
*/
public function testConfig_setDeleteChunksOnSave()
{
$config = new Config();
$config->setDeleteChunksOnSave(false);
$this->assertFalse($config->getDeleteChunksOnSave());
}
public function testConfig_hashNameCallback()
{
$request = new Request($this->requestArr);
$expHash = sha1($request->getIdentifier());
$this->assertSame($expHash, Config::hashNameCallback($request));
}
}
<?php
namespace Unit;
use Flow\File;
use Flow\Config;
use Flow\FileLockException;
use Flow\FileOpenException;
use Flow\Request;
use \org\bovigo\vfs\vfsStreamWrapper;
use \org\bovigo\vfs\vfsStreamDirectory;
use \org\bovigo\vfs\vfsStream;
/**
* File unit tests
*
* @coversDefaultClass \Flow\File
*
* @package Unit
*/
class FileTest extends FlowUnitCase
{
/**
* Config
*
* @var Config
*/
protected $config;
/**
* Virtual file system
*
* @var vfsStreamDirectory
*/
protected $vfs;
protected function setUp()
{
parent::setUp();
// Setup virtual file system
vfsStreamWrapper::register();
$this->vfs = new vfsStreamDirectory('chunks');
vfsStreamWrapper::setRoot($this->vfs);
// Setup Config
$this->config = new Config();
$this->config->setTempDir($this->vfs->url());
}
/**
* @covers ::__construct
* @covers ::getIdentifier
*/
public function testFile_construct_withRequest()
{
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
$expIdentifier = sha1($this->requestArr['flowIdentifier']);
$this->assertSame($expIdentifier, $file->getIdentifier());
}
/**
* @covers ::__construct
* @covers ::getIdentifier
*/
public function testFile_construct_noRequest()
{
$_REQUEST = $this->requestArr;
$file = new File($this->config);
$expIdentifier = sha1($this->requestArr['flowIdentifier']);
$this->assertSame($expIdentifier, $file->getIdentifier());
}
/**
* @covers ::getChunkPath
*/
public function testFile_construct_getChunkPath()
{
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
$expPath = $this->vfs->url() . DIRECTORY_SEPARATOR . sha1($this->requestArr['flowIdentifier']) . '_1';
$this->assertSame($expPath, $file->getChunkPath(1));
}
/**
* @covers ::checkChunk
*/
public function testFile_construct_checkChunk()
{
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
$this->assertFalse($file->checkChunk());
$chunkName = sha1($request->getIdentifier()) . '_' . $request->getCurrentChunkNumber();
$firstChunk = vfsStream::newFile($chunkName);
$this->vfs->addChild($firstChunk);
$this->assertTrue($file->checkChunk());
}
/**
* @covers ::validateChunk
*/
public function testFile_validateChunk()
{
// No $_FILES
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
$this->assertFalse($file->validateChunk());
// No 'file' key $_FILES
$fileInfo = new \ArrayObject();
$request = new Request($this->requestArr, $fileInfo);
$file = new File($this->config, $request);
$this->assertFalse($file->validateChunk());
// Upload OK
$fileInfo->exchangeArray(array(
'size' => 10,
'error' => UPLOAD_ERR_OK,
'tmp_name' => ''
));
$this->assertTrue($file->validateChunk());
// Chunk size doesn't match
$fileInfo->exchangeArray(array(
'size' => 9,
'error' => UPLOAD_ERR_OK,
'tmp_name' => ''
));
$this->assertFalse($file->validateChunk());
// Upload error
$fileInfo->exchangeArray(array(
'size' => 10,
'error' => UPLOAD_ERR_EXTENSION,
'tmp_name' => ''
));
$this->assertFalse($file->validateChunk());
}
/**
* @covers ::validateFile
*/
public function testFile_validateFile()
{
$this->requestArr['flowTotalSize'] = 10;
$this->requestArr['flowTotalChunks'] = 3;
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
$chunkPrefix = sha1($request->getIdentifier()) . '_';
// No chunks uploaded yet
$this->assertFalse($file->validateFile());
// First chunk
$firstChunk = vfsStream::newFile($chunkPrefix . '1');
$firstChunk->setContent('123');
$this->vfs->addChild($firstChunk);
// Uploaded not yet complete
$this->assertFalse($file->validateFile());
// Second chunk
$secondChunk = vfsStream::newFile($chunkPrefix . '2');
$secondChunk->setContent('456');
$this->vfs->addChild($secondChunk);
// Uploaded not yet complete
$this->assertFalse($file->validateFile());
// Third chunk
$lastChunk = vfsStream::newFile($chunkPrefix . '3');
$lastChunk->setContent('7890');
$this->vfs->addChild($lastChunk);
// All chunks uploaded
$this->assertTrue($file->validateFile());
//// Test false values
// File size doesn't match
$lastChunk->setContent('789');
$this->assertFalse($file->validateFile());
// Correct file size and expect true
$this->requestArr['flowTotalSize'] = 9;
$this->assertTrue($file->validateFile());
}
/**
* @covers ::deleteChunks
*/
public function testFile_deleteChunks()
{
//// Setup test
$this->requestArr['flowTotalChunks'] = 4;
$fileInfo = new \ArrayObject();
$request = new Request($this->requestArr, $fileInfo);
$file = new File($this->config, $request);
$chunkPrefix = sha1($request->getIdentifier()) . '_';
$firstChunk = vfsStream::newFile($chunkPrefix . 1);
$this->vfs->addChild($firstChunk);
$secondChunk = vfsStream::newFile($chunkPrefix . 3);
$this->vfs->addChild($secondChunk);
$thirdChunk = vfsStream::newFile('other');
$this->vfs->addChild($thirdChunk);
//// Actual test
$this->assertTrue(file_exists($firstChunk->url()));
$this->assertTrue(file_exists($secondChunk->url()));
$this->assertTrue(file_exists($thirdChunk->url()));
$file->deleteChunks();
$this->assertFalse(file_exists($firstChunk->url()));
$this->assertFalse(file_exists($secondChunk->url()));
$this->assertTrue(file_exists($thirdChunk->url()));
}
/**
* @covers ::saveChunk
*/
public function testFile_saveChunk()
{
//// Setup test
// Setup temporary file
$tmpDir = new vfsStreamDirectory('tmp');
$tmpFile = vfsStream::newFile('tmpFile');
$tmpFile->setContent('1234567890');
$tmpDir->addChild($tmpFile);
$this->vfs->addChild($tmpDir);
$this->filesArr['file']['tmp_name'] = $tmpFile->url();
// Mock File to use rename instead of move_uploaded_file
$request = new Request($this->requestArr, $this->filesArr['file']);
$file = $this->getMock('Flow\File', array('_move_uploaded_file'), array($this->config, $request));
$file->expects($this->once())
->method('_move_uploaded_file')
->will($this->returnCallback(function ($filename, $destination) {
return rename($filename, $destination);
}));
// Expected destination file
$expDstFile = $this->vfs->url() . DIRECTORY_SEPARATOR . sha1($request->getIdentifier()) . '_1';
//// Accrual test
$this->assertFalse(file_exists($expDstFile));
$this->assertTrue(file_exists($tmpFile->url()));
/** @noinspection PhpUndefinedMethodInspection */
$this->assertTrue($file->saveChunk());
$this->assertTrue(file_exists($expDstFile));
//$this->assertFalse(file_exists($tmpFile->url()));
$this->assertSame('1234567890', file_get_contents($expDstFile));
}
/**
* @covers ::save
*/
public function testFile_save()
{
//// Setup test
$this->requestArr['flowTotalChunks'] = 3;
$this->requestArr['flowTotalSize'] = 10;
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
$chunkPrefix = sha1($request->getIdentifier()) . '_';
$chunk = vfsStream::newFile($chunkPrefix . '1', 0777);
$chunk->setContent('0123');
$this->vfs->addChild($chunk);
$chunk = vfsStream::newFile($chunkPrefix . '2', 0777);
$chunk->setContent('456');
$this->vfs->addChild($chunk);
$chunk = vfsStream::newFile($chunkPrefix . '3', 0777);
$chunk->setContent('789');
$this->vfs->addChild($chunk);
$filePath = $this->vfs->url() . DIRECTORY_SEPARATOR . 'file';
//// Actual test
$this->assertTrue($file->save($filePath));
$this->assertTrue(file_exists($filePath));
$this->assertEquals($request->getTotalSize(), filesize($filePath));
}
/**
* @covers ::save
*/
public function testFile_save_lock()
{
//// Setup test
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
$dstFile = $this->vfs->url() . DIRECTORY_SEPARATOR . 'file';
// Lock file
$fh = fopen($dstFile, 'wb');
$this->assertTrue(flock($fh, LOCK_EX));
//// Actual test
try {
// practically on a normal file system exception would not be thrown, this happens
// because vfsStreamWrapper does not support locking with block
$file->save($dstFile);
$this->fail();
} catch (FileLockException $e) {
$this->assertEquals('failed to lock file: ' . $dstFile, $e->getMessage());
}
}
/**
* @covers ::save
*/
public function testFile_save_FileOpenException()
{
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
try {
@$file->save('not/existing/path');
$this->fail();
} catch (FileOpenException $e) {
$this->assertEquals('failed to open destination file: not/existing/path', $e->getMessage());
}
}
/**
* @covers ::save
*/
public function testFile_save_chunk_FileOpenException()
{
//// Setup test
$this->requestArr['flowTotalChunks'] = 3;
$this->requestArr['flowTotalSize'] = 10;
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
$chunkPrefix = sha1($request->getIdentifier()) . '_';
$chunk = vfsStream::newFile($chunkPrefix . '1', 0777);
$chunk->setContent('0123');
$this->vfs->addChild($chunk);
$chunk = vfsStream::newFile($chunkPrefix . '2', 0777);
$chunk->setContent('456');
$this->vfs->addChild($chunk);
$missingChunk = $this->vfs->url() . DIRECTORY_SEPARATOR . $chunkPrefix . '3';
$filePath = $this->vfs->url() . DIRECTORY_SEPARATOR . 'file';
//// Actual test
try {
@$file->save($filePath);
} catch (FileOpenException $e) {
$this->assertEquals('failed to open chunk: ' . $missingChunk, $e->getMessage());
}
}
/**
* @covers ::save
*/
public function testFile_save_preProcess()
{
//// Setup test
$this->requestArr['flowTotalChunks'] = 1;
$this->requestArr['flowTotalSize'] = 10;
$processCalled = false;
$process = function($chunk) use (&$processCalled)
{
$processCalled = true;
};
$this->config->setPreprocessCallback($process);
$request = new Request($this->requestArr);
$file = new File($this->config, $request);
$chunkPrefix = sha1($request->getIdentifier()) . '_';
$chunk = vfsStream::newFile($chunkPrefix . '1', 0777);
$chunk->setContent('1234567890');
$this->vfs->addChild($chunk);
$filePath = $this->vfs->url() . DIRECTORY_SEPARATOR . 'file';
//// Actual test
$this->assertTrue($file->save($filePath));
$this->assertTrue(file_exists($filePath));
$this->assertEquals($request->getTotalSize(), filesize($filePath));
$this->assertTrue($processCalled);
}
}
<?php
namespace Unit;
use ArrayObject;
class FlowUnitCase extends \PHPUnit_Framework_TestCase
{
/**
* Test request
*
* @var array
*/
protected $requestArr;
/**
* $_FILES
*
* @var array
*/
protected $filesArr;
protected function setUp()
{
$this->requestArr = new ArrayObject(array(
'flowChunkNumber' => 1,
'flowChunkSize' => 1048576,
'flowCurrentChunkSize' => 10,
'flowTotalSize' => 100,
'flowIdentifier' => '13632-prettifyjs',
'flowFilename' => 'prettify.js',
'flowRelativePath' => 'home/prettify.js',
'flowTotalChunks' => 42
));
$this->filesArr = array(
'file' => array(
'name' => 'someFile.gif',
'type' => 'image/gif',
'size' => '10',
'tmp_name' => '/tmp/abc1234',
'error' => UPLOAD_ERR_OK
)
);
}
protected function tearDown()
{
$_REQUEST = array();
$_FILES = array();
}
}
<?php
namespace Unit;
use Flow\File;
use Flow\FustyRequest;
use Flow\Config;
use org\bovigo\vfs\vfsStreamWrapper;
use org\bovigo\vfs\vfsStreamDirectory;
use org\bovigo\vfs\vfsStream;
/**
* FustyRequest unit tests
*
* @coversDefaultClass \Flow\FustyRequest
*
* @package Unit
*/
class FustyRequestTest extends FlowUnitCase
{
/**
* Virtual file system
*
* @var vfsStreamDirectory
*/
protected $vfs;
protected function setUp()
{
parent::setUp();
vfsStreamWrapper::register();
$this->vfs = new vfsStreamDirectory('chunks');
vfsStreamWrapper::setRoot($this->vfs);
}
/**
* @covers ::__construct
* @covers ::isFustyFlowRequest
*/
public function testFustyRequest_construct()
{
$firstChunk = vfsStream::newFile('temp_file');
$firstChunk->setContent('1234567890');
$this->vfs->addChild($firstChunk);
$fileInfo = new \ArrayObject(array(
'size' => 10,
'error' => UPLOAD_ERR_OK,
'tmp_name' => $firstChunk->url()
));
$request = new \ArrayObject(array(
'flowIdentifier' => '13632-prettifyjs',
'flowFilename' => 'prettify.js',
'flowRelativePath' => 'home/prettify.js'
));
$fustyRequest = new FustyRequest($request, $fileInfo);
$this->assertSame('prettify.js', $fustyRequest->getFileName());
$this->assertSame('13632-prettifyjs', $fustyRequest->getIdentifier());
$this->assertSame('home/prettify.js', $fustyRequest->getRelativePath());
$this->assertSame(1, $fustyRequest->getCurrentChunkNumber());
$this->assertTrue($fustyRequest->isFustyFlowRequest());
$this->assertSame(10, $fustyRequest->getTotalSize());
$this->assertSame(10, $fustyRequest->getDefaultChunkSize());
$this->assertSame(10, $fustyRequest->getCurrentChunkSize());
$this->assertSame(1, $fustyRequest->getTotalChunks());
}
/**
*/
public function testFustyRequest_ValidateUpload()
{
//// Setup test
$firstChunk = vfsStream::newFile('temp_file');
$firstChunk->setContent('1234567890');
$this->vfs->addChild($firstChunk);
$fileInfo = new \ArrayObject(array(
'size' => 10,
'error' => UPLOAD_ERR_OK,
'tmp_name' => $firstChunk->url()
));
$request = new \ArrayObject(array(
'flowIdentifier' => '13632-prettifyjs',
'flowFilename' => 'prettify.js',
'flowRelativePath' => 'home/prettify.js'
));
$fustyRequest = new FustyRequest($request, $fileInfo);
$config = new Config();
$config->setTempDir($this->vfs->url());
/** @var File $file */
$file = $this->getMock('Flow\File', array('_move_uploaded_file'), array($config, $fustyRequest));
/** @noinspection PhpUndefinedMethodInspection */
$file->expects($this->once())
->method('_move_uploaded_file')
->will($this->returnCallback(function ($filename, $destination) {
return rename($filename, $destination);
}));
//// Actual test
$this->assertTrue($file->validateChunk());
$this->assertFalse($file->validateFile());
$this->assertTrue($file->saveChunk());
$this->assertTrue($file->validateFile());
$path = $this->vfs->url() . DIRECTORY_SEPARATOR . 'new';
$this->assertTrue($file->save($path));
$this->assertEquals(10, filesize($path));
}
}
<?php
namespace Unit;
use Flow\Request;
/**
* Request unit tests
*
* @coversDefaultClass \Flow\Request
*
* @package Unit
*/
class RequestTest extends FlowUnitCase
{
/**
* @covers ::__construct
*/
public function testRequest_construct_withREQUEST()
{
$_REQUEST = $this->requestArr;
$request = new Request();
$this->assertSame('prettify.js', $request->getFileName());
$this->assertSame(100, $request->getTotalSize());
$this->assertSame('13632-prettifyjs', $request->getIdentifier());
$this->assertSame('home/prettify.js', $request->getRelativePath());
$this->assertSame(42, $request->getTotalChunks());
$this->assertSame(1048576, $request->getDefaultChunkSize());
$this->assertSame(1, $request->getCurrentChunkNumber());
$this->assertSame(10, $request->getCurrentChunkSize());
$this->assertSame(null, $request->getFile());
$this->assertFalse($request->isFustyFlowRequest());
}
/**
* @covers ::__construct
* @covers ::getParam
* @covers ::getFileName
* @covers ::getTotalSize
* @covers ::getIdentifier
* @covers ::getRelativePath
* @covers ::getTotalChunks
* @covers ::getDefaultChunkSize
* @covers ::getCurrentChunkNumber
* @covers ::getCurrentChunkSize
* @covers ::getFile
* @covers ::isFustyFlowRequest
*/
public function testRequest_construct_withCustomRequest()
{
$request = new Request($this->requestArr);
$this->assertSame('prettify.js', $request->getFileName());
$this->assertSame(100, $request->getTotalSize());
$this->assertSame('13632-prettifyjs', $request->getIdentifier());
$this->assertSame('home/prettify.js', $request->getRelativePath());
$this->assertSame(42, $request->getTotalChunks());
$this->assertSame(1048576, $request->getDefaultChunkSize());
$this->assertSame(1, $request->getCurrentChunkNumber());
$this->assertSame(10, $request->getCurrentChunkSize());
$this->assertSame(null, $request->getFile());
$this->assertFalse($request->isFustyFlowRequest());
}
/**
* @covers ::__construct
*/
public function testRequest_construct_withFILES()
{
$_FILES = $this->filesArr;
$request = new Request();
$this->assertSame($this->filesArr['file'], $request->getFile());
}
/**
* @covers ::__construct
*/
public function testRequest_construct_withCustFiles()
{
$request = new Request(null, $this->filesArr['file']);
$this->assertSame($this->filesArr['file'], $request->getFile());
}
}
<?php
namespace Unit;
use Flow\FileOpenException;
use org\bovigo\vfs\vfsStreamWrapper;
use org\bovigo\vfs\vfsStreamDirectory;
use org\bovigo\vfs\vfsStream;
use Flow\Uploader;
/**
* Uploader unit tests
*
* @coversDefaultClass \Flow\Uploader
*
* @package Unit
*/
class UploaderTest extends FlowUnitCase
{
/**
* Virtual file system
*
* @var vfsStreamDirectory
*/
protected $vfs;
protected function setUp()
{
vfsStreamWrapper::register();
$this->vfs = new vfsStreamDirectory('chunks');
vfsStreamWrapper::setRoot($this->vfs);
}
/**
* @covers ::pruneChunks
*/
public function testUploader_pruneChunks()
{
//// Setup test
$newDir = vfsStream::newDirectory('1');
$newDir->lastModified(time()-31);
$newDir->lastModified(time());
$fileFirst = vfsStream::newFile('file31');
$fileFirst->lastModified(time()-31);
$fileSecond = vfsStream::newFile('random_file');
$fileSecond->lastModified(time()-30);
$upDir = vfsStream::newFile('..');
$this->vfs->addChild($newDir);
$this->vfs->addChild($fileFirst);
$this->vfs->addChild($fileSecond);
$this->vfs->addChild($upDir);
//// Actual test
Uploader::pruneChunks($this->vfs->url(), 30);
$this->assertTrue(file_exists($newDir->url()));
$this->assertFalse(file_exists($fileFirst->url()));
$this->assertTrue(file_exists($fileSecond->url()));
}
/**
* @covers ::pruneChunks
*/
public function testUploader_exception()
{
try {
@Uploader::pruneChunks('not/existing/dir', 30);
$this->fail();
} catch (FileOpenException $e) {
$this->assertSame('failed to open folder: not/existing/dir', $e->getMessage());
}
}
}
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
require_once(__DIR__ . '/../src/Flow/Autoloader.php');
require_once(__DIR__ . '/Unit/FlowUnitCase.php');
Flow\Autoloader::register();
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="test/bootstrap.php"
>
<testsuites>
<testsuite name="Flow Test Suite">
<directory>./test/Unit/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/Flow/</directory>
<exclude>
<file>./src/Flow/Basic.php</file>
</exclude>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="build/coverage.xml"/>
<log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/>
</logging>
<listeners>
<listener class="League\PHPUnitCoverageListener\Listener">
<arguments>
<array>
<element key="printer">
<object class="League\PHPUnitCoverageListener\Printer\StdOut"/>
</element>
<element key="hook">
<object class="League\PHPUnitCoverageListener\Hook\Travis"/>
</element>
<element key="namespace">
<string>Flow</string>
</element>
<element key="repo_token">
<string>TtscpSyNYUnuG2LkxtWCQmAtBk8vWAMsI</string>
</element>
<element key="target_url">
<string>https://coveralls.io/api/v1/jobs</string>
</element>
<element key="coverage_dir">
<string>build</string>
</element>
</array>
</arguments>
</listener>
</listeners>
</phpunit>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment