Convert string to an image file & save it with PHP

Ahmet Ekrem SABAN
Ahmet Ekrem SABAN used Ask the Experts™
on
Hello!

I have a PHP script that should create a valid image file on server side. The following code runs fine:
$filename = $_GET['filename'];

// Only proceed if we got valid input
if ($filename !== null) {
    echo "$filename is not null.";
    $image = @imagecreatetruecolor(10, 10)
                or die('Cannot Initialize new GD image stream');

    if (strpos($image, '/gif') !== false) {
        $image_type = "GIF";
        header('Content-Type: image/gif');
        $successful = imagegif($image, "./$filename");
    } else if (strpos($image, '/jpeg') !== false) {
        $image_type = "JPG";
        header('Content-Type: image/jpeg');
        $successful = imagejpeg($image, "./$filename");
    } else if (strpos($image, '/png') !== false) {
        $image_type = "PNG";
        header('Content-Type: image/png');
        $successful = imagepng($image, "./$filename");
    }

    if ($successful) {
        echo "Image written to '$filename'.";
    } else {
        echo "Could not write $image_type image to '$filename'.";
    }
    imagedestroy($image);
    echo "image destroyed.";
} else {
    echo "$filename is null.";
}

Open in new window


This works fine & an image with the $filename is created. But actually, I do not only receive the file name but the image content as well. So, the real code is

$filename = $_GET['filename'];

$image = $_GET['image'];

// Only proceed if we got valid input
if ($filename !== null) {
    echo "$filename is not null.";
    $image = base64_decode($image);
    $slash1 = strpos($image, '/');

    $image_type = substr($image, $slash1, strpos($image, ';') - $slash1);

    if (file_exists($filename)) unlink($filename);

    header('Content-Type: image/' . $image_type);

    switch ($image_type) {
        case "gif":
            $successful = imagegif($image, "./$filename");
            break;
        case "jpeg":
        case "jpg":
            $successful = imagejpeg($image, "./$filename");
            break;
         case "png":
            $successful = imagepng($image, "./$filename");
            break;
    }
    if ($successful) {
        echo "Image written to '$filename'.";
    } else {
        echo "Could not write $image_type image to '$filename'.";
    }
    imagedestroy($image);
    echo "image destroyed.";
} else {
    echo "$filename is null.";
}

Open in new window


But still, I get the reply Could not write image to 't.jpg'. Can you see my error(s)?
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Nereo BerardozziWeb Developer
Commented:
If you didn't edit the error message, the problem is that the variable $image_type is blank.
Ahmet Ekrem SABANSenior IT consultant

Author

Commented:
Thank you very much for the excellent point! You are right: the $image_type was undefined. Now, I corrected the code to

$filename = $_GET['filename'];

$image = $_GET['image'];

echo "file $filename = '$image'.";

// Only proceed if we got valid input
if ($filename !== null) {
    echo "'$filename' is not null.";
    $slash1 = strpos($image, '/') + 1;
    
    $image_type = substr($image, $slash1, strpos($image, ';') - $slash1);
    $image = base64_decode($image);

    if (file_exists($filename)) unlink($filename);

    header('Content-Type: image/' . $image_type);

    switch ($image_type) {
        case "gif":
            $successful = imagegif($image, "./$filename");
            break;
        case "jpeg":
        case "jpg":
            $successful = imagejpeg($image, "./$filename");
            break;
        case "png":
            $successful = imagepng($image, "./$filename");
            break;
    }
    
    if ($successful) {
        echo "Image written to '$filename'.";
    } else {
        echo "Could not write $image_type image to '$filename'.";
    }
    imagedestroy($image);
    echo "image destroyed.";
} else {
    echo "$filename is null.";
}

?>

Open in new window


but still get a Could not write jpeg image to 't.jpg' message.
Nereo BerardozziWeb Developer

Commented:
Can I know what do you pass to "&image="?
Build an E-Commerce Site with Angular 5

Learn how to build an E-Commerce site with Angular 5, a JavaScript framework used by developers to build web, desktop, and mobile applications.

