Generate password_hash which matches Magento Go API - magento

I am trying to authenticate a user through the Magento Go SOAP API and having problems generating a matching hash. According to the docs the password_hash contains password:salt however when I md5 it's not matching the password_hash.
Example:
1) I changed my password through admin control panel to 'testtest'
2) Run the following code:
$client = new SoapClient('http://XXXX.gostorego.com/api/v2_soap/?wsdl');
$session = $client->login($api_user, $api_pass);
$params = array('filter'=>array(array('key'=>'email','value'=>'user#domain.com')));
$data = $client->customerCustomerList($session, $params);
echo '<pre>CUSTOMER: '.print_r($data, true).'</pre>';
if (count($data)) {
$hash = explode(':',$data[0]->password_hash);
$salt = $hash[1];
echo '<pre>HASH PARTS:'.print_r($hash, true).'</pre>';
echo '<br>' .md5($salt.$password);
}
3) password_hash is f35604820826428dd7633b91cd6078f4075c9bfa1a37db7bc70f563475ad8495:qK
4) MD5 is 0b04a656c770ba2f10b5918f94529cd8

I've never done this with with Magento Go (and I'm not sure it's supported/possible) but the hash string
f35604820826428dd7633b91cd6078f4075c9bfa1a37db7bc70f563475ad8495:qK
is too long to be a MD5 hash of a string. That's a 64 byte hash (plus the :, plus the salt qK). My guess is it's SHA256, but that's a guess based on character length.

On the backend, Both Md5 and SHA are being supported, with newer support leaning towards the SHA (in enterprise).
If your password were: 12341234
The DB Hash would infact be similar to: cdb757ce51af9749d2fabea4cf71dc72a1ec7b8721e5f8de83020f574ca3c5f1:TR
And is indeed SHA256.
However, the remote connection should be "https:" over SSL for the WSDL file and you should be entering your SOAP API key in normal/plain text.
ie:
$username = "myUsername"; //ie. yourApiUsername
$password = "myUserPass"; //ie. 12341234
If you want to replicate their hashing for your own internal purposes, you need to look at their methods: class Mage_Core_Model_Encryption
public function hash($data)
{
return md5($data);
}
/**
* Validate hash against hashing method (with or without salt)
*
* #param string $password
* #param string $hash
* #return bool
* #throws Exception
*/
public function validateHash($password, $hash)
{
$hashArr = explode(':', $hash);
switch (count($hashArr)) {
case 1:
return $this->hash($password) === $hash;
case 2:
return $this->hash($hashArr[1] . $password) === $hashArr[0];
}
Mage::throwException('Invalid hash.');
}
Enterprise:
public function hash($data, $version = self::HASH_VERSION_LATEST)
{
if (self::HASH_VERSION_MD5 === $version) {
return md5($data);
}
return hash('sha256', $data);
}
/**
* Validate hash by specified version
*
* #param string $password
* #param string $hash
* #param int $version
* #return bool
*/
public function validateHashByVersion($password, $hash, $version = self::HASH_VERSION_LATEST)
{
// look for salt
$hashArr = explode(':', $hash, 2);
if (1 === count($hashArr)) {
return $this->hash($password, $version) === $hash;
}
list($hash, $salt) = $hashArr;
return $this->hash($salt . $password, $version) === $hash;
}

Related

pass an index to the method reduce() laravel

I have this function created to show a piechart but I need each element to show a different color. For this I have created an array with all the colors and I need in each iteration of the reduce() method to have an index to access the colors[i]. I have tried this way and it does not work. Any suggestion?
$i = 0;
$pieChartModel = $options->groupBy('survey_options_id')
->reduce(function (PieChartModel $pieChartModel, $data) use ($i) {
$type = $data->first()->survey_options_id;
$value = $data->sum('value');
// $color = "#" . substr(md5(rand()), 0, 6);
$NameOption = Survey_options::where('id', $type)->pluck('name');
return $pieChartModel->addSlice($NameOption, $value, $this->colors[$i]->hexa);
$i++;
}, (new PieChartModel())->setAnimated($this->firstRun)->setDataLabelsEnabled(true));
A problem I'm seeing in your code is how you're incrementing $i AFTER a return statement.
After taking a look at the source code for the reduce() function
/**
* Reduce the collection to a single value.
*
* #param callable $callback
* #param mixed $initial
* #return mixed
*/
public function reduce(callable $callback, $initial = null)
{
$result = $initial;
foreach ($this as $key => $value) {
$result = $callback($result, $value, $key);
}
return $result;
}
You should be able to use the key (or index) in the callback.
->reduce(function ($carry, $item, $key) { ... }, $initial)
->reduce(function (PieChartModel $pieChartModel, $data, $i) {
...
}, (new PieChartModel())->setAnimated($this->firstRun)->setDataLabelsEnabled(true))

