/** * Get the MIME type based on a file's extension. If the finfo class exists in PHP, and the file * exists relative to the project root, then use that extension, otherwise fallback to a list of * commonly known MIME types. * * @param string $filename * * @return string */ public static function get_mime_type($filename) { // If the finfo module is compiled into PHP, use it. $path = BASE_PATH . DIRECTORY_SEPARATOR . $filename; if (class_exists('finfo') && file_exists($path)) { $finfo = new finfo(FILEINFO_MIME_TYPE); return $finfo->file($path); } // Fallback to use the list from the HTTP.yml configuration and rely on the file extension // to get the file mime-type $ext = File::get_file_extension($filename); // Get the mime-types $mimeTypes = HTTP::config()->get('MimeTypes'); // The mime type doesn't exist if (!isset($mimeTypes[$ext])) { return 'application/unknown'; } return $mimeTypes[$ext]; }
/** * Returns a link to the previous page, if the first page is not currently * active. * * @return string */ public function PrevLink() { if ($this->NotFirstPage()) { return HTTP::setGetVar($this->getPaginationGetVar(), $this->getPageStart() - $this->getPageLength()); } }
/** * Return the attributes of the form tag - used by the templates. * * @param array $attrs Custom attributes to process. Falls back to {@link getAttributes()}. * If at least one argument is passed as a string, all arguments act as excludes by name. * * @return string HTML attributes, ready for insertion into an HTML tag */ public function getAttributesHTML($attrs = null) { $exclude = is_string($attrs) ? func_get_args() : null; // Figure out if we can cache this form // - forms with validation shouldn't be cached, cos their error messages won't be shown // - forms with security tokens shouldn't be cached because security tokens expire $needsCacheDisabled = false; if ($this->getSecurityToken()->isEnabled()) { $needsCacheDisabled = true; } if ($this->FormMethod() != 'GET') { $needsCacheDisabled = true; } if (!$this->validator instanceof RequiredFields || count($this->validator->getRequired())) { $needsCacheDisabled = true; } // If we need to disable cache, do it if ($needsCacheDisabled) { HTTP::set_cache_age(0); } $attrs = $this->getAttributes(); // Remove empty $attrs = array_filter((array) $attrs, create_function('$v', 'return ($v || $v === 0);')); // Remove excluded if ($exclude) { $attrs = array_diff_key($attrs, array_flip($exclude)); } // Prepare HTML-friendly 'method' attribute (lower-case) if (isset($attrs['method'])) { $attrs['method'] = strtolower($attrs['method']); } // Create markup $parts = array(); foreach ($attrs as $name => $value) { $parts[] = $value === true ? "{$name}=\"{$name}\"" : "{$name}=\"" . Convert::raw2att($value) . "\""; } return implode(' ', $parts); }
/** * Construct a new DataObject. * * @param array|null $record This will be null for a new database record. Alternatively, you can pass an array of * field values. Normally this constructor is only used by the internal systems that get objects from the database. * @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods. * Singletons don't have their defaults set. * @param DataModel $model * @param array $queryParams List of DataQuery params necessary to lazy load, or load related objects. */ public function __construct($record = null, $isSingleton = false, $model = null, $queryParams = array()) { parent::__construct(); // Set query params on the DataObject to tell the lazy loading mechanism the context the object creation context $this->setSourceQueryParams($queryParams); // Set the fields data. if (!$record) { $record = array('ID' => 0, 'ClassName' => static::class, 'RecordClassName' => static::class); } if (!is_array($record) && !is_a($record, "stdClass")) { if (is_object($record)) { $passed = "an object of type '" . get_class($record) . "'"; } else { $passed = "The value '{$record}'"; } user_error("DataObject::__construct passed {$passed}. It's supposed to be passed an array," . " taken straight from the database. Perhaps you should use DataList::create()->First(); instead?", E_USER_WARNING); $record = null; } if (is_a($record, "stdClass")) { $record = (array) $record; } // Set $this->record to $record, but ignore NULLs $this->record = array(); foreach ($record as $k => $v) { // Ensure that ID is stored as a number and not a string // To do: this kind of clean-up should be done on all numeric fields, in some relatively // performant manner if ($v !== null) { if ($k == 'ID' && is_numeric($v)) { $this->record[$k] = (int) $v; } else { $this->record[$k] = $v; } } } // Identify fields that should be lazy loaded, but only on existing records if (!empty($record['ID'])) { // Get all field specs scoped to class for later lazy loading $fields = static::getSchema()->fieldSpecs(static::class, DataObjectSchema::INCLUDE_CLASS | DataObjectSchema::DB_ONLY); foreach ($fields as $field => $fieldSpec) { $fieldClass = strtok($fieldSpec, "."); if (!array_key_exists($field, $record)) { $this->record[$field . '_Lazy'] = $fieldClass; } } } $this->original = $this->record; // Keep track of the modification date of all the data sourced to make this page // From this we create a Last-Modified HTTP header if (isset($record['LastEdited'])) { HTTP::register_modification_date($record['LastEdited']); } // this must be called before populateDefaults(), as field getters on a DataObject // may call getComponent() and others, which rely on $this->model being set. $this->model = $model ? $model : DataModel::inst(); // Must be called after parent constructor if (!$isSingleton && (!isset($this->record['ID']) || !$this->record['ID'])) { $this->populateDefaults(); } // prevent populateDefaults() and setField() from marking overwritten defaults as changed $this->changed = array(); }
/** * Change the password * * @param array $data The user submitted data * @return HTTPResponse */ public function doChangePassword(array $data) { if ($member = Member::currentUser()) { // The user was logged in, check the current password if (empty($data['OldPassword']) || !$member->checkPassword($data['OldPassword'])->valid()) { $this->clearMessage(); $this->sessionMessage(_t('Member.ERRORPASSWORDNOTMATCH', "Your current password does not match, please try again"), "bad"); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. return $this->controller->redirect($this->controller->Link('changepassword')); } } if (!$member) { if (Session::get('AutoLoginHash')) { $member = Member::member_from_autologinhash(Session::get('AutoLoginHash')); } // The user is not logged in and no valid auto login hash is available if (!$member) { Session::clear('AutoLoginHash'); return $this->controller->redirect($this->controller->Link('login')); } } // Check the new password if (empty($data['NewPassword1'])) { $this->clearMessage(); $this->sessionMessage(_t('Member.EMPTYNEWPASSWORD', "The new password can't be empty, please try again"), "bad"); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. return $this->controller->redirect($this->controller->Link('changepassword')); } else { if ($data['NewPassword1'] == $data['NewPassword2']) { $isValid = $member->changePassword($data['NewPassword1']); if ($isValid->valid()) { // Clear locked out status $member->LockedOutUntil = null; $member->FailedLoginCount = null; $member->write(); if ($member->canLogIn()->valid()) { $member->logIn(); } // TODO Add confirmation message to login redirect Session::clear('AutoLoginHash'); if (!empty($_REQUEST['BackURL']) && Director::is_site_url($_REQUEST['BackURL'])) { $url = Director::absoluteURL($_REQUEST['BackURL']); return $this->controller->redirect($url); } else { // Redirect to default location - the login form saying "You are logged in as..." $redirectURL = HTTP::setGetVar('BackURL', Director::absoluteBaseURL(), $this->controller->Link('login')); return $this->controller->redirect($redirectURL); } } else { $this->clearMessage(); $this->sessionMessage(_t('Member.INVALIDNEWPASSWORD', "We couldn't accept that password: {password}", array('password' => nl2br("\n" . Convert::raw2xml($isValid->starredList())))), "bad", false); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. return $this->controller->redirect($this->controller->Link('changepassword')); } } else { $this->clearMessage(); $this->sessionMessage(_t('Member.ERRORNEWPASSWORD', "You have entered your new password differently, try again"), "bad"); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. return $this->controller->redirect($this->controller->Link('changepassword')); } } }
/** * Construct an HTTPResponse that will deliver a file to the client. * Caution: Since it requires $fileData to be passed as binary data (no stream support), * it's only advisable to send small files through this method. * * @static * @param $fileData * @param $fileName * @param null $mimeType * @return HTTPResponse */ public static function send_file($fileData, $fileName, $mimeType = null) { if (!$mimeType) { $mimeType = HTTP::get_mime_type($fileName); } $response = new HTTPResponse($fileData); $response->addHeader("Content-Type", "{$mimeType}; name=\"" . addslashes($fileName) . "\""); // Note a IE-only fix that inspects this header in HTTP::add_cache_headers(). $response->addHeader("Content-Disposition", "attachment; filename=\"" . addslashes($fileName) . "\""); $response->addHeader("Content-Length", strlen($fileData)); return $response; }
public function testFilename2url() { $this->withBaseURL('http://www.silverstripe.org/', function ($test) { $frameworkTests = ltrim(FRAMEWORK_DIR . '/tests', '/'); $test->assertEquals("http://www.silverstripe.org/{$frameworkTests}/control/HTTPTest.php", HTTP::filename2url(__FILE__)); }); }
/** * Redirect back. Uses either the HTTP-Referer or a manually set request-variable called "BackURL". * This variable is needed in scenarios where HTTP-Referer is not sent (e.g when calling a page by * location.href in IE). If none of the two variables is available, it will redirect to the base * URL (see {@link Director::baseURL()}). * * @uses redirect() * * @return bool|HTTPResponse */ public function redirectBack() { // Don't cache the redirect back ever HTTP::set_cache_age(0); $url = null; // In edge-cases, this will be called outside of a handleRequest() context; in that case, // redirect to the homepage - don't break into the global state at this stage because we'll // be calling from a test context or something else where the global state is inappropraite if ($this->getRequest()) { if ($this->getRequest()->requestVar('BackURL')) { $url = $this->getRequest()->requestVar('BackURL'); } else { if ($this->getRequest()->isAjax() && $this->getRequest()->getHeader('X-Backurl')) { $url = $this->getRequest()->getHeader('X-Backurl'); } else { if ($this->getRequest()->getHeader('Referer')) { $url = $this->getRequest()->getHeader('Referer'); } } } } if (!$url) { $url = Director::baseURL(); } // absolute redirection URLs not located on this site may cause phishing if (Director::is_site_url($url)) { $url = Director::absoluteURL($url, true); return $this->redirect($url); } else { return false; } }
/** * Skip any further processing and immediately respond with a redirect to the passed URL. * * @param string $destURL */ protected static function force_redirect($destURL) { $response = new HTTPResponse(); $response->redirect($destURL, 301); HTTP::add_cache_headers($response); // TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block $response->output(); die; }
/** * Return the value of the field with relative links converted to absolute urls (with placeholders parsed). * @return string */ public function AbsoluteLinks() { return HTTP::absoluteURLs($this->forTemplate()); }
/** * Encode the contents of a file for emailing, including headers * * $file can be an array, in which case it expects these members: * 'filename' - the filename of the file * 'contents' - the raw binary contents of the file as a string * and can optionally include these members: * 'mimetype' - the mimetype of the file (calculated from filename if missing) * '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 * * h5. contentLocation * * Content Location is one of the two methods allowed for embedding images into an html email. * It's also the simplest, and best supported. * * Assume we have an email with this in the body: * * <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 * 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. * * 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 * will use this attached copy instead of downloading it. Better, * most viewers will show it without a 'Show images in this email' conformation. * * Here is an example of passing this information through Email.php: * * $email = new Email(); * $email->attachments[] = array( * 'filename' => BASE_PATH . "/themes/mytheme/images/header.gif", * 'contents' => file_get_contents(BASE_PATH . "/themes/mytheme/images/header.gif"), * 'mimetype' => 'image/gif', * 'contentLocation' => Director::absoluteBaseURL() . "/themes/mytheme/images/header.gif" * ); * * @param array|string $file * @param bool $destFileName * @param string $disposition * @param string $extraHeaders * @return string */ protected function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $extraHeaders = "") { if (!$file) { throw new InvalidArgumentException("Not passed a filename and/or data"); } if (is_string($file)) { $file = array('filename' => $file); $fh = fopen($file['filename'], "rb"); if ($fh) { $file['contents'] = ""; 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 = !empty($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($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'] = quoted_printable_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']; }
/** * Output the feed to the browser. * * TODO: Pass $response object to ->outputToBrowser() to loosen dependence on global state for easier testing/prototyping so dev can inject custom HTTPResponse instance. * * @return DBHTMLText */ public function outputToBrowser() { $prevState = SSViewer::config()->get('source_file_comments'); SSViewer::config()->update('source_file_comments', false); $response = Controller::curr()->getResponse(); if (is_int($this->lastModified)) { HTTP::register_modification_timestamp($this->lastModified); $response->addHeader("Last-Modified", gmdate("D, d M Y H:i:s", $this->lastModified) . ' GMT'); } if (!empty($this->etag)) { HTTP::register_etag($this->etag); } if (!headers_sent()) { HTTP::add_cache_headers(); $response->addHeader("Content-Type", "application/rss+xml; charset=utf-8"); } SSViewer::config()->update('source_file_comments', $prevState); return $this->renderWith($this->getTemplates()); }
/** * Load all the template variables into the internal variables, including * the template into body. Called before send() or debugSend() * $isPlain=true will cause the template to be ignored, otherwise the GenericEmail template will be used * and it won't be plain email :) * * @param bool $isPlain * @return $this */ protected function parseVariables($isPlain = false) { $origState = SSViewer::config()->get('source_file_comments'); SSViewer::config()->update('source_file_comments', false); if (!$this->parseVariables_done) { $this->parseVariables_done = true; // Parse $ variables in the base parameters $this->templateData(); // Process a .SS template file $fullBody = $this->body; if ($this->ss_template && !$isPlain) { // Requery data so that updated versions of To, From, Subject, etc are included $data = $this->templateData(); $candidateTemplates = [$this->ss_template, ['type' => 'email', $this->ss_template]]; $template = new SSViewer($candidateTemplates); if ($template->exists()) { $fullBody = $template->process($data); } } // Rewrite relative URLs $this->body = HTTP::absoluteURLs($fullBody); } SSViewer::config()->update('source_file_comments', $origState); return $this; }