I have a laravel system and I am storing one of my response in a particular file like this :
$objFile = new Filesystem();
$path = "files/FileName.php";
$string = $this->getSlides();
if ($objFile->exists($path))
{
$objFile->put($path,"",$lock = false);
$objFile->put($path,$string,$lock = false);
$objFile->getRequire($path);
}
else
return getcwd() . "\n";
Now i get the contents using the following lines:
$objFile = new Filesystem();
$path = "files/FileName.php";
if ($objFile->exists($path))
{
return Response::json([$objFile->getRequire($path)],200);
}
else
return getcwd() . "\n";
Now what's happening is that when I store the file on the server it adds some header like :
HTTP/1.0 200 OK
Cache-Control: no-cache
Content-Type: application/json
Date: Thu, 10 Nov 2016 05:38:08 GMT
followed by my stored file , so when I call the file on my frontend , I get the following error :
SyntaxError: Unexpected token H in JSON at position 0(…)
of course it expects from me a json value however i m giving it something that doesn't start like 1. Any idea how i can remove that on php level?
You are using wrong method for getting file content.
$objFile->getRequire($path) - it will execute following code chunk: return require $path;. You have to use $objFile->get($path) to get file's content.
Sorry I made a bit of blunder while retrieving the result:
In the getSlides method i was using :
return Response::json([
'Data' => $final_array
],200);
Now i replaced it with :
$final_array2 = array(
'Data' => $final_array
);
return json_encode($final_array2);
Sorry because i didn't put this code and hence no one could have got any idea
Related
I might be asking the wrong question here, but I cant seem to figure out where this is coming form. I am using both the HTTP Request2 and NET URL2 libraries in order to send some GET request to the Vuforia web services. This all works fine, but everytime I send a request, it also shows said request on screen.
GET d41d8cd98f00b204e9800998ecf8427e Mon, 09 Dec 2019 22:49:52 GMT /summary/ba2246f8cd29466899c69b8d05af09a1
The code that I use to get the above text appear on screen is as follows.
Main code:
<?php if(sizeof($items) > 0){
foreach($items as $item){
echo '<tr>';
echo'<td>'.$item['itemid'].'</td>';
echo'<td>'.$item['name'].'</td>';
echo'<td>'.$item['surname'].'</td>';
echo'<td>'.$item['phone'].'</td>';
$recos = $targetdata = json_decode(CheckVuforiaTarget("ba2246f8cd29466899c69b8d05af09a1"), true);
echo'<td>'.$recos['current_month_recos'].'</td>';
}
} else echo '<tr><td>Geen kandidaten</td></tr>';?>
Script holding the CheckVuforiaTarget function:
function CheckVuforiaTarget($vuforiaid){
$vuforiaTargetTracker = new TargetTracker($vuforiaid);
$response = $vuforiaTargetTracker->TargetTracker();
return ($response);
//print_r($vuforiaResult);
}
TargetTracker class:
<?php
require_once 'HTTP/Request2.php';
require_once 'SignatureBuilder.php';
// See the Vuforia Web Services Developer API Specification - https://developer.vuforia.com/resources/dev-guide/retrieving-target-cloud-database
// The DeleteTarget sample demonstrates how to delete a target from its Cloud Database using the target's target id.
// * note that targets cannot be 'Processing' and must be inactive to be deleted.
class TargetTracker{
//Server Keys
private $access_key = "...";
private $secret_key = "...";
private $url = "https://vws.vuforia.com";
private $requestPath = "/summary/";
private $request;
private $targetId = "";
public function __construct($targetId) {
$this->targetId = $targetId;
}
function TargetTracker(){
$this->requestPath = $this->requestPath . $this->targetId;
return $this->execTargetTracker();
}
public function execTargetTracker(){
$this->request = new HTTP_Request2();
$this->request->setMethod( HTTP_Request2::METHOD_GET );
$this->request->setConfig(array('ssl_verify_peer' => false));
$this->request->setURL( $this->url . $this->requestPath );
$this->setHeaders();
try {
$response = $this->request->send();
if (200 == $response->getStatus()) {
return $response->getBody();
} else {
//echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
// $response->getReasonPhrase(). ' ' . $response->getBody();
return $response->getBody();
}
} catch (HTTP_Request2_Exception $e) {
return $e->getMessage();
}
}
private function setHeaders(){
$sb = new SignatureBuilder();
$date = new DateTime("now", new DateTimeZone("GMT"));
// Define the Date field using the proper GMT format
$this->request->setHeader('Date', $date->format("D, d M Y H:i:s") . " GMT" );
// Generate the Auth field value by concatenating the public server access key w/ the private query signature for this request
$this->request->setHeader("Authorization" , "VWS " . $this->access_key . ":" . $sb->tmsSignature( $this->request , $this->secret_key ));
}
}
?>
Both HTTP/Request2.php(and everything it came with) and SignatureBuilder.php are both default scripts/classes I've downloaded from the internet without altering them.
Now with my basic understanding of PHP, i've tried to find anything related to an echo or whatever command would show this on screen, but I can't seem to find it.
Does someone have some pointers for me, so I can figure out the source?
Thanks in advance!
The code you provided has no echo/print output that would explain the message you see. The echo/print statement must be somewhere else.
I use the LINE BOT API Trial SDK PHP (https://github.com/line/line-bot-sdk-php).
But, for this method:
$res = $bot->sendText(['TARGET_MID'], 'Message');
How to get the user's MID to send them a message?
Thanks for help.
1) A very quick way to get the mid of an interactive user (more of a fun hack, really) is to register a callback URL as per the API instructions, then capture the POST data to that URL like so:
// /callback/index.php
<?php
$postdata = file_get_contents("php://input");
#file_get_contents('https://'.$_SERVER['SERVER_NAME'].'/LINE/' . json_encode($postdata));
Next, scan the QR code in the channel console and add your trial bot to your LINE account. After that is done, send it a quick "Hello!" text.
You could then save the POST data to a text file if you wish, or you could check the server logs. For example, you might see something like this:
163.128.118.223 - - [03/Sep/2016:07:25:25 -0700] "POST /line/callback/ HTTP/1.1" 200 - "-" "ChannelEventDispatcher/1.0"
106.152.218.107 - - [03/Sep/2016:07:25:25 -0700] "GET /LINE/{\"result\":[{\"content\":{\"toType\":1,\"createdTime\":1472114754839,\"from\":\"ub7dbd4a12c322f6c0117773d739c55a4\",\"location\":null,\"id\":\"4357194057879\",\"to\":[\"u2b6a4ba287028dee7291122094dac827\"],\"text\":\"Hello!\",\"contentMetadata\":{\"AT_RECV_MODE\":\"2\",\"SKIP_BADGE_COUNT\":\"true\"},\"deliveredTime\":0,\"contentType\":1,\"seq\":null},\"createdTime\":1472912724868,\"eventType\":\"138311609000106301\",\"from\":\"u236d23c2e36bd87217655609a1c31cb8\",\"fromChannel\":1241102815,\"id\":\"WB1519-3102846635\",\"to\":[\"u2b6a4ba287028dee7291122094dac827\"],\"toChannel\":1462261375}]} HTTP/1.1" 404 15 "-" "-"
The \"from\":\"ub7dbd4a12c322f6c0117773d739c55a4\" is the pertinent part.
2) If you'd like to get started with receiving messages, you can start like this as your callback script. Simply send your BOT the message 'mid' and it should respond with your mid.
Here is a starting callback script I made with signature verification included for you.
// /callback/index.php
<?php
// Show all errors for testing
error_reporting(E_ALL);
// SDK is installed via composer
require_once __DIR__ . "/includes/vendor/autoload.php";
use LINE\LINEBot;
use LINE\LINEBot\HTTPClient\GuzzleHTTPClient;
// Set these
$config = [
'channelId' => LINE_CHANNEL_ID,
'channelSecret' => LINE_CHANNEL_SECRET,
'channelMid' => LINE_CHANNEL_MID,
];
$sdk = new LINEBot($config, new GuzzleHTTPClient($config));
$postdata = #file_get_contents("php://input");
$messages = $sdk->createReceivesFromJSON($postdata);
// Verify the signature
// REF: http://line.github.io/line-bot-api-doc/en/api/callback/post.html#signature-verification
$sigheader = 'X-LINE-ChannelSignature';
// REF: http://stackoverflow.com/a/541450
$signature = #$_SERVER[ 'HTTP_'.strtoupper(str_replace('-','_',$sigheader)) ];
if($signature && $sdk->validateSignature($postdata, $signature)) {
// Next, extract the messages
if(is_array($messages)) {
foreach ($messages as $message) {
if ($message instanceof LINEBot\Receive\Message\Text) {
$text = $message->getText();
if ($text == "mid") {
$fromMid = $message->getFromMid();
// Send the mid back to the sender and check if the message was delivered
$result = $sdk->sendText([$fromMid], 'mid: ' . $fromMid);
if(!$result instanceof LINE\LINEBot\Response\SucceededResponse) {
error_log('LINE error: ' . json_encode($result));
}
} else {
// Process normally, or do nothing
}
} else {
// Process other types of LINE messages like image, video, sticker, etc.
}
}
} // Else, error
} else {
error_log('LINE signatures didn\'t match');
}
Original question
I'm serving an mp3 file from a ZF2 controller action. This works fine in all browsers except for Safari on OS X and iPhone/iPad.
The audio plays, but the duration is just displayed as NaN:NaN, whereas in every other browser the correct duration is being displayed.
I went over all the threads on SO talking about the same problem and it seems like it has something to do with the response headers and the Content-Range and Accept-Ranges headers in particular. I've tried all the different combinations but still to no avail - Safari still refuses to display the duration correctly.
The relevant code snippet looks like this:
$path = $teaserAudioPath . DIRECTORY_SEPARATOR . $teaserFile;
$fp = fopen($path, 'r');
$etag = md5(serialize(fstat($fp)));
fclose($fp);
$fsize = filesize($path);
$shortlen = $fsize - 1;
$response->setStatusCode(Response::STATUS_CODE_200);
$response->getHeaders()
->addHeaderLine('Pragma', 'public')
->addHeaderLine('Expires', -1)
->addHeaderLine('Content-Type', 'audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3')
->addHeaderLine('Content-Length', $fsize)
->addHeaderLine('Content-Disposition', 'attachment; filename="teaser.mp3"')
->addHeaderLine('Content-Transfer-Encoding', 'binary')
->addHeaderLine('Content-Range', 'bytes 0-' . $shortlen . '/' . $fsize)
->addHeaderLine('Accept-Ranges', 'bytes')
->addHeaderLine('X-Pad', 'avoid browser bug')
->addHeaderLine('Cache-Control', 'no-cache')
->addHeaderLine('Etag', $etag);
$response->setContent(file_get_contents($path));
return $response;
The player (I'm using mediaelementjs) looks like this in Safari:
I've also tried interpreting the HTTP_RANGE request header based on another example, like so:
$fileSize = filesize($path);
$fileTime = date('r', filemtime($path));
$fileHandle = fopen($path, 'r');
$rangeFrom = 0;
$rangeTo = $fileSize - 1;
$etag = md5(serialize(fstat($fileHandle)));
$cacheExpires = new \DateTime();
if (isset($_SERVER['HTTP_RANGE']))
{
if (!preg_match('/^bytes=\d*-\d*(,\d*-\d*)*$/i', $_SERVER['HTTP_RANGE']))
{
$statusCode = 416;
}
else
{
$ranges = explode(',', substr($_SERVER['HTTP_RANGE'], 6));
foreach ($ranges as $range)
{
$parts = explode('-', $range);
$rangeFrom = intval($parts[0]); // If this is empty, this should be 0.
$rangeTo = intval($parts[1]); // If this is empty or greater than than filelength - 1, this should be filelength - 1.
if (empty($rangeTo)) $rangeTo = $fileSize - 1;
if (($rangeFrom > $rangeTo) || ($rangeTo > $fileSize - 1))
{
$statusCode = 416;
}
else
{
$statusCode = 206;
}
}
}
}
else
{
$statusCode = 200;
}
if ($statusCode == 416)
{
$response = $this->getResponse();
$response->setStatusCode(416); // HTTP/1.1 416 Requested Range Not Satisfiable
$response->addHeaderLine('Content-Range', "bytes */{$fileSize}"); // Required in 416.
}
else
{
fseek($fileHandle, $rangeFrom);
set_time_limit(0); // try to disable time limit
$response = new Stream();
$response->setStream($fileHandle);
$response->setStatusCode($statusCode);
$response->setStreamName(basename($path));
$headers = new Headers();
$headers->addHeaders(array(
'Pragma' => 'public',
'Expires' => $cacheExpires->format('Y/m/d H:i:s'),
'Cache-Control' => 'no-cache',
'Accept-Ranges' => 'bytes',
'Content-Description' => 'File Transfer',
'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => 'attachment; filename="' . basename($path) .'"',
'Content-Type' => 'audio/mpeg', // $media->getFileType(),
'Content-Length' => $fileSize,
'Last-Modified' => $fileTime,
'Etag' => $etag,
'X-Pad' => 'avoid browser bug',
));
if ($statusCode == 206)
{
$headers->addHeaderLine('Content-Range', "bytes {$rangeFrom}-{$rangeTo}/{$fileSize}");
}
$response->setHeaders($headers);
}
fclose($fileHandle);
This still gives me the same result in Safari. I even tried using core PHP functions instead of the ZF2 Response object to render a response, using header() calls and readfile(), but that doesn't work either.
Any ideas on how to solve this are welcome.
Edit
As suggested by #MarcB I compared the response headers of the two requests. The first request is to the PHP action serving the mp3 file data and the second is when I browse to the same mp3 file directly. At first the headers weren't completely the same, but I modified the PHP script to match the headers of the direct download, see Firebug screenshots below:
Response headers served by PHP:
Response headers direct download:
As you can see they are exactly the same except for the Date header, but that's because there was about a minute and a half in between the requests. Still Safari is claiming it is a live broadcast when I try to serve the file from the PHP script and so the audioplayer still shows NaN for the total time when I load it that way. Is there any way to tell Safari to just download the whole file and just trust me when I say this is not a live broadcast?
Also could it be that Safari sends different request headers and thus the response headers are also different? I usually do my debugging in Firefox with Firebug. When I open the mp3 file URL in Safari for instance I cannot open the Web Inspector dialog. Is there any other way to view what headers are being sent and received by Safari?
Edit 2
I'm now using a simple stream function implementing the range requests. This seems to work on my dev machine even in Safari, but not on the live VPS server where the site is running.
The function I use now (courtesy of another SO-er, don't remember the exact link):
private function stream($file, $content_type = 'application/octet-stream', $logger)
{
// Make sure the files exists, otherwise we are wasting our time
if (!file_exists($file))
{
$logger->debug('File not found');
header("HTTP/1.1 404 Not Found");
exit();
}
// Get file size
$filesize = sprintf("%u", filesize($file));
// Handle 'Range' header
if (isset($_SERVER['HTTP_RANGE']))
{
$range = $_SERVER['HTTP_RANGE'];
$logger->debug('Got Range: ' . $range);
}
elseif ($apache = apache_request_headers())
{
$logger->debug('Got Apache headers: ' . print_r($apache, 1));
$headers = array();
foreach ($apache as $header => $val)
{
$headers[strtolower($header)] = $val;
}
if (isset($headers['range']))
{
$range = $headers['range'];
}
else
$range = FALSE;
}
else
$range = FALSE;
// Is range
if ($range)
{
$partial = true;
list ($param, $range) = explode('=', $range);
// Bad request - range unit is not 'bytes'
if (strtolower(trim($param)) != 'bytes')
{
header("HTTP/1.1 400 Invalid Request");
exit();
}
// Get range values
$range = explode(',', $range);
$range = explode('-', $range[0]);
// Deal with range values
if ($range[0] === '')
{
$end = $filesize - 1;
$start = $end - intval($range[0]);
}
else
if ($range[1] === '')
{
$start = intval($range[0]);
$end = $filesize - 1;
}
else
{
// Both numbers present, return specific range
$start = intval($range[0]);
$end = intval($range[1]);
if ($end >= $filesize || (! $start && (! $end || $end == ($filesize - 1))))
$partial = false; // Invalid range/whole file specified, return whole file
}
$length = $end - $start + 1;
}
// No range requested
else
$partial = false;
// Send standard headers
header("Content-Type: $content_type");
header("Content-Length: $filesize");
header('X-Pad: avoid browser bug');
header('Accept-Ranges: bytes');
header('Connection: Keep-Alive"');
// send extra headers for range handling...
if ($partial)
{
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes $start-$end/$filesize");
if (! $fp = fopen($file, 'rb'))
{
header("HTTP/1.1 500 Internal Server Error");
exit();
}
if ($start)
fseek($fp, $start);
while ($length)
{
set_time_limit(0);
$read = ($length > 8192) ? 8192 : $length;
$length -= $read;
print(fread($fp, $read));
}
fclose($fp);
}
// just send the whole file
else
readfile($file);
exit();
}
This is then called in the controller action:
$path = $teaserAudioPath . DIRECTORY_SEPARATOR . $teaserFile;
$fsize = filesize($path);
$this->stream($path, 'audio/mpeg', $logger);
I added some logging for debugging purposes and the difference seems to be in the request headers. On my local dev machine, where it works I get this in the log:
2014-03-09T18:01:17-07:00 DEBUG (7): Got Range: bytes=0-1
2014-03-09T18:01:18-07:00 DEBUG (7): Got Range: bytes=0-502423
2014-03-09T18:01:18-07:00 DEBUG (7): Got Range: bytes=131072-502423
On the VPS, where it doesn't work I get this:
2014-03-09T18:02:25-07:00 DEBUG (7): Got Range: bytes=0-1
2014-03-09T18:02:29-07:00 DEBUG (7): Got Range: bytes=0-1
2014-03-09T18:02:35-07:00 DEBUG (7): Got Apache headers: Array
(
[Accept] => */*
[Accept-Encoding] => identity
[Connection] => close
[Cookie] => __utma=71101845.663885222.1368064857.1368814780.1368818927.55; _nsz9=385E69DA4D1C04EEB22937B75731EFEF7F2445091454C0AEA12658A483606D07; PHPSESSID=c6745c6c8f61460747409fdd9643804c; _ga=GA1.2.663885222.1368064857
[Host] => <edited out>
[Icy-Metadata] => 1
[Referer] => <edited out>
[User-Agent] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11
[X-Playback-Session-Id] => 04E79834-DEB5-47F6-AF22-CFDA0B45B99F
)
Somehow on the live server only the initial request for the first two bytes, which Safari uses to determine if a server supports range requests comes in (twice), but the range request for the actual data is never done. Instead I'm getting a bunch of strange request headers as returned by the apache_request_headers() call in the stream function. I'm not getting that on my local dev machine, which also runs Apache.
Any ideas would be greatly appreciated, really pulling my hair out here.
Tonight I spent a while on a similar problem - audio tags that work fine on most browsers, but don't deal with progress properly on Safari. I have found a solution that works for me, hopefully it works for you too.
I also read the other SO questions about similar issues, and they all spoke about dealing with the Range header. There are a few snippets floating around that aim to deal with the Range header. I found a short(ish) function on github that has been working for me. https://github.com/pomle/php-serveFilePartial
I did have to make one change to the file though. On line 38:
header(sprintf('Content-Range: bytes %d-%d/%d', $byteOffset, $byteLength - 1, $fileSize));
I made a small modification (removed a -1)
header(sprintf('Content-Range: bytes %d-%d/%d', $byteOffset, $byteLength, $fileSize));
I have posted an issue to the github just now explaining why I made this change. The way I found this issue is interesting: It appears that Safari doesn't trust a server when it says it can provide partial content: Safari (or technically Quicktime I think) requests bytes 0-1 of a file with a range header like this:
Range: bytes=0-1
as its first request to the file. If the server returns the whole file - it treats the file as a 'stream', which has no beginning or end. If the server responds with a single byte from that file, and the correct headers, it will then ask for a few different ranges of that file (which grossly overlap in what seems like a very inappropriate way). I see that you have already noticed this, and that you have experienced that Safari/Quicktime only makes the first ranged (0-1) request, and no subsequent 'real' ranged requests. It appears from my poking-around that this is happening because your server did not serve a 'satisfactory' ranged reply, so it gave up on the whole ranged request idea. I was experiencing this problem when I used the linked serverFilePartial function, before making my adjustment to it. However, after 'fixing' that line, Safari/Quicktime seems to be happy with the first response, and continues to make subsequent ranged requests, and the progress bar and everything appears, and we are all good.
So, long story short, give the linked library a go, and see if it works for you like it did for me :) I know you have already found a php solution that works on your dev machine but not on your production machine, but maybe my different solution will be different on your VPS machine? worth a try.
just as a complement of information, I believe this could be related to this bug https://bugs.webkit.org/show_bug.cgi?id=82672
There have been a few "workarounds" proposed like :
xhr.setRequestHeader("If-None-Match", "webkit-no-cache");
Hope this can help you or people with similar problems.
do you instantiate the player yourself via javascript? Just try to load your resource via
player.setSrc("http://www.xxx.de/controller/action");
and listen for the 'canplay' - Event, if you want to play directly:
player.addEventListener("canplay", function(){
this.play();
});
I am writing a REST API and currently testing some things. I am trying to make it send an error response when it does not find anything in the database.
The part that is running (because i am testing currently by just entering the url into my browser) is below:
else if ($request->getHttpAccept() === 'xml')
{
if(isset($data['s']) && isset($data['n'])) {
$id = $db->getAlcoholIDByNameSize($data['n'], $data['s']);
$prices = $db->pricesByAlcohol($id);
}
if(isset($id)) {
$resData = array();
if(!empty($prices)) {
foreach($prices as $p) {
$store = $db->store($p['store']);
array_push($resData, array('storeID' => $p['store'], 'store_name' => $store['name'], 'store_gps' => $store['gps'], 'price' => round($p['price'], 2)));
}
RestUtils::sendResponse(200, json_encode($resData), 'application/json');
} else {
RestUtils::sendResponse(204, 'error', 'application/json');
}
} else {
RestUtils::sendResponse(204, 'error', 'application/json');
}
//RestUtils::sendResponse(501, "xml response not implemented", 'application/xml');
}
everything works fine if the queries return something to be stored in $id and $prices. If they do not exist in the database, however, it tries to load the page, and then goes back to the previous page you were on. You can see the behavior by going to:
http://easyuniv.com/API/alc/coorsa/2 <-- works
http://easyuniv.com/API/alc/coors/3 <-- works
http://easyuniv.com/API/alc/coorsa/5 <-- doesn't work(or anything else, the two above are the only ones)
here is my sendResponse function:
public static function sendResponse($status = 200, $body = '', $content_type = 'text/html')
{
$status_header = 'HTTP/1.1 ' . $status . ' ' . RestUtils::getStatusCodeMessage($status);
// set the status
header($status_header);
// set the content type
header('Content-type: ' . $content_type);
// pages with body are easy
if($body !== '')
{
$temp = json_decode($body);
$body = json_encode(array('result' => array('status' => $status, 'message' => RestUtils::getStatusCodeMessage($status)), 'data' => $temp));
// send the body
echo $body;
exit;
}
// we need to create the body if none is passed
else
{
$body = "else".json_encode(array('result' => array('status' => $status, 'message' => RestUtils::getStatusCodeMessage($status))));
echo $body;
exit;
}
}
I have tried debugging using echos but I cant seem to narrow down what the issue is. Any help would be appreciated, thanks.
The problem is that when there is no appropriate data found in the data base you are returning HTTP 204 which is telling the browser there is absolutely nothing for it to display. This is not true in your case.
You still want to output the message that there was nothing found.
To fix you need to replace the two instances of 204 in your code with 200.
I modified tested your code using: Note, nothing will display as is. To get the message to display change 204 to 200 in the $status_header variable.
<?php
$status_header = 'HTTP/1.1 204';
// set the status
header($status_header);
// set the content type
header('Content-type: text/html');
echo "Can you see me???";
?>
Note: When testing this always close the tab and use a fresh tab for each call or else it will look like it is loading data from the previous call, like you have explained.
Is there a way I can add a soap attachment to a request using PHP's built-in SoapClient classes? It doesn't look like it's supported, but maybe I can manually build the mime boundaries? I know the PEAR SOAP library supports them, but in order to use that I have to rewrite my entire library to use it.
Why don't you just send files using Data URI scheme rather than implement SoapAttachment ? Here is an example :
Client
$client = new SoapClient(null, array(
'location' => "http://localhost/lab/stackoverflow/a.php?h=none",
'uri' => "http://localhost/",
'trace' => 1
));
// Method 1 Array
// File to upload
$file = "golf3.png";
// First Example
$data = array();
$data['name'] = $file;
$data['data'] = getDataURI($file, "image/png");
echo "Example 1: ";
echo ($return = $client->upload($data)) ? "File Uploaded : $return bytes" : "Error Uploading Files";
// Method 2 Objects
// File to upload
$file = "original.png";
// Second Example
$attachment = new ImageObj($file);
$param = new SoapVar($attachment, SOAP_ENC_OBJECT, "ImageObj");
$param = new SoapParam($param, "param");
echo "Example 2: ";
echo ($return = $client->uploadObj($attachment)) ? "File Uploaded : $return bytes" : "Error Uploading Files";
Output
Example 1: File Uploaded : 976182 bytes
Example 2: File Uploaded : 233821 bytes
Server
class UploadService {
public function upload($args) {
$file = __DIR__ . "/test/" . $args['name'];
return file_put_contents($file, file_get_contents($args['data']));
}
public function uploadObj($args) {
$file = __DIR__ . "/test/" . $args->name;
$data = sprintf("data://%s;%s,%s", $args->mime, $args->encoding, $args->data);
return file_put_contents($file, file_get_contents($data));
}
}
try {
$server = new SOAPServer(NULL, array(
'uri' => 'http://localhost/'
));
$server->setClass('UploadService');
$server->handle();
} catch (SOAPFault $f) {
print $f->faultstring;
}
Client Util
// Function Used
function getDataURI($image, $mime = '') {
return 'data: ' . (function_exists('mime_content_type') ?
mime_content_type($image) : $mime) . ';base64,' .
base64_encode(file_get_contents($image));
}
// Simple Image Object
class ImageObj{
function __construct($file, $mime = "") {
$this->file = $file;
$this->name = basename($file);
if (function_exists('mime_content_type')) {
$this->mime = mime_content_type($file);
} elseif (function_exists('finfo_open')) {
$this->mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file);
} else {
$this->mime = $mime;
}
$this->encoding = "base64";
$this->data = base64_encode(file_get_contents($file));
}
}
Yes, you can build the MIME component of the message using something like imap_mail_compose.
You'll need to construct a multipart message as they do in the first example, putting the XML from the $request parameter, from an overridden SoapClient::__doRequest method, into the first part of the MIME message.
Then you can do as others have shown in the first imap_mail_compose example to add one or more messages parts with attachments. These attachements can, but do not have to be base64 encoded, they can just as well be binary. The encoding for each part is specified by part-specific headers.
You'll also need to cook up an appropriate set of HTTP headers, per the SwA Document #Baba linked to earlier.
Once it's all said and done, you should have something looking like the examples from that document:
MIME-Version: 1.0
Content-Type: Multipart/Related; boundary=MIME_boundary; type=text/xml;
start="<claim061400a.xml#claiming-it.com>"
Content-Description: This is the optional message description.
--MIME_boundary
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: 8bit
Content-ID: <claim061400a.xml#claiming-it.com>
<?xml version='1.0' ?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
..
<theSignedForm href="cid:claim061400a.tiff#claiming-it.com"/>
..
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
--MIME_boundary
Content-Type: image/tiff
Content-Transfer-Encoding: binary
Content-ID: <claim061400a.tiff#claiming-it.com>
...binary TIFF image...
--MIME_boundary--
And you can send that across the wire with the aforementioned overridden SoapClient::__doRequest method. Things I have noticed in trying to implement it myself thus far:
You may need to create an href URI reference from each SOAP node to the corresponding attachment, something like href="cid:claim061400a.tiff#claiming-it.com" above
You will need to extract the boundary component from the MIME content returned by imap_mail_compose for use in an HTTP Content-Type header
Don't forget the start component of the Content-Type header either, it should look something like this:
imap_mail_compose appears fairly minimal (but low hanging fruit), if it proves insufficient, consider Mail_Mime instead
Content-Type: Multipart/Related; boundary=MIME_boundary;
type=text/xml; start=""
Lastly, I'm not sure how evenly the various implementations of SwA are out there on the Internet... Suffice it to say, I've not been able to get an upload to a remote service with a crude implementation of what I've described above yet. It does seem like SwA is the typical SOAP attachment paradigm of choice though, from what I gather reading around on the net.