How to do a live user activity tracking function on Codeigniter?

I'm looking for how to tracking user activity (Example: XXX commented on the post/ XXX signed in/XXX shared a post or etc; XXX are registered User/IP address) on my Codeigniter website.
Is it have any recommendations for doing this function because I already researched a few days and get no idea.
Normally I would not even answer a question like this as it is too broad but since I have a similar system I will share my approach. Please note, I am offering no support on this, but rather this is for you to get an idea and/or develop your own implementation.
Let's assume we want to log user logins/failures:
if ($this->ion_auth->login($identity, $this->input->post('password'), false)) {
$this->curr_user->update_ip();
$this->activity->log('logged_in');
$this->response->success()->json();
} else {
$this->activity->log('login_failed', array('username' => $identity));
$this->response->error($this->ion_auth->errors())->json();
}
We simply call the log function before redirecting/rendering output but after the desired action (insert/update, login, .etc.) is completed.
MODEL
The log function uses a switch statement to keep messages homogeneous, and keeps certain information separate from the main message for querying purposes. To be honest using a switch statement isn't super elegant here especially if you have a lot of messages, so implementing a table to hold the messages wouldn't be a bad idea, but this works for my needs.
get_logs is a cool little function that combines all the data and renders it in a sorted list by date the action occurred.
class User_activity_model extends CI_Model {
/**
* Admin-only viewable log types
*
* #var array
*/
private $admin_types = array('page_access', 'user_registered', 'logged_in', 'login_failed', 'login_failed_sa', 'delete');
/**
* Main user activity logging function
*
* #param string $action
* #param array $arr Additional attributes per case
* #return void
*/
public function log($action, $arr = array()) {
switch ($action) {
default:
return;
case 'backup_created':
$msg = "{username} created a backup {$arr['filename']} at {time}.";
break;
case 'backup_restored':
$msg = "{username} restored backup {$arr['filename']} at {time}.";
break;
case 'user_registered':
$msg = "{$arr['first_name']} {$arr['last_name']} registered with username: {$arr['username']}.";
break;
case 'added':
$msg = "{username} added item {$arr['id']} to the {$arr['table']} table.";
break;
case 'modified':
$msg = "{username} modified item {$arr['id']} in the {$arr['table']} table.";
break;
case 'deleted':
$msg = "{username} deleted item {$arr['id']} from the {$arr['table']} table.";
break;
case 'published':
$msg = "{username} published item {$arr['id']} from the {$arr['table']} table.";
break;
case 'unpublished':
$msg = "{username} unpublished item {$arr['id']} from the {$arr['table']} table.";
break;
case 'logged_in':
$ip = $this->input->ip_address();
$msg = "{username} logged in at {time} from IP {$ip}.";
break;
case 'login_failed':
$ip = $this->input->ip_address();
$msg = "Someone tried to login with username {$arr['username']} at {time} from IP {$ip}.";
break;
case 'login_failed_sa':
$ip = $this->input->ip_address();
$msg = "Someone tried to login with social auth provider {$arr['provider']} at {time} from IP {$ip}.";
break;
case 'page_access':
$identity = $this->ion_auth->logged_in() ? '{username}' : "Someone (IP: {$this->input->ip_address()})";
$msg = "{$identity} tried to access a page they didn't have permission for: {$arr['uri']}.";
break;
case 'logout':
$msg = "{username} logged out at {time}.";
break;
case 'user_forgotten':
$msg = 'Someone deleted their account.';
break;
}
$this->add($action, $msg);
}
/**
* Adds identifier information to the insert query
*
* #param string $action
* #param string $msg
* #return void
*/
private function add($action, $msg) {
$data = array(
'ip' => $this->input->ip_address(),
'action' => $action,
'type' => in_array($action, $this->admin_types) ? 'admin' : 'all',
'message' => $msg
);
if ($this->ion_auth->logged_in()) {
$data['username'] = $this->curr_user->details()->username;
$data['user_id'] = $this->curr_user->id();
}
$this->db->set('time', 'NOW()', false);
$this->db->insert('user_activity', $data);
}
/**
* Generates log array
*
* Format:
*
* array(
* [date]
* array(
* [0] = message1
* [1] = message2
*
* #param boolean $admin_only Show all logs?
* #return boolean|object Logs object, FALSE otherwise
*/
public function get_logs($admin_only = true) {
if (!$admin_only) {
$this->db->where('type !=', 'admin');
}
$this->db->limit(10000);
$this->db->order_by('time', 'DESC');
$query = $this->db->get('user_activity');
if ($query->num_rows() < 1) {
return false;
}
$rows = $query->result();
$data = new \stdClass();
foreach ($rows as $row) {
// replace {time} with timezone converted time
$time = $this->timezone->convert($row->time);
$row->message = str_replace('{time}', $time, $row->message);
$username = is_null($row->username) ? $this->lang->line('deleted_user') : $row->username;
$row->message = str_replace('{username}', $username, $row->message);
// date Y-m-d acts as key for messages on that day
$key = date('Y-m-d', strtotime($time));
$data->{$key}[] = $row;
}
return $data;
}
/**
* Truncates user_activity table
*
* #return boolean
*/
public function delete_logs() {
return $this->db->truncate('user_activity');
}
/**
* Stub for automate hook to add 'added' activity
*
* #param array $data
*/
public function automate_activity_add($data) {
$this->activity->log('added', array('table' => $data['table'], 'id' => $data['id']));
}
/**
* Stub for automate hook to add 'modified' activity
*
* #param array $data
*/
public function automate_activity_modify($data) {
$this->activity->log('modified', array('table' => $data['table'], 'id' => $data['id']));
}
}
DATABASE
db dump: https://pastebin.com/wCEnUigH

How do i redirect to an external url with headers?

How i have an application which is sitting on A server and i would like to allow user to get to another application which is in B server with a header of the user information. I have done some tries but i m not getting the header in B server. How can i achieve that ya?
Bellow are the codes which i have tried:-
return redirect()->away($apiUrl)->header('x-api-token', $token);
and
$client = new Client();
$request = $client->request('get', $apiUrl, [
'headers' => [
'x-api-user-token' => $userToken
]
]);
Is there a way for me to redirect to an external url with a header?
You might want to try the helper method provided by Laravel and it works like a charm for me.
return redirect('http://external.url/', 302, [
'custom-header' => 'custom value'
])
If you want to look at the source code please refer
/vendor/laravel/framework/src/Illuminate/Foundation/Helpers.php
/**
* Get an instance of the redirector.
*
* #param string|null $to
* #param int $status
* #param array $headers
* #param bool $secure
* #return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
*/
function redirect($to = null, $status = 302, $headers = [], $secure = null)
{
if (is_null($to)) {
return app('redirect');
}
return app('redirect')->to($to, $status, $headers, $secure);
}

Typo3 fluid image from external resource

is it possible to resize images in fluid from external resource. I have an extension with datas from SOAP. So image URL looks like http://www.example.com/url/of/image/imagename.jpg.
<f:image src="{data.url.image}" with="300" />
is not working.
Maybe an own ViewHelper which fetch the external image and save it to an temporary folder could help. After this you can modify the image.
Something like this (not tested):
<?php
namespace MyNamespaece\MyExt\ViewHelpers;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\ViewHelpers\ImageViewHelper;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder;
class ExternalImageViewHelper extends ImageViewHelper
{
const UPLOAD_DIRECTORY = 'externalImages';
const TEMP_PREFIX = 'MyExt';
/**
* ResourceFactory
*
* #var \TYPO3\CMS\Core\Resource\ResourceFactory
* #inject
*/
protected $resourceFactory = null;
/**
* Resizes a given image (if required) and renders the respective img tag
*
* #see https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Image/
*
* #param string $src a path to a file, a combined FAL identifier or an uid (integer). If $treatIdAsReference is set, the integer is considered the uid of the sys_file_reference record. If you already got a FAL object, consider using the $image parameter instead
* #param string $width width of the image. This can be a numeric value representing the fixed width of the image in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.
* #param string $height height of the image. This can be a numeric value representing the fixed height of the image in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.
* #param integer $minWidth minimum width of the image
* #param integer $minHeight minimum height of the image
* #param integer $maxWidth maximum width of the image
* #param integer $maxHeight maximum height of the image
* #param boolean $treatIdAsReference given src argument is a sys_file_reference record
* #param FileInterface|AbstractFileFolder $image a FAL object
*
* #return string
* #throws \Exception
* #throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException
* #throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException
* #throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
*/
public function render($src = null, $width = null, $height = null, $minWidth = null, $minHeight = null, $maxWidth = null, $maxHeight = null, $treatIdAsReference = false, $image = null)
{
if (filter_var($src, FILTER_VALIDATE_URL)) {
$storage = $this->resourceFactory->getDefaultStorage();
if (!$storage->hasFolder(self::UPLOAD_DIRECTORY)) {
$storage->createFolder(self::UPLOAD_DIRECTORY);
}
$externalFile = GeneralUtility::getUrl($src);
if ($externalFile) {
$tempFileName = tempnam(sys_get_temp_dir(), self::TEMP_PREFIX);
$handle = fopen($tempFileName, "w");
fwrite($handle, $externalFile);
fclose($handle);
$uploadFolder = $storage->getFolder(self::UPLOAD_DIRECTORY);
$file = $uploadFolder->addFile($tempFileName, basename(basename($src)), 'changeName');
$src = $file->getPublicUrl();
unlink($tempFileName);
} else {
throw new \Exception(sprintf('External URL % cannot accessed.', $src), 1473233519);
}
}
return parent::render($src, $width, $height, $minWidth, $minHeight, $maxWidth, $maxHeight, $treatIdAsReference, $image);
}
}
Please Note: This ViewHelper has no check if the image is allready fetched! So an check should be integrated. Otherwise this viewhelper fetch the image at each page refresh!
As mentioned in the comments I want to clarify that this ViewHelper should not be used in any production environment. It should only demonstrate how the way to such an viewhelper could be. Compiled templates are not supported. Also no needed check if the file already exists is implemented. Your hosting environment could be flooded with downloads and can break you file quota!
The short answer is: This is not possible.
The long answer is: Of course it is possible if you fetch the image first. There are various ways to do it:
At runtime as rpflamm suggested by using a ViewHelper
Do it in your controller/service when you fetch the SOAP call. IMO this would be the best way. Persist then the image and use the local path
If the images you fetch are not that big, of course a resizing via CSS is also an option
Working Version with cropping-feature for TYPO3 10.4 LTS:
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*
* Example: <xyz:externalImage filename="OPTINAL_FILENAME" src="EXTERNAL_URL" width="480c" height="270c" title="YOUR TITLE" alt="YOUR ALT" class="YOUR-CLASS" />
*/
namespace YourNamespaece\YourExtension\ViewHelpers;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
use TYPO3\CMS\Fluid\Core\Widget\Exception;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
class ExternalImageViewHelper extends AbstractTagBasedViewHelper {
/**
* #var string
*/
protected $tagName = 'img';
/**
* #var \TYPO3\CMS\Extbase\Service\ImageService
*/
protected $imageService;
/**
* #param \TYPO3\CMS\Extbase\Service\ImageService $imageService
*/
public function injectImageService(ImageService $imageService)
{
$this->imageService = $imageService;
}
const UPLOAD_DIRECTORY = 'externalImages';
const TEMP_PREFIX = 'diakonie_baukasten';
/**
* ResourceFactory
*
* #var ResourceFactory
* #TYPO3\CMS\Extbase\Annotation\Inject
*/
protected ResourceFactory $resourceFactory;
/**
* Initialize arguments.
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerUniversalTagAttributes();
$this->registerTagAttribute('alt', 'string', 'Specifies an alternate text for an image', false);
$this->registerArgument('filename', 'string', 'Override filename for local file.', false, '');
$this->registerArgument('src', 'string', 'a path to a file, a combined FAL identifier or an uid (int). If $treatIdAsReference is set, the integer is considered the uid of the sys_file_reference record. If you already got a FAL object, consider using the $image parameter instead', true, '');
$this->registerArgument('width', 'string', 'width of the image. This can be a numeric value representing the fixed width of the image in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.');
$this->registerArgument('height', 'string', 'height of the image. This can be a numeric value representing the fixed height of the image in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.');
$this->registerArgument('minWidth', 'int', 'minimum width of the image');
$this->registerArgument('minHeight', 'int', 'minimum height of the image');
$this->registerArgument('maxWidth', 'int', 'maximum width of the image');
$this->registerArgument('maxHeight', 'int', 'maximum height of the image');
$this->registerArgument('absolute', 'bool', 'Force absolute URL', false, false);
}
/**
* Resizes a given image (if required) and renders the respective img tag
*
* #see https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Image/
*
* #return string Rendered tag
* #throws \Exception
*/
public function render()
{
$src = (string)$this->arguments['src'];
$filename = (string)$this->arguments['filename'];
if ($src === '') {
throw new Exception('You must either specify a string src', 1382284106);
}
// A URL was given as src, this is kept as is, and we can only scale
if ($src !== '' && preg_match('/^(https?:)?\/\//', $src)) {
if (filter_var($src, FILTER_VALIDATE_URL)) {
$storage = $this->resourceFactory->getDefaultStorage();
if (!$storage->hasFolder(self::UPLOAD_DIRECTORY)) {
$storage->createFolder(self::UPLOAD_DIRECTORY);
}
$externalFile = GeneralUtility::getUrl($src);
if ($externalFile) {
$tempFileName = tempnam(sys_get_temp_dir(), self::TEMP_PREFIX);
$handle = fopen($tempFileName, "w");
fwrite($handle, $externalFile);
fclose($handle);
if ($filename !== '') {
$fileNameNoExtension = preg_replace("/\.[^.]+$/", "", $src);
$fileExtension = str_replace($fileNameNoExtension,"", $src);
$src = $filename.$fileExtension;
}
$uploadFolder = $storage->getFolder(self::UPLOAD_DIRECTORY);
$file = $uploadFolder->addFile($tempFileName, basename(basename($src)), 'replace');
$src = $file->getPublicUrl();
unlink($tempFileName);
$image = $this->imageService->getImage($src, null, false);
$processingInstructions = [
'width' => $this->arguments['width'],
'height' => $this->arguments['height'],
'minWidth' => $this->arguments['minWidth'],
'minHeight' => $this->arguments['minHeight'],
'maxWidth' => $this->arguments['maxWidth'],
'maxHeight' => $this->arguments['maxHeight'],
'crop' => null,
];
$processedImage = $this->imageService->applyProcessingInstructions($image, $processingInstructions);
$imageUri = $this->imageService->getImageUri($processedImage, $this->arguments['absolute']);
$this->tag->addAttribute('src', $imageUri);
$this->tag->addAttribute('width', $processedImage->getProperty('width'));
$this->tag->addAttribute('height', $processedImage->getProperty('height'));
// The alt-attribute is mandatory to have valid html-code, therefore add it even if it is empty
if (empty($this->arguments['alt'])) {
$this->tag->addAttribute('alt', $image->hasProperty('alternative') ? $image->getProperty('alternative') : '');
}
// Add title-attribute from property if not already set and the property is not an empty string
$title = (string)($image->hasProperty('title') ? $image->getProperty('title') : '');
if (empty($this->arguments['title']) && $title !== '') {
$this->tag->addAttribute('title', $title);
}
return $this->tag->render();
} else {
throw new \Exception(sprintf('External URL % cannot accessed.', $src), 1473233519);
}
}
}
}
}

fine-uploader PHP Server Side Merge

I'm been experimenting with Fine Uploader. I am really interested in the chunking and resume features, but I'm experiencing difficulties putting the files back together server side;
What I've found is that I have to allow for a blank file extension on the server side to allow the upload of the chunks, otherwise the upload will fail with unknown file type. It uploads the chunks fine with file names such as "blob" and "blob63" (no file extension) however is does not merge them back at completion of upload.
Any help or pointers would be appreciated.
$('#edit-file-uploader').fineUploader({
request: {
endpoint: 'upload.php'
},
multiple: false,
validation:{
allowedExtentions: ['stl', 'obj', '3ds', 'zpr', 'zip'],
sizeLimit: 104857600 // 100mb * 1024 (kb) * 1024 (bytes)
},
text: {
uploadButton: 'Select File'
},
autoUpload: false,
chunking: {
enabled: true
},
callbacks: {
onComplete: function(id, fileName, responseJSON) {
if (responseJSON.success) {
/** some code here **??
}
}
});
And this is the server side script (PHP):
// list of valid extensions, ex. array("stl", "xml", "bmp")
$allowedExtensions = array("stl", "");
// max file size in bytes
$sizeLimit = null;
$uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
// Call handleUpload() with the name of the folder, relative to PHP's getcwd()
$result = $uploader->handleUpload('uploads/');
// to pass data through iframe you will need to encode all html tags
echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);
/******************************************/
/**
* Handle file uploads via XMLHttpRequest
*/
class qqUploadedFileXhr {
/**
* Save the file to the specified path
* #return boolean TRUE on success
*/
public function save($path) {
$input = fopen("php://input", "r");
$temp = tmpfile();
$realSize = stream_copy_to_stream($input, $temp);
fclose($input);
if ($realSize != $this->getSize()){
return false;
}
$target = fopen($path, "w");
fseek($temp, 0, SEEK_SET);
stream_copy_to_stream($temp, $target);
fclose($target);
return true;
}
/**
* Get the original filename
* #return string filename
*/
public function getName() {
return $_GET['qqfile'];
}
/**
* Get the file size
* #return integer file-size in byte
*/
public function getSize() {
if (isset($_SERVER["CONTENT_LENGTH"])){
return (int)$_SERVER["CONTENT_LENGTH"];
} else {
throw new Exception('Getting content length is not supported.');
}
}
}
/**
* Handle file uploads via regular form post (uses the $_FILES array)
*/
class qqUploadedFileForm {
/**
* Save the file to the specified path
* #return boolean TRUE on success
*/
public function save($path) {
return move_uploaded_file($_FILES['qqfile']['tmp_name'], $path);
}
/**
* Get the original filename
* #return string filename
*/
public function getName() {
return $_FILES['qqfile']['name'];
}
/**
* Get the file size
* #return integer file-size in byte
*/
public function getSize() {
return $_FILES['qqfile']['size'];
}
}
/**
* Class that encapsulates the file-upload internals
*/
class qqFileUploader {
private $allowedExtensions;
private $sizeLimit;
private $file;
private $uploadName;
/**
* #param array $allowedExtensions; defaults to an empty array
* #param int $sizeLimit; defaults to the server's upload_max_filesize setting
*/
function __construct(array $allowedExtensions = null, $sizeLimit = null){
if($allowedExtensions===null) {
$allowedExtensions = array();
}
if($sizeLimit===null) {
$sizeLimit = $this->toBytes(ini_get('upload_max_filesize'));
}
$allowedExtensions = array_map("strtolower", $allowedExtensions);
$this->allowedExtensions = $allowedExtensions;
$this->sizeLimit = $sizeLimit;
$this->checkServerSettings();
if(!isset($_SERVER['CONTENT_TYPE'])) {
$this->file = false;
} else if (strpos(strtolower($_SERVER['CONTENT_TYPE']), 'multipart/') === 0) {
$this->file = new qqUploadedFileForm();
} else {
$this->file = new qqUploadedFileXhr();
}
}
/**
* Get the name of the uploaded file
* #return string
*/
public function getUploadName(){
if( isset( $this->uploadName ) )
return $this->uploadName;
}
/**
* Get the original filename
* #return string filename
*/
public function getName(){
if ($this->file)
return $this->file->getName();
}
/**
* Internal function that checks if server's may sizes match the
* object's maximum size for uploads
*/
private function checkServerSettings(){
$postSize = $this->toBytes(ini_get('post_max_size'));
$uploadSize = $this->toBytes(ini_get('upload_max_filesize'));
if ($postSize < $this->sizeLimit || $uploadSize < $this->sizeLimit){
$size = max(1, $this->sizeLimit / 1024 / 1024) . 'M';
die(json_encode(array('error'=>'increase post_max_size and upload_max_filesize to ' . $size)));
}
}
/**
* Convert a given size with units to bytes
* #param string $str
*/
private function toBytes($str){
$val = trim($str);
$last = strtolower($str[strlen($str)-1]);
switch($last) {
case 'g': $val *= 1024;
case 'm': $val *= 1024;
case 'k': $val *= 1024;
}
return $val;
}
/**
* Handle the uploaded file
* #param string $uploadDirectory
* #param string $replaceOldFile=true
* #returns array('success'=>true) or array('error'=>'error message')
*/
function handleUpload($uploadDirectory, $replaceOldFile = FALSE){
if (!is_writable($uploadDirectory)){
return array('error' => "Server error. Upload directory isn't writable.");
}
if (!$this->file){
return array('error' => 'No files were uploaded.');
}
$size = $this->file->getSize();
if ($size == 0) {
return array('error' => 'File is empty');
}
if ($size > $this->sizeLimit) {
return array('error' => 'File is too large');
}
$pathinfo = pathinfo($this->file->getName());
$filename = $pathinfo['filename'];
//$filename = md5(uniqid());
$ext = #$pathinfo['extension']; // hide notices if extension is empty
if($this->allowedExtensions && !in_array(strtolower($ext), $this->allowedExtensions)){
$these = implode(', ', $this->allowedExtensions);
return array('error' => 'File has an invalid extension, it should be one of '. $these . '.');
}
$ext = ($ext == '') ? $ext : '.' . $ext;
if(!$replaceOldFile){
/// don't overwrite previous files that were uploaded
while (file_exists($uploadDirectory . DIRECTORY_SEPARATOR . $filename . $ext)) {
$filename .= rand(10, 99);
}
}
$this->uploadName = $filename . $ext;
if ($this->file->save($uploadDirectory . DIRECTORY_SEPARATOR . $filename . $ext)){
return array('success'=>true);
} else {
return array('error'=> 'Could not save uploaded file.' .
'The upload was cancelled, or server error encountered');
}
}
}
In order to handle chunked requests, you MUST store each chunk separately in your filesystem.
How you name these chunks or where you store them is up to you, but I suggest you name them using the UUID provided by Fine Uploader and append the part number parameter included with each chunked request. After the last chunk has been sent, combine all chunks into one file, with the proper name, and return a standard success response as described in the Fine Uploader documentation. The original name of the file is, by default, passed in a qqfilename parameter with each request. This is also discussed in the docs and the blog.
It doesn't look like you've made any attempt to handle chunks server-side. There is a PHP example in the Widen/fine-uploader-server repo that you can use. Also, the documentation has a "server-side" section that explains how to handle chunking in detail. I'm guessing you did not read this. Have a look.) in the Widen/fine-uploader-server repo that you can use. Also, the documentation has a "server-side" section that explains how to handle chunking in detail. I'm guessing you did not read this. Have a look.
Note that, starting with Fine Uploader 3.8 (set to release VERY soon) you will be able to delegate all server-side upload handling to Amazon S3, as Fine Uploader will provide tight integration with S3 that sends all of your files directly to your bucket from the browser without you having to worry about constructing a policy document, making REST API calls, handling responses from S3, etc. I mention this as using S3 means that you never have to worry about handling things like chunked requests on your server again.

Resources