Ahmet Ekrem SABANSenior IT consultant

Author

Commented:
Yes, here you go!

http://…/…?filename=t&image=data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/7AARRHVja3kAAQAEAAAAPAAA/+0ALFBob3Rvc2hvcCAzLjAAOEJJTQQlAAAAAAAQAAAAAAAAAAAAAAAAAAAAAP/bAEMAAgEBAgEBAgICAgICAgIDBQMDAwMDBgQEAwUHBgcHBwYHBwgJCwkICAoIBwcKDQoKCwwMDAwHCQ4PDQwOCwwMDP/bAEMBAgICAwMDBgMDBgwIBwgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAAEAAQMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APA6KKK/qA/Cz//Z

Open in new window


I modified the code a little, as it is not correct that the passed filename determines the file extension; this should correspond to the actual image type.

$filename = $_GET['filename'];

$image = $_GET['image'];

echo "file $filename = '$image'.";

// Only proceed if we got valid input
if ($filename !== null) {
	$slash = strpos($image, '/') + 1;
	
	$image_type = substr($image, $slash, strpos($image, ';') - $slash);
	$comma = strpos($image, ',') + 1;
	
	$image = substr($image, $comma);
	
	$decoded_image = base64_decode($image);
	
	$image = imagecreatefromstring($decoded_image);

	echo "The image type is '$image_type'.";
	
 	if (file_exists($filename)) {
		unlink($filename);
		echo "Deleted file '$filename'.";
	}

	header('Content-Type: image/' . $image_type);
	$filename .= '.' . $image_type;
	
	switch ($image_type) {
		case "gif":
			$successful = imagegif($image, "./$filename");
			break;
		case "jpeg":
		case "jpg":
			$successful = imagejpeg($image, "./$filename");
			break;
		case "png":
			$successful = imagepng($image, "./$filename");
			break;
	}
	
	if ($successful) {
		echo "Image written to '$filename'.";
	} else {
		echo "Could not write $image_type image to '$filename'.";
	}

	if (imagedestroy($image) === true) {
		echo "Image destroyed.";
	}
} else {
	echo "$filename is null.";
}

Open in new window

Nereo BerardozziWeb Developer

Commented:
To fix it you should encode the string of the image before passing it:
$encoded_string = urlencode('/9j/4AAQSkZJRgABAQEAYABgAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/7AARRHVja3kAAQAEAAAAPAAA/+0ALFBob3Rvc2hvcCAzLjAAOEJJTQQlAAAAAAAQAAAAAAAAAAAAAAAAAAAAAP/bAEMAAgEBAgEBAgICAgICAgIDBQMDAwMDBgQEAwUHBgcHBwYHBwgJCwkICAoIBwcKDQoKCwwMDAwHCQ4PDQwOCwwMDP/bAEMBAgICAwMDBgMDBgwIBwgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAAEAAQMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APA6KKK/qA/Cz//Z');
echo $encoded_string;

Open in new window

