Merge remote-tracking branch 'origin/3.0' into 3.1

Conflicts:
	admin/javascript/LeftAndMain.AddForm.js
	control/Director.php
	control/HTTPResponse.php
	dev/Profiler.php
	email/Mailer.php
	forms/ComplexTableField.php
	forms/ManyManyComplexTableField.php
	forms/SimpleImageField.php
	forms/TableField.php
	forms/TableListField.php
	javascript/ComplexTableField.js
	javascript/ImageFormAction.js
	javascript/TableField.js
	javascript/TableListField.js
	security/Member.php
	tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php
	tests/forms/TableListFieldTest.php
This commit is contained in:
Ingo Schommer 2012-12-12 10:11:56 +01:00
commit f03ad7b0dd
7 changed files with 362 additions and 277 deletions

View File

@ -141,18 +141,18 @@ class Director implements TemplateGlobalProvider {
$res = Injector::inst()->get('RequestProcessor')->postRequest($req, $response, $model); $res = Injector::inst()->get('RequestProcessor')->postRequest($req, $response, $model);
if ($res !== false) { if ($res !== false) {
// Set content length (according to RFC2616) // Set content length (according to RFC2616)
if( if(
!headers_sent() !headers_sent()
&& $response->getBody() && $response->getBody()
&& $req->httpMethod() != 'HEAD' && $req->httpMethod() != 'HEAD'
&& $response->getStatusCode() >= 200 && $response->getStatusCode() >= 200
&& !in_array($response->getStatusCode(), array(204, 304)) && !in_array($response->getStatusCode(), array(204, 304))
) { ) {
$response->fixContentLength(); $response->fixContentLength();
} }
$response->output(); $response->output();
} else { } else {
// @TODO Proper response here. // @TODO Proper response here.
throw new SS_HTTPResponse_Exception("Invalid response"); throw new SS_HTTPResponse_Exception("Invalid response");
@ -308,7 +308,7 @@ class Director implements TemplateGlobalProvider {
} }
} }
} }
/** /**
* Set url parameters (should only be called internally by RequestHandler->handleRequest()). * Set url parameters (should only be called internally by RequestHandler->handleRequest()).
* *
@ -454,7 +454,7 @@ class Director implements TemplateGlobalProvider {
// Allow for the accidental inclusion whitespace and // in the URL // Allow for the accidental inclusion whitespace and // in the URL
$url = trim(preg_replace('#([^:])//#', '\\1/', $url)); $url = trim(preg_replace('#([^:])//#', '\\1/', $url));
$base1 = self::absoluteBaseURL(); $base1 = self::absoluteBaseURL();
$baseDomain = substr($base1, strlen(self::protocol())); $baseDomain = substr($base1, strlen(self::protocol()));
// Only bother comparing the URL to the absolute version if $url looks like a URL. // Only bother comparing the URL to the absolute version if $url looks like a URL.
@ -470,10 +470,10 @@ class Director implements TemplateGlobalProvider {
return substr($url,strlen($base1)); return substr($url,strlen($base1));
} }
else if(substr($base1,-1)=="/" && $url == substr($base1,0,-1)) { else if(substr($base1,-1)=="/" && $url == substr($base1,0,-1)) {
// Convert http://www.mydomain.com/mysitedir to '' // Convert http://www.mydomain.com/mysitedir to ''
return ""; return "";
} }
if(substr($urlWithoutProtocol,0,strlen($baseDomain)) == $baseDomain) { if(substr($urlWithoutProtocol,0,strlen($baseDomain)) == $baseDomain) {
return substr($urlWithoutProtocol,strlen($baseDomain)); return substr($urlWithoutProtocol,strlen($baseDomain));
} }
@ -488,7 +488,7 @@ class Director implements TemplateGlobalProvider {
if(substr($url,0,strlen($base3)) == $base3) { if(substr($url,0,strlen($base3)) == $base3) {
return substr($url,strlen($base3)); return substr($url,strlen($base3));
} }
// Test for relative base url, e.g mywebsite/ if the full url is localhost/myswebsite // Test for relative base url, e.g mywebsite/ if the full url is localhost/myswebsite
if(substr($url,0,strlen($baseDomain)) == $baseDomain) { if(substr($url,0,strlen($baseDomain)) == $baseDomain) {
return substr($url, strlen($baseDomain)); return substr($url, strlen($baseDomain));
@ -539,8 +539,8 @@ class Director implements TemplateGlobalProvider {
// Base check for existence of a host on a compliant URL // Base check for existence of a host on a compliant URL
parse_url($url, PHP_URL_HOST) parse_url($url, PHP_URL_HOST)
// Check for more than one leading slash without a protocol. // Check for more than one leading slash without a protocol.
// While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers, // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
// and hence a potential security risk. Single leading slashes are not an issue though. // and hence a potential security risk. Single leading slashes are not an issue though.
|| preg_match('/\s*[\/]{2,}/', $url) || preg_match('/\s*[\/]{2,}/', $url)
|| ( || (
// If a colon is found, check if it's part of a valid scheme definition // If a colon is found, check if it's part of a valid scheme definition

View File

@ -123,7 +123,7 @@ class SS_HTTPResponse {
* The text to be given alongside the status code ("reason phrase"). * The text to be given alongside the status code ("reason phrase").
* Caution: Will be overwritten by {@link setStatusCode()}. * Caution: Will be overwritten by {@link setStatusCode()}.
* *
* @param String $description * @param String $description
* @return SS_HTTPRequest $this * @return SS_HTTPRequest $this
*/ */
public function setStatusDescription($description) { public function setStatusDescription($description) {
@ -153,7 +153,7 @@ class SS_HTTPResponse {
public function isError() { public function isError() {
return $this->statusCode && ($this->statusCode < 200 || $this->statusCode > 399); return $this->statusCode && ($this->statusCode < 200 || $this->statusCode > 399);
} }
/** /**
* @param string $body * @param string $body
* @return SS_HTTPRequest $this * @return SS_HTTPRequest $this
@ -161,7 +161,7 @@ class SS_HTTPResponse {
public function setBody($body) { public function setBody($body) {
$this->body = $body; $this->body = $body;
} }
/** /**
* @return null|string * @return null|string
*/ */
@ -173,7 +173,7 @@ class SS_HTTPResponse {
* Add a HTTP header to the response, replacing any header of the same name. * Add a HTTP header to the response, replacing any header of the same name.
* *
* @param string $header Example: "Content-Type" * @param string $header Example: "Content-Type"
* @param string $value Example: "text/xml" * @param string $value Example: "text/xml"
* @return SS_HTTPRequest $this * @return SS_HTTPRequest $this
*/ */
public function addHeader($header, $value) { public function addHeader($header, $value) {
@ -189,9 +189,9 @@ class SS_HTTPResponse {
*/ */
public function getHeader($header) { public function getHeader($header) {
if(isset($this->headers[$header])) if(isset($this->headers[$header]))
return $this->headers[$header]; return $this->headers[$header];
return null; return null;
} }
/** /**
* @return array * @return array
@ -211,7 +211,7 @@ class SS_HTTPResponse {
if(isset($this->headers[$header])) unset($this->headers[$header]); if(isset($this->headers[$header])) unset($this->headers[$header]);
return $this; return $this;
} }
/** /**
* @param string $dest * @param string $dest
* @param int $code * @param int $code

View File

@ -362,6 +362,7 @@ class Injector {
// EXCEPT when there's already an existing instance at this id. // EXCEPT when there's already an existing instance at this id.
// if so, we need to instantiate and replace immediately // if so, we need to instantiate and replace immediately
if (isset($this->serviceCache[$id])) { if (isset($this->serviceCache[$id])) {
$this->updateSpecConstructor($spec);
$this->instantiate($spec, $id); $this->instantiate($spec, $id);
} }
} }
@ -403,6 +404,20 @@ class Injector {
} }
} }
} }
/**
* Update a class specification to convert constructor configuration information if needed
*
* We do this as a separate process to avoid unneeded calls to convertServiceProperty
*
* @param array $spec
* The class specification to update
*/
protected function updateSpecConstructor(&$spec) {
if (isset($spec['constructor'])) {
$spec['constructor'] = $this->convertServiceProperty($spec['constructor']);
}
}
/** /**
* Recursively convert a value into its proper representation with service references * Recursively convert a value into its proper representation with service references
@ -468,7 +483,7 @@ class Injector {
$constructorParams = $spec['constructor']; $constructorParams = $spec['constructor'];
} }
$object = $this->objectCreator->create($this, $class, $constructorParams); $object = $this->objectCreator->create($class, $constructorParams);
// figure out if we have a specific id set or not. In some cases, we might be instantiating objects // figure out if we have a specific id set or not. In some cases, we might be instantiating objects
// that we don't manage directly; we don't want to store these in the service cache below // that we don't manage directly; we don't want to store these in the service cache below
@ -730,15 +745,22 @@ class Injector {
// we don't want to return the singleton version of it. // we don't want to return the singleton version of it.
$spec = $this->specs[$serviceName]; $spec = $this->specs[$serviceName];
$type = isset($spec['type']) ? $spec['type'] : null; $type = isset($spec['type']) ? $spec['type'] : null;
// if we're explicitly a prototype OR we're not wanting a singleton // if we're explicitly a prototype OR we're not wanting a singleton
if (($type && $type == 'prototype') || !$asSingleton) { if (($type && $type == 'prototype') || !$asSingleton) {
if ($spec && $constructorArgs) { if ($spec && $constructorArgs) {
$spec['constructor'] = $constructorArgs; $spec['constructor'] = $constructorArgs;
} else {
// convert any _configured_ constructor args.
// we don't call this for get() calls where someone passes in
// constructor args, otherwise we end up calling convertServiceParams
// way too often
$this->updateSpecConstructor($spec);
} }
return $this->instantiate($spec, $serviceName, !$type ? 'prototype' : $type); return $this->instantiate($spec, $serviceName, !$type ? 'prototype' : $type);
} else { } else {
if (!isset($this->serviceCache[$serviceName])) { if (!isset($this->serviceCache[$serviceName])) {
$this->updateSpecConstructor($spec);
$this->instantiate($spec, $serviceName); $this->instantiate($spec, $serviceName);
} }
return $this->serviceCache[$serviceName]; return $this->serviceCache[$serviceName];
@ -750,6 +772,7 @@ class Injector {
$this->load(array($name => $config)); $this->load(array($name => $config));
if (isset($this->specs[$name])) { if (isset($this->specs[$name])) {
$spec = $this->specs[$name]; $spec = $this->specs[$name];
$this->updateSpecConstructor($spec);
return $this->instantiate($spec, $name); return $this->instantiate($spec, $name);
} }
} }
@ -816,10 +839,10 @@ class InjectionCreator {
* @param array $params * @param array $params
* An array of parameters to be passed to the constructor * An array of parameters to be passed to the constructor
*/ */
public function create(Injector $injector, $class, $params = array()) { public function create($class, $params = array()) {
$reflector = new ReflectionClass($class); $reflector = new ReflectionClass($class);
if (count($params)) { if (count($params)) {
return $reflector->newInstanceArgs($injector->convertServiceProperty($params)); return $reflector->newInstanceArgs($params);
} }
return $reflector->newInstance(); return $reflector->newInstance();
} }
@ -833,10 +856,10 @@ class SilverStripeInjectionCreator {
* @param array $params * @param array $params
* An array of parameters to be passed to the constructor * An array of parameters to be passed to the constructor
*/ */
public function create(Injector $injector, $class, $params = array()) { public function create($class, $params = array()) {
$class = Object::getCustomClass($class); $class = Object::getCustomClass($class);
$reflector = new ReflectionClass($class); $reflector = new ReflectionClass($class);
return $reflector->newInstanceArgs($injector->convertServiceProperty($params)); return $reflector->newInstanceArgs($params);
} }
} }

View File

@ -9,7 +9,7 @@
/** /**
* Execution time profiler. * Execution time profiler.
* *
* @deprecated 3.1 The Profiler class is deprecated, use third party tools like XHProf instead * @deprecated 3.1 The Profiler class is deprecated, use third party tools like XHProf instead
* *
* @package framework * @package framework

View File

@ -26,8 +26,8 @@ class Mailer {
if ($customheaders && is_array($customheaders) == false) { if ($customheaders && is_array($customheaders) == false) {
echo "htmlEmail($to, $from, $subject, ...) could not send mail: improper \$customheaders passed:<BR>"; echo "htmlEmail($to, $from, $subject, ...) could not send mail: improper \$customheaders passed:<BR>";
dieprintr($customheaders); dieprintr($customheaders);
} }
// If the subject line contains extended characters, we must encode it // If the subject line contains extended characters, we must encode it
$subject = Convert::xml2raw($subject); $subject = Convert::xml2raw($subject);
$subject = "=?UTF-8?B?" . base64_encode($subject) . "?="; $subject = "=?UTF-8?B?" . base64_encode($subject) . "?=";
@ -52,9 +52,9 @@ class Mailer {
$messageParts[] = $this->encodeFileForEmail($file['tmp_name'], $file['name']); $messageParts[] = $this->encodeFileForEmail($file['tmp_name'], $file['name']);
} else { } else {
$messageParts[] = $this->encodeFileForEmail($file); $messageParts[] = $this->encodeFileForEmail($file);
} }
} }
// We further wrap all of this into another multipart block // We further wrap all of this into another multipart block
list($fullBody, $headers) = $this->encodeMultipart($messageParts, "multipart/mixed"); list($fullBody, $headers) = $this->encodeMultipart($messageParts, "multipart/mixed");
@ -104,268 +104,268 @@ class Mailer {
} }
/** /**
* Sends an email as a both HTML and plaintext * Sends an email as a both HTML and plaintext
* *
* $attachedFiles should be an array of file names * $attachedFiles should be an array of file names
* - if you pass the entire $_FILES entry, the user-uploaded filename will be preserved * - if you pass the entire $_FILES entry, the user-uploaded filename will be preserved
* use $plainContent to override default plain-content generation * use $plainContent to override default plain-content generation
* *
* @return bool * @return bool
*/ */
public function sendHTML($to, $from, $subject, $htmlContent, $attachedFiles = false, $customheaders = false, public function sendHTML($to, $from, $subject, $htmlContent, $attachedFiles = false, $customheaders = false,
$plainContent = false) { $plainContent = false) {
if ($customheaders && is_array($customheaders) == false) { if ($customheaders && is_array($customheaders) == false) {
echo "htmlEmail($to, $from, $subject, ...) could not send mail: improper \$customheaders passed:<BR>"; echo "htmlEmail($to, $from, $subject, ...) could not send mail: improper \$customheaders passed:<BR>";
dieprintr($customheaders); dieprintr($customheaders);
} }
$bodyIsUnicode = (strpos($htmlContent,"&#") !== false); $bodyIsUnicode = (strpos($htmlContent,"&#") !== false);
$plainEncoding = ""; $plainEncoding = "";
// We generate plaintext content by default, but you can pass custom stuff // We generate plaintext content by default, but you can pass custom stuff
$plainEncoding = ''; $plainEncoding = '';
if(!$plainContent) { if(!$plainContent) {
$plainContent = Convert::xml2raw($htmlContent); $plainContent = Convert::xml2raw($htmlContent);
if(isset($bodyIsUnicode) && $bodyIsUnicode) $plainEncoding = "base64"; if(isset($bodyIsUnicode) && $bodyIsUnicode) $plainEncoding = "base64";
} }
// If the subject line contains extended characters, we must encode the // If the subject line contains extended characters, we must encode the
$subject = Convert::xml2raw($subject); $subject = Convert::xml2raw($subject);
$subject = "=?UTF-8?B?" . base64_encode($subject) . "?="; $subject = "=?UTF-8?B?" . base64_encode($subject) . "?=";
// Make the plain text part // Make the plain text part
$headers["Content-Type"] = "text/plain; charset=utf-8"; $headers["Content-Type"] = "text/plain; charset=utf-8";
$headers["Content-Transfer-Encoding"] = $plainEncoding ? $plainEncoding : "quoted-printable"; $headers["Content-Transfer-Encoding"] = $plainEncoding ? $plainEncoding : "quoted-printable";
$plainPart = $this->processHeaders($headers, ($plainEncoding == "base64") $plainPart = $this->processHeaders($headers, ($plainEncoding == "base64")
? chunk_split(base64_encode($plainContent),60) ? chunk_split(base64_encode($plainContent),60)
: wordwrap($this->QuotedPrintable_encode($plainContent),75)); : wordwrap($this->QuotedPrintable_encode($plainContent),75));
// Make the HTML part // Make the HTML part
$headers["Content-Type"] = "text/html; charset=utf-8"; $headers["Content-Type"] = "text/html; charset=utf-8";
// Add basic wrapper tags if the body tag hasn't been given // Add basic wrapper tags if the body tag hasn't been given
if(stripos($htmlContent, '<body') === false) { if(stripos($htmlContent, '<body') === false) {
$htmlContent = $htmlContent =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n" . "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n" .
"<HTML><HEAD>\n" . "<HTML><HEAD>\n" .
"<META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n" . "<META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n" .
"<STYLE type=\"text/css\"></STYLE>\n\n". "<STYLE type=\"text/css\"></STYLE>\n\n".
"</HEAD>\n" . "</HEAD>\n" .
"<BODY bgColor=\"#ffffff\">\n" . "<BODY bgColor=\"#ffffff\">\n" .
$htmlContent . $htmlContent .
"\n</BODY>\n" . "\n</BODY>\n" .
"</HTML>"; "</HTML>";
} }
$headers["Content-Transfer-Encoding"] = "quoted-printable"; $headers["Content-Transfer-Encoding"] = "quoted-printable";
$htmlPart = $this->processHeaders($headers, wordwrap($this->QuotedPrintable_encode($htmlContent),75)); $htmlPart = $this->processHeaders($headers, wordwrap($this->QuotedPrintable_encode($htmlContent),75));
list($messageBody, $messageHeaders) = $this->encodeMultipart( list($messageBody, $messageHeaders) = $this->encodeMultipart(
array($plainPart,$htmlPart), array($plainPart,$htmlPart),
"multipart/alternative" "multipart/alternative"
); );
// Messages with attachments are handled differently // Messages with attachments are handled differently
if($attachedFiles && is_array($attachedFiles)) { if($attachedFiles && is_array($attachedFiles)) {
// The first part is the message itself // The first part is the message itself
$fullMessage = $this->processHeaders($messageHeaders, $messageBody); $fullMessage = $this->processHeaders($messageHeaders, $messageBody);
$messageParts = array($fullMessage); $messageParts = array($fullMessage);
// Include any specified attachments as additional parts // Include any specified attachments as additional parts
foreach($attachedFiles as $file) { foreach($attachedFiles as $file) {
if(isset($file['tmp_name']) && isset($file['name'])) { if(isset($file['tmp_name']) && isset($file['name'])) {
$messageParts[] = $this->encodeFileForEmail($file['tmp_name'], $file['name']); $messageParts[] = $this->encodeFileForEmail($file['tmp_name'], $file['name']);
} else { } else {
$messageParts[] = $this->encodeFileForEmail($file); $messageParts[] = $this->encodeFileForEmail($file);
}
} }
}
// We further wrap all of this into another multipart block
// We further wrap all of this into another multipart block
list($fullBody, $headers) = $this->encodeMultipart($messageParts, "multipart/mixed"); list($fullBody, $headers) = $this->encodeMultipart($messageParts, "multipart/mixed");
// Messages without attachments do not require such treatment // Messages without attachments do not require such treatment
} else { } else {
$headers = $messageHeaders; $headers = $messageHeaders;
$fullBody = $messageBody; $fullBody = $messageBody;
} }
// Email headers // Email headers
$headers["From"] = $this->validEmailAddr($from); $headers["From"] = $this->validEmailAddr($from);
// Messages with the X-SilverStripeMessageID header can be tracked // Messages with the X-SilverStripeMessageID header can be tracked
if(isset($customheaders["X-SilverStripeMessageID"]) && defined('BOUNCE_EMAIL')) { if(isset($customheaders["X-SilverStripeMessageID"]) && defined('BOUNCE_EMAIL')) {
$bounceAddress = BOUNCE_EMAIL; $bounceAddress = BOUNCE_EMAIL;
} else { } else {
$bounceAddress = $from; $bounceAddress = $from;
} }
// Strip the human name from the bounce address // Strip the human name from the bounce address
if(preg_match('/^([^<>]*)<([^<>]+)> *$/', $bounceAddress, $parts)) $bounceAddress = $parts[2]; if(preg_match('/^([^<>]*)<([^<>]+)> *$/', $bounceAddress, $parts)) $bounceAddress = $parts[2];
// $headers["Sender"] = $from; // $headers["Sender"] = $from;
$headers["X-Mailer"] = X_MAILER; $headers["X-Mailer"] = X_MAILER;
if (!isset($customheaders["X-Priority"])) $headers["X-Priority"] = 3; if (!isset($customheaders["X-Priority"])) $headers["X-Priority"] = 3;
$headers = array_merge((array)$headers, (array)$customheaders); $headers = array_merge((array)$headers, (array)$customheaders);
// the carbon copy header has to be 'Cc', not 'CC' or 'cc' -- ensure this. // the carbon copy header has to be 'Cc', not 'CC' or 'cc' -- ensure this.
if (isset($headers['CC'])) { $headers['Cc'] = $headers['CC']; unset($headers['CC']); } if (isset($headers['CC'])) { $headers['Cc'] = $headers['CC']; unset($headers['CC']); }
if (isset($headers['cc'])) { $headers['Cc'] = $headers['cc']; unset($headers['cc']); } if (isset($headers['cc'])) { $headers['Cc'] = $headers['cc']; unset($headers['cc']); }
// the carbon copy header has to be 'Bcc', not 'BCC' or 'bcc' -- ensure this.
if (isset($headers['BCC'])) {$headers['Bcc']=$headers['BCC']; unset($headers['BCC']); }
if (isset($headers['bcc'])) {$headers['Bcc']=$headers['bcc']; unset($headers['bcc']); }
// the carbon copy header has to be 'Bcc', not 'BCC' or 'bcc' -- ensure this.
if (isset($headers['BCC'])) {$headers['Bcc']=$headers['BCC']; unset($headers['BCC']); } // Send the email
if (isset($headers['bcc'])) {$headers['Bcc']=$headers['bcc']; unset($headers['bcc']); }
// Send the email
$headers = $this->processHeaders($headers); $headers = $this->processHeaders($headers);
$to = $this->validEmailAddr($to); $to = $this->validEmailAddr($to);
// Try it without the -f option if it fails // Try it without the -f option if it fails
if(!($result = @mail($to, $subject, $fullBody, $headers, "-f$bounceAddress"))) { if(!($result = @mail($to, $subject, $fullBody, $headers, "-f$bounceAddress"))) {
$result = mail($to, $subject, $fullBody, $headers); $result = mail($to, $subject, $fullBody, $headers);
}
return $result;
} }
return $result;
}
/** /**
* @todo Make visibility protected in 3.2 * @todo Make visibility protected in 3.2
*/ */
function encodeMultipart($parts, $contentType, $headers = false) { function encodeMultipart($parts, $contentType, $headers = false) {
$separator = "----=_NextPart_" . preg_replace('/[^0-9]/', '', rand() * 10000000000); $separator = "----=_NextPart_" . preg_replace('/[^0-9]/', '', rand() * 10000000000);
$headers["MIME-Version"] = "1.0"; $headers["MIME-Version"] = "1.0";
$headers["Content-Type"] = "$contentType; boundary=\"$separator\""; $headers["Content-Type"] = "$contentType; boundary=\"$separator\"";
$headers["Content-Transfer-Encoding"] = "7bit"; $headers["Content-Transfer-Encoding"] = "7bit";
if($contentType == "multipart/alternative") { if($contentType == "multipart/alternative") {
// $baseMessage = "This is an encoded HTML message. There are two parts: a plain text and an HTML message, // $baseMessage = "This is an encoded HTML message. There are two parts: a plain text and an HTML message,
// open whatever suits you better."; // open whatever suits you better.";
$baseMessage = "\nThis is a multi-part message in MIME format."; $baseMessage = "\nThis is a multi-part message in MIME format.";
} else { } else {
// $baseMessage = "This is a message containing attachments. The e-mail body is contained in the first // $baseMessage = "This is a message containing attachments. The e-mail body is contained in the first
// attachment"; // attachment";
$baseMessage = "\nThis is a multi-part message in MIME format."; $baseMessage = "\nThis is a multi-part message in MIME format.";
}
$separator = "\n--$separator\n";
$body = "$baseMessage\n" .
$separator . implode("\n".$separator, $parts) . "\n" . trim($separator) . "--";
return array($body, $headers);
} }
$separator = "\n--$separator\n";
$body = "$baseMessage\n" .
$separator . implode("\n".$separator, $parts) . "\n" . trim($separator) . "--";
return array($body, $headers);
}
/** /**
* @todo Make visibility protected in 3.2 * @todo Make visibility protected in 3.2
*/ */
function processHeaders($headers, $body = false) { function processHeaders($headers, $body = false) {
$res = ''; $res = '';
if(is_array($headers)) while(list($k, $v) = each($headers)) if(is_array($headers)) while(list($k, $v) = each($headers))
$res .= "$k: $v\n"; $res .= "$k: $v\n";
if($body) $res .= "\n$body"; if($body) $res .= "\n$body";
return $res; return $res;
} }
/** /**
* Encode the contents of a file for emailing, including headers * Encode the contents of a file for emailing, including headers
* *
* $file can be an array, in which case it expects these members: * $file can be an array, in which case it expects these members:
* 'filename' - the filename of the file * 'filename' - the filename of the file
* 'contents' - the raw binary contents of the file as a string * 'contents' - the raw binary contents of the file as a string
* and can optionally include these members: * and can optionally include these members:
* 'mimetype' - the mimetype of the file (calculated from filename if missing) * 'mimetype' - the mimetype of the file (calculated from filename if missing)
* 'contentLocation' - the 'Content-Location' header value for the file * 'contentLocation' - the 'Content-Location' header value for the file
* *
* $file can also be a string, in which case it is assumed to be the filename * $file can also be a string, in which case it is assumed to be the filename
* *
* h5. contentLocation * h5. contentLocation
* *
* Content Location is one of the two methods allowed for embedding images into an html email. * Content Location is one of the two methods allowed for embedding images into an html email.
* It's also the simplest, and best supported. * It's also the simplest, and best supported.
* *
* Assume we have an email with this in the body: * Assume we have an email with this in the body:
* *
* <img src="http://example.com/image.gif" /> * <img src="http://example.com/image.gif" />
* *
* To display the image, an email viewer would have to download the image from the web every time * To display the image, an email viewer would have to download the image from the web every time
* it is displayed. Due to privacy issues, most viewers will not display any images unless * it is displayed. Due to privacy issues, most viewers will not display any images unless
* the user clicks 'Show images in this email'. Not optimal. * the user clicks 'Show images in this email'. Not optimal.
* *
* However, we can also include a copy of this image as an attached file in the email. * However, we can also include a copy of this image as an attached file in the email.
* By giving it a contentLocation of "http://example.com/image.gif" most email viewers * By giving it a contentLocation of "http://example.com/image.gif" most email viewers
* will use this attached copy instead of downloading it. Better, * will use this attached copy instead of downloading it. Better,
* most viewers will show it without a 'Show images in this email' conformation. * most viewers will show it without a 'Show images in this email' conformation.
* *
* Here is an example of passing this information through Email.php: * Here is an example of passing this information through Email.php:
* *
* $email = new Email(); * $email = new Email();
* $email->attachments[] = array( * $email->attachments[] = array(
* 'filename' => BASE_PATH . "/themes/mytheme/images/header.gif", * 'filename' => BASE_PATH . "/themes/mytheme/images/header.gif",
* 'contents' => file_get_contents(BASE_PATH . "/themes/mytheme/images/header.gif"), * 'contents' => file_get_contents(BASE_PATH . "/themes/mytheme/images/header.gif"),
* 'mimetype' => 'image/gif', * 'mimetype' => 'image/gif',
* 'contentLocation' => Director::absoluteBaseURL() . "/themes/mytheme/images/header.gif" * 'contentLocation' => Director::absoluteBaseURL() . "/themes/mytheme/images/header.gif"
* ); * );
* *
* @todo Make visibility protected in 3.2 * @todo Make visibility protected in 3.2
*/ */
function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $extraHeaders = "") { function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $extraHeaders = "") {
if(!$file) { if(!$file) {
user_error("encodeFileForEmail: not passed a filename and/or data", E_USER_WARNING); user_error("encodeFileForEmail: not passed a filename and/or data", E_USER_WARNING);
return; return;
}
if (is_string($file)) {
$file = array('filename' => $file);
$fh = fopen($file['filename'], "rb");
if ($fh) {
while(!feof($fh)) $file['contents'] .= fread($fh, 10000);
fclose($fh);
}
}
// Build headers, including content type
if(!$destFileName) $base = basename($file['filename']);
else $base = $destFileName;
$mimeType = $file['mimetype'] ? $file['mimetype'] : HTTP::get_mime_type($file['filename']);
if(!$mimeType) $mimeType = "application/unknown";
if (empty($disposition)) $disposition = isset($file['contentLocation']) ? 'inline' : 'attachment';
// Encode for emailing
if (substr($file['mimetype'], 0, 4) != 'text') {
$encoding = "base64";
$file['contents'] = chunk_split(base64_encode($file['contents']));
} else {
// This mime type is needed, otherwise some clients will show it as an inline attachment
$mimeType = 'application/octet-stream';
$encoding = "quoted-printable";
$file['contents'] = $this->QuotedPrintable_encode($file['contents']);
}
$headers = "Content-type: $mimeType;\n\tname=\"$base\"\n".
"Content-Transfer-Encoding: $encoding\n".
"Content-Disposition: $disposition;\n\tfilename=\"$base\"\n" ;
if ( isset($file['contentLocation']) ) $headers .= 'Content-Location: ' . $file['contentLocation'] . "\n" ;
$headers .= $extraHeaders . "\n";
// Return completed packet
return $headers . $file['contents'];
} }
if (is_string($file)) {
$file = array('filename' => $file);
$fh = fopen($file['filename'], "rb");
if ($fh) {
while(!feof($fh)) $file['contents'] .= fread($fh, 10000);
fclose($fh);
}
}
// Build headers, including content type
if(!$destFileName) $base = basename($file['filename']);
else $base = $destFileName;
$mimeType = $file['mimetype'] ? $file['mimetype'] : HTTP::get_mime_type($file['filename']);
if(!$mimeType) $mimeType = "application/unknown";
if (empty($disposition)) $disposition = isset($file['contentLocation']) ? 'inline' : 'attachment';
// Encode for emailing
if (substr($file['mimetype'], 0, 4) != 'text') {
$encoding = "base64";
$file['contents'] = chunk_split(base64_encode($file['contents']));
} else {
// This mime type is needed, otherwise some clients will show it as an inline attachment
$mimeType = 'application/octet-stream';
$encoding = "quoted-printable";
$file['contents'] = $this->QuotedPrintable_encode($file['contents']);
}
$headers = "Content-type: $mimeType;\n\tname=\"$base\"\n".
"Content-Transfer-Encoding: $encoding\n".
"Content-Disposition: $disposition;\n\tfilename=\"$base\"\n";
if ( isset($file['contentLocation']) ) $headers .= 'Content-Location: ' . $file['contentLocation'] . "\n" ;
$headers .= $extraHeaders . "\n";
// Return completed packet
return $headers . $file['contents'];
}
/** /**
* @todo Make visibility protected in 3.2 * @todo Make visibility protected in 3.2
*/ */
function QuotedPrintable_encode($quotprint) { function QuotedPrintable_encode($quotprint) {
$quotprint = (string)str_replace('\r\n',chr(13).chr(10),$quotprint); $quotprint = (string)str_replace('\r\n',chr(13).chr(10),$quotprint);
$quotprint = (string)str_replace('\n', chr(13).chr(10),$quotprint); $quotprint = (string)str_replace('\n', chr(13).chr(10),$quotprint);
$quotprint = (string)preg_replace("~([\x01-\x1F\x3D\x7F-\xFF])~e", "sprintf('=%02X', ord('\\1'))", $quotprint); $quotprint = (string)preg_replace("~([\x01-\x1F\x3D\x7F-\xFF])~e", "sprintf('=%02X', ord('\\1'))", $quotprint);
@ -375,25 +375,25 @@ class Mailer {
$quotprint = (string)str_replace('=0D',"\n",$quotprint); $quotprint = (string)str_replace('=0D',"\n",$quotprint);
$quotprint = (string)str_replace('=0A',"\n",$quotprint); $quotprint = (string)str_replace('=0A',"\n",$quotprint);
return (string) $quotprint; return (string) $quotprint;
} }
/** /**
* @todo Make visibility protected in 3.2 * @todo Make visibility protected in 3.2
*/ */
function validEmailAddr($emailAddress) { function validEmailAddr($emailAddress) {
$emailAddress = trim($emailAddress); $emailAddress = trim($emailAddress);
$angBrack = strpos($emailAddress, '<'); $angBrack = strpos($emailAddress, '<');
if($angBrack === 0) {
$emailAddress = substr($emailAddress, 1, strpos($emailAddress,'>')-1);
if($angBrack === 0) { } else if($angBrack) {
$emailAddress = substr($emailAddress, 1, strpos($emailAddress,'>')-1); $emailAddress = str_replace('@', '', substr($emailAddress, 0, $angBrack))
.substr($emailAddress, $angBrack);
} else if($angBrack) {
$emailAddress = str_replace('@', '', substr($emailAddress, 0, $angBrack))
.substr($emailAddress, $angBrack);
}
return $emailAddress;
} }
return $emailAddress;
}
} }
/** /**

View File

@ -339,9 +339,9 @@ class CmsUiContext extends BehatContext
} }
assertGreaterThan(0, count($formFields), sprintf( assertGreaterThan(0, count($formFields), sprintf(
'Chosen.js dropdown named "%s" not found', 'Chosen.js dropdown named "%s" not found',
$field $field
)); ));
$containers = array(); $containers = array();
foreach($formFields as $formField) { foreach($formFields as $formField) {
@ -357,11 +357,11 @@ class CmsUiContext extends BehatContext
&& preg_match('/field/', $containerCandidate->getAttribute('class')) && preg_match('/field/', $containerCandidate->getAttribute('class'))
) { ) {
$containers[] = $containerCandidate; $containers[] = $containerCandidate;
}
} }
}
assertGreaterThan(0, count($containers), 'Chosen.js field container not found');
assertGreaterThan(0, count($containers), 'Chosen.js field container not found');
// Default to first visible container // Default to first visible container
$container = $containers[0]; $container = $containers[0];
@ -373,9 +373,9 @@ class CmsUiContext extends BehatContext
$listEl = $container->find('xpath', sprintf('.//li[contains(normalize-space(string(.)), \'%s\')]', $value)); $listEl = $container->find('xpath', sprintf('.//li[contains(normalize-space(string(.)), \'%s\')]', $value));
assertNotNull($listEl, sprintf( assertNotNull($listEl, sprintf(
'Chosen.js list element with title "%s" not found', 'Chosen.js list element with title "%s" not found',
$value $value
)); ));
// Dropdown flyout might be animated // Dropdown flyout might be animated
// $this->getSession()->wait(1000, 'jQuery(":animated").length == 0'); // $this->getSession()->wait(1000, 'jQuery(":animated").length == 0');

View File

@ -442,7 +442,7 @@ class InjectorTest extends SapphireTest {
public function testCustomObjectCreator() { public function testCustomObjectCreator() {
$injector = new Injector(); $injector = new Injector();
$injector->setObjectCreator(new SSObjectCreator()); $injector->setObjectCreator(new SSObjectCreator($injector));
$config = array( $config = array(
'OriginalRequirementsBackend', 'OriginalRequirementsBackend',
'DummyRequirements' => array( 'DummyRequirements' => array(
@ -485,9 +485,65 @@ class InjectorTest extends SapphireTest {
$again = $injector->get('NeedsBothCirculars'); $again = $injector->get('NeedsBothCirculars');
$this->assertEquals($again->var, 'One'); $this->assertEquals($again->var, 'One');
} }
public function testConvertServicePropertyOnCreate() {
// make sure convert service property is not called on direct calls to create, only on configured
// declarations to avoid un-needed function calls
$injector = new Injector();
$item = $injector->create('ConstructableObject', '%$TestObject');
$this->assertEquals('%$TestObject', $item->property);
// do it again but have test object configured as a constructor dependency
$injector = new Injector();
$config = array(
'ConstructableObject' => array(
'constructor' => array(
'%$TestObject'
)
)
);
$injector->load($config);
$item = $injector->get('ConstructableObject');
$this->assertTrue($item->property instanceof TestObject);
// and with a configured object defining TestObject to be something else!
$injector = new Injector(array('locator' => 'InjectorTestConfigLocator'));
$config = array(
'ConstructableObject' => array(
'constructor' => array(
'%$TestObject'
)
),
);
$injector->load($config);
$item = $injector->get('ConstructableObject');
$this->assertTrue($item->property instanceof ConstructableObject);
$this->assertInstanceOf('OtherTestObject', $item->property->property);
}
} }
class TestObject { class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly {
public function locateConfigFor($name) {
if ($name == 'TestObject') {
return array('class' => 'ConstructableObject', 'constructor' => array('%$OtherTestObject'));
}
return parent::locateConfigFor($name);
}
}
class ConstructableObject implements TestOnly {
public $property;
public function __construct($prop) {
$this->property = $prop;
}
}
class TestObject implements TestOnly {
public $sampleService; public $sampleService;
@ -497,7 +553,7 @@ class TestObject {
} }
class OtherTestObject { class OtherTestObject implements TestOnly {
private $sampleService; private $sampleService;
@ -511,13 +567,13 @@ class OtherTestObject {
} }
class CircularOne { class CircularOne implements TestOnly {
public $circularTwo; public $circularTwo;
} }
class CircularTwo { class CircularTwo implements TestOnly {
public $circularOne; public $circularOne;
@ -528,7 +584,7 @@ class CircularTwo {
} }
} }
class NeedsBothCirculars { class NeedsBothCirculars implements TestOnly{
public $circularOne; public $circularOne;
public $circularTwo; public $circularTwo;
@ -536,15 +592,15 @@ class NeedsBothCirculars {
} }
class MyParentClass { class MyParentClass implements TestOnly {
public $one; public $one;
} }
class MyChildClass extends MyParentClass { class MyChildClass extends MyParentClass implements TestOnly {
} }
class DummyRequirements { class DummyRequirements implements TestOnly {
public $backend; public $backend;
@ -558,15 +614,15 @@ class DummyRequirements {
} }
class OriginalRequirementsBackend { class OriginalRequirementsBackend implements TestOnly {
} }
class NewRequirementsBackend { class NewRequirementsBackend implements TestOnly {
} }
class TestStaticInjections { class TestStaticInjections implements TestOnly {
public $backend; public $backend;
static $dependencies = array( static $dependencies = array(
@ -582,13 +638,19 @@ class TestStaticInjections {
* @see https://github.com/silverstripe/sapphire * @see https://github.com/silverstripe/sapphire
*/ */
class SSObjectCreator extends InjectionCreator { class SSObjectCreator extends InjectionCreator {
private $injector;
public function __construct($injector) {
$this->injector = $injector;
}
public function create(Injector $injector, $class, $params = array()) { public function create($class, $params = array()) {
if (strpos($class, '(') === false) { if (strpos($class, '(') === false) {
return parent::create($injector, $class, $params); return parent::create($class, $params);
} else { } else {
list($class, $params) = self::parse_class_spec($class); list($class, $params) = self::parse_class_spec($class);
return parent::create($injector, $class, $params); $params = $this->injector->convertServiceProperty($params);
return parent::create($class, $params);
} }
} }