greetings  Ahmet Ekrem SABAN , , , ,   You seem to be having trouble with dealing with the uploaded IMAGE data string. This looks like a standard "BROWSER" javascript string to have Image file in a string and use base64 encode. I have found it somewhat better to parse (shorten) the FILE string for the base64 in Javascript before I sent it to the PHP in an upload. This is the page Code I used in PHP for an Base 64 encode string in the PHP    $_POST['png']   upload -
<!doctype html><html><head><title>Canvas to PNG</title>
<style>body{background:#afa;}</style></head>
<body><h3>Canvas to PNG</h3>
<?php ini_set("display_errors",1);error_reporting(E_ALL);

$png64 = (empty($_POST['png'])) ? 0 : $_POST['png'];
if (!$png64) {
  echo '<b>ERROR, the PNG base64 was NOT SENT in post!</b><br />';
  exit();
  }

$png64 = base64_decode($png64);
file_put_contents('canpng1.png', $png64);

/*
	
$size_info = getimagesizefromstring($png64);
if (!$size_info) {
  echo '<b>ERROR, the PNG Image Data is Corupt</b><br />';
  exit();
  }
echo '<p>png Image height is '.$size_info[1].', width is '.$size_info[0].'</p><hr>';

*/
?>
<img src="canpng1.png" />
</body></html>

Open in new window


this is the Javascript Function that sends the $_POST['png'] up -
<script>/* <![CDATA[ */

function draw_square() {
   ctx.fillStyle = "#f08";
   ctx.fillRect(5, 5, 13, 13); // Draw On the Canvas

   var td = can.toDataURL(); // This gets the Canvas as a PNG base64 Image

   td = td.substr(22); // delete the first 22 characters (as this file header)
   //alert(td.length+":length "+td);

// I place this base 64 string into a <form>
   document.getElementById("ih").value = td;
   document.pngF.submit(); // submit form
}

/* ]]> */</script>

Open in new window


you have the AJAX upload URL string in you comment  ID: 42299732, which begins with -
     "data:image/jpeg;base64,"
a 23 length file header, which is UNNEEDED data, why do you send it if it is unneeded?

you do NOT need to place the Image data string into the PHP GD image, the Image data after the
     base64_decode(  )
if the usual FILE binary bytes, which you can just write to disk with a file write, I use -
    file_put_contents(  )
but there are other PHP file writes.

Can you tell us More about your operations and processes to get this base64 File string in to the browser Javascript? Do you use a browser Canvas element? Or what source places your Image file data string?
NerdsOfTechTechnology Scientist

Commented:
I think the only thing missing is that the folder should be CHMOD for script file writing privileges. Check the folder via FTP. You might need to change it to 744 opposed to 644.
Ahmet Ekrem SABANSenior IT consultant

Author

Commented:
Thank you all for your reply! I try it one-by-one.

@Nereo Berardozzi, I changed the code as follows:

if ($filename !== null) {
	$slash = strpos($image, '/') + 1;
	
	$image_type = substr($image, $slash, strpos($image, ';') - $slash);
	// delboy1978uk
	$comma = strpos($image, ',') + 1;
	// Remove "header" information from rest of image:
	$image = substr($image, $comma);
	
	// delboy1978uk
	//$decoded_image = base64_decode($image);
	$encoded_image = urlencode($image);
	
	echo "encoded image = '$encoded_image'.";
	//$image = imagecreatefromstring($decoded_image);
	$image = imagecreatefromstring($encoded_image);

	echo "The image type is '$image_type'.";

Open in new window


and called http://<address>/ServiceTest/saveHeaderImage.php?filename=t&image=data:image/jpeg;base64,/9j/4AA … Cz//Z. The result changed to

file t = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA … A6KKK/qA/Cz//Z'.encoded image = '%2F9j%2F4AAQSkZJRgABAQEAYABgAAD … %2FCz%2F%2FZ'.The image type is 'jpeg'.Could not write jpeg image to 't.jpeg'.

Open in new window


@NerdsOfTech, I can write to the destination directory, but only a dummy generated image, so there are no access problems.

And special thanks to @Slick812, as I could now write the file with the following code:

ini_set("display_errors", 1);
error_reporting(E_ALL);

// Get the input data safely
$filename = $_GET['filename'];

$image = $_GET['image'];

echo "file $filename = '$image'.";

// Only proceed if we got valid input
if ($filename !== null) {
	$slash = strpos($image, '/') + 1;
	
	$image_type = substr($image, $slash, strpos($image, ';') - $slash);
	$comma = strpos($image, ',') + 1;
	// Remove "header" information from rest of image:
	$image = substr($image, $comma);
	
	$decoded_image = base64_decode($image);
	
	echo "The image type is '$image_type'.";
	
 	if (file_exists($filename)) {
		unlink($filename);
		echo "Deleted file '$filename'.";
	}

	$filename .= '.' . $image_type;
	
	$successful = file_put_contents($filename, $decoded_image);
	
	if ($successful) {
		echo "Image written to '$filename'.";
	} else {
		echo "Could not write $image_type image to '$filename'.";
	}
} else {

Open in new window


About the process: I am modifying an existing code for a page generator that gets the image of interest by a drag-and-drop action. The element is not a canvas, but a special DIV element. The image is represented only with a blob:null/<numbers-numbers> URL that points to the browser cash. So, I take the image and try to "throw" it to the "other side", i.e. the server directory. :->

What remains is to save an image file that has a valid content, as the currently saved file cannot be opened with any image app.
t.jpeg
Ahmet Ekrem SABANSenior IT consultant

Author

Commented:
Another technical problem I have while debugging this is that I can test the backend part in PHP only with a GET request, as I can send the data over the address bar. The real image, however, is far larger than the test image and will not fit for sure into the 8 KB limit of the GET command. So, to test the real case is tricky.
NerdsOfTechTechnology Scientist

Commented:
Can you use PUT instead?

You might be able to use CURL also.
I looked at your PHP code attempts to get this image into a file, and I decided to try a PHP test myself, here is ie code I used -
<!doctype html><html><head><title>Base64 Image TEST</title>
<style>body{background:#afa;}</style></head>
<body><h3>Base64 Image TEST</h3><?php
ini_set('display_errors', 1);
error_reporting(E_ALL);

$image = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAA'.
'D/7AARRHVja3kAAQAEAAAAPAAA/+0ALFBob3Rvc2hvcCAzLjAAOEJJTQQlAAAAAAAQAAAAAAAAAAAAAAAAAAAAAP'.
'/bAEMAAgEBAgEBAgICAgICAgIDBQMDAwMDBgQEAwUHBgcHBwYHBwgJCwkICAoIBwcKDQoKCwwMDAwHCQ4PDQwOCwwMD'.
'P/bAEMBAgICAwMDBgMDBgwIBwgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw'.
'MDP/AABEIAAEAAQMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1'.
'EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpD'.
'REVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1d'.
'bX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQ'.
'IDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGl'.
'qc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19v'.
'f4+fr/2gAMAwEAAhEDEQA/APA6KKK/qA/Cz//Z';
$image = explode(",",$image);
$image[1] = base64_decode($image[1]);
file_put_contents('aTest.jpg', $image[1]);
echo 'The base64 header = ',$image[0],'<br />';
$image[0] = substr($image[1], 0, 11);
echo 'The Binary header = ',$image[0],'<br />';
$size_info = getimagesizefromstring($image[1]);
if (!$size_info) {
  echo '<b>ERROR, the PNG Image Data is Corupt</b><br />';
  } else echo ', width is ',$size_info[0],'<br />';
?>

<img src="aTest.jpg" style="width:8px" />
</body></html>

Open in new window

and this displayed a JPEG image in the -
    <img src="aTest.jpg" style="width:8px" />
although when I tried to open the aTest.jpg in an image editor, the editor said the file was NOT a proper JFIF file and did not open it?

I beleive your problem is in the JAVASCRIPT?
you do NOT correctly set up the URL string in the javascript for the GET web address, as  Nereo Berardozzi  tried to tell you about the special characters in a URL string, you must make sure that the GET URL has special characters changed to the correct substitutes for a URL string, in javascript I use this -
     encodeURIComponent( )
to change characters to the format for URL strings.

Can you tell us more about the operations that place the URL string into the javascript , , AND more about the javascript?

AND you will have to use a POST to send the larger Image files in base64.
Ahmet Ekrem SABANSenior IT consultant

Author

Commented:
That is a good point with encodeURIComponent( ), as an arbitrary string cannot be properly sent over the net without this. But I switch over to the POST request. Is this then still an issue? Here is the upload code…

function fileUpload(file, fileName, url) {
	var functionName = logging.createFunctionName('fileUpload', file, fileName, url);
	var xhr = new XMLHttpRequest();    // AJAX request

	messages.print(functionName, 'called');

	console.log('url = "' + url + '"');
	xhr.open('POST', url);
	var formData = new FormData();

	formData.append('filename', fileName);
	formData.append('image', file);

	xhr.send(formData);
}

Open in new window


And here are the lines where it is called:

function continueWithNewsTicker(parameters) {
	var functionName = logging.createFunctionName(server.PrototypeName
		+ Dot + 'continueWithNewsTicker', data);
	var newsTicker = {};

	newsTicker['ip'] = document.getElementById('newsTickerURLField').value;
	newsTicker['text'] = document.getElementById('newsTickerTextArea').value;
	/*var w = window.open('');

	w.document.write(image.outerHTML);*/
	var url = server.ServiceTest + 'saveHeaderImage.php';	// http://www. …/ServiceTest/saveHeaderImage.php

	if (strings.hasMinimalLength(parameters[headerImageLocation], 1)) {
		fileUpload(parameters[image].src, parameters[headerImageLocation], url);	// <<<<<<<<<<<<<<<<<<<<
	}
	// The following code makes an entry to the database with a reference to the uploaded file URI
	var data = {
		description: parameters[description],
		elements: parameters[elements],
		header: parameters[headerImageLocation],
		id: parameters[value],
		name: parameters[name],
		newsTicker: parameters[newsTicker]
	};
	//continueMessage(data, message, value);
	parameters[ipAddress] = createIPAddressPrintMessage(parameters[ipAddress]);

	server.uber('write', parameters[ipAddress], parameters[scope], parameters[serverAccess], data);
}

Open in new window

Senior IT consultant
Commented:
I have good news to you: The problem is solved!! Here is the code that gets the browser-cached image & sends it to a requested server location. Getting back the image from the server, it turns out to be the same image. First the PHP code:

ini_set('display_errors', 1);
error_reporting(E_ALL);

// Get the input data safely
$filename = $_POST['filename'];
$image = $_POST['image'];

// Only proceed if we got valid input
if ($filename !== null) {
	// Prepare to remove "header" information:
	$comma = strpos($image, ',') + 1;
	$slash = strpos($image, '/') + 1;
	// The image type will also determine the file extension
	$image_type = substr($image, $slash, strpos($image, ';') - $slash);
	// Remove "header" information from rest of image:
	$image = substr($image, $comma);
	
	$decoded_image = base64_decode($image);
	$filename .= '.' . $image_type;
	
 	if (file_exists($filename)) unlink($filename);
	
	$successful = file_put_contents($filename, $decoded_image);
	
	if ($successful) {
		echo "Image written to '$filename'.";
	} else {
		echo "Could not write $image_type image to '$filename'.";
	}
} else {
	echo "$filename is null.";
}

Open in new window


And the JavaScript code (for those who are interested):

	[…]
	var parameters = {
		[…],
		headerImageLocation: '',
		image: new Image(),
		[…],
	};

	if (strings.hasMinimalLength(blobURL, 9)) {
		this.getBlobFromURL(blobURL).then(this.fromBlobToBase64).then(function(result) {
			parameters['image'].src = result;
			parameters['headerImageLocation'] = './' + strings.generateID();

			server.continueWithNewsTicker(parameters);
		});
	} else {
		this.continueWithNewsTicker(parameters);
	}

// Prototype "MainServer":
MainServer.method('continueWithNewsTicker', function(parameters) {
	var url = server.ServiceTest + 'saveHeaderImage.php';

	if (strings.hasMinimalLength(parameters['headerImageLocation'], 1)) {
		var formData = new FormData();

		formData.append('filename', parameters['headerImageLocation']);
		formData.append('image', parameters['image'].src);

		this.uploadFile(formData, url);
	}
	[…]
});

// Prototype "Server":
Server.method('uploadFile', function (data, url) {
	var xhr = new XMLHttpRequest();    // AJAX request

	xhr.open('POST', url);
	xhr.send(data);
});

// Prototype "Strings":
Strings.method('generateID', function () {
	function s4() {
		return Math.floor((1 + Math.random())*0x10000)
			.toString(16)
			.substring(1);
	}
	return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
		s4() + '-' + s4() + s4() + s4();
});

Open in new window

Ahmet Ekrem SABANSenior IT consultant

Author

Commented:
My solution gave the result I was searching for, an image file created on the server side that could be opened & is identical to the original image.